Skip to content

Commit 95a022f

Browse files
committed
Addition of Derail Coefficient and further coupler animation
1 parent 99b646c commit 95a022f

File tree

3 files changed

+131
-64
lines changed

3 files changed

+131
-64
lines changed

Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,23 @@ public virtual void LoadFromWagFile(string wagFilePath)
436436
}
437437
}
438438

439+
// Initialise car body lengths. Assume overhang is 2.0m each end, and bogie centres are the car length minus this value
440+
441+
if (CarCouplerFaceLengthM == 0)
442+
{
443+
CarCouplerFaceLengthM = CarLengthM;
444+
}
445+
446+
if (CarBodyLengthM == 0)
447+
{
448+
CarBodyLengthM = CarCouplerFaceLengthM - 0.8f;
449+
}
450+
451+
if (CarBogieCentreLengthM == 0)
452+
{
453+
CarBogieCentreLengthM = (CarCouplerFaceLengthM - 4.3f);
454+
}
455+
439456
// Ensure Drive Axles is set to a default if no OR value added to WAG file
440457
if (WagonNumAxles == 0)
441458
{
@@ -964,6 +981,9 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
964981
CarLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
965982
stf.SkipRestOfBlock();
966983
break;
984+
case "wagon(ortslengthbogiecentre": CarBogieCentreLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
985+
case "wagon(ortslengthcarbody": CarBodyLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
986+
case "wagon(ortslengthcouplerface": CarCouplerFaceLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break;
967987
case "wagon(ortstrackgauge":
968988
stf.MustMatch("(");
969989
TrackGaugeM = stf.ReadFloat(STFReader.UNITS.Distance, null);
@@ -1247,7 +1267,7 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
12471267
case "wagon(orts3dcab": Parse3DCab(stf); break;
12481268
case "wagon(numwheels": MSTSWagonNumWheels= stf.ReadFloatBlock(STFReader.UNITS.None, 4.0f); break;
12491269
case "wagon(ortsnumberaxles": WagonNumAxles = stf.ReadIntBlock(null); break;
1250-
1270+
case "wagon(ortsnumberbogies": WagonNumBogies = stf.ReadIntBlock(null); break;
12511271
case "wagon(ortspantographs":
12521272
Pantographs.Parse(lowercasetoken, stf);
12531273
break;
@@ -1330,10 +1350,14 @@ public virtual void Copy(MSTSWagon copy)
13301350
InitialCentreOfGravityM = copy.InitialCentreOfGravityM;
13311351
UnbalancedSuperElevationM = copy.UnbalancedSuperElevationM;
13321352
RigidWheelBaseM = copy.RigidWheelBaseM;
1353+
CarBogieCentreLengthM = copy.CarBogieCentreLengthM;
1354+
CarBodyLengthM = copy.CarBodyLengthM;
1355+
CarCouplerFaceLengthM = copy.CarCouplerFaceLengthM;
13331356
AuxTenderWaterMassKG = copy.AuxTenderWaterMassKG;
13341357
TenderWagonMaxCoalMassKG = copy.TenderWagonMaxCoalMassKG;
13351358
TenderWagonMaxWaterMassKG = copy.TenderWagonMaxWaterMassKG;
13361359
WagonNumAxles = copy.WagonNumAxles;
1360+
WagonNumBogies = copy.WagonNumBogies;
13371361
MSTSWagonNumWheels = copy.MSTSWagonNumWheels;
13381362
MassKG = copy.MassKG;
13391363
InitialMassKG = copy.InitialMassKG;

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

Lines changed: 101 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ public static Interpolator SteamHeatBoilerFuelUsageGalukpH()
191191
public bool HasPassengerCapacity = false;
192192
public bool HasInsideView = false;
193193
public float CarHeightAboveSeaLevelM;
194+
public float WagonNumBogies;
195+
public float CarBogieCentreLengthM;
196+
public float CarBodyLengthM;
197+
public float CarCouplerFaceLengthM;
198+
public float DerailmentCoefficient;
194199

195200
public float MaxHandbrakeForceN;
196201
public float MaxBrakeForceN = 89e3f;
@@ -514,7 +519,7 @@ public Direction Direction
514519
public float DynamicBrakeForceN = 0f; // Raw dynamic brake force for diesel and electric locomotives
515520

516521
// Derailment variables
517-
public float WagonVerticalDerailForceN; // Vertical force of wagon/car - essentially determined by the weight
522+
public float TotalWagonVerticalDerailForceN; // Vertical force of wagon/car - essentially determined by the weight
518523
public float TotalWagonLateralDerailForceN;
519524
public float LateralWindForceN;
520525
public float WagonFrontCouplerAngleRad;
@@ -523,7 +528,7 @@ public Direction Direction
523528
public float AdjustedWagonRearCouplerAngleRad;
524529
public float WagonFrontCouplerCurveExtM;
525530
public float WagonRearCouplerCurveExtM;
526-
// public float WagonVerticalForceN; // Vertical force of wagon/car - essentially determined by the weight
531+
public float WagonCouplerAngleDerailRad;
527532

528533

529534
public bool BuffForceExceeded;
@@ -585,6 +590,7 @@ public Direction Direction
585590
protected float SuperelevationM; // Super elevation on the curve
586591
protected float UnbalancedSuperElevationM; // Unbalanced superelevation, read from MSTS Wagon File
587592
protected float SuperElevationTotalM; // Total superelevation
593+
public float SuperElevationAngleRad;
588594
protected bool IsMaxSafeCurveSpeed = false; // Has equal loading speed around the curve been exceeded, ie are all the wheesl still on the track?
589595
public bool IsCriticalMaxSpeed = false; // Has the critical maximum speed around the curve been reached, is the wagon about to overturn?
590596
public bool IsCriticalMinSpeed = false; // Is the speed less then the minimum required for the wagon to travel around the curve
@@ -1093,30 +1099,25 @@ public virtual void UpdateTunnelForce()
10931099

10941100
public void UpdateTrainDerailmentRisk()
10951101
{
1096-
// Train will derail if lateral forces on the train exceed the vertical forces holding the train on the railway track.
1097-
// Typically the train is most at risk when travelling around a curve
1098-
1099-
// Based upon ??????
1100-
1101-
// Calculate Lateral forces
1102-
1103-
foreach (var w in WheelAxles)
1102+
// Calculate coupler angle when travelling around curve
1103+
// To achieve an accurate coupler angle calculation the following length need to be calculated. These values can be included in the ENG/WAG file for greatest accuracy, or alternatively OR will
1104+
// calculate some default values based upon the length of the car specified in the "Size" statement. This value may however be inaccurate, and sets the "visual" distance for placement of the
1105+
// animated coupler. So often it is a good idea to add the values in the WAG file.
1106+
1107+
var OverhangThisCarM = 0.5f * (CarBodyLengthM - CarBogieCentreLengthM); // Vehicle overhang - B
1108+
var BogieDistanceThisCarM = 0.5f * CarBogieCentreLengthM; // 0.5 * distance between bogie centres - A
1109+
var CouplerDistanceThisCarM = 0.5f * (CarCouplerFaceLengthM - CarBodyLengthM);
1110+
1111+
var OverhangBehindCarM = 2.545f; // Vehicle overhang - B
1112+
var BogieDistanceBehindCarM = 8.23f; // 0.5 * distance between bogie centres - A
1113+
var CouplerDistanceBehindCarM = 0.5f * (CarCouplerFaceLengthM - CarBodyLengthM);
1114+
if (CarBehind != null)
11041115
{
1105-
// Trace.TraceInformation("Car ID {0} Length {1} Bogie {2} Offset {3} MAtrix {4}", CarID, CarLengthM, w.BogieIndex, w.OffsetM, w.BogieMatrix);
1106-
1116+
OverhangBehindCarM = 0.5f * (CarBehind.CarBodyLengthM - CarBehind.CarBogieCentreLengthM); // Vehicle overhang - B
1117+
BogieDistanceBehindCarM = 0.5f * CarBehind.CarBogieCentreLengthM; // 0.5 * distance between bogie centres - A
1118+
CouplerDistanceBehindCarM = 0.5f * (CarBehind.CarCouplerFaceLengthM - CarBehind.CarBodyLengthM);
11071119
}
11081120

1109-
// Calculate the vertival force on the wheel of the car, to determine whether wagon derails or not
1110-
WagonVerticalDerailForceN = MassKG * GravitationalAccelerationMpS2 * Train.WagonCoefficientFriction;
1111-
1112-
1113-
1114-
// Calculate coupler angle when travelling around curve
1115-
1116-
float OverhangThisCarM = 2.545f; // Vehicle overhang - B
1117-
float OverhangBehindCarM = 2.545f; // Vehicle overhang - B
1118-
float BogieDistanceThisCarM = 8.23f; // 0.5 * distance between bogie centres - A
1119-
float BogieDistanceBehindCarM = 8.23f; // 0.5 * distance between bogie centres - A
11201121
float CouplerAlphaAngleRad;
11211122
float CouplerBetaAngleRad;
11221123
float CouplerGammaAngleRad;
@@ -1125,7 +1126,14 @@ public void UpdateTrainDerailmentRisk()
11251126
float finalCouplerBetaAngleRad;
11261127
float finalCouplerGammaAngleRad;
11271128

1128-
float BogieCentresAdjVehiclesM = OverhangThisCarM + OverhangBehindCarM + CouplerSlackM; // L value = Overhangs + Coupler spacing
1129+
var couplerDistanceM = CouplerDistanceThisCarM + CouplerDistanceBehindCarM + CouplerSlackM;
1130+
1131+
if (couplerDistanceM == 0)
1132+
{
1133+
couplerDistanceM = 0.0001f; // Stop couplerDistance equalling zero as this causes NaN calculations in following calculations.
1134+
}
1135+
1136+
float BogieCentresAdjVehiclesM = OverhangThisCarM + OverhangBehindCarM + couplerDistanceM; // L value = Overhangs + Coupler spacing - D
11291137

11301138
if (CarBehind != null)
11311139
{
@@ -1170,18 +1178,12 @@ public void UpdateTrainDerailmentRisk()
11701178

11711179
float finalAngleBetweenCarbodies = finalCouplerAlphaAngleRad + finalCouplerBetaAngleRad + 2.0f * finalCouplerGammaAngleRad;
11721180

1173-
var couplerDistanceM = CouplerSlackM;
1174-
1175-
if (couplerDistanceM == 0)
1176-
{
1177-
couplerDistanceM = 0.0001f; // Stop couplerDistance equalling zero as this causes NaN calculations in following calculations.
1178-
}
11791181

11801182
// Find maximum coupler angle expected in this curve, ie both cars will be on the curve
11811183
var finalWagonRearCouplerAngleRad = (BogieCentresAdjVehiclesM * (finalCouplerGammaAngleRad + finalCouplerAlphaAngleRad) - OverhangBehindCarM * finalAngleBetweenCarbodies) / couplerDistanceM;
11821184
var finalWagonFrontCouplerAngleRad = (BogieCentresAdjVehiclesM * (finalCouplerGammaAngleRad + finalCouplerBetaAngleRad) - OverhangThisCarM * finalAngleBetweenCarbodies) / couplerDistanceM;
11831185

1184-
// If first car is starting to turn the slowly increase coupler angle to the maximum value expected
1186+
// If first car is starting to turn then slowly increase coupler angle to the maximum value expected
11851187
if (CurrentCurveRadius != 0 && CarBehind.CurrentCurveRadius == 0)
11861188
{
11871189
WagonRearCouplerAngleRad += 0.0006f;
@@ -1191,7 +1193,7 @@ public void UpdateTrainDerailmentRisk()
11911193
CarBehind.WagonFrontCouplerAngleRad = MathHelper.Clamp(CarBehind.WagonFrontCouplerAngleRad, 0, finalWagonFrontCouplerAngleRad);
11921194

11931195
}
1194-
else if (CurrentCurveRadius != 0 && CarBehind.CurrentCurveRadius != 0)
1196+
else if (CurrentCurveRadius != 0 && CarBehind.CurrentCurveRadius != 0) // both cars on the curve
11951197
{
11961198
// Find coupler angle for rear coupler on the car
11971199
WagonRearCouplerAngleRad = (BogieCentresAdjVehiclesM * (CouplerGammaAngleRad + CouplerAlphaAngleRad) - OverhangBehindCarM * AngleBetweenCarbodies) / couplerDistanceM;
@@ -1240,23 +1242,77 @@ public void UpdateTrainDerailmentRisk()
12401242
{
12411243
AdjustedWagonRearCouplerAngleRad = 0.0f;
12421244
CarBehind.AdjustedWagonFrontCouplerAngleRad = 0.0f;
1245+
WagonRearCouplerAngleRad = 0;
1246+
CarBehind.WagonFrontCouplerAngleRad = 0;
1247+
CarAhead.WagonRearCouplerAngleRad = 0;
12431248
}
12441249
}
12451250
}
12461251

1247-
// Lateral Force = Coupler force x Sin (Coupler Angle)
1248-
float CouplerLateralForceN = CouplerForceU * (float)Math.Sin(WagonFrontCouplerAngleRad);
1252+
// Train will derail if lateral forces on the train exceed the vertical forces holding the train on the railway track.
1253+
// Coupler force is calculated at the rear of each car, so calculation values may need to be from the car ahead.
1254+
// Typically the train is most at risk when travelling around a curve.
12491255

1256+
// Calculate the vertical force on the wheel of the car, to determine whether wagon derails or not
1257+
// To calculate vertical force on outer wheel = (WagMass / NumWheels) * gravity + WagMass / NumAxles * ( (Speed^2 / CurveRadius) - (gravity * superelevation angle)) * (height * track width)
12501258

1251-
TotalWagonLateralDerailForceN = CouplerLateralForceN;
1252-
1253-
if (TotalWagonLateralDerailForceN > WagonVerticalDerailForceN)
1254-
{
1255-
BuffForceExceeded = true;
1256-
}
1257-
else
1259+
if (IsPlayerTrain)
12581260
{
1259-
BuffForceExceeded = false;
1261+
WagonCouplerAngleDerailRad = Math.Abs(WagonRearCouplerAngleRad);
1262+
var numWheels = WagonNumAxles * 2;
1263+
1264+
// Trace.TraceInformation("Wagon Values - CarID {0} Axles {1} Bogies {2} Wheels {3}", CarID, WagonNumAxles, WagonNumBogies, numWheels);
1265+
1266+
if (CurrentCurveRadius != 0)
1267+
{
1268+
var A = MassKG * GravitationalAccelerationMpS2 / numWheels;
1269+
var B1 = (MassKG / WagonNumAxles) * (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius;
1270+
var B2 = GravitationalAccelerationMpS2 * (float)Math.Cos(SuperElevationAngleRad);
1271+
var B3 = CentreOfGravityM.Y / TrackGaugeM;
1272+
1273+
TotalWagonVerticalDerailForceN = A + (B1 - B2) * B3;
1274+
1275+
// Calculate lateral force per wheelset on the first bogie
1276+
// Lateral Force = (Coupler force x Sin (Coupler Angle) / NumBogies) + WagMass / NumAxles * ( (Speed^2 / CurveRadius) - (gravity * superelevation angle))
1277+
1278+
if (CarAhead != null)
1279+
{
1280+
var AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad) / WagonNumBogies;
1281+
var BB1 = MassKG / WagonNumAxles;
1282+
var BB2 = (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius;
1283+
var BB3 = GravitationalAccelerationMpS2 * (float)Math.Sin(SuperElevationAngleRad);
1284+
1285+
TotalWagonLateralDerailForceN = AA1 + BB1 * (BB2 - BB3);
1286+
}
1287+
1288+
DerailmentCoefficient = Math.Abs(TotalWagonLateralDerailForceN / TotalWagonVerticalDerailForceN);
1289+
1290+
// use the dynamic multiplication coefficient to calculate final derailment coefficient
1291+
if (IsOverJunction())
1292+
{
1293+
DerailmentCoefficient *= 3.1f;
1294+
}
1295+
else
1296+
{
1297+
DerailmentCoefficient *= 2.0f;
1298+
}
1299+
1300+
}
1301+
else
1302+
{
1303+
TotalWagonLateralDerailForceN = 0;
1304+
TotalWagonVerticalDerailForceN = 0;
1305+
DerailmentCoefficient = 0;
1306+
}
1307+
1308+
if (TotalWagonLateralDerailForceN > TotalWagonVerticalDerailForceN)
1309+
{
1310+
BuffForceExceeded = true;
1311+
}
1312+
else
1313+
{
1314+
BuffForceExceeded = false;
1315+
}
12601316
}
12611317

12621318
}
@@ -1345,9 +1401,6 @@ public string GetCurveDirection()
13451401
}
13461402

13471403

1348-
1349-
1350-
13511404
#region Calculate permissible speeds around curves
13521405
/// <summary>
13531406
/// Reads current curve radius and computes the maximum recommended speed around the curve based upon the
@@ -1441,7 +1494,7 @@ public virtual void UpdateCurveSpeedLimit()
14411494

14421495
SuperelevationM = MathHelper.Clamp(SuperelevationM, 0.0001f, 0.150f); // If superelevation is greater then 6" (150mm) then limit to this value, having a value of zero causes problems with calculations
14431496

1444-
float SuperElevationAngleRad = (float)Math.Sinh(SuperelevationM); // Total superelevation includes both balanced and unbalanced superelevation
1497+
SuperElevationAngleRad = (float)Math.Sinh(SuperelevationM); // Total superelevation includes both balanced and unbalanced superelevation
14451498

14461499
MaxCurveEqualLoadSpeedMps = (float)Math.Sqrt((SuperelevationM * GravitationalAccelerationMpS2 * CurrentCurveRadius) / TrackGaugeM); // Used for calculating curve resistance
14471500

@@ -2168,6 +2221,8 @@ public void AddBogie(float offset, int matrix, int id, string bogie, int numBogi
21682221
Parts[id].bogie = true;//identify this is a bogie, will be used for hold rails on track
21692222
}
21702223

2224+
WagonNumBogies = Parts.Count - 1;
2225+
21712226
} // end AddBogie()
21722227

