Skip to content

Commit e0bf062

Browse files
authored
Merge pull request #908 from cjakeman/feature/advanced-adhesion-switching2
feat: supports switching adhesion precisions
2 parents bae4730 + ad3362c commit e0bf062

File tree

3 files changed

+103
-70
lines changed
  • Source

3 files changed

+103
-70
lines changed

Source/Documentation/Manual/physics.rst

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,17 @@ wheel adhesion. The first model is based upon an algorithm by Pacha, whilst the
271271
uses an algorithm developed by Polach. The Polach algorithm provides
272272
a more accurate outcome and facilitates the future inclusion of track conditions.
273273
However due to the number of algorithm steps required to calculate the wheel adhesion
274-
value, it is more CPU load intensive then the Pacha one. This can produce low
275-
frame rates for the screen display in machines with low performance specifications.
274+
value, it is more CPU load-intensive then the Pacha one. On low performance PCs, this would lower the
275+
frame rate for the screen display to an unacceptable degree.
276276

277-
Hence OR automatically senses the CPU load, and switches to the Pacha algorithm at
278-
high loads and to the Polach algorithm under lower CPU loads. In this way OR attempts
279-
to support the operation of lower specification computers. When OR is using the
277+
To avoid this, OR senses the frame rate and switches from the Polach algorithm
278+
to the Pacha one as follows.
279+
If the frame rate falls below 30 fps, then a switch is made to Pacha until the frame rate
280+
recovers to more than 40 fps. If a switch to Pacha happens more than once in a 5 minute interval
281+
then it will persist for the rest of the session.
282+
283+
In this way OR provides a more accurate algorithm whilst retaining
284+
the original one for lower specification computers. When OR is using the
280285
Pacha algorithm, the "Wheel Adh (Max)" values will both read 99%, whereas when the
281286
Polach algorithm is being used these values will be around the expected values of 30-55%.
282287

@@ -288,6 +293,7 @@ The heart of the adhesion algorithm is the slip characteristics (pictured below)
288293
:align: center
289294
:scale: 70%
290295

296+
291297
The *wheel creep* describes the stable area of the characteristics and is
292298
used in the most of the operation time. When the tractive force reaches
293299
the actual maximum of the slip characteristics, force transition falls

Source/Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions/Axle.cs

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -295,23 +295,105 @@ public void Restore(BinaryReader inf)
295295
AxleList[i].Restore(inf);
296296
}
297297
}
298+
299+
/// <summary>
300+
/// switch between Polach and Pacha adhesion calculation
301+
/// </summary>
302+
public static bool UsePolachAdhesion = false; // "static" so there's only one value in the program.
303+
public bool PreviousUsePolachAdhesion = false; // Keep a note for each Axles so that we can tell if it changed.
304+
298305
/// <summary>
299306
/// Updates each axle on the list
300307
/// </summary>
301-
/// <param name="elapsedClockSeconds">Time span within the simulation cycle</param>
302-
public void Update(float elapsedClockSeconds)
308+
/// <param name="elapsedSeconds">Time span within the simulation cycle</param>
309+
public void Update(float elapsedSeconds)
303310
{
311+
UsePolachAdhesion = AdhesionPrecision.IsPrecisionHigh(this, elapsedSeconds, Car.Simulator.GameTime);
304312
foreach (var axle in AxleList)
305313
{
306-
axle.Update(elapsedClockSeconds);
314+
if (UsePolachAdhesion != PreviousUsePolachAdhesion) // There's been a transition
315+
{
316+
axle.AxleSpeedMpS = axle.TrainSpeedMpS; // So the transition doesn't cause a wheelslip
317+
}
318+
axle.Update(elapsedSeconds);
307319
}
320+
PreviousUsePolachAdhesion = UsePolachAdhesion;
308321
}
309322
public List<Axle>.Enumerator GetEnumerator()
310323
{
311324
return AxleList.GetEnumerator();
312325
}
313-
}
314326

