@@ -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