Skip to content

Commit 9f0c709

Browse files
committed
add animated air hoses (first pass)
1 parent 95a022f commit 9f0c709

File tree

3 files changed

+244
-5
lines changed

3 files changed

+244
-5
lines changed

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

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,18 @@ public virtual void LoadFromWagFile(string wagFilePath)
419419
RearCouplerShapeFileName = null;
420420
}
421421

422+
if (FrontAirHoseShapeFileName != null && !File.Exists(wagonFolderSlash + FrontAirHoseShapeFileName))
423+
{
424+
Trace.TraceWarning("{0} references non-existent shape {1}", WagFilePath, wagonFolderSlash + FrontAirHoseShapeFileName);
425+
FrontAirHoseShapeFileName = null;
426+
}
427+
428+
if (RearAirHoseShapeFileName != null && !File.Exists(wagonFolderSlash + RearAirHoseShapeFileName))
429+
{
430+
Trace.TraceWarning("{0} references non-existent shape {1}", WagFilePath, wagonFolderSlash + RearAirHoseShapeFileName);
431+
RearAirHoseShapeFileName = null;
432+
}
433+
422434
// If trailing loco resistance constant has not been defined in WAG/ENG file then assign default value based upon orig Davis values
423435
if (TrailLocoResistanceFactor == 0)
424436
{
@@ -1141,6 +1153,15 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
11411153
stf.SkipRestOfBlock();
11421154
break;
11431155

1156+
case "wagon(coupling(frontairhoseanim":
1157+
stf.MustMatch("(");
1158+
FrontAirHoseShapeFileName = stf.ReadString();
1159+
FrontAirHoseAnimWidthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1160+
FrontAirHoseAnimHeightM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1161+
FrontAirHoseAnimLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1162+
stf.SkipRestOfBlock();
1163+
break;
1164+
11441165
case "wagon(coupling(rearcoupleranim":
11451166
stf.MustMatch("(");
11461167
RearCouplerShapeFileName = stf.ReadString();
@@ -1150,7 +1171,16 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
11501171
stf.SkipRestOfBlock();
11511172
break;
11521173

1153-
case "wagon(coupling(spring(ortstensionstiffness":
1174+
case "wagon(coupling(rearairhoseanim":
1175+
stf.MustMatch("(");
1176+
RearAirHoseShapeFileName = stf.ReadString();
1177+
RearAirHoseAnimWidthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1178+
RearAirHoseAnimHeightM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1179+
RearAirHoseAnimLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1180+
stf.SkipRestOfBlock();
1181+
break;
1182+
1183+
case "wagon(coupling(spring(ortstensionstiffness":
11541184
stf.MustMatch("(");
11551185
Couplers[CouplerCountLocation].SetTensionStiffness(stf.ReadFloat(STFReader.UNITS.Force, null), stf.ReadFloat(STFReader.UNITS.Force, null));
11561186
stf.SkipRestOfBlock();
@@ -1176,6 +1206,25 @@ public virtual void Parse(string lowercasetoken, STFReader stf)
11761206
stf.SkipRestOfBlock();
11771207
break;
11781208

1209+
case "wagon(coupling(frontairhosediconnectedanim":
1210+
stf.MustMatch("(");
1211+
FrontAirHoseDisconnectedShapeFileName = stf.ReadString();
1212+
FrontAirHoseDisconnectedAnimWidthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1213+
FrontAirHoseDisconnectedAnimHeightM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1214+
FrontAirHoseDisconnectedAnimLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1215+
stf.SkipRestOfBlock();
1216+
break;
1217+
1218+
case "wagon(coupling(rearairhosediconnectedanim":
1219+
stf.MustMatch("(");
1220+
RearAirHoseDisconnectedShapeFileName = stf.ReadString();
1221+
RearAirHoseDisconnectedAnimWidthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1222+
RearAirHoseDisconnectedAnimHeightM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1223+
RearAirHoseDisconnectedAnimLengthM = stf.ReadFloat(STFReader.UNITS.Distance, null);
1224+
stf.SkipRestOfBlock();
1225+
break;
1226+
1227+
11791228
case "wagon(coupling(spring(ortscompressionstiffness":
11801229
stf.MustMatch("(");
11811230
Couplers[CouplerCountLocation].SetCompressionStiffness(stf.ReadFloat(STFReader.UNITS.Force, null), stf.ReadFloat(STFReader.UNITS.Force, null));
@@ -1342,6 +1391,27 @@ public virtual void Copy(MSTSWagon copy)
13421391
RearCouplerOpenAnimHeightM = copy.RearCouplerOpenAnimHeightM;
13431392
RearCouplerOpenAnimLengthM = copy.RearCouplerOpenAnimLengthM;
13441393
RearCouplerOpenFitted = copy.RearCouplerOpenFitted;
1394+
1395+
FrontAirHoseShapeFileName = copy.FrontAirHoseShapeFileName;
1396+
FrontAirHoseAnimWidthM = copy.FrontAirHoseAnimWidthM;
1397+
FrontAirHoseAnimHeightM = copy.FrontAirHoseAnimHeightM;
1398+
FrontAirHoseAnimLengthM = copy.FrontAirHoseAnimLengthM;
1399+
1400+
FrontAirHoseDisconnectedShapeFileName = copy.FrontAirHoseDisconnectedShapeFileName;
1401+
FrontAirHoseDisconnectedAnimWidthM = copy.FrontAirHoseDisconnectedAnimWidthM;
1402+
FrontAirHoseDisconnectedAnimHeightM = copy.FrontAirHoseDisconnectedAnimHeightM;
1403+
FrontAirHoseDisconnectedAnimLengthM = copy.FrontAirHoseDisconnectedAnimLengthM;
1404+
1405+
RearAirHoseShapeFileName = copy.RearAirHoseShapeFileName;
1406+
RearAirHoseAnimWidthM = copy.RearAirHoseAnimWidthM;
1407+
RearAirHoseAnimHeightM = copy.RearAirHoseAnimHeightM;
1408+
RearAirHoseAnimLengthM = copy.RearAirHoseAnimLengthM;
1409+
1410+
RearAirHoseDisconnectedShapeFileName = copy.RearAirHoseDisconnectedShapeFileName;
1411+
RearAirHoseDisconnectedAnimWidthM = copy.RearAirHoseDisconnectedAnimWidthM;
1412+
RearAirHoseDisconnectedAnimHeightM = copy.RearAirHoseDisconnectedAnimHeightM;
1413+
RearAirHoseDisconnectedAnimLengthM = copy.RearAirHoseDisconnectedAnimLengthM;
1414+
13451415
CarWidthM = copy.CarWidthM;
13461416
CarHeightM = copy.CarHeightM;
13471417
CarLengthM = copy.CarLengthM;

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,27 @@ public static Interpolator SteamHeatBoilerFuelUsageGalukpH()
227227
public bool RearCouplerOpenFitted = false;
228228
public bool RearCouplerOpen = false;
229229

230+
// Air hose animation
231+
public string FrontAirHoseShapeFileName;
232+
public float FrontAirHoseAnimLengthM;
233+
public float FrontAirHoseAnimWidthM;
234+
public float FrontAirHoseAnimHeightM;
235+
236+
public string FrontAirHoseDisconnectedShapeFileName;
237+
public float FrontAirHoseDisconnectedAnimLengthM;
238+
public float FrontAirHoseDisconnectedAnimWidthM;
239+
public float FrontAirHoseDisconnectedAnimHeightM;
240+
241+
public string RearAirHoseShapeFileName;
242+
public float RearAirHoseAnimLengthM;
243+
public float RearAirHoseAnimWidthM;
244+
public float RearAirHoseAnimHeightM;
245+
246+
public string RearAirHoseDisconnectedShapeFileName;
247+
public float RearAirHoseDisconnectedAnimLengthM;
248+
public float RearAirHoseDisconnectedAnimWidthM;
249+
public float RearAirHoseDisconnectedAnimHeightM;
250+
230251
// Used to calculate Carriage Steam Heat Loss
231252
public const float BogieHeightM = 1.06f; // Height reduced by 1.06m to allow for bogies, etc
232253
public const float CarCouplingPipeM = 1.2f; // Allow for connection between cars (assume 2' each end) - no heat is contributed to carriages.

Source/RunActivity/Viewer3D/RollingStock/MSTSWagonViewer.cs

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ public class MSTSWagonViewer : TrainCarViewer
4444
protected AnimatedShape FrontCouplerOpenShape;
4545
protected AnimatedShape RearCouplerShape;
4646
protected AnimatedShape RearCouplerOpenShape;
47+
48+
protected AnimatedShape FrontAirHoseShape;
49+
protected AnimatedShape FrontAirHoseDisconnectedShape;
50+
protected AnimatedShape RearAirHoseShape;
51+
protected AnimatedShape RearAirHoseDisconnectedShape;
52+
4753
public static readonly Action Noop = () => { };
4854
/// <summary>
4955
/// Dictionary of built-in locomotive control keyboard commands, Action[] is in the order {KeyRelease, KeyPress}
@@ -276,6 +282,28 @@ from data in effect.Value
276282
RearCouplerOpenShape = new AnimatedShape(viewer, wagonFolderSlash + car.RearCouplerOpenShapeFileName + '\0' + wagonFolderSlash, new WorldPosition(car.WorldPosition), ShapeFlags.ShadowCaster);
277283
}
278284