327+
static class AdhesionPrecision // "static" so all "Axles" share the same level of precision
328+
{
329+
enum AdhesionPrecisionLevel
330+
{
331+
/// <summary>
332+
/// Initial level uses Polach algorithm
333+
/// </summary>
334+
High = 0,
335+
/// <summary>
336+
/// Low-performance PCs use Pacha's algorithm
337+
/// </summary>
338+
Low = 1,
339+
/// <summary>
340+
/// After frequent transitions, low-performance PCs are locked to Pacha's algorithm
341+
/// </summary>
342+
LowLocked = 2
343+
}
344+
345+
// Adjustable limits
346+
const float LowerLimitS = 0.025f; // timespan 0.025 = 40 fps screen rate, low timeSpan and high FPS
347+
const float UpperLimitS = 0.033f; // timespan 0.033 = 30 fps screen rate, high timeSpan and low FPS
348+
const double IntervalBetweenDowngradesLimitS = 5 * 60; // Locks in low precision if < 5 mins between downgrades
349+
350+
static AdhesionPrecisionLevel PrecisionLevel = AdhesionPrecisionLevel.High;
351+
static double TimeOfLatestDowngrade = 0 - IntervalBetweenDowngradesLimitS; // Starts at -5 mins
352+
353+
// Tested by dropping the framerate below 30 fps interactively. Did this by opening and closing the HelpWindow after inserting
354+
// Threading.Thread.Sleep(40);
355+
// into HelpWindow.PrepareFrame() temporarily.
356+
public static bool IsPrecisionHigh(Axles axles, float elapsedSeconds, double gameTime)
357+
{
358+
// Switches between Polach (high precision) adhesion model and Pacha (low precision) adhesion model depending upon the PC performance
359+
switch (PrecisionLevel)
360+
{
361+
case AdhesionPrecisionLevel.High:
362+
if (elapsedSeconds > UpperLimitS)
363+
{
364+
var screenFrameRate = 1 / elapsedSeconds;
365+
var timeSincePreviousDowngradeS = gameTime - TimeOfLatestDowngrade;
366+
if (timeSincePreviousDowngradeS < IntervalBetweenDowngradesLimitS)
367+
{
368+
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})");
369+
PrecisionLevel = AdhesionPrecisionLevel.LowLocked;
370+
}
371+
else
372+
{
373+
TimeOfLatestDowngrade = gameTime;
374+
Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision after low frame rate {screenFrameRate:F1} below limit {1 / UpperLimitS:F0}");
375+
PrecisionLevel = AdhesionPrecisionLevel.Low;
376+
}
377+
}
378+
break;
379+
380+
case AdhesionPrecisionLevel.Low:
381+
if (elapsedSeconds > 0 // When debugging step by step, elapsedSeconds == 0, so test for that
382+
&& elapsedSeconds < LowerLimitS)
383+
{
384+
PrecisionLevel = AdhesionPrecisionLevel.High;
385+
var ScreenFrameRate = 1 / elapsedSeconds;
386+
Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to high precision after high frame rate {ScreenFrameRate:F1} above limit {1 / LowerLimitS:F0}");
387+
}
388+
break;
389+
390+
case AdhesionPrecisionLevel.LowLocked:
391+
break;
392+
}
393+
return (PrecisionLevel == AdhesionPrecisionLevel.High);
394+
}
395+
}
396+
}
315397

316398

317399
/// <summary>
@@ -446,11 +528,6 @@ public float InertiaKgm2
446528
/// </summary>
447529
float forceToAccelerationFactor;
448530

449-
/// <summary>
450-
/// switch between Polach and Pacha adhesion calculation
451-
/// </summary>
452-
public static bool UsePolachAdhesion = false; // "static" so it's shared by all axles of the Player's loco
453-
454531
/// <summary>
455532
/// Pre-calculation of slip characteristics at 0 slip speed
456533
/// </summary>
@@ -578,7 +655,7 @@ public float TransmissionEfficiency
578655
/// <summary>
579656
/// Axle speed value, in metric meters per second
580657
/// </summary>
581-
public double AxleSpeedMpS { get; private set; }
658+
public double AxleSpeedMpS { get; set; }
582659

583660
/// <summary>
584661
/// Axle angular position in radians
@@ -860,7 +937,7 @@ public void Save(BinaryWriter outf)
860937
double slipSpeedMpS = axleSpeedMpS - TrainSpeedMpS;
861938
double axleOutForceN = 0;
862939

863-
if (UsePolachAdhesion)
940+
if (Axles.UsePolachAdhesion)
864941
{
865942
axleOutForceN = Math.Sign(slipSpeedMpS) * AxleWeightN * SlipCharacteristicsPolach(slipSpeedMpS);
866943
}
@@ -906,7 +983,7 @@ void Integrate(float elapsedClockSeconds)
906983
if (elapsedClockSeconds <= 0) return;
907984
double prevSpeedMpS = AxleSpeedMpS;
908985

