@@ -911,9 +911,11 @@ public virtual void Update(float elapsedClockSeconds)
911911 // Initialize RigidWheelBaseM in first loop if not defined in ENG file, then ignore
912912 if ( RigidWheelBaseM == 0 && ! RigidWheelBaseInitialised ) // Calculate default values if no value in Wag File
913913 {
914- float Axles = WheelAxles . Count ;
915- float Bogies = Parts . Count - 1 ;
916- float BogieSize = Axles / Bogies ;
914+ int Axles = WheelAxles . Sum ( w => w . Fake ? 0 : 1 ) ; // Only consider real axles
915+ int Bogies = Parts . Sum ( p => p . Bogie ? 1 : 0 ) ;
916+ int BogieSize = Axles ;
917+ if ( Bogies > 0 )
918+ BogieSize = ( int ) ( WheelAxles . Sum ( w => ! w . Fake && w . Part . Bogie ? 1 : 0 ) / Bogies ) ; // Only consider axles attached to bogies
917919
918920 RigidWheelBaseM = 1.6764f ; // Set a default in case no option is found - assume a standard 4 wheel (2 axle) bogie - wheel base - 5' 6" (1.6764m)
919921
@@ -2804,16 +2806,20 @@ public void SetUpWheels()
28042806 // No parts means no bogies (always?), so make sure we've got Parts[0] for the car itself.
28052807 if ( Parts . Count == 0 )
28062808 Parts . Add ( new TrainCarPart ( Vector3 . Zero , 0 ) ) ;
2807- // No axles but we have bogies.
2808- if ( WheelAxles . Count == 0 && Parts . Count > 1 )
2809- {
2810- // Fake the axles by pretending each bogie has 1 axle.
2809+ // Determine how many parts are considered bogies (used to calculate position of the car)
2810+ int bogieCount = 0 ;
2811+ foreach ( TrainCarPart p in Parts )
2812+ if ( p . Bogie == true )
2813+ bogieCount ++ ;
2814+ // No axles but we have bogies. Each bogie needs axles to function correctly
2815+ if ( WheelAxles . Count == 0 && bogieCount > 0 )
2816+ {
2817+ // Add a fake axle to each bogie, a second fake axle will be added later if needed
28112818 for ( int i = 1 ; i < Parts . Count ; i ++ )
2812- WheelAxles . Add ( new WheelAxle ( Parts [ i ] . OffsetM , i , Parts [ i ] . iMatrix ) ) ;
2813- Trace . TraceInformation ( "Wheel axle data faked based on {1} bogies for {0}" , WagFilePath , Parts . Count - 1 ) ;
2819+ if ( Parts [ i ] . Bogie == true )
2820+ WheelAxles . Add ( new WheelAxle ( Parts [ i ] . OffsetM , i , Parts [ i ] . iMatrix , true ) ) ;
2821+ Trace . TraceInformation ( "Wheel axle data faked based on {1} bogies for {0}" , WagFilePath , bogieCount ) ;
28142822 }
2815- bool articFront = ! WheelAxles . Any ( a => a . OffsetM . Z < 0 ) ;
2816- bool articRear = ! WheelAxles . Any ( a => a . OffsetM . Z > 0 ) ;
28172823 // Validate the axles' assigned bogies and count up the axles on each bogie.
28182824 if ( WheelAxles . Count > 0 )
28192825 {
@@ -2833,10 +2839,6 @@ public void SetUpWheels()
28332839 w . Part = Parts [ w . BogieIndex ] ;
28342840 w . Part . SumWgt ++ ;
28352841 }
2836-
2837- // Make sure the axles are sorted by OffsetM along the car.
2838- // Attempting to sort car w/o WheelAxles will resort to an error.
2839- WheelAxles . Sort ( WheelAxles [ 0 ] ) ;
28402842 }
28412843
28422844 //fix bogies with only one wheel set:
@@ -2856,7 +2858,7 @@ public void SetUpWheels()
28562858 {
28572859 if ( w . OffsetM . Z . AlmostEqual ( Parts [ i ] . OffsetM . Z , 0.6f ) )
28582860 {
2859- var w1 = new WheelAxle ( new Vector3 ( w . OffsetM . X , w . OffsetM . Y , w . OffsetM . Z - 0.5f ) , w . BogieIndex , i ) ;
2861+ var w1 = new WheelAxle ( new Vector3 ( w . OffsetM . X , w . OffsetM . Y , w . OffsetM . Z - 0.5f ) , w . BogieIndex , i , true ) ;
28602862 w1 . Part = Parts [ w1 . BogieIndex ] ; //create virtual wheel
28612863 w1 . Part . SumWgt ++ ;
28622864 WheelAxles . Add ( w1 ) ;
@@ -2869,34 +2871,29 @@ public void SetUpWheels()
28692871 }
28702872 }
28712873
2872- // Count up the number of bogies (parts) with at least 2 axles.
2874+ // Check how many parts can drive the position of the car itself
2875+ // Each part needs at least 2 components (sum of weights > 1.5) for position calculation to work
28732876 for ( var i = 1 ; i < Parts . Count ; i ++ )
28742877 if ( Parts [ i ] . SumWgt > 1.5 )
28752878 Parts [ 0 ] . SumWgt ++ ;
28762879
2877- // This check is for the single axle/bogie issue.
2878- // Check SumWgt using Parts[0].SumWgt.
2879- // Certain locomotives do not test well when using Part.SumWgt versus Parts[0].SumWgt.
2880- // Make sure test using Parts[0] is performed after the above for loop.
2880+ // Check if articulation is desired on this car, as this requires different handling
2881+ bool articFront = ( FrontArticulation == 1 || ( FrontArticulation == - 1 && ! WheelAxles . Any ( a => a . OffsetM . Z < 0 ) ) ) ;
2882+ bool articRear = ( RearArticulation == 1 || ( RearArticulation == - 1 && ! WheelAxles . Any ( a => a . OffsetM . Z > 0 ) ) ) ;
2883+
2884+ // If car has insufficient bogies and it's not because of articulation, attempt to avoid position calculation errors
2885+ // Detach wheels from the last bogie, and instead attach to the main part, which should allow calculations to work properly
28812886 if ( ! articFront && ! articRear && ( Parts [ 0 ] . SumWgt < 1.5 ) )
28822887 {
2883- foreach ( var w in WheelAxles )
2888+ foreach ( WheelAxle w in WheelAxles )
28842889 {
28852890 if ( w . BogieIndex >= Parts . Count - 1 )
28862891 {
28872892 w . BogieIndex = 0 ;
28882893 w . Part = Parts [ w . BogieIndex ] ;
2889-
28902894 }
28912895 }
28922896 }
2893- // Using WheelAxles.Count test to control WheelAxlesLoaded flag.
2894- if ( WheelAxles . Count >= 2 ) // Some cars only have two axles.
2895- {
2896- WheelAxles . Sort ( WheelAxles [ 0 ] ) ;
2897- WheelAxlesLoaded = true ;
2898- }
2899-
29002897
29012898#if DEBUG_WHEELS
29022899 Console . WriteLine ( WagFilePath ) ;
@@ -2907,43 +2904,49 @@ public void SetUpWheels()
29072904 foreach ( var p in Parts )
29082905 Console . WriteLine ( " part: matrix {1,5:F0} offset {0,10:F4} weight {2,5:F0}" , p . OffsetM , p . iMatrix , p . SumWgt ) ;
29092906#endif
2910- // Decided to control what is sent to SetUpWheelsArticulation()by using
2911- // WheelAxlesLoaded as a flag. This way, wagons that have to be processed are included
2912- // and the rest left out.
2913-
2914- // Force articulation if stock is configured as such
2915- // Otherwise, use default behavior which gives articulation if there are no axles forward/rearward on the model,
2916- // disables articulation on engines, and only allows articulation with 3 or fewer axles, but not 1 axle
2917- bool articulatedFront = ( FrontArticulation == 1 ||
2918- ( FrontArticulation == - 1 && ! WheelAxles . Any ( a => a . OffsetM . Z < 0 ) && WagonType != WagonTypes . Engine && WheelAxles . Count != 1 && WheelAxles . Count <= 3 ) ) ;
2919- bool articulatedRear = ( RearArticulation == 1 ||
2920- ( RearArticulation == - 1 && ! WheelAxles . Any ( a => a . OffsetM . Z > 0 ) && WagonType != WagonTypes . Engine && WheelAxles . Count != 1 && WheelAxles . Count <= 3 ) ) ;
2921-
2922- if ( articulatedFront || articulatedRear )
2923- {
2907+ // Add fake axle(s) to train car for articulation when desired
2908+ // Adding fake axles automatically is only allowed on non-engines with 0, 2, or 3 axles
2909+ // These limitations prevent various incompatibilities with existing content
2910+ bool allowAutoArticulate = WagonType != WagonTypes . Engine && WheelAxles . Count != 1 && WheelAxles . Count <= 3 ;
2911+ articFront &= ! ( FrontArticulation == - 1 && ! allowAutoArticulate ) ;
2912+ articRear &= ! ( RearArticulation == - 1 && ! allowAutoArticulate ) ;
2913+
2914+ if ( articFront || articRear )
2915+ SetUpWheelsArticulation ( articFront , articRear ) ;
2916+
2917+ // Other calculations require axles to be sorted based on their Z-offset
2918+ WheelAxles . Sort ( WheelAxles [ 0 ] ) ;
2919+
2920+ // After all processing is complete, check if the car can have its position calculated
2921+ // using the position of the axles, which is indicated by the 'WheelAxlesLoaded' flag.
2922+ // The train car must have at least 2 position references. These references can be either
2923+ // an axle or a bogie, but each bogie itself needs 2 position references.
2924+ int [ ] posReferences = new int [ Parts . Count ] ;
2925+ // Count the number of axles associated with each part (including main object)
2926+ foreach ( WheelAxle w in WheelAxles )
2927+ posReferences [ w . BogieIndex ] ++ ;
2928+ // Add a position reference to the main object for each bogie itself with at least 2 position references
2929+ for ( int i = 1 ; i < Parts . Count ; i ++ )
2930+ if ( posReferences [ i ] >= 2 )
2931+ posReferences [ 0 ] ++ ;
2932+ // Car has a suitable arrangement of axles for position calculation if the main object has at least 2 position references
2933+ if ( posReferences [ 0 ] >= 2 )
29242934 WheelAxlesLoaded = true ;
2925- SetUpWheelsArticulation ( articulatedFront , articulatedRear ) ;
2926- }
29272935 } // end SetUpWheels()
29282936
29292937 protected void SetUpWheelsArticulation ( bool front , bool rear )
29302938 {
2931- // If there are no forward wheels , this car is articulated (joined
2939+ // If there are no forward axles , this car is articulated (joined
29322940 // to the car in front) at the front. Likewise for the rear.
2933- // Original process originally used caused too many issues.
2934- // The original process did include the below process of just using WheelAxles.Add
2935- // if the initial test did not work. Since the below process is working without issues the
2936- // original process was stripped down to what is below
2937- if ( front || rear )
2938- {
2939- if ( front )
2940- WheelAxles . Add ( new WheelAxle ( new Vector3 ( 0.0f , BogiePivotHeightM , - CarLengthM / 2.0f ) , 0 , 0 ) { Part = Parts [ 0 ] } ) ;
2941+ // This will cause the car to move incorrectly, so to produce the
2942+ // expected motion, a fake axle is added at the articulated end(s)
2943+ // of the car, attached to the car itself. This will drive the positioning
2944+ // of the car as expected.
2945+ if ( front )
2946+ WheelAxles . Add ( new WheelAxle ( new Vector3 ( 0.0f , BogiePivotHeightM , - CarLengthM / 2.0f ) , 0 , 0 , true ) { Part = Parts [ 0 ] } ) ;
29412947
2942- if ( rear )
2943- WheelAxles . Add ( new WheelAxle ( new Vector3 ( 0.0f , BogiePivotHeightM , CarLengthM / 2.0f ) , 0 , 0 ) { Part = Parts [ 0 ] } ) ;
2944-
2945- WheelAxles . Sort ( WheelAxles [ 0 ] ) ;
2946- }
2948+ if ( rear )
2949+ WheelAxles . Add ( new WheelAxle ( new Vector3 ( 0.0f , BogiePivotHeightM , CarLengthM / 2.0f ) , 0 , 0 , true ) { Part = Parts [ 0 ] } ) ;
29472950
29482951
29492952#if DEBUG_WHEELS
@@ -3718,14 +3721,16 @@ public void UpdateGravity(Matrix orientation)
37183721 public class WheelAxle : IComparer < WheelAxle >
37193722 {
37203723 public Vector3 OffsetM ; // Offset from the bogie center
3721- public int BogieIndex ;
3722- public int BogieMatrix ;
3723- public TrainCarPart Part ;
3724- public WheelAxle ( Vector3 offset , int bogie , int parentMatrix )
3724+ public int BogieIndex ; // Index in the Parts list of the bogie this is attached to
3725+ public int BogieMatrix ; // Index in the matrix hierarchy of the bogie this is attached to
3726+ public TrainCarPart Part ; // Reference to the object for the bogie this is attached to
3727+ public bool Fake ; // True for axles that aren't present in the 3D model
3728+ public WheelAxle ( Vector3 offset , int bogie , int parentMatrix , bool fake = false )
37253729 {
37263730 OffsetM = offset ;
37273731 BogieIndex = bogie ;
37283732 BogieMatrix = parentMatrix ;
3733+ Fake = fake ;
37293734 }
37303735 public int Compare ( WheelAxle a , WheelAxle b )
37313736 {
0 commit comments