From 4b4afe311f1f7e5982e6357f5e32ec1435496089 Mon Sep 17 00:00:00 2001 From: Chris Jakeman Date: Thu, 28 Dec 2023 11:57:13 +0000 Subject: [PATCH] Avoids wheelslip when the precision level is changed. Also moved IsPrecisionHigh() from Axle class to Axles. --- .../SubSystems/PowerTransmissions/Axle.cs | 180 +++++++++--------- .../RunActivity/Viewer3D/Popups/HUDWindow.cs | 2 +- 2 files changed, 92 insertions(+), 90 deletions(-) diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions/Axle.cs b/Source/Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions/Axle.cs index b59fa7afde..c394fe796f 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions/Axle.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions/Axle.cs @@ -281,23 +281,105 @@ public void Restore(BinaryReader inf) AxleList[i].Restore(inf); } } + + /// + /// switch between Polach and Pacha adhesion calculation + /// + public static bool UsePolachAdhesion = false; // "static" so there's only one value in the program. + public bool PreviousUsePolachAdhesion = false; // Keep a note for each Axles so that we can tell if it changed. + /// /// Updates each axle on the list /// - /// Time span within the simulation cycle - public void Update(float elapsedClockSeconds) + /// Time span within the simulation cycle + public void Update(float elapsedSeconds) { + UsePolachAdhesion = AdhesionPrecision.IsPrecisionHigh(this, elapsedSeconds, Car.Simulator.GameTime); foreach (var axle in AxleList) { - axle.Update(elapsedClockSeconds); + if (UsePolachAdhesion != PreviousUsePolachAdhesion) // There's been a transition + { + axle.AxleSpeedMpS = axle.TrainSpeedMpS; // So the transition doesn't cause a wheelslip + } + axle.Update(elapsedSeconds); } + PreviousUsePolachAdhesion = UsePolachAdhesion; } public List.Enumerator GetEnumerator() { return AxleList.GetEnumerator(); } - } + static class AdhesionPrecision // "static" so all "Axles" share the same level of precision + { + enum AdhesionPrecisionLevel + { + /// + /// Initial level uses Polach algorithm + /// + High = 0, + /// + /// Low-performance PCs use Pacha's algorithm + /// + Low = 1, + /// + /// After frequent transitions, low-performance PCs are locked to Pacha's algorithm + /// + LowLocked = 2 + } + + // Adjustable limits + const float LowerLimitS = 0.025f; // timespan 0.025 = 40 fps screen rate, low timeSpan and high FPS + const float UpperLimitS = 0.033f; // timespan 0.033 = 30 fps screen rate, high timeSpan and low FPS + const double IntervalBetweenDowngradesLimitS = 5 * 60; // Locks in low precision if < 5 mins between downgrades + + static AdhesionPrecisionLevel PrecisionLevel = AdhesionPrecisionLevel.High; + static double TimeOfLatestDowngrade = 0 - IntervalBetweenDowngradesLimitS; // Starts at -5 mins + + // Tested by dropping the framerate below 30 fps interactively. Did this by opening and closing the HelpWindow after inserting + // Threading.Thread.Sleep(40); + // into HelpWindow.PrepareFrame() temporarily. + public static bool IsPrecisionHigh(Axles axles, float elapsedSeconds, double gameTime) + { + // Switches between Polach (high precision) adhesion model and Pacha (low precision) adhesion model depending upon the PC performance + switch (PrecisionLevel) + { + case AdhesionPrecisionLevel.High: + if (elapsedSeconds > UpperLimitS) + { + var screenFrameRate = 1 / elapsedSeconds; + var timeSincePreviousDowngradeS = gameTime - TimeOfLatestDowngrade; + if (timeSincePreviousDowngradeS < IntervalBetweenDowngradesLimitS) + { + Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision permanently after {timeSincePreviousDowngradeS:F0} secs since previous switch (less than limit of {IntervalBetweenDowngradesLimitS})"); + PrecisionLevel = AdhesionPrecisionLevel.LowLocked; + } + else + { + TimeOfLatestDowngrade = gameTime; + Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision after low frame rate {screenFrameRate:F1} below limit {1 / UpperLimitS:F0}"); + PrecisionLevel = AdhesionPrecisionLevel.Low; + } + } + break; + + case AdhesionPrecisionLevel.Low: + if (elapsedSeconds > 0 // When debugging step by step, elapsedSeconds == 0, so test for that + && elapsedSeconds < LowerLimitS) + { + PrecisionLevel = AdhesionPrecisionLevel.High; + var ScreenFrameRate = 1 / elapsedSeconds; + Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to high precision after high frame rate {ScreenFrameRate:F1} above limit {1 / LowerLimitS:F0}"); + } + break; + + case AdhesionPrecisionLevel.LowLocked: + break; + } + return (PrecisionLevel == AdhesionPrecisionLevel.High); + } + } + } /// @@ -428,12 +510,6 @@ public float InertiaKgm2 /// float forceToAccelerationFactor; - /// - /// switch between Polach and Pacha adhesion calculation - /// - public static bool UsePolachAdhesion = false; // "static" so it's shared by all axles of the Player's loco - public double GameTime; // Set by MSTSLocomotive and used by AdhesionPrecision.IsPrecisionHigh - /// /// Pre-calculation of slip characteristics at 0 slip speed /// @@ -546,7 +622,7 @@ public float TransmissionEfficiency /// /// Axle speed value, in metric meters per second /// - public double AxleSpeedMpS { get; private set; } + public double AxleSpeedMpS { get; set; } /// /// Axle angular position in radians @@ -824,7 +900,7 @@ public void Save(BinaryWriter outf) double slipSpeedMpS = axleSpeedMpS - TrainSpeedMpS; double axleOutForceN = 0; - if (UsePolachAdhesion) + if (Axles.UsePolachAdhesion) { axleOutForceN = Math.Sign(slipSpeedMpS) * AxleWeightN * SlipCharacteristicsPolach(slipSpeedMpS); } @@ -870,7 +946,7 @@ void Integrate(float elapsedClockSeconds) if (elapsedClockSeconds <= 0) return; double prevSpeedMpS = AxleSpeedMpS; - if (UsePolachAdhesion) + if (Axles.UsePolachAdhesion) { float upperSubStepLimit = 100; @@ -959,7 +1035,7 @@ void Integrate(float elapsedClockSeconds) { var k1 = GetAxleMotionVariation(AxleSpeedMpS, dt); - if (i == 0 && !UsePolachAdhesion) + if (i == 0 && !Axles.UsePolachAdhesion) { if (k1.Item1 * dt > Math.Max((Math.Abs(SlipSpeedMpS) - 1) * 10, 1) / 100) { @@ -1002,8 +1078,7 @@ void Integrate(float elapsedClockSeconds) /// public virtual void Update(float elapsedSeconds) { - UsePolachAdhesion = AdhesionPrecision.IsPrecisionHigh(elapsedSeconds, GameTime); - if (UsePolachAdhesion) + if (Axles.UsePolachAdhesion) { forceToAccelerationFactor = WheelRadiusM * WheelRadiusM / totalInertiaKgm2; @@ -1098,79 +1173,6 @@ public virtual void Update(float elapsedSeconds) } } - static class AdhesionPrecision // "static" so all "Axle"s share the same level of precision - { - enum AdhesionPrecisionLevel - { - /// - /// Initial level uses Polach algorithm - /// - High = 0, - /// - /// Low-performance PCs use Pacha's algorithm - /// - Low = 1, - /// - /// After frequent transitions, low-performance PCs are locked to Pacha's algorithm - /// - LowLocked = 2 - } - - // Adjustable limits - const float LowerLimitS = 0.025f; // timespan 0.025 = 40 fps screen rate, low timeSpan and high FPS - const float UpperLimitS = 0.033f; // timespan 0.033 = 30 fps screen rate, high timeSpan and low FPS - const double IntervalBetweenDowngradesLimitS = 5 * 60; // Locks in low precision if < 5 mins between downgrades - - static AdhesionPrecisionLevel PrecisionLevel = AdhesionPrecisionLevel.High; - static double TimeOfLatestDowngrade = 0 - IntervalBetweenDowngradesLimitS; // Starts at -5 mins - - // Tested by varying the framerate interactively. Did this by opening and closing the HelpWindow after inserting - // Threading.Thread.Sleep(40); - // into HelpWindow.PrepareFrame() temporarily. - public static bool IsPrecisionHigh(float elapsedSeconds, double gameTime) - { - // Switches between Polach (high precision) adhesion model and Pacha (low precision) adhesion model depending upon the PC performance - switch (PrecisionLevel) - { - case AdhesionPrecisionLevel.High: - if (elapsedSeconds > UpperLimitS) - { - var screenFrameRate = 1 / elapsedSeconds; - var timeSincePreviousDowngradeS = gameTime - TimeOfLatestDowngrade; - if (timeSincePreviousDowngradeS < IntervalBetweenDowngradesLimitS) - { - Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision permanently after {timeSincePreviousDowngradeS:F0} secs since previous switch (less than limit of {IntervalBetweenDowngradesLimitS})"); - PrecisionLevel = AdhesionPrecisionLevel.LowLocked; - } - else - { - TimeOfLatestDowngrade = gameTime; - - Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision after low frame rate {screenFrameRate:F1} below limit {1 / UpperLimitS:F0}"); - PrecisionLevel = AdhesionPrecisionLevel.Low; - - } - } - break; - - case AdhesionPrecisionLevel.Low: - if (elapsedSeconds > 0 // When debugging step by step, elapsedSeconds == 0, so test for that - && elapsedSeconds < LowerLimitS) - { - PrecisionLevel = AdhesionPrecisionLevel.High; - var ScreenFrameRate = 1 / elapsedSeconds; - Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to high precision after high frame rate {ScreenFrameRate:F1} above limit {1 / LowerLimitS:F0}"); - } - break; - - case AdhesionPrecisionLevel.LowLocked: - break; - - } - return (PrecisionLevel == AdhesionPrecisionLevel.High); - } - } - class PolachCalculator { Axle Axle; diff --git a/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs b/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs index 521172d820..4611d83f2b 100644 --- a/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs @@ -1080,7 +1080,7 @@ void TextPageForceInfo(TableData table) if (mstsLocomotive.AdvancedAdhesionModel) { var text = Viewer.Catalog.GetString("(Advanced adhesion model)"); - if (Axle.UsePolachAdhesion == false) text += "???"; + if (Axles.UsePolachAdhesion == false) text += "???"; TableAddLine(table, text); int row0 = table.CurrentRow; TableSetCell(table, table.CurrentRow++, table.CurrentLabelColumn, Viewer.Catalog.GetString("Wheel slip (Thres)"));