From fe71b5a4702bb3e985fb72494eb39c40929fa3aa Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Sun, 12 May 2024 11:29:31 -0700 Subject: [PATCH 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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 06/15] 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); From b1595de2e14ebddddd93d53782ea1f0764bbccee Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Fri, 21 Feb 2025 20:32:44 -0800 Subject: [PATCH 07/15] More work. Settled on longitudinal and lateral forces. Scroll works. --- .../Viewer3D/Popups/TrainForcesWindow.cs | 341 ++++++++++-------- 1 file changed, 200 insertions(+), 141 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 31316aa2b3..9d7a22baee 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -17,33 +17,53 @@ // This file is the responsibility of the 3D & Environment Team. -// This is a prorotype to evaluate a train forces popup display. The +#region Design Notes +// This is a prototype 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. +// Forces HUD, display or browser, is hard 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: +// professional train 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. +// Longitudinal Force: +// Shows the length-wise pull or push force at each coupling, as a colored bar graph. Up +// (positive) is pull, down (negative) is push. The scale is determined by the weakest +// coupler in the train. The steps are non-linear, 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. +// Lateral Force: +// Shows the sideway push or pull at the wheels as a colored bar graph. Up (positive) is +// pull to the inside (stringline), down (negative) is push to the outside (jackknife). +// The scale is determined by the lowest axle-load (vertical force). The steps are +// non-linear, to provide more sensitivity near the derailing point. +// +// Bar Graph for Force: +// +/- 9 bars; 4 green, 3 orange, 2 red +// blue middle-bar is an engine, white is a car // // Slack: -// Shows the slack, in or out, as a bar graph. Yet to be evaluated. +// Was considered, but is sufficiently reflected by the lateral force display. +// +// Break Pipe Pressure or Brake Force: +// Was considered. It is not really a train-handling parameter. +// +// Grade & Curvature: +// It is not practical to show grade or curvature in a meaningfule way +// without needing significant screen-space and calculations. Thus they +// are not included. // -// Break Pipe Pressure: -// Not force related, but useful for the train handling. Shows how -// propagation relates to train forces. +// Notes: +// * Design was copied from the old (horizontal) train operations window. +// * The derail coefficient was considered, but the lateral force provides a more uniform +// view across the train. +// * Using text-hight for field-width, as text width is variable. +// * Lateral Forces and Derailment: +// - As of Feb 2025, lateral forces are not calculated on straight track. +// - As of Feb 2025, longitudinal buff forces may also cause coupler breaks. +#endregion using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -52,6 +72,9 @@ using ORTS.Common; using System.Diagnostics; using System; +using System.Collections.Generic; +using System.Xml; +using SharpDX.Direct2D1.Effects; namespace Orts.Viewer3D.Popups { @@ -63,7 +86,7 @@ public class TrainForcesWindow : Window static Texture2D ForceBarTextures; const int BarGraphHight = 40; const int HalfBarGraphHight = 20; - const int BarGraphWidth = 6; + const int BarWidth = 6; float SimSeconds = 0f; float LastLogSeconds = 0f; @@ -73,24 +96,34 @@ public class TrainForcesWindow : Window int LastPlayerTrainCars; bool LastPlayerLocomotiveFlippedState; - float MaxCouplerStrengthN = 0.0f; float MinCouplerStrengthN = ImpossiblyHighForce; float CouplerStrengthScaleN; + float MinDerailForceN = ImpossiblyHighForce; + float DerailForceScaleN; + Image[] CouplerForceBarGraph; - Image[] CouplerImpulseBarGraph; - Image[] DerailCoeffBarGraph; - Image[] SlackBarGraph; + Image[] WheelForceBarGraph; + + Label MaxLongForceForTextBox; + Label MaxLatForceForTextBox; - Label MaxForceValueLabel; - Label MaxImpulseValueLabel; float PreviousMaxImpulseValue = 0f; float PreviousMaxImpulseTime = 0f; - Label MaxDerailCoeffValueLabel; + float LastAbsForceN = 0f; + float NextLowerTime = 0f; + /// + /// Constructor. Window is wide enough for about 150 cars. Longer trains + /// have a scrollbar. This seems a reasonable compromise between typical + /// display size and typical train length. + /// public TrainForcesWindow(WindowManager owner) - : base(owner, Window.DecorationSize.X + owner.TextFontDefault.Height * 60, Window.DecorationSize.Y + owner.TextFontDefault.Height * 13, Viewer.Catalog.GetString("Train Forces")) + : base(owner, Window.DecorationSize.X + 1000, Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarGraphHight * 2 + 20, Viewer.Catalog.GetString("Train Forces")) { } + /// + /// Initialize display. Loads static data, such as the bar graph images. + /// protected internal override void Initialize() { base.Initialize(); @@ -100,68 +133,67 @@ protected internal override void Initialize() } } + /// + /// Create the layout. Defines the components within the window. + /// protected override ControlLayout Layout(ControlLayout layout) { var textHeight = Owner.TextFontDefault.Height; - - var vbox = base.Layout(layout).AddLayoutVertical(); - var scrollbox = vbox.AddLayoutScrollboxHorizontal(vbox.RemainingHeight - textHeight); - var innerBox = scrollbox.AddLayoutVertical(scrollbox.RemainingHeight); - var forceBox = innerBox.AddLayoutHorizontal(BarGraphHight + 4); - forceBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, 5 * textHeight, BarGraphHight, Viewer.Catalog.GetString("Force:"))); - var impulseBox = innerBox.AddLayoutHorizontal(BarGraphHight + 4); - impulseBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, 5 * textHeight, BarGraphHight, Viewer.Catalog.GetString("Impulse:"))); - var derailCoeffBox = innerBox.AddLayoutHorizontal(HalfBarGraphHight + 4); - derailCoeffBox.Add(new Label(0, (HalfBarGraphHight - textHeight) / 2, 5 * textHeight, HalfBarGraphHight, Viewer.Catalog.GetString("Derail:"))); - var slackBox = innerBox.AddLayoutHorizontal(BarGraphHight + 4); - slackBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, 5 * textHeight, BarGraphHight, Viewer.Catalog.GetString("Slack:"))); + var labelWidth = textHeight * 6; + int numBars = 60; // enough to show the important part of the text line + if (PlayerTrain != null && PlayerTrain.Cars != null && PlayerTrain.Cars.Count > numBars) { numBars = PlayerTrain.Cars.Count; } + var innerBoxWidth = labelWidth + BarWidth * numBars + 4; + + var hbox = base.Layout(layout).AddLayoutHorizontal(); + var scrollbox = hbox.AddLayoutScrollboxHorizontal(hbox.RemainingHeight); + var vbox = scrollbox.AddLayoutVertical(Math.Max(innerBoxWidth,scrollbox.RemainingWidth)); + var longForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); + longForceBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, labelWidth, BarGraphHight, Viewer.Catalog.GetString("Longitudinal") + ": ")); + var latForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); + latForceBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, labelWidth, BarGraphHight, Viewer.Catalog.GetString("Lateral") + ": ")); if (PlayerTrain != null) { SetConsistProperties(PlayerTrain); CouplerForceBarGraph = new Image[PlayerTrain.Cars.Count]; - CouplerImpulseBarGraph = new Image[PlayerTrain.Cars.Count]; - DerailCoeffBarGraph = new Image[PlayerTrain.Cars.Count]; - SlackBarGraph = new Image[PlayerTrain.Cars.Count]; + WheelForceBarGraph = new Image[PlayerTrain.Cars.Count]; int carPosition = 0; foreach (var car in PlayerTrain.Cars) { - forceBox.Add(CouplerForceBarGraph[carPosition] = new Image(BarGraphWidth, BarGraphHight)); + longForceBox.Add(CouplerForceBarGraph[carPosition] = new Image(BarWidth, BarGraphHight)); CouplerForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateCouplerForceImage(car, carPosition); - impulseBox.Add(CouplerImpulseBarGraph[carPosition] = new Image(BarGraphWidth, BarGraphHight)); - CouplerImpulseBarGraph[carPosition].Texture = ForceBarTextures; - UpdateCouplerImpulseImage(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); + latForceBox.Add(WheelForceBarGraph[carPosition] = new Image(BarWidth, BarGraphHight)); + WheelForceBarGraph[carPosition].Texture = ForceBarTextures; + UpdateWheelForceImage(car, carPosition); carPosition++; } + vbox.AddHorizontalSeparator(); var textbox = vbox.AddLayoutHorizontalLineOfText(); - textbox.Add(new Label(7 * textHeight, textHeight, Viewer.Catalog.GetString("Max Force:"), LabelAlignment.Right)); - textbox.Add(MaxForceValueLabel = new Label(6 * textHeight, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); - textbox.Add(new Label(7 * textHeight, textHeight, Viewer.Catalog.GetString("Max Impulse:"), LabelAlignment.Right)); - textbox.Add(MaxImpulseValueLabel = new Label(6 * 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)); - textbox.Add(new Label(5 * textHeight, textHeight, FormatStrings.FormatLargeForce(MaxCouplerStrengthN, false), LabelAlignment.Left)); + textbox.Add(new Label(textHeight * 9, textHeight, Viewer.Catalog.GetString("Max Long Force") + ": ", LabelAlignment.Right)); + textbox.Add(MaxLongForceForTextBox = new Label(textHeight * 7, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textbox.Add(new Label(textHeight * 9, textHeight, Viewer.Catalog.GetString("Max Lat Force") + ": ", LabelAlignment.Right)); + textbox.Add(MaxLatForceForTextBox = new Label(textHeight * 7, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + + textbox.Add(new Label(textHeight * 8, textHeight, Viewer.Catalog.GetString("Min Coupler") + ": ", LabelAlignment.Right)); + textbox.Add(new Label(textHeight * 5, textHeight, FormatStrings.FormatLargeForce(MinCouplerStrengthN, false), LabelAlignment.Right)); + textbox.Add(new Label(textHeight * 8, textHeight, Viewer.Catalog.GetString("Min Derail") + ": ", LabelAlignment.Right)); + textbox.Add(new Label(textHeight * 5, textHeight, FormatStrings.FormatLargeForce(MinDerailForceN, false), LabelAlignment.Right)); + + LastAbsForceN = 0f; } - return vbox; + + return hbox; } + /// + /// Prepare frame for rendering. Update the data (graphs and values in text box). + /// public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) { base.PrepareFrame(elapsedTime, updateFull); @@ -188,134 +220,161 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) } else if (PlayerTrain != null) { - var absMaxForceN = 0.0f; var forceSign = 1.0f; var maxForceCarNum = 0; - var absMaxImpulseN = 0.0f; var impulseSign = 1.0f; var maxImpulseCarNum = 0; - var maxDerailCoeff = 0.0f; + var absMaxLongForceN = 0.0f; var longForceSign = 1.0f; var maxLongForceCarNum = 0; + var absMaxLatForceN = 0.0f; var latForceSign = 1.0f; var maxLatForceCarNum = 0; int carPosition = 0; foreach (var car in PlayerTrain.Cars) { UpdateCouplerForceImage(car, carPosition); - UpdateCouplerImpulseImage(car, carPosition); - UpdateDerailCoeffImage(car, carPosition); - UpdateSlackImage(car, carPosition); + UpdateWheelForceImage(car, carPosition); + + var longForceN = car.CouplerForceU; var absLongForceN = Math.Abs(longForceN); + if (absLongForceN > absMaxLongForceN) { absMaxLongForceN = absLongForceN; longForceSign = longForceN > 0 ? -1.0f : 1.0f; maxLongForceCarNum = carPosition + 1; } - var forceN = car.CouplerForceU; var absForceN = Math.Abs(forceN); - 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; } + // see TrainCar.UpdateTrainDerailmentRisk() + var absLatForceN = car.TotalWagonLateralDerailForceN; + if (car.WagonNumBogies <= 0 || car.GetWagonNumAxles() <= 0) { absLatForceN = car.DerailmentCoefficient * DerailForceScaleN; } + if (absLatForceN > absMaxLatForceN) { absMaxLatForceN = absLatForceN; latForceSign = (car.CouplerForceU > 0 && car.CouplerSlackM < 0) ? -1.0f : 1.0f; maxLatForceCarNum = carPosition + 1; } carPosition++; } - if (MaxForceValueLabel != null) { MaxForceValueLabel.Text = FormatStrings.FormatLargeForce(absMaxForceN * forceSign, false) + string.Format(" ({0})", maxForceCarNum); } - if (MaxImpulseValueLabel != null) + // update max coupler force; TODO: smooth the downslope + if (MaxLongForceForTextBox != 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; - } + MaxLongForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxLongForceN * longForceSign, false) + string.Format(" ({0})", maxLongForceCarNum); + } + + // update max derail force + if (MaxLatForceForTextBox != null) + { + MaxLatForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxLatForceN * latForceSign, false) + string.Format(" ({0})", maxLatForceCarNum); } - if (MaxDerailCoeffValueLabel != null) { MaxDerailCoeffValueLabel.Text = String.Format("{0:F0}%", maxDerailCoeff * 100f); } } } + /// + /// Get static force values from consist, such as coupler strength and + /// force that causes the wheel to derail. + /// protected void SetConsistProperties(Train theTrain) { - float lengthM = 0.0f; - float massKg = 0.0f; - float powerW = 0.0f; - float maxCouplerBreakN = 0.0f; float minCouplerBreakN = ImpossiblyHighForce; + float minDerailForceN = ImpossiblyHighForce; 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; } - if (couplerBreakForceN > maxCouplerBreakN) { maxCouplerBreakN = couplerBreakForceN; } + + // simplified from TrainCar.UpdateTrainDerailmentRisk() + var numWheels = wag.GetWagonNumAxles() * 2; + if (numWheels <= 0) { numWheels = 4; } // err towards higher vertical force + var wheelDerailForceN = wag.MassKG / numWheels * wag.GetGravitationalAccelerationMpS2(); + if (wheelDerailForceN > 1000f) // exclude improbable vales + { + if (wheelDerailForceN < minDerailForceN) { minDerailForceN = wheelDerailForceN; } + } } - if (car is MSTSLocomotive eng) { powerW += eng.MaxPowerW; } } - MaxCouplerStrengthN = maxCouplerBreakN; MinCouplerStrengthN = minCouplerBreakN; CouplerStrengthScaleN = Math.Min(minCouplerBreakN, HighCouplerStrengthN) * 1.05f; + + MinDerailForceN = minDerailForceN; + DerailForceScaleN = Math.Min(minDerailForceN, HighCouplerStrengthN) * 1.05f; } + /// + /// Update the coupler force (longitudinal) icon for a car. The image has 19 icons; + /// index 0 is max push, 9 is neutral, 18 is max pull. + /// 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; + var idx = 9; // neutral var absForceN = Math.Abs(car.SmoothedCouplerForceUN); - if (absForceN > 1000f && CouplerStrengthScaleN > 1000f) + + if (absForceN > 1000f && CouplerStrengthScaleN > 1000f) // exclude improbabl values { - // 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 + // power scale, to be sensitve at limit: 1k lbf, 32%, 50%, 63%, 73%, 82%, 89%, 95%, 100% var relForce = absForceN / CouplerStrengthScaleN; var expForce = Math.Pow(9, relForce); idx = (int)Math.Floor(expForce); idx = (car.SmoothedCouplerForceUN > 0f) ? idx * -1 + 9 : idx + 9; // positive force is push if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } + // TODO: for push force, may need to scale differently (how?); containers derail at 300 klbf } - 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); } + + if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarGraphHight); } + else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight, BarWidth, BarGraphHight); } } - protected void UpdateCouplerImpulseImage(TrainCar car, int carPosition) + /// + /// Update the wheel force (lateral) icon for a car. The image has 19 icons; + /// index 0 is max push (outside), 9 is neutral, 18 is max pull (inside). + /// + protected void UpdateWheelForceImage(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) + var idx = 9; // neutral + + var absForceN = car.TotalWagonLateralDerailForceN; + + // see TrainCar.UpdateTrainDerailmentRisk() + if (car.WagonNumBogies <= 0 || car.GetWagonNumAxles() <= 0) { - // 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; } + absForceN = car.DerailmentCoefficient * DerailForceScaleN; + if (car.CouplerForceU > 0 && car.CouplerSlackM < 0) { absForceN /= 1.77f; } // push to outside + else { absForceN /= 1.34f; } // pull to inside } - 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); - 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); } - } + // see TrainCar.UpdateTrainDerailmentRisk() + float directionalScaleN = DerailForceScaleN; + if (car.CouplerForceU > 0 && car.CouplerSlackM < 0) { directionalScaleN /= 1.77f; } // push to outside + else if (car.CouplerForceU < 0 && car.CouplerSlackM > 0) { directionalScaleN /= 1.34f; } // pull to inside - 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) + #region Debug + // debug + if (car.DerailmentCoefficient > 1.0f) + { + var numWheels = car.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } + var wheelDerailForceN = car.MassKG / numWheels * car.GetGravitationalAccelerationMpS2(); + + Debug.WriteLine("DebugCoeff > 1: car-no {0}, car-id {1}, mass {2}, axles {3}, bogies {10}, vertical {4}, lateral {5}, Coeff {6}, limit {7}, abs {8}, scale {9}", + carPosition, car.CarID, FormatStrings.FormatLargeMass(car.MassKG, false, false), car.GetWagonNumAxles(), FormatStrings.FormatLargeForce(car.TotalWagonVerticalDerailForceN, false), + FormatStrings.FormatLargeForce(car.TotalWagonLateralDerailForceN, false), car.DerailmentCoefficient, FormatStrings.FormatLargeForce(wheelDerailForceN, false), + FormatStrings.FormatLargeForce(absForceN, false), FormatStrings.FormatLargeForce(DerailForceScaleN, false), car.WagonNumBogies); + } + #endregion + + if (absForceN > 1000f && DerailForceScaleN > 1000f) // exclude improbable values { - var slack = car.CouplerSlackM; - var relSlack = slack / maxSlack * 9; // 9 bars - idx = 9 + (int)Math.Floor(relSlack); + // flatter scale due to discrete curve radus: 1k lbf, 21%, 37%, 51%, 64%, 74%, 84%, 93%, 100% + var relForce = absForceN / DerailForceScaleN; + // var expForce = Math.Pow(9, relForce); + var expForce = (Math.Pow(3, relForce) - 1) * 4 + 1; + idx = (int)Math.Floor(expForce); + idx = (car.CouplerForceU > 0f && car.CouplerSlackM < 0) ? 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) { SlackBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, 0, BarGraphWidth, BarGraphHight); } - else { SlackBarGraph[carPosition].Source = new Rectangle(1 + idx * BarGraphWidth, BarGraphHight, BarGraphWidth, BarGraphHight); } + + #region Debug + // debug + if (idx == 0 || idx == 18) + { + var numWheels = car.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } + var wheelDerailForceN = car.MassKG / numWheels * car.GetGravitationalAccelerationMpS2(); + + Debug.WriteLine("Idx at boundary: car-no {0}, car-id {1}, mass {2}, axles {3}, bogies {10}, vertical {4}, lateral {5}, Coeff {6}, limit {7}, abs {8}, scale {9}", + carPosition, car.CarID, FormatStrings.FormatLargeMass(car.MassKG, false, false), car.GetWagonNumAxles(), FormatStrings.FormatLargeForce(car.TotalWagonVerticalDerailForceN, false), + FormatStrings.FormatLargeForce(car.TotalWagonLateralDerailForceN, false), car.DerailmentCoefficient, FormatStrings.FormatLargeForce(wheelDerailForceN, false), + FormatStrings.FormatLargeForce(absForceN, false), FormatStrings.FormatLargeForce(DerailForceScaleN, false), car.WagonNumBogies); + } + #endregion + + if (car.WagonType == TrainCar.WagonTypes.Engine) { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarGraphHight); } + else { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight, BarWidth, BarGraphHight); } } } } From 8311f76eaebe8eeb60a112392c30780af8bcfb2e Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Sun, 23 Feb 2025 15:38:48 -0800 Subject: [PATCH 08/15] Flatten power curve for longitudinal force. --- Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 9d7a22baee..acbda49785 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -298,9 +298,9 @@ protected void UpdateCouplerForceImage(TrainCar car, int carPosition) if (absForceN > 1000f && CouplerStrengthScaleN > 1000f) // exclude improbabl values { - // power scale, to be sensitve at limit: 1k lbf, 32%, 50%, 63%, 73%, 82%, 89%, 95%, 100% + // power scale, to be sensitve at limit: 1k lbf, 28%, 46%, 59%, 70%, 80%, 87%, 94%, 100% var relForce = absForceN / CouplerStrengthScaleN; - var expForce = Math.Pow(9, relForce); + var expForce = (Math.Pow(6, relForce) - 1) * 1.5 + 1; idx = (int)Math.Floor(expForce); idx = (car.SmoothedCouplerForceUN > 0f) ? idx * -1 + 9 : idx + 9; // positive force is push if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } @@ -352,7 +352,6 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) { // flatter scale due to discrete curve radus: 1k lbf, 21%, 37%, 51%, 64%, 74%, 84%, 93%, 100% var relForce = absForceN / DerailForceScaleN; - // var expForce = Math.Pow(9, relForce); var expForce = (Math.Pow(3, relForce) - 1) * 4 + 1; idx = (int)Math.Floor(expForce); idx = (car.CouplerForceU > 0f && car.CouplerSlackM < 0) ? idx * -1 + 9 : idx + 9; // positive force is push From 66d5dfffa8311de50a239e9bbacd071e71e76ce6 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Tue, 25 Feb 2025 10:17:39 -0800 Subject: [PATCH 09/15] Add resizing of window when consist changes. --- Source/Orts.Settings/UserSettings.cs | 2 + .../Viewer3D/Popups/TrainForcesWindow.cs | 144 +++++++++++------- 2 files changed, 87 insertions(+), 59 deletions(-) diff --git a/Source/Orts.Settings/UserSettings.cs b/Source/Orts.Settings/UserSettings.cs index 7898be0ce7..ffc65d57bd 100644 --- a/Source/Orts.Settings/UserSettings.cs +++ b/Source/Orts.Settings/UserSettings.cs @@ -412,6 +412,8 @@ public string DirectXFeatureLevel public int[] WindowPosition_ComposeMessage { get; set; } [Default(new[] { 100, 0 })] public int[] WindowPosition_TrainList { get; set; } + [Default(new[] { 50, 50 })] + public int[] WindowPosition_TrainForces { get; set; } [Default("")] public string LastViewNotificationDate { get; set; } diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index acbda49785..1b9189420a 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -21,7 +21,7 @@ // This is a prototype 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 hard to read. +// Forces HUD, display or browser, is hard to read for long trains. // 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 train simulators. See the discussion in the Elvas tower @@ -38,7 +38,9 @@ // Shows the sideway push or pull at the wheels as a colored bar graph. Up (positive) is // pull to the inside (stringline), down (negative) is push to the outside (jackknife). // The scale is determined by the lowest axle-load (vertical force). The steps are -// non-linear, to provide more sensitivity near the derailing point. +// non-linear, to provide more sensitivity near the derailing point. But this is less +// effective for lateral forces, as the force is proporation to the curve radius, which +// changes in discrete steps. // // Bar Graph for Force: // +/- 9 bars; 4 green, 3 orange, 2 red @@ -56,13 +58,13 @@ // are not included. // // Notes: -// * Design was copied from the old (horizontal) train operations window. +// * Design was copied from the old (horizontal) train operations window. Using text-hight +// for field-width, as text width is variable. // * The derail coefficient was considered, but the lateral force provides a more uniform // view across the train. -// * Using text-hight for field-width, as text width is variable. // * Lateral Forces and Derailment: // - As of Feb 2025, lateral forces are not calculated on straight track. -// - As of Feb 2025, longitudinal buff forces may also cause coupler breaks. +// - As of Feb 2025, high longitudinal buff forces cause coupler breaks, not derailment. #endregion using Microsoft.Xna.Framework; @@ -72,9 +74,6 @@ using ORTS.Common; using System.Diagnostics; using System; -using System.Collections.Generic; -using System.Xml; -using SharpDX.Direct2D1.Effects; namespace Orts.Viewer3D.Popups { @@ -88,10 +87,6 @@ public class TrainForcesWindow : Window const int HalfBarGraphHight = 20; const int BarWidth = 6; - float SimSeconds = 0f; - float LastLogSeconds = 0f; - int PrepareCalls = 0; - Train PlayerTrain; int LastPlayerTrainCars; bool LastPlayerLocomotiveFlippedState; @@ -108,17 +103,30 @@ public class TrainForcesWindow : Window Label MaxLongForceForTextBox; Label MaxLatForceForTextBox; - float LastAbsForceN = 0f; - float NextLowerTime = 0f; + // window size + readonly int TextHight; + readonly int GraphLabelWidth; + readonly int TextLineWidth; + readonly int WindowHeight; + readonly int WindowWidthMin; + readonly int WindowWidthMax; + /// - /// Constructor. Window is wide enough for about 150 cars. Longer trains - /// have a scrollbar. This seems a reasonable compromise between typical - /// display size and typical train length. + /// Constructor. Initial window is wide enough for the two current forces in the + /// line. This is good for about 50 cars. Window will resize when the number of + /// cars is greater, and will scroll when the window would have to be greater + /// than 1000 pixels. /// public TrainForcesWindow(WindowManager owner) - : base(owner, Window.DecorationSize.X + 1000, Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarGraphHight * 2 + 20, Viewer.Catalog.GetString("Train Forces")) + : base(owner, Window.DecorationSize.X + 500, Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarGraphHight * 2 + 18, Viewer.Catalog.GetString("Train Forces")) { + TextHight = owner.TextFontDefault.Height; + GraphLabelWidth = TextHight * 6; + TextLineWidth = TextHight * (9 + 7 + 9 + 7 + 8 + 5 + 8 + 5) + 2; + WindowHeight = Location.Height; + WindowWidthMin = Location.Width; + WindowWidthMax = 1024 - Window.DecorationSize.X; } /// @@ -138,19 +146,20 @@ protected internal override void Initialize() /// protected override ControlLayout Layout(ControlLayout layout) { - var textHeight = Owner.TextFontDefault.Height; - var labelWidth = textHeight * 6; - int numBars = 60; // enough to show the important part of the text line - if (PlayerTrain != null && PlayerTrain.Cars != null && PlayerTrain.Cars.Count > numBars) { numBars = PlayerTrain.Cars.Count; } - var innerBoxWidth = labelWidth + BarWidth * numBars + 4; + var innerBoxWidth = TextLineWidth; + if (PlayerTrain != null && PlayerTrain.Cars != null) + { + int barGraphWidth = GraphLabelWidth + BarWidth * PlayerTrain.Cars.Count + 4; + if (barGraphWidth > innerBoxWidth) { innerBoxWidth = barGraphWidth; } + } var hbox = base.Layout(layout).AddLayoutHorizontal(); var scrollbox = hbox.AddLayoutScrollboxHorizontal(hbox.RemainingHeight); var vbox = scrollbox.AddLayoutVertical(Math.Max(innerBoxWidth,scrollbox.RemainingWidth)); var longForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); - longForceBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, labelWidth, BarGraphHight, Viewer.Catalog.GetString("Longitudinal") + ": ")); + longForceBox.Add(new Label(0, (BarGraphHight - TextHight) / 2, GraphLabelWidth, BarGraphHight, Viewer.Catalog.GetString("Longitudinal") + ": ")); var latForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); - latForceBox.Add(new Label(0, (BarGraphHight - textHeight) / 2, labelWidth, BarGraphHight, Viewer.Catalog.GetString("Lateral") + ": ")); + latForceBox.Add(new Label(0, (BarGraphHight - TextHight) / 2, GraphLabelWidth, BarGraphHight, Viewer.Catalog.GetString("Lateral") + ": ")); if (PlayerTrain != null) { @@ -174,18 +183,17 @@ protected override ControlLayout Layout(ControlLayout layout) } vbox.AddHorizontalSeparator(); - var textbox = vbox.AddLayoutHorizontalLineOfText(); - textbox.Add(new Label(textHeight * 9, textHeight, Viewer.Catalog.GetString("Max Long Force") + ": ", LabelAlignment.Right)); - textbox.Add(MaxLongForceForTextBox = new Label(textHeight * 7, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); - textbox.Add(new Label(textHeight * 9, textHeight, Viewer.Catalog.GetString("Max Lat Force") + ": ", LabelAlignment.Right)); - textbox.Add(MaxLatForceForTextBox = new Label(textHeight * 7, textHeight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); - - textbox.Add(new Label(textHeight * 8, textHeight, Viewer.Catalog.GetString("Min Coupler") + ": ", LabelAlignment.Right)); - textbox.Add(new Label(textHeight * 5, textHeight, FormatStrings.FormatLargeForce(MinCouplerStrengthN, false), LabelAlignment.Right)); - textbox.Add(new Label(textHeight * 8, textHeight, Viewer.Catalog.GetString("Min Derail") + ": ", LabelAlignment.Right)); - textbox.Add(new Label(textHeight * 5, textHeight, FormatStrings.FormatLargeForce(MinDerailForceN, false), LabelAlignment.Right)); - - LastAbsForceN = 0f; + var textLine = vbox.AddLayoutHorizontalLineOfText(); + + textLine.Add(new Label(TextHight * 9, TextHight, Viewer.Catalog.GetString("Max Long Force") + ": ", LabelAlignment.Right)); + textLine.Add(MaxLongForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 9, TextHight, Viewer.Catalog.GetString("Max Lat Force") + ": ", LabelAlignment.Right)); + textLine.Add(MaxLatForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + + textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Min Coupler") + ": ", LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(MinCouplerStrengthN, false), LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Min Derail") + ": ", LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(MinDerailForceN, false), LabelAlignment.Right)); } return hbox; @@ -198,28 +206,27 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) { base.PrepareFrame(elapsedTime, updateFull); - //PrepareCalls++; - //SimSeconds += elapsedTime.RealSeconds; - // - //if (PrepareCalls % 60 == 0) - //{ - // // Trace.TraceInformation("TrainForcesWindow:PrepareFrame() called for the {0} time; elapsed time is {1:F3}", PrepareCalls, SimSeconds - LastLogSeconds); - // LastLogSeconds = SimSeconds; - //} - if (updateFull) { - if (PlayerTrain != Owner.Viewer.PlayerTrain || Owner.Viewer.PlayerTrain.Cars.Count != LastPlayerTrainCars || (Owner.Viewer.PlayerLocomotive != null && - LastPlayerLocomotiveFlippedState != Owner.Viewer.PlayerLocomotive.Flipped)) + 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; + ResizeWindow(Window.DecorationSize.X + GraphLabelWidth + BarWidth * PlayerTrain.Cars.Count + 4); Layout(); } } - else if (PlayerTrain != null) + + if (PlayerTrain != null) { + if (PlayerTrain.Cars.Count != CouplerForceBarGraph.Length) + { + ResizeWindow(Window.DecorationSize.X + GraphLabelWidth + BarWidth * PlayerTrain.Cars.Count + 4); + Layout(); + } + var absMaxLongForceN = 0.0f; var longForceSign = 1.0f; var maxLongForceCarNum = 0; var absMaxLatForceN = 0.0f; var latForceSign = 1.0f; var maxLatForceCarNum = 0; @@ -230,23 +237,32 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) UpdateWheelForceImage(car, carPosition); var longForceN = car.CouplerForceU; var absLongForceN = Math.Abs(longForceN); - if (absLongForceN > absMaxLongForceN) { absMaxLongForceN = absLongForceN; longForceSign = longForceN > 0 ? -1.0f : 1.0f; maxLongForceCarNum = carPosition + 1; } + if (absLongForceN > absMaxLongForceN) + { + absMaxLongForceN = absLongForceN; + longForceSign = longForceN > 0 ? -1.0f : 1.0f; + maxLongForceCarNum = carPosition + 1; + } // see TrainCar.UpdateTrainDerailmentRisk() var absLatForceN = car.TotalWagonLateralDerailForceN; if (car.WagonNumBogies <= 0 || car.GetWagonNumAxles() <= 0) { absLatForceN = car.DerailmentCoefficient * DerailForceScaleN; } - if (absLatForceN > absMaxLatForceN) { absMaxLatForceN = absLatForceN; latForceSign = (car.CouplerForceU > 0 && car.CouplerSlackM < 0) ? -1.0f : 1.0f; maxLatForceCarNum = carPosition + 1; } + if (absLatForceN > absMaxLatForceN) + { + absMaxLatForceN = absLatForceN; + latForceSign = (car.CouplerForceU > 0 && car.CouplerSlackM < 0) ? -1.0f : 1.0f; + maxLatForceCarNum = carPosition + 1; + } carPosition++; } - // update max coupler force; TODO: smooth the downslope if (MaxLongForceForTextBox != null) { + // TODO: smooth the downslope MaxLongForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxLongForceN * longForceSign, false) + string.Format(" ({0})", maxLongForceCarNum); } - // update max derail force if (MaxLatForceForTextBox != null) { MaxLatForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxLatForceN * latForceSign, false) + string.Format(" ({0})", maxLatForceCarNum); @@ -334,8 +350,7 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) if (car.CouplerForceU > 0 && car.CouplerSlackM < 0) { directionalScaleN /= 1.77f; } // push to outside else if (car.CouplerForceU < 0 && car.CouplerSlackM > 0) { directionalScaleN /= 1.34f; } // pull to inside - #region Debug - // debug +#if DEBUG if (car.DerailmentCoefficient > 1.0f) { var numWheels = car.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } @@ -346,7 +361,7 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) FormatStrings.FormatLargeForce(car.TotalWagonLateralDerailForceN, false), car.DerailmentCoefficient, FormatStrings.FormatLargeForce(wheelDerailForceN, false), FormatStrings.FormatLargeForce(absForceN, false), FormatStrings.FormatLargeForce(DerailForceScaleN, false), car.WagonNumBogies); } - #endregion +#endif if (absForceN > 1000f && DerailForceScaleN > 1000f) // exclude improbable values { @@ -358,8 +373,7 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } } - #region Debug - // debug +#if DEBUG if (idx == 0 || idx == 18) { var numWheels = car.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } @@ -370,10 +384,22 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) FormatStrings.FormatLargeForce(car.TotalWagonLateralDerailForceN, false), car.DerailmentCoefficient, FormatStrings.FormatLargeForce(wheelDerailForceN, false), FormatStrings.FormatLargeForce(absForceN, false), FormatStrings.FormatLargeForce(DerailForceScaleN, false), car.WagonNumBogies); } - #endregion +#endif if (car.WagonType == TrainCar.WagonTypes.Engine) { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarGraphHight); } else { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight, BarWidth, BarGraphHight); } } + + /// + /// Resize the window to fit the bar graph for the number of cars. + /// Limited by min and max size. + /// + void ResizeWindow(int newWidth) + { + if (newWidth < WindowWidthMin) { newWidth = WindowWidthMin; } + else if (newWidth > WindowWidthMax) { newWidth = WindowWidthMax; } + + SizeTo(newWidth, WindowHeight); + } } } From 264372f92e1950413090a0c6d05fdfb3a8c2660d Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Wed, 26 Feb 2025 17:03:13 -0800 Subject: [PATCH 10/15] Add brake force bar graph. Minor adjustments too. --- Source/RunActivity/Content/BarGraph.png | Bin 502 -> 700 bytes .../Viewer3D/Popups/TrainForcesWindow.cs | 96 +++++++++++++----- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/Source/RunActivity/Content/BarGraph.png b/Source/RunActivity/Content/BarGraph.png index f40876e9ee5dd72914dc3b3e7df636b61d5334a0..c15b8c53f5cd87df2faaf1b8dc241933cae5b4bc 100644 GIT binary patch delta 652 zcmV;70(1TL1H1*07Ycm{1^@s6bCpN$ks&C5!2kdb!2!6DYwZ940#QjsK~#8N?cKXg zLqQOQVJ{LX2^oTn9w>r#D1&w=f*!~~WF({n7g!x3OV+hm*uC@E|4V$Bbmm|l#_W(w z(IWzRyS!c$f1JNOvCE9us$*+Sq@fdGs}3xWX9tZdH}9o&u82(7>V|qXk%mr$tqw4M zx`{M&B5d^^9$v2wnsR&bT&(7b$b_x_f>q;M=ZeULt?sdEROWY$%yn(9h)me(FIY9Mb*_j^*yO)R!`!i=ZZjFDH^H>Ui8K`9ZE~$vOhJy!($tJm84 mU+|*O6;t9>e*mzN-R={SG9Zo}>=<+a0000K_E9cDb9zaoW!fw9}u#WAEJ?(H2zzrzk9 zEs6Ro@&4}RfgkVxecq_2yeRbev}q@ttesZ#_59v_^U0X@maz37{(ZHGK6U!A`=(vmNSCf)h(_uDaTk zbzfA_Gw*BepSh<0X5|Tg-}Y|#_1S;;8vHAlh4Fv9RqnbVR#9vf$C1ldoJ1SzcEqeu zYS_bOcZJ1b>q#e3#xL%l|m{A%7uf;R=DN_*nf4?5XC z@LwO{vRYbetBcqz!Kk$hz6nKUxMzIj3X?C6CG50Pn^W)AE(wAh7XHs)@ImCfl~ public TrainForcesWindow(WindowManager owner) - : base(owner, Window.DecorationSize.X + 500, Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarGraphHight * 2 + 18, Viewer.Catalog.GetString("Train Forces")) + : base(owner, Window.DecorationSize.X + 500, + Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarGraphHight * 2 + HalfBarGraphHight + 18, + Viewer.Catalog.GetString("Train Forces")) { TextHight = owner.TextFontDefault.Height; GraphLabelWidth = TextHight * 6; @@ -141,6 +145,18 @@ protected internal override void Initialize() } } + /// + /// Resize the window to fit the bar graph for the number of cars. + /// Limited by min and max size. + /// + void ResizeWindow(int newWidth) + { + if (newWidth < WindowWidthMin) { newWidth = WindowWidthMin; } + else if (newWidth > WindowWidthMax) { newWidth = WindowWidthMax; } + + SizeTo(newWidth, WindowHeight); + } + /// /// Create the layout. Defines the components within the window. /// @@ -160,6 +176,8 @@ protected override ControlLayout Layout(ControlLayout layout) longForceBox.Add(new Label(0, (BarGraphHight - TextHight) / 2, GraphLabelWidth, BarGraphHight, Viewer.Catalog.GetString("Longitudinal") + ": ")); var latForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); latForceBox.Add(new Label(0, (BarGraphHight - TextHight) / 2, GraphLabelWidth, BarGraphHight, Viewer.Catalog.GetString("Lateral") + ": ")); + var brakeForceBox = vbox.AddLayoutHorizontal(HalfBarGraphHight + 4); + brakeForceBox.Add(new Label(0, (HalfBarGraphHight - TextHight) / 2, GraphLabelWidth, HalfBarGraphHight, Viewer.Catalog.GetString("Brake") + ": ")); if (PlayerTrain != null) { @@ -167,6 +185,7 @@ protected override ControlLayout Layout(ControlLayout layout) CouplerForceBarGraph = new Image[PlayerTrain.Cars.Count]; WheelForceBarGraph = new Image[PlayerTrain.Cars.Count]; + BrakeForceBarGraph = new Image[PlayerTrain.Cars.Count]; int carPosition = 0; foreach (var car in PlayerTrain.Cars) @@ -179,6 +198,10 @@ protected override ControlLayout Layout(ControlLayout layout) WheelForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateWheelForceImage(car, carPosition); + brakeForceBox.Add(BrakeForceBarGraph[carPosition] = new Image(BarWidth, HalfBarGraphHight)); + BrakeForceBarGraph[carPosition].Texture = ForceBarTextures; + UpdateBrakeForceImage(car, carPosition); + carPosition++; } @@ -191,9 +214,11 @@ protected override ControlLayout Layout(ControlLayout layout) textLine.Add(MaxLatForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Min Coupler") + ": ", LabelAlignment.Right)); - textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(MinCouplerStrengthN, false), LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(LimitForCouplerStrengthN, false), LabelAlignment.Right)); textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Min Derail") + ": ", LabelAlignment.Right)); - textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(MinDerailForceN, false), LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(LimitForDerailForceN, false), LabelAlignment.Right)); + + // no text for brake force } return hbox; @@ -235,6 +260,7 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) { UpdateCouplerForceImage(car, carPosition); UpdateWheelForceImage(car, carPosition); + UpdateBrakeForceImage(car, carPosition); var longForceN = car.CouplerForceU; var absLongForceN = Math.Abs(longForceN); if (absLongForceN > absMaxLongForceN) @@ -276,31 +302,35 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) /// protected void SetConsistProperties(Train theTrain) { - float minCouplerBreakN = ImpossiblyHighForce; - float minDerailForceN = ImpossiblyHighForce; + float lowestCouplerBreakN = LimitForCouplerStrengthN; + float lowestDerailForceN = LimitForDerailForceN; + float lowestMaxBrakeForceN = LimitForBrakeForceN; foreach (var car in theTrain.Cars) { if (car is MSTSWagon wag) { - var couplerBreakForceN = wag.GetCouplerBreak2N() > 1.0f ? wag.GetCouplerBreak2N() : wag.GetCouplerBreak1N(); - if (couplerBreakForceN < minCouplerBreakN) { minCouplerBreakN = couplerBreakForceN; } + var couplerBreakForceN = wag.GetCouplerBreak2N() > 1000f ? wag.GetCouplerBreak2N() : wag.GetCouplerBreak1N(); + if (couplerBreakForceN > 1000f && couplerBreakForceN < lowestCouplerBreakN) { lowestCouplerBreakN = couplerBreakForceN; } // simplified from TrainCar.UpdateTrainDerailmentRisk() - var numWheels = wag.GetWagonNumAxles() * 2; + var numWheels = wag.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } // err towards higher vertical force var wheelDerailForceN = wag.MassKG / numWheels * wag.GetGravitationalAccelerationMpS2(); - if (wheelDerailForceN > 1000f) // exclude improbable vales - { - if (wheelDerailForceN < minDerailForceN) { minDerailForceN = wheelDerailForceN; } - } + if (wheelDerailForceN > 1000f && wheelDerailForceN < lowestDerailForceN) { lowestDerailForceN = wheelDerailForceN; } + + var maxBrakeForceN = wag.MaxBrakeForceN; + if (maxBrakeForceN > 1000f && maxBrakeForceN < lowestMaxBrakeForceN) { lowestMaxBrakeForceN = maxBrakeForceN; } } } - MinCouplerStrengthN = minCouplerBreakN; - CouplerStrengthScaleN = Math.Min(minCouplerBreakN, HighCouplerStrengthN) * 1.05f; + LimitForCouplerStrengthN = lowestCouplerBreakN; + CouplerStrengthScaleN = lowestCouplerBreakN * 1.05f; - MinDerailForceN = minDerailForceN; - DerailForceScaleN = Math.Min(minDerailForceN, HighCouplerStrengthN) * 1.05f; + LimitForDerailForceN = lowestDerailForceN; + DerailForceScaleN = lowestDerailForceN * 1.1f; + + LimitForBrakeForceN = lowestMaxBrakeForceN; + BrakeForceScaleN = lowestMaxBrakeForceN * 1.5f; } /// @@ -391,15 +421,25 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) } /// - /// Resize the window to fit the bar graph for the number of cars. - /// Limited by min and max size. + /// Update the brake force icon for a car. The image has 10 icons; + /// index 0 is neutral, 9 is max braking. /// - void ResizeWindow(int newWidth) + protected void UpdateBrakeForceImage(TrainCar car, int carPosition) { - if (newWidth < WindowWidthMin) { newWidth = WindowWidthMin; } - else if (newWidth > WindowWidthMax) { newWidth = WindowWidthMax; } + var idx = 0; // neutral + var absForceN = car.BrakeForceN; - SizeTo(newWidth, WindowHeight); + if (absForceN > 1000f && BrakeForceScaleN > 1000f) // exclude improbabl values + { + // log scale, to be sensitve at small application: 1k lbf, 7%, 146%, 22%, 30%, 39%, 51%, 68%, 100% + var relForce = absForceN / BrakeForceScaleN; + var logForce = (1 / (1 + Math.Pow(10, -1.5f * relForce)) - 0.5f) * 17.05f + 1f; + idx = (int)Math.Floor(logForce); + if (idx < 0) { idx = 0; } else if (idx > 9) { idx = 9; } + } + + if (car.WagonType == TrainCar.WagonTypes.Engine) { BrakeForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight * 2, BarWidth, HalfBarGraphHight); } + else { BrakeForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight * 2 + HalfBarGraphHight, BarWidth, HalfBarGraphHight); } } } } From 57c0496a3a8ce3d468dba52d5f608c473abbe3a1 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Wed, 26 Feb 2025 20:24:09 -0800 Subject: [PATCH 11/15] Some renaming. --- .../Viewer3D/Popups/TrainForcesWindow.cs | 116 ++++++++++-------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index a90384339f..352ffc3ef1 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -28,30 +28,35 @@ // forum, at: // https://www.elvastower.com/forums/index.php?/topic/38056-proposal-for-train-forces-popup-display/ // -// Longitudinal Force: +// Coupler Force (longitudinal): // Shows the length-wise pull or push force at each coupling, as a colored bar graph. Up // (positive) is pull, down (negative) is push. The scale is determined by the weakest // coupler in the train. The steps are non-linear, to provide more sensitivity near the // breaking point. // -// Lateral Force: +// Derail Force (lateral): // Shows the sideway push or pull at the wheels as a colored bar graph. Up (positive) is // pull to the inside (stringline), down (negative) is push to the outside (jackknife). -// The scale is determined by the lowest axle-load (vertical force). The steps are +// The scale is determined by the lowest axle-load (lowest vertical force). The steps are // non-linear, to provide more sensitivity near the derailing point. But this is less // effective for lateral forces, as the force is proporation to the curve radius, which // changes in discrete steps. // -// Bar Graph for Force: +// Brake Force: +// Shows the brake force of each car as a bar graph. The scale is determined by the car +// with the smallest brake force (smallest weight). The steps are non-linear, to provide +// more sensitivity near the small brake applications. As the weight (and thus brake +// force varies greatly between cars (and especially engines), the graph can be quite +// jaggered. +// +// Bar Graph: // +/- 9 bars; 4 green, 3 orange, 2 red // blue middle-bar is an engine, white is a car +// except: brake force only has the up side, and all bars are green. // // Slack: // Was considered, but is sufficiently reflected by the lateral force display. // -// Break Pipe Pressure or Brake Force: -// Was considered. It is not really a train-handling parameter. -// // Grade & Curvature: // It is not practical to show grade or curvature in a meaningfule way // without needing significant screen-space and calculations. Thus they @@ -60,8 +65,10 @@ // Notes: // * Design was copied from the old (horizontal) train operations window. Using text-hight // for field-width, as text width is variable. -// * The derail coefficient was considered, but the lateral force provides a more uniform -// view across the train. +// * All bar graphs present absolute forces (scaled by the weakest car in the train). This +// provides a good overview of the actual forces along the train. An alternavive would be +// to scale each bar for the the car it represents. That would be a better indication +// where the risk is. But it would not reflect the real forces along the train. // * Lateral Forces and Derailment: // - As of Feb 2025, lateral forces are not calculated on straight track. // - As of Feb 2025, high longitudinal buff forces cause coupler breaks, not derailment. @@ -81,8 +88,8 @@ namespace Orts.Viewer3D.Popups public class TrainForcesWindow : Window { static Texture2D ForceBarTextures; - const int BarGraphHight = 40; - const int HalfBarGraphHight = 22; + const int BarHight = 40; + const int HalfBarHight = 22; const int BarWidth = 6; Train PlayerTrain; @@ -102,8 +109,8 @@ public class TrainForcesWindow : Window Image[] WheelForceBarGraph; Image[] BrakeForceBarGraph; - Label MaxLongForceForTextBox; - Label MaxLatForceForTextBox; + Label MaxCouplerForceForTextBox; + Label MaxDerailForceForTextBox; // window size readonly int TextHight; @@ -122,7 +129,7 @@ public class TrainForcesWindow : Window /// public TrainForcesWindow(WindowManager owner) : base(owner, Window.DecorationSize.X + 500, - Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarGraphHight * 2 + HalfBarGraphHight + 18, + Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarHight * 2 + HalfBarHight + 18, Viewer.Catalog.GetString("Train Forces")) { TextHight = owner.TextFontDefault.Height; @@ -141,7 +148,8 @@ protected internal override void Initialize() base.Initialize(); if (ForceBarTextures == null) { - ForceBarTextures = 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")); } } @@ -172,12 +180,12 @@ protected override ControlLayout Layout(ControlLayout layout) var hbox = base.Layout(layout).AddLayoutHorizontal(); var scrollbox = hbox.AddLayoutScrollboxHorizontal(hbox.RemainingHeight); var vbox = scrollbox.AddLayoutVertical(Math.Max(innerBoxWidth,scrollbox.RemainingWidth)); - var longForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); - longForceBox.Add(new Label(0, (BarGraphHight - TextHight) / 2, GraphLabelWidth, BarGraphHight, Viewer.Catalog.GetString("Longitudinal") + ": ")); - var latForceBox = vbox.AddLayoutHorizontal(BarGraphHight + 4); - latForceBox.Add(new Label(0, (BarGraphHight - TextHight) / 2, GraphLabelWidth, BarGraphHight, Viewer.Catalog.GetString("Lateral") + ": ")); - var brakeForceBox = vbox.AddLayoutHorizontal(HalfBarGraphHight + 4); - brakeForceBox.Add(new Label(0, (HalfBarGraphHight - TextHight) / 2, GraphLabelWidth, HalfBarGraphHight, Viewer.Catalog.GetString("Brake") + ": ")); + var couplerForceBox = vbox.AddLayoutHorizontal(BarHight + 4); + couplerForceBox.Add(new Label(0, (BarHight - TextHight) / 2, GraphLabelWidth, BarHight, Viewer.Catalog.GetString("Coupler") + ": ")); + var derailForceBox = vbox.AddLayoutHorizontal(BarHight + 4); + derailForceBox.Add(new Label(0, (BarHight - TextHight) / 2, GraphLabelWidth, BarHight, Viewer.Catalog.GetString("Derail") + ": ")); + var brakeForceBox = vbox.AddLayoutHorizontal(HalfBarHight + 4); + brakeForceBox.Add(new Label(0, (HalfBarHight - TextHight) / 2, GraphLabelWidth, HalfBarHight, Viewer.Catalog.GetString("Brake") + ": ")); if (PlayerTrain != null) { @@ -190,15 +198,15 @@ protected override ControlLayout Layout(ControlLayout layout) int carPosition = 0; foreach (var car in PlayerTrain.Cars) { - longForceBox.Add(CouplerForceBarGraph[carPosition] = new Image(BarWidth, BarGraphHight)); + couplerForceBox.Add(CouplerForceBarGraph[carPosition] = new Image(BarWidth, BarHight)); CouplerForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateCouplerForceImage(car, carPosition); - latForceBox.Add(WheelForceBarGraph[carPosition] = new Image(BarWidth, BarGraphHight)); + derailForceBox.Add(WheelForceBarGraph[carPosition] = new Image(BarWidth, BarHight)); WheelForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateWheelForceImage(car, carPosition); - brakeForceBox.Add(BrakeForceBarGraph[carPosition] = new Image(BarWidth, HalfBarGraphHight)); + brakeForceBox.Add(BrakeForceBarGraph[carPosition] = new Image(BarWidth, HalfBarHight)); BrakeForceBarGraph[carPosition].Texture = ForceBarTextures; UpdateBrakeForceImage(car, carPosition); @@ -208,14 +216,14 @@ protected override ControlLayout Layout(ControlLayout layout) vbox.AddHorizontalSeparator(); var textLine = vbox.AddLayoutHorizontalLineOfText(); - textLine.Add(new Label(TextHight * 9, TextHight, Viewer.Catalog.GetString("Max Long Force") + ": ", LabelAlignment.Right)); - textLine.Add(MaxLongForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); - textLine.Add(new Label(TextHight * 9, TextHight, Viewer.Catalog.GetString("Max Lat Force") + ": ", LabelAlignment.Right)); - textLine.Add(MaxLatForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 9, TextHight, Viewer.Catalog.GetString("Max Coupler") + ": ", LabelAlignment.Right)); + textLine.Add(MaxCouplerForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 9, TextHight, Viewer.Catalog.GetString("Max Derail") + ": ", LabelAlignment.Right)); + textLine.Add(MaxDerailForceForTextBox = new Label(TextHight * 7, TextHight, FormatStrings.FormatLargeForce(0f, false), LabelAlignment.Right)); - textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Min Coupler") + ": ", LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Low Coupler") + ": ", LabelAlignment.Right)); textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(LimitForCouplerStrengthN, false), LabelAlignment.Right)); - textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Min Derail") + ": ", LabelAlignment.Right)); + textLine.Add(new Label(TextHight * 8, TextHight, Viewer.Catalog.GetString("Low Derail") + ": ", LabelAlignment.Right)); textLine.Add(new Label(TextHight * 5, TextHight, FormatStrings.FormatLargeForce(LimitForDerailForceN, false), LabelAlignment.Right)); // no text for brake force @@ -252,8 +260,8 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) Layout(); } - var absMaxLongForceN = 0.0f; var longForceSign = 1.0f; var maxLongForceCarNum = 0; - var absMaxLatForceN = 0.0f; var latForceSign = 1.0f; var maxLatForceCarNum = 0; + var absMaxCouplerForceN = 0.0f; var couplerForceSign = 1.0f; var maxCouplerForceCarNum = 0; + var absMaxDerailForceN = 0.0f; var derailForceSign = 1.0f; var maxDerailForceCarNum = 0; int carPosition = 0; foreach (var car in PlayerTrain.Cars) @@ -262,36 +270,38 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) UpdateWheelForceImage(car, carPosition); UpdateBrakeForceImage(car, carPosition); - var longForceN = car.CouplerForceU; var absLongForceN = Math.Abs(longForceN); - if (absLongForceN > absMaxLongForceN) + var couplerForceN = car.CouplerForceU; var absCouplerForceN = Math.Abs(couplerForceN); + if (absCouplerForceN > absMaxCouplerForceN) { - absMaxLongForceN = absLongForceN; - longForceSign = longForceN > 0 ? -1.0f : 1.0f; - maxLongForceCarNum = carPosition + 1; + absMaxCouplerForceN = absCouplerForceN; + couplerForceSign = couplerForceN > 0 ? -1.0f : 1.0f; + maxCouplerForceCarNum = carPosition + 1; } // see TrainCar.UpdateTrainDerailmentRisk() - var absLatForceN = car.TotalWagonLateralDerailForceN; - if (car.WagonNumBogies <= 0 || car.GetWagonNumAxles() <= 0) { absLatForceN = car.DerailmentCoefficient * DerailForceScaleN; } - if (absLatForceN > absMaxLatForceN) + var absDerailForceN = car.TotalWagonLateralDerailForceN; + if (car.WagonNumBogies <= 0 || car.GetWagonNumAxles() <= 0) { absDerailForceN = car.DerailmentCoefficient * DerailForceScaleN; } + if (absDerailForceN > absMaxDerailForceN) { - absMaxLatForceN = absLatForceN; - latForceSign = (car.CouplerForceU > 0 && car.CouplerSlackM < 0) ? -1.0f : 1.0f; - maxLatForceCarNum = carPosition + 1; + absMaxDerailForceN = absDerailForceN; + derailForceSign = (car.CouplerForceU > 0 && car.CouplerSlackM < 0) ? -1.0f : 1.0f; + maxDerailForceCarNum = carPosition + 1; } carPosition++; } - if (MaxLongForceForTextBox != null) + if (MaxCouplerForceForTextBox != null) { // TODO: smooth the downslope - MaxLongForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxLongForceN * longForceSign, false) + string.Format(" ({0})", maxLongForceCarNum); + MaxCouplerForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxCouplerForceN * couplerForceSign, false) + + string.Format(" ({0,3})", maxCouplerForceCarNum); } - if (MaxLatForceForTextBox != null) + if (MaxDerailForceForTextBox != null) { - MaxLatForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxLatForceN * latForceSign, false) + string.Format(" ({0})", maxLatForceCarNum); + MaxDerailForceForTextBox.Text = FormatStrings.FormatLargeForce(absMaxDerailForceN * derailForceSign, false) + + string.Format(" ({0,3})", maxDerailForceCarNum); } } } @@ -353,8 +363,8 @@ protected void UpdateCouplerForceImage(TrainCar car, int carPosition) // TODO: for push force, may need to scale differently (how?); containers derail at 300 klbf } - if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarGraphHight); } - else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight, BarWidth, BarGraphHight); } + if (car.WagonType == TrainCar.WagonTypes.Engine) { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarHight); } + else { CouplerForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarHight, BarWidth, BarHight); } } /// @@ -416,8 +426,8 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) } #endif - if (car.WagonType == TrainCar.WagonTypes.Engine) { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarGraphHight); } - else { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight, BarWidth, BarGraphHight); } + if (car.WagonType == TrainCar.WagonTypes.Engine) { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarHight); } + else { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarHight, BarWidth, BarHight); } } /// @@ -438,8 +448,8 @@ protected void UpdateBrakeForceImage(TrainCar car, int carPosition) if (idx < 0) { idx = 0; } else if (idx > 9) { idx = 9; } } - if (car.WagonType == TrainCar.WagonTypes.Engine) { BrakeForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight * 2, BarWidth, HalfBarGraphHight); } - else { BrakeForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarGraphHight * 2 + HalfBarGraphHight, BarWidth, HalfBarGraphHight); } + if (car.WagonType == TrainCar.WagonTypes.Engine) { BrakeForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarHight * 2, BarWidth, HalfBarHight); } + else { BrakeForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarHight * 2 + HalfBarHight, BarWidth, HalfBarHight); } } } } From 105a9240d1920039774960f5f30d89d060ff48b8 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Thu, 27 Feb 2025 14:38:28 -0800 Subject: [PATCH 12/15] Add reminder for dynamic length text. --- Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 352ffc3ef1..4b810e79c8 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -113,6 +113,7 @@ public class TrainForcesWindow : Window Label MaxDerailForceForTextBox; // window size + readonly WindowTextFont Font; readonly int TextHight; readonly int GraphLabelWidth; readonly int TextLineWidth; @@ -132,7 +133,8 @@ public TrainForcesWindow(WindowManager owner) Window.DecorationSize.Y + owner.TextFontDefault.Height * 2 + BarHight * 2 + HalfBarHight + 18, Viewer.Catalog.GetString("Train Forces")) { - TextHight = owner.TextFontDefault.Height; + Font = owner.TextFontDefault; // for Font.MeasureString(string) + TextHight = Font.Height; GraphLabelWidth = TextHight * 6; TextLineWidth = TextHight * (9 + 7 + 9 + 7 + 8 + 5 + 8 + 5) + 2; WindowHeight = Location.Height; From d1ed9655463fd39de6e53950abfe8ee06f846f79 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Thu, 27 Feb 2025 15:42:14 -0800 Subject: [PATCH 13/15] Delete variants of the bar graph images. --- Source/RunActivity/Content/BarGraph-bar6wide.png | Bin 552 -> 0 bytes Source/RunActivity/Content/BarGraph-v1.png | Bin 502 -> 0 bytes Source/RunActivity/Content/BarGraph-withBG.png | Bin 522 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Source/RunActivity/Content/BarGraph-bar6wide.png delete mode 100644 Source/RunActivity/Content/BarGraph-v1.png delete mode 100644 Source/RunActivity/Content/BarGraph-withBG.png diff --git a/Source/RunActivity/Content/BarGraph-bar6wide.png b/Source/RunActivity/Content/BarGraph-bar6wide.png deleted file mode 100644 index a02d1220464d3d2f579a9cc352809f98d11c5da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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;= diff --git a/Source/RunActivity/Content/BarGraph-withBG.png b/Source/RunActivity/Content/BarGraph-withBG.png deleted file mode 100644 index c787fccb0d4520a30bd65b0d6e9990c74cbf9a3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 Date: Sun, 16 Mar 2025 15:30:31 -0700 Subject: [PATCH 14/15] Change to use Alt-F7. Also change name of sprites file. --- Source/Orts.Settings/InputSettings.cs | 2 +- .../{BarGraph.png => TrainForcesSprites.png} | Bin Source/RunActivity/RunActivity.csproj | 2 +- .../Viewer3D/Popups/TrainForcesWindow.cs | 2 +- Source/RunActivity/Viewer3D/Viewer.cs | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename Source/RunActivity/Content/{BarGraph.png => TrainForcesSprites.png} (100%) diff --git a/Source/Orts.Settings/InputSettings.cs b/Source/Orts.Settings/InputSettings.cs index afb9c2a869..c6c0617c0e 100644 --- a/Source/Orts.Settings/InputSettings.cs +++ b/Source/Orts.Settings/InputSettings.cs @@ -516,7 +516,7 @@ static void InitializeCommands(UserCommandInput[] Commands) Commands[(int)UserCommand.DisplayTrainOperationsWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Control | KeyModifiers.Alt); Commands[(int)UserCommand.DisplayTrainDpuWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Shift); Commands[(int)UserCommand.DisplayEOTListWindow] = new UserCommandKeyInput(0x43, KeyModifiers.Control); - Commands[(int)UserCommand.DisplayTrainForcesWindow] = new UserCommandKeyInput(0x42, KeyModifiers.Alt); + Commands[(int)UserCommand.DisplayTrainForcesWindow] = new UserCommandKeyInput(0x41, KeyModifiers.Alt); Commands[(int)UserCommand.DisplayControlRectangle] = new UserCommandKeyInput(0x3F, KeyModifiers.Control); Commands[(int)UserCommand.GameAutopilotMode] = new UserCommandKeyInput(0x1E, KeyModifiers.Alt); diff --git a/Source/RunActivity/Content/BarGraph.png b/Source/RunActivity/Content/TrainForcesSprites.png similarity index 100% rename from Source/RunActivity/Content/BarGraph.png rename to Source/RunActivity/Content/TrainForcesSprites.png diff --git a/Source/RunActivity/RunActivity.csproj b/Source/RunActivity/RunActivity.csproj index c60008c1b9..f097fb96f9 100644 --- a/Source/RunActivity/RunActivity.csproj +++ b/Source/RunActivity/RunActivity.csproj @@ -42,7 +42,7 @@ Native\X64\OpenAL32.dll PreserveNewest - + PreserveNewest diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 4b810e79c8..422c86de8f 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -151,7 +151,7 @@ protected internal override void Initialize() if (ForceBarTextures == null) { ForceBarTextures = SharedTextureManager.Get(Owner.Viewer.RenderProcess.GraphicsDevice, System.IO.Path.Combine(Owner.Viewer.ContentPath, - "BarGraph.png")); + "TrainForcesSprites.png")); } } diff --git a/Source/RunActivity/Viewer3D/Viewer.cs b/Source/RunActivity/Viewer3D/Viewer.cs index 1b45a08ac9..8ed3856511 100644 --- a/Source/RunActivity/Viewer3D/Viewer.cs +++ b/Source/RunActivity/Viewer3D/Viewer.cs @@ -102,7 +102,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 TrainForcesWindow TrainForcesWindow { get; private set; } // Alt-F7 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 From 95b1159421746d7f6005fe20e90dd25835a6a386 Mon Sep 17 00:00:00 2001 From: rwf-rr Date: Mon, 17 Mar 2025 16:03:32 -0700 Subject: [PATCH 15/15] Final cleanup. Reay to create PR. --- .../Viewer3D/Popups/TrainForcesWindow.cs | 88 +++++++------------ 1 file changed, 30 insertions(+), 58 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs index 422c86de8f..1a9140dce8 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainForcesWindow.cs @@ -79,47 +79,45 @@ using Orts.Simulation.Physics; using Orts.Simulation.RollingStocks; using ORTS.Common; -using System.Diagnostics; using System; -using System.IO; namespace Orts.Viewer3D.Popups { public class TrainForcesWindow : Window { - static Texture2D ForceBarTextures; - const int BarHight = 40; - const int HalfBarHight = 22; - const int BarWidth = 6; + private static Texture2D ForceBarTextures; + private const int BarHight = 40; + private const int HalfBarHight = 22; + private const int BarWidth = 6; - Train PlayerTrain; - int LastPlayerTrainCars; - bool LastPlayerLocomotiveFlippedState; + private Train PlayerTrain; + private int LastPlayerTrainCars; + private bool LastPlayerLocomotiveFlippedState; - float LimitForCouplerStrengthN = 2.2e6f; // 500k lbf, used for graph scale only - float CouplerStrengthScaleN; + private float LimitForCouplerStrengthN = 2.2e6f; // 500k lbf, used for graph scale only + private float CouplerStrengthScaleN; - float LimitForDerailForceN = 1.55e5f; // 35k lbf, used for graph scale only - float DerailForceScaleN; + private float LimitForDerailForceN = 1.55e5f; // 35k lbf, used for graph scale only + private float DerailForceScaleN; - float LimitForBrakeForceN = 2.0e5f; // 45k lbf, used for graph scale only - float BrakeForceScaleN; + private float LimitForBrakeForceN = 2.0e5f; // 45k lbf, used for graph scale only + private float BrakeForceScaleN; - Image[] CouplerForceBarGraph; - Image[] WheelForceBarGraph; - Image[] BrakeForceBarGraph; + private Image[] CouplerForceBarGraph; + private Image[] WheelForceBarGraph; + private Image[] BrakeForceBarGraph; - Label MaxCouplerForceForTextBox; - Label MaxDerailForceForTextBox; + private Label MaxCouplerForceForTextBox; + private Label MaxDerailForceForTextBox; // window size - readonly WindowTextFont Font; - readonly int TextHight; - readonly int GraphLabelWidth; - readonly int TextLineWidth; - readonly int WindowHeight; - readonly int WindowWidthMin; - readonly int WindowWidthMax; + private readonly WindowTextFont Font; + private readonly int TextHight; + private readonly int GraphLabelWidth; + private readonly int TextLineWidth; + private readonly int WindowHeight; + private readonly int WindowWidthMin; + private readonly int WindowWidthMax; /// @@ -159,7 +157,7 @@ protected internal override void Initialize() /// Resize the window to fit the bar graph for the number of cars. /// Limited by min and max size. /// - void ResizeWindow(int newWidth) + protected void ResizeWindow(int newWidth) { if (newWidth < WindowWidthMin) { newWidth = WindowWidthMin; } else if (newWidth > WindowWidthMax) { newWidth = WindowWidthMax; } @@ -312,7 +310,7 @@ public override void PrepareFrame(ElapsedTime elapsedTime, bool updateFull) /// Get static force values from consist, such as coupler strength and /// force that causes the wheel to derail. /// - protected void SetConsistProperties(Train theTrain) + private void SetConsistProperties(Train theTrain) { float lowestCouplerBreakN = LimitForCouplerStrengthN; float lowestDerailForceN = LimitForDerailForceN; @@ -349,7 +347,7 @@ protected void SetConsistProperties(Train theTrain) /// Update the coupler force (longitudinal) icon for a car. The image has 19 icons; /// index 0 is max push, 9 is neutral, 18 is max pull. /// - protected void UpdateCouplerForceImage(TrainCar car, int carPosition) + private void UpdateCouplerForceImage(TrainCar car, int carPosition) { var idx = 9; // neutral var absForceN = Math.Abs(car.SmoothedCouplerForceUN); @@ -373,7 +371,7 @@ protected void UpdateCouplerForceImage(TrainCar car, int carPosition) /// Update the wheel force (lateral) icon for a car. The image has 19 icons; /// index 0 is max push (outside), 9 is neutral, 18 is max pull (inside). /// - protected void UpdateWheelForceImage(TrainCar car, int carPosition) + private void UpdateWheelForceImage(TrainCar car, int carPosition) { var idx = 9; // neutral @@ -392,19 +390,6 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) if (car.CouplerForceU > 0 && car.CouplerSlackM < 0) { directionalScaleN /= 1.77f; } // push to outside else if (car.CouplerForceU < 0 && car.CouplerSlackM > 0) { directionalScaleN /= 1.34f; } // pull to inside -#if DEBUG - if (car.DerailmentCoefficient > 1.0f) - { - var numWheels = car.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } - var wheelDerailForceN = car.MassKG / numWheels * car.GetGravitationalAccelerationMpS2(); - - Debug.WriteLine("DebugCoeff > 1: car-no {0}, car-id {1}, mass {2}, axles {3}, bogies {10}, vertical {4}, lateral {5}, Coeff {6}, limit {7}, abs {8}, scale {9}", - carPosition, car.CarID, FormatStrings.FormatLargeMass(car.MassKG, false, false), car.GetWagonNumAxles(), FormatStrings.FormatLargeForce(car.TotalWagonVerticalDerailForceN, false), - FormatStrings.FormatLargeForce(car.TotalWagonLateralDerailForceN, false), car.DerailmentCoefficient, FormatStrings.FormatLargeForce(wheelDerailForceN, false), - FormatStrings.FormatLargeForce(absForceN, false), FormatStrings.FormatLargeForce(DerailForceScaleN, false), car.WagonNumBogies); - } -#endif - if (absForceN > 1000f && DerailForceScaleN > 1000f) // exclude improbable values { // flatter scale due to discrete curve radus: 1k lbf, 21%, 37%, 51%, 64%, 74%, 84%, 93%, 100% @@ -415,19 +400,6 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) if (idx < 0) { idx = 0; } else if (idx > 18) { idx = 18; } } -#if DEBUG - if (idx == 0 || idx == 18) - { - var numWheels = car.GetWagonNumAxles() * 2; if (numWheels <= 0) { numWheels = 4; } - var wheelDerailForceN = car.MassKG / numWheels * car.GetGravitationalAccelerationMpS2(); - - Debug.WriteLine("Idx at boundary: car-no {0}, car-id {1}, mass {2}, axles {3}, bogies {10}, vertical {4}, lateral {5}, Coeff {6}, limit {7}, abs {8}, scale {9}", - carPosition, car.CarID, FormatStrings.FormatLargeMass(car.MassKG, false, false), car.GetWagonNumAxles(), FormatStrings.FormatLargeForce(car.TotalWagonVerticalDerailForceN, false), - FormatStrings.FormatLargeForce(car.TotalWagonLateralDerailForceN, false), car.DerailmentCoefficient, FormatStrings.FormatLargeForce(wheelDerailForceN, false), - FormatStrings.FormatLargeForce(absForceN, false), FormatStrings.FormatLargeForce(DerailForceScaleN, false), car.WagonNumBogies); - } -#endif - if (car.WagonType == TrainCar.WagonTypes.Engine) { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, 0, BarWidth, BarHight); } else { WheelForceBarGraph[carPosition].Source = new Rectangle(1 + idx * BarWidth, BarHight, BarWidth, BarHight); } } @@ -436,7 +408,7 @@ protected void UpdateWheelForceImage(TrainCar car, int carPosition) /// Update the brake force icon for a car. The image has 10 icons; /// index 0 is neutral, 9 is max braking. /// - protected void UpdateBrakeForceImage(TrainCar car, int carPosition) + private void UpdateBrakeForceImage(TrainCar car, int carPosition) { var idx = 0; // neutral var absForceN = car.BrakeForceN;