285+
// Initialise air hose shapes
286+
287+
if (car.FrontAirHoseShapeFileName != null)
288+
{
289+
FrontAirHoseShape = new AnimatedShape(viewer, wagonFolderSlash + car.FrontAirHoseShapeFileName + '\0' + wagonFolderSlash, new WorldPosition(car.WorldPosition), ShapeFlags.ShadowCaster);
290+
}
291+
292+
if (car.FrontAirHoseDisconnectedShapeFileName != null)
293+
{
294+
FrontAirHoseDisconnectedShape = new AnimatedShape(viewer, wagonFolderSlash + car.FrontAirHoseDisconnectedShapeFileName + '\0' + wagonFolderSlash, new WorldPosition(car.WorldPosition), ShapeFlags.ShadowCaster);
295+
}
296+
297+
if (car.RearAirHoseShapeFileName != null)
298+
{
299+
RearAirHoseShape = new AnimatedShape(viewer, wagonFolderSlash + car.RearAirHoseShapeFileName + '\0' + wagonFolderSlash, new WorldPosition(car.WorldPosition), ShapeFlags.ShadowCaster);
300+
}
301+
302+
if (car.RearAirHoseDisconnectedShapeFileName != null)
303+
{
304+
RearAirHoseDisconnectedShape = new AnimatedShape(viewer, wagonFolderSlash + car.RearAirHoseDisconnectedShapeFileName + '\0' + wagonFolderSlash, new WorldPosition(car.WorldPosition), ShapeFlags.ShadowCaster);
305+
}
306+
279307

