// update toppling, cant and spring internal static void UpdateTopplingCantAndSpring(Train Train, int CarIndex, double TimeElapsed) { if (TimeElapsed == 0.0 | TimeElapsed > 0.5) { return; } // get direction, up and side vectors double dx, dy, dz; double ux, uy, uz; double sx, sy, sz; { dx = Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.X - Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.X; dy = Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Y - Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Y; dz = Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Z - Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Z; double t = 1.0 / Math.Sqrt(dx * dx + dy * dy + dz * dz); dx *= t; dy *= t; dz *= t; t = 1.0 / Math.Sqrt(dx * dx + dz * dz); double ex = dx * t; double ez = dz * t; sx = ez; sy = 0.0; sz = -ex; World.Cross(dx, dy, dz, sx, sy, sz, out ux, out uy, out uz); } // cant and radius double c; { double ca = Train.Cars[CarIndex].FrontAxle.Follower.CurveCant; double cb = Train.Cars[CarIndex].RearAxle.Follower.CurveCant; c = Math.Tan(0.5 * (Math.Atan(ca) + Math.Atan(cb))); } double r, rs; if (Train.Cars[CarIndex].FrontAxle.Follower.CurveRadius != 0.0 & Train.Cars[CarIndex].RearAxle.Follower.CurveRadius != 0.0) { r = Math.Sqrt(Math.Abs(Train.Cars[CarIndex].FrontAxle.Follower.CurveRadius * Train.Cars[CarIndex].RearAxle.Follower.CurveRadius)); rs = (double)Math.Sign(Train.Cars[CarIndex].FrontAxle.Follower.CurveRadius + Train.Cars[CarIndex].RearAxle.Follower.CurveRadius); } else if (Train.Cars[CarIndex].FrontAxle.Follower.CurveRadius != 0.0) { r = Math.Abs(Train.Cars[CarIndex].FrontAxle.Follower.CurveRadius); rs = (double)Math.Sign(Train.Cars[CarIndex].FrontAxle.Follower.CurveRadius); } else if (Train.Cars[CarIndex].RearAxle.Follower.CurveRadius != 0.0) { r = Math.Abs(Train.Cars[CarIndex].RearAxle.Follower.CurveRadius); rs = (double)Math.Sign(Train.Cars[CarIndex].RearAxle.Follower.CurveRadius); } else { r = 0.0; rs = 0.0; } // roll due to shaking { double a0 = Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngle; double a1; if (Train.Cars[CarIndex].Specs.CurrentRollShakeDirection != 0.0) { const double c0 = 0.03; const double c1 = 0.15; a1 = c1 * Math.Atan(c0 * Train.Cars[CarIndex].Specs.CurrentRollShakeDirection); double d = 0.5 + Train.Cars[CarIndex].Specs.CurrentRollShakeDirection * Train.Cars[CarIndex].Specs.CurrentRollShakeDirection; if (Train.Cars[CarIndex].Specs.CurrentRollShakeDirection < 0.0) { Train.Cars[CarIndex].Specs.CurrentRollShakeDirection += d * TimeElapsed; if (Train.Cars[CarIndex].Specs.CurrentRollShakeDirection > 0.0) Train.Cars[CarIndex].Specs.CurrentRollShakeDirection = 0.0; } else { Train.Cars[CarIndex].Specs.CurrentRollShakeDirection -= d * TimeElapsed; if (Train.Cars[CarIndex].Specs.CurrentRollShakeDirection < 0.0) Train.Cars[CarIndex].Specs.CurrentRollShakeDirection = 0.0; } } else { a1 = 0.0; } double SpringAcceleration; if (!Train.Cars[CarIndex].Derailed) { SpringAcceleration = 15.0 * Math.Abs(a1 - a0); } else { SpringAcceleration = 1.5 * Math.Abs(a1 - a0); } double SpringDeceleration = 0.25 * SpringAcceleration; Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngularSpeed += (double)Math.Sign(a1 - a0) * SpringAcceleration * TimeElapsed; double x = (double)Math.Sign(Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngularSpeed) * SpringDeceleration * TimeElapsed; if (Math.Abs(x) < Math.Abs(Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngularSpeed)) { Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngularSpeed -= x; } else { Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngularSpeed = 0.0; } a0 += Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngularSpeed * TimeElapsed; Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngle = a0; } // roll due to cant (incorporates shaking) { double cantAngle = Math.Atan(c / Game.RouteRailGauge); Train.Cars[CarIndex].Specs.CurrentRollDueToCantAngle = cantAngle + Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngle; } // pitch due to acceleration { for (int i = 0; i < 3; i++) { double a, v, j; if (i == 0) { a = Train.Cars[CarIndex].Specs.CurrentAcceleration; v = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationFastValue; j = 1.8; } else if (i == 1) { a = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationFastValue; v = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationMediumValue; j = 1.2; } else { a = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationFastValue; v = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationSlowValue; j = 1.0; } double d = a - v; if (d < 0.0) { v -= j * TimeElapsed; if (v < a) v = a; } else { v += j * TimeElapsed; if (v > a) v = a; } if (i == 0) { Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationFastValue = v; } else if (i == 1) { Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationMediumValue = v; } else { Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationSlowValue = v; } } { double d = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationSlowValue - Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationFastValue; Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationTargetAngle = 0.03 * Math.Atan(d); } { double a = 3.0 * (double)Math.Sign(Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationTargetAngle - Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngle); Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngularSpeed += a * TimeElapsed; double s = Math.Abs(Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationTargetAngle - Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngle); if (Math.Abs(Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngularSpeed) > s) { Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngularSpeed = s * (double)Math.Sign(Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngularSpeed); } Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngle += Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngularSpeed * TimeElapsed; } } // derailment if (Interface.CurrentOptions.Derailments & !Train.Cars[CarIndex].Derailed) { double a = Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle + Train.Cars[CarIndex].Specs.CurrentRollDueToCantAngle; double sa = (double)Math.Sign(a); double tc = Train.Cars[CarIndex].Specs.CriticalTopplingAngle; if (a * sa > tc) { Train.Derail(CarIndex, TimeElapsed); } } // toppling roll if (Interface.CurrentOptions.Toppling | Train.Cars[CarIndex].Derailed) { double a = Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle; double ab = Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle + Train.Cars[CarIndex].Specs.CurrentRollDueToCantAngle; double h = Train.Cars[CarIndex].Specs.CenterOfGravityHeight; double s = Math.Abs(Train.Cars[CarIndex].Specs.CurrentSpeed); double rmax = 2.0 * h * s * s / (Game.RouteAccelerationDueToGravity * Game.RouteRailGauge); double ta; Train.Cars[CarIndex].Topples = false; if (Train.Cars[CarIndex].Derailed) { double sab = (double)Math.Sign(ab); ta = 0.5 * Math.PI * (sab == 0.0 ? Program.RandomNumberGenerator.NextDouble() < 0.5 ? -1.0 : 1.0 : sab); } else { if (r != 0.0) { if (r < rmax) { double s0 = Math.Sqrt(r * Game.RouteAccelerationDueToGravity * Game.RouteRailGauge / (2.0 * h)); const double fac = 0.25; // arbitrary coefficient ta = -fac * (s - s0) * rs; Train.Topple(CarIndex, TimeElapsed); } else { ta = 0.0; } } else { ta = 0.0; } } double td; if (Train.Cars[CarIndex].Derailed) { td = Math.Abs(ab); if (td < 0.1) td = 0.1; } else { td = 1.0; } if (a > ta) { double d = a - ta; if (td > d) td = d; a -= td * TimeElapsed; } else if (a < ta) { double d = ta - a; if (td > d) td = d; a += td * TimeElapsed; } Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle = a; } else { Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle = 0.0; } // apply position due to cant/toppling { double a = Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle + Train.Cars[CarIndex].Specs.CurrentRollDueToCantAngle; double x = Math.Sign(a) * 0.5 * Game.RouteRailGauge * (1.0 - Math.Cos(a)); double y = Math.Abs(0.5 * Game.RouteRailGauge * Math.Sin(a)); double cx = sx * x + ux * y; double cy = sy * x + uy * y; double cz = sz * x + uz * y; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.X += cx; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Y += cy; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Z += cz; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.X += cx; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Y += cy; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Z += cz; } // apply rolling { double a = -Train.Cars[CarIndex].Specs.CurrentRollDueToTopplingAngle - Train.Cars[CarIndex].Specs.CurrentRollDueToCantAngle; double cosa = Math.Cos(a); double sina = Math.Sin(a); World.Rotate(ref sx, ref sy, ref sz, dx, dy, dz, cosa, sina); World.Rotate(ref ux, ref uy, ref uz, dx, dy, dz, cosa, sina); Train.Cars[CarIndex].Up.X = ux; Train.Cars[CarIndex].Up.Y = uy; Train.Cars[CarIndex].Up.Z = uz; } // apply pitching if (Train.Cars[CarIndex].CurrentCarSection >= 0 && Train.Cars[CarIndex].CarSections[Train.Cars[CarIndex].CurrentCarSection].Overlay) { double a = Train.Cars[CarIndex].Specs.CurrentPitchDueToAccelerationAngle; double cosa = Math.Cos(a); double sina = Math.Sin(a); World.Rotate(ref dx, ref dy, ref dz, sx, sy, sz, cosa, sina); World.Rotate(ref ux, ref uy, ref uz, sx, sy, sz, cosa, sina); double cx = 0.5 * (Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.X + Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.X); double cy = 0.5 * (Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Y + Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Y); double cz = 0.5 * (Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Z + Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Z); Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.X -= cx; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Y -= cy; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Z -= cz; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.X -= cx; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Y -= cy; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Z -= cz; World.Rotate(ref Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition, sx, sy, sz, cosa, sina); World.Rotate(ref Train.Cars[CarIndex].RearAxle.Follower.WorldPosition, sx, sy, sz, cosa, sina); Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.X += cx; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Y += cy; Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition.Z += cz; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.X += cx; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Y += cy; Train.Cars[CarIndex].RearAxle.Follower.WorldPosition.Z += cz; Train.Cars[CarIndex].Up.X = ux; Train.Cars[CarIndex].Up.Y = uy; Train.Cars[CarIndex].Up.Z = uz; } // spring sound { double a = Train.Cars[CarIndex].Specs.CurrentRollDueToShakingAngle; double diff = a - Train.Cars[CarIndex].Sounds.SpringPlayedAngle; const double angleTolerance = 0.001; if (diff < -angleTolerance) { Sounds.SoundBuffer buffer = Train.Cars[CarIndex].Sounds.SpringL.Buffer; if (buffer != null) { if (!Sounds.IsPlaying(Train.Cars[CarIndex].Sounds.SpringL.Source)) { OpenBveApi.Math.Vector3 pos = Train.Cars[CarIndex].Sounds.SpringL.Position; Train.Cars[CarIndex].Sounds.SpringL.Source = Sounds.PlaySound(buffer, 1.0, 1.0, pos, Train, CarIndex, false); } } Train.Cars[CarIndex].Sounds.SpringPlayedAngle = a; } else if (diff > angleTolerance) { Sounds.SoundBuffer buffer = Train.Cars[CarIndex].Sounds.SpringR.Buffer; if (buffer != null) { if (!Sounds.IsPlaying(Train.Cars[CarIndex].Sounds.SpringR.Source)) { OpenBveApi.Math.Vector3 pos = Train.Cars[CarIndex].Sounds.SpringR.Position; Train.Cars[CarIndex].Sounds.SpringR.Source = Sounds.PlaySound(buffer, 1.0, 1.0, pos, Train, CarIndex, false); } } Train.Cars[CarIndex].Sounds.SpringPlayedAngle = a; } } // flange sound { /* * This determines the amount of flange noise as a result of the angle at which the * line that forms between the axles hits the rail, i.e. the less perpendicular that * line is to the rails, the more flange noise there will be. * */ Vector3 d = Train.Cars[CarIndex].FrontAxle.Follower.WorldPosition - Train.Cars[CarIndex].RearAxle.Follower.WorldPosition; World.Normalize(ref d.X, ref d.Y, ref d.Z); double b0 = d.X * Train.Cars[CarIndex].RearAxle.Follower.WorldSide.X + d.Y * Train.Cars[CarIndex].RearAxle.Follower.WorldSide.Y + d.Z * Train.Cars[CarIndex].RearAxle.Follower.WorldSide.Z; double b1 = d.X * Train.Cars[CarIndex].FrontAxle.Follower.WorldSide.X + d.Y * Train.Cars[CarIndex].FrontAxle.Follower.WorldSide.Y + d.Z * Train.Cars[CarIndex].FrontAxle.Follower.WorldSide.Z; double spd = Math.Abs(Train.Cars[CarIndex].Specs.CurrentSpeed); double pitch = 0.5 + 0.04 * spd; double b2 = Math.Abs(b0) + Math.Abs(b1); double basegain = 0.5 * b2 * b2 * spd * spd; /* * This determines additional flange noise as a result of the roll angle of the car * compared to the roll angle of the rails, i.e. if the car bounces due to inaccuracies, * there will be additional flange noise. * */ double cdti = Math.Abs(Train.Cars[CarIndex].FrontAxle.Follower.CantDueToInaccuracy) + Math.Abs(Train.Cars[CarIndex].RearAxle.Follower.CantDueToInaccuracy); basegain += 0.2 * spd * spd * cdti * cdti; /* * This applies the settings. * */ if (basegain < 0.0) basegain = 0.0; if (basegain > 0.75) basegain = 0.75; if (pitch > Train.Cars[CarIndex].Sounds.FlangePitch) { Train.Cars[CarIndex].Sounds.FlangePitch += TimeElapsed; if (Train.Cars[CarIndex].Sounds.FlangePitch > pitch) Train.Cars[CarIndex].Sounds.FlangePitch = pitch; } else { Train.Cars[CarIndex].Sounds.FlangePitch -= TimeElapsed; if (Train.Cars[CarIndex].Sounds.FlangePitch < pitch) Train.Cars[CarIndex].Sounds.FlangePitch = pitch; } pitch = Train.Cars[CarIndex].Sounds.FlangePitch; for (int i = 0; i < Train.Cars[CarIndex].Sounds.Flange.Length; i++) { if (i == Train.Cars[CarIndex].Sounds.FrontAxleFlangeIndex | i == Train.Cars[CarIndex].Sounds.RearAxleFlangeIndex) { Train.Cars[CarIndex].Sounds.FlangeVolume[i] += TimeElapsed; if (Train.Cars[CarIndex].Sounds.FlangeVolume[i] > 1.0) Train.Cars[CarIndex].Sounds.FlangeVolume[i] = 1.0; } else { Train.Cars[CarIndex].Sounds.FlangeVolume[i] -= TimeElapsed; if (Train.Cars[CarIndex].Sounds.FlangeVolume[i] < 0.0) Train.Cars[CarIndex].Sounds.FlangeVolume[i] = 0.0; } double gain = basegain * Train.Cars[CarIndex].Sounds.FlangeVolume[i]; if (Sounds.IsPlaying(Train.Cars[CarIndex].Sounds.Flange[i].Source)) { if (pitch > 0.01 & gain > 0.0001) { Train.Cars[CarIndex].Sounds.Flange[i].Source.Pitch = pitch; Train.Cars[CarIndex].Sounds.Flange[i].Source.Volume = gain; } else { Train.Cars[CarIndex].Sounds.Flange[i].Source.Stop(); } } else if (pitch > 0.02 & gain > 0.01) { Sounds.SoundBuffer buffer = Train.Cars[CarIndex].Sounds.Flange[i].Buffer; if (buffer != null) { OpenBveApi.Math.Vector3 pos = Train.Cars[CarIndex].Sounds.Flange[i].Position; Train.Cars[CarIndex].Sounds.Flange[i].Source = Sounds.PlaySound(buffer, pitch, gain, pos, Train, CarIndex, true); } } } } }