909-
if (UsePolachAdhesion)
986+
if (Axles.UsePolachAdhesion)
910987
{
911988

912989
float upperSubStepLimit = 100;
@@ -995,7 +1072,7 @@ void Integrate(float elapsedClockSeconds)
9951072
{
9961073
var k1 = GetAxleMotionVariation(AxleSpeedMpS, dt);
9971074

998-
if (i == 0 && !UsePolachAdhesion)
1075+
if (i == 0 && !Axles.UsePolachAdhesion)
9991076
{
10001077
if (k1.Item1 * dt > Math.Max((Math.Abs(SlipSpeedMpS) - 1) * 10, 1) / 100)
10011078
{
@@ -1038,8 +1115,7 @@ void Integrate(float elapsedClockSeconds)
10381115
/// <param name="elapsedSeconds"></param>
10391116
public virtual void Update(float elapsedSeconds)
10401117
{
1041-
UsePolachAdhesion = AdhesionPrecision.IsPrecisionHigh(elapsedSeconds);
1042-
if (UsePolachAdhesion)
1118+
if (Axles.UsePolachAdhesion)
10431119
{
10441120
forceToAccelerationFactor = WheelRadiusM * WheelRadiusM / totalInertiaKgm2;
10451121

@@ -1143,55 +1219,6 @@ public virtual void Update(float elapsedSeconds)
11431219
}
11441220
}
11451221

1146-
static class AdhesionPrecision // "static" so all "Axle"s share the same level of precision
1147-
{
1148-
enum AdhesionPrecisionLevel
1149-
{
1150-
/// <summary>
1151-
/// Initial level uses Polach algorithm
1152-
/// </summary>
1153-
High,
1154-
/// <summary>
1155-
/// Low-performance PCs use Pacha's algorithm
1156-
/// </summary>
1157-
Low
1158-
}
1159-
1160-
static AdhesionPrecisionLevel PrecisionLevel = AdhesionPrecisionLevel.High;
1161-
static double TimeOfLatestDowngrade = 0;
1162-
1163-
// Adjustable limits
1164-
const float UpperLimitS = 0.033f; // timespan 0.033 = 30 fps screen rate, high timeSpan and low FPS
1165-
1166-
// Tested by varying the framerate interactively. Did this by opening and closing the HelpWindow after inserting
1167-
// Threading.Thread.Sleep(40);
1168-
// into HelpWindow.PrepareFrame() temporarily.
1169-
public static bool IsPrecisionHigh(float elapsedSeconds)
1170-
{
1171-
if (elapsedSeconds > 0) // Ignore period with elapsedSeconds == 0 until user starts game.
1172-
{
1173-
// Switches between Polach (high precision) adhesion model and Pacha (low precision) adhesion model depending upon the PC performance
1174-
switch (PrecisionLevel)
1175-
{
1176-
case AdhesionPrecisionLevel.High:
1177-
if (elapsedSeconds > UpperLimitS)
1178-
{
1179-
var screenFrameRate = 1 / elapsedSeconds;
1180-
{
1181-
Trace.TraceInformation($"Advanced adhesion model switched to low precision permanently after low frame rate {screenFrameRate:F1} below limit {1 / UpperLimitS:F0}");
1182-
PrecisionLevel = AdhesionPrecisionLevel.Low;
1183-
}
1184-
}
1185-
break;
1186-
1187-
case AdhesionPrecisionLevel.Low:
1188-
break;
1189-
}
1190-
}
1191-
return (PrecisionLevel == AdhesionPrecisionLevel.High);
1192-
}
1193-
}
1194-
11951222
class PolachCalculator
11961223
{
11971224
Axle Axle;

Source/RunActivity/Viewer3D/Popups/HUDWindow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ void TextPageForceInfo(TableData table)
11531153
if (mstsLocomotive.AdvancedAdhesionModel)
11541154
{
11551155
var text = Viewer.Catalog.GetString("(Advanced adhesion model)");
1156-
if (Axle.UsePolachAdhesion == false) text += "???";
1156+
if (Axles.UsePolachAdhesion == false) text += "???";
11571157
TableAddLine(table, text);
11581158
int row0 = table.CurrentRow;
11591159
TableSetCell(table, table.CurrentRow++, table.CurrentLabelColumn, Viewer.Catalog.GetString("Wheel slip (Thres)"));

0 commit comments

Comments
 (0)