Source Code (JavaScript) of How Airplanes follow the Curvature of the Earth, Physics Simulation.
#INCLUDE NewtonSolver.inc
#INCLUDE JsGraph.inc
#INCLUDE ControlPanel.inc
#INCLUDE Sim.inc
var V2 = JsgVect2;
function rad( x ) { return x * Math.PI / 180; }
function deg( x ) { return x * 180 / Math.PI; }
function ms_ftmin( x ) { return x * 196.85; }
function ftmin_ms( x ) { return x / 196.85; }
function ms_kt( x ) { return x * 1.94384; }
function kt_ms( x ) { return x / 1.94384; }
function m_ft( x ) { return x * 3.28084; }
function ft_m( x ) { return x / 3.28084; }
function ms_kmh( x ) { return x * 3.6; }
function kmh_ms( x ) { return x / 3.6; }
//////////////////////////////////////////////////////////
// Tables with reference values used in the
// BaroModel standard atmospheric model.
var BaroConst = {
hIx: 0,
// air level data of standard atmosphere model
hLimitTab: [
11000,
20000,
32000,
47000,
51000,
71000,
84852,
0
],
hRefTab: [
0,
11000,
20000,
32000,
47000,
51000,
71000,
NaN
],
alphaTab: [
-0.0065,
0,
0.001,
0.0028,
0,
-0.0028,
-0.002,
NaN
],
TRefTab: [
288.15,
216.65,
216.65,
228.65,
270.65,
270.65,
214.65,
NaN
],
rhoRefTab: [
1.225,
0.363918,
0.0880348,
0.013225,
0.00142753,
0.000861605,
0.000064211,
NaN
],
pRefTab: [
101325,
22632.1,
5474.89,
868.019,
110.906,
66.9389,
3.95642,
NaN
],
// some general constants
g: 9.80665,
R: 8.31446,
RS: 287.058,
kappa: 1.4,
M: 28.9644,
T0: 288.15,
p0: 101325,
rho0: 1.225,
a0: 340.294,
// private function
defIx: function(i) { return xDefNum( i, this.hIx ); },
// set altitude range for following functions
SetAltRange: function( h ) {
for (var i = 0; i < this.hLimitTab.length; i++) {
if (h <= this.hLimitTab[i]) {
this.hIx = i;
return;
}
}
this.hIx = this.hLimitTab.length - 1;
},
SetAltRangeFromP: function( p ) {
var nPrefs = this.pRefTab.length - 2;
for (var i = nPrefs; i >= 0; i--) {
if (p <= this.pRefTab[i]) {
this.hIx = i;
return;
}
}
this.hIx = 0;
},
// query level dependent constants
// default for i is this.hIx, see SetAltRange(h)
hRef: function(i) { return this.hRefTab[this.defIx(i)]; },
alpha: function(i) { return this.alphaTab[this.defIx(i)]; },
TRef: function(i) { return this.TRefTab[this.defIx(i)]; },
rhoRef: function(i) { return this.rhoRefTab[this.defIx(i)]; },
pRef: function(i) { return this.pRefTab[this.defIx(i)]; }
};
//////////////////////////////////////////////////////////
// model of standard atmosphere to calculate
// temperature, pressure, density, temperature gradient
// for any altitude up to 84852 m.
//
// usage:
// BaroModel.Update( h ); // set reference altitude h
// print( BaroModel.T, ..., BaroModel.alpha );
var BaroModel = {
h: 0, // m
T: 0, // K
p: 0, // Pa
rho: 0, // kg/m^3
alpha: 0, // C/m
TempOfH: function( h, updateH ) {
updateH = xDefBool( updateH, true );
if (updateH) BaroConst.SetAltRange( h );
return BaroConst.TRef() +
BaroConst.alpha() * (h - BaroConst.hRef());
},
TempGradOfH: function( h, updateH ) {
updateH = xDefBool( updateH, true );
if (updateH) BaroConst.SetAltRange( h );
return BaroConst.alpha();
},
PressureOfH: function( h, updateH ) {
updateH = xDefBool( updateH, true );
if (updateH) BaroConst.SetAltRange( h );
var alpha = BaroConst.alpha();
if (alpha == 0) {
// isoterm
var hs = BaroConst.RS * BaroConst.TRef() / BaroConst.g;
var p = BaroConst.pRef() * Math.exp( -(h - BaroConst.hRef()) / hs );
return p;
} else {
var beta = BaroConst.g / BaroConst.RS / alpha;
var p = BaroConst.pRef() * Math.pow( 1 + alpha * (h - BaroConst.hRef()) / BaroConst.TRef(), -beta );
return p;
}
},
DensityOfH: function( h, updateH ) {
updateH = xDefBool( updateH, true );
if (updateH) BaroConst.SetAltRange( h );
var alpha = BaroConst.alpha();
if (alpha == 0) {
// isoterm
var hs = BaroConst.RS * BaroConst.TRef() / BaroConst.g;
var r = BaroConst.rhoRef() * Math.exp( -(h - BaroConst.hRef()) / hs );
return r;
} else {
var beta = BaroConst.g / BaroConst.RS / alpha;
var r = BaroConst.rhoRef() * Math.pow( 1 + alpha * (h - BaroConst.hRef()) / BaroConst.TRef(), -beta-1 );
return r;
}
},
HofPressure: function( p ) {
// calculates the height for a certain pressure (inverse of PressureFromH())
// usefull to calculate atmospheric parameters if pressure is not standard:
//
// BaroModel.Update( h ); // compute standard parameters for altitude h
// var p = BaroModel.p; // standard pressure for h
// var rho = BaroModel.rho; // standard density for h
// var deltaH = BaroModel.HofPressure( p+deltaP ) - h; // apparent change in altitude due to non standard pressure
// BaroModel.Update( h + deltaH ); // compute parameters for new altitude h+deltaH
// var deltaRho = BaroModel.rho - rho; // corrected density due to non standard pressure p+deltaP
BaroConst.SetAltRangeFromP( p );
var alpha = BaroConst.alpha();
if (alpha == 0) {
// isoterm
var hs = BaroConst.RS * BaroConst.TRef() / BaroConst.g;
var h = BaroConst.hRef() - hs * Math.log( p / BaroConst.pRef() );
} else {
// linear temperature gradient
var beta = BaroConst.g / BaroConst.RS / alpha;
var h = ((Math.pow( p / BaroConst.pRef(), -1/beta ) - 1) * BaroConst.TRef() + alpha * BaroConst.hRef()) / alpha;
}
return h;
},
Update: function( h ) {
this.h = h;
if (this.h > 84852) this.h = 84852;
if (this.h < 0) this.h = 0;
this.T = this.TempOfH( this.h, true );
this.p = this.PressureOfH( this.h, false );
this.rho = this.DensityOfH( this.h, false );
this.alpha = this.TempGradOfH( this.h, false );
},
};
//////////////////////////////////////////////////////////
// Model to calculate and convert between the air speeds
// tas, cas, eas, mach. Calculates also dynamic pressures (q,qc),
// atmospheric parameters (T,p,rho) and speed of sound (a) for any
// altitude (h).
//
// usage:
// AirSpeedModel.Update( h, tas );
// print( AirSpeedModel.cas, ... );
var AirSpeedModel = {
h: 0,
tas: 0,
cas: 0,
eas: 0,
mach: 0,
q: 0,
qc: 0,
T: 0,
p: 0,
rho: 0,
a: 0,
AofH: function( h ) {
// require TempOfH() is called
return Math.sqrt( BaroConst.kappa * BaroConst.RS * this.T );
},
AtmosphereOfH: function() {
this.T = BaroModel.TempOfH( this.h, true );
this.p = BaroModel.PressureOfH( this.h, false );
this.rho = BaroModel.DensityOfH( this.h, false );
this.a = this.AofH( this.h, false );
},
GetTASofCAS: function( cas ) {
var k1 = (BaroConst.kappa - 1) / 2;
var k2 = BaroConst.kappa / (BaroConst.kappa - 1);
var v = cas / BaroConst.a0;
var qc = BaroConst.p0 * ( Math.pow( k1 * v * v + 1, k2 ) - 1 );
var a = this.AofH( this.h );
var tas = a * Math.sqrt( (1/k1) * ( Math.pow( qc / this.p + 1 , 1/k2 ) - 1 ) );
return tas;
},
GetTASofMach: function( mach ) {
return this.a * mach;
},
AllOfTas: function() {
this.mach = this.tas / this.a;
this.eas = Math.sqrt( this.rho / BaroConst.rho0 ) * this.tas;
var k1 = (BaroConst.kappa - 1) / 2;
var k2 = BaroConst.kappa / (BaroConst.kappa - 1);
var v = this.tas / this.a;
this.qc = this.p * ( Math.pow( k1 * v * v + 1 , k2 ) - 1 );
this.cas = BaroConst.a0 * Math.sqrt( (1/k1) * ( Math.pow( this.qc / BaroConst.p0 + 1 , 1/k2 ) - 1 ) );
this.q = 0.5 * this.rho * this.tas * this.tas;
},
Update: function( h, tas ) {
this.h = h;
this.tas = tas;
this.AtmosphereOfH();
this.AllOfTas();
}
};
//////////////////////////////////////////////////////////
// Simple customizable model of a turbo fan engine.
// Optionally change some default parameters, call Init()
// and then use Compute( n1, tas ) to calculate all
// dependant parameters and the thrust.
//
// var engine = new TurbofanModel();
// engine.Init();
// engine.Compute( n1, tas );
// print( engine.F ); // thrust
function TurbofanModel() {
// data sheet
this.F_max = 117900; // N
this.m0_max = 340.0; // kg/s (more thrust at high speed, old: 397.6)
this.mu_max = 6;
// parameters from other sources
this.F_idle = 3500; // N
this.theta = 60.0 / 180.0 * Math.PI;
this.v9_max = 343; // m/s = a0
this.N1_max = 1.0;
this.N1_idle = 0.15; // (shallower curve, old: 0.215)
this.N1_maxrev= 0.8;
this.N2_max = 0.972;
this.N2_idle = 0.607;
// input parameters
this.N1 = this.N1_idle;
this.v0 = 0.0;
// simulated values as functions of N1 and v0
this.F = 0.0; // N
this.F_rev = 0.0; // N
// simulated internal values as functions of N1 and v0
this.N2 = 0.0;
this.F1 = 0.0; // N
this.F2 = 0.0; // N
this.m1 = 0.0; // kg/s
this.m2 = 0.0; // ks/s
this.v9 = 0.0; // m/s
this.v19 = 0.0; // m/s
this.mu = 0.0;
this.kv = 0.0;
// internal constant parameters, computed in Init()
this.k = 0.0;
this.r = 0.0;
this.k1 = 0.0;
this.r1 = 0.0;
this.a = 0.0;
this.b = 0.0;
this.km1 = 0.0;
this.km2 = 0.0;
this.m1_max = 0.0;
this.m1_idle = 0.0;
this.m2_max = 0.0;
this.kv_max = 0.0;
this.kv_idle = 0.0;
this.F1_max = 0.0;
this.F1_idle = 0.0;
this.v9_idle = 0.0;
}
TurbofanModel.prototype.Init = function(){
this.m1_max = this.m0_max / (this.mu_max + 1);
this.m2_max = this.m1_max * this.mu_max;
this.a = (this.N2_max - this.N2_idle) / (this.N1_max - this.N1_idle);
this.b = this.N2_idle - this.a * this.N1_idle;
this.k = this.SolveForK( this.N1_max, 1.0, this.N1_idle, this.F_idle/this.F_max );
this.r = this.N1_max / Math.log( (1.0 / this.k) + 1.0 );
this.km1 = this.m0_max / ((this.mu_max + 1.0) * (this.a + this.b));
this.km2 = this.m0_max * this.mu_max / (this.mu_max + 1.0);
this.kv_max = ((this.F_max * (1.0 + this.mu_max)) / (this.m0_max * this.mu_max * this.v9_max)) - (1.0 / this.mu_max);
this.kv_idle = this.KVofN1( this.N1_idle );
this.F1_max = this.m1_max * this.v9_max;
this.m1_idle = this.M1ofN1( this.N1_idle );
this.v9_idle = this.FofN1(this.N1_idle) / ( this.M1ofN1(this.N1_idle) + this.M2ofN1(this.N1_idle) * this.kv_idle );
this.F1_idle = this.m1_idle * this.v9_idle;
this.k1 = this.SolveForK( this.N2_max, 1.0, this.N2_idle, this.F1_idle/this.F1_max );
this.r1 = this.N2_max / Math.log( (1.0 / this.k1) + 1.0 );
}
TurbofanModel.prototype.Compute = function( n1, v ) {
if (xDef(n1)) { this.N1 = n1; }
if (xDef(v)) { this.v0 = v; }
this.N2 = this.N2ofN1(this.N1);
this.F1 = this.F1ofN1(this.N1);
this.F2 = this.F2ofN1(this.N1);
this.m1 = this.M1ofN1(this.N1);
this.m2 = this.M2ofN1(this.N1);
this.v9 = this.F1 / this.m1;
this.v19 = (this.m2 < 0.01) ? 0.0 : (this.F2 / this.m2);
this.mu = this.m2 / this.m1;
this.kv = this.v19 / this.v9;
this.F = this.m1 * (this.v9 - this.v0) + this.m2 * (this.v19 - this.v0);
this.F_rev = this.m1 * (this.v9 - this.v0) - this.m2 * (Math.cos(this.theta) * this.v19 + this.v0);
}
TurbofanModel.prototype.FofN1 = function( n ) {
// require k, r, is computed
return this.k * (Math.exp(n/this.r) - 1.0) * this.F_max;
}
TurbofanModel.prototype.F1ofN1 = function( n ) {
// require k1, r1, F1_max is computed
return this.k1 * (Math.exp(this.N2ofN1(n)/this.r1) - 1.0) * this.F1_max;
}
TurbofanModel.prototype.F2ofN1 = function( n ) {
// require k, r, k1, r1, F1_max is computed
return this.FofN1(n) - this.F1ofN1(n);
}
TurbofanModel.prototype.N2ofN1 = function( n ) {
return this.a * n + this.b;
}
TurbofanModel.prototype.M1ofN1 = function( n ) {
// require km1 is computed!
return this.km1 * this.N2ofN1(n);
}
TurbofanModel.prototype.M2ofN1 = function( n ) {
// require km2 is computed!
return this.km2 * n;
}
TurbofanModel.prototype.KVofN1 = function( n ) {
// require kv_max is computed!
return (this.kv_max - 1.0) * n + 1.0;
}
TurbofanModel.prototype.SolveForK = function( N1, F1, N2, F2 ) {
var myFunction = function(x) {
var t1 = N1 * Math.log( (F2 / x) + 1 );
var t2 = N2 * Math.log( (F1 / x) + 1 );
return t1 - t2;
};
var k = SolveWithNewton( myFunction, 0, 0.0000001, 0.0000001 );
if (NewtonSolver.Status != '') {
alert( 'NewtonSolver Error: ' + NewtonSolver.Status );
}
return k;
}
//////////////////////////////////////////////////////////
// Model to calculate lift and drag coefficients over the
// whole range of angle of attack -pi..pi.
var ClModel = {
aoa1: -5,
aoa2: 18,
cl1: 0.2,
cl2: 5.16,
cd1: 0.02,
cd2: 0.026,
clpOffset: 0.1,
q: 25,
CL: function( aoa ) {
// lift coefficient for wing profile
var result = this.cl1 + this.cl2 * aoa;
return result;
},
CD: function( aoa ) {
// drag coefficient for wing profile
var result = this.cd1 + this.cd2 * Math.pow( this.CL(aoa), 2 );
return result;
},
CLP: function( aoa ) {
// lift coefficient for flat wing
var result = Math.sin( 2 * aoa ) + this.clpOffset;
return result;
},
CDP: function( aoa ) {
// drag coefficient for flat wing
var result = 1 - Math.cos( 2 * aoa );
return result;
},
Up: function( x, pos ) {
// ramp up from 0 to 1 at pos
var result = 1 / (1 + Math.exp( -this.q * (x - pos)));
return result;
},
Down: function( x, pos ) {
// ramp down from 0 to 1 at pos
var result = 1 / (1 + Math.exp( this.q * (x - pos)));
return result;
},
Win: function( x, pos1, pos2 ) {
// ramp up from 0 to 1 at pos1 and down again at pos 2
var result = this.Up( x, pos1 ) * this.Down( x, pos2 );
return result;
},
// public
CLT: function( aoa ) {
// lift coefficient for whole range of AoA
// aoa in rad
if (aoa > Math.PI/2) aoa -= Math.PI;
if (aoa < -Math.PI/2) aoa += Math.PI;
var aoa1 = rad(this.aoa1);
var aoa2 = rad(this.aoa2);
var result =
this.Down( aoa, aoa1 ) * this.CLP( aoa ) +
this.Win( aoa, aoa1, aoa2 ) * this.CL( aoa ) +
this.Up( aoa, aoa2) * this.CLP( aoa );
return result;
},
CLT_deg: function( aoaDeg ) {
// lift coefficient for whole range of AoA
return this.CLT( rad(aoaDeg) );
},
CDT: function( aoa ) {
// drag coefficient for whole range of AoA
// aoa in rad
if (aoa > Math.PI/2) aoa -= Math.PI;
if (aoa < -Math.PI/2) aoa += Math.PI;
var aoa1 = rad(this.aoa1);
var aoa2 = rad(this.aoa2);
var result =
this.Down( aoa, aoa1 ) * this.CDP( aoa ) +
this.Win( aoa, aoa1, aoa2 ) * this.CD( aoa ) +
this.Up( aoa, aoa2) * this.CDP( aoa );
return result;
},
CDT_deg: function( aoaDeg ) {
return this.CDT( rad(aoaDeg) );
},
};
//////////////////////////////////////////////////////////
var AircraftModel = {
// aircraft state
State: { // [ x, y, pitch ]
Accel: [ 0, 0, 0 ], // m/s^2
Speed: [ 0, 0, 0 ], // m/s
Pos: [ 0, 0, 0 ], // m
OldAccel: [ 0, 0, 0 ],
OldSpeed: [ 0, 0, 0 ],
},
TAS: 0, // m/s
CAS: 0, // m/s
Mach: 0,
Altitude: 0, // m pressure altitude
Elevation: 0, // m above sea level
VS: 0, // m/s
Pitch: 0, // rad
PitchRate: 0, // rad/s
PitchElevMod:0, // modulation of pitch due to pitch rate if pitch damping is enabled
AoA: 0, // rad
N1: 0, // %
TimeOffset: 0, // s, see ResetTime()
LowestManeuverSpeed: 0,
StallSpeed: 0,
MaxOperatingSpeed: 0,
ShowAircraft: true,
ShowFlightPath: true,
ShowForces: true,
ShowTrend: true,
ShowCoordSys: true,
ShowSpeed: true,
ShowAccel: true,
ShowFxTimes5: true,
PitchDamping: true,
LimitExceeded: '',
Engines: null,
AirTemperature: 0,
AirDensity: 0,
AirPressure: 0,
SpeedOfSound: 0, // m/s at current altitude
// initial state
InitMass: 55200, // kg
InitCG: 0, // -1..+1 -> 0% MAC..75% MAC -> MinCG..MaxCG
InitTAS: kt_ms(443), //
InitAltitude: ft_m(37000),
InitVS: 0,
InitPitch: rad(3.10),
InitThrust: 0.9060, // thrust lever position
InitTrim: rad(3.76),
InitElevator: 0,
InitDeltaPressure: 0, // Pa
InitEarthRadius: 6371000/20,
// aircraft parameters and commands
Mass: 0, // kg
ThrustLever: 0, // 0...1
TrimLever: 0, // MinTrimAngle...MaxTrimAngle
ElevatorLever: 0, // MinElevatorAngle...MaxElevatorAngle
DeltaPressure: 0, // +/- 3000 Pa
Cl0: 0.2,
ClAoA: 5.16,
Cd0: 0.02,
Cd1: 0.0264,
Cm0: 0,
CmAoA: 0,
Ct: 5, // lift coefficients for trim and elevator flaps
Ce: 2.5,
Cpd: 10, // pitch damping coefficient, 2*(1/2*cross_section_AC_top_view+ElevatorArea)
MAC: 4.1935,
WingArea: 122.4,
WingSpan: 34.09,
AircraftLength: 37.6,
TrimAngleRate: 0.02, // rad/s
ElevatorAngleRate:0.35, // rad/s
EngineSpoolRate: 0.1, // %N1/s
AoAoffset: 0, // if aircraft is flying pitch = 0, this is the angle of attack
ElevatorArea: 31,
MomentOfInertia: 3*2400000, // kg*m^2 Trägheitsmoment I, analog zu masse https://www.researchgate.net/figure/Parameters-of-icing-research-airbus_tbl1_339115153
// aircraft geometry with respect to Center of Lift = AircraftZeroPos
// X points forwards, Z points upwards
AircraftZeroPos: [ -16.5, 2.8 ], // [m] = CenterOfLift, measured from the bottom left of the sideview,
CenterOfLift: [ 0.0, 0.0 ],
CenterOfGravity: [ 1.0, 0.0 ],
ElevatorPos: [ -17.5, 1.4 ],
EnginePos: [ 4.0, -1.6 ],
CenterGravityPos: 1, // forward from center of lift modulated by MinCG..MaxCG
// aircraft limits
MinMass: 38600, // kg
MaxMass: 73600, // kg
MinN1: 0.40,
MaxN1: 1,
MinTrimAngle: rad(-4),
MaxTrimAngle: rad(13.5),
MinElevatorAngle: rad(-17),
MaxElevatorAngle: rad(30),
MaxCG: 0.8, // 1 GC
MinCG: -0.8, // -1 CG
MinAoA: rad(-5), // used for stall indication
MaxAoA: rad(15), // used to calculate min speeds and stall indication
MaxTAS: kt_ms(580), // used for length of force arrows scaling
LimitCAS: kt_ms(350), // used for Vmo
LimitMach: 0.82, // used for Vmo
// aircraft unit vectors in ECEF coordinates
Xsurface: [ 0, 0 ],
Zsurface: [ 0, 0 ],
Xaircraft: [ 0, 0 ],
Zaircraft: [ 0, 0 ],
SpeedDir: [ 0, 0 ],
SpeedPerp: [ 0, 0 ],
// forces, vectors in ECEF coordinates
FWeight: [ 0, 0 ],
FLift: [ 0, 0 ],
FThrust: [ 0, 0 ],
FDrag: [ 0, 0 ],
FTrim: [ 0, 0 ],
Ftot: [ 0, 0 ],
PitchTorque: 0, // Drehmoment M, analog zu Kraft
// for trend vectors
FLiftOld: [ 0, 0 ],
FThrustOld: [ 0, 0 ],
FDragOld: [ 0, 0 ],
FTrimOld: [ 0, 0 ],
TASold: 0,
Thrust: 0,
N1Command: 0,
TrimAngle: 0,
TrimAngleCommand: 0,
ElevatorAngle: 0,
ElevatorAngleCommand: 0,
DynamicPressure: 0, // q(h,tas) for use in lift equation
EarthRadius: 6371000/20,
g: 9.806, // m/s^2 gravitational acceleration
// referenced objects
Sim: null, // simulator
Jsg: null, // graphic
// sim callbacks
Init: function( jsg, sim ) {
this.Jsg = jsg;
this.Sim = sim;
this.Engines = new TurbofanModel();
this.Engines.Init();
this.OnSimReset();
// create flight path vector symbol
jsg.AddMarkerDef( 'FPSymbol',
[ { type: 'Circle', x: 0, y: 0, r: -0.5 },
{ type: 'Line', x1: -1.5, y1: 0, x2: -0.5, y2: 0 },
{ type: 'Line', x1: 1.5, y1: 0, x2: 0.5, y2: 0 },
{ type: 'Line', x1: 0, y1: -0.5, x2: 0, y2: -1.0 }
]
);
},
OnSimReset: function() {
this.Mass = this.InitMass;
this.CG = this.InitCG;
this.TAS = this.InitTAS;
this.TASold = this.TAS;
this.Altitude = this.InitAltitude;
this.VS = this.InitVS;
this.Pitch = this.InitPitch;
this.ThrustLever = this.InitThrust;
this.N1Command = (this.MaxN1-this.MinN1) * this.ThrustLever + this.MinN1;
this.N1 = this.N1Command;
this.TrimLever = this.InitTrim;
this.TrimAngleCommand = this.TrimLever;
this.TrimAngle = this.TrimAngleCommand;
this.ElevatorLever = this.InitElevator;
this.ElevatorAngleCommand = this.ElevatorLever;
this.ElevatorAngle = this.ElevatorAngleCommand;
this.DeltaPressure = this.InitDeltaPressure;
this.TimeOffset = 0;
this.ShowAircraft = true;
this.ShowFlightPath = true;
this.ShowForces = true;
this.ShowTrend = true;
this.ShowCoordSys = true;
this.ShowSpeed = true;
this.ShowAccel = true;
this.ShowFxTimes5 = true;
this.ComputeInitialState();
this.CompCoordSysAxes();
this.UpdateAircraftState();
this.Sim.InitStates( this.State );
this.SaveOldState();
this.Update();
},
OnSimUpdate: function() {
// is called by simulator to calculate the next AC state
var elev = V2.Length( this.State.Pos ) - this.EarthRadius;
if (elev <= 0) {
this.Sim.Reset();
this.Sim.Run();
return;
}
this.UpdateAircraftState();
},
OnSimDraw: function() {
// is called by simulator to draw currect AC state
this.Update();
},
UpdateAircraftState: function() {
this.LimitExceeded = '';
this.CompAccel();
this.Sim.CompNewStates( this.State );
},
Update: function( field ) {
if (ControlPanels.MatchesField( field, 'EarthRadius' )) {
var currentAlt = this.Elevation;
var newPos = V2.Scale( V2.Norm( this.State.Pos ), this.EarthRadius + currentAlt );
this.State.Pos[0] = newPos[0]; this.State.Pos[1] = newPos[1];
}
ControlPanels.Update();
this.Draw();
},
ResetPos: function() {
var angle = V2.Angle( V2.Norm(this.State.Pos), [0,1] );
this.State.Pos[0] = 0;
this.State.Pos[1] = this.EarthRadius + this.Elevation;
this.State.Pos[2] = this.Pitch;
var newSpeedVect = V2.Rotate( this.State.Speed, angle );
this.State.Speed[0] = newSpeedVect[0];
this.State.Speed[1] = newSpeedVect[1];
this.CompCoordSysAxes();
this.UpdateAircraftState();
this.Update();
},
ResetTime: function() {
this.TimeOffset = this.Sim.SimulTime;
this.Update();
},
ComputeInitialState: function() {
this.State.Pos[0] = 0;
this.State.Pos[1] = this.EarthRadius + this.Altitude;
this.State.Pos[2] = this.Pitch;
this.State.Speed[0] = this.TAS;
this.State.Speed[1] = this.VS;
this.State.Speed[2] = 0; // pitch rate
},
CompCoordSysAxes: function() {
// require state is computed
this.Zsurface = V2.Norm( this.State.Pos );
this.Xsurface = V2.Rotate( this.Zsurface, -Math.PI/2 );
this.Xaircraft = V2.Rotate( this.Xsurface, this.Pitch );
this.Zaircraft = V2.Rotate( this.Xaircraft, Math.PI/2 );
this.SpeedDir = V2.Norm( this.State.Speed );
this.SpeedPerp = V2.Rotate( this.SpeedDir, Math.PI/2 );
},
CompSpeedsAndBaro: function() {
// Computes barometric parameters like air pressure, density, temperature
// speed of sound for current Elevation.
// Computes the air parameter dependent different air speeds:
// TAS, Density(Elevation) -> CAS, Mach, speed of sound (a)
// Use BaroModel to calculate all air parameters for Elevation
BaroModel.Update( this.Elevation );
// Compute Pressure Altitude due to non standard pressure DeltaPressure
if (this.DeltaPressure == 0) {
this.Altitude = this.Elevation;
} else {
var p = BaroModel.p; // standard pressure for Elevation
// compute pressure altitude
this.Altitude = BaroModel.HofPressure( p + this.DeltaPressure );
// compute parameters from pressure altitude (Elevation+DeltaPressure)
BaroModel.Update( this.Altitude );
}
// Use AirSpeedModel to calculate various air speeds from TAS
AirSpeedModel.Update( this.Altitude, this.TAS );
this.CAS = AirSpeedModel.cas;
this.Mach = AirSpeedModel.mach;
this.SpeedOfSound = AirSpeedModel.a;
// Save air parameters in AircraftModel
this.AirPressure = AirSpeedModel.p;
this.AirTemperature = AirSpeedModel.T - 273.15; // K to °C
this.AirDensity = AirSpeedModel.rho;
this.DynamicPressure = AirSpeedModel.q;
// compute stall speed and LowestManeuveringSpeed for speed tape red bar
var Clmax = this.CL( 0.9 * this.MaxAoA );
var stallSpeed =
Math.sqrt( 2 * this.Mass * this.Gravity(this.Elevation) /
(this.AirDensity * Clmax * this.WingArea) );
AirSpeedModel.Update( this.Altitude, stallSpeed ); // convert to CAS
this.StallSpeed = AirSpeedModel.cas;
this.LowestManeuverSpeed = this.StallSpeed * 1.15;
// indicate SPEED and STALL
if (this.CAS < this.StallSpeed) {
this.LimitExceeded = 'STALL';
} else if (this.CAS < this.LowestManeuverSpeed) {
this.LimitExceeded = 'SPEED';
}
// compute max operating speed as the smaller of
// max CAS limit and altitude dependent Mach limit
// mach limit for altitude
var mach = AirSpeedModel.AofH( this.Altitude ) * this.LimitMach;
AirSpeedModel.Update( this.Altitude, mach ); // convert to CAS
var vmo = AirSpeedModel.cas;
if (vmo > this.LimitCAS) vmo = this.LimitCAS;
this.MaxOperatingSpeed = vmo;
// indicate overspeed as SPEED
if (this.CAS > this.MaxOperatingSpeed) {
this.LimitExceeded = 'SPEED';
}
},
CL: function( a ) {
// Compute lift coefficient; a = AoA in rad
// return this.Cl0 + this.ClAoA * a;
return ClModel.CLT(a);
},
CD: function( a ) {
// Compute drag coefficient; a = AoA in rad
// var cl = this.CL( a );
// return this.Cd0 + this.Cd1 * cl * cl;
return ClModel.CDT(a);
},
CM: function( a ) {
// Compute momentum coeffiecient; a = AoA in rad
return this.Cm0 + this.CmAoA * a;
},
CT: function( trimAngle, elevatorAngle ) {
// Compute trim lift coefficient for trimAngle and elevator angle
return this.Ct * trimAngle + this.Ce * elevatorAngle;
},
Gravity: function( h ) {
return this.g * Math.pow( this.EarthRadius, 2 ) / Math.pow( this.EarthRadius + h, 2 );
},
CompAccel: function() {
// Compute all forces, moments and the acceleration and PitchRate of the aircraft.
// save previous forces for calculation of trend vectors
this.SaveOldState();
// compute all forces for current aircraft state
this.CompForces();
// Compute acceleration from sum of all forces
var accel = V2.Scale( this.Ftot, 1 / this.Mass );
var pitchAccel = this.PitchTorque / this.MomentOfInertia;
this.State.Accel[0] = accel[0];
this.State.Accel[1] = accel[1];
this.State.Accel[2] = pitchAccel;
},
SaveOldState: function() {
this.FLiftOld = V2.Copy( this.FLift );
this.FDragOld = V2.Copy( this.FDrag );
this.FTrimOld = V2.Copy( this.FTrim );
this.FThrustOld = V2.Copy( this.FThrust );
},
LinearRampTo: function( command, current, rate ) {
if (command < current) {
// ramp down
current -= this.Sim.DeltaTime * rate;
if (current < command) current = command;
} else if (command > current) {
// ramp up
current += this.Sim.DeltaTime * rate;
if (current > command) current = command;
}
return current;
},
TorqueAroundCG: function( forceOrigin, forceVect ) {
// computes the torque of a force with origin not at the center of gravity
// around the center of gravity CG.
// The torque is the cross product (= vector product) of the vector forceOrigin-CG and
// the forceVect. This product in 3D only has a component in the wing axis Y.
// This Y component is just our torque. I can use the V2.VectProd() function to
// calculate the Y component.
// transform forceVect from ECEF into aircraft coordinate system
var forceVect = [
V2.ScalarProd( this.Xaircraft, forceVect ),
V2.ScalarProd( this.Zaircraft, forceVect ) ];
var torque = V2.VectProd( V2.Sub( forceOrigin, this.CenterOfGravity ), forceVect );
return torque;
},
CompForces: function() {
// get state
this.Elevation = V2.Length( this.State.Pos ) - this.EarthRadius;
this.Pitch = this.State.Pos[2];
this.TASold = this.TAS;
this.TAS = V2.Length( this.State.Speed );
this.VS = V2.ScalarProd( this.Zsurface, this.State.Speed );
this.PitchRate = this.State.Speed[2];
// limit pitch
while (this.Pitch <= -rad(180)) {
this.Pitch += rad(360);
}
while (this.Pitch > rad(180)) {
this.Pitch -= rad(360);
}
this.State.Pos[2] = this.Pitch;
// Compute the coordinate systems
this.CompCoordSysAxes();
// Compute the center of gravity from CG lever
var centerGravityMAC = (this.MaxCG - this.MinCG) * (this.CG+1)/2 + this.MinCG;
this.CenterOfGravity[0] = this.CenterGravityPos + centerGravityMAC;
// TrimLever position is translated into a TrimAngleCommand.
// TrimAngle is set in a ramp defined by TrimAngleRate,
// because the horizontal stabilizer trim moved driven by a motor
// at a contstant speed.
this.TrimAngleCommand = this.TrimLever;
this.TrimAngle = this.LinearRampTo( this.TrimAngleCommand, this.TrimAngle, this.TrimAngleRate );
// ElevatorLever position is translated into a ElevatorAngleCommand.
// ElevatorAngle is set in a ramp defined by ElevatorAngleRate
// Modulate elevator depending on vertical speed and pitch rate
// to dampen pitch rate and Phugoid oscillation.
// Damping Factors empirically derived.
this.ElevatorAngleCommand = this.ElevatorLever;
var elevAngle = this.ElevatorAngleCommand;
if (this.PitchDamping) {
var vsAngleModulationLimit = 0.8 * this.MaxElevatorAngle;
var vsMaxModulation = this.VS / ftmin_ms(6000);
vsElevAngleMod = vsAngleModulationLimit * vsMaxModulation * Math.abs(vsMaxModulation);
if (vsElevAngleMod > vsAngleModulationLimit) vsElevAngleMod = vsAngleModulationLimit;
if (vsElevAngleMod < -vsAngleModulationLimit) vsElevAngleMod = -vsAngleModulationLimit;
this.PitchElevMod = 2.25 * this.PitchRate;
elevAngle -= this.PitchElevMod + vsElevAngleMod;
if (elevAngle > this.MaxElevatorAngle) elevAngle = this.MaxElevatorAngle;
if (elevAngle < this.MinElevatorAngle) elevAngle = this.MinElevatorAngle;
}
this.ElevatorAngle =
this.LinearRampTo( elevAngle, this.ElevatorAngle, this.ElevatorAngleRate );
// Compute air parameters for current altitude and depending on them
// the various air speeds: TAS, Altitude -> CAS, Mach, speed of sound a
// Note: if air pressure is not standard, elevation and altitude are not the same.
// Elevation is the geometric distance from the surface of sea level.
// Altitude is the altitude derived from air pressure measurements.
this.CompSpeedsAndBaro();
// ThrustLever positions are translated into N1Commands.
// N1 change is set in a ramp given by EngineSpoolRate.
this.N1Command = (this.MaxN1-this.MinN1) * this.ThrustLever + this.MinN1;
this.N1 = this.LinearRampTo( this.N1Command, this.N1, this.EngineSpoolRate );
// compute enging thrust from N1 (rotation rate in % of max N1).
// Effective thrust force depends on TAS acting in the X axis of the airplane.
// Only on TAS = 0 max thrust is possible.
// On low N1 the engines create drag (negative thrust force).
// v0 is TAS component in the X axis of the airplane.
var v0 = V2.ScalarProd( this.Xaircraft, this.State.Speed );
this.Engines.Compute( this.N1, v0 );
this.Thrust = 2 * this.Engines.F;
// correct thrust for density and dynamic pressure
this.Thrust *= this.CAS / this.TAS;
// Compute Angle of Attack AoA from direction of flight through the air
// (SpeedDir and the direction the X-axis of the airplane is pointing.
// As the model does not implement an autopilot that implements flight
// envelope protection, AoA is hardcoded to be limited to a valid range
// to prevent simulation instabilities.
// The stall behavior is therefore not simulated accurately.
var windNoseAngle = V2.Angle( this.SpeedDir, this.Xaircraft );
this.AoA = windNoseAngle + this.AoAoffset;
while (this.AoA <= -rad(180)) {
this.AoA += rad(360);
}
while (this.AoA > rad(180)) {
this.AoA -= rad(360);
}
if (this.AoA < this.MinAoA) {
this.LimitExceeded = 'AoA';
} else if (this.AoA > this.MaxAoA) {
this.LimitExceeded = 'STALL';
}
// compute the different forces from the current aircraft state
this.FWeight = V2.Scale( this.Zsurface, -this.Mass * this.Gravity(this.Elevation) );
this.FThrust = V2.Scale( this.Xaircraft, this.Thrust );
var lift = this.DynamicPressure * this.CL(this.AoA) * this.WingArea;
this.FLift = V2.Scale( this.SpeedPerp, lift );
var effTrimAngle = this.TrimAngle - windNoseAngle;
var effElevAngle = this.ElevatorAngle + this.TrimAngle - windNoseAngle;
var trimLift = this.DynamicPressure * this.CT( effTrimAngle, effElevAngle ) * this.ElevatorArea;
var trimLiftVect = V2.Rotate( this.Xaircraft, -this.TrimAngle - Math.PI/2 );
this.FTrim = V2.Scale( trimLiftVect, trimLift );
var drag = this.DynamicPressure * this.CD(this.AoA) * this.WingArea;
this.FDrag = V2.Scale( this.SpeedDir, -drag );
// Compute sum of all forces
var fTot = this.FWeight;
fTot = V2.Add( fTot, this.FLift );
fTot = V2.Add( fTot, this.FThrust );
fTot = V2.Add( fTot, this.FDrag );
fTot = V2.Add( fTot, this.FTrim );
this.Ftot = fTot;
// compute the various torques and total PitchTorque
var liftMoment = this.DynamicPressure * this.CM(this.AoA) * this.WingArea * this.MAC;
var liftTorque = this.TorqueAroundCG( this.CenterOfLift, this.FLift );
var trimTorque = this.TorqueAroundCG( this.ElevatorPos, this.FTrim );
var thrustTorque = this.TorqueAroundCG( this.EnginePos, this.FThrust );
// force daming up/down oscillations depending on PitchRate
// acting on the horizontal stabilizer
var horizStabDistFromCG = this.CenterOfGravity[0] - this.ElevatorPos[0];
var horizStabVertSpeed = this.PitchRate * horizStabDistFromCG;
var pitchDampingForce = -0.5 * this.AirDensity * Math.pow(horizStabVertSpeed,2) * this.Cpd * this.ElevatorArea;
var pitchDampingTorque = horizStabDistFromCG * pitchDampingForce;
// total pitch torque
this.PitchTorque = liftMoment + liftTorque + trimTorque + thrustTorque + pitchDampingTorque;
},
Draw: function() {
g = this.Jsg;
g.Reset();
g.SetAngleMeasure( 'rad' );
var vpX = 0.7 * g.CanvasHeight;
var vpY = 0;
var vpWidth = g.CanvasWidth - 0.7 * g.CanvasHeight;
var vpHeight = g.CanvasHeight;
g.SetViewport( vpX, vpY, vpWidth, vpHeight, false, true );
var winXcenter = 0;
var winYcenter = 0;
var winWidth = 1.5 * this.AircraftLength;
var winHeight = 1.5 * this.AircraftLength;
g.MapWindow( winXcenter, winYcenter, winWidth, winHeight, 0 );
this.DrawHorizon( g );
this.DrawAircraft( g );
// elevator trim indication
var dspWidth = vpWidth / 6;
var dspHeight = dspWidth;
var vpX = 0.7 * g.CanvasHeight;
var vpY = g.CanvasHeight - dspHeight;
this.DrawElevatorTrimIndication( g, vpX, vpY, dspWidth, dspHeight );
// engine indication
var dspWidth = vpWidth / 6;
var dspHeight = dspWidth;
var vpX = 0.7 * g.CanvasHeight + dspWidth;
var vpY = g.CanvasHeight - dspHeight;
this.DrawEngineIndication( g, vpX, vpY, dspWidth, dspHeight );
// primary flight display PFD
var vpWidth = 0.7 * g.CanvasHeight;
var vpHeight = 5/6 * vpWidth;
var vpX = 0;
var vpY = g.CanvasHeight - vpHeight;
this.DrawPfd( g, vpX, vpY, vpWidth, vpHeight );
// Forces Display
var vpHeight = g.CanvasHeight - 5/6 * vpWidth;
var vpX = 0;
var vpY = 0;
this.DrawDataDisplay( g, vpX, vpY, vpWidth, vpHeight );
},
DrawHorizon: function( g ) {
var surfaceAngle = V2.Angle( [0,1], this.Zsurface );
g.TransRotate( surfaceAngle );
// horizon drops according to AC altitude and size of the earth
var dropScale = -(1/6) * Math.log10( this.EarthRadius ) + (10/6);
var horizonDrop = 7 * dropScale * g.WinHeight * Math.sqrt( 2 * this.Elevation / 6371000 );
// draw earth
g.SetAlpha( 0.6 );
var grad = g.CreateLinearGradient( {
X1: 0, Y1: -horizonDrop, X2: 0, Y2: -0.4*g.WinWidth,
Stops: [
{ Pos: 0 , Color: '#b6b3ff' },
{ Pos: 0.45, Color: '#354d8b' },
{ Pos: 1, Color: '#223d36' }
]
} );
g.SetAreaAttr( grad, grad, 1 );
g.Rect( -1.5 * g.WinWidth, -1.5 * g.WinWidth, 1.5 * g.WinWidth, -horizonDrop, 2 );
// draw sky
g.SetAlpha( 1 );
var grad = g.CreateLinearGradient( {
X1: 0, Y1: 1*g.WinWidth, X2: 0, Y2: -horizonDrop,
Stops: [
{ Pos: 0, Color: '#3a51f7' },
{ Pos: 1, Color: '#ffffff' }
]
} );
g.SetAreaAttr( grad, grad, 1 );
g.Rect( -1.5 * g.WinWidth, -horizonDrop, 1.5 * g.WinWidth, 1.5 * g.WinWidth, 2 );
g.SetAlpha( 1 );
g.ResetTrans();
},
DrawAircraft: function( g ) {
// The airplane in this function is scaled to a length of 1 unit
// and graphuc transformations are used to scale, move and rotate the airplane.
// scale and center aircraft at center of screen
g.TransScale( -this.AircraftLength, this.AircraftLength );
g.TransMove( -this.AircraftZeroPos[0], -this.AircraftZeroPos[1] );
// set pitch
var surfaceAngle = V2.Angle( [0,1], this.Zsurface );
g.TransRotate( this.Pitch + surfaceAngle );
// move vertical to indicate vertical speed
// using atan to limit the vertical displacement
var vsDisplacement = 4 * Math.atan( this.VS / 20 );
var vsDisplacementVect = V2.Scale( this.Zsurface, vsDisplacement );
g.TransMove( vsDisplacementVect );
if (!this.ShowAircraft) {
g.SetAlpha( 0.15 );
}
g.SetAreaAttr( 'white', 'black', 1 );
function op(){g.OpenPath();}
function mt(x,y){g.MoveTo(x,y);}
function lt(x,y){g.LineTo(x,y);}
function pt(m){g.Path(m);}
function bc(cx1,cy1,cx2,cy2,x,y){g.BezierCurveTo(cx1,cy1,cx2,cy2,x,y);}
function ln(x1,y1,x2,y2){g.Line(x1,y1,x2,y2);}
function rc(x,y,w,h,m){g.RectWH(x,y,w,h,m);}
function po(x,y,m){g.Polygon(x,y,m);}
// draw aircraft
op();
mt( 0.765, 0.141 );
bc( 0.783, 0.145, 0.796, 0.148, 0.806, 0.158 );
bc( 0.816, 0.167, 0.917, 0.279, 0.923, 0.286 );
bc( 0.929, 0.293, 0.934, 0.293, 0.934, 0.293 );
lt( 0.980, 0.293 );
bc( 0.980, 0.293, 0.980, 0.291, 0.979, 0.283 );
bc( 0.978, 0.275, 0.937, 0.132, 0.937, 0.132 );
pt( 7 );
ln( 0.964, 0.293, 0.891, 0.135 );
op();
mt( 0.684, 0.0329 );
bc( 0.666, 0.0326, 0.154, 0.0329, 0.107, 0.0333 );
bc( 0.0603, 0.0336, -4.73e-17, 0.0513, 0.000147, 0.0703 );
bc( 0.000294, 0.0892, 0.0321, 0.0990, 0.0335, 0.101 );
bc( 0.0349, 0.103, 0.0433, 0.116, 0.0622, 0.127 );
bc( 0.0811, 0.137, 0.101, 0.141, 0.129, 0.141 );
bc( 0.157, 0.141, 0.566, 0.141, 0.605, 0.141 );
bc( 0.643, 0.141, 0.744, 0.141, 0.779, 0.141 );
bc( 0.814, 0.140, 0.875, 0.139, 0.894, 0.137 );
bc( 0.913, 0.136, 0.927, 0.134, 0.937, 0.133 );
bc( 0.948, 0.132, 0.999, 0.128, 0.999, 0.114 );
bc( 1.00, 0.100, 0.926, 0.0784, 0.889, 0.0700 );
bc( 0.853, 0.0616, 0.761, 0.0416, 0.735, 0.0374 );
bc( 0.709, 0.0332, 0.702, 0.0333, 0.684, 0.0329 );
pt( 7 );
op();
mt( 0.432, 0.0280 );
bc( 0.512, 0.0280, 0.545, 0.0322, 0.556, 0.0364 );
bc( 0.566, 0.0406, 0.573, 0.0483, 0.572, 0.0525 );
bc( 0.570, 0.0567, 0.563, 0.0616, 0.523, 0.0665 );
bc( 0.483, 0.0714, 0.437, 0.0826, 0.400, 0.0798 );
bc( 0.363, 0.0770, 0.353, 0.0784, 0.325, 0.0735 );
bc( 0.297, 0.0686, 0.288, 0.0630, 0.287, 0.0483 );
bc( 0.286, 0.0336, 0.339, 0.0280, 0.371, 0.0273 );
bc( 0.403, 0.0266, 0.432, 0.0280, 0.432, 0.0280 );
pt( 7 );
op();
mt( 0.297, 0.0609 );
bc( 0.311, 0.0660, 0.320, 0.0664, 0.335, 0.0674 );
bc( 0.340, 0.0613, 0.401, 0.0656, 0.426, 0.0655 );
bc( 0.451, 0.0654, 0.477, 0.0639, 0.477, 0.0639 );
bc( 0.477, 0.0639, 0.478, 0.0609, 0.475, 0.0582 );
bc( 0.473, 0.0555, 0.452, 0.0402, 0.437, 0.0398 );
bc( 0.422, 0.0394, 0.385, 0.0392, 0.385, 0.0392 );
lt( 0.303, 0.0576 );
lt( 0.297, 0.0609 );
pt( 7 );
op();
mt( 0.280, 0.0111 );
mt( 0.280, 0.0552 );
bc( 0.280, 0.0583, 0.287, 0.0611, 0.291, 0.0611 );
bc( 0.296, 0.0611, 0.323, 0.0610, 0.331, 0.0610 );
bc( 0.340, 0.0609, 0.343, 0.0593, 0.343, 0.0566 );
bc( 0.343, 0.0540, 0.343, 0.00738, 0.343, 0.00457 );
bc( 0.343, 0.00176, 0.341, 7.57e-17, 0.327, 0.000597 );
bc( 0.312, 0.00119, 0.303, 0.00154, 0.297, 0.00293 );
bc( 0.290, 0.00431, 0.280, 0.00751, 0.280, 0.0111 );
pt( 7 );
ln( 0.310, 0.0610, 0.310, 0.00186 );
op();
mt( 0.344, 0.00399 );
mt( 0.344, 0.0572 );
bc( 0.370, 0.0576, 0.376, 0.0506, 0.376, 0.0506 );
lt( 0.376, 0.0140 );
bc( 0.376, 0.0140, 0.368, 0.00420, 0.344, 0.00399 );
pt( 7 );
op();
mt( 0.376, 0.0174 );
mt( 0.376, 0.0455 );
bc( 0.386, 0.0455, 0.392, 0.0401, 0.392, 0.0380 );
lt( 0.392, 0.0246 );
bc( 0.392, 0.0246, 0.387, 0.0176, 0.376, 0.0174 );
pt( 7 );
po( [ 0.392, 0.392, 0.392, 0.415], [ 0.0372, 0.0255, 0.0253, 0.0305], 7 );
op();
mt( 0.616, 0.109 );
bc( 0.616, 0.111, 0.618, 0.112, 0.620, 0.112 );
bc( 0.621, 0.112, 0.623, 0.111, 0.623, 0.109 );
bc( 0.623, 0.108, 0.623, 0.103, 0.623, 0.101 );
bc( 0.623, 0.100, 0.622, 0.0994, 0.620, 0.0994 );
bc( 0.618, 0.0994, 0.616, 0.100, 0.616, 0.101 );
pt( 7 );
op();
mt( 0.709, 0.109 );
bc( 0.709, 0.111, 0.711, 0.112, 0.712, 0.112 );
bc( 0.714, 0.112, 0.716, 0.111, 0.716, 0.109 );
bc( 0.716, 0.108, 0.716, 0.103, 0.716, 0.101 );
bc( 0.716, 0.100, 0.714, 0.0994, 0.712, 0.0994 );
bc( 0.711, 0.0994, 0.709, 0.100, 0.709, 0.101 );
pt( 7 );
op();
mt( 0.629, 0.109 );
bc( 0.629, 0.111, 0.631, 0.112, 0.633, 0.112 );
bc( 0.635, 0.112, 0.636, 0.111, 0.636, 0.109 );
bc( 0.636, 0.108, 0.636, 0.103, 0.636, 0.101 );
bc( 0.636, 0.100, 0.635, 0.0994, 0.633, 0.0994 );
bc( 0.631, 0.0994, 0.629, 0.100, 0.629, 0.101 );
pt( 7 );
op();
mt( 0.722, 0.109 );
bc( 0.722, 0.111, 0.724, 0.112, 0.726, 0.112 );
bc( 0.727, 0.112, 0.729, 0.111, 0.729, 0.109 );
bc( 0.729, 0.108, 0.729, 0.103, 0.729, 0.101 );
bc( 0.729, 0.100, 0.728, 0.0994, 0.726, 0.0994 );
bc( 0.724, 0.0994, 0.722, 0.100, 0.722, 0.101 );
pt( 7 );
op();
mt( 0.643, 0.109 );
bc( 0.643, 0.111, 0.644, 0.112, 0.646, 0.112 );
bc( 0.648, 0.112, 0.650, 0.111, 0.650, 0.109 );
bc( 0.650, 0.108, 0.650, 0.103, 0.650, 0.101 );
bc( 0.650, 0.100, 0.648, 0.0994, 0.646, 0.0994 );
bc( 0.644, 0.0994, 0.643, 0.100, 0.643, 0.101 );
pt( 7 );
op();
mt( 0.656, 0.109 );
bc( 0.656, 0.111, 0.658, 0.112, 0.659, 0.112 );
bc( 0.661, 0.112, 0.663, 0.111, 0.663, 0.109 );
bc( 0.663, 0.108, 0.663, 0.103, 0.663, 0.101 );
bc( 0.663, 0.100, 0.661, 0.0994, 0.659, 0.0994 );
bc( 0.657, 0.0994, 0.656, 0.100, 0.656, 0.101 );
pt( 7 );
op();
mt( 0.669, 0.109 );
bc( 0.669, 0.111, 0.671, 0.112, 0.673, 0.112 );
bc( 0.675, 0.112, 0.676, 0.111, 0.676, 0.109 );
bc( 0.676, 0.108, 0.676, 0.103, 0.676, 0.101 );
bc( 0.676, 0.100, 0.675, 0.0994, 0.673, 0.0994 );
bc( 0.671, 0.0994, 0.669, 0.100, 0.669, 0.101 );
pt( 7 );
op();
mt( 0.683, 0.109 );
bc( 0.683, 0.111, 0.684, 0.112, 0.686, 0.112 );
bc( 0.688, 0.112, 0.690, 0.111, 0.690, 0.109 );
bc( 0.690, 0.108, 0.690, 0.103, 0.690, 0.101 );
bc( 0.690, 0.100, 0.688, 0.0994, 0.686, 0.0994 );
bc( 0.684, 0.0994, 0.683, 0.100, 0.683, 0.101 );
pt( 7 );
op();
mt( 0.603, 0.109 );
bc( 0.603, 0.111, 0.604, 0.112, 0.606, 0.112 );
bc( 0.608, 0.112, 0.610, 0.111, 0.610, 0.109 );
bc( 0.610, 0.108, 0.610, 0.103, 0.610, 0.101 );
bc( 0.610, 0.100, 0.608, 0.0994, 0.606, 0.0994 );
bc( 0.604, 0.0994, 0.603, 0.100, 0.603, 0.101 );
pt( 7 );
op();
mt( 0.696, 0.109 );
bc( 0.696, 0.111, 0.698, 0.112, 0.699, 0.112 );
bc( 0.701, 0.112, 0.703, 0.111, 0.703, 0.109 );
bc( 0.703, 0.108, 0.703, 0.103, 0.703, 0.101 );
bc( 0.703, 0.100, 0.701, 0.0994, 0.699, 0.0994 );
bc( 0.697, 0.0994, 0.696, 0.100, 0.696, 0.101 );
pt( 7 );
op();
mt( 0.418, 0.109 );
bc( 0.418, 0.111, 0.419, 0.112, 0.421, 0.112 );
bc( 0.423, 0.112, 0.425, 0.111, 0.425, 0.109 );
bc( 0.425, 0.108, 0.425, 0.103, 0.425, 0.101 );
bc( 0.425, 0.100, 0.423, 0.0994, 0.421, 0.0994 );
bc( 0.419, 0.0994, 0.418, 0.100, 0.418, 0.101 );
pt( 7 );
op();
mt( 0.511, 0.109 );
bc( 0.511, 0.111, 0.512, 0.112, 0.514, 0.112 );
bc( 0.516, 0.112, 0.518, 0.111, 0.518, 0.109 );
bc( 0.518, 0.108, 0.518, 0.103, 0.518, 0.101 );
bc( 0.518, 0.100, 0.516, 0.0994, 0.514, 0.0994 );
bc( 0.512, 0.0994, 0.511, 0.100, 0.511, 0.101 );
pt( 7 );
op();
mt( 0.431, 0.109 );
bc( 0.431, 0.111, 0.433, 0.112, 0.434, 0.112 );
bc( 0.436, 0.112, 0.438, 0.111, 0.438, 0.109 );
bc( 0.438, 0.108, 0.438, 0.103, 0.438, 0.101 );
bc( 0.438, 0.100, 0.436, 0.0994, 0.434, 0.0994 );
bc( 0.433, 0.0994, 0.431, 0.100, 0.431, 0.101 );
pt( 7 );
op();
mt( 0.524, 0.109 );
bc( 0.524, 0.111, 0.525, 0.112, 0.527, 0.112 );
bc( 0.529, 0.112, 0.531, 0.111, 0.531, 0.109 );
bc( 0.531, 0.108, 0.531, 0.103, 0.531, 0.101 );
bc( 0.531, 0.100, 0.529, 0.0994, 0.527, 0.0994 );
bc( 0.525, 0.0994, 0.524, 0.100, 0.524, 0.101 );
pt( 7 );
op();
mt( 0.444, 0.109 );
bc( 0.444, 0.111, 0.446, 0.112, 0.448, 0.112 );
bc( 0.450, 0.112, 0.451, 0.111, 0.451, 0.109 );
bc( 0.451, 0.108, 0.451, 0.103, 0.451, 0.101 );
bc( 0.451, 0.100, 0.450, 0.0994, 0.448, 0.0994 );
bc( 0.446, 0.0994, 0.444, 0.100, 0.444, 0.101 );
pt( 7 );
op();
mt( 0.537, 0.109 );
bc( 0.537, 0.111, 0.539, 0.112, 0.540, 0.112 );
bc( 0.542, 0.112, 0.544, 0.111, 0.544, 0.109 );
bc( 0.544, 0.108, 0.544, 0.103, 0.544, 0.101 );
bc( 0.544, 0.100, 0.542, 0.0994, 0.540, 0.0994 );
bc( 0.538, 0.0994, 0.537, 0.100, 0.537, 0.101 );
pt( 7 );
op();
mt( 0.458, 0.109 );
bc( 0.458, 0.111, 0.459, 0.112, 0.461, 0.112 );
bc( 0.463, 0.112, 0.465, 0.111, 0.465, 0.109 );
bc( 0.465, 0.108, 0.465, 0.103, 0.465, 0.101 );
bc( 0.465, 0.100, 0.463, 0.0994, 0.461, 0.0994 );
bc( 0.459, 0.0994, 0.458, 0.100, 0.458, 0.101 );
pt( 7 );
op();
mt( 0.550, 0.109 );
bc( 0.550, 0.111, 0.552, 0.112, 0.554, 0.112 );
bc( 0.555, 0.112, 0.557, 0.111, 0.557, 0.109 );
bc( 0.557, 0.108, 0.557, 0.103, 0.557, 0.101 );
bc( 0.557, 0.100, 0.555, 0.0994, 0.554, 0.0994 );
bc( 0.552, 0.0994, 0.550, 0.100, 0.550, 0.101 );
pt( 7 );
op();
mt( 0.471, 0.109 );
bc( 0.471, 0.111, 0.473, 0.112, 0.474, 0.112 );
bc( 0.476, 0.112, 0.478, 0.111, 0.478, 0.109 );
bc( 0.478, 0.108, 0.478, 0.103, 0.478, 0.101 );
bc( 0.478, 0.100, 0.476, 0.0994, 0.474, 0.0994 );
bc( 0.472, 0.0994, 0.471, 0.100, 0.471, 0.101 );
pt( 7 );
op();
mt( 0.563, 0.109 );
bc( 0.563, 0.111, 0.565, 0.112, 0.567, 0.112 );
bc( 0.568, 0.112, 0.570, 0.111, 0.570, 0.109 );
bc( 0.570, 0.108, 0.570, 0.103, 0.570, 0.101 );
bc( 0.570, 0.100, 0.569, 0.0994, 0.567, 0.0994 );
bc( 0.565, 0.0994, 0.563, 0.100, 0.563, 0.101 );
pt( 7 );
op();
mt( 0.484, 0.109 );
bc( 0.484, 0.111, 0.486, 0.112, 0.488, 0.112 );
bc( 0.490, 0.112, 0.491, 0.111, 0.491, 0.109 );
bc( 0.491, 0.108, 0.491, 0.103, 0.491, 0.101 );
bc( 0.491, 0.100, 0.490, 0.0994, 0.488, 0.0994 );
bc( 0.486, 0.0994, 0.484, 0.100, 0.484, 0.101 );
pt( 7 );
op();
mt( 0.576, 0.109 );
bc( 0.576, 0.111, 0.578, 0.112, 0.580, 0.112 );
bc( 0.582, 0.112, 0.583, 0.111, 0.583, 0.109 );
bc( 0.583, 0.108, 0.583, 0.103, 0.583, 0.101 );
bc( 0.583, 0.100, 0.582, 0.0994, 0.580, 0.0994 );
bc( 0.578, 0.0994, 0.576, 0.100, 0.576, 0.101 );
pt( 7 );
op();
mt( 0.590, 0.109 );
bc( 0.590, 0.111, 0.591, 0.112, 0.593, 0.112 );
bc( 0.595, 0.112, 0.596, 0.111, 0.597, 0.109 );
bc( 0.597, 0.108, 0.596, 0.103, 0.596, 0.101 );
bc( 0.596, 0.100, 0.595, 0.0994, 0.593, 0.0994 );
bc( 0.591, 0.0994, 0.590, 0.100, 0.590, 0.101 );
pt( 7 );
op();
mt( 0.404, 0.109 );
bc( 0.404, 0.111, 0.406, 0.112, 0.408, 0.112 );
bc( 0.410, 0.112, 0.411, 0.111, 0.411, 0.109 );
bc( 0.411, 0.108, 0.411, 0.103, 0.411, 0.101 );
bc( 0.411, 0.100, 0.410, 0.0994, 0.408, 0.0994 );
bc( 0.406, 0.0994, 0.404, 0.100, 0.404, 0.101 );
pt( 7 );
op();
mt( 0.498, 0.109 );
bc( 0.498, 0.111, 0.499, 0.112, 0.501, 0.112 );
bc( 0.503, 0.112, 0.504, 0.111, 0.504, 0.109 );
bc( 0.504, 0.108, 0.504, 0.103, 0.504, 0.101 );
bc( 0.504, 0.100, 0.503, 0.0994, 0.501, 0.0994 );
bc( 0.499, 0.0994, 0.498, 0.100, 0.498, 0.101 );
pt( 7 );
op();
mt( 0.172, 0.109 );
bc( 0.172, 0.111, 0.174, 0.112, 0.175, 0.112 );
bc( 0.177, 0.112, 0.179, 0.111, 0.179, 0.109 );
bc( 0.179, 0.108, 0.179, 0.103, 0.179, 0.101 );
bc( 0.179, 0.100, 0.177, 0.0994, 0.175, 0.0994 );
bc( 0.174, 0.0994, 0.172, 0.100, 0.172, 0.101 );
pt( 7 );
op();
mt( 0.265, 0.109 );
bc( 0.265, 0.111, 0.267, 0.112, 0.269, 0.112 );
bc( 0.270, 0.112, 0.272, 0.111, 0.272, 0.109 );
bc( 0.272, 0.108, 0.272, 0.103, 0.272, 0.101 );
bc( 0.272, 0.100, 0.271, 0.0994, 0.269, 0.0994 );
bc( 0.267, 0.0994, 0.265, 0.100, 0.265, 0.101 );
pt( 7 );
op();
mt( 0.185, 0.109 );
bc( 0.185, 0.111, 0.187, 0.112, 0.189, 0.112 );
bc( 0.191, 0.112, 0.192, 0.111, 0.192, 0.109 );
bc( 0.192, 0.108, 0.192, 0.103, 0.192, 0.101 );
bc( 0.192, 0.100, 0.191, 0.0994, 0.189, 0.0994 );
bc( 0.187, 0.0994, 0.185, 0.100, 0.185, 0.101 );
pt( 7 );
op();
mt( 0.278, 0.109 );
bc( 0.278, 0.111, 0.280, 0.112, 0.282, 0.112 );
bc( 0.284, 0.112, 0.285, 0.111, 0.285, 0.109 );
bc( 0.285, 0.108, 0.285, 0.103, 0.285, 0.101 );
bc( 0.285, 0.100, 0.284, 0.0994, 0.282, 0.0994 );
bc( 0.280, 0.0994, 0.278, 0.100, 0.278, 0.101 );
pt( 7 );
op();
mt( 0.199, 0.109 );
bc( 0.199, 0.111, 0.200, 0.112, 0.202, 0.112 );
bc( 0.204, 0.112, 0.206, 0.111, 0.206, 0.109 );
bc( 0.206, 0.108, 0.206, 0.103, 0.206, 0.101 );
bc( 0.206, 0.100, 0.204, 0.0994, 0.202, 0.0994 );
bc( 0.200, 0.0994, 0.199, 0.100, 0.199, 0.101 );
pt( 7 );
op();
mt( 0.292, 0.109 );
bc( 0.292, 0.111, 0.293, 0.112, 0.295, 0.112 );
bc( 0.297, 0.112, 0.298, 0.111, 0.298, 0.109 );
bc( 0.298, 0.108, 0.298, 0.103, 0.298, 0.101 );
bc( 0.298, 0.100, 0.297, 0.0994, 0.295, 0.0994 );
bc( 0.293, 0.0994, 0.292, 0.100, 0.292, 0.101 );
pt( 7 );
op();
mt( 0.212, 0.109 );
bc( 0.212, 0.111, 0.214, 0.112, 0.215, 0.112 );
bc( 0.217, 0.112, 0.219, 0.111, 0.219, 0.109 );
bc( 0.219, 0.108, 0.219, 0.103, 0.219, 0.101 );
bc( 0.219, 0.100, 0.217, 0.0994, 0.215, 0.0994 );
bc( 0.214, 0.0994, 0.212, 0.100, 0.212, 0.101 );
pt( 7 );
op();
mt( 0.305, 0.109 );
bc( 0.305, 0.111, 0.306, 0.112, 0.308, 0.112 );
bc( 0.310, 0.112, 0.312, 0.111, 0.312, 0.109 );
bc( 0.312, 0.108, 0.312, 0.103, 0.312, 0.101 );
bc( 0.312, 0.100, 0.310, 0.0994, 0.308, 0.0994 );
bc( 0.306, 0.0994, 0.305, 0.100, 0.305, 0.101 );
pt( 7 );
op();
mt( 0.225, 0.109 );
bc( 0.225, 0.111, 0.227, 0.112, 0.229, 0.112 );
bc( 0.231, 0.112, 0.232, 0.111, 0.232, 0.109 );
bc( 0.232, 0.108, 0.232, 0.103, 0.232, 0.101 );
bc( 0.232, 0.100, 0.231, 0.0994, 0.229, 0.0994 );
bc( 0.227, 0.0994, 0.225, 0.100, 0.225, 0.101 );
pt( 7 );
op();
mt( 0.318, 0.109 );
bc( 0.318, 0.111, 0.320, 0.112, 0.321, 0.112 );
bc( 0.323, 0.112, 0.325, 0.111, 0.325, 0.109 );
bc( 0.325, 0.108, 0.325, 0.103, 0.325, 0.101 );
bc( 0.325, 0.100, 0.323, 0.0994, 0.321, 0.0994 );
bc( 0.319, 0.0994, 0.318, 0.100, 0.318, 0.101 );
pt( 7 );
op();
mt( 0.239, 0.109 );
bc( 0.239, 0.111, 0.240, 0.112, 0.242, 0.112 );
bc( 0.244, 0.112, 0.246, 0.111, 0.246, 0.109 );
bc( 0.246, 0.108, 0.246, 0.103, 0.246, 0.101 );
bc( 0.246, 0.100, 0.244, 0.0994, 0.242, 0.0994 );
bc( 0.240, 0.0994, 0.239, 0.100, 0.239, 0.101 );
pt( 7 );
op();
mt( 0.331, 0.109 );
bc( 0.331, 0.111, 0.333, 0.112, 0.334, 0.112 );
bc( 0.336, 0.112, 0.338, 0.111, 0.338, 0.109 );
bc( 0.338, 0.108, 0.338, 0.103, 0.338, 0.101 );
bc( 0.338, 0.100, 0.336, 0.0994, 0.334, 0.0994 );
bc( 0.333, 0.0994, 0.331, 0.100, 0.331, 0.101 );
pt( 7 );
op();
mt( 0.344, 0.109 );
bc( 0.344, 0.111, 0.346, 0.112, 0.348, 0.112 );
bc( 0.349, 0.112, 0.351, 0.111, 0.351, 0.109 );
bc( 0.351, 0.108, 0.351, 0.103, 0.351, 0.101 );
bc( 0.351, 0.100, 0.350, 0.0994, 0.348, 0.0994 );
bc( 0.346, 0.0994, 0.344, 0.100, 0.344, 0.101 );
pt( 7 );
po( [ 0.0666, 0.0813, 0.0826, 0.0825, 0.0786, 0.0666], [ 0.0996, 0.0996, 0.101, 0.111, 0.115, 0.116], 7 );
po( [ 0.0576, 0.0495, 0.0578, 0.0649, 0.0649], [ 0.0998, 0.103, 0.115, 0.115, 0.0998], 7 );
po( [ 0.0475, 0.0559, 0.0475, 0.0358], [ 0.103, 0.115, 0.115, 0.103], 7 );
op();
mt( 0.159, 0.109 );
bc( 0.159, 0.111, 0.160, 0.112, 0.162, 0.112 );
bc( 0.164, 0.112, 0.166, 0.111, 0.166, 0.109 );
bc( 0.166, 0.108, 0.166, 0.103, 0.166, 0.101 );
bc( 0.166, 0.100, 0.164, 0.0994, 0.162, 0.0994 );
bc( 0.160, 0.0994, 0.159, 0.100, 0.159, 0.101 );
pt( 7 );
op();
mt( 0.252, 0.109 );
bc( 0.252, 0.111, 0.254, 0.112, 0.255, 0.112 );
bc( 0.257, 0.112, 0.259, 0.111, 0.259, 0.109 );
bc( 0.259, 0.108, 0.259, 0.103, 0.259, 0.101 );
bc( 0.259, 0.100, 0.257, 0.0994, 0.255, 0.0994 );
bc( 0.253, 0.0994, 0.252, 0.100, 0.252, 0.101 );
pt( 7 );
op();
mt( 0.334, 0.0677 );
bc( 0.376, 0.0760, 0.413, 0.0778, 0.448, 0.0851 );
bc( 0.487, 0.0932, 0.527, 0.0994, 0.536, 0.101 );
bc( 0.541, 0.102, 0.579, 0.105, 0.580, 0.102 );
bc( 0.581, 0.100, 0.490, 0.0752, 0.490, 0.0752 );
lt( 0.477, 0.0638 );
bc( 0.439, 0.0633, 0.373, 0.0636, 0.363, 0.0635 );
bc( 0.353, 0.0633, 0.335, 0.0634, 0.334, 0.0677 );
pt( 7 );
op();
mt( 0.853, 0.107 );
bc( 0.853, 0.104, 0.859, 0.100, 0.867, 0.100 );
bc( 0.876, 0.0998, 0.906, 0.102, 0.921, 0.104 );
bc( 0.937, 0.106, 0.948, 0.105, 0.950, 0.106 );
lt( 0.979, 0.126 );
bc( 0.979, 0.126, 0.957, 0.128, 0.949, 0.127 );
bc( 0.945, 0.127, 0.922, 0.121, 0.901, 0.116 );
bc( 0.877, 0.112, 0.853, 0.109, 0.853, 0.107 );
pt( 7 );
op();
mt( 0.468, 0.0799 );
bc( 0.468, 0.0778, 0.503, 0.0748, 0.523, 0.0753 );
bc( 0.542, 0.0758, 0.552, 0.0775, 0.551, 0.0796 );
bc( 0.551, 0.0817, 0.538, 0.0827, 0.529, 0.0827 );
bc( 0.521, 0.0827, 0.525, 0.0828, 0.518, 0.0827 );
bc( 0.511, 0.0826, 0.506, 0.0810, 0.502, 0.0809 );
lt( 0.468, 0.0799 );
pt( 7 );
op();
mt( 0.442, 0.0731 );
bc( 0.442, 0.0711, 0.476, 0.0680, 0.496, 0.0685 );
bc( 0.515, 0.0690, 0.521, 0.0706, 0.521, 0.0727 );
bc( 0.520, 0.0748, 0.511, 0.0759, 0.502, 0.0759 );
bc( 0.494, 0.0759, 0.498, 0.0760, 0.491, 0.0759 );
bc( 0.484, 0.0759, 0.479, 0.0742, 0.476, 0.0741 );
lt( 0.442, 0.0731 );
pt( 7 );
op();
mt( 0.421, 0.0633 );
bc( 0.421, 0.0613, 0.456, 0.0582, 0.475, 0.0587 );
bc( 0.495, 0.0592, 0.501, 0.0608, 0.500, 0.0629 );
bc( 0.500, 0.0650, 0.490, 0.0661, 0.482, 0.0661 );
bc( 0.473, 0.0661, 0.478, 0.0662, 0.471, 0.0661 );
bc( 0.464, 0.0661, 0.459, 0.0644, 0.455, 0.0643 );
lt( 0.421, 0.0633 );
pt( 7 );
rc( 0.361, 0.115, 0.0151, -0.0297, 3 );
rc( 0.385, 0.115, 0.0151, -0.0297, 3 );
rc( 0.769, 0.128, 0.0261, -0.0512, 3 );
rc( 0.113, 0.130, 0.0261, -0.0512, 3 );
op();
mt( 0.365, 0.109 );
bc( 0.365, 0.111, 0.367, 0.112, 0.368, 0.112 );
bc( 0.370, 0.112, 0.372, 0.111, 0.372, 0.109 );
bc( 0.372, 0.108, 0.372, 0.103, 0.372, 0.101 );
bc( 0.372, 0.100, 0.370, 0.0994, 0.368, 0.0994 );
bc( 0.367, 0.0994, 0.365, 0.100, 0.365, 0.101 );
lt( 0.365, 0.109 );
pt( 7 );
op();
mt( 0.389, 0.109 );
bc( 0.389, 0.111, 0.391, 0.112, 0.392, 0.112 );
bc( 0.394, 0.112, 0.396, 0.111, 0.396, 0.109 );
bc( 0.396, 0.108, 0.396, 0.103, 0.396, 0.101 );
bc( 0.396, 0.100, 0.394, 0.0994, 0.392, 0.0994 );
bc( 0.390, 0.0994, 0.389, 0.100, 0.389, 0.101 );
lt( 0.389, 0.109 );
pt( 7 );
g.SetAlpha( 1 );
g.ResetTrans();
if (this.ShowForces || this.ShowSpeed || this.ShowAccel || this.ShowCoordSys) {
this.DrawForces( g, vsDisplacementVect );
}
},
DrawForces: function( g, vsDisplacementVect ) {
// depending on the vertical speed the airplane is displaced up or down
// by vsDisplacementVect. Force vectors are given in ECEF coordinates,
// so the arrows have to be rotated to surfaceAngle+pitch angle.
function drawArrow( from, vect, oldVect, scale, color, type ) {
// use oldVect = null if no old vector is available
type = xDefNum( type, 0 );
// compute from and to arrow coordinates
// rotate and move start arrow position (from) relative to CenterOfGravity
from = V2.Add( me.CenterOfGravity, V2.Rotate( from, pitch+surfaceAngle ) );
var to = V2.Add( from, V2.Scale( vect, scale ) );
// calculate arrow symbol size depending on vect length
var symbolSize = 10;
var arrowLength = g.WinToPixX( V2.Length( V2.Sub( to, from ) ) );
if (arrowLength < 2*symbolSize) {
symbolSize *= arrowLength/(2*symbolSize);
if (symbolSize < 4 ) symbolSize = 4;
}
// draw arrow depending on type
var sym = 'Arrow1';
var lineWidth = 1.5;
if (type == 1) {
sym = 'Arrow2';
lineWidth = 1;
}
g.SetMarkerAttr( sym, symbolSize, color, color, lineWidth );
g.Arrow( from, to, 1, 3 ); // 1 = end arrow, 3 = border + fill
// draw trend arrow
if (me.ShowTrend && oldVect) {
// compute trend magnitude from vect and oldVect
// trend = delta force / delta time = how fast does the force change
var currMagnitude = V2.Length(vect);
var oldMagnitude = V2.Length(oldVect);
trend = (currMagnitude - oldMagnitude) / me.Sim.DeltaTime;
if (Math.abs(trend) > 10 && currMagnitude > 100) {
var trendArrowLength = 1.2 * g.PixToWinX(symbolSize);
var trendDir = V2.Norm( V2.Sub( to, from ) );
var trendPos1 = V2.Add( to, V2.Scale( trendDir, 0.5 * trendArrowLength ) );
var trendPos2 = V2.Add( to, V2.Scale( trendDir, 1.5 * trendArrowLength ) );
symbolSize *= 0.75;
if (symbolSize < 4) symbolSize = 4;
g.SetMarkerAttr( sym, symbolSize, color, color, 1 );
if (trend > 0) {
g.Arrow( trendPos1, trendPos2, 5, 3 ); // 5 = end arrow + no line, 3 = border + fill
} else {
g.Arrow( trendPos2, trendPos1, 5, 3 ); // 5 = end arrow + no line, 3 = border + fill
}
}
}
}
var me = this;
var pitch = this.Pitch;
var surfaceAngle = V2.Angle( [0,1], this.Zsurface );
var oldAutoScalePix = g.SetAutoScalePix( true );
g.TransMove( -this.CenterOfGravity[0], this.CenterOfGravity[1] );
g.TransMove( vsDisplacementVect );
// draw coordinate system axes
if (this.ShowCoordSys) {
if (this.ShowSpeed || this.ShowForces || this.ShowAccel) {
g.SetAlpha( 0.5 );
}
drawArrow( this.CenterOfLift, this.Xsurface, null, 35, 'gray', 1 );
drawArrow( this.CenterOfLift, this.Zsurface, null, 25, 'gray', 1 );
drawArrow( this.CenterOfLift, this.Xaircraft, null, 32, 'brown', 1 );
g.SetAlpha( 1 );
}
if (this.ShowSpeed) {
// compute arrow start pos in front of the aircraft nose
// and length proportional to TAS
var speedScale = 0.3 / this.MaxTAS * this.AircraftLength;
var arrowStart = 0.45 * this.AircraftLength;
var from = V2.Add( this.CenterOfGravity, V2.Scale( this.SpeedDir, arrowStart ) );
var to = V2.Add( this.CenterOfGravity, V2.Scale( this.SpeedDir, arrowStart + this.TAS * speedScale ) );
// draw the arrow lines (red if limits are exceeded)
var color = 'green';
if (this.LimitExceeded != '') color = 'red';
g.SetLineAttr( color, 5 );
g.Line( from, to );
g.SetLineAttr( 'white', 3 )
g.Line( from, to );
// displace the arrow tip
to = V2.Add( to, V2.Scale( this.SpeedDir, g.PixToWinX(8) ) );
// draw arrow tip
g.SetAreaAttr( 'white', color, 1 );
g.SetMarkerSize( 12 );
g.SetMarkerSymbol( 'ArrowRight' );
g.Arrow( from, to, 5, 3 ); // 5 = end arrow + no line, 3 = border + fill
// draw speed trend arrow
// compute trend magnitude from vect and oldVect
// trend = delta TAS / delta time = how fast does the speed change
var trend = (this.TAS - this.TASold) / this.Sim.DeltaTime;
if (this.ShowTrend && Math.abs(trend) > 0.01) {
var trendArrowLength = g.PixToWinX(12);
var trendDir = V2.Norm( V2.Sub( to, from ) );
var trendPos1 = V2.Add( to, V2.Scale( trendDir, 0.5 * trendArrowLength ) );
var trendPos2 = V2.Add( to, V2.Scale( trendDir, 1.5 * trendArrowLength ) );
g.SetMarkerSize( 9 );
if (trend > 0) {
g.Arrow( trendPos1, trendPos2, 5, 3 ); // 5 = end arrow + no line, 3 = border + fill
} else {
g.Arrow( trendPos2, trendPos1, 5, 3 ); // 5 = end arrow + no line, 3 = border + fill
}
}
}
if (this.ShowAccel) {
var accel = V2.Length( this.State.Accel );
if (accel > 0.005) {
// compute arrow start pos and length proportional to acceleration
accelDir = V2.Norm( this.State.Accel );
var scale = Math.sqrt( accel / 10 ) * this.AircraftLength / 5;
// compute a multiplicator curve depending on accelDir in range 0..1
var mult = 0.5 * Math.cos( 2 * V2.Angle( this.Xaircraft, accelDir ) ) + 0.5;
var n = 3; // arrow in Z axis is 1/n times nearer than in X axis
var arrowStart = 0.57 * this.AircraftLength * (mult + n - 1) / n;
var from = V2.Add( this.CenterOfGravity, V2.Scale( accelDir, arrowStart ) );
var to = V2.Add( this.CenterOfGravity, V2.Scale( accelDir, arrowStart + scale ) );
// draw the arrow lines
var color = 'red';
g.SetLineAttr( color, 3 );
g.Line( from, to );
g.SetLineAttr( 'white', 1 )
g.Line( from, to );
// displace the arrow tip
to = V2.Add( to, V2.Scale( accelDir, g.PixToWinX(9) ) );
// draw arrow tip
g.SetAreaAttr( 'white', color, 1 );
g.SetMarkerSize( 9 );
g.SetMarkerSymbol( 'ArrowRight' );
g.Arrow( from, to, 5, 3 ); // 5 = end arrow + no line, 3 = border + fill
}
}
if (this.ShowForces) {
var fScale = 1.5/this.MaxMass;
var hfScale = fScale;
if (this.ShowFxTimes5) {
hfScale *= 5; // horizontal forces are scaled more than vertical forces
}
drawArrow( this.CenterOfLift, this.FLift, this.FLiftOld, fScale, 'blue' );
drawArrow( this.CenterOfGravity, this.FWeight, null, fScale, 'green' );
drawArrow( this.ElevatorPos, this.FTrim, this.FTrimOld, fScale, 'blue' );
drawArrow( this.CenterOfLift, this.FDrag, this.FDragOld, hfScale, 'red' );
drawArrow( this.EnginePos, this.FThrust, this.FThrustOld, hfScale, 'orange');
}
g.SetAutoScalePix( oldAutoScalePix );
g.ResetTrans();
},
DrawElevatorTrimIndication: function( g, x, y, w, h ) {
// x,y,w,h as viewport coordinates
g.SetViewport( x, y, w, h, false, true );
g.SetWindowWH( -2, -3.5, 4, 7 );
var oldLineCap = g.LineCap;
// elevator indication
// elevator scale
var xPos = -1;
g.SetLineCap( 'square' );
g.SetLineAttr( 'black', g.ScalePix(2) );
g.Line( xPos, -2, xPos, 2 );
g.Line( xPos, 2, xPos-0.2, 2 );
g.Line( xPos, -2, xPos-0.2, -2 );
var yPos = 4 * (rad(-10)-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
g.Line( xPos, yPos, xPos-0.1, yPos );
var yPos = 4 * (-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
g.Line( xPos, yPos, xPos-0.2, yPos );
var yPos = 4 * (rad(10)-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
g.Line( xPos, yPos, xPos-0.1, yPos );
var yPos = 4 * (rad(20)-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
g.Line( xPos, yPos, xPos-0.1, yPos );
// pitch modulation indication due to pitch rate damping
if (this.PitchDamping) {
var yPos = 4 * (this.ElevatorAngle-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
var pitchMod = yPos - Math.atan( 500 * this.PitchElevMod ) * 4 / Math.PI;
if (pitchMod > 2) pitchMod = 2;
if (pitchMod < -2) pitchMod = -2;
g.SetLineCap( oldLineCap );
g.SetLineAttr( 'lightgreen', g.ScalePix(2) );
g.Line( xPos, yPos, xPos, pitchMod );
}
// elevator angle indicator pointer
var yPos = 4 * (this.ElevatorAngle-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
g.SetMarkerAttr( 'ArrowLeft', g.WinToPixX(0.4), 'black', 'lightgreen', 1 );
g.Marker( xPos+0.05, yPos, 3 );
// elevator command indicator pointer
var yPos = 4 * (this.ElevatorAngleCommand-this.MinElevatorAngle) / (this.MaxElevatorAngle-this.MinElevatorAngle) - 2;
g.SetMarkerAttr( 'ArrowRight', g.WinToPixX(0.4), 'black', 'Magenta', 1 );
g.Marker( xPos-0.05, yPos, 3 );
// elevator angle value
yPos = -2.75;
g.SetAreaAttr( 'black', 'black', 1 );
g.RectWH( xPos-0.75, yPos-0.5, 1.5, 1, 3 );
g.SetTextAttrS( 'Arial', g.WinToPixY(1), 'lightgreen', 'C' );
g.Text( this.FormatValue( deg(this.ElevatorAngle), '', 2 ), xPos, -2.8 );
// trim indication
// trim scale
var xPos = 1;
g.SetLineCap( 'square' );
g.SetLineAttr( 'black', g.ScalePix(2) );
g.Line( xPos, -2, xPos, 2 );
g.Line( xPos, -2, xPos-0.2, -2 );
g.Line( xPos, -1, xPos-0.1, -1 );
g.Line( xPos, 0, xPos-0.2, 0 );
g.Line( xPos, 1, xPos-0.1, 1 );
g.Line( xPos, 2, xPos-0.2, 2 );
// trim angle indicator pointer
var yPos = 4 * (this.TrimAngle-this.MinTrimAngle) / (this.MaxTrimAngle-this.MinTrimAngle) - 2;
g.SetMarkerAttr( 'ArrowLeft', g.WinToPixX(0.4), 'black', 'lightgreen', 1 );
g.Marker( xPos+0.05, yPos, 3 );
// trim command indicator pointer
var yPos = 4 * (this.TrimAngleCommand-this.MinTrimAngle) / (this.MaxTrimAngle-this.MinTrimAngle) - 2;
g.SetMarkerAttr( 'ArrowRight', g.WinToPixX(0.4), 'black', 'Magenta', 1 );
g.Marker( xPos-0.05, yPos, 3 );
// trim angle value
yPos = -2.75;
g.SetAreaAttr( 'black', 'black', 1 );
g.RectWH( xPos-0.75, yPos-0.5, 1.5, 1, 3 );
g.SetTextAttrS( 'Arial', g.WinToPixY(1), 'lightgreen', 'C' );
g.Text( this.FormatValue( deg(this.TrimAngle), '', 2 ), xPos, -2.8 );
// labels
g.SetTextAttrS( 'Arial', g.WinToPixY(1), 'black', 'bC' );
g.Text( 'ELEV', -1, 2.75 );
g.Text( 'TRIM', 1, 2.75 );
g.SetTextAttrS( 'Arial', g.WinToPixY(0.9), 'black', 'bC' );
g.Text( 'UP', 0, 1.75 );
g.Text( 'DW', 0, -1.75 );
g.SetLineCap( oldLineCap );
},
DrawEngineIndication: function( g, x, y, w, h ) {
// x,y,w,h as viewport coordinates
g.SetViewport( x, y, w, h, false, true );
g.SetWindowWH( -3, -2.5, 6, 6 );
var oldLineCap = g.LineCap;
// arc scale
var r = 2;
g.SetLineAttr( 'black', g.ScalePix(2) );
g.Arc( 0, 0, r, rad(36), rad(216), 1 );
g.SetLineAttr( 'red', g.ScalePix(2) );
g.Arc( 0, 0, r, 0, rad(36), 1 );
function tic(a) {
var c = Math.cos(rad(a));
var s = Math.sin(rad(a));
g.Line( r2*c, r2*s, r*c, r*s );
}
function val(a,v) {
var c = Math.cos(rad(a));
var s = Math.sin(rad(a));
g.Text( v, r*c, r*s );
}
var r2 = 0.85 * r;
tic( 0 );
g.SetLineAttr( 'black', g.ScalePix(2) );
tic( 36 );
tic( 72 );
tic( 108 );
tic( 144 );
tic( 216 );
r = 1.35;
g.SetTextAttrS( 'Arial', g.WinToPixX(0.5), 'black', 'C' );
val( 36, 10 );
val( 72, 8 );
val( 108, 6 );
val( 144, 4 );
// N1 pointer
var a = 216 - this.N1 * 180;
r = 2.2;
r2 = 0;
g.SetLineAttr( 'black', g.ScalePix(4) );
tic( a );
g.SetLineAttr( 'lightgreen', g.ScalePix(2) );
tic( a );
// N1 command indicator
a = 216 - this.N1Command * 180;
r = 2.4;
var c = Math.cos(rad(a));
var s = Math.sin(rad(a));
g.SetMarkerAttr( 'Circle', g.WinToPixX(0.3), 'black', 'magenta', g.ScalePix(1) );
g.Marker( r*c, r*s, 3 );
// N1 value
yPos = -1.95;
g.SetAreaAttr( 'black', 'black', 1 );
g.RectWH( -1.2, yPos-0.35, 2.4, 0.95, 2.7 );
g.SetTextAttrS( 'Arial', g.WinToPixY(0.86), 'lightgreen', 'C' );
g.Text( this.FormatValue( this.N1*100, '', 2 ), 0, yPos+0.055 );
// N1 label
g.SetTextAttrS( 'Arial', g.WinToPixX(0.78), 'black', 'bC' );
g.Text( 'N1', 0, 2.8 );
g.SetLineCap( oldLineCap );
},
DrawPfd: function( g, x, y, w, h ) {
// PFD = primary flight display
// x,y,w,h as viewport coordinates
g.SetViewport( x, y, w, h, false, true );
// PFD background
var bg = g.GetFrameRect();
g.SetAreaAttr( 'black', 'black', 1 );
g.Rect( bg, 3 );
// PFD is rastered with a raster width of dspUnitDiv
var dspUnitDiv = w / 6;
var dspMargin = dspUnitDiv / 15;
// draw Speed Tape
var dspX = x + dspMargin;
var dspY = y + dspMargin;
var dspW = dspUnitDiv - 2 * dspMargin;
var textH = dspW / 3;
var dspH = h - 2 * dspMargin - 1.5 * textH;
this.DrawSpeedTape( g, dspX, dspY, dspW, dspH );
g.SetViewport( x, y, w, h, false, true );
// draw Mach number below speed tape
g.SetTextAttrS( 'Arial', textH, 'white', 'bC' );
var oldTrans = g.SelectTrans( 'viewport' );
var mach = this.Mach.toPrecision(3);
g.Text( ''+mach, dspX + 0.5 * dspW, dspH + 1.1*textH );
g.SelectTrans( oldTrans );
// draw VS Tape (VS = vertical speed)
var dspX = x + 5 * dspUnitDiv + dspMargin;
var dspY = y + dspMargin;
var dspW = dspUnitDiv - 2 * dspMargin;
var textH = dspW / 3;
var dspH = h - 2 * dspMargin - 1.5 * textH;
this.DrawVSTape( g, dspX, dspY, dspW, dspH );
g.SetViewport( x, y, w, h, false, true );
// Altimeter Tape
var dspX = x + 4 * dspUnitDiv + dspMargin;
var dspY = y + dspMargin;
var dspW = dspUnitDiv - 2 * dspMargin;
var textH = dspW / 3;
var dspH = h - 2 * dspMargin - 1.5 * textH;
this.DrawAltTape( g, dspX, dspY, dspW, dspH );
g.SetViewport( x, y, w, h, false, true );
// draw vertical speed VS below Altitude tape
g.SetTextAttrS( 'Arial', textH, 'white', 'bCW' );
var oldTrans = g.SelectTrans( 'viewport' );
var vs = Math.floor( ms_ftmin(this.VS) + 0.5 );
g.Text( 'VS '+vs, dspX, dspH + 1.1*textH );
g.SelectTrans( oldTrans );
// draw attitude indicator (AI)
var dspX = x + dspUnitDiv + dspMargin;
var dspY = y + 0.8 * dspUnitDiv + dspMargin;
var dspW = 3 * dspUnitDiv - 2 * dspMargin;
var dspH = 3 * dspUnitDiv - 2 * dspMargin;
this.DrawAI( g, dspX, dspY, dspW, dspH );
g.SetViewport( x, y, w, h, false, true );
// TAS
var textMargin = g.ScalePix(3);
var oldTrans = g.SelectTrans( 'viewport' );
var textH = dspUnitDiv / 3.5;
var tas = ms_kt(this.TAS);
g.SetTextAttrS( 'Arial', textH, 'white', 'bNW', textMargin );
g.Text( 'TAS '+Math.floor(tas+0.5), dspUnitDiv, 3 );
// Time
var time = NumFormatter.Format( (this.Sim.SimulTime-this.TimeOffset)/3600, 'hms', 4 );
g.SetTextAttrS( 'Arial', textH, 'white', 'NW', textMargin );
g.Text( ''+time, dspUnitDiv, 3+1.3*textH );
// AoA
var AoA = deg(this.AoA);
g.SetTextAttrS( 'Arial', textH, 'white', 'bNE', textMargin );
g.Text( 'AoA '+AoA.toFixed(1)+'°', 4 * dspUnitDiv, 3 );
// Trim Angle
var trim = deg(this.TrimAngle);
g.SetTextAttrS( 'Arial', textH, 'white', 'NE', textMargin );
g.Text( 'Trim '+trim.toFixed(1)+'°', 4 * dspUnitDiv, 3+1.3*textH );
// Temperature
var temp = this.AirTemperature;
var dspY = 0.75 * dspUnitDiv + dspMargin;
var ty = dspY + 3 * dspUnitDiv;
g.SetTextAttrS( 'Arial', textH, 'white', 'NW', textMargin );
g.Text( temp.toFixed(1)+'°C', 1 * dspUnitDiv, ty );
// Pressure
var p = this.AirPressure / 100;
var ty = dspY + 3 * dspUnitDiv + 1.3 * textH;
g.SetTextAttrS( 'Arial', textH, 'white', 'NW', textMargin );
g.Text( p.toFixed(1)+' hPa', 1 * dspUnitDiv, ty );
// N1
var n1 = this.N1 * 100;
var ty = dspY + 3 * dspUnitDiv;
g.SetTextAttrS( 'Arial', textH, 'white', 'NE', textMargin );
g.Text( 'N1 '+n1.toFixed(1)+'%', 4 * dspUnitDiv, ty );
// G-Force
var gAtElevation = this.Gravity( this.Elevation );
var gForce = V2.Add( V2.Scale(this.Zsurface, -gAtElevation), V2.Scale( this.State.Accel, -1 ) );
var gForceSign = V2.ScalarProd( this.Zaircraft, gForce ) > 0 ? -1 : 1;
var gForceMagnitude = gForceSign * V2.Length( gForce ) / this.g;
var ty = dspY + 3 * dspUnitDiv + 1.3 * textH;
g.SetTextAttrS( 'Arial', textH, 'white', 'NE', textMargin );
g.Text( 'G '+gForceMagnitude.toFixed(3), 4 * dspUnitDiv, ty );
// indicate Limit Exceeded
if (this.LimitExceeded != '') {
g.SetTextAttrS( 'Arial', textH, 'red', 'bNC', g.ScalePix(2) );
g.Text( this.LimitExceeded, 5.5 * dspUnitDiv, 3 );
}
g.SelectTrans( oldTrans );
},
DrawSpeedTape: function( g, x, y, w, h ) {
// x,y,w,h as viewport coordinates
g.SetViewport( x, y, w, h, false, true );
var CAS = ms_kt(this.CAS);
// backgroung
var bg = g.GetFrameRect();
g.SetAreaAttr( '#666', '#666', 1 );
g.Rect( bg, 3 );
// draw ticks
var range = 120;
var low = CAS - range / 2;
var high = CAS + range / 2;
var tickSpacing = 10; // kt CAS
g.SetWindowWH( 0, low, 5, high-low );
g.SetLineAttr( 'white', g.ScalePix(2) );
var first = (Math.floor( low / tickSpacing ) - 1 ) * tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 2) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
g.Line( 4, tic, 5, tic );
}
// draw tick values
g.SetTextAttrS( 'Arial', w/4, 'white', 'C' );
var tickSpacing = 20;
var first = (Math.floor( low / tickSpacing ) - 1) * tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 2) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
if (tic >= 0) {
g.Text( ''+(tic), 2.5, tic );
}
}
// draw current value
var textH = w / 3;
var boxH2 = g.PixToWinY( textH );
var boxB = CAS - boxH2;
var boxT = CAS + boxH2;
g.SetTextAttrS( 'Arial', textH, 'white', 'bW', g.ScalePix(2) );
g.SetAreaAttr( 'black', 'white', g.ScalePix(1) );
g.Polygon( [ 0, 3, 4, 3, 0 ], [ boxB, boxB, CAS, boxT, boxT ], 7 ); // 7 = border+fill+close
var CASint = Math.floor( CAS + 0.5 );
g.Text( ''+CASint, 0, CAS );
// draw stall speed bar
var stallSpeed = ms_kt(this.StallSpeed);
g.SetAreaAttr( 'red', 'red', g.ScalePix(1) );
g.Rect( 4.5, 0, 5, stallSpeed, 2 ); // 2 = fill
var maneuvreSpeed = ms_kt(this.LowestManeuverSpeed);
g.Rect( 4.5, 0, 5, maneuvreSpeed, 1 ); // 1 = border
// draw max operating speed bar
var vmo = ms_kt(this.MaxOperatingSpeed);
g.SetAreaAttr( 'red', 'red', g.ScalePix(1) );
g.Rect( 4.5, vmo, 5, 1000 , 3 );
},
DrawVSTape: function( g, x, y, w, h ) {
// x,y,w,h as viewport coordinates
g.SetViewport( x, y+h/10, w, 8/10*h, false, true );
var vs = ms_ftmin(this.VS);
// backgroung
var range = 12000;
var low = -range / 2;
var high = range / 2;
g.SetWindowWH( 0, low, 8, range );
g.SetAreaAttr( '#666', '#666', 1 );
g.Polygon( [ 0, 4, 8, 8, 4, 0 ], [ -high, -high, -high/2, high/2, high, high ], 7 );
// draw ticks
var tickSpacing = 500; // ft
g.SetWindowWH( 0, low+tickSpacing/2, 8, range-tickSpacing );
g.SetLineAttr( 'white', g.ScalePix(2) );
for (var tic = low; tic < high; tic += tickSpacing) {
g.Line( 2.9, tic, 3.9, tic );
}
// draw tick values
g.SetTextAttrS( 'Arial', w/5, 'white', 'E', g.ScalePix(2) );
var tickSpacing = 1000;
for (var tic = low+tickSpacing; tic < high; tic += tickSpacing) {
g.Text( ''+Math.floor(tic/1000), 2.6, tic );
}
// draw needle
var vsDsp = vs;
if (vsDsp < -range/2) vsDsp = -range/2;
if (vsDsp > range/2) vsDsp = range/2;
g.Line( 4, vsDsp, 8, vsDsp/2 );
},
DrawAltTape: function( g, x, y, w, h ) {
// x,y,w,h as viewport coordinates
g.SetViewport( x, y, w, h, false, true );
var Alt = m_ft(this.Altitude);
// backgroung
var bg = g.GetFrameRect();
g.SetAreaAttr( '#666', '#666', 1 );
g.Rect( bg, 3 );
// draw ticks
var range = 1050;
var low = Alt - range / 2;
var high = Alt + range / 2;
var tickSpacing = 100; // ft
g.SetWindowWH( 0, low, 5, high-low );
g.SetLineAttr( 'white', g.ScalePix(2) );
var first = (Math.floor( low / tickSpacing ) - 1)* tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 2) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
g.Line( 0, tic, 1, tic );
}
// draw tick values
g.SetTextAttrS( 'Arial', w/4, 'white', 'E', g.ScalePix(2) );
var tickSpacing = 200;
var first = (Math.floor( low / tickSpacing ) - 1) * tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 2) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
if (tic >= 0) {
g.Text( ''+Math.floor(tic/1), 5, tic );
}
}
// draw current value
g.SetClipping('');
var textH = w / 3;
var boxH2 = g.PixToWinY( textH );
var boxB = Alt - boxH2;
var boxT = Alt + boxH2;
g.SetTextAttrS( 'Arial', textH, 'white', 'bE', g.ScalePix(2) );
g.SetAreaAttr( 'black', 'white', g.ScalePix(1) );
g.Polygon( [ 0, 1, 6, 6, 1 ], [ Alt, boxB, boxB, boxT, boxT ], 7 );
var AltInt = Math.floor( Alt + 0.5 );
g.Text( ''+AltInt, 6, Alt );
},
DrawAI: function( g, x, y, w, h ) {
// x,y,w,h as viewport coordinates
g.SetViewport( x, y, w, h, false, true );
var pitch = deg(this.Pitch);
var range = 45;
var range2 = range / 2;
var low = pitch - range2;
var high = pitch + range2;
g.SetWindowWH( -7, low, 14, high-low );
// draw background (ground and sky depending on pitch angle)
g.SetAreaAttr( 'blue', 'blue', 1 );
g.Rect( -7, -270, 7, -180, 2 );
g.Rect( -7, 0, 7, 180, 2 );
g.SetAreaAttr( 'brown', 'brown', 1 );
g.Rect( -7, -180, 7, 0, 2 );
g.Rect( -7, 180, 7, 270, 2 );
g.SetLineAttr( 'white', g.ScalePix(2) );
g.Line( -7, -180, 7, -180 );
g.Line( -7, 0, 7, 0 );
g.Line( -7, 180, 7, 180 );
// draw ticks
var tickSpacing = 2.5; // deg
g.SetLineAttr( 'white', g.ScalePix(1.5) );
var first = Math.floor( low / tickSpacing ) * tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 1) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
var ticAbs = Math.abs( tic ) / 5;
var rem = ticAbs - Math.floor( ticAbs + 0.001 );
if (rem > 0.001) {
g.Line( -1, tic, 1, tic );
}
}
var tickSpacing = 5; // deg
var first = Math.floor( low / tickSpacing ) * tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 1) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
var ticAbs = Math.abs( tic ) / 10;
var rem = ticAbs - Math.floor( ticAbs + 0.001 );
if (rem > 0.001) {
g.Line( -2, tic, 2, tic );
}
}
var tickSpacing = 10; // deg
var first = Math.floor( low / tickSpacing ) * tickSpacing;
var last = (Math.floor( high / tickSpacing ) + 1) * tickSpacing;
for (var tic = first; tic < last; tic += tickSpacing) {
g.Line( -3, tic, 3, tic );
}
// draw tick values
var tickSpacing = 10;
var first = Math.floor( (low / tickSpacing) - 1 ) * tickSpacing;
var last = Math.floor( (high / tickSpacing) + 2 ) * tickSpacing;
g.SetTextAttrS( 'Arial', w/12, 'white', 'CE', g.ScalePix(4) );
for (var tic = first; tic < last; tic += tickSpacing) {
var ticv = tic;
if (ticv > 90) ticv = 180 - ticv;
if (ticv < -90) ticv = -180 - ticv;
if (ticv != 0) {
g.Text( ''+(Math.floor(ticv)), -3, tic );
}
}
g.SetTextAttrS( 'Arial', w/12, 'white', 'CW', g.ScalePix(4) );
for (var tic = first; tic < last; tic += tickSpacing) {
var ticv = tic;
if (ticv > 90) ticv = 180 - ticv;
if (ticv < -90) ticv = -180 - ticv;
if (ticv != 0) {
g.Text( ''+(Math.floor(ticv)), 3, tic );
}
}
// draw aircraft symbol
var asC = (high + low) / 2;
var asH = (high - low) / 80;
var asW = asH * 14 / (high-low);
var asL = -6;
var asR = -3;
g.SetAreaAttr( 'black', 'white', g.ScalePix(1.5) );
g.Polygon( [ asL, asR-asW, asR-asW, asR+asW, asR+asW, asL ], [ asC-asH, asC-asH, asC-3*asH, asC-3*asH, asC+asH, asC+asH ], 7 );
var asL = 3;
var asR = 6;
g.Polygon( [ asL-asW, asL+asW, asL+asW, asR, asR, asL-asW ], [ asC-3*asH, asC-3*asH, asC-asH, asC-asH, asC+asH, asC+asH ], 7 );
g.Rect( -asW, asC-asH, asW, asC+asH, 3 );
// draw up-down arrows
if (pitch > 20 && pitch < 90) {
g.SetMarkerAttr( 'ArrowDown', g.WinToPixX(1), 'black', 'yellow', 1 );
g.Marker( -6, low+1.5, 3 );
g.Marker( 6, low+1.5, 3 );
}
if (pitch < -10 || pitch >= 90) {
g.SetMarkerAttr( 'ArrowUp', g.WinToPixX(1), 'black', 'yellow', 1 );
g.Marker( -6, high-1.5, 3 );
g.Marker( 6, high-1.5, 3 );
}
// draw flight path vector symbol
if (this.ShowFlightPath) {
var r = w / 20;
var angle = pitch - deg(V2.Angle( this.SpeedDir, this.Xaircraft ));
g.SetMarkerAttr( 'FPSymbol', 2*r, 'orange', 'orange', g.ScalePix(2) );
g.Marker( 0, angle, 1 );
}
g.SetLineAttr( 'black', 1 );
g.Frame();
},
DrawDataDisplay: function( g, x, y, w, h ) {
g.SetViewport( x, y, w, h, false, true );
g.SetWindowWH( 0, h, w, -h );
g.SetAreaAttr( '#888', 'black', 1 );
g.Frame(3);
this.DrawVectValue( g, 0, 0, 'Lift', this.FLift, 'N', 'blue', w, h );
this.DrawVectValue( g, 1, 0, 'Trim', this.FTrim, 'N', 'blue', w, h );
this.DrawVectValue( g, 2, 0, 'Weight', this.FWeight, 'N', 'green', w, h );
this.DrawVectValue( g, 3, 0, 'Thrust', this.FThrust, 'N', 'orange', w, h );
this.DrawVectValue( g, 4, 0, 'Drag', this.FDrag, 'N', 'red', w, h );
this.DrawVectValue( g, 0, 1, 'Ftot', this.Ftot, 'N', 'magenta', w, h );
this.DrawVectValue( g, 1, 1, 'Acc', this.State.Accel, 'm/s^2', 'magenta', w, h );
this.DrawVectValue( g, 2, 1, 'Speed', this.State.Speed, 'm/s', 'magenta', w, h );
this.DrawScalarValue( g, 3, 1, 'Speed of Sound', this.SpeedOfSound, 'm/s', 4, 'lightblue', w, h );
this.DrawScalarValue( g, 4, 1, 'Air Density', this.AirDensity, 'kg/m^3', 4, 'lightblue', w, h );
var groundSpeed = V2.ScalarProd( this.Xsurface, this.State.Speed );
this.DrawScalarValue( g, 0, 2, 'Pitch', deg(this.Pitch), '°', 2, 'brown', w, h );
this.DrawScalarValue( g, 1, 2, 'Pitch Rate', deg(this.PitchRate), '°/s', 3, 'brown', w, h );
this.DrawScalarValue( g, 2, 2, 'Ground Speed', ms_kmh(groundSpeed), 'km/h', 3, 'black', w, h );
this.DrawScalarValue( g, 3, 2, 'Elevation', m_ft(this.Elevation), 'ft', 4, 'black', w, h );
this.DrawScalarValue( g, 4, 2, 'Elevation', this.Elevation, 'm', 4, 'black', w, h );
},
DrawScalarValue: function( g, row, col, label, val, unit, digits, color, w, h ) {
var textSize = 0.065 * h;
var margin = g.ScalePix(5);
var lineHeight = textSize + g.ScalePix(3);
var textW = ( w / 3 ) - margin;
var textH = ( h / 5 ) - margin;
var txtL = col * (textW + margin) + margin/2;
var txtT = row * (textH + margin) + g.ScalePix(margin/2);
var boxHeight = 2 * lineHeight + g.ScalePix(2);
g.SetAreaAttr( 'white', color, g.ScalePix(2) );
g.RectWH( txtL, txtT, textW, boxHeight, 3 );
var txtX = txtL + 0.5 * textW;
var txtY = txtT;
g.SetTextAttrS( 'Arial', textSize, 'black', 'NC', g.ScalePix(2) );
g.Text( label, txtX, txtY );
txt = this.FormatValue( val, unit, digits );
g.Text( txt, txtX, txtY + lineHeight );
},
DrawVectValue: function( g, row, col, label, vect, unit, color, w, h ) {
var textSize = 0.065 * h;
var margin = g.ScalePix(5);
var lineHeight = textSize + g.ScalePix(3);
var textW = ( w / 3 ) - margin;
var textH = ( h / 5 ) - margin;
var txtL = col * (textW + margin) + margin/2;
var txtT = row * (textH + margin) + g.ScalePix(margin/2);
var boxHeight = 2 * lineHeight + g.ScalePix(2);
g.SetAreaAttr( 'white', color, g.ScalePix(2) );
g.RectWH( txtL, txtT, textW, boxHeight, 3 );
var txtX = txtL + 0.5 * textW;
var txtY = txtT;
var value = V2.Length( vect );
var txt = label + ' = ' + this.FormatValue( value, unit );
g.SetTextAttrS( 'Arial', textSize, 'black', 'NC', g.ScalePix(2) );
g.Text( txt, txtX, txtY );
var x = V2.ScalarProd( this.Xsurface, vect );
var y = V2.ScalarProd( this.Zsurface, vect );
txt = '( ' + this.FormatValue( x ) + ' ' + this.FormatValue( y ) + ' )';
g.Text( txt, txtX, txtY + lineHeight );
},
FormatValue: function( value, unit, digits ) {
digits = xDefNum( digits, 2 );
if (xStr(unit)) {
unit = ' ' + unit;
} else {
unit = '';
}
var sign = value < 0 ? '-' : ' ';
value = Math.abs( value );
if (value < Math.pow(10,-digits)/2) sign = ' ';
if (value < 1) {
return sign + NumFormatter.Format( value, 'fix0', digits, unit );
} else {
if (unit == ' m' && value >= 999500) {
return sign + NumFormatter.Format( value/1000, 'fix0', 0, ' km' );
} else {
return sign + NumFormatter.Format( value, 'unit', digits+1, unit );
}
}
},
};
///////////////////////////////////////////////////
var AircraftGraph = NewGraph2D( {
Id: 'AircraftGraph',
Width: '100%',
Height: '50%',
DrawFunc: function(){ AircraftModel.Draw(); },
AutoReset: true,
AutoClear: true,
AutoScalePix: false,
} );
var AircraftSim = new Sim( {
SimObj: AircraftModel,
ResetFuncs: function(sim) { sim.SimObj.OnSimReset(); },
TimeStepFuncs: function(sim) { sim.SimObj.OnSimUpdate(); },
FrameFuncs: function(sim) { sim.SimObj.OnSimDraw(); },
} );
AircraftModel.Init( AircraftGraph, AircraftSim );
AircraftSim.Run( true );
// Tabs Button Handlers
xOnLoad( function() {
Tabs.AddButtonClickHandler( 'ControlsTab', 'TabsPauseButton', PauseSimulation );
Tabs.AddButtonClickHandler( 'ControlsTab', 'TabsResetTimeButton', ResetTime );
Tabs.AddButtonClickHandler( 'ControlsTab', 'TabsResetPosButton', ResetPosition );
Tabs.AddButtonClickHandler( 'ControlsTab', 'TabsResetButton', ResetSimulation );
} );
function ResetSimulation( buttonData ) {
if (AircraftSim.Running) {
AircraftSim.Run(true);
} else {
AircraftSim.Reset();
}
}
function ResetTime( buttonData ) {
AircraftModel.ResetTime();
}
function ResetPosition( buttonData ) {
AircraftModel.ResetPos();
}
function PauseSimulation( buttonData ) {
AircraftSim.Pause();
Tabs.SetButtonText( buttonData, AircraftSim.Running ? 'Stop' : 'Run' );
}
/////////////////////////////////////////////////////////////////
ControlPanels.NewSliderPanel( {
Name: 'FlightControls',
ModelRef: 'AircraftModel',
NCols: 1,
ValuePos: 'left',
OnModelChange: function(field){ AircraftModel.Update(field); },
Format: 'fix0',
Digits: 2,
ReadOnly: false,
PanelFormat: 'InputWidth75'
} ).AddValueSliderField( {
Name: 'ElevatorLever',
Label: 'Elevator',
Color: 'black',
Min: AircraftModel.MinElevatorAngle,
Max: AircraftModel.MaxElevatorAngle,
LowerLimit: AircraftModel.MinElevatorAngle,
UpperLimit: AircraftModel.MaxElevatorAngle,
SnapTo: 0,
Mult: rad(1),
Units: '°',
Inc: 1,
} ).AddValueSliderField( {
Name: 'TrimLever',
Label: 'Trim',
Color: 'green',
Min: AircraftModel.MinTrimAngle,
Max: AircraftModel.MaxTrimAngle,
LowerLimit: AircraftModel.MinTrimAngle,
UpperLimit: AircraftModel.MaxTrimAngle,
SnapTo: (AircraftModel.MinTrimAngle+AircraftModel.MaxTrimAngle)/2,
Mult: rad(1),
Units: '°',
Inc: 1,
} ).AddValueSliderField( {
Name: 'ThrustLever',
Label: 'Thrust',
Color: 'red',
Min: 0,
Max: 1,
LowerLimit: 0,
UpperLimit: 1,
Mult: 0.01,
Units: '%',
Inc: 1,
} ).AddValueSliderField( {
Name: 'Mass',
Color: 'gray',
Min: 38600,
Max: 73600,
LowerLimit: 38600,
UpperLimit: 73600,
Units: 't',
Mult: 1000,
Inc: 1,
} ).AddValueSliderField( {
Name: 'CG',
Color: 'gray',
Min: -1,
Max: 1,
LowerLimit: -1,
UpperLimit: 1,
SnapTo: 0,
} ).AddValueSliderField( {
Name: 'DeltaPressure',
Label: 'ΔP',
Color: 'blue',
Min: -3000,
Max: 3000,
Units: 'hPa',
Mult: 100,
SnapTo: 0,
LowerLimit: -3000,
UpperLimit: 3000,
Inc: 1,
} ).AddValueSliderField( {
Name: 'TimeSpeed',
ValueRef: 'AircraftSim.TimeSpeed',
Label: 'Time x',
Color: 'black',
Min: 0,
Max: 2,
LowerLimit: 1,
UpperLimit: 100,
LogScale: true,
SnapTo: [ 0.30103, 0.69897, 1, 1.30103, 1.69897 ],
SnapRange: 50,
} ).Render();
///////////////////////////////////////////////////////
ControlPanels.NewPanel( {
Name: 'OptionsPanel',
ModelRef: 'AircraftModel',
OnModelChange: function(field){ AircraftModel.Update(field); },
NCols: 2,
Format: 'fix0',
Digits: 0,
PanelFormat: 'InputWidth75'
} ).AddCheckboxField( {
Name: 'OptionsShow',
Label: 'Show',
NCols: 8,
ColSpan: 3,
Items: [
{
Name: 'ShowAircraft',
Text: 'Aircraft',
}, {
Name: 'ShowForces',
Text: 'Forces',
}, {
Name: 'ShowSpeed',
Text: 'Speed',
}, {
Name: 'ShowAccel',
Text: 'Accel',
}, {
Name: 'ShowTrend',
Text: 'Trend',
}, {
Name: 'ShowCoordSys',
Text: 'Coords',
}, {
Name: 'ShowFxTimes5',
Text: 'Fx × 5',
}, {
Name: 'ShowFlightPath',
Text: 'FPV',
},
]
} ).AddCheckboxField( {
Name: 'OptionsPhugDamp',
Label: 'Enable',
NCols: 1,
Items: [
{
Name: 'PitchDamping',
Text: 'Pitch Damping',
},
]
} ).AddTextField( {
Name: 'EarthRadius',
Label: 'Earth Radius',
Units: '% of 6371 km',
Mult: 63710,
LowerLimit: 63710,
UpperLimit: 6.371e9,
Inc: 10,
} ).Render();
///////////////////////////////////////////////////////
ControlPanels.NewPanel( {
Name: 'InitValuesPanel',
ModelRef: 'AircraftModel',
NCols: 2,
Format: 'fix0',
Digits: 2,
ReadOnly: false,
PanelFormat: 'InputNormalWidth'
} ).AddTextField( {
Name: 'InitTAS',
Label: 'TAS',
LowerLimit: 0,
UpperLimit: 340,
Mult: kt_ms(1),
Units: 'kt',
} ).AddTextField( {
Name: 'InitVS',
Label: 'VS',
LowerLimit: ftmin_ms(-10000),
UpperLimit: ftmin_ms(10000),
Mult: ftmin_ms(1),
Units: 'ft/min',
} ).AddTextField( {
Name: 'InitAltitude',
Label: 'Altitude',
LowerLimit: 0,
UpperLimit: 50000, // instable above
Mult: ft_m(1),
Units: 'ft',
} ).AddTextField( {
Name: 'InitPitch',
Label: 'Pitch',
LowerLimit: rad(-45),
UpperLimit: rad(45),
Mult: rad(1),
Units: '°',
} ).AddTextField( {
Name: 'InitTrim',
Label: 'Trim',
LowerLimit: AircraftModel.MinTrimAngle,
UpperLimit: AircraftModel.MaxTrimAngle,
Mult: rad(1),
Units: '°',
} ).AddTextField( {
Name: 'InitElevator',
Label: 'Elevator',
LowerLimit: AircraftModel.MinElevatorAngle,
UpperLimit: AircraftModel.MaxElevatorAngle,
Mult: rad(1),
Units: '°',
} ).AddTextField( {
Name: 'InitThrust',
Label: 'Thrust',
LowerLimit: 0,
UpperLimit: 1,
Mult: 0.01,
Units: '%',
} ).AddTextField( {
Name: 'InitMass',
Label: 'Mass',
LowerLimit: 38600,
UpperLimit: 73600,
Units: 't',
Mult: 1000,
} ).AddTextField( {
Name: 'InitCG',
Label: 'CG',
LowerLimit: -1,
UpperLimit: 1,
} ).AddTextField( {
Name: 'InitDeltaPressure',
Label: 'ΔP',
Units: 'hPa',
Mult: 100,
LowerLimit: -3000,
UpperLimit: 3000,
} ).AddTextField( {
Name: 'InitEarthRadius',
Label: 'Earth Radius',
Units: '% of 6371 km',
Mult: 63710,
LowerLimit: 63710,
UpperLimit: 6.371e9,
Inc: 10,
} ).Render();