From 7ec7a8b60c2911e6c2f88442de69c1eac8e2bf8a Mon Sep 17 00:00:00 2001
From: Erik Hofman <erik@ehofman.com>
Date: Wed, 7 Dec 2016 11:05:07 +0100
Subject: [PATCH] Add the first version of AISim but

---
 src/FDM/SP/AISim.cpp | 514 +++++++++++++++++++++++++++++++++++++++++++
 src/FDM/SP/AISim.hpp | 217 ++++++++++++++++++
 2 files changed, 731 insertions(+)
 create mode 100644 src/FDM/SP/AISim.cpp
 create mode 100644 src/FDM/SP/AISim.hpp

diff --git a/src/FDM/SP/AISim.cpp b/src/FDM/SP/AISim.cpp
new file mode 100644
index 000000000..47ef6d0ae
--- /dev/null
+++ b/src/FDM/SP/AISim.cpp
@@ -0,0 +1,514 @@
+// AISim.cxx -- interface to the AI Sim
+//
+// Written by Erik Hofman, started November 2016
+//
+// Copyright (C) 2016  Erik Hofman <erik@ehofman.com>
+//
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//
+
+#include <cmath>
+#include <limits>
+#include <stdio.h>
+
+#include <simgear/constants.h>
+#include <simgear/math/simd.hxx>
+#include <simgear/math/simd4x4.hxx>
+#include <simgear/math/sg_geodesy.hxx>
+
+#include <Aircraft/controls.hxx>
+#include <Main/fg_props.hxx>
+#include <Main/globals.hxx>
+#include <FDM/flight.hxx>
+
+#include "AISim.hpp"
+
+
+FGAISim::FGAISim(double dt)
+{
+    SGPropertyNode_ptr aero = fgGetNode("sim/aero", true);
+    load(aero->getStringValue());
+    load("");
+
+    for (int i=0; i<4; i++) {
+        xCDYLT[i] = 0.0f;
+        xClmnT[i] = 0.0f;
+    }
+
+    PQR_body_prev = 0.0f;
+    ABY_body_prev = 0.0f;
+
+    br = 0.0f;
+
+    UVW_body = 0.0f;
+    velocity = 0.0f;
+    NED_cm = 0.0f;
+    PQR_body = 0.0f;
+    ABY_body = 0.0f;
+    ABY_dot = 0.0f;
+    euler = 0.0f;
+    wind_ned = 0.0f;
+    agl = 0.0f;
+
+    copy_to_AISim();
+    set_location_geod(get_Latitude(), get_Longitude(), get_Altitude());
+    set_alpha_rad(0.0f);
+    set_beta_rad(0.0f);
+    set_velocity_fps(0.0f);
+
+    /* useful constants */
+    FThrust = 0.0f;
+    MThrust = 0.0f;
+    gravity = 0.0f; gravity[DOWN] = G;
+    dt2_2m = 0.5f*dt*dt/m;
+}
+
+FGAISim::~FGAISim()
+{
+}
+
+// Initialize the AISim flight model, dt is the time increment for
+// each subsequent iteration through the EOM
+void
+FGAISim::init() {
+
+    // do init common to all the FDM's           
+    common_init();
+
+    // now do init specific to the AISim
+    SG_LOG( SG_FLIGHT, SG_INFO, "Starting initializing AISim" );
+
+    set_location_geod( get_Latitude(), get_Longitude(), get_Altitude() );
+    set_euler_angles_rad( get_Phi(), get_Theta(), get_Psi() );
+
+    UVW_body[U] = fgGetFloat("sim/presets/uBody-fps");
+    UVW_body[V] = fgGetFloat("sim/presets/vBody-fps");
+    UVW_body[W] = fgGetFloat("sim/presets/wBody-fps");
+    set_velocity_fps( simd4::magnitude(UVW_body) );
+}
+
+void
+FGAISim::update(double ddt)
+{
+    if (is_suspended())
+        return;
+
+    // initialize all of AISim vars
+    float dt = float(ddt);
+
+    copy_to_AISim();
+
+    ABY_dot = (ABY_body - ABY_body_prev)*dt;
+    PQR_dot = (PQR_body - PQR_body_prev)*dt;
+
+    /* update the history */
+    PQR_body_prev = PQR_body;
+    ABY_body_prev = ABY_body;
+
+    // Earth-to-Body-Axis Transformation Matrix
+    // body = pitch, roll, yaw
+    simd4_t<float,3> angles = euler;
+    float angle = simd4::normalize(angles);
+    simd4x4_t<float,4> EBM = simd4x4::rotation_matrix<float>(angle, angles);
+
+    // Body-Axis Gravity Components
+    simd4_t<float,4> gb = EBM*simd4_t<float,4>(gravity);
+    simd4_t<float,4> windb = EBM*simd4_t<float,4>(wind_ned);
+
+    // Air-Relative velocity vector
+    simd4_t<float,3> Va = UVW_body + simd4_t<float,3>(windb);
+    update_UVW_body(simd4::magnitude(Va));
+    if (std::abs(velocity) > 1.0f) {
+        set_alpha_rad( std::atan(Va[3]/std::abs(Va[1])) );
+        set_beta_rad( std::asin(Va[2]/velocity) );
+    }
+
+    // Force and Moment Coefficients
+    simd4_t<float,4> CDYLT = 0.0f;
+    simd4_t<float,4> ClmnT = 0.0f;
+    for (int i=0; i<4; i++) {
+        CDYLT += xCDYLT[i];
+        ClmnT += xClmnT[i];
+    }
+
+    float p = PQR_body[P];
+    float q = PQR_body[Q];
+    float r = PQR_body[R];
+    do {
+        float adot = ABY_dot[ALPHA];
+        float CL = CDYLT[LIFT];
+
+        CDYLT[DRAG] += CDi*CL*CL;
+        CDYLT[LIFT] += (CLadot*adot + CLq*q)*cbar_2U;
+        CDYLT[SIDE] += (CYp*p       + CYr*r)*b_2U;
+        ClmnT[ROLL]  += (Clp*p       + Clr*r)*b_2U;
+        ClmnT[YAW]   += (Cnp*p       + Cnr*r)*b_2U;
+        ClmnT[PITCH] += (Cmadot*adot + Cmq*q)*cbar_2U;
+    }
+    while (0);
+
+    // State Accelerations
+    simd4_t<float,4> FDYLT = CDYLT*C2F;
+    simd4_t<float,4> MlmnT = ClmnT*C2M;
+
+    /* convert from wind axes to body axes */
+    simd4_t<float,3> aby = ABY_body;
+    angle = simd4::normalize(aby);
+
+    simd4x4_t<float,4> WBM = simd4x4::rotation_matrix<float>(angle, aby);
+    simd4_t<float,4>FXYZ = WBM*FDYLT;
+
+     // Thrust -- todo: propulsion in non x-directions
+    FThrust[X] = FDYLT[THRUST];
+    MThrust[ROLL] = MlmnT[THRUST];
+
+    FXYZ += FThrust;
+//  MlmnT += MThrust;
+
+    // Gear forces - http://tutorial.math.lamar.edu/Classes/DE/Vibrations.aspx
+    if (0) { // WoW
+        simd4_t<float,3> FLMLG = 0.0f, FRLMG = 0.0f, FNLG = 0.0f;
+        float gear_comp = 1.0f + _MINMAX(agl/gear[Z], -1.0f, 0.0f);
+        FNLG[Z] = FLMLG[Z] = FRLMG[Z] = Cgear*gear_comp;
+        simd4_t<float,3> FLGear = 0.0f, MLGear = 0.0f;
+
+        FXYZ += FLGear;
+        MlmnT +=  MLGear;
+    }
+    FXYZ /= m;
+
+    // Dynamic Equations
+
+    /* body-axis velocity: forward, sideward, upward */
+    float u = UVW_body[U];
+    float v = UVW_body[V];
+    float w = UVW_body[W];
+    simd4_t<float,3> dUVW = FXYZ + gb;
+    dUVW[U] +=  r*v - q*w;
+    dUVW[V] += -r*u + p*w;
+    dUVW[W] +=  q*u - p*v;
+    UVW_body += dUVW;
+printf("UVW: %5.4f, %5.4f, %5.4f\n", dUVW[U], dUVW[V], dUVW[W]);
+
+    /* position of center of mass wrt earth: north, east, down */
+    simd4_t<float,4> dNED_cm;
+    dNED_cm = simd4x4::transpose(EBM)*simd4_t<float,4>(UVW_body);
+
+    double az2, dist = simd4::magnitude(dNED_cm);
+    SGGeod pos2, pos = getPosition();
+    geo_direct_wgs_84 ( pos, euler[PSI] * SGD_RADIANS_TO_DEGREES,
+                        dist, pos2, &az2 );
+    set_location_geod( pos2.getLatitudeRad(),
+                       pos2.getLongitudeRad(), pos.getElevationFt() );
+
+    /* body-axis iniertial rates: pitching, rolling, yawing */
+    simd4_t<float,3> dPQR;
+    dPQR[P] = I[ZZ]*MlmnT[ROLL]-(I[ZZ]+I[YY])*r*q/I[XX];
+    dPQR[Q] =      MlmnT[PITCH]-(I[XX]+I[ZZ])*p*r;
+    dPQR[R] = I[XX]*MlmnT[YAW] +(I[XX]-I[YY])*p*q/I[ZZ];
+//  PQR_body += dPQR*dt;
+
+    /* angle of body wrt earth: phi (roll), theta (pitch), psi (yaw) */
+    float cos_t = std::cos(euler[THETA]);
+    float sin_t = std::sin(euler[THETA]);
+
+    if (std::abs(cos_t) < 0.00001f) cos_t = std::copysign(0.00001f,cos_t);
+
+    float sin_p = std::sin(euler[PHI]);
+    float cos_p = std::cos(euler[PHI]);
+    simd4_t<float,3> deuler;
+    deuler[PHI] =   P+(sin_p*Q+cos_p*r)*sin_t/cos_t;
+    deuler[THETA] =    cos_p*q-sin_p*r;
+    deuler[PSI] =     (sin_p*q+cos_p*r)    / cos_t;
+    euler += deuler;
+
+    copy_from_AISim();
+}
+
+bool
+FGAISim::copy_to_AISim()
+{
+    set_rudder_norm(globals->get_controls()->get_rudder());
+    set_elevator_norm(globals->get_controls()->get_elevator());
+    set_aileron_norm(globals->get_controls()->get_aileron());
+    set_flaps_norm(globals->get_controls()->get_flaps());
+    set_throttle_norm(globals->get_controls()->get_throttle(0));
+    set_brake_norm(0.5f*(globals->get_controls()->get_brake_left()
+                         +globals->get_controls()->get_brake_right()));
+
+    set_altitude_agl_ft(get_Altitude_AGL());
+//  set_location_geod(get_Latitude(), get_Longitude(), get_Altitude());
+//  set_velocity_fps(get_V_calibrated_kts());
+
+    return true;
+}
+
+bool
+FGAISim::copy_from_AISim()
+{
+    // Accelerations
+//  _set_Accels_Omega( PQR_body[P], PQR_body[Q], PQR_body[R] );
+
+    // Velocities
+    _set_Velocities_Local( UVW_body[U], UVW_body[V], UVW_body[W] );
+
+    _set_Omega_Body( PQR_body[P], PQR_body[Q], PQR_body[R] );
+
+    // Positions
+    double sl_radius, lat_geoc;
+    sgGeodToGeoc( NED_cm[LATITUDE], NED_cm[ALTITUDE], &sl_radius, &lat_geoc );
+    _set_Geocentric_Position( lat_geoc, NED_cm[LONGITUDE], sl_radius);
+//  _update_ground_elev_at_pos();
+//  _set_Sea_level_radius( sl_radius * SG_METER_TO_FEET);
+
+    _set_Euler_Angles( euler[PHI], euler[THETA], euler[PSI] );
+
+    _set_Alpha( ABY_body[ALPHA] );
+    _set_Beta(  ABY_body[BETA] );
+
+    return true;
+}
+
+void
+FGAISim::update_UVW_body(float f)
+{
+    velocity = f;
+    xCDYLT[VELOCITY][THRUST] = CTu*f;
+
+    if (std::abs(f) < 0.00001f) f = std::copysign(0.00001f,f);
+    b_2U = 0.5f*b/f;
+    cbar_2U = 0.5f*cbar/f;
+    update_qbar();
+}
+
+// ----------------------------------------------------------------------------
+
+void
+FGAISim::update_qbar()
+{
+    unsigned int hi = _MINMAX(std::rint(NED_cm[ALTITUDE]/1000.0f), 0, 100);
+    float rho = env[hi][RHO];
+
+    float qbar = 0.5f*rho*velocity*velocity;
+    float Sqbar = S*qbar;
+    float Sbqbar = Sqbar*b;
+    float Sqbarcbar = Sqbar*cbar;
+
+    C2F = Sqbar;
+    C2F[THRUST] = rho*RPS2D4*CTmax;
+
+    C2M = Sbqbar;
+    C2M[PITCH] = Sqbarcbar;
+}
+
+// 1976 Standard Atmosphere
+//  Density     Speed of sound
+// slugs/ft2       ft/s
+float FGAISim::env[101][2] =
+{
+  { 0.00237717f,   1116.45f },
+  { 0.00230839f,   1112.61f },
+  { 0.00224114f,   1108.75f },
+  { 0.00217539f,   1104.88f },
+  { 0.00211114f,   1100.99f },
+  { 0.00204834f,   1097.09f },
+  { 0.00198698f,   1093.18f },
+  { 0.00192704f,   1089.25f },
+  { 0.00186850f,   1085.31f },
+  { 0.00181132f,   1081.36f },
+  { 0.00175549f,   1077.39f },
+  { 0.00170099f,   1073.40f },
+  { 0.00164779f,   1069.40f },
+  { 0.00159588f,   1065.39f },
+  { 0.00154522f,   1061.36f },
+  { 0.00149581f,   1057.31f },
+  { 0.00144761f,   1053.25f },
+  { 0.00140061f,   1049.18f },
+  { 0.00135479f,   1045.08f },
+  { 0.00131012f,   1040.97f },
+  { 0.00126659f,   1036.85f },
+  { 0.00122417f,   1032.71f },
+  { 0.00118285f,   1028.55f },
+  { 0.00114260f,   1024.38f },
+  { 0.00110341f,   1020.19f },
+  { 0.00106526f,   1015.98f },
+  { 0.00102812f,   1011.75f },
+  { 0.000991984f,  1007.51f },
+  { 0.000956827f,  1003.24f },
+  { 0.000922631f,  998.963f },
+  { 0.000889378f,  994.664f },
+  { 0.000857050f,  990.347f },
+  { 0.000825628f,  986.010f },
+  { 0.000795096f,  981.655f },
+  { 0.000765434f,  977.280f },
+  { 0.000736627f,  972.885f },
+  { 0.000708657f,  968.471f },
+  { 0.000675954f,  968.076f },
+  { 0.000644234f,  968.076f },
+  { 0.000614002f,  968.076f },
+  { 0.000585189f,  968.076f },
+  { 0.000557728f,  968.076f },
+  { 0.000531556f,  968.076f },
+  { 0.000506612f,  968.076f },
+  { 0.000482838f,  968.076f },
+  { 0.000460180f,  968.076f },
+  { 0.000438586f,  968.076f },
+  { 0.000418004f,  968.076f },
+  { 0.000398389f,  968.076f },
+  { 0.000379694f,  968.076f },
+  { 0.000361876f,  968.076f },
+  { 0.000344894f,  968.076f },
+  { 0.000328709f,  968.076f },
+  { 0.000313284f,  968.076f },
+  { 0.000298583f,  968.076f },
+  { 0.000284571f,  968.076f },
+  { 0.000271217f,  968.076f },
+  { 0.000258490f,  968.076f },
+  { 0.000246360f,  968.076f },
+  { 0.000234799f,  968.076f },
+  { 0.000223781f,  968.076f },
+  { 0.000213279f,  968.076f },
+  { 0.000203271f,  968.076f },
+  { 0.000193732f,  968.076f },
+  { 0.000184641f,  968.076f },
+  { 0.000175976f,  968.076f },
+  { 0.000167629f,  968.337f },
+  { 0.000159548f,  969.017f },
+  { 0.000151867f,  969.698f },
+  { 0.000144566f,  970.377f },
+  { 0.000137625f,  971.056f },
+  { 0.000131026f,  971.735f },
+  { 0.000124753f,  972.413f },
+  { 0.000118788f,  973.091f },
+  { 0.000113116f,  973.768f },
+  { 0.000107722f,  974.445f },
+  { 0.000102592f,  975.121f },
+  { 0.0000977131f, 975.797f },
+  { 0.0000930725f, 976.472f },
+  { 0.0000886582f, 977.147f },
+  { 0.0000844590f, 977.822f },
+  { 0.0000804641f, 978.496f },
+  { 0.0000766632f, 979.169f },
+  { 0.0000730467f, 979.842f },
+  { 0.0000696054f, 980.515f },
+  { 0.0000663307f, 981.187f },
+  { 0.0000632142f, 981.858f },
+  { 0.0000602481f, 982.530f },
+  { 0.0000574249f, 983.200f },
+  { 0.0000547376f, 983.871f },
+  { 0.0000521794f, 984.541f },
+  { 0.0000497441f, 985.210f },
+  { 0.0000474254f, 985.879f },
+  { 0.0000452178f, 986.547f },
+  { 0.0000431158f, 987.215f },
+  { 0.0000411140f, 987.883f },
+  { 0.0000392078f, 988.550f },
+  { 0.0000373923f, 989.217f },
+  { 0.0000356632f, 989.883f },
+  { 0.0000340162f, 990.549f },
+  { 0.0000324473f, 991.214f }
+};
+
+bool
+FGAISim::load(std::string path)
+{
+    /* defaults for the Cessna 182, taken from Roskam */
+    S = 172.0f;
+    cbar = 4.90f;
+    b = 36.0f;
+
+    m = 2650.0f*G;
+    I[XX] = 948.0f;
+    I[YY] = 1346.0f;
+    I[ZZ] = 1967.0f;
+
+    // gear ground contact points relative tot cg at (0,0,0)
+    float no_gears = 3.0f;
+    Cgear = 5400.0f*no_gears;
+#if 1
+    gear[X] =   0.0f*INCHES_TO_FEET;
+    gear[Y] =   0.0f*INCHES_TO_FEET;
+    gear[Z] = -54.4f*INCHES_TO_FEET;
+#else
+    /* nose */
+    gear[0][X] = -47.8f*INCHES_TO_FEET;
+    gear[0][Y] =   0.0f*INCHES_TO_FEET;
+    gear[0][Z] = -54.4f*INCHES_TO_FEET;
+    /* left */
+    gear[1][X] =  17.2f*INCHES_TO_FEET;
+    gear[1][Y] = -43.0f*INCHES_TO_FEET;
+    gear[1][Z] = -54.4f*INCHES_TO_FEET;
+    /* right */
+    gear[2][X] =  17.2f*INCHES_TO_FEET;
+    gear[2][Y] =  43.0f*INCHES_TO_FEET;
+    gear[2][Z] = -54.4f*INCHES_TO_FEET;
+#endif
+
+    float de_max = 24.0f*SG_DEGREES_TO_RADIANS;
+    float dr_max = 16.0f*SG_DEGREES_TO_RADIANS;
+    float da_max = 17.5f*SG_DEGREES_TO_RADIANS;
+    float df_max = 30.0f*SG_DEGREES_TO_RADIANS;
+
+    no_engines = 1.0f;
+    CTmax = 0.073f*no_engines;
+    CTu = -0.0960f*no_engines;
+
+    CLmin = 0.307f;
+    CLa = 4.410f;
+    CLadot = 1.70f;
+    CLq = 3.90f;
+    CLdf_n = 0.6685f*df_max;
+
+    CDmin = 0.0270f;
+    CDa = 0.121f;
+    CDb = 0.0f;
+    CDi = 0.0f;
+    CDdf_n = 0.0816f*df_max;
+
+    CYb = -0.393f;
+    CYp = -0.0750f;
+    CYr = 0.214f;
+    CYdr_n = 0.187f*dr_max;
+
+    Clb = -0.0923f;
+    Clp = -0.484f;
+    Clr = 0.0798f;
+    Clda_n = 0.2290f*da_max;
+    Cldr_n = 0.0147f*dr_max;
+
+    Cma = -0.613f;
+    Cmadot = -7.27f;
+    Cmq = -12.40f;
+    Cmde_n = -1.122f*de_max;
+    Cmdf_n = -0.2177f*df_max;
+
+    Cnb = 0.0587f;
+    Cnp = -0.0278f;
+    Cnr = -0.0937;
+    Cnda_n = -0.0216f*da_max;
+    Cndr_n = -0.0645f*dr_max;
+
+    /* thuster / propulsion */
+    float D = 75.0f*INCHES_TO_FEET;
+    float RPS = 2700.0f/60.0f;
+    float prop_Ixx = 1.67f;
+
+    C2M[THRUST] = prop_Ixx*(2.0f*RPS*PI)/no_engines;
+    RPS2D4 = RPS*RPS * D*D*D*D;
+
+    return true;
+}
+
diff --git a/src/FDM/SP/AISim.hpp b/src/FDM/SP/AISim.hpp
new file mode 100644
index 000000000..e65f59db0
--- /dev/null
+++ b/src/FDM/SP/AISim.hpp
@@ -0,0 +1,217 @@
+// AISim.cxx -- interface to the AI Sim
+//
+// Written by Erik Hofman, started November 2016
+//
+// Copyright (C) 2016  Erik Hofman <erik@ehofman.com>
+//
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//
+
+
+#ifndef _FGAISim_HXX
+#define _FGAISim_HXX
+
+#include <string>
+
+#include <simgear/math/SGVec3.hxx>
+#include <FDM/flight.hxx>
+
+
+#define G		32.174f
+#define PI		3.1415926535f
+#define INCHES_TO_FEET	0.0833333333f
+#define FPS_TO_KTS	0.592484313161f
+#ifndef _MINMAX
+# define _MINMAX(a,b,c)	(((a)>(c)) ? (c) : (((a)<(b)) ? (b) : (a)))
+#endif
+
+class FGAISim : public FGInterface
+{
+private:
+    enum { X=0, Y=1, Z=2 };
+    enum { XX=0, YY=1, ZZ=2, XZ=3 };
+    enum { LATITUDE=0, LONGITUDE=1, ALTITUDE=2 };
+    enum { NORTH=0, EAST=2, DOWN=3, N=0, E=2, D=3 };
+    enum { LEFT=0, RIGHT=1 };
+    enum { MAX=0, VELOCITY=1 };
+    enum { PROPULSION=0, FLAPS=2, RUDDER=2, MIN=3, AILERON=3, ELEVATOR=3 };
+    enum { DRAG=0, SIDE=1, LIFT=2, THRUST=3 };
+    enum { ROLL=0, PITCH=1, YAW=2 };
+    enum { ALPHA=0, BETA=1 };
+    enum { RHO=0, VSOUND=1 };
+    enum { ALT=3, ASL=3 };
+    enum { PHI, THETA, PSI };
+    enum { P=0, Q=1, R=2 };
+    enum { U=0, V=1, W=2 };
+
+public:
+    FGAISim(double dt);
+    ~FGAISim();
+
+    // reset flight params to a specific location 
+    void init();
+
+    // update location based on properties
+    void update(double dt);
+
+    bool load(std::string path);
+
+    // copy FDM state to AISim structures
+    bool copy_to_AISim();
+
+    // copy AISim structures to FDM state
+    bool copy_from_AISim();
+
+    /* controls */
+    inline void set_rudder_norm(float f) {
+        xCDYLT[RUDDER][SIDE] = CYdr_n*f;
+        xClmnT[RUDDER][ROLL] = Cldr_n*f;
+        xClmnT[RUDDER][YAW] = Cndr_n*f;
+    }
+    inline void set_elevator_norm(float f) {
+        xClmnT[ELEVATOR][PITCH] = Cmde_n*f;
+    }
+    inline void set_aileron_norm(float f) {
+        xClmnT[AILERON][ROLL] = Clda_n*f;
+        xClmnT[AILERON][YAW] = Cnda_n*f;
+    }
+    inline void set_flaps_norm(float f) { 
+        xCDYLT[FLAPS][LIFT] = CLdf_n*f;
+        xCDYLT[FLAPS][DRAG] = CDdf_n*f;
+        xClmnT[FLAPS][PITCH] = Cmdf_n*f;
+    }
+    inline void set_throttle_norm(float f) {
+        xCDYLT[MAX][THRUST] = f;
+        xClmnT[PROPULSION][THRUST] = f;
+    }
+    inline void set_brake_norm(float f) { br = f; }
+
+    /* (initial) state, local frame */
+    inline void set_location_geod(const SGVec3f& p) {
+        NED_cm = p.simd3(); update_qbar();
+    }
+    inline void set_location_geod(float lat, float lon, float alt) {
+        NED_cm[LATITUDE] = lat;
+        NED_cm[LONGITUDE] = lon;
+        NED_cm[ALTITUDE] = alt;
+        update_qbar();
+    }
+    inline void set_altitude_asl_ft(float f) { NED_cm[DOWN] = -f; };
+    inline void set_altitude_agl_ft(float f) { agl = _MINMAX(f, 0.0f, 100000.0f); }
+
+    inline void set_pitch_rad(float f) { euler[PHI] = f; }
+    inline void set_roll_rad(float f) { euler[THETA] = f; }
+    inline void set_yaw_rad(float f) { euler[PSI] = f; }
+    inline void set_euler_angles_rad(const SGVec3f& o) { euler = o.simd3(); }
+    inline void set_euler_angles_rad(float phi, float theta, float psi) {
+        euler[PHI] = phi;
+        euler[THETA] = theta;
+        euler[PSI] = psi;
+    }
+
+    void set_velocity_fps(float f) {
+        UVW_body = 0.0f; UVW_body[X] = f; update_UVW_body(f);
+    }
+    inline void set_wind_ned(const SGVec3f& w) { wind_ned = w.simd3(); }
+    inline void set_wind_ned(float n, float e, float d) {
+        wind_ned[NORTH] = n;
+        wind_ned[EAST] = e;
+        wind_ned[DOWN] = d;
+    }
+
+    inline void set_alpha_rad(float f) {
+        xCDYLT[ALPHA][DRAG] = CDa*std::abs(f);
+        xCDYLT[ALPHA][LIFT] = CLa*f;
+        xClmnT[ALPHA][PITCH] = Cma*f;
+        ABY_body[ALPHA] = f;
+    }
+    inline void set_beta_rad(float f) {
+        xCDYLT[BETA][DRAG] = CDb*std::abs(f);
+        xCDYLT[BETA][SIDE] = CYb*f;
+        xClmnT[BETA][ROLL] = Clb*f; 
+        xClmnT[BETA][YAW] = Cnb*f;
+        ABY_body[BETA] = f;
+    }
+    inline float get_alpha_rad() {
+        return ABY_body[ALPHA];
+    }
+    inline float get_beta_rad() {
+        return ABY_body[BETA];
+    }
+
+private:
+    void update_qbar();
+    void update_UVW_body(float f);
+
+    /* aircraft normalized controls */
+    float  br;				/* brake			*/
+
+    /* aircraft state */
+    simd4_t<float,3> NED_cm;		/* lat, lon, altitude           */
+    simd4_t<float,3> UVW_body;		/* fwd, up, side speed          */
+    simd4_t<float,3> PQR_body;		/* P, Q, R rate			*/
+    simd4_t<float,3> PQR_dot;		/* Pdot, Qdot, Rdot accel.	*/
+    simd4_t<float,3> ABY_body;		/* alpha, beta, gamma		*/
+    simd4_t<float,3> ABY_dot;		/* adot, bdot, ydot		*/
+    simd4_t<float,3> euler;		/* phi, theta, psi		*/
+    simd4_t<float,3> wind_ned;
+    float agl, velocity;
+
+    /* history, these change between every call to update() */
+    simd4_t<float,3> PQR_body_prev, ABY_body_prev;
+
+
+    /* ---------------------------------------------------------------- */
+    /* This should reduce the time spent in update() since controls	*/
+    /* change less often than the update function runs which  might	*/
+    /* run 20 to 60 times (or more) per second				*/
+
+    /* cache, values that don't change very often */
+    simd4_t<float,3> FThrust, MThrust;
+    float b_2U, cbar_2U;
+
+    /* dynamic coefficients (already multiplied with their value) */
+    simd4_t<float,4> xCDYLT[4];
+    simd4_t<float,4> xClmnT[4];
+    simd4_t<float,4> C2F, C2M;
+
+    /* ---------------------------------------------------------------- */
+    /* aircraft static data */
+    int no_engines;
+    simd4_t<float,3> gear;	/* NED_cms in structural frame	*/
+    simd4_t<float,3> I;		/* inertia				*/
+    float S, cbar, b;		/* wing area, mean average chord, span	*/
+    float m;                    /* mass					*/
+    float RPS2D4;		/* propeller diameter, ening RPS	*/
+
+    /* static oefficients, *_n is for normalized surface deflection */
+    float Cgear;		/* gear					*/
+    float CTmax, CTu;		/* thrust max, due to speed		*/
+    float CLmin, CLa, CLadot, CLq, CLdf_n;
+    float CDmin, CDa, CDb, CDi, CDdf_n;
+    float CYb, CYp, CYr, CYdr_n;
+    float Clb, Clp, Clr, Clda_n, Cldr_n;
+    float Cma, Cmadot, Cmq, Cmde_n, Cmdf_n;
+    float Cnb, Cnp, Cnr, Cnda_n, Cndr_n;
+
+    /* static environment  data */
+    static float env[101][2];
+    simd4_t<float,3> gravity;
+    float dt2_2m;
+};
+
+#endif // _FGAISim_HXX
+