1717
1818using System ;
1919using System . IO ;
20+ using Microsoft . Xna . Framework ;
2021using ORTS . Common ;
2122
2223namespace Orts . Simulation . RollingStocks . SubSystems . PowerTransmissions
@@ -53,11 +54,7 @@ public enum AxleDriveType
5354 /// </summary>
5455 public class Axle
5556 {
56- /// <summary>
57- /// Integrator used for axle dynamic solving
58- /// </summary>
59- public Integrator AxleRevolutionsInt = new Integrator ( 0.0f , IntegratorMethods . RungeKutta4 ) ;
60- public int NumOfSubstepsPS { get { return AxleRevolutionsInt . NumOfSubstepsPS ; } }
57+ public int NumOfSubstepsPS { get ; set ; }
6158
6259 /// <summary>
6360 /// Brake force covered by BrakeForceN interface
@@ -297,38 +294,17 @@ public float AdhesionK
297294 /// </summary>
298295 public float Adhesion2 { set ; get ; }
299296 /// <summary>
300- /// Axle speed value, covered by AxleSpeedMpS interface, in metric meters per second
301- /// </summary>
302- float axleSpeedMpS ;
303- /// <summary>
304297 /// Axle speed value, in metric meters per second
305298 /// </summary>
306- public float AxleSpeedMpS
307- {
308- set // used in initialisation at speed > = 0
309- {
310- axleSpeedMpS = value ;
311- }
312- get
313- {
314- return axleSpeedMpS ;
315- }
316- }
317-
299+ public float AxleSpeedMpS { get ; set ; }
318300 /// <summary>
319- /// Axle force value, covered by AxleForceN interface, in Newtons
301+ /// Axle angular position in radians
320302 /// </summary>
321- float axleForceN ;
303+ public float AxlePositionRad { get ; private set ; }
322304 /// <summary>
323305 /// Read only axle force value, in Newtons
324306 /// </summary>
325- public float AxleForceN
326- {
327- get
328- {
329- return axleForceN ;
330- }
331- }
307+ public float AxleForceN { get ; private set ; }
332308
333309 /// <summary>
334310 /// Compensated Axle force value, this provided the motive force equivalent excluding brake force, in Newtons
@@ -402,7 +378,7 @@ public float SlipSpeedMpS
402378 {
403379 get
404380 {
405- return ( axleSpeedMpS - TrainSpeedMpS ) ;
381+ return ( AxleSpeedMpS - TrainSpeedMpS ) ;
406382 }
407383 }
408384
@@ -458,6 +434,9 @@ public float SlipDerivationPercentpS
458434 }
459435 }
460436
437+ float integratorError ;
438+ int waitBeforeSpeedingUp ;
439+
461440 /// <summary>
462441 /// Read/Write relative slip speed warning threshold value, in percent of maximal effective slip
463442 /// </summary>
@@ -478,22 +457,16 @@ public Axle()
478457 transmissionEfficiency = 0.99f ;
479458 SlipWarningTresholdPercent = 70.0f ;
480459 driveType = AxleDriveType . ForceDriven ;
481- AxleRevolutionsInt . IsLimited = true ;
482- AxleRevolutionsInt . MaxSubsteps = 50 ;
483460 Adhesion2 = 0.331455f ;
484461
485462 switch ( driveType )
486463 {
487464 case AxleDriveType . NotDriven :
488465 break ;
489466 case AxleDriveType . MotorDriven :
490- AxleRevolutionsInt . Max = 5000.0f ;
491- AxleRevolutionsInt . Min = - 5000.0f ;
492467 totalInertiaKgm2 = inertiaKgm2 + transmissionRatio * transmissionRatio * motor . InertiaKgm2 ;
493468 break ;
494469 case AxleDriveType . ForceDriven :
495- AxleRevolutionsInt . Max = 1000.0f ;
496- AxleRevolutionsInt . Min = - 1000.0f ;
497470 totalInertiaKgm2 = inertiaKgm2 ;
498471 break ;
499472 default :
@@ -516,7 +489,6 @@ public Axle(ElectricMotor electricMotor)
516489 motor . AxleConnected = this ;
517490 transmissionEfficiency = 0.99f ;
518491 driveType = AxleDriveType . MotorDriven ;
519- AxleRevolutionsInt . IsLimited = true ;
520492 Adhesion2 = 0.331455f ;
521493
522494 switch ( driveType )
@@ -525,13 +497,9 @@ public Axle(ElectricMotor electricMotor)
525497 totalInertiaKgm2 = inertiaKgm2 ;
526498 break ;
527499 case AxleDriveType . MotorDriven :
528- AxleRevolutionsInt . Max = 5000.0f ;
529- AxleRevolutionsInt . Min = - 5000.0f ;
530500 totalInertiaKgm2 = inertiaKgm2 + transmissionRatio * transmissionRatio * motor . InertiaKgm2 ;
531501 break ;
532502 case AxleDriveType . ForceDriven :
533- AxleRevolutionsInt . Max = 100.0f ;
534- AxleRevolutionsInt . Min = - 100.0f ;
535503 totalInertiaKgm2 = inertiaKgm2 ;
536504 break ;
537505 default :
@@ -548,7 +516,7 @@ public Axle(BinaryReader inf) : this()
548516 {
549517 previousSlipPercent = inf . ReadSingle ( ) ;
550518 previousSlipSpeedMpS = inf . ReadSingle ( ) ;
551- axleForceN = inf . ReadSingle ( ) ;
519+ AxleForceN = inf . ReadSingle ( ) ;
552520 adhesionK = inf . ReadSingle ( ) ;
553521 AdhesionConditions = inf . ReadSingle ( ) ;
554522 frictionN = inf . ReadSingle ( ) ;
@@ -563,17 +531,17 @@ public void Save(BinaryWriter outf)
563531 {
564532 outf . Write ( previousSlipPercent ) ;
565533 outf . Write ( previousSlipSpeedMpS ) ;
566- outf . Write ( axleForceN ) ;
534+ outf . Write ( AxleForceN ) ;
567535 outf . Write ( adhesionK ) ;
568536 outf . Write ( AdhesionConditions ) ;
569537 outf . Write ( frictionN ) ;
570538 outf . Write ( dampingNs ) ;
571539 }
572540
573- float avgAxleForce ;
574- int times ;
575-
576- public float GetSpeedVariation ( float axleSpeedMpS )
541+ /// <summary>
542+ /// Compute variation in axle dynamics. Calculates axle speed, axle angular position and rail force.
543+ /// </summary>
544+ public ( float , float , float ) GetAxleMotionVariation ( float axleSpeedMpS , float axlePositionRad )
577545 {
578546 float axleForceN = AxleWeightN * SlipCharacteristics ( axleSpeedMpS - TrainSpeedMpS , TrainSpeedMpS , AdhesionK , AdhesionConditions , Adhesion2 ) ;
579547
@@ -600,20 +568,49 @@ public float GetSpeedVariation(float axleSpeedMpS)
600568 frictionalForceN -= Math . Abs ( motiveAxleForceN ) ;
601569 }
602570 }
603- // Assuming Runge-Kutta 4 integration
604- // Average force computed weighting the four function calls for each iteration
605- int c = times % 6 ;
606- if ( c > 0 && c < 5 )
571+ return ( totalAxleForceN * axleDiameterM * axleDiameterM / 4 / totalInertiaKgm2 , axleSpeedMpS * 2 / axleDiameterM , axleForceN ) ;
572+ }
573+
574+ void Integrate ( float elapsedClockSeconds )
575+ {
576+ if ( elapsedClockSeconds <= 0 ) return ;
577+ float prevSpeedMpS = AxleSpeedMpS ;
578+
579+ if ( Math . Abs ( integratorError ) > Math . Max ( SlipSpeedMpS , 1 ) / 1000 )
607580 {
608- avgAxleForce += 2 * axleForceN ;
609- times += 2 ;
581+ ++ NumOfSubstepsPS ;
582+ waitBeforeSpeedingUp = 100 ;
610583 }
611584 else
612585 {
613- avgAxleForce += axleForceN ;
614- times ++ ;
586+ if ( -- waitBeforeSpeedingUp <= 0 ) //wait for a while before speeding up the integration
587+ {
588+ -- NumOfSubstepsPS ;
589+ waitBeforeSpeedingUp = 10 ; //not so fast ;)
590+ }
591+ }
592+
593+ NumOfSubstepsPS = Math . Max ( Math . Min ( NumOfSubstepsPS , 50 ) , 5 ) ;
594+ float dt = elapsedClockSeconds / NumOfSubstepsPS ;
595+ float hdt = dt / 2.0f ;
596+ float axleForceSumN = 0 ;
597+ for ( int i = 0 ; i < NumOfSubstepsPS ; i ++ )
598+ {
599+ var k1 = GetAxleMotionVariation ( AxleSpeedMpS , AxlePositionRad ) ;
600+ var k2 = GetAxleMotionVariation ( AxleSpeedMpS + k1 . Item1 * hdt , AxlePositionRad + k1 . Item2 * hdt ) ;
601+ var k3 = GetAxleMotionVariation ( AxleSpeedMpS + k2 . Item1 * hdt , AxlePositionRad + k2 . Item2 * hdt ) ;
602+ var k4 = GetAxleMotionVariation ( AxleSpeedMpS + k3 . Item1 * dt , AxlePositionRad + k3 . Item2 * dt ) ;
603+ AxleSpeedMpS += ( integratorError = ( k1 . Item1 + 2 * ( k2 . Item1 + k3 . Item1 ) + k4 . Item1 ) * dt / 6.0f ) ;
604+ AxlePositionRad += ( k1 . Item2 + 2 * ( k2 . Item2 + k3 . Item2 ) + k4 . Item2 ) * dt / 6.0f ;
605+ axleForceSumN += ( k1 . Item3 + 2 * ( k2 . Item3 + k3 . Item3 ) + k4 . Item3 ) ;
606+ }
607+ AxleForceN = axleForceSumN / ( NumOfSubstepsPS * 6 ) ;
608+ AxlePositionRad = MathHelper . WrapAngle ( AxlePositionRad ) ;
609+
610+ if ( ( prevSpeedMpS > 0 && AxleSpeedMpS <= 0 ) || ( prevSpeedMpS < 0 && AxleSpeedMpS >= 0 ) )
611+ {
612+ if ( Math . Max ( brakeRetardForceN , frictionN ) > Math . Abs ( driveForceN - AxleForceN ) ) Reset ( ) ;
615613 }
616- return totalAxleForceN * axleDiameterM * axleDiameterM / 4 / totalInertiaKgm2 ;
617614 }
618615
619616 /// <summary>
@@ -625,29 +622,19 @@ public float GetSpeedVariation(float axleSpeedMpS)
625622 /// <param name="timeSpan"></param>
626623 public virtual void Update ( float timeSpan )
627624 {
628- float prevSpeedMpS = axleSpeedMpS ;
629- times = 0 ;
630- avgAxleForce = 0 ;
631- axleSpeedMpS = AxleRevolutionsInt . Integrate ( timeSpan , GetSpeedVariation ) ; //GetSpeedVariation(axleSpeedMpS) * timeSpan;
632- if ( times > 0 ) axleForceN = avgAxleForce / times ;
633- // TODO: around zero wheel speed calculations become unstable
634- // Near-zero regime will probably need further corrections
635- if ( ( prevSpeedMpS > 0 && axleSpeedMpS <= 0 ) || ( prevSpeedMpS < 0 && axleSpeedMpS >= 0 ) )
636- {
637- if ( Math . Max ( brakeRetardForceN , frictionN ) > Math . Abs ( driveForceN - axleForceN ) ) Reset ( ) ;
638- }
625+ Integrate ( timeSpan ) ;
639626 // TODO: We should calculate brake force here
640627 // Adding and substracting the brake force is correct for normal operation,
641628 // but during wheelslip this will produce wrong results.
642629 // The Axle module subtracts brake force from the motive force for calculation purposes. However brake force is already taken into account in the braking module.
643630 // And thus there is a duplication of the braking effect in OR. To compensate for this, after the slip characteristics have been calculated, the output of the axle module
644631 // has the brake force "added" back in to give the appropriate motive force output for the locomotive. Braking force is handled separately.
645632 // Hence CompensatedAxleForce is the actual output force on the axle.
646- CompensatedAxleForceN = axleForceN + Math . Sign ( TrainSpeedMpS ) * brakeRetardForceN ;
633+ CompensatedAxleForceN = AxleForceN + Math . Sign ( TrainSpeedMpS ) * brakeRetardForceN ;
647634
648635 if ( driveType == AxleDriveType . MotorDriven )
649636 {
650- motor . RevolutionsRad = axleSpeedMpS * 2.0f * transmissionRatio / ( axleDiameterM ) ;
637+ motor . RevolutionsRad = AxleSpeedMpS * 2.0f * transmissionRatio / ( axleDiameterM ) ;
651638 motor . Update ( timeSpan ) ;
652639 }
653640 if ( timeSpan > 0.0f )
@@ -665,8 +652,7 @@ public virtual void Update(float timeSpan)
665652 /// </summary>
666653 public void Reset ( )
667654 {
668- axleSpeedMpS = 0 ;
669- AxleRevolutionsInt . Reset ( ) ;
655+ AxleSpeedMpS = 0 ;
670656 adhesionK = adhesionK_orig ;
671657 if ( motor != null )
672658 motor . Reset ( ) ;
@@ -679,10 +665,7 @@ public void Reset()
679665 /// <param name="initValue">Initial condition</param>
680666 public void Reset ( double resetTime , float initValue )
681667 {
682- axleSpeedMpS = initValue ;
683- AxleRevolutionsInt . InitialCondition = initValue ;
684- AxleRevolutionsInt . Reset ( ) ;
685- AxleRevolutionsInt . InitialCondition = 0 ;
668+ AxleSpeedMpS = initValue ;
686669 ResetTime = resetTime ;
687670 if ( motor != null )
688671 motor . Reset ( ) ;
0 commit comments