280308
if (car.InteriorShapeFileName != null)
281309
InteriorShape = new AnimatedShape(viewer, wagonFolderSlash + car.InteriorShapeFileName + '\0' + wagonFolderSlash, car.WorldPosition, ShapeFlags.Interior, 30.0f);
@@ -892,7 +920,8 @@ private void UpdateCouplers(RenderFrame frame, ElapsedTime elapsedTime)
892920
{
893921
// Get the movement that would be needed to locate the coupler on the car if they were pointing in the default direction.
894922
var displacement = new Vector3
895-
{ X = Car.FrontCouplerAnimWidthM,
923+
{
924+
X = Car.FrontCouplerAnimWidthM,
896925
Y = Car.FrontCouplerAnimHeightM,
897926
Z = (Car.FrontCouplerAnimLengthM + (Car.CarLengthM / 2.0f) + Car.FrontCouplerSlackM - Car.WagonFrontCouplerCurveExtM)
898927
};
@@ -993,6 +1022,105 @@ private void UpdateCouplers(RenderFrame frame, ElapsedTime elapsedTime)
9931022
RearCouplerShape.PrepareFrame(frame, elapsedTime);
9941023
}
9951024
}
1025+
1026+
// Display front airhose in sim if open coupler shape is configured, otherwise skip to next section, and just display closed (default) coupler if configured
1027+
if (FrontAirHoseShape != null && !(Viewer.Camera.AttachedCar == this.MSTSWagon && Viewer.Camera.Style == Camera.Styles.ThreeDimCab))
1028+
{
1029+
// Get the movement that would be needed to locate the coupler on the car if they were pointing in the default direction.
1030+
var displacement = new Vector3
1031+
{
1032+
X = Car.FrontAirHoseAnimWidthM,
1033+
Y = Car.FrontAirHoseAnimHeightM,
1034+
Z = (Car.FrontAirHoseAnimLengthM + (Car.CarLengthM / 2.0f)) // Reversed as this is the rear coupler of the wagon
1035+
};
1036+
1037+
if (Car.CarAhead != null) // Display animated coupler if there is a car behind this car
1038+
{
1039+
var quaternion = PositionCoupler(Car, FrontAirHoseShape, displacement);
1040+
1041+
var quaternionCar = new Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
1042+
1043+
var maximumCouplerExtension = Me.FromIn(3.0f);
1044+
var AirHoseAngleRadians = (Car.CouplerSlackM / maximumCouplerExtension) * 0.174533f;
1045+
1046+
AlignCouplerWithCar(Car, FrontAirHoseShape);
1047+
1048+
AdjustAirHoseAngle(Car, FrontAirHoseShape, quaternionCar, AirHoseAngleRadians);
1049+
1050+
// Display Animation Shape
1051+
FrontAirHoseShape.PrepareFrame(frame, elapsedTime);
1052+
1053+
}
1054+
else if (FrontAirHoseDisconnectedShape != null && Car.RearCouplerOpenFitted && Car.RearCouplerOpen) // Display open coupler if no car is behind car, and an open coupler shape is present
1055+
{
1056+
var quaternion = PositionCoupler(Car, FrontAirHoseDisconnectedShape, displacement);
1057+
1058+
AlignCouplerWithCar(Car, FrontAirHoseDisconnectedShape);
1059+
1060+
// Display Animation Shape
1061+
FrontAirHoseDisconnectedShape.PrepareFrame(frame, elapsedTime);
1062+
}
1063+
else //Display closed static coupler by default if other conditions not met
1064+
{
1065+
var quaternion = PositionCoupler(Car, FrontAirHoseShape, displacement);
1066+
1067+
AlignCouplerWithCar(Car, FrontAirHoseShape);
1068+
1069+
// Display Animation Shape
1070+
FrontAirHoseShape.PrepareFrame(frame, elapsedTime);
1071+
}
1072+
}
1073+
1074+
1075+
// Display rear airhose in sim if open coupler shape is configured, otherwise skip to next section, and just display closed (default) coupler if configured
1076+
if (RearAirHoseShape != null && !(Viewer.Camera.AttachedCar == this.MSTSWagon && Viewer.Camera.Style == Camera.Styles.ThreeDimCab))
1077+
{
1078+
// Get the movement that would be needed to locate the coupler on the car if they were pointing in the default direction.
1079+
var displacement = new Vector3
1080+
{
1081+
X = Car.RearAirHoseAnimWidthM,
1082+
Y = Car.RearAirHoseAnimHeightM,
1083+
Z = -(Car.RearAirHoseAnimLengthM + (Car.CarLengthM / 2.0f)) // Reversed as this is the rear coupler of the wagon
1084+
};
1085+
1086+
if (Car.CarBehind != null) // Display animated coupler if there is a car behind this car
1087+
{
1088+
var quaternion = PositionCoupler(Car, RearAirHoseShape, displacement);
1089+
1090+
var quaternionCar = new Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
1091+
1092+
var maximumCouplerExtension = Me.FromIn(3.0f);
1093+
var AirHoseAngleRadians = -(Car.CouplerSlackM / maximumCouplerExtension) * 0.174533f;
1094+
1095+
AlignCouplerWithCar(Car, RearAirHoseShape);
1096+
1097+
AdjustAirHoseAngle(Car, RearAirHoseShape, quaternionCar, AirHoseAngleRadians);
1098+
1099+
// Display Animation Shape
1100+
RearAirHoseShape.PrepareFrame(frame, elapsedTime);
1101+
1102+
}
1103+
else if (RearAirHoseDisconnectedShape != null && Car.RearCouplerOpenFitted && Car.RearCouplerOpen) // Display open coupler if no car is behind car, and an open coupler shape is present
1104+
{
1105+
var quaternion = PositionCoupler(Car, RearAirHoseDisconnectedShape, displacement);
1106+
1107+
AlignCouplerWithCar(Car, RearAirHoseDisconnectedShape);
1108+
1109+
// Display Animation Shape
1110+
RearAirHoseDisconnectedShape.PrepareFrame(frame, elapsedTime);
1111+
}
1112+
else //Display closed static coupler by default if other conditions not met
1113+
{
1114+
var quaternion = PositionCoupler(Car, RearAirHoseShape, displacement);
1115+
1116+
AlignCouplerWithCar(Car, RearAirHoseShape);
1117+
1118+
// Display Animation Shape
1119+
RearAirHoseShape.PrepareFrame(frame, elapsedTime);
1120+
}
1121+
}
1122+
1123+
9961124
}
9971125

