// Simulation des Bremsvorganges eines Verkehrsflugzeugs
// http://walter.bislins.ch/
//
// Dependencies:
// - engine.js
function CPlane() {
// Airbus A320-200
// simulation params
this.BrakingDeltaTime = 0.02; // [s]
this.OszillationDeltaTime = 0.1; // [s]
this.StopSimulationOnHalt = false;
// environment
this.Altitude = 500.0; // [m] (over mean see level)
this.QNH = 101325; // [Pa] = [N/m^2] = [kg/m/s^2]
this.Temperature = 15; // [C]
this.WindSpeed = 0; // [m/s] (+ Headwind/ - Tailwind)
this.MPD = 1.75; // [mm] Meand Profile Depth of Runway
this.F60 = 0.4; // friction at 60 km/h
this.CR0 = 3.7699e-3; // [N^-3] Roll coefficient const
this.CR1 = 4.60824e-5; // [m^-1 N^-3] Roll coefficient const
this.Slope = 0.0; // [rad]
// start conditions
this.LandingWeight = 61200.0; // [kg]
this.CG = 25; // [% MAC]
this.AoA = -0.034906; // [rad] (-2 deg)
this.Vapp = 70.47; // [m/s] (132 + 5 = 137 kt)
this.VFlareReduction = 5.144; // [m/s] (1-7% + 2-4% Vapp)
this.SpoilerExtendDelay = 0.5;
this.SpoilerTransTime = 3.0; // [s]
this.SpoilerExtend = 1.0; // 0..1
this.TrimTransTime = 5.0; // [s], Trim Neutral is triggered by SpoilerExtend
this.FlapsExtend = 1.0; // 0..1
this.Reverse = 1.0; // N1 rate (0..1) = (idle..maxrev), < 0 = no reverse
this.ReverseDelay = 3.5;
this.ReverseSpeedLimit = 30.864; // [m/s] (60kt)
this.ReverseIdleSpeed = 15.432; // [m/s] (30kt)
this.ReverseTransTime = 1.5; // [s]
this.SpoolTime = 3.0; // [s]
this.Thrust = 0.0; // N1 rate (0..1) = (idle..1), < 0 = engines off
this.MaxBrakeForce = 360000.0; // N (entspricht ca. 6m/s^2 Verzögerung = max Autobrake)
this.ManualBraking = 0.0; // 0..1 as % of MaxBrakeForce
this.ManBrakeSpeed = 10.288; // [m/s] apply manual braking below this speed (20 kt)
this.ManBrakeTransTime = 1.0; // [s]
this.AutoBrake = 0.57; // 0.57 = LOW = 1.7 m/s^2, 1 = MED = 3 m/s^2
this.AutoBrakeDelay = 0.0; // [s]: 0 = Auto: (< 0.6) -> 4s; (>= 0.6) -> 2s after SpoilerExtendDelay
this.AutoBrakeTransTime = 3.0; // [s]
this.ParkingBrakeSet = false;
this.NEngines = 2;
this.engine = new CTurbofan()
// simulated values
this.Cr = 0.0; // Roll coefficient f(Speed,FMz)
this.Mue = 0.0; // Friction f(F60,MPD,speed)
this.Sp = 0.0; // [m/s] Speed Constant
this.CL = 0.0; // Lift coefficient
this.CD = 0.0; // Drag coefficient
this.CT = 0.0; // [N s^2 / m^2]: Trim coefficient
this.Rho = 0.0;
this.ForceAcceleration = 0.0; // [N]
this.ForceWeight = 0.0; // [N]
this.ForceSlope = 0.0; // [N]
this.ForceLift = 0.0; // [N]
this.ForceTrim = 0.0; // [N]
this.ForceDrag = 0.0; // [N]
this.ForceMainGear = 0.0; // [N]
this.ForceNoseGear = 0.0; // [N]
this.ForceGear = 0.0; // [N]
this.ForceMainBrake = 0.0; // [N]
this.ForceNoseBrake = 0.0; // [N]
this.ForceBrake = 0.0; // [N] Note: ForceBrake includes ForceRoll!!!
this.ForceRoll = 0.0; // [N]
this.ForceEngines = 0.0; // [N]
this.Acceleration = 0.0; // [m/s^2]
this.Speed = 0.0; // [m/s] Ground Speed
this.IsRolling = true;
this.HasStopped = true;
this.Distance = 0.0; // [m]
this.Time = 0.0; // [s]
this.V0Time = 0.0; // [s] time of last speed < Vmin
this.DeltaDistance = 0.0; // [m] used to compute energies (Force times distance)
this.EnergyBrake = 0.0; // [Nm=J] total energy applied by brakes
this.EnergyRoll = 0.0; // [Nm=J] total energy part of roll resitance
this.EnergyDrag = 0.0; // [Nm=J] total energy part of drag
this.EnergyEngines = 0.0; // [Nm=J] total energy applied by engintes (reversers)
this.TotalEnergy = 0.0; // [Nm=J]
// aircraft specific constants
this.Pwheel = 1448000; // [N/m^2] tyre inflation pressure = 210 psi
this.CLa = 5.16; // Lift coefficients
this.CL0 = 0.2;
this.CLflaps = 1.12;
this.CLspoiler = -0.36; //
// this.CD = 0.2293; // [http://brahimtahiri.bravehost.com/03_ContaminatedRunway.pdf]
this.CD0 = 0.02; // sa_knebel.pdf P. 24
this.CD2 = 0.04;
this.CDflaps = 0.08; // sa_knebel.pdf P. 24
this.CDspoiler = 0.1293; // http://brahimtahiri.bravehost.com/03_ContaminatedRunway.pdf
this.CM = -0.05; // Momentenbeiwert (pitching moment coefficient)
this.WingArea = 122.6; // [m^2]
this.WingSpan = 34.10; // [m] (Wikipedia); not used in any computation
this.MAC = 4.1935; // [m] (mean aerodynamic chord)
this.PCGx = 16.31; // [m] default, will be computed from CG
this.PCGz = 2.6; // [m] estimated Pos of CG from ground
this.PFx = 5.07; // [m] Pos of nose gear
this.PMx = 17.71; // [m] Pos of main gear
this.PLx = 16.31; // [m] Pos of aerodynamic center
this.PLz = 2.6; // [m] Pos of aerodynamic center from ground (estimated)
this.PTx = 33.898; // [m] Pos of ac of trimm
this.PJz = 1.7; // [m] Pos of Engine from ground
this.AutoBrakeLimit = 3.0; // [m/s^2] LOW = 1, MED = 2, MAX = 3
this.XL = 0.0; // [m] CenterGravity to CenterLift
this.ZL = 0.0; // [m]
this.XT = 0.0; // [m] CenterGravity to CenterTrimm
this.ZJ = 0.0; // [m] CenterGravity to CenterEngine
this.ZM = 0.0; // [m] CenterGravity to Ground
this.XM = 0.0; // [m] CenterGravity to MainGear
this.XF = 0.0; // [m] CenterGravity to NoseGear
this.Kf = 200000.0; // [N/m] Federkonstante
this.Kd = 200000.0; // [Ns/m] Dämpfung
// general constants used
this.GravityConstant = 9.80665;// [m/s^2]
this.GasMass = 0.0289644; // [kg/mol]
this.GasConstant = 8.31446; // [J/K/mol]
this.NormTemp = 288.15; // [K]
this.LapseRate = 0.0065; // [K/m]
// internal simulation params
this.DeltaTime = 0.1; // [s]
this.OldSpeed = 0.0;
this.GroundContactDistance = 0.0; // [m]
this.ReverseState = 0; // 0: before reverse; 1: Thrust to Idle; 2: Idle to ReverseIdle; 3: IdleReverse to Reverse; 4: Reverse; 5: Reverse to IdleReverse; 6: IdleReverse; 7: IdleReverse to Idle; 8: Idle
this.ReverseStateTime = 0.0;
this.ReverseInterrupted = 0.0; // 0..1
this.BrakeDelay = 0.0; // [s]
this.AutoBrakeReleaseTime = 0.0;
this.AutoBrakeReleaseKm = 0.0;
this.OldAcceleration = 0.0;
}
CPlane.prototype.StartSimulation = function() {
this.ParkingBrakeSet = false;
this.DeltaTime = this.BrakingDeltaTime;
this.Time = 0.0;
this.Speed = this.Vapp - this.VFlareReduction - this.WindSpeed; // [m/s]
this.OldSpeed = this.Speed;
this.Distance = 0.0;
this.GroundContactDistance = 0.0;
this.IsRolling = true;
this.HasStopped = false;
// 0: before reverse; 1: Thrust to Idle; 2: Idle to ReverseIdle; 3: IdleReverse to Reverse; 4: Reverse; 5: Reverse to IdleReverse; 6: IdleReverse; 7: IdleReverse to Idle; 8: Idle
this.ReverseState = 0;
this.ReverseStateTime = 0;
if (this.Time >= this.ReverseDelay) {
this.ReverseState = 1;
}
if (this.Speed <= this.ReverseSpeedLimit) {
this.ReverseState = 1;
}
if (this.Speed <= this.ReverseIdleSpeed) {
this.ReverseState = 1;
}
this.AutoBrakeReleaseTime = 0.0;
// compute initial values with start conditions
this.ComputeStaticValues();
this.ComputeValues();
this.Acceleration = this.ForceAcceleration / this.LandingWeight;
this.OldAcceleration = this.Acceleration;
this.DeltaDistance = 0.0;
this.EnergyBrake = 0.0;
this.EnergyRoll = 0.0;
this.EnergyDrag = 0.0;
this.EnergyEngines = 0.0;
this.TotalEnergy = 0.0;
}
CPlane.prototype.StepSimulation = function() {
var da, dv, ds;
if (this.HasStopped) { return; }
// compute change in acceleration and speed
da = this.Acceleration - this.OldAcceleration;
dv = (this.Acceleration + 0.5 * da) * this.DeltaTime;
if (this.Speed + dv <= 0.01 && this.StopSimulationOnHalt) {
// do not simulate damped oscillation
this.Acceleration = 0;
this.Speed = 0;
dv = 0;
da = 0;
this.HasStopped = true;
return;
}
if (this.Speed + dv <= 0.01) {
// init damped oscillation
var dtOld = this.DeltaTime;
this.DeltaTime = this.OszillationDeltaTime;
da = da / dtOld * this.DeltaTime;
dv = (this.Acceleration + 0.5 * da) * this.DeltaTime;
}
// compute change in distance
ds = (this.Speed + 0.5 * dv) * this.DeltaTime;
// compute new current values
this.Time += this.DeltaTime;
this.OldSpeed = this.Speed;
this.Speed += dv;
this.Distance += ds;
this.DeltaDistance = ds;
this.ComputeValues();
// do not track after liftoff
if (this.ForceMainGear <= 0.0) this.Distance -= ds;
// compute new acceleration from all forces
this.OldAcceleration = this.Acceleration;
this.Acceleration = this.ForceAcceleration / this.LandingWeight;
// check stop (stop is when speed is 2 sec below Vmin)
if (Math.abs(this.Speed) < 0.1) {
if ((this.Time - this.V0Time > 2) || this.StopSimulationOnHalt) this.HasStopped = true;
} else {
this.V0Time = this.Time;
}
}
CPlane.prototype.ComputeStaticValues = function() {
// environment
var temp0 = (this.Temperature+273) + this.LapseRate * this.Altitude;
var rho0 = this.QNH * this.GasMass / (this.GasConstant * temp0);
var base = 1.0 - (this.LapseRate * this.Altitude / temp0);
var expo = (this.GasMass * this.GravityConstant / this.GasConstant / this.LapseRate) - 1.0;
this.Rho = rho0 * Math.pow(base, expo);
// engine
this.engine.Init();
// geometry
this.PCGx = this.PLx + (this.CG-25)*this.MAC/100.0;
this.XL = this.PLx - this.PCGx;
this.ZL = this.PCGz - this.PLz;
this.XT = this.PTx - this.PCGx;
this.ZJ = this.PCGz - this.PJz;
this.ZM = this.PCGz;
this.XM = this.PMx - this.PCGx;
this.XF = this.PCGx - this.PFx;
// some static forces
this.ForceWeight = this.LandingWeight * this.GravityConstant * Math.cos(this.Slope);
this.ForceSlope = this.LandingWeight * this.GravityConstant * Math.sin(this.Slope);
// compute Trim coefficient CT
var ft = this.ForceWeight * this.XL / (this.XL - this.XT);
var vapp = (this.Vapp < 60) ? 60 : this.Vapp;
this.CT = ft / (vapp * vapp);
// Speed Constant Sp used in computation of Mue
this.Sp = (-4.64 + 102.07 * this.MPD) / 3.6; // (/ 3.6) => km/h -> [m/s]
// set BrakeDelay depending on AutoBrakeDelay and Spoiler extension
if (this.AutoBrakeDelay == 0.0 && this.AutoBrake <= 1.0) {
if (this.AutoBrake < 0.6) {
this.BrakeDelay = this.SpoilerExtendDelay + 4;
} else {
this.BrakeDelay = this.SpoilerExtendDelay + 2;
}
} else {
this.BrakeDelay = this.AutoBrakeDelay;
}
}
CPlane.prototype.N1 = function( aThrust ) {
return aThrust * (1.0 - this.engine.N1_idle) + this.engine.N1_idle;
}
CPlane.prototype.N1rev = function( aThrust ) {
return aThrust * (this.engine.N1_maxrev - this.engine.N1_idle) + this.engine.N1_idle;
}
CPlane.prototype.ComputeValues = function() {
var fmx, fmz, m, kf, km, ml;
// compute TAS v and dynamic pressure q
var v = this.Speed + this.WindSpeed;
var q = 0.5 * this.Rho * v*v;
var spoiler = this.SpoilerExtend * RampCosUp( this.Time, this.SpoilerExtendDelay, this.SpoilerTransTime );
var clNoSpoiler = this.CLa * this.AoA + this.CL0 + this.CLflaps * this.FlapsExtend;
if (v >= 0.0) {
// wind blows from front
// compute lift coefficient CL as f(AoA,flaps,spoiler)
this.CL = clNoSpoiler + this.CLspoiler * spoiler;
// compute drag coefficient CD as f(flaps,spoiler)
this.CD = this.CD2 * clNoSpoiler*clNoSpoiler + this.CD0 + this.FlapsExtend * this.CDflaps + spoiler * this.CDspoiler;
// compute lift, trim and drag forces and pitching moment ml as f(q,CL,CD)
this.ForceLift = q * this.CL * this.WingArea;
this.ForceDrag = q * this.CD * this.WingArea;
this.ForceTrim = this.CT * v*v * RampDown( this.Time, this.SpoilerExtendDelay, this.TrimTransTime );
ml = q * this.CM * this.MAC * this.WingArea;
} else {
// wind blows from behind
// compute lift coefficient CL as f(AoA,flaps,spoiler); (spoiler has no effect on lift)
this.CL = -clNoSpoiler;
// compute drag coefficient CD as f(flaps,spoiler)
this.CD = -( this.CD2 * clNoSpoiler*clNoSpoiler + this.CD0 + this.FlapsExtend * this.CDflaps + spoiler * this.CDspoiler );
// compute lift, trim and drag forces and pitching moment ml as f(q,CL,CD)
this.ForceLift = q * this.CL * this.WingArea;
this.ForceDrag = q * this.CD * this.WingArea;
this.ForceTrim = -this.CT * v*v * RampDown( this.Time, this.SpoilerExtendDelay, this.TrimTransTime );
ml = -( q * this.CM * this.MAC * this.WingArea );
}
// compute engine thrust depenging on various reverse states
if (this.Thrust < -0.005) { // engines off
this.ForceEngines = 0.0;
} else {
if (this.ReverseState == 0) {
// before reverse
if (this.Time < this.ReverseDelay) {
// const thrust
this.engine.Compute( this.N1( this.Thrust ), v );
this.ForceEngines = this.NEngines * this.engine.F;
} else {
// init spool down to idle
this.ReverseStateTime = this.ReverseDelay;
this.ReverseState = 1;
}
}
if (this.ReverseState == 1) {
// Thrust to Idle
var tt = this.Thrust * this.SpoolTime;
if (this.Time < this.ReverseStateTime + tt) {
// spooling down
var f = RampQDown( this.Time, this.ReverseStateTime, tt );
this.engine.Compute( this.N1( f * this.Thrust ), v );
this.ForceEngines = this.NEngines * this.engine.F;
} else {
// init transition to reverse or go to state Idle
if (this.Reverse < -0.005) {
// no reverser used -> state 8
this.ReverseState = 8;
} else {
// init transition to reverse
this.ReverseStateTime += tt;
this.ReverseState = 2;
}
}
}
if (this.ReverseState == 2) {
// Idle to ReverseIdle
if (this.Time < this.ReverseStateTime + this.ReverseTransTime) {
// compute transition thrust
this.engine.Compute( this.N1(0), v );
var f = RampCosDown( this.Time, this.ReverseStateTime, this.ReverseTransTime );
this.ForceEngines = this.NEngines * (f * this.engine.F + (1-f) * this.engine.F_rev);
} else {
// reverse idle reached -> hold it or apply reverse thrust or back to idle
if (this.Speed <= this.ReverseIdleSpeed) {
// idle speed limit exceedet -> back to idle
this.ReverseStateTime = this.Time;
this.ReverseState = 7;
} else {
if (this.Reverse == 0) {
// hold reverse idle
this.ReverseState = 6;
} else {
// init transition to reverse thrust
this.ReverseStateTime += this.ReverseTransTime;
this.ReverseState = 3;
}
}
}
}
if (this.ReverseState == 3) {
// ReverseIdle to reverse thrust
var tt = this.Reverse * this.engine.N1_maxrev * this.SpoolTime;
if (this.Time < this.ReverseStateTime + tt) {
// spooling up to reverse thrust
var f = RampQUp( this.Time, this.ReverseStateTime, tt );
if (this.Speed <= this.ReverseSpeedLimit) {
// reverse speed limit exceedet -> back to reverse idle
this.ReverseInterrupted = f * this.Reverse;
this.ReverseStateTime = this.Time;
this.ReverseState = 5;
} else {
// compute current reverse thrust on spooling up
this.engine.Compute( this.N1rev(f * this.Reverse), v );
this.ForceEngines = this.NEngines * this.engine.F_rev;
}
} else {
// reverse thrust reached -> hold it
this.ReverseState = 4;
}
}
if (this.ReverseState == 4) {
// Reverse thrust set; monitor speed
if (this.Speed <= this.ReverseSpeedLimit) {
// reverse speed limit exceedet -> spool down to reverse idle
this.ReverseStateTime = this.Time;
this.ReverseState = 5;
} else {
// computing reverse force
this.engine.Compute( this.N1rev(this.Reverse), v );
this.ForceEngines = this.NEngines * this.engine.F_rev;
}
}
if (this.ReverseState == 5) {
// spooling down from reverse thrust to reverse idle
var thr = this.Reverse;
if (this.ReverseInterrupted > 0.0) thr = this.ReverseInterrupted;
var tt = thr * this.SpoolTime * this.engine.N1_maxrev;
if (this.Time < this.ReverseStateTime + tt) {
// spooling down to reverse idle
var f = RampQDown( this.Time, this.ReverseStateTime, tt );
this.engine.Compute( this.N1rev(f * thr), v );
this.ForceEngines = this.NEngines * this.engine.F_rev;
} else {
// reverse idle reached -> hold it
this.ReverseState = 6;
this.ReverseInterrupted = 0.0;
}
}
if (this.ReverseState == 6) {
// back to reverse idle set; monitor speed
if (this.Speed <= this.ReverseIdleSpeed) {
// reverse idle speed limit exceedet -> transition to idle thrust
this.ReverseStateTime = this.Time;
this.ReverseState = 7;
} else {
// hold reverse idle
this.engine.Compute( this.N1rev(0), v );
this.ForceEngines = this.NEngines * this.engine.F_rev;
}
}
if (this.ReverseState == 7) {
// transition from reverse idle to idle
if (this.Time < this.ReverseStateTime + this.ReverseTransTime) {
// compute transition thrust
this.engine.Compute( this.N1(0), v );
var f = RampCosUp( this.Time, this.ReverseStateTime, this.ReverseTransTime );
this.ForceEngines = this.NEngines * (f * this.engine.F + (1-f) * this.engine.F_rev);
} else {
// idle thrust reached -> hold it
this.ReverseState = 8;
}
}
if (this.ReverseState == 8) {
// hold idle thrust
this.engine.Compute( this.N1(0), v );
this.ForceEngines = this.NEngines * this.engine.F;
}
}
// compute all forces and total moment except gear and brake forces
fmx = this.ForceDrag + this.ForceSlope - this.ForceEngines;
fmz = this.ForceWeight - this.ForceLift - this.ForceTrim;
m = this.ForceLift * this.XL + this.ForceTrim * this.XT + this.ForceDrag * this.ZL - this.ForceEngines * this.ZJ - ml;
// compute mue
if (this.IsRolling) {
this.Mue = this.F60 * Math.exp( (16.667 - Math.abs(0.13*this.Speed)) / this.Sp );
} else {
this.Mue = this.F60 * Math.exp( 16.667 / this.Sp );
}
// compute Cr, assert Cr <= Mue
this.Cr = (this.CR0 + this.CR1 * this.Speed * this.Speed / 2 / this.GravityConstant) * Math.pow( fmz, 1/3 ) * this.QNH / this.Pwheel;
// if verry slippery, Cr = Mue
if (this.Cr > this.Mue) this.Cr = this.Mue;
// compute gear and brake forces
if (fmz < 0) {
// lift off
this.Mue = 0.0;
this.ForceMainGear = 0.0;
this.ForceNoseGear = 0.0;
this.ForceGear = 0.0;
this.ForceMainBrake = 0.0;
this.ForceNoseBrake = 0.0;
this.ForceBrake = 0.0;
this.ForceRoll = 0.0;
} else {
// init brake coefficients to minimum; Note: Cr <= Mue
kf = this.Cr;
km = this.Cr;
if (this.Speed < 0) {
this.ParkingBrakeSet = true;
}
// compute brake coefficients depending on braking action and environment conditions
if (this.ParkingBrakeSet) {
km = this.Mue;
} else {
if (this.AutoBrakeReleaseTime == 0.0) {
// auto braking; compute km
if (this.AutoBrake > 0.0) {
var aReq = this.AutoBrake * this.AutoBrakeLimit;
var fb = this.LandingWeight * aReq - fmx;
if (fb > 0) {
var kmReq = ( fb * ( kf * this.ZM - this.XF - this.XM ) + kf * ( m + fmz * this.XM ) ) / ( fb * this.ZM - fmz * this.XF + m );
if (kmReq > km) {
var b = RampCosUp( this.Time, this.BrakeDelay, this.AutoBrakeTransTime*this.AutoBrake );
km = b * (kmReq-km) + km;
if (km > this.Mue) { km = this.Mue; }
}
}
}
// if MaxBrakeForce is exceedet, apply MaxBrakeForce
var fbrake = km * (fmz * (this.XF - kf * this.ZM) - m) / ((this.XM + this.XF) + this.ZM * (km - kf));
if (fbrake > this.MaxBrakeForce) {
var kmMax = this.Cr;
var fb = this.MaxBrakeForce + this.Cr * fmz;
var kmReq = ( fb * ( kf * this.ZM - this.XF - this.XM ) + kf * ( m + fmz * this.XM ) ) / ( fb * this.ZM - fmz * this.XF + m );
if (kmReq > kmMax) {
kmMax = kmReq;
if (kmMax < km) { km = kmMax; }
if (km > this.Mue) { km = this.Mue; }
}
}
if (this.ManualBraking > 0.0 && this.Speed <= this.ManBrakeSpeed) {
// trigger manual braking
this.AutoBrakeReleaseTime = this.Time;
this.AutoBrakeReleaseKm = km;
}
}
if (this.AutoBrakeReleaseTime > 0.0) {
// manual braking
var fb = this.ManualBraking * this.MaxBrakeForce + this.Cr * fmz;
var kmReq = ( fb * ( kf * this.ZM - this.XF - this.XM ) + kf * ( m + fmz * this.XM ) ) / ( fb * this.ZM - fmz * this.XF + m );
var kmMin = km;
if (kmReq > km) {
var b = RampCosUp( this.Time, this.AutoBrakeReleaseTime, this.ManBrakeTransTime*this.ManualBraking+1 );
km = b * (kmReq-km) + km;
if (km > this.Mue) { km = this.Mue; }
}
var ba = RampQDown( this.Time, this.AutoBrakeReleaseTime, this.AutoBrakeTransTime );
var kmAuto = ba * (this.AutoBrakeReleaseKm-kmMin) + kmMin;
if (kmAuto > km) {
km = kmAuto;
if (km > this.Mue) { km = this.Mue; }
}
}
}
// check sticky or rolling
if (this.IsRolling) {
// becomes sticky, if speed changes direction
if (this.OldSpeed*this.Speed < 0) {
this.IsRolling = false;
this.GroundContactDistance = this.Distance - (this.OldSpeed/Math.abs(this.OldSpeed)) * this.ForceMainBrake / this.Kf;
}
} else {
var fb = this.Kf * (this.Distance - this.GroundContactDistance) + this.Kd * this.Speed;
var fgm = (fmz * this.XF - fb * this.ZM - m) / (this.XM + this.XF);
var fgf = fmz - fgm;
var fbMax = km * fgm + kf * fgf;
// becomes rolling, |fb| > fbMax
if (Math.abs(fb) > fbMax) this.IsRolling = true;
}
// compute gear forces
if (this.IsRolling) {
this.ForceMainGear = (fmz * (this.XF - kf * this.ZM) - m) / ((this.XM + this.XF) + this.ZM * (km - kf));
this.ForceNoseGear = fmz - this.ForceMainGear;
this.ForceGear = this.ForceMainGear + this.ForceNoseGear;
this.ForceMainBrake = km * this.ForceMainGear;
this.ForceNoseBrake = kf * this.ForceNoseGear;
if (this.Speed < 0) {
this.ForceMainBrake = -this.ForceMainBrake;
this.ForceNoseBrake = -this.ForceNoseBrake;
}
this.ForceBrake = this.ForceMainBrake + this.ForceNoseBrake;
this.ForceRoll = this.ForceGear * kf;
var deltaEnergy = this.DeltaDistance * this.ForceDrag;
this.EnergyDrag += deltaEnergy;
this.TotalEnergy += deltaEnergy;
var deltaEnergy = this.DeltaDistance * (-this.ForceEngines);
this.EnergyEngines += deltaEnergy;
this.TotalEnergy += deltaEnergy;
var deltaEnergy = this.DeltaDistance * (this.ForceBrake - this.ForceRoll);
this.EnergyBrake += deltaEnergy;
this.TotalEnergy += deltaEnergy;
var deltaEnergy = this.DeltaDistance * this.ForceRoll;
this.EnergyRoll += deltaEnergy;
this.TotalEnergy += deltaEnergy;
} else {
var fb = this.Kf * (this.Distance - this.GroundContactDistance) + this.Kd * this.Speed;
this.ForceMainGear = (fmz * this.XF - fb * this.ZM - m) / (this.XM + this.XF);
this.ForceNoseGear = fmz - this.ForceMainGear;
this.ForceGear = this.ForceMainGear + this.ForceNoseGear;
var fbf = fb * this.ForceNoseGear / this.ForceGear;
if (fbf > kf * this.ForceNoseGear) fbf = kf * this.ForceNoseGear;
this.ForceNoseBrake = fbf;
this.ForceMainBrake = fb - fbf;
this.ForceBrake = fb;
this.ForceRoll = 0.0;
}
}
this.ForceAcceleration = this.ForceEngines - this.ForceDrag - this.ForceBrake - this.ForceSlope;
}
function RampUp( t, triggerTime, upDuration ) {
if (t <= triggerTime) return 0.0;
if (t >= triggerTime+upDuration) return 1.0;
return (t-triggerTime)/upDuration;
}
function RampDown( t, triggerTime, downDuration ) {
if (t <= triggerTime) return 1.0;
if (t >= triggerTime+downDuration) return 0.0;
return 1.0 - (t-triggerTime)/downDuration;
}
function RampQUp( t, triggerTime, upDuration ) {
var x = RampUp( t, triggerTime, upDuration );
return 1.0 - Math.pow( x - 1.0, 2 );
}
function RampQDown( t, triggerTime, downDuration ) {
var x = RampUp( t, triggerTime, downDuration );
return Math.pow( x - 1.0, 2 );
}
function RampCosUp( t, triggerTime, upDuration ) {
var x = RampUp( t, triggerTime, upDuration );
return 0.5 - Math.cos( x * Math.PI ) / 2.0;
}
function RampCosDown( t, triggerTime, downDuration ) {
var x = RampUp( t, triggerTime, downDuration );
return 0.5 + Math.cos( x * Math.PI ) / 2.0;
}