From fe71b5a4702bb3e985fb72494eb39c40929fa3aa Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Sun, 12 May 2024 11:29:31 -0700 Subject: [PATCH 1/6] First draft version. 5 pixel bar graph, 6x40 images. --- Source/RunActivity/Content/BarGraph.png | Bin 0 -> 502 bytes .../Viewer3D/Popups/TrainForcesWindow.cs | 177 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 Source/RunActivity/Content/BarGraph.png create mode 100644 Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs diff --git a/Source/RunActivity/Content/BarGraph.png b/Source/RunActivity/Content/BarGraph.png new file mode 100644 index 0000000000000000000000000000000000000000..f40876e9ee5dd72914dc3b3e7df636b61d5334a0 GIT binary patch literal 502 zcmeAS@N?(olHy`uVBq!ia0vp^B|sd&!3HExEDvM{QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKn>lVE{-7;ac}P!`Wz=dI|Hzix+}sq?kHQ({bOp>Gk(-Z~vRl+*4)}v7m)#r%gNIWbL$?ujlvfn~yGi_K&I#cTw50YhjRg%0k(NSGltEC$^TggsuPZ z@2f@hsndtuH|^3^5{lZ}aP`r(d!mAzYyBd0S6_)0oLF*m)zzk~`=WxLd0%t?%r*Tt zD^K|Qws*^~&;G;L;9t2cjQ``Ua@Pg1iejrcj$FRtB-&WFBW8tC!yY!fD=ZdUPdbS* zt_dw!*u~&=a`mI`r47O9;;T3qG@9gB^F9*1DHv7S6R&>I$@YQ&`UscR(ppj ztzGab&P$tYAiXN2?aoN0Y4q zt_$S#fHo_vZ{#;+^vm58;L4CK8Vhu2(5F>Dk~AM&xd~Lb!H}nZxk5(7`HzLqfpNv) M>FVdQ&MBb@0M#_pp8x;= literal 0 HcmV?d00001 diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs new file mode 100644 index 0000000000..84581e1171 --- /dev/null +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -0,0 +1,177 @@ +// COPYRIGHT 2010, 2011, 2012, 2013, 2014, 2015 by the Open Rails project. +// +// This file is part of Open Rails. +// +// Open Rails is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Open Rails is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Open Rails. If not, see . + +// This file is the responsibility of the 3D & Environment Team. + +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Orts.Formats.Msts; +using Orts.Simulation.Physics; +using Orts.Simulation.RollingStocks; +using ORTS.Common; +using SharpDX.Direct2D1; +using SharpDX.MediaFoundation; +using System; +using System.Linq; + +namespace Orts.Viewer3D.Popups +{ + public class TrainForcesWindow : Window + { + const float ImpossibleHighForce = 9.999e8f; + + Train PlayerTrain; + int LastPlayerTrainCars; + bool LastPlayerLocomotiveFlippedState; + + float TrainLengthM = 0.0f; + float TrainMassKg = 0.0f; + float TrainPowerW = 0.0f; + float MinCouplerStrengthN = ImpossibleHighForce; + float MinDerailForceN = ImpossibleHighForce; + + Image[] RearCouplerBar; + static Texture2D BarTextures; + static Random Rnd = new Random(); // temporart, for testing + + public TrainForcesWindow(WindowManager owner) + : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 80, Window.DecorationSize.Y + owner.TextFontDefault.Height * 6, Viewer.Catalog.GetString("Train Forces")) + { + } + + protected internal override void Initialize() + { + base.Initialize(); + if (BarTextures == null) + { + BarTextures = SharedTextureManager.Get(Owner.Viewer.RenderProcess.GraphicsDevice, System.IO.Path.Combine(Owner.Viewer.ContentPath, "BarGraph.png")); + } + } + + protected override ControlLayout Layout(ControlLayout layout) + { + var textHeight = Owner.TextFontDefault.Height; + + var vbox = base.Layout(layout).AddLayoutVertical(); + var scrollbox = vbox.AddLayoutScrollboxHorizontal(vbox.RemainingHeight - textHeight); + if (PlayerTrain != null) + { + SetConsistProperties(PlayerTrain); + RearCouplerBar = new Image[PlayerTrain.Cars.Count]; + + int carPosition = 0; + foreach (var car in PlayerTrain.Cars) + { + scrollbox.Add(RearCouplerBar[carPosition] = new Image(6, 38)); + RearCouplerBar[carPosition].Texture = BarTextures; + var idx = CalcBarIndex(car.SmoothedCouplerForceUN, car.Flipped); + if (car.WagonType == TrainCar.WagonTypes.Engine) { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 0, 6, 40); } + else { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 40, 6, 40); } + + carPosition++; + } + var textbox = vbox.AddLayoutHorizontalLineOfText(); + textbox.Add(new Label(5 * textHeight, textHeight, Viewer.Catalog.GetString("Length:"), LabelAlignment.Right)); + textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatShortDistanceDisplay( TrainLengthM, false), LabelAlignment.Left)); + textbox.Add(new Label(5 * textHeight, textHeight, Viewer.Catalog.GetString("Weight:"), LabelAlignment.Right)); + textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeMass(TrainMassKg, false, false), LabelAlignment.Left)); + textbox.Add(new Label(5 * textHeight, textHeight, Viewer.Catalog.GetString("Power:"), LabelAlignment.Right)); + textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatPower(TrainPowerW, false, false, false), LabelAlignment.Left)); + textbox.Add(new Label(6 * textHeight, textHeight, Viewer.Catalog.GetString("Coupler Strength:"), LabelAlignment.Right)); + textbox.Add(new Label(6 * textHeight, textHeight, FormatStrings.FormatForce(MinCouplerStrengthN, false), LabelAlignment.Left)); + textbox.Add(new Label(6 * textHeight, textHeight, Viewer.Catalog.GetString("Derail Force:"), LabelAlignment.Right)); + textbox.Add(new Label(6 * textHeight, textHeight, FormatStrings.FormatForce(MinDerailForceN, false), LabelAlignment.Left)); + } + return vbox; + } + + public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) + { + base.PrepareFrame(elapsedTime, updateFull); + + if (updateFull) + { + if (PlayerTrain != Owner.Viewer.PlayerTrain || Owner.Viewer.PlayerTrain.Cars.Count != LastPlayerTrainCars || (Owner.Viewer.PlayerLocomotive != null && + LastPlayerLocomotiveFlippedState != Owner.Viewer.PlayerLocomotive.Flipped)) + { + PlayerTrain = Owner.Viewer.PlayerTrain; + LastPlayerTrainCars = Owner.Viewer.PlayerTrain.Cars.Count; + if (Owner.Viewer.PlayerLocomotive != null) LastPlayerLocomotiveFlippedState = Owner.Viewer.PlayerLocomotive.Flipped; + Layout(); + } + } + if (PlayerTrain != null) + { + int carPosition = 0; + foreach (var car in PlayerTrain.Cars) + { + var idx = CalcBarIndex(car.SmoothedCouplerForceUN, car.Flipped); + if (car.WagonType == TrainCar.WagonTypes.Engine) { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 1, 6, 38); } + else { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 41, 6, 38); } + carPosition++; + } + } + } + + protected void SetConsistProperties(Train theTrain) + { + float lengthM = 0.0f; + float massKg = 0.0f; + float powerW = 0.0f; + float minCouplerBreakN = ImpossibleHighForce; + float minDerailForceN = ImpossibleHighForce; + + foreach (var car in theTrain.Cars) + { + lengthM += car.CarLengthM; + massKg += car.MassKG; + if (car is MSTSWagon wag) + { + var couplerBreakForceN = wag.GetCouplerBreak2N() > 1.0f ? wag.GetCouplerBreak2N() : wag.GetCouplerBreak1N(); + if (couplerBreakForceN < minCouplerBreakN) { minCouplerBreakN = couplerBreakForceN; } + + // losely based on TrainCar.UpdateTrainRerailmentRisk + var numWheels = (wag.LocoNumDrvAxles + wag.GetWagonNumAxles()) * 2; + var derailForceN = (wag.MassKG / numWheels) * wag.GetGravitationalAccelerationMpS2(); + if (derailForceN < minDerailForceN) { minDerailForceN = derailForceN; } + } + if (car is MSTSLocomotive eng) { powerW += eng.MaxPowerW; } + } + TrainLengthM = lengthM; + TrainMassKg = massKg; + TrainPowerW = powerW; + MinCouplerStrengthN = minCouplerBreakN; + MinDerailForceN = minDerailForceN; + } + + protected int CalcBarIndex( float forceN, bool flipped) + { + var idx = 9; + var absForceN = Math.Abs(forceN); + if (absForceN > 1000f && MinCouplerStrengthN > 1000f) + { + var relForce = absForceN / MinCouplerStrengthN * 9f + 1f; + var log10Force = Math.Log10(relForce); + //if ((forceN < 0f) != flipped) { log10Force *= -1; } + if (forceN > 0f) { log10Force *= -1; } + idx = (int)(log10Force * 9f) + 9; + if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } + } + return idx; + } + } +} From 0a2b1d9de417f1c81cd37c55a27ce4001abc9f47 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Tue, 14 May 2024 13:33:40 -0700 Subject: [PATCH 2/6] Additional files for first version, and minor changes. --- Source/ORTS.Common/Input/UserCommand.cs | 1 + Source/ORTS.Settings/InputSettings.cs | 2 ++ .../Simulation/RollingStocks/TrainCar.cs | 4 ++++ Source/RunActivity/RunActivity.csproj | 3 +++ .../Viewer3D/Popups/TrainForcesWindow.cs | 20 ++++++++++--------- Source/RunActivity/Viewer3D/Viewer.cs | 3 +++ 6 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Source/ORTS.Common/Input/UserCommand.cs b/Source/ORTS.Common/Input/UserCommand.cs index 46292f58f2..3ddb159832 100644 --- a/Source/ORTS.Common/Input/UserCommand.cs +++ b/Source/ORTS.Common/Input/UserCommand.cs @@ -46,6 +46,7 @@ public enum UserCommand [GetString("Display Train Operations Window")] DisplayTrainOperationsWindow, [GetString("Display Train Dpu Window")] DisplayTrainDpuWindow, [GetString("Display Next Station Window")] DisplayNextStationWindow, + [GetString("Display Train Forces Window")] DisplayTrainForcesWindow, [GetString("Display Compass Window")] DisplayCompassWindow, [GetString("Display Train List Window")] DisplayTrainListWindow, [GetString("Display EOT List Window")] DisplayEOTListWindow, diff --git a/Source/ORTS.Settings/InputSettings.cs b/Source/ORTS.Settings/InputSettings.cs index 329415c1bc..f8d7fbe3f2 100644 --- a/Source/ORTS.Settings/InputSettings.cs +++ b/Source/ORTS.Settings/InputSettings.cs @@ -515,6 +515,8 @@ static void InitializeCommands(UserCommandInput[] Commands) Commands[(int)UserCommand.DisplayTrainOperationsWindow] = new UserCommandKeyInput(0x43); Commands[(int)UserCommand.DisplayTrainDpuWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Shift); Commands[(int)UserCommand.DisplayEOTListWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Control); +// Commands[(int)UserCommand.DisplayTrainForcesWindow] = new UserCommandKeyInput(0x57); + Commands[(int)UserCommand.DisplayTrainForcesWindow] = new UserCommandKeyInput(0x42, KeyModifiers.Alt); Commands[(int)UserCommand.GameAutopilotMode] = new UserCommandKeyInput(0x1E, KeyModifiers.Alt); Commands[(int)UserCommand.GameChangeCab] = new UserCommandKeyInput(0x12, KeyModifiers.Control); diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index 1dab36450b..905ec34ce2 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -3561,6 +3561,10 @@ public LatLonDirection GetLatLonDirection() return new LatLonDirection(latLon, directionDeg); ; } + + public int GetWagonNumAxles() { return WagonNumAxles; } + + public float GetGravitationalAccelerationMpS2() { return GravitationalAccelerationMpS2; } } public class WheelAxle : IComparer diff --git a/Source/RunActivity/RunActivity.csproj b/Source/RunActivity/RunActivity.csproj index ec2a90d36c..f75e36faee 100644 --- a/Source/RunActivity/RunActivity.csproj +++ b/Source/RunActivity/RunActivity.csproj @@ -41,6 +41,9 @@ Native\X64\OpenAL32.dll PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 84581e1171..a8b2e56e37 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -76,12 +76,9 @@ protected override ControlLayout Layout(ControlLayout layout) int carPosition = 0; foreach (var car in PlayerTrain.Cars) { - scrollbox.Add(RearCouplerBar[carPosition] = new Image(6, 38)); + scrollbox.Add(RearCouplerBar[carPosition] = new Image(6, 40)); RearCouplerBar[carPosition].Texture = BarTextures; - var idx = CalcBarIndex(car.SmoothedCouplerForceUN, car.Flipped); - if (car.WagonType == TrainCar.WagonTypes.Engine) { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 0, 6, 40); } - else { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 40, 6, 40); } - + UpdateCouplerImage(car, carPosition); carPosition++; } var textbox = vbox.AddLayoutHorizontalLineOfText(); @@ -114,14 +111,12 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) Layout(); } } - if (PlayerTrain != null) + else if (PlayerTrain != null) { int carPosition = 0; foreach (var car in PlayerTrain.Cars) { - var idx = CalcBarIndex(car.SmoothedCouplerForceUN, car.Flipped); - if (car.WagonType == TrainCar.WagonTypes.Engine) { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 1, 6, 38); } - else { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 41, 6, 38); } + UpdateCouplerImage(car, carPosition); carPosition++; } } @@ -158,6 +153,13 @@ protected void SetConsistProperties(Train theTrain) MinDerailForceN = minDerailForceN; } + protected void UpdateCouplerImage(TrainCar car, int carPosition) + { + var idx = CalcBarIndex(car.SmoothedCouplerForceUN, car.Flipped); + if (car.WagonType == TrainCar.WagonTypes.Engine) { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 0, 6, 40); } + else { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 40, 6, 40); } + } + protected int CalcBarIndex( float forceN, bool flipped) { var idx = 9; diff --git a/Source/RunActivity/Viewer3D/Viewer.cs b/Source/RunActivity/Viewer3D/Viewer.cs index 4e775e3969..53b3a1de18 100644 --- a/Source/RunActivity/Viewer3D/Viewer.cs +++ b/Source/RunActivity/Viewer3D/Viewer.cs @@ -96,6 +96,7 @@ public class Viewer public CarOperationsWindow CarOperationsWindow { get; private set; } // F9 sub-window for car operations public TrainDpuWindow TrainDpuWindow { get; private set; } // Shift + F9 train distributed power window public NextStationWindow NextStationWindow { get; private set; } // F10 window + public TrainForcesWindow TrainForcesWindow { get; private set; } // F11 window public CompassWindow CompassWindow { get; private set; } // 0 window public TracksDebugWindow TracksDebugWindow { get; private set; } // Control-Alt-F6 public SignallingDebugWindow SignallingDebugWindow { get; private set; } // Control-Alt-F11 window @@ -494,6 +495,7 @@ internal void Initialize() CarOperationsWindow = new CarOperationsWindow(WindowManager); TrainDpuWindow = new TrainDpuWindow(WindowManager); NextStationWindow = new NextStationWindow(WindowManager); + TrainForcesWindow = new TrainForcesWindow(WindowManager); CompassWindow = new CompassWindow(WindowManager); TracksDebugWindow = new TracksDebugWindow(WindowManager); SignallingDebugWindow = new SignallingDebugWindow(WindowManager); @@ -995,6 +997,7 @@ void HandleUserInput(ElapsedTime elapsedTime) if (UserInput.IsPressed(UserCommand.DisplayTrainDpuWindow)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) TrainDpuWindow.Visible = !TrainDpuWindow.Visible ; else TrainDpuWindow.TabAction(); if (UserInput.IsPressed(UserCommand.DisplayNextStationWindow)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) NextStationWindow.TabAction(); else NextStationWindow.Visible = !NextStationWindow.Visible; if (UserInput.IsPressed(UserCommand.DisplayCompassWindow)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) CompassWindow.TabAction(); else CompassWindow.Visible = !CompassWindow.Visible; + if (UserInput.IsPressed(UserCommand.DisplayTrainForcesWindow)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) TrainForcesWindow.TabAction(); else TrainForcesWindow.Visible = !TrainForcesWindow.Visible; if (UserInput.IsPressed(UserCommand.DebugTracks)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) TracksDebugWindow.TabAction(); else TracksDebugWindow.Visible = !TracksDebugWindow.Visible; if (UserInput.IsPressed(UserCommand.DebugSignalling)) if (UserInput.IsDown(UserCommand.DisplayNextWindowTab)) SignallingDebugWindow.TabAction(); else SignallingDebugWindow.Visible = !SignallingDebugWindow.Visible; if (UserInput.IsPressed(UserCommand.DisplayTrainListWindow)) TrainListWindow.Visible = !TrainListWindow.Visible; From ee684f8c49ae10fc5fce3e07c53e0f961e9743a2 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Tue, 14 May 2024 15:14:25 -0700 Subject: [PATCH 3/6] Remove derail force; it is not useful in this form. --- Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index a8b2e56e37..f0fbd8e127 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -42,7 +42,6 @@ public class TrainForcesWindow : Window float TrainMassKg = 0.0f; float TrainPowerW = 0.0f; float MinCouplerStrengthN = ImpossibleHighForce; - float MinDerailForceN = ImpossibleHighForce; Image[] RearCouplerBar; static Texture2D BarTextures; @@ -90,8 +89,6 @@ protected override ControlLayout Layout(ControlLayout layout) textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatPower(TrainPowerW, false, false, false), LabelAlignment.Left)); textbox.Add(new Label(6 * textHeight, textHeight, Viewer.Catalog.GetString("Coupler Strength:"), LabelAlignment.Right)); textbox.Add(new Label(6 * textHeight, textHeight, FormatStrings.FormatForce(MinCouplerStrengthN, false), LabelAlignment.Left)); - textbox.Add(new Label(6 * textHeight, textHeight, Viewer.Catalog.GetString("Derail Force:"), LabelAlignment.Right)); - textbox.Add(new Label(6 * textHeight, textHeight, FormatStrings.FormatForce(MinDerailForceN, false), LabelAlignment.Left)); } return vbox; } @@ -128,7 +125,6 @@ protected void SetConsistProperties(Train theTrain) float massKg = 0.0f; float powerW = 0.0f; float minCouplerBreakN = ImpossibleHighForce; - float minDerailForceN = ImpossibleHighForce; foreach (var car in theTrain.Cars) { @@ -142,7 +138,6 @@ protected void SetConsistProperties(Train theTrain) // losely based on TrainCar.UpdateTrainRerailmentRisk var numWheels = (wag.LocoNumDrvAxles + wag.GetWagonNumAxles()) * 2; var derailForceN = (wag.MassKG / numWheels) * wag.GetGravitationalAccelerationMpS2(); - if (derailForceN < minDerailForceN) { minDerailForceN = derailForceN; } } if (car is MSTSLocomotive eng) { powerW += eng.MaxPowerW; } } @@ -150,7 +145,6 @@ protected void SetConsistProperties(Train theTrain) TrainMassKg = massKg; TrainPowerW = powerW; MinCouplerStrengthN = minCouplerBreakN; - MinDerailForceN = minDerailForceN; } protected void UpdateCouplerImage(TrainCar car, int carPosition) From 108607cb58c167ac7304a323a95a18ba124068a4 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Tue, 21 May 2024 15:43:54 -0700 Subject: [PATCH 4/6] Push/Pull force good enough for now. More later. --- Source/ORTS.Common/Conversions.cs | 7 ++ .../Viewer3D/Popups/TrainForcesWindow.cs | 88 +++++++++---------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/Source/ORTS.Common/Conversions.cs b/Source/ORTS.Common/Conversions.cs index 70082788a6..8a4de89350 100644 --- a/Source/ORTS.Common/Conversions.cs +++ b/Source/ORTS.Common/Conversions.cs @@ -759,6 +759,13 @@ public static string FormatForce(float forceN, bool isMetric) return String.Format(CultureInfo.CurrentCulture, kilo ? "{0:F1} {1}" : "{0:F0} {1}", force, unit); } + public static string FormatLargeForce(float forceN, bool isMetric) + { + var force = isMetric ? forceN : N.ToLbf(forceN); + var unit = isMetric ? kN : klbf; + return String.Format(CultureInfo.CurrentCulture, "{0:F1} {1}", force * 1e-3f, unit); + } + public static string FormatTemperature(float temperatureC, bool isMetric, bool isDelta) { var temperature = isMetric ? temperatureC : isDelta ? C.ToDeltaF(temperatureC) : C.ToF(temperatureC); diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index f0fbd8e127..5a81059451 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -19,45 +19,41 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Orts.Formats.Msts; using Orts.Simulation.Physics; using Orts.Simulation.RollingStocks; using ORTS.Common; -using SharpDX.Direct2D1; -using SharpDX.MediaFoundation; using System; -using System.Linq; namespace Orts.Viewer3D.Popups { public class TrainForcesWindow : Window { - const float ImpossibleHighForce = 9.999e8f; + const float HighCouplerStrengthN = 2.2e6f; // 500 klbf + const float ImpossiblyHighForce = 9.999e8f; Train PlayerTrain; int LastPlayerTrainCars; bool LastPlayerLocomotiveFlippedState; - float TrainLengthM = 0.0f; - float TrainMassKg = 0.0f; - float TrainPowerW = 0.0f; - float MinCouplerStrengthN = ImpossibleHighForce; + float MaxCouplerStrengthN = 0.0f; + float MinCouplerStrengthN = ImpossiblyHighForce; - Image[] RearCouplerBar; - static Texture2D BarTextures; - static Random Rnd = new Random(); // temporart, for testing + Image[] CouplerForceBarGraph; + static Texture2D ForceBarTextures; + + Label MaxForceLabelValue; public TrainForcesWindow(WindowManager owner) - : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 80, Window.DecorationSize.Y + owner.TextFontDefault.Height * 6, Viewer.Catalog.GetString("Train Forces")) + : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 50, Window.DecorationSize.Y + owner.TextFontDefault.Height * 6, Viewer.Catalog.GetString("Train Forces")) { } protected internal override void Initialize() { base.Initialize(); - if (BarTextures == null) + if (ForceBarTextures == null) { - BarTextures = SharedTextureManager.Get(Owner.Viewer.RenderProcess.GraphicsDevice, System.IO.Path.Combine(Owner.Viewer.ContentPath, "BarGraph.png")); + ForceBarTextures = SharedTextureManager.Get(Owner.Viewer.RenderProcess.GraphicsDevice, System.IO.Path.Combine(Owner.Viewer.ContentPath, "BarGraph.png")); } } @@ -70,25 +66,24 @@ protected override ControlLayout Layout(ControlLayout layout) if (PlayerTrain != null) { SetConsistProperties(PlayerTrain); - RearCouplerBar = new Image[PlayerTrain.Cars.Count]; + CouplerForceBarGraph = new Image[PlayerTrain.Cars.Count]; int carPosition = 0; foreach (var car in PlayerTrain.Cars) { - scrollbox.Add(RearCouplerBar[carPosition] = new Image(6, 40)); - RearCouplerBar[carPosition].Texture = BarTextures; + scrollbox.Add(CouplerForceBarGraph[carPosition] = new Image(6, 40)); + CouplerForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateCouplerImage(car, carPosition); carPosition++; } + var textbox = vbox.AddLayoutHorizontalLineOfText(); - textbox.Add(new Label(5 * textHeight, textHeight, Viewer.Catalog.GetString("Length:"), LabelAlignment.Right)); - textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatShortDistanceDisplay( TrainLengthM, false), LabelAlignment.Left)); - textbox.Add(new Label(5 * textHeight, textHeight, Viewer.Catalog.GetString("Weight:"), LabelAlignment.Right)); - textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeMass(TrainMassKg, false, false), LabelAlignment.Left)); - textbox.Add(new Label(5 * textHeight, textHeight, Viewer.Catalog.GetString("Power:"), LabelAlignment.Right)); - textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatPower(TrainPowerW, false, false, false), LabelAlignment.Left)); - textbox.Add(new Label(6 * textHeight, textHeight, Viewer.Catalog.GetString("Coupler Strength:"), LabelAlignment.Right)); - textbox.Add(new Label(6 * textHeight, textHeight, FormatStrings.FormatForce(MinCouplerStrengthN, false), LabelAlignment.Left)); + textbox.Add(new Label(7 * textHeight, textHeight, Viewer.Catalog.GetString("Max Force:"), LabelAlignment.Right)); + textbox.Add(MaxForceLabelValue = new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textbox.Add(new Label(9 * textHeight, textHeight, Viewer.Catalog.GetString("Coupler Strength:"), LabelAlignment.Right)); + textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(MinCouplerStrengthN, false), LabelAlignment.Right)); + textbox.Add(new Label(1 * textHeight, textHeight, " - ", LabelAlignment.Center)); + textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(MaxCouplerStrengthN, false), LabelAlignment.Left)); } return vbox; } @@ -110,12 +105,20 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) } else if (PlayerTrain != null) { + var absMaxForceN = 0.0f; var forceSign = 1.0f; + int carPosition = 0; foreach (var car in PlayerTrain.Cars) { UpdateCouplerImage(car, carPosition); + + var forceN = car.CouplerForceU; var absForceN = Math.Abs(forceN); + if (absForceN > absMaxForceN) { absMaxForceN = absForceN; forceSign = forceN > 0 ? 1.0f : -1.0f; } + carPosition++; } + + if (MaxForceLabelValue != null) { MaxForceLabelValue.Text = FormatStrings.FormatLargeForce(absMaxForceN * forceSign, false); } } } @@ -124,7 +127,8 @@ protected void SetConsistProperties(Train theTrain) float lengthM = 0.0f; float massKg = 0.0f; float powerW = 0.0f; - float minCouplerBreakN = ImpossibleHighForce; + float maxCouplerBreakN = 0.0f; + float minCouplerBreakN = ImpossiblyHighForce; foreach (var car in theTrain.Cars) { @@ -134,37 +138,33 @@ protected void SetConsistProperties(Train theTrain) { var couplerBreakForceN = wag.GetCouplerBreak2N() > 1.0f ? wag.GetCouplerBreak2N() : wag.GetCouplerBreak1N(); if (couplerBreakForceN < minCouplerBreakN) { minCouplerBreakN = couplerBreakForceN; } - - // losely based on TrainCar.UpdateTrainRerailmentRisk - var numWheels = (wag.LocoNumDrvAxles + wag.GetWagonNumAxles()) * 2; - var derailForceN = (wag.MassKG / numWheels) * wag.GetGravitationalAccelerationMpS2(); + if (couplerBreakForceN > maxCouplerBreakN) { maxCouplerBreakN = couplerBreakForceN; } } if (car is MSTSLocomotive eng) { powerW += eng.MaxPowerW; } } - TrainLengthM = lengthM; - TrainMassKg = massKg; - TrainPowerW = powerW; - MinCouplerStrengthN = minCouplerBreakN; + MaxCouplerStrengthN = Math.Min( maxCouplerBreakN, HighCouplerStrengthN); + MinCouplerStrengthN = Math.Min(minCouplerBreakN, maxCouplerBreakN); } protected void UpdateCouplerImage(TrainCar car, int carPosition) { - var idx = CalcBarIndex(car.SmoothedCouplerForceUN, car.Flipped); - if (car.WagonType == TrainCar.WagonTypes.Engine) { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 0, 6, 40); } - else { RearCouplerBar[carPosition].Source = new Rectangle(1 + idx * 6, 40, 6, 40); } + var idx = CalcBarIndex(car.SmoothedCouplerForceUN); + if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * 6, 0, 6, 40); } + else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * 6, 40, 6, 40); } } - protected int CalcBarIndex( float forceN, bool flipped) + protected int CalcBarIndex( float forceN) { + // the image has 19 icons, 0 is max push, 9 is neutral, 18 is max pull var idx = 9; var absForceN = Math.Abs(forceN); if (absForceN > 1000f && MinCouplerStrengthN > 1000f) { - var relForce = absForceN / MinCouplerStrengthN * 9f + 1f; - var log10Force = Math.Log10(relForce); - //if ((forceN < 0f) != flipped) { log10Force *= -1; } - if (forceN > 0f) { log10Force *= -1; } - idx = (int)(log10Force * 9f) + 9; + // TODO: for push force, may need to scale differently (how?); containers derail at 300 klbf + var relForce = absForceN / MinCouplerStrengthN; + var expForce = Math.Pow(9, relForce); + idx = (int)Math.Floor(expForce); + idx = (forceN > 0f) ? idx * -1 + 9: idx + 9; // positive force is push if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } } return idx; From 5c0658bfe5daea561631fa1be9b7bf1bde76fc28 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Fri, 24 May 2024 16:53:16 -0700 Subject: [PATCH 5/6] Add slack graph. Not useful. --- .../Viewer3D/Popups/TrainForcesWindow.cs | 127 +++++++++++++++--- 1 file changed, 107 insertions(+), 20 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 5a81059451..ee80203e1b 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -17,12 +17,41 @@ // This file is the responsibility of the 3D & Environment Team. +// This is a prorotype to evaluate a train forces popup display. The +// intent is to provide real-time train-handling feeback of the forces +// within the train, particularly for long, heavy freight trains. The +// Forces HUD, display or browser, is hear to read. +// An alternative, better in the long-term, might be an external window +// that provides both in-train and over time feedback, as seen on +// professional training simulators. +// See the discussion in the Elvas tower forum, at: +// https://www.elvastower.com/forums/index.php?/topic/38056-proposal-for-train-forces-popup-display/ +// +// Force: +// Shows the pull or push force at each coupling, as a colored bar graph. +// The scale is determined by the weakest coupler in the train. The steps +// are logarithmic, to provide more sensitivity near the breaking point. +// +// Dearail Coefficient: +// Shows the derail coefficient (later force vs vertical force) as a +// colored bar graph. The value may exceed 1.0 (without the train +// derailing. The steps are logarithmic, to provide more sensitivity near +// the derailing point. +// +// Slack: +// Shows the slack, in or out, as a bar graph. Yet to be evaluated. +// +// Break Pipe Pressure: +// Not force related, but useful for the train handling. Shows how +// propagation relates to train forces. + using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Orts.Simulation.Physics; using Orts.Simulation.RollingStocks; using ORTS.Common; using System; +using System.Runtime.InteropServices; namespace Orts.Viewer3D.Popups { @@ -31,20 +60,29 @@ public class TrainForcesWindow : Window const float HighCouplerStrengthN = 2.2e6f; // 500 klbf const float ImpossiblyHighForce = 9.999e8f; + static Texture2D ForceBarTextures; + const int BarGraphHight = 40; + const int HalfBarGraphHight = 20; + const int BarGraphWidth = 6; + + Train PlayerTrain; int LastPlayerTrainCars; bool LastPlayerLocomotiveFlippedState; float MaxCouplerStrengthN = 0.0f; float MinCouplerStrengthN = ImpossiblyHighForce; + float CouplerStrengthScaleN; Image[] CouplerForceBarGraph; - static Texture2D ForceBarTextures; + Image[] DerailCoeffBarGraph; + Image[] SlackBarGraph; - Label MaxForceLabelValue; + Label MaxForceValueLabel; + Label MaxDerailCoeffValueLabel; public TrainForcesWindow(WindowManager owner) - : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 50, Window.DecorationSize.Y + owner.TextFontDefault.Height * 6, Viewer.Catalog.GetString("Train Forces")) + : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 50, Window.DecorationSize.Y + owner.TextFontDefault.Height * 12, Viewer.Catalog.GetString("Train Forces")) { } @@ -63,23 +101,45 @@ protected override ControlLayout Layout(ControlLayout layout) var vbox = base.Layout(layout).AddLayoutVertical(); var scrollbox = vbox.AddLayoutScrollboxHorizontal(vbox.RemainingHeight - textHeight); + var innerBox = scrollbox.AddLayoutVertical(scrollbox.RemainingHeight); + var forceBox = innerBox.AddLayoutHorizontal(scrollbox.RemainingHeight / 4); + forceBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, 5 * textHeight, BarGraphHight, Viewer.Catalog.GetString("Force:"))); + var derailCoeffBox = innerBox.AddLayoutHorizontal(scrollbox.RemainingHeight / 4); + derailCoeffBox.Add(new Label(0, (HalfBarGraphHight - textHeight) / 2, 5 * textHeight, HalfBarGraphHight, Viewer.Catalog.GetString("Derail:"))); + var slackBox = innerBox.AddLayoutHorizontal(scrollbox.RemainingHeight / 4); + slackBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, 5 * textHeight, BarGraphHight, Viewer.Catalog.GetString("Slack:"))); + if (PlayerTrain != null) { SetConsistProperties(PlayerTrain); + CouplerForceBarGraph = new Image[PlayerTrain.Cars.Count]; + DerailCoeffBarGraph = new Image[PlayerTrain.Cars.Count]; + SlackBarGraph = new Image[PlayerTrain.Cars.Count]; int carPosition = 0; foreach (var car in PlayerTrain.Cars) { - scrollbox.Add(CouplerForceBarGraph[carPosition] = new Image(6, 40)); + forceBox.Add(CouplerForceBarGraph[carPosition] = new Image(BarGraphWidth, BarGraphHight)); CouplerForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateCouplerImage(car, carPosition); + + derailCoeffBox.Add(DerailCoeffBarGraph[carPosition] = new Image(BarGraphWidth, HalfBarGraphHight)); + DerailCoeffBarGraph[carPosition].Texture = ForceBarTextures; + UpdateDerailCoeffImage(car, carPosition); + + slackBox.Add(SlackBarGraph[carPosition] = new Image(BarGraphWidth, BarGraphHight)); + SlackBarGraph[carPosition].Texture = ForceBarTextures; + UpdateSlackImage(car, carPosition); + carPosition++; } var textbox = vbox.AddLayoutHorizontalLineOfText(); textbox.Add(new Label(7 * textHeight, textHeight, Viewer.Catalog.GetString("Max Force:"), LabelAlignment.Right)); - textbox.Add(MaxForceLabelValue = new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textbox.Add(MaxForceValueLabel = new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textbox.Add(new Label(9 * textHeight, textHeight, Viewer.Catalog.GetString("Max Derail Coeff:"), LabelAlignment.Right)); + textbox.Add(MaxDerailCoeffValueLabel = new Label(5 * textHeight, textHeight, String.Format("{0:F0}%", 0f), LabelAlignment.Right)); textbox.Add(new Label(9 * textHeight, textHeight, Viewer.Catalog.GetString("Coupler Strength:"), LabelAlignment.Right)); textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(MinCouplerStrengthN, false), LabelAlignment.Right)); textbox.Add(new Label(1 * textHeight, textHeight, " - ", LabelAlignment.Center)); @@ -106,19 +166,24 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) else if (PlayerTrain != null) { var absMaxForceN = 0.0f; var forceSign = 1.0f; + var maxDerailCoeff = 0.0f; int carPosition = 0; foreach (var car in PlayerTrain.Cars) { UpdateCouplerImage(car, carPosition); + UpdateDerailCoeffImage(car, carPosition); + UpdateSlackImage(car, carPosition); var forceN = car.CouplerForceU; var absForceN = Math.Abs(forceN); if (absForceN > absMaxForceN) { absMaxForceN = absForceN; forceSign = forceN > 0 ? 1.0f : -1.0f; } + if (car.DerailmentCoefficient > maxDerailCoeff) { maxDerailCoeff = car.DerailmentCoefficient; } carPosition++; } - if (MaxForceLabelValue != null) { MaxForceLabelValue.Text = FormatStrings.FormatLargeForce(absMaxForceN * forceSign, false); } + if (MaxForceValueLabel != null) { MaxForceValueLabel.Text = FormatStrings.FormatLargeForce(absMaxForceN * forceSign, false); } + if (MaxDerailCoeffValueLabel != null) { MaxDerailCoeffValueLabel.Text = String.Format("{0:F0}%", maxDerailCoeff * 100f); } } } @@ -142,32 +207,54 @@ protected void SetConsistProperties(Train theTrain) } if (car is MSTSLocomotive eng) { powerW += eng.MaxPowerW; } } - MaxCouplerStrengthN = Math.Min( maxCouplerBreakN, HighCouplerStrengthN); - MinCouplerStrengthN = Math.Min(minCouplerBreakN, maxCouplerBreakN); + MaxCouplerStrengthN = maxCouplerBreakN; + MinCouplerStrengthN = minCouplerBreakN; + CouplerStrengthScaleN = Math.Min(minCouplerBreakN, HighCouplerStrengthN) * 1.05f; } protected void UpdateCouplerImage(TrainCar car, int carPosition) - { - var idx = CalcBarIndex(car.SmoothedCouplerForceUN); - if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * 6, 0, 6, 40); } - else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * 6, 40, 6, 40); } - } - - protected int CalcBarIndex( float forceN) { // the image has 19 icons, 0 is max push, 9 is neutral, 18 is max pull var idx = 9; - var absForceN = Math.Abs(forceN); - if (absForceN > 1000f && MinCouplerStrengthN > 1000f) + var absForceN = Math.Abs(car.SmoothedCouplerForceUN); + if (absForceN > 1000f && CouplerStrengthScaleN > 1000f) { // TODO: for push force, may need to scale differently (how?); containers derail at 300 klbf - var relForce = absForceN / MinCouplerStrengthN; + // TODO: may determine bar color to each car's coupler strength + var relForce = absForceN / CouplerStrengthScaleN; var expForce = Math.Pow(9, relForce); idx = (int)Math.Floor(expForce); - idx = (forceN > 0f) ? idx * -1 + 9: idx + 9; // positive force is push + idx = (car.SmoothedCouplerForceUN > 0f) ? idx * -1 + 9 : idx + 9; // positive force is push + if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } + } + if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, 0, BarGraphWidth, BarGraphHight); } + else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, BarGraphHight, BarGraphWidth, BarGraphHight); } + } + + protected void UpdateDerailCoeffImage(TrainCar car, int carPosition) + { + var expForce = Math.Pow(9, car.DerailmentCoefficient); + var idx = 8 + (int)Math.Floor(expForce); + //var idx = 9 + (int)Math.Floor(car.DerailmentCoefficient * 9f); + if (idx < 9) { idx = 9; } else if (idx > 18) { idx = 18; } + if (car.WagonType == TrainCar.WagonTypes.Engine) { DerailCoeffBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, 1, BarGraphWidth, HalfBarGraphHight); } + else { DerailCoeffBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, 1 + BarGraphHight, BarGraphWidth, HalfBarGraphHight); } + } + + protected void UpdateSlackImage(TrainCar car, int carPosition) + { + // There is a CouplerSlack2M, but it seems to be static. HUD only uses CouplerSlackM. + var idx = 9; // the image has 19 icons, 0 is max push, 9 is neutral, 18 is max pull + var maxSlack = Math.Max(car.GetMaximumSimpleCouplerSlack1M(), car.GetMaximumSimpleCouplerSlack2M()); + if (maxSlack > 0f) + { + var slack = car.CouplerSlackM; + var relSlack = slack / maxSlack * 9; // 9 bars + idx = 9 + (int)Math.Floor(relSlack); if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } } - return idx; + if (car.WagonType == TrainCar.WagonTypes.Engine) { SlackBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, 0, BarGraphWidth, BarGraphHight); } + else { SlackBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, BarGraphHight, BarGraphWidth, BarGraphHight); } } } } From 35a7465e1522f7407ca32f3336208429f995e8be Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Wed, 16 Oct 2024 13:02:49 -0700 Subject: [PATCH 6/6] work-state before summer break. Includes experimental components (eg. different graphs, timing, etc) that has not been fully tested / explored. --- .../RunActivity/Content/BarGraph-bar6wide.png | Bin 0 -> 552 bytes Source/RunActivity/Content/BarGraph-v1.png | Bin 0 -> 502 bytes .../RunActivity/Content/BarGraph-withBG.png | Bin 0 -> 522 bytes .../Viewer3D/Popups/TrainForcesWindow.cs | 85 +++++++++++++++--- 4 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 Source/RunActivity/Content/BarGraph-bar6wide.png create mode 100644 Source/RunActivity/Content/BarGraph-v1.png create mode 100644 Source/RunActivity/Content/BarGraph-withBG.png diff --git a/Source/RunActivity/Content/BarGraph-bar6wide.png b/Source/RunActivity/Content/BarGraph-bar6wide.png new file mode 100644 index 0000000000000000000000000000000000000000..a02d1220464d3d2f579a9cc352809f98d11c5da8 GIT binary patch literal 552 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$SgAGWQF8Y}dq!^2X+?^QKos)S9a~60+7BevL9R^{>vhqxCLy-@oew78hb?B!7eo`y6VT=-(rF%G>$U4vD~Yc-=Cp*#BsXs$KeG9GLu6 zM*%48{4CCr+oNih-R5cco>`T3bJSk`!W9*@Y325g3+BG_S1jGSst_zFsH=PF4p4CW zr7vIIe!l8YJe(We7JcN^pZNj`>y5VR+Wt9L+s%kd%49M$ACzK<1Rt zk7*JLOI<;nO{+l8E}OdalVY2L7rVHD5-{Gdg9G(z!}c%D&xknAUA+{f#M9N!Wt~$( F69A2bNn{1`ISV`@iy0XB4ude`@%$AjKn>lVE{-7;ac}P!`Wz=dI|Hzix+}sq?kHQ({bOp>Gk(-Z~vRl+*4)}v7m)#r%gNIWbL$?ujlvfn~yGi_K&I#cTw50YhjRg%0k(NSGltEC$^TggsuPZ z@2f@hsndtuH|^3^5{lZ}aP`r(d!mAzYyBd0S6_)0oLF*m)zzk~`=WxLd0%t?%r*Tt zD^K|Qws*^~&;G;L;9t2cjQ``Ua@Pg1iejrcj$FRtB-&WFBW8tC!yY!fD=ZdUPdbS* zt_dw!*u~&=a`mI`r47O9;;T3qG@9gB^F9*1DHv7S6R&>I$@YQ&`UscR(ppj ztzGab&P$tYAiXN2?aoN0Y4q zt_$S#fHo_vZ{#;+^vm58;L4CK8Vhu2(5F>Dk~AM&xd~Lb!H}nZxk5(7`HzLqfpNv) M>FVdQ&MBb@0M#_pp8x;= literal 0 HcmV?d00001 diff --git a/Source/RunActivity/Content/BarGraph-withBG.png b/Source/RunActivity/Content/BarGraph-withBG.png new file mode 100644 index 0000000000000000000000000000000000000000..c787fccb0d4520a30bd65b0d6e9990c74cbf9a3d GIT binary patch literal 522 zcmV+l0`>igP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0iQ`kK~#8N?c2>s z1VIo$;nf5L!Gj0AX$S^kAO>Y124M(#(}M>=FoF?wYGq-6XnOk1PItfWVp+%HF)Yq{ zHVnf`43AsP&&^#-YnPwz9v9?sGwpV2xwG=PX{@^y23u^qhT$L#s%LO%cT&}^qjpc$WJ}y^Wsg31=;<(Rm-{bG2 zr~4~6|FSoh3yR}%&z(}6n5tsApoNwjQ{^ldw1{$JYL4ZCPEc-49k5)`=D65T!TF6ad1#?%4J1#OOt zy+?|nOO^{NU9PrRE@)c0I?8fEaom(|o5rMMmJ2#qZcLiMaxtsbANQjLo absMaxForceN) { absMaxForceN = absForceN; forceSign = forceN > 0 ? 1.0f : -1.0f; } + if (absForceN > absMaxForceN) { absMaxForceN = absForceN; forceSign = forceN > 0 ? 1.0f : -1.0f; maxForceCarNum = carPosition + 1; } + var impulseN = car.ImpulseCouplerForceUN; var absImpulseN = Math.Abs(impulseN); + if (absImpulseN > absMaxImpulseN) { absMaxImpulseN = absImpulseN; impulseSign = impulseN > 0 ? 1.0f : -1.0f; maxImpulseCarNum = carPosition + 1; } if (car.DerailmentCoefficient > maxDerailCoeff) { maxDerailCoeff = car.DerailmentCoefficient; } carPosition++; } - if (MaxForceValueLabel != null) { MaxForceValueLabel.Text = FormatStrings.FormatLargeForce(absMaxForceN * forceSign, false); } + if (MaxForceValueLabel != null) { MaxForceValueLabel.Text = FormatStrings.FormatLargeForce(absMaxForceN * forceSign, false) + string.Format(" ({0})", maxForceCarNum); } + if (MaxImpulseValueLabel != null) + { + if (absMaxImpulseN > PreviousMaxImpulseValue) + { + MaxImpulseValueLabel.Text = FormatStrings.FormatLargeForce(absMaxImpulseN * impulseSign, false) + string.Format(" ({0})", maxImpulseCarNum); + PreviousMaxImpulseValue = absMaxImpulseN; + PreviousMaxImpulseTime = SimSeconds; + } + else if (absMaxImpulseN < PreviousMaxImpulseValue && SimSeconds > (PreviousMaxImpulseTime + 1f)) + { + MaxImpulseValueLabel.Text = FormatStrings.FormatLargeForce(absMaxImpulseN * impulseSign, false) + string.Format(" ({0})", maxImpulseCarNum); + PreviousMaxImpulseValue = absMaxImpulseN; + PreviousMaxImpulseTime = SimSeconds; + } + } if (MaxDerailCoeffValueLabel != null) { MaxDerailCoeffValueLabel.Text = String.Format("{0:F0}%", maxDerailCoeff * 100f); } } } @@ -212,7 +254,7 @@ protected void SetConsistProperties(Train theTrain) CouplerStrengthScaleN = Math.Min(minCouplerBreakN, HighCouplerStrengthN) * 1.05f; } - protected void UpdateCouplerImage(TrainCar car, int carPosition) + protected void UpdateCouplerForceImage(TrainCar car, int carPosition) { // the image has 19 icons, 0 is max push, 9 is neutral, 18 is max pull var idx = 9; @@ -231,6 +273,25 @@ protected void UpdateCouplerImage(TrainCar car, int carPosition) else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, BarGraphHight, BarGraphWidth, BarGraphHight); } } + protected void UpdateCouplerImpulseImage(TrainCar car, int carPosition) + { + // the image has 19 icons, 0 is max push, 9 is neutral, 18 is max pull + var idx = 9; + var absImpulseN = Math.Abs(car.ImpulseCouplerForceUN); + if (absImpulseN > 1000f && CouplerStrengthScaleN > 1000f) + { + // TODO: for push force, may need to scale differently (how?); containers derail at 300 klbf + // TODO: may determine bar color to each car's coupler strength + var relImpulse = absImpulseN / CouplerStrengthScaleN; + var expImpulse = Math.Pow(9, relImpulse); + idx = (int)Math.Floor(expImpulse); + idx = (car.ImpulseCouplerForceUN > 0f) ? idx * -1 + 9 : idx + 9; // positive force is push + if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } + } + if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerImpulseBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, 0, BarGraphWidth, BarGraphHight); } + else { CouplerImpulseBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, BarGraphHight, BarGraphWidth, BarGraphHight); } + } + protected void UpdateDerailCoeffImage(TrainCar car, int carPosition) { var expForce = Math.Pow(9, car.DerailmentCoefficient);