Skip to content

Commit 62061b8

Browse files
committed
Improve dynamic track superelevation
1 parent c8a37a7 commit 62061b8

File tree

5 files changed

+122
-109
lines changed

5 files changed

+122
-109
lines changed

Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,7 +1952,11 @@ public virtual void UpdateCurveSpeedLimit(float elapsedClockSeconds)
19521952
// If speed exceeds the overturning speed, then indicated that an error condition has been reached.
19531953
if (s > CriticalMaxSpeedMpS)
19541954
{
1955-
if (!IsCriticalMaxSpeed)
1955+
// Consider a tolerance so error isn't immediately thrown, should reduce overkill notifications on routes with jerky track laying
1956+
// Will be reduced faster if simultaneously above the max safe speed
1957+
ComfortTolerance -= 0.25f * (s / MaxSafeCurveSpeedMps - 1.0f) * elapsedClockSeconds;
1958+
1959+
if (!IsCriticalMaxSpeed && ComfortTolerance <= 0.0f)
19561960
{
19571961
IsCriticalMaxSpeed = true; // set flag for IsCriticalSpeed reached
19581962

@@ -2896,7 +2900,10 @@ public void UpdateCurvePhys(Traveller traveller, float[] offsets)
28962900
// Set superelevation angle used by physics system
28972901
SuperElevationAngleRad = (float)Math.Asin(SuperelevationM / TrackGaugeM);
28982902

2899-
CurrentCurveRadiusM = curveRadii.Min();
2903+
CurrentCurveRadiusM = curveRadii.Average();
2904+
// Straight track has a "radius" of infinity, but rest of code expects straight to have a "radius" of 0
2905+
if (CurrentCurveRadiusM == float.PositiveInfinity)
2906+
CurrentCurveRadiusM = 0;
29002907
}
29012908
#endregion
29022909

Source/Orts.Simulation/Simulation/SuperElevation.cs

Lines changed: 43 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -162,63 +162,64 @@ void MarkSections(Simulator simulator, List<TrVectorSection> SectionList, float
162162
if (standard == null)
163163
{
164164
foreach (TrVectorSection s in SectionList)
165-
s.NomElevM = 0;
166-
return; // No superelevation needed, stop processing here
165+
s.NomElevM = 0; // No superelevation needed
167166
}
168-
if ((standard.MinCantM / effectiveRunoffSlope) * 2.0f > totLen * 0.75f)
167+
else if ((standard.MinCantM / effectiveRunoffSlope) * 2.0f > totLen * 0.75f)
169168
{
170169
foreach (TrVectorSection s in SectionList)
171-
s.NomElevM = 0;
172-
return; // Curve is so short that no meaningful superelevation can be applied
170+
s.NomElevM = 0; // Curve is so short that no meaningful superelevation can be applied
173171
}
174-
175-
// Determine proper level of superelevation for every section
176-
for (int i = 0; i < SectionList.Count; i++)
172+
else
177173
{
178-
// Superelevation has not been calculated for this section yet
179-
if (SectionList[i].NomElevM < 0.0f)
174+
// Superelevation can be applied, run calculations
175+
foreach (TrVectorSection s in SectionList)
180176
{
181-
var sectionData = simulator.TSectionDat.TrackSections.Get(SectionList[i].SectionIndex);
182-
183-
if (sectionData == null || sectionData.SectionCurve == null)
177+
// Superelevation has not been calculated for this section yet
178+
// FUTURE: Superelevation NomElevM may be specified externally, eg: by a route editor
179+
if (s.NomElevM < 0.0f)
184180
{
185-
SectionList[i].NomElevM = 0.0f;
186-
continue;
187-
}
188-
else
189-
{
190-
float superElevation;
181+
var sectionData = simulator.TSectionDat.TrackSections.Get(s.SectionIndex);
191182

192-
// Support for old system with superelevation set directly in Route (TRK) file
193-
if (standard.UseLegacyCalculation && simulator.TRK.Tr_RouteFile.SuperElevationHgtpRadiusM != null)
183+
if (sectionData == null || sectionData.SectionCurve == null)
194184
{
195-
superElevation = simulator.TRK.Tr_RouteFile.SuperElevationHgtpRadiusM[sectionData.SectionCurve.Radius];
185+
s.NomElevM = 0.0f;
186+
continue;
196187
}
197-
else // Newer standard for calculating superelevation
188+
else
198189
{
199-
if (standard != null)
200-
{
201-
float paxSpeed = SectionList[i].PassSpeedMpS;
202-
float freightSpeed = SectionList[i].FreightSpeedMpS;
203-
204-
// Approximate ideal level of superelevation determined using E = (G*V^2) / (g*R), then subtract off cant deficiency
205-
// For different speeds on the same curve, we can factor out speed and get a constant c = G / (g*R)
206-
float elevationFactor = simulator.SuperElevationGauge / (9.81f * sectionData.SectionCurve.Radius);
207-
// Calculate required superelevation for passenger and freight separately
208-
float paxElevation = elevationFactor * (paxSpeed * paxSpeed) - standard.MaxPaxUnderbalanceM;
209-
float freightElevation = elevationFactor * (freightSpeed * freightSpeed) - standard.MaxFreightUnderbalanceM;
190+
float superElevation;
210191

211-
superElevation = Math.Max(paxElevation, freightElevation); // Choose the highest required superelevation
192+
// Support for old system with superelevation set directly in Route (TRK) file
193+
if (standard.UseLegacyCalculation && simulator.TRK.Tr_RouteFile.SuperElevationHgtpRadiusM != null)
194+
{
195+
superElevation = simulator.TRK.Tr_RouteFile.SuperElevationHgtpRadiusM[sectionData.SectionCurve.Radius];
212196
}
213-
else // No superelevation needed (shouldn't reach this point, this is a failsafe)
214-
superElevation = 0.0f;
215-
}
216-
superElevation = (float)Math.Round(superElevation / standard.PrecisionM, MidpointRounding.AwayFromZero)
217-
* standard.PrecisionM; // Round superelevation amount to next higher increment of precision
197+
else // Newer standard for calculating superelevation
198+
{
199+
if (standard != null)
200+
{
201+
float paxSpeed = s.PassSpeedMpS;
202+
float freightSpeed = s.FreightSpeedMpS;
203+
204+
// Approximate ideal level of superelevation determined using E = (G*V^2) / (g*R), then subtract off cant deficiency
205+
// For different speeds on the same curve, we can factor out speed and get a constant c = G / (g*R)
206+
float elevationFactor = simulator.SuperElevationGauge / (9.81f * sectionData.SectionCurve.Radius);
207+
// Calculate required superelevation for passenger and freight separately
208+
float paxElevation = elevationFactor * (paxSpeed * paxSpeed) - standard.MaxPaxUnderbalanceM;
209+
float freightElevation = elevationFactor * (freightSpeed * freightSpeed) - standard.MaxFreightUnderbalanceM;
210+
211+
superElevation = Math.Max(paxElevation, freightElevation); // Choose the highest required superelevation
212+
}
213+
else // No superelevation needed (shouldn't reach this point, this is a failsafe)
214+
superElevation = 0.0f;
215+
}
216+
superElevation = (float)Math.Round(superElevation / standard.PrecisionM, MidpointRounding.AwayFromZero)
217+
* standard.PrecisionM; // Round superelevation amount to next higher increment of precision
218218

219-
superElevation = MathHelper.Clamp(superElevation, standard.MinCantM, maxElev);
219+
superElevation = MathHelper.Clamp(superElevation, standard.MinCantM, maxElev);
220220

221-
SectionList[i].NomElevM = superElevation;
221+
s.NomElevM = superElevation;
222+
}
222223
}
223224
}
224225
}

Source/Orts.Simulation/Simulation/Traveller.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ public float GetCurvature()
980980
public void GetCurveData(out float physicsElev, out float curveRadius)
981981
{
982982
physicsElev = 0;
983-
curveRadius = 0;
983+
curveRadius = float.PositiveInfinity;
984984

985985
if (trackSection == null || trackVectorSection == null)
986986
return;

Source/RunActivity/Viewer3D/Scenery.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -376,20 +376,22 @@ public WorldFile(Viewer viewer, int tileX, int tileZ, bool visible)
376376
// We might not have found the junction node; if so, fall back to the static track shape.
377377
if (trJunctionNode != null)
378378
{
379+
// Handle superelevation on junctions (usually this means disabling it)
379380
if (viewer.Simulator.UseSuperElevation)
380-
SuperElevationManager.DecomposeStaticSuperElevation(viewer, dTrackList, trackObj, worldMatrix, TileX, TileZ, shapeFilePath);
381+
SuperElevationManager.PrepareStaticSuperElevation(viewer, trackObj, TileX, TileZ, shapeFilePath);
381382
sceneryObjects.Add(new SwitchTrackShape(viewer, shapeFilePath, worldMatrix, trJunctionNode));
382383
}
383384
else
384385
{
385-
//if want to use super elevation, we will generate tracks using dynamic tracks
386+
// See if superelevation should be used on this piece of track
386387
if (viewer.Simulator.UseSuperElevation
387-
&& SuperElevationManager.DecomposeStaticSuperElevation(viewer, dTrackList, trackObj, worldMatrix, TileX, TileZ, shapeFilePath))
388+
&& SuperElevationManager.PrepareStaticSuperElevation(viewer, trackObj, TileX, TileZ, shapeFilePath))
388389
{
389-
// No need to add shapes for this segment of track
390+
// Don't add scenery for this section of track, dynamic superelevated track will be created instead
390391
}
391-
//otherwise, use shapes
392-
else if (!containsMovingTable) sceneryObjects.Add(new StaticTrackShape(viewer, shapeFilePath, worldMatrix));
392+
// No superelevation, use shapes
393+
else if (!containsMovingTable)
394+
sceneryObjects.Add(new StaticTrackShape(viewer, shapeFilePath, worldMatrix));
393395
else
394396
{
395397
var found = false;
@@ -429,22 +431,24 @@ public WorldFile(Viewer viewer, int tileX, int tileZ, bool visible)
429431
}
430432
else if (worldObject.GetType() == typeof(DyntrackObj))
431433
{
434+
// Dynamic track will require track profiles, set up track profiles if not yet set
432435
if (viewer.TRPs == null)
433436
{
434-
// First to need a track profile creates it
435437
Trace.Write(" TRP");
436-
// Creates profile and loads materials into SceneryMaterials
438+
// Creates profile(s) and loads materials into SceneryMaterials
437439
TRPFile.CreateTrackProfile(viewer, viewer.Simulator.RoutePath, out viewer.TRPs);
438440
}
439441

440442
if (viewer.Simulator.Settings.Wire == true && viewer.Simulator.TRK.Tr_RouteFile.Electrified == true)
441443
Wire.DecomposeDynamicWire(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix);
442444
// Add DyntrackDrawers for individual subsections
443-
if (viewer.Simulator.UseSuperElevation && SuperElevationManager.UseSuperElevationDyn(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix))
445+
if (viewer.Simulator.UseSuperElevation && SuperElevationManager.UseSuperElevationDyn((DyntrackObj)worldObject))
444446
SuperElevationManager.DecomposeDynamicSuperElevation(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix);
445-
else DynamicTrack.Decompose(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix);
447+
else
448+
DynamicTrack.Decompose(viewer, dTrackList, (DyntrackObj)worldObject, worldMatrix);
446449

447-
} // end else if DyntrackObj
450+
}
451+
// Objects other than tracks
448452
else if (worldObject.GetType() == typeof(ForestObj))
449453
{
450454
if (!(worldObject as ForestObj).IsYard)
@@ -593,8 +597,9 @@ public WorldFile(Viewer viewer, int tileX, int tileZ, bool visible)
593597
}
594598
}
595599

596-
if (viewer.Simulator.UseSuperElevation) SuperElevationManager.DecomposeStaticSuperElevation(Viewer, dTrackList, TileX, TileZ);
597-
if (Viewer.World.Sounds != null) Viewer.World.Sounds.AddByTile(TileX, TileZ);
600+
if (viewer.Simulator.UseSuperElevation)
601+
SuperElevationManager.DecomposeTileSuperElevation(Viewer, dTrackList, TileX, TileZ);
602+
Viewer.World.Sounds?.AddByTile(TileX, TileZ);
598603
}
599604

600605
/// <summary>

0 commit comments

Comments
 (0)