9981126
/// <summary>
@@ -1058,11 +1186,31 @@ private void AdjustCouplerAngle(TrainCar adjacentCar, AnimatedShape couplerShape
10581186
}
10591187

10601188
/// <summary>
1061-
/// Rotate the coupler to align with the direction (attitude) of the car.
1189+
/// Turn coupler the required angle between the cars
10621190
/// </summary>
1063-
/// <param name="car"></param>
1191+
/// <param name="adjacentCar"></param>
10641192
/// <param name="couplerShape"></param>
1065-
private void AlignCouplerWithCar(TrainCar car, AnimatedShape couplerShape)
1193+
/// <param name="quaternionCar"></param>
1194+
private void AdjustAirHoseAngle(TrainCar adjacentCar, AnimatedShape airhoseShape, Quaternion quaternionCar, float angle)
1195+
{
1196+
var mRotation = Matrix.CreateRotationZ(angle);
1197+
1198+
// Rotate the coupler to align with the calculated angle direction
1199+
airhoseShape.Location.XNAMatrix = mRotation * airhoseShape.Location.XNAMatrix;
1200+
1201+
// var mextRotation = Matrix.CreateRotationX(-angle);
1202+
1203+
// Rotate the coupler to align with the calculated angle direction
1204+
// airhoseShape.Location.XNAMatrix = mextRotation * airhoseShape.Location.XNAMatrix;
1205+
1206+
}
1207+
1208+
/// <summary>
1209+
/// Rotate the coupler to align with the direction (attitude) of the car.
1210+
/// </summary>
1211+
/// <param name="car"></param>
1212+
/// <param name="couplerShape"></param>
1213+
private void AlignCouplerWithCar(TrainCar car, AnimatedShape couplerShape)
10661214
{
10671215

10681216
var p = new WorldPosition(car.WorldPosition);

0 commit comments

Comments
 (0)