21732228
public void SetUpWheels()

Source/RunActivity/Viewer3D/Popups/HUDWindow.cs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,14 +1035,10 @@ void TextPageForceInfo(TableData table)
10351035
Viewer.Catalog.GetString("Curve"),
10361036
Viewer.Catalog.GetString("Brk Frict."),
10371037
Viewer.Catalog.GetString("Brk Slide"),
1038-
Viewer.Catalog.GetString("Bear Temp")
1039-
1040-
// Possibly needed for buffing forces
1041-
// Viewer.Catalog.GetString("VertD"),
1042-
// Viewer.Catalog.GetString("VertL"),
1043-
// Viewer.Catalog.GetString("BuffExc"),
1044-
// Viewer.Catalog.GetString("CplAng")
1045-
1038+
Viewer.Catalog.GetString("Bear Temp"),
1039+
Viewer.Catalog.GetString(" "),
1040+
Viewer.Catalog.GetString("DerailCoeff")
1041+
10461042
);
10471043
TableAddLine(table);
10481044

@@ -1069,16 +1065,8 @@ void TextPageForceInfo(TableData table)
10691065
TableSetCell(table, 15, "{0:F0}%", car.BrakeShoeCoefficientFriction * 100.0f);
10701066
TableSetCell(table, 16, car.HUDBrakeSkid ? Viewer.Catalog.GetString("Yes") : Viewer.Catalog.GetString("No"));
10711067
TableSetCell(table, 17, "{0} {1}", FormatStrings.FormatTemperature(car.WheelBearingTemperatureDegC, car.IsMetric, false), car.DisplayWheelBearingTemperatureStatus);
1072-
1073-
// Possibly needed for buffing forces
1074-
// TableSetCell(table, 17, "{0}", FormatStrings.FormatForce(car.WagonVerticalDerailForceN, car.IsMetric));
1075-
// TableSetCell(table, 18, "{0}", FormatStrings.FormatForce(car.TotalWagonLateralDerailForceN, car.IsMetric));
1076-
// TableSetCell(table, 19, car.BuffForceExceeded ? Viewer.Catalog.GetString("Yes") : "No");
1077-
1078-
// TableSetCell(table, 20, "{0:F2}", MathHelper.ToDegrees(car.WagonFrontCouplerAngleRad));
1079-
1080-
10811068
TableSetCell(table, 18, car.Flipped ? Viewer.Catalog.GetString("Flipped") : "");
1069+
TableSetCell(table, 19, "{0:F2}{1}", car.DerailmentCoefficient, car.DerailmentCoefficient > 1 ? "!!!" : car.DerailmentCoefficient < 1 && car.DerailmentCoefficient > 0.66 ? "???" : "");
10821070
TableAddLine(table);
10831071

10841072
}

0 commit comments

Comments
 (0)