Skip to content

Commit cc032d0

Browse files
committed
Automatic merge of T1.5.1-866-gbae4730e2 and 17 pull requests
- Pull request #570 at 3539862: Experimental glTF 2.0 support with PBR lighting - Pull request #839 at d00beb9: First phase of https://blueprints.launchpad.net/or/+spec/additional-cruise-control-parameters - Pull request #876 at f92de76: docs: add source for documents previously on website to source Documentation folder - Pull request #882 at 9c456aa: Blueprint/train car operations UI window - Pull request #885 at 8f94333: feat: Add notifications to Menu - Pull request #886 at 6c0785b: Scene viewer extension to TrackViewer - Pull request #892 at 1f5ba4c: Signal Function OPP_SIG_ID_TRAINPATH - Pull request #896 at 5866028: First implementation of https://blueprints.launchpad.net/or/+spec/specific-sounds-for-ai-trains - Pull request #897 at 42f1dd9: feat: Improved system information collection - Pull request #903 at 0d6d045: Downloading route content (Github, zip) - Pull request #907 at 9b0b04f: Bug fix for https://bugs.launchpad.net/or/+bug/2047300 Dynamic tracks disappear after long tunnel - Pull request #908 at ad3362c: feat: supports switching adhesion precisions - Pull request #911 at 6834af0: docs: Add refactoring as a special type of PR - Pull request #912 at 659396e: New Triple Valve Features Vol. 2 - Pull request #914 at adc9bd7: Adjustments to Duplex steam - Pull request #915 at 6d911d7: Correct calculation error with curve friction - Pull request #916 at e466e27: Distributed Power Air Brake Synchronization
19 parents 1428a2e + bae4730 + 3539862 + d00beb9 + f92de76 + 9c456aa + 8f94333 + 6c0785b + 1f5ba4c + 5866028 + 42f1dd9 + 0d6d045 + 9b0b04f + ad3362c + 6834af0 + 659396e + adc9bd7 + 6d911d7 + e466e27 commit cc032d0

File tree

8 files changed

+419
-140
lines changed

8 files changed

+419
-140
lines changed

Source/Documentation/Manual/cabs.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,14 +684,16 @@ Air Flow Meter
684684

685685
.. index::
686686
single: ORTS_AIR_FLOW_METER
687+
single: ORTS_TRAIN_AIR_FLOW_METER
687688

688689
This cabview control is used on some locomotives, particularly in North America, to show the
689690
volumetric flow rate of air moving from the main res to the brake pipe during release/recharge.
690691
Such an indication can be used to determine when brake pipe charging is complete,
691692
measure the amount of brake pipe leakage, and so on.
692693
The control will only function on locomotives with air brakes.
693694

694-
Here is an example implementation of ORTS_AIR_FLOW_METER as an analog dial::
695+
Here is an example implementation of ORTS_AIR_FLOW_METER as an analog dial (display types other
696+
than analog dials can be used)::
695697

696698

697699
Dial (
@@ -706,8 +708,10 @@ Here is an example implementation of ORTS_AIR_FLOW_METER as an analog dial::
706708
DirIncrease ( 0 )
707709
)
708710

709-
Applicable user-defined units are CUBIC_FT_MIN, LITERS_S, LITERS_MIN, and CUBIC_M_S. Cubic meters per
710-
second will be used if no units are specified.
711+
Alternately, a control type of ORTS_TRAIN_AIR_FLOW_METER can be used to display the total
712+
air flow rate of all locomotives, useful for distributed power where multiple locomotives can
713+
charge the brake pipe simultaneously. Applicable user-defined units are CUBIC_FT_MIN, LITERS_S,
714+
LITERS_MIN, and CUBIC_M_S. Cubic meters per second will be used if no units are specified.
711715

712716

713717
Animated 2D Wipers

Source/Documentation/Manual/physics.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2350,6 +2350,52 @@ The actual set value of traction or dynamic brake of *async* group is shown in
23502350
lines *Throttle* and *Dynamic Brake*, respectively, in brackets, e.g.:
23512351
Throttle: 0% (50%).
23522352

