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);
}
}
}
}
}