From a78797f22dd0185aa4b546fa3ccd25bc6c903370 Mon Sep 17 00:00:00 2001 From: peternewell Date: Mon, 27 Sep 2021 13:02:56 +1000 Subject: [PATCH 01/10] Update derailment functionality --- .../Simulation/RollingStocks/MSTSWagon.cs | 36 ++++++++ .../Simulation/RollingStocks/TrainCar.cs | 86 ++++++++++++++++++- .../RunActivity/Viewer3D/Popups/HUDWindow.cs | 2 +- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs index 8bc92cb78..1518d640d 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs @@ -500,6 +500,28 @@ public virtual void LoadFromWagFile(string wagFilePath) } } + // Should always be at least one bogie on rolling stock. If is zero then NaN error occurs. + if (WagonNumBogies == 0) + { + WagonNumBogies = 1; + } + + // Set wheel flange parameters to default values. + if (MaximumWheelFlangeAngleRad == 0) + { + MaximumWheelFlangeAngleRad = 1.09956f; // Default = 63.36 deg. + } + else + { + const float convertDegtoRad = 0.01745329252f; + MaximumWheelFlangeAngleRad = convertDegtoRad * MaximumWheelFlangeAngleRad; // Assume that input has been in degrees - TO ADD - appropriate input parameters + } + + if (WheelFlangeLengthM == 0) + { + WheelFlangeLengthM = 0.026194f; // Height = 1.031in + } + // Initialise steam heat parameters if (TrainHeatBoilerWaterUsageGalukpH == null) // If no table entered in WAG file, then use the default table { @@ -1015,6 +1037,8 @@ public virtual void Parse(string lowercasetoken, STFReader stf) case "wagon(ortslengthairhose": CarAirHoseLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; case "wagon(ortshorizontallengthairhose": CarAirHoseHorizontalLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; case "wagon(ortslengthcouplerface": CarCouplerFaceLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; + case "wagon(ortswheelflangelength": WheelFlangeLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; + case "wagon(ortsmaximumwheelflangeangle": MaximumWheelFlangeAngleRad = stf.ReadFloatBlock(STFReader.UNITS.None, null); break; case "wagon(ortstrackgauge": stf.MustMatch("("); TrackGaugeM = stf.ReadFloat(STFReader.UNITS.Distance, null); @@ -1472,6 +1496,8 @@ public virtual void Copy(MSTSWagon copy) CarCouplerFaceLengthM = copy.CarCouplerFaceLengthM; CarAirHoseLengthM = copy.CarAirHoseLengthM; CarAirHoseHorizontalLengthM = copy.CarAirHoseHorizontalLengthM; + MaximumWheelFlangeAngleRad = copy.MaximumWheelFlangeAngleRad; + WheelFlangeLengthM = copy.WheelFlangeLengthM; AuxTenderWaterMassKG = copy.AuxTenderWaterMassKG; TenderWagonMaxCoalMassKG = copy.TenderWagonMaxCoalMassKG; TenderWagonMaxWaterMassKG = copy.TenderWagonMaxWaterMassKG; @@ -1710,6 +1736,11 @@ public override void Save(BinaryWriter outf) outf.Write(WheelBrakeSlideProtectionActive); outf.Write(WheelBrakeSlideProtectionTimerS); + outf.Write(AngleOfAttackRad); + outf.Write(DerailClimbDistanceM); + outf.Write(DerailPossible); + outf.Write(DerailExpected); + outf.Write(DerailElapsedTimeS); base.Save(outf); } @@ -1757,6 +1788,11 @@ public override void Restore(BinaryReader inf) WheelBrakeSlideProtectionActive = inf.ReadBoolean(); WheelBrakeSlideProtectionTimerS = inf.ReadInt32(); + AngleOfAttackRad = inf.ReadSingle(); + DerailClimbDistanceM = inf.ReadSingle(); + DerailPossible = inf.ReadBoolean(); + DerailExpected = inf.ReadBoolean(); + DerailElapsedTimeS = inf.ReadSingle(); base.Restore(inf); } diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index 21c80d6e2..b82176965 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -196,6 +196,14 @@ public static Interpolator SteamHeatBoilerFuelUsageGalukpH() public float CarBodyLengthM; public float CarCouplerFaceLengthM; public float DerailmentCoefficient; + public float NadalDerailmentCoefficient; + public float MaximumWheelFlangeAngleRad; + public float WheelFlangeLengthM; + public float AngleOfAttackRad; + public float DerailClimbDistanceM; + public bool DerailPossible = false; + public bool DerailExpected = false; + public float DerailElapsedTimeS; public float MaxHandbrakeForceN; public float MaxBrakeForceN = 89e3f; @@ -852,7 +860,7 @@ public virtual void Update(float elapsedClockSeconds) UpdateCurveForce(elapsedClockSeconds); UpdateTunnelForce(); UpdateBrakeSlideCalculation(); - UpdateTrainDerailmentRisk(); + UpdateTrainDerailmentRisk(elapsedClockSeconds); // acceleration if (elapsedClockSeconds > 0.0f) @@ -1173,9 +1181,24 @@ public virtual void UpdateTunnelForce() /// /// This section calculates the coupler angle behind the current car (ie the rear coupler on this car and the front coupler on the following car. The coupler angle will be used for /// coupler automation as well as calculating Lateral forces on the car. + /// + /// In addition Chapter 2 - Flange Climb Derailment Criteria of the TRB’s Transit Cooperative Research Program (TCRP) Report 71, examines flange climb derailment criteria for transit + /// vehicles that include lateral-to-vertical ratio limits and a corresponding flange-climb-distance limit. The report also includes guidance to transit agencies on wheel and rail + /// maintenance practices. + /// + /// Some of the concepts described in this publication have also been used to calculate the derailment likelihood. + /// + /// https://www.nap.edu/read/13841/chapter/4 + /// + /// It should be noted that car derailment is a very complex process that is impacted by many diferent factors, including the track structure and train conditions. To model all of + /// these factors is not practical so only some of the key factors are considered. For eaxmple, wheel wear may determine whether a particular car will derial or not. So the same + /// type of car can either derail or not under similar circumstances. + /// + /// Hence these calculations provide a "generic" approach to determining whether a car will derial or not. + /// /// - public void UpdateTrainDerailmentRisk() + public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) { // Calculate coupler angle when travelling around curve // 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 @@ -1438,14 +1461,71 @@ public void UpdateTrainDerailmentRisk() DerailmentCoefficient *= 2.0f; } + var wagonAdhesion = Train.WagonCoefficientFriction; + + // Calculate Nadal derailment coefficient limit + NadalDerailmentCoefficient = ((float) Math.Tan(MaximumWheelFlangeAngleRad) - wagonAdhesion) / (1f + wagonAdhesion * (float) Math.Tan(MaximumWheelFlangeAngleRad)); + + // Calculate Angle of Attack - AOA = sin-1(2 * bogie wheel base / curve radius) + AngleOfAttackRad = (float)Math.Asin(2 * RigidWheelBaseM / CurrentCurveRadius); + var angleofAttackmRad = AngleOfAttackRad * 1000f; // Convert to micro radians + + // Calculate the derail climb distance - uses the general form equation 2.4 from the above publication + var parameterA_1 = ((100 / (-1.9128f * MathHelper.ToDegrees(MaximumWheelFlangeAngleRad) + 146.56f)) + 3.1f) * Me.ToIn(WheelFlangeLengthM); + + var parameterA_2 = (1.0f / (-0.0092f * Math.Pow(MathHelper.ToDegrees(MaximumWheelFlangeAngleRad), 2) + 1.2125f * MathHelper.ToDegrees(MaximumWheelFlangeAngleRad) - 39.031f)) + 1.23f; + + var parameterA = parameterA_1 + parameterA_2; + + var parameterB_1 = ((10f / (-21.157f * Me.ToIn(WheelFlangeLengthM) + 2.1052f)) + 0.05f) * MathHelper.ToDegrees(MaximumWheelFlangeAngleRad); + + var parameterB_2 = (10 / (0.2688f * Me.ToIn(WheelFlangeLengthM) - 0.0266f)) - 5f; + + var parameterB = parameterB_1 + parameterB_2; + + DerailClimbDistanceM = Me.FromFt( (float)((parameterA * parameterB * Me.ToIn(WheelFlangeLengthM)) / ((angleofAttackmRad + (parameterB * Me.ToIn(WheelFlangeLengthM))))) ); + + // calculate the time taken to travel the derail climb distance + var derailTimeS = AbsSpeedMpS / DerailClimbDistanceM; + + // Set indication that a derail may occur + if (DerailmentCoefficient > NadalDerailmentCoefficient) + { + DerailPossible = true; + } + else + { + DerailPossible = false; + } + + // If derail climb time exceeded, then derail happens + if (DerailPossible && DerailElapsedTimeS > derailTimeS) + { + DerailExpected = true; + Simulator.Confirmer.Message(ConfirmLevel.Warning, Simulator.Catalog.GetStringFmt("Car {0} has derailed on the curve.", CarID)); + + } + else if (DerailPossible) + { + DerailElapsedTimeS += elapsedClockSeconds; + } + else + { + DerailElapsedTimeS = 0; // Reset timer if derail is not possible + } } else { TotalWagonLateralDerailForceN = 0; TotalWagonVerticalDerailForceN = 0; DerailmentCoefficient = 0; + DerailExpected = false; + DerailPossible = false; + DerailElapsedTimeS = 0; } + + if (TotalWagonLateralDerailForceN > TotalWagonVerticalDerailForceN) { BuffForceExceeded = true; @@ -1690,7 +1770,7 @@ public virtual void UpdateCurveSpeedLimit() } else { - Simulator.Confirmer.Message(ConfirmLevel.Warning, Simulator.Catalog.GetStringFmt("You are travelling too fast for this curve. Slow down, your passengers in car {0} are feeling uncomfortable. The recommended speed for this curve is {1}", CarID, FormatStrings.FormatSpeedDisplay(MaxSafeCurveSpeedMps, IsMetric))); ; + Simulator.Confirmer.Message(ConfirmLevel.Warning, Simulator.Catalog.GetStringFmt("You are travelling too fast for this curve. Slow down, your passengers in car {0} are feeling uncomfortable. The recommended speed for this curve is {1}", CarID, FormatStrings.FormatSpeedDisplay(MaxSafeCurveSpeedMps, IsMetric))); } if (dbfmaxsafecurvespeedmps != MaxSafeCurveSpeedMps)//Debrief eval diff --git a/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs b/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs index 31c1aa79c..02eec115b 100644 --- a/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/HUDWindow.cs @@ -1139,7 +1139,7 @@ void TextPageForceInfo(TableData table) TableSetCell(table, 16, car.HUDBrakeSkid ? Viewer.Catalog.GetString("Yes") : Viewer.Catalog.GetString("No")); TableSetCell(table, 17, "{0} {1}", FormatStrings.FormatTemperature(car.WheelBearingTemperatureDegC, car.IsMetric, false), car.DisplayWheelBearingTemperatureStatus); TableSetCell(table, 18, car.Flipped ? Viewer.Catalog.GetString("Flipped") : ""); - TableSetCell(table, 19, "{0:F2}{1}", car.DerailmentCoefficient, car.DerailmentCoefficient > 1 ? "!!!" : car.DerailmentCoefficient < 1 && car.DerailmentCoefficient > 0.66 ? "???" : ""); + TableSetCell(table, 19, "{0:F2}{1}", car.DerailmentCoefficient, car.DerailExpected ? "!!!" : car.DerailPossible ? "???" : ""); TableAddLine(table); } From f0c670c1fdc27432dc6749249554cb604455b294 Mon Sep 17 00:00:00 2001 From: peternewell Date: Mon, 27 Sep 2021 18:01:04 +1000 Subject: [PATCH 02/10] correct calculation issue --- Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index b82176965..21042018e 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -1486,7 +1486,7 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) DerailClimbDistanceM = Me.FromFt( (float)((parameterA * parameterB * Me.ToIn(WheelFlangeLengthM)) / ((angleofAttackmRad + (parameterB * Me.ToIn(WheelFlangeLengthM))))) ); // calculate the time taken to travel the derail climb distance - var derailTimeS = AbsSpeedMpS / DerailClimbDistanceM; + var derailTimeS = DerailClimbDistanceM / AbsSpeedMpS; // Set indication that a derail may occur if (DerailmentCoefficient > NadalDerailmentCoefficient) From 06a5735175ee9ad9b484dfa3d7fd46eddfef5d0c Mon Sep 17 00:00:00 2001 From: peternewell Date: Wed, 29 Sep 2021 14:15:45 +1000 Subject: [PATCH 03/10] Add input checking for angles. --- Source/Orts.Parsers.Msts/STFReader.cs | 13 +++++++++++++ .../Simulation/RollingStocks/MSTSWagon.cs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Source/Orts.Parsers.Msts/STFReader.cs b/Source/Orts.Parsers.Msts/STFReader.cs index 5091fa08e..cc4ce114c 100644 --- a/Source/Orts.Parsers.Msts/STFReader.cs +++ b/Source/Orts.Parsers.Msts/STFReader.cs @@ -809,6 +809,12 @@ public enum UNITS /// Temperature = 1 << 27, // "Temperature", note above TemperatureDifference, is different + /// + /// Valid Units: deg, rad + /// Scaled to Radians + /// + Angle = 1 << 28, // "Temperature", note above TemperatureDifference, is different + // "Any" is used where units cannot easily be specified, such as generic routines for interpolating continuous data from point values. // or interpreting locomotive cab attributes from the ORTSExtendedCVF experimental mechanism. // "Any" should not be used where the dimensions of a unit are predictable. @@ -1128,6 +1134,13 @@ internal double ParseUnitSuffix(ref string constant, UNITS validUnits) return 1; } } + if ((validUnits & UNITS.Angle) > 0) + switch (suffix) + { + case "": return 1.0; + case "rad": return 1; + case "deg": return 0.0174533; // 1 deg = 0.0174533 radians + } STFException.TraceWarning(this, "Found a suffix '" + suffix + "' which could not be parsed as a " + validUnits.ToString() + " unit"); return 1; } diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs index 1518d640d..25a4027e0 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs @@ -1038,7 +1038,7 @@ public virtual void Parse(string lowercasetoken, STFReader stf) case "wagon(ortshorizontallengthairhose": CarAirHoseHorizontalLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; case "wagon(ortslengthcouplerface": CarCouplerFaceLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; case "wagon(ortswheelflangelength": WheelFlangeLengthM = stf.ReadFloatBlock(STFReader.UNITS.Distance, null); break; - case "wagon(ortsmaximumwheelflangeangle": MaximumWheelFlangeAngleRad = stf.ReadFloatBlock(STFReader.UNITS.None, null); break; + case "wagon(ortsmaximumwheelflangeangle": MaximumWheelFlangeAngleRad = stf.ReadFloatBlock(STFReader.UNITS.Angle, null); break; case "wagon(ortstrackgauge": stf.MustMatch("("); TrackGaugeM = stf.ReadFloat(STFReader.UNITS.Distance, null); From 562fe42c8a32d2a68ca3a8f75b967360e7151fd9 Mon Sep 17 00:00:00 2001 From: peternewell Date: Sat, 2 Oct 2021 11:06:00 +1000 Subject: [PATCH 04/10] Further adjustments to derail --- Source/Orts.Parsers.Msts/STFReader.cs | 2 +- .../Simulation/RollingStocks/MSTSWagon.cs | 9 ++------- .../Simulation/RollingStocks/TrainCar.cs | 12 ++++++++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Source/Orts.Parsers.Msts/STFReader.cs b/Source/Orts.Parsers.Msts/STFReader.cs index cc4ce114c..9946e9d7f 100644 --- a/Source/Orts.Parsers.Msts/STFReader.cs +++ b/Source/Orts.Parsers.Msts/STFReader.cs @@ -813,7 +813,7 @@ public enum UNITS /// Valid Units: deg, rad /// Scaled to Radians /// - Angle = 1 << 28, // "Temperature", note above TemperatureDifference, is different + Angle = 1 << 28, // "Any" is used where units cannot easily be specified, such as generic routines for interpolating continuous data from point values. // or interpreting locomotive cab attributes from the ORTSExtendedCVF experimental mechanism. diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs index 25a4027e0..e380c7d53 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/MSTSWagon.cs @@ -509,17 +509,12 @@ public virtual void LoadFromWagFile(string wagFilePath) // Set wheel flange parameters to default values. if (MaximumWheelFlangeAngleRad == 0) { - MaximumWheelFlangeAngleRad = 1.09956f; // Default = 63.36 deg. - } - else - { - const float convertDegtoRad = 0.01745329252f; - MaximumWheelFlangeAngleRad = convertDegtoRad * MaximumWheelFlangeAngleRad; // Assume that input has been in degrees - TO ADD - appropriate input parameters + MaximumWheelFlangeAngleRad = 1.22173f; // Default = 70 deg - Pre 1990 AAR 1:20 wheel } if (WheelFlangeLengthM == 0) { - WheelFlangeLengthM = 0.026194f; // Height = 1.031in + WheelFlangeLengthM = 0.0254f; // Height = 1.00in - Pre 1990 AAR 1:20 wheel } // Initialise steam heat parameters diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index 21042018e..00529c772 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -1513,6 +1513,18 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) { DerailElapsedTimeS = 0; // Reset timer if derail is not possible } + + if (AbsSpeedMpS < 0.01) + { + DerailExpected = false; + DerailPossible = false; + } + +// if (CarID == "0 - 84" || CarID == "0 - 83" || CarID == "0 - 82" || CarID == "0 - 81" || CarID == "0 - 80" || CarID == "0 - 79") +// { +// Trace.TraceInformation("Nadal - {0}, Adhesion {1} Flange Angle {2}", NadalDerailmentCoefficient, wagonAdhesion, MaximumWheelFlangeAngleRad); +// Trace.TraceInformation("Derailment - CarID {0}, Nadal {1}, Derail {2} Possible {3} Expected {4} Derail Distance {5} ElapsedTime {6} DerailTime {7}", CarID, NadalDerailmentCoefficient, DerailmentCoefficient, DerailPossible, DerailExpected, DerailClimbDistanceM, DerailElapsedTimeS, derailTimeS); +// } } else { From 69a70cb7fb2aea028b52e086eb854d32d3598762 Mon Sep 17 00:00:00 2001 From: peternewell Date: Sun, 3 Oct 2021 17:01:05 +1100 Subject: [PATCH 05/10] Adjust Train Driver Window to line up with HuD operation of derailment window. --- .../Viewer3D/Popups/TrainDrivingWindow.cs | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/Source/RunActivity/Viewer3D/Popups/TrainDrivingWindow.cs b/Source/RunActivity/Viewer3D/Popups/TrainDrivingWindow.cs index 09e021417..f8b529d1e 100644 --- a/Source/RunActivity/Viewer3D/Popups/TrainDrivingWindow.cs +++ b/Source/RunActivity/Viewer3D/Popups/TrainDrivingWindow.cs @@ -1178,47 +1178,58 @@ void AddSeparator() => AddLabel(new ListLabel } //Derailment Coefficient. Changed the float value output by a text label. - var maxDerailCoeff = 0.0f; var carIDerailCoeff = ""; + var carDerailPossible = false; + var carDerailExpected = false; + for (var i = 0; i < train.Cars.Count; i++) { var carDerailCoeff = train.Cars[i].DerailmentCoefficient; carDerailCoeff = float.IsInfinity(carDerailCoeff) || float.IsNaN(carDerailCoeff) ? 0 : carDerailCoeff; - if (carDerailCoeff > maxDerailCoeff) + + carIDerailCoeff = train.Cars[i].CarID; + + // Only record the first car that has derailed, stop looking for other derailed cars + carDerailExpected = train.Cars[i].DerailExpected; + if (carDerailExpected) + { + break; + } + + // Only record first instance of a possible car derailment (warning) + if (train.Cars[i].DerailPossible && !carDerailPossible) { - maxDerailCoeff = carDerailCoeff; - carIDerailCoeff = train.Cars[i].CarID; + carDerailPossible = train.Cars[i].DerailPossible; } } - if (maxDerailCoeff > 0.66) + if (carDerailPossible || carDerailExpected) { derailLabelVisible = true; clockDerailTime = Owner.Viewer.Simulator.ClockTime; } - if (maxDerailCoeff > 0.66) + // The most extreme instance of the derail coefficient will only be displayed in the TDW + if (carDerailExpected) { - if (maxDerailCoeff > 1) + AddLabel(new ListLabel { - AddLabel(new ListLabel - { - FirstCol = Viewer.Catalog.GetString("DerailCoeff"), - LastCol = $"{Viewer.Catalog.GetString("Derailed")} {carIDerailCoeff}" + ColorCode[Color.OrangeRed], - }); - } - else if (maxDerailCoeff < 1 && maxDerailCoeff > 0.66) + FirstCol = Viewer.Catalog.GetString("DerailCoeff"), + LastCol = $"{Viewer.Catalog.GetString("Derailed")} {carIDerailCoeff}" + ColorCode[Color.OrangeRed], + }); + } + else if (carDerailPossible) + { + AddLabel(new ListLabel { - AddLabel(new ListLabel - { - FirstCol = Viewer.Catalog.GetString("DerailCoeff"), - LastCol = $"{Viewer.Catalog.GetString("Warning")} {carIDerailCoeff}" + ColorCode[Color.Yellow], - }); - } + FirstCol = Viewer.Catalog.GetString("DerailCoeff"), + LastCol = $"{Viewer.Catalog.GetString("Warning")} {carIDerailCoeff}" + ColorCode[Color.Yellow], + }); } + else { - // delay to hide the derailcoeff label + // delay to hide the derailcoeff label if normal if (derailLabelVisible && clockDerailTime + 3 < Owner.Viewer.Simulator.ClockTime) derailLabelVisible = false; From a2845f81af42ce1b343c2d22210c4d45ab5103eb Mon Sep 17 00:00:00 2001 From: peternewell Date: Sun, 10 Oct 2021 08:46:44 +1100 Subject: [PATCH 06/10] Correct NaN issue --- .../Simulation/RollingStocks/TrainCar.cs | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index 00529c772..f20c8ee45 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -1429,8 +1429,28 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) if (CurrentCurveRadius != 0) { - var A = MassKG * GravitationalAccelerationMpS2 / numWheels; - var B1 = (MassKG / numAxles) * (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; + float A = 0; + float B1 = 0; + + // Prevent NaN if numWheels = 0 + if (numWheels != 0) + { + A = MassKG * GravitationalAccelerationMpS2 / numWheels; + } + else + { + A = MassKG * GravitationalAccelerationMpS2; + } + + // Prevent NaN if numAxles = 0 + if (numAxles != 0) + { + B1 = (MassKG / numAxles) * (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; + } + else + { + B1 = MassKG * (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; + } var B2 = GravitationalAccelerationMpS2 * (float)Math.Cos(SuperElevationAngleRad); var B3 = CentreOfGravityM.Y / TrackGaugeM; @@ -1441,8 +1461,28 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) if (CarAhead != null) { - var AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad) / WagonNumBogies; - var BB1 = MassKG / numAxles; + float AA1 = 0; + float BB1 = 0; + + // Prevent NaN if WagonNumBogies = 0 + if ( WagonNumBogies != 0) + { + AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad) / WagonNumBogies; + } + else + { + AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad); + } + + // Prevent NaN if numAxles = 0 + if (numAxles != 0) + { + BB1 = MassKG / numAxles; + } + else + { + BB1 = MassKG; + } var BB2 = (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; var BB3 = GravitationalAccelerationMpS2 * (float)Math.Sin(SuperElevationAngleRad); From 8a7c2af9a6fccc349b414fdc5605eb9223e8a071 Mon Sep 17 00:00:00 2001 From: peternewell Date: Tue, 12 Oct 2021 16:13:44 +1100 Subject: [PATCH 07/10] Add documentation for manual --- Source/Documentation/Manual/physics.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Source/Documentation/Manual/physics.rst b/Source/Documentation/Manual/physics.rst index 5a5e2d84d..5a1adc671 100644 --- a/Source/Documentation/Manual/physics.rst +++ b/Source/Documentation/Manual/physics.rst @@ -4490,3 +4490,25 @@ Two other parameters in the Engine section of the ENG file are used by the TCS: - ``DoesBrakeCutPower( x )`` sets whether applying brake on the locomotive cuts the traction (1 for enabled, 0 for disabled) - ``BrakeCutsPowerAtBrakeCylinderPressure( x )`` sets the minimum pressure in the brake cylinder that cuts the traction (by default 4 PSI) + + +Train Derailment +---------------- + +Open Rails calculates when it is likely that a train derailment has occurred. The derailment modeled is based upon the wheel climbing the rail +when the train is in a curve. Light (empty wagons) can sometimes derail due to 'string lining' where the train forces attempt to pull the train +in a straight line, rather then following the curve. + +OR calculates the Nadal Critera for each wagon, and then calculates the actual L/V ratio based upon the wagon weight and the relevant +"in train" forces. Open Rails uses some calculated default parameters for the various parameters required to determine the actual L/V +ratio, however more accurate results will be obtained if actual parameters are entered into the ENG or WAG file. The derailment calculations +use information relating to the wagon dimensions, weight and wheel profile information. + +Wheel profile details can be entered with the following two parameters: + +- ``ORTSMaximumWheelFlangeAngle`` - Wheel flange angle is defined as the maximum angle of the wheel flange relative to the horizontal axis. +UoM - Angle (deg, radians) - default is rad. Typically this value maybe between approx 60 and 75 degrees. + +- ``ORTSWheelFlangeLength`` - Wheel flange length is defined as the length of flange starting from the beginning of the maximum flange angle +to the point where flange angle reduces to 26.6 degrees. UoM - Distance (m, in, ft, etc) - default is m + From 780b0e8c99222653fb7bd19d32245db51465c4be Mon Sep 17 00:00:00 2001 From: peternewell Date: Mon, 18 Oct 2021 20:24:46 +1100 Subject: [PATCH 08/10] Adjustments to derail algorithim --- .../Simulation/RollingStocks/TrainCar.cs | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index f20c8ee45..b2a56dfc3 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -1420,6 +1420,7 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) // Calculate the vertical force on the wheel of the car, to determine whether wagon derails or not // To calculate vertical force on outer wheel = (WagMass / NumWheels) * gravity + WagMass / NumAxles * ( (Speed^2 / CurveRadius) - (gravity * superelevation angle)) * (height * track width) + // Equation 5 if (IsPlayerTrain) { @@ -1435,7 +1436,7 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) // Prevent NaN if numWheels = 0 if (numWheels != 0) { - A = MassKG * GravitationalAccelerationMpS2 / numWheels; + A = (MassKG / numWheels) * GravitationalAccelerationMpS2; } else { @@ -1445,16 +1446,17 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) // Prevent NaN if numAxles = 0 if (numAxles != 0) { - B1 = (MassKG / numAxles) * (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; + B1 = (MassKG / numAxles); } else { - B1 = MassKG * (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; + B1 = MassKG; } - var B2 = GravitationalAccelerationMpS2 * (float)Math.Cos(SuperElevationAngleRad); - var B3 = CentreOfGravityM.Y / TrackGaugeM; + var B2 = GravitationalAccelerationMpS2 * (float)Math.Sin(SuperElevationAngleRad); + var B3 = (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; + var B4 = CentreOfGravityM.Y / TrackGaugeM; - TotalWagonVerticalDerailForceN = A + (B1 - B2) * B3; + TotalWagonVerticalDerailForceN = A + B1 * (B3 - B2) * B4; // Calculate lateral force per wheelset on the first bogie // Lateral Force = (Coupler force x Sin (Coupler Angle) / NumBogies) + WagMass / NumAxles * ( (Speed^2 / CurveRadius) - (gravity * superelevation angle)) @@ -1467,11 +1469,13 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) // Prevent NaN if WagonNumBogies = 0 if ( WagonNumBogies != 0) { - AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad) / WagonNumBogies; + // AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad) / WagonNumBogies; + AA1 = Math.Abs(CarAhead.CouplerForceUSmoothed.SmoothedValue) * (float)Math.Sin(WagonCouplerAngleDerailRad) / WagonNumBogies; } else { - AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad); + // AA1 = CarAhead.CouplerForceU * (float)Math.Sin(WagonCouplerAngleDerailRad); + AA1 = Math.Abs(CarAhead.CouplerForceUSmoothed.SmoothedValue) * (float)Math.Sin(WagonCouplerAngleDerailRad); } // Prevent NaN if numAxles = 0 @@ -1486,19 +1490,21 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) var BB2 = (float)Math.Pow(Math.Abs(SpeedMpS), 2) / CurrentCurveRadius; var BB3 = GravitationalAccelerationMpS2 * (float)Math.Sin(SuperElevationAngleRad); - TotalWagonLateralDerailForceN = AA1 + BB1 * (BB2 - BB3); + TotalWagonLateralDerailForceN = Math.Abs(AA1 + BB1 * (BB2 - BB3)); } - DerailmentCoefficient = Math.Abs(TotalWagonLateralDerailForceN / TotalWagonVerticalDerailForceN); + DerailmentCoefficient = TotalWagonLateralDerailForceN / TotalWagonVerticalDerailForceN; - // use the dynamic multiplication coefficient to calculate final derailment coefficient + // use the dynamic multiplication coefficient to calculate final derailment coefficient, the above method calculated using quasi-static factors. + // The quasi-static do not include allowance for wheel unloading due to static carbody pitch. Hence the following factors have been used to adjust to dynamic effects. + // They have been adjusted slightly based upon derailment accident reports. Original figures quoted were 2 x for standard curves, and 3.1 x for turnouts. if (IsOverJunction()) { - DerailmentCoefficient *= 3.1f; + DerailmentCoefficient *= 2.17f; } else { - DerailmentCoefficient *= 2.0f; + DerailmentCoefficient *= 1.4f; } var wagonAdhesion = Train.WagonCoefficientFriction; @@ -1543,11 +1549,13 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) { DerailExpected = true; Simulator.Confirmer.Message(ConfirmLevel.Warning, Simulator.Catalog.GetStringFmt("Car {0} has derailed on the curve.", CarID)); - + // Trace.TraceInformation("Car Derail - CarID: {0}, Coupler: {1}, CouplerSmoothed {2}, Lateral {3}, Vertical {4}, Angle {5} Nadal {6} Coeff {7}", CarID, CouplerForceU, CouplerForceUSmoothed.SmoothedValue, TotalWagonLateralDerailForceN, TotalWagonVerticalDerailForceN, WagonCouplerAngleDerailRad, NadalDerailmentCoefficient, DerailmentCoefficient); + // Trace.TraceInformation("Car Ahead Derail - CarID: {0}, Coupler: {1}, CouplerSmoothed {2}, Lateral {3}, Vertical {4}, Angle {5}", CarAhead.CarID, CarAhead.CouplerForceU, CarAhead.CouplerForceUSmoothed.SmoothedValue, CarAhead.TotalWagonLateralDerailForceN, CarAhead.TotalWagonVerticalDerailForceN, CarAhead.WagonCouplerAngleDerailRad); } else if (DerailPossible) { DerailElapsedTimeS += elapsedClockSeconds; + // Trace.TraceInformation("Car Derail Time - CarID: {0}, Coupler: {1}, CouplerSmoothed {2}, Lateral {3}, Vertical {4}, Angle {5}, Elapsed {6}, DeratilTime {7}, Distance {8} Nadal {9} Coeff {10}", CarID, CouplerForceU, CouplerForceUSmoothed.SmoothedValue, TotalWagonLateralDerailForceN, TotalWagonVerticalDerailForceN, WagonCouplerAngleDerailRad, DerailElapsedTimeS, derailTimeS, DerailClimbDistanceM, NadalDerailmentCoefficient, DerailmentCoefficient); } else { @@ -1767,7 +1775,7 @@ public virtual void UpdateCurveSpeedLimit() 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 - SuperElevationAngleRad = (float)Math.Sinh(SuperelevationM); // Total superelevation includes both balanced and unbalanced superelevation + SuperElevationAngleRad = (float)Math.Sinh(SuperelevationM); // Balanced superelevation only angle MaxCurveEqualLoadSpeedMps = (float)Math.Sqrt((SuperelevationM * GravitationalAccelerationMpS2 * CurrentCurveRadius) / TrackGaugeM); // Used for calculating curve resistance From 918960c80f58c46bf7979068b4c6af56387f6ed0 Mon Sep 17 00:00:00 2001 From: peternewell Date: Wed, 20 Oct 2021 15:48:53 +1100 Subject: [PATCH 09/10] Add buff force coupler angle calculation --- .../Simulation/RollingStocks/TrainCar.cs | 87 ++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index b2a56dfc3..c042d7cc7 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -572,7 +572,10 @@ public Direction Direction public float TotalWagonLateralDerailForceN; public float LateralWindForceN; public float WagonFrontCouplerAngleRad; + public float WagonFrontCouplerBuffAngleRad; public float WagonRearCouplerAngleRad; + public float WagonRearCouplerBuffAngleRad; + public float CarTrackPlayM = Me.FromIn(2.0f); public float AdjustedWagonFrontCouplerAngleRad; public float AdjustedWagonRearCouplerAngleRad; public float WagonFrontCouplerCurveExtM; @@ -1196,6 +1199,10 @@ public virtual void UpdateTunnelForce() /// /// Hence these calculations provide a "generic" approach to determining whether a car will derial or not. /// + /// Buff Coupler angle calculated from this publication: In-Train Force Limit Study by National Research Council Canada + /// + /// https://nrc-publications.canada.ca/eng/view/ft/?id=8cc206d0-5dbd-42ed-9b4e-35fd9f8b8efb + /// /// public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) @@ -1336,6 +1343,69 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) AdjustedWagonRearCouplerAngleRad = WagonRearCouplerAngleRad; CarBehind.AdjustedWagonFrontCouplerAngleRad = CarBehind.WagonFrontCouplerAngleRad; } + + // Only process this code segment if coupler is in compression + if (CouplerForceU > 0 && CouplerSlackM < 0) + { + + // Calculate Buff coupler angles. Car1 is current car, and Car2 is the car behind + // Car ahead rear coupler angle + var ThiscarCouplerlengthft = Me.ToFt(CarCouplerFaceLengthM - CarBodyLengthM) + CouplerSlackM / 2; + var CarbehindCouplerlengthft = Me.ToFt(CarBehind.CarCouplerFaceLengthM - CarBehind.CarBodyLengthM) + CouplerSlackM / 2; + var A1 = Math.Sqrt(Math.Pow(Me.ToFt(CurrentCurveRadius), 2) - Math.Pow(Me.ToFt(CarBogieCentreLengthM), 2) / 4.0f); + var A2 = (Me.ToFt(CarCouplerFaceLengthM) / 2.0f) - ThiscarCouplerlengthft; + var A = (float)Math.Atan(A1 / A2); + + var B = (float)Math.Asin(2.0f * Me.ToFt(CarTrackPlayM) / Me.ToFt(CarBogieCentreLengthM)); + var C1 = Math.Pow(ThiscarCouplerlengthft + CarbehindCouplerlengthft, 2); + + var C2_1 = Math.Sqrt(Math.Pow(Me.ToFt(CarCouplerFaceLengthM) / 2.0f - ThiscarCouplerlengthft, 2) + Math.Pow(Me.ToFt(CurrentCurveRadius), 2) - Math.Pow(Me.ToFt(CarBogieCentreLengthM), 2) / 4.0f); + var C2_2 = (2.0f * Me.ToFt(CarTrackPlayM) * (Me.ToFt(CarCouplerFaceLengthM) / 2.0f - ThiscarCouplerlengthft)) / Me.ToFt(CarBogieCentreLengthM); + var C2 = Math.Pow((C2_1 + C2_2), 2); + + var C3_1 = Math.Sqrt(Math.Pow(Me.ToFt(CarBehind.CarCouplerFaceLengthM) / 2.0f - CarbehindCouplerlengthft, 2) + Math.Pow(Me.ToFt(CurrentCurveRadius), 2) - Math.Pow(Me.ToFt(CarBehind.CarBogieCentreLengthM), 2) / 4.0f); + var C3_2 = (2.0f * Me.ToFt(CarBehind.CarTrackPlayM) * (Me.ToFt(CarBehind.CarCouplerFaceLengthM) / 2.0f - CarbehindCouplerlengthft)) / Me.ToFt(CarBehind.CarBogieCentreLengthM); + var C3 = Math.Pow((C3_1 + C3_2), 2); + + var C4 = 2.0f * (ThiscarCouplerlengthft + CarbehindCouplerlengthft) * (C2_1 + C2_2); + + var C = (float)Math.Acos((C1 + C2 - C3) / C4); + + WagonRearCouplerBuffAngleRad = MathHelper.ToRadians(180.0f) - A + B - C; + + + // Trace.TraceInformation("Buff - CarId {0} Carahead {1} A {2} B {3} C {4} 180 {5}", CarID, CarAhead.WagonRearCouplerBuffAngleRad, A, B, C, MathHelper.ToRadians(180.0f)); + + + + // This car front coupler angle + var X1 = Math.Sqrt(Math.Pow(Me.ToFt(CurrentCurveRadius), 2) - Math.Pow(Me.ToFt(CarBehind.CarBogieCentreLengthM), 2) / 4.0f); + var X2 = (Me.ToFt(CarBehind.CarCouplerFaceLengthM) / 2.0f) - CarbehindCouplerlengthft; + var X = (float)Math.Atan(X1 / X2); + + var Y = (float)Math.Asin(2.0f * Me.ToFt(CarBehind.CarTrackPlayM) / Me.ToFt(CarBehind.CarBogieCentreLengthM)); + + var Z1 = Math.Pow(ThiscarCouplerlengthft + CarbehindCouplerlengthft, 2); + var Z2_1 = Math.Sqrt(Math.Pow(Me.ToFt(CarBehind.CarCouplerFaceLengthM) / 2.0f - CarbehindCouplerlengthft, 2) + Math.Pow(Me.ToFt(CurrentCurveRadius), 2) - Math.Pow(Me.ToFt(CarBehind.CarBogieCentreLengthM), 2) / 4.0f); + var Z2_2 = (2.0f * Me.ToFt(CarBehind.CarTrackPlayM) * (Me.ToFt(CarBehind.CarCouplerFaceLengthM) / 2.0f - CarbehindCouplerlengthft)) / Me.ToFt(CarBehind.CarBogieCentreLengthM); + var Z2 = Math.Pow((Z2_1 + Z2_2), 2); + + var Z3_1 = Math.Sqrt(Math.Pow(Me.ToFt(CarCouplerFaceLengthM) / 2.0f - ThiscarCouplerlengthft, 2) + Math.Pow(Me.ToFt(CurrentCurveRadius), 2) - Math.Pow(Me.ToFt(CarBogieCentreLengthM), 2) / 4.0f); + var Z3_2 = (2.0f * Me.ToFt(CarTrackPlayM) * (Me.ToFt(CarCouplerFaceLengthM) / 2.0f - ThiscarCouplerlengthft)) / Me.ToFt(CarBogieCentreLengthM); + var Z3 = Math.Pow((Z3_1 + Z3_2), 2); + + var Z4 = 2.0f * (ThiscarCouplerlengthft + CarbehindCouplerlengthft) * (Z2_1 + Z2_2); + + var Z = (float)Math.Acos((Z1 + Z2 - Z3) / Z4); + + CarBehind.WagonFrontCouplerBuffAngleRad = MathHelper.ToRadians(180.0f) - X + Y - Z; + + // Trace.TraceInformation("Buff - CarId {0} Thiscar {1} A {2} B {3} C {4} 180 {5}", CarID, WagonFrontCouplerBuffAngleRad, X, Y, Z, MathHelper.ToRadians(180.0f)); + + // Trace.TraceInformation("Buff - CarId {0} StringThis {1} StringBehind {2} BuffThis {3} BuffAhead {4}", CarID, WagonRearCouplerAngleRad, CarBehind.WagonFrontCouplerAngleRad, WagonRearCouplerBuffAngleRad, CarBehind.WagonFrontCouplerBuffAngleRad); + + } + } else if (CarAhead != null) { @@ -1344,6 +1414,9 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) AdjustedWagonRearCouplerAngleRad = 0.0f; CarBehind.AdjustedWagonFrontCouplerAngleRad = 0.0f; WagonRearCouplerAngleRad = 0; + WagonFrontCouplerAngleRad = 0; + WagonRearCouplerBuffAngleRad = 0; + WagonFrontCouplerBuffAngleRad = 0; CarBehind.WagonFrontCouplerAngleRad = 0; CarAhead.WagonRearCouplerAngleRad = 0; } @@ -1424,7 +1497,16 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) if (IsPlayerTrain) { - WagonCouplerAngleDerailRad = Math.Abs(WagonRearCouplerAngleRad); + if (CouplerForceU > 0 && CouplerSlackM < 0) // If car coupler is in compression, use the buff angle + { + WagonCouplerAngleDerailRad = Math.Abs(WagonRearCouplerBuffAngleRad); + } + else // if coupler in tension, then use tension angle + { + WagonCouplerAngleDerailRad = Math.Abs(WagonRearCouplerAngleRad); + } + + var numAxles = LocoNumDrvAxles + WagonNumAxles; var numWheels = numAxles * 2; @@ -1504,7 +1586,8 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) } else { - DerailmentCoefficient *= 1.4f; + // DerailmentCoefficient *= 1.4f; + DerailmentCoefficient *= 2.0f; } var wagonAdhesion = Train.WagonCoefficientFriction; From 0e9a259c348df2d1c2e1b1320ea9ab5c682c4c17 Mon Sep 17 00:00:00 2001 From: peternewell Date: Thu, 21 Oct 2021 14:36:42 +1100 Subject: [PATCH 10/10] Further tuning of derailment capability. --- Source/Documentation/Manual/physics.rst | 2 +- .../Simulation/RollingStocks/TrainCar.cs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/Documentation/Manual/physics.rst b/Source/Documentation/Manual/physics.rst index 5a1adc671..a5c3a05c2 100644 --- a/Source/Documentation/Manual/physics.rst +++ b/Source/Documentation/Manual/physics.rst @@ -4499,7 +4499,7 @@ Open Rails calculates when it is likely that a train derailment has occurred. Th when the train is in a curve. Light (empty wagons) can sometimes derail due to 'string lining' where the train forces attempt to pull the train in a straight line, rather then following the curve. -OR calculates the Nadal Critera for each wagon, and then calculates the actual L/V ratio based upon the wagon weight and the relevant +OR calculates the Nadal Criteria for each wagon, and then calculates the actual L/V ratio based upon the wagon weight and the relevant "in train" forces. Open Rails uses some calculated default parameters for the various parameters required to determine the actual L/V ratio, however more accurate results will be obtained if actual parameters are entered into the ENG or WAG file. The derailment calculations use information relating to the wagon dimensions, weight and wheel profile information. diff --git a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs index c042d7cc7..ad103f62b 100644 --- a/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs +++ b/Source/Orts.Simulation/Simulation/RollingStocks/TrainCar.cs @@ -1197,7 +1197,7 @@ public virtual void UpdateTunnelForce() /// these factors is not practical so only some of the key factors are considered. For eaxmple, wheel wear may determine whether a particular car will derial or not. So the same /// type of car can either derail or not under similar circumstances. /// - /// Hence these calculations provide a "generic" approach to determining whether a car will derial or not. + /// Hence these calculations provide a "generic" approach to determining whether a car will derail or not. /// /// Buff Coupler angle calculated from this publication: In-Train Force Limit Study by National Research Council Canada /// @@ -1578,16 +1578,18 @@ public void UpdateTrainDerailmentRisk(float elapsedClockSeconds) DerailmentCoefficient = TotalWagonLateralDerailForceN / TotalWagonVerticalDerailForceN; // use the dynamic multiplication coefficient to calculate final derailment coefficient, the above method calculated using quasi-static factors. - // The quasi-static do not include allowance for wheel unloading due to static carbody pitch. Hence the following factors have been used to adjust to dynamic effects. - // They have been adjusted slightly based upon derailment accident reports. Original figures quoted were 2 x for standard curves, and 3.1 x for turnouts. - if (IsOverJunction()) + // The differences between quasi-static and dynamic limits are due to effects of creepage, curve, conicity, wheel unloading ratio, track geometry, + // car configurations and the share of wheel load changes which are not taken into account in the static analysis etc. + // Hence the following factors have been used to adjust to dynamic effects. + // Original figures quoted - Static Draft = 0.389, Static Buff = 0.389, Dynamic Draft = 0.29, Dynamic Buff = 0.22. + // Hence use the following multiplication factors, Buff = 1.77, Draft = 1.34. + if (CouplerForceU > 0 && CouplerSlackM < 0) { - DerailmentCoefficient *= 2.17f; + DerailmentCoefficient *= 1.77f; // coupler in buff condition } else { - // DerailmentCoefficient *= 1.4f; - DerailmentCoefficient *= 2.0f; + DerailmentCoefficient *= 1.34f; } var wagonAdhesion = Train.WagonCoefficientFriction;