2353+
In addition to applying power and dynamic brake, remote units can also manage the
2354+
train brake, independent brake, and emergency brake in sync with the lead locomotive.
2355+
This can dramatically speed up brake application and release on long trains, which has
2356+
allowed trains to increase in length substantially without major decreases in brake
2357+
performance. Only one locomotive in each group, the 'lead' DP unit, will have brakes
2358+
cut-in. Usually this is the same locomotive recieving throttle data from the lead
2359+
locomotive. In Open Rails, these locomotives are designated automatically. To determine
2360+
which units are the 'lead' in each group, check the ID row on the DPU Info window.
2361+
2362+
As described earlier, operation in *sync* mode or *async* mode has no effect on air
2363+
brake behavior. In reality, additional remote modes such as *set-out*, *bv out*,
2364+
and *isolate* would disable air brakes on remote units, but these modes are not
2365+
present for simplicity.
2366+
2367+
.. index::
2368+
single: ORTSDPBrakeSynchronization
2369+
2370+
By default, Open Rails will treat remote groups as manned helpers who typically
2371+
would not assist in train brake operations, so only independent brakes will synchronize.
2372+
To enable train brake synchronization, the token ``engine(ORTSDPBrakeSynchronization(``
2373+
should be used. The valid settings for ``ORTSDPBrakeSynchronization`` are as follows:
2374+
2375+
- ``"Apply"``: DP units will reduce the brake pipe pressure locally to match the
2376+
equalizing reservoir pressure of the controlling locomotive. (The controlling
2377+
locomotive must also have the ``"Apply"`` setting.)
2378+
- ``"Release"``: DP units will increase the brake pipe pressure locally to match
2379+
the equalizing reservoir pressure of the controlling locomotive. (The controlling
2380+
locomotive must also have the ``"Release"`` setting.)
2381+
- ``"Emergency"``: DP units will vent the brake pipe to 0 if an emergency application
2382+
is triggered by the controlling locomotive. (The controlling locomotive must also
2383+
have the ``"Emergency"`` setting.)
2384+
- ``"Independent"``: DP units will match the brake cylinder pressure of the
2385+
controlling locomotive, and will automatically bail-off automatic brake
2386+
applications if needed. (The controlling locomotive must also have the
2387+
``"Independent"`` setting.)
2388+
- NOTE: Although ``"Independent"`` is enabled by default,
2389+
if ``ORTSDPBrakeSynchronization`` is present in the .eng
2390+
file but ``"Independent"`` is not specified as an option,
2391+
independent brakes will NOT be synchronized.
2392+
2393+
All settings can be combined as needed, simply place a comma between each setting
2394+
in the string: ``ORTSDPBrakeSynchronization("Apply, Release, Emergency, Independent")``
2395+
will simulate the configuration of most modern locomotives. Unlike other distributed power
2396+
features, brake synchronization can be applied to any locomotive type to simulate a wide
2397+
variety of braking systems.
2398+
23532399
Distributed power info and commands can also be displayed and operated through
23542400
cabview controls, as explained :ref:`here <cabs-distributed-power>`
23552401

Source/Orts.Formats.Msts/CabViewFile.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ public enum CABViewControlTypes
201201
ORTS_QUICKRELEASE,
202202
ORTS_OVERCHARGE,
203203
ORTS_AIR_FLOW_METER,
204+
ORTS_TRAIN_AIR_FLOW_METER,
204205
ORTS_BATTERY_SWITCH_COMMAND_SWITCH,
205206
ORTS_BATTERY_SWITCH_COMMAND_BUTTON_CLOSE,
206207
ORTS_BATTERY_SWITCH_COMMAND_BUTTON_OPEN,

Source/Orts.Simulation/Simulation/Physics/Train.cs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ namespace Orts.Simulation.Physics
7676
public class Train
7777
{
7878
public List<TrainCar> Cars = new List<TrainCar>(); // listed front to back
79+
public List<TrainCar> DPLeadUnits = new List<TrainCar>(); // list of all DP lead locomotives
80+
// list of connected locomotives, each element is a list of the connected locomotives themselves
81+
public List<List<TrainCar>> LocoGroups = new List<List<TrainCar>>();
7982
public int Number;
8083
public string Name;
8184
public string TcsParametersFileName;
@@ -138,6 +141,7 @@ public TrainCar LastCar
138141
public float BrakeLine4 = -1; // extra line just in case, ep brake control line. -2: hold, -1: inactive, 0: release, 0 < value <=1: apply
139142
public RetainerSetting RetainerSetting = RetainerSetting.Exhaust;
140143
public int RetainerPercent = 100;
144+
public float TotalBrakePipeFlowM3pS; // Total flow rate of air from all MR to BP
141145
public float TotalTrainBrakePipeVolumeM3; // Total volume of train brake pipe
142146
public float TotalTrainBrakeCylinderVolumeM3; // Total volume of train brake cylinders
143147
public float TotalTrainBrakeSystemVolumeM3; // Total volume of train brake system
@@ -691,6 +695,7 @@ public Train(Simulator simulator, BinaryReader inf)
691695
BrakeLine2PressurePSI = inf.ReadSingle();
692696
BrakeLine3PressurePSI = inf.ReadSingle();
693697
BrakeLine4 = inf.ReadSingle();
698+
TotalBrakePipeFlowM3pS = inf.ReadSingle();
694699
aiBrakePercent = inf.ReadSingle();
695700
LeadLocomotiveIndex = inf.ReadInt32();
696701
RetainerSetting = (RetainerSetting)inf.ReadInt32();
@@ -1039,6 +1044,7 @@ public virtual void Save(BinaryWriter outf)
10391044
outf.Write(BrakeLine2PressurePSI);
10401045
outf.Write(BrakeLine3PressurePSI);
10411046
outf.Write(BrakeLine4);
1047+
outf.Write(TotalBrakePipeFlowM3pS);
10421048
outf.Write(aiBrakePercent);
10431049
outf.Write(LeadLocomotiveIndex);
10441050
outf.Write((int)RetainerSetting);
@@ -1314,6 +1320,7 @@ public TrainCar GetNextCab()
13141320
LeadLocomotiveIndex = Math.Abs(nextCabIndex) - 1;
13151321
Trace.Assert(LeadLocomotive != null, "Tried to switch to non-existent loco");
13161322
TrainCar newLead = LeadLocomotive; // Changing LeadLocomotiveIndex also changed LeadLocomotive
1323+
SetDPUnitIDs(true); // DP IDs must be reprocessed when lead locomotive changes
13171324
((MSTSLocomotive)newLead).UsingRearCab = nextCabIndex < 0;
13181325

13191326
if (oldLead != null && newLead != null && oldLead != newLead)
@@ -1372,6 +1379,7 @@ public void LeadNextLocomotive()
13721379
else if (coud > 1)
13731380
LeadLocomotiveIndex = firstLead;
13741381
TrainCar newLead = LeadLocomotive;
1382+
SetDPUnitIDs(true); // DP IDs must be reprocessed when lead locomotive changes
13751383
if (prevLead != null && newLead != null && prevLead != newLead)
13761384
newLead.CopyControllerSettings(prevLead);
13771385
}
@@ -1529,19 +1537,59 @@ public void ReverseCars()
15291537
/// </summary>
15301538
public void SetDPUnitIDs(bool keepRemoteGroups = false)
15311539
{
1540+
// List to keep track of new 'lead' DP units
1541+
// 'Lead' DP units follow the air brake commands of the master loco, 'trail' DP units do not
1542+
List<TrainCar> tempDPLead = new List<TrainCar>();
1543+
1544+
// List of each DP group's locomotives
1545+
List<List<TrainCar>> tempLocoGroups = new List<List<TrainCar>>();
1546+
1547+
var prevId = -1;
1548+
15321549
var id = 0;
15331550
foreach (var car in Cars)
15341551
{
15351552
//Console.WriteLine("___{0} {1}", car.CarID, id);
1536-
if (car is MSTSLocomotive)
1553+
if (car is MSTSLocomotive loco)
15371554
{
1538-
(car as MSTSLocomotive).DPUnitID = id;
1555+
loco.DPUnitID = id;
1556+
1557+
if (id != prevId && !tempDPLead.Contains(car)) // If this is a new ID, that means we found a 'lead' unit
1558+
{
1559+
tempDPLead.Add(car);
1560+
1561+
prevId = id;
1562+
}
1563+
15391564
if (car.RemoteControlGroup == 1 && !keepRemoteGroups)
15401565
car.RemoteControlGroup = 0;
15411566
}
15421567
else
15431568
id++;
15441569
}
1570+
1571+
foreach (TrainCar locoCar in tempDPLead)
1572+
{
1573+
// The train's lead unit should always be a DP lead unit, even if not at the front
1574+
// If a different locomotive in the lead loco's group has been declared DP lead, replace that loco with the lead loco
1575+
if (LeadLocomotive is MSTSLocomotive lead && locoCar is MSTSLocomotive loco)
1576+
if (loco.DPUnitID == lead.DPUnitID && locoCar != LeadLocomotive)
1577+
{
1578+
tempDPLead.Insert(tempDPLead.IndexOf(locoCar), LeadLocomotive);
1579+
tempDPLead.Remove(locoCar);
1580+
break; // foreach doesn't like it when the collection is modified during the loop, break to mitigate error
1581+
}
1582+
}
1583+
1584+
DPLeadUnits = tempDPLead;
1585+
1586+
foreach (TrainCar loco in DPLeadUnits)
1587+
{
1588+
// Find all locomotives connected to each DP unit
1589+
tempLocoGroups.Add(DetermineLocomotiveGroup(loco));
1590+
}
1591+
1592+
LocoGroups = tempLocoGroups;
15451593
}
15461594

15471595
/// <summary>
@@ -4234,6 +4282,76 @@ public TrainCar FindLeadLocomotive()
42344282
return null;
42354283
}
42364284

4285+
//================================================================================================//
4286+
/// <summary>
4287+
/// Find connected locomotives
4288+
/// <\summary>
4289+
4290+
// Finds all locomotives connected to the TrainCar reference provided as input,
4291+
// returning the connected locomotives* as a list of TrainCars.
4292+
// Returns null if there are no locomotives in the given group.
4293+
// *If the input is a steam locomotive, the output will instead be the steam locomotive and any tenders connected.
4294+
// Useful for determining locomotive brake propagation on groups of locomotives other than the lead group.
4295+
4296+
public List<TrainCar> DetermineLocomotiveGroup(TrainCar loco)
4297+
{
4298+
if (loco is MSTSLocomotive)
4299+
{
4300+
List<TrainCar> tempGroup = new List<TrainCar>();
4301+
int first;
4302+
int last;
4303+
4304+
first = last = Cars.IndexOf(loco);
4305+
4306+
// If locomotive is a steam locomotive, check for tenders only
4307+
if (first >= 0 && loco is MSTSSteamLocomotive)
4308+
{
4309+
if (first > 0 && Cars[first - 1].WagonType == TrainCar.WagonTypes.Tender)
4310+
first--;
4311+
if (last < Cars.Count - 1 && Cars[last + 1].WagonType == TrainCar.WagonTypes.Tender)
4312+
last++;
4313+
}
4314+
else // Other locomotive types
4315+
{
4316+
for (int i = last; i < Cars.Count && (Cars[i] is MSTSLocomotive && !(Cars[i] is MSTSSteamLocomotive)); i++)
4317+
last = i;
4318+
for (int i = first; i >= 0 && (Cars[i] is MSTSLocomotive && !(Cars[i] is MSTSSteamLocomotive)); i--)
4319+
first = i;
4320+
}
4321+
4322+
if (first < 0 || last < 0)
4323+
return null;
4324+
else
4325+
{
4326+
for (int i = first; i <= last; i++)
4327+
tempGroup.Add(Cars[i]);
4328+
return tempGroup;
4329+
}
4330+
}
4331+
else
4332+
return null;
4333+
}
4334+
4335+
//================================================================================================//
4336+
/// <summary>
4337+
/// Find connected DP lead locomotive
4338+
/// <\summary>
4339+
4340+
// Finds the DP lead locomotive to the TrainCar reference provided as input,
4341+
// returning the TrainCar reference to the DP lead unit.
4342+
// Returns null if there are no DP lead units controlling the given train car.
4343+
4344+
public TrainCar DetermineDPLeadLocomotive(TrainCar locoCar)
4345+
{
4346+
if (Cars.Contains(locoCar) && locoCar is MSTSLocomotive loco)
4347+
foreach (TrainCar dpLead in DPLeadUnits)
4348+
if (dpLead is MSTSLocomotive dpLeadLoco)
4349+
if (dpLeadLoco.DPUnitID == loco.DPUnitID)
4350+
return dpLead;
4351+
4352+
return null;
4353+
}
4354+
42374355
//================================================================================================//
42384356
/// <summary>
42394357
/// Propagate brake pressure

Source/Orts.Simulation/Simulation/RollingStocks/MSTSDieselLocomotive.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,8 +1171,7 @@ public string GetDpuStatus(bool dataDpu, CABViewControlUnits loadUnits = CABView
11711171
var brakeInfoValue = brakeValue(Simulator.Catalog.GetString("BP"), Simulator.Catalog.GetString("Flow"));
11721172
status.AppendFormat("{0:F0}\t", brakeInfoValue);
11731173
// Air flow meter
1174-
brakeInfoValue = brakeValue(Simulator.Catalog.GetString("Flow"), Simulator.Catalog.GetString("EOT"));
1175-
status.AppendFormat("{0:F0}\t", brakeInfoValue);
1174+
status.AppendFormat("{0:F0}\t", FormatStrings.FormatAirFlow(FilteredBrakePipeFlowM3pS, IsMetric));
11761175

11771176
// Remote
11781177
if (dataDpu)

Source/Orts.Simulation/Simulation/RollingStocks/MSTSLocomotive.cs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,10 @@ public float OdometerM
428428
public bool DoesBrakeCutPower { get; private set; }
429429
public float BrakeCutsPowerAtBrakeCylinderPressurePSI { get; private set; }
430430
public bool DoesHornTriggerBell { get; private set; }
431+
public bool DPSyncTrainApplication { get; private set; }
432+
public bool DPSyncTrainRelease { get; private set; }
433+
public bool DPSyncEmergency { get; private set; }
434+
public bool DPSyncIndependent { get; private set; } = true;
431435

432436
protected const float DefaultCompressorRestartToMaxSysPressureDiff = 35; // Used to check if difference between these two .eng parameters is correct, and to correct it
433437
protected const float DefaultMaxMainResToCompressorRestartPressureDiff = 10; // Used to check if difference between these two .eng parameters is correct, and to correct it
@@ -1110,6 +1114,19 @@ public override void Parse(string lowercasetoken, STFReader stf)
11101114
}
11111115
}
11121116
break;
1117+
case "engine(ortsdpbrakesynchronization":
1118+
var dpSyncModes = stf.ReadStringBlock("").ToLower().Replace(" ", "").Split(',');
1119+
if (dpSyncModes.Contains("apply"))
1120+
DPSyncTrainApplication = true;
1121+
if (dpSyncModes.Contains("release"))
1122+
DPSyncTrainRelease = true;
1123+
if (dpSyncModes.Contains("emergency"))
1124+
DPSyncEmergency = true;
1125+
if (dpSyncModes.Contains("independent"))
1126+
DPSyncIndependent = true;
1127+
else // Independent synchronization is assumed to be enabled unless explicitly not enabled
1128+
DPSyncIndependent = false;
1129+
break;
11131130
case "engine(ortsdynamicblendingoverride": DynamicBrakeBlendingOverride = stf.ReadBoolBlock(false); break;
11141131
case "engine(ortsdynamicblendingforcematch": DynamicBrakeBlendingForceMatch = stf.ReadBoolBlock(false); break;
11151132
case "engine(vacuumbrakeshasvacuumpump": VacuumPumpFitted = stf.ReadBoolBlock(false); break;
@@ -2686,12 +2703,21 @@ protected virtual void UpdateCompressor(float elapsedClockSeconds)
26862703
if (MainResPressurePSI < CompressorRestartPressurePSI && LocomotivePowerSupply.AuxiliaryPowerSupplyState == PowerSupplyState.PowerOn && !CompressorIsOn)
26872704
{
26882705
SignalEvent(Event.CompressorOn);
2689-
foreach (var car in Train.Cars)
2706+
foreach (List<TrainCar> locoGroup in Train.LocoGroups)
26902707
{
2691-
if (car is MSTSLocomotive loco && loco.RemoteControlGroup == 0 && loco.LocomotivePowerSupply.AuxiliaryPowerSupplyOn && !loco.CompressorIsOn && loco.CompressorIsMUControlled)
2708+
// Synchronize compressor between coupled locomotives
2709+
if (locoGroup.Contains(this as TrainCar))
26922710
{
2711+
foreach (TrainCar locoCar in locoGroup)
2712+
if (locoCar is MSTSLocomotive loco && loco.LocomotivePowerSupply.AuxiliaryPowerSupplyOn && !loco.CompressorIsOn)
26932713
loco.SignalEvent(Event.CompressorOn);
26942714
}
2715+
else if (CompressorIsMUControlled) // Synchronize compressor between remote groups if configured to do so
2716+
{
2717+
foreach (TrainCar remoteLocoCar in locoGroup)
2718+
if (remoteLocoCar is MSTSLocomotive remoteLoco && remoteLoco.LocomotivePowerSupply.AuxiliaryPowerSupplyOn && !remoteLoco.CompressorIsOn && remoteLoco.CompressorIsMUControlled)
2719+
remoteLoco.SignalEvent(Event.CompressorOn);
2720+
}
26952721
}
26962722
}
26972723
else if ((MainResPressurePSI >= MaxMainResPressurePSI || LocomotivePowerSupply.AuxiliaryPowerSupplyState != PowerSupplyState.PowerOn) && CompressorIsOn)
@@ -5464,6 +5490,32 @@ public virtual float GetDataOf(CabViewControl cvc)
54645490
data = this.FilteredBrakePipeFlowM3pS;
54655491
break;
54665492

5493+
}
5494+
break;
5495+
}
5496+
case CABViewControlTypes.ORTS_TRAIN_AIR_FLOW_METER:
5497+
{
5498+
switch (cvc.Units)
5499+
{
5500+
case CABViewControlUnits.CUBIC_FT_MIN:
5501+
data = this.Train.TotalBrakePipeFlowM3pS * 35.3147f * 60.0f;
5502+
break;
5503+
5504+
case CABViewControlUnits.LITRES_S:
5505+
case CABViewControlUnits.LITERS_S:
5506+
data = this.Train.TotalBrakePipeFlowM3pS * 1000.0f;
5507+
break;
5508+
5509+
case CABViewControlUnits.LITRES_MIN:
5510+
case CABViewControlUnits.LITERS_MIN:
5511+
data = this.Train.TotalBrakePipeFlowM3pS * 1000.0f * 60.0f;
5512+
break;
5513+
5514+
case CABViewControlUnits.CUBIC_M_S:
5515+
default:
5516+
data = this.Train.TotalBrakePipeFlowM3pS;
5517+
break;
5518+
54675519
}
54685520
break;
54695521
}

0 commit comments

Comments
 (0)