WaBis

walter.bislins.ch

Aircraft Simulation Source Code

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: '&Delta;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 &times; 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: '&Delta;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();

More Page Infos / Sitemap
Created Tuesday, June 29, 2021
Scroll to Top of Page
Changed Thursday, August 5, 2021