/*****************************************************************************
 * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Physics model (Aircraft).
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define PHYSICS_AIRCRAFT_PHYSICS_CC 1
#include "base/module.hh"
#include "base/conf.hh"
#include "base/stream.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
#include "math/funcs_vector.hh"
using namespace math;
#include "object/module.hh"
#include "object/aircraft.hh"
using namespace object;
#include "world/module.hh"
using namespace world;
#include "physics/module.hh"
#include "physics/defs.hh"
#include "physics/conf.hh"
#include "physics/aircraft_specs.hh"
#include "physics/physics_aircraft.hh"
#include "physics/physics_control.hh"

namespace physics {

INTERN const fp AIR_BRAKES_DRAG_FACTOR   = 0.25f;  // added to drag factor
INTERN const fp WHEEL_BRAKES_DRAG_FACTOR = 1.00f;  // added to drag factor

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  AircraftPhysics  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
AircraftPhysics::AircraftPhysics( SafePtr<Aircraft> aircraft, const AircraftSpecs& specs )
:   mEnable(true),
    mAircraft(aircraft),  // cannot be a shptr else Aircraft would have a reference to itself
    mSpecs(specs),
    mPrevTick(0),
    mWorld(),
    mThrustMag(0),
    mBrakes(false)
{
    SET_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);
    Reset();
}

AircraftPhysics::~AircraftPhysics()
{
    INVALIDATE_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);
}

////////////////////////////////////////////////////////////////////////////////
///////////////////  AircraftPhysics : enablement, reset  //////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Enable physics.
 *****************************************************************************/
void
AircraftPhysics::Enable( bool enable )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    mEnable = enable;  // this instance
}

/*****************************************************************************
 * If physics is enabled.
 *****************************************************************************/
bool
AircraftPhysics::IfEnabled( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    return mEnable and PhysicsControl::IfEnabled();
}

/*****************************************************************************
 * Called when Aircraft is reset.
 *****************************************************************************/
void
AircraftPhysics::Reset( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    mPrevTick = Milliseconds(0);  // force re-measuring time segment
    mWorld.Reset();  // reset MotionSpace
}

////////////////////////////////////////////////////////////////////////////////
////////////////////  AircraftPhysics : Object animation  //////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Main method of physics computations. 
 *****************************************************************************/
void
AircraftPhysics::Tick( const Milliseconds timeElapsed )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Do general physics computation.
    // Then do specific physics computation according to state of Aircraft.
    // AnimateAircraft() returns false if no animation should be done.
    const bool doAnimation = AnimateAircraft( timeElapsed );
    if ( EX( doAnimation ) )
    {
        switch ( mAircraft->GetState() )
        {
            case Aircraft::eState_LANDING:
            {
                AnimateAircraftLanding( timeElapsed );
            }
            break;

            default:
            break;  // NOP
        }
    }
}

/*****************************************************************************
 * This is the core of physics computations.
 *
 * Move an Object in World Space according to net force vectors.
 * Manipulates Object using Object interface.
 * Computations are done in World Space.
 *
 * Note: Axises of an Object different from World (esp. up/down).
 * Use AircraftAxis*() to produce an appropriate vector.
 * Axises in a Aircraft's local space:
 * X : left/right
 * Y : up/down
 * Z : for/backward
 *
 * @param   millisecElapsed
 *          Total milliseconds between now and when program was started.
 * @return  False if animation wasn't done (physics disabled etc).
 *****************************************************************************/
bool
AircraftPhysics::AnimateAircraft( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Wait until time-segment can be measured.
    if ( mPrevTick == Milliseconds(0) )
    {
        mPrevTick = millisecElapsed;
        return false;
    }

    // NOP?
    if ( UX( (not IfEnabled()) or mAircraft->IfCollisionFatal() ) )
        return false;

    //..........................................................................
    // Track time between now and the previous tick.

    const fpx timeRatio = (millisecElapsed.FPX() - mPrevTick.FPX()) / 1000.0;

    ASSERT( FP_GE(timeRatio,0.0) );  // shouldn't be negative
  //ASSERT( timeRatio < 10.0 );      // catch large values
    if ( UX( timeRatio > 10.0 ) )    // large values do occur on slow systems
    {
        CERROR << "AircraftPhysics::AnimateAircraft: timeRatio too large (slow system?)" << std::endl;
        mPrevTick = millisecElapsed;  // erase time delay
        return false;
    }

    // Wait for next tick if elapsed time is too short.
    if ( timeRatio < 0.00005 )
        return false;
    mPrevTick = millisecElapsed;

    //..........................................................................
    // Pre-compute variables:
    //
    // SI units are in seconds but this function is called in terms of milliseconds.

          Matrix       matrix            = mAircraft->GetMatrix();
    const SphereVertex spherePosition    = world::conv::WorldVertex2SphereVertex( mAircraft->GetPosition() );
    const bool         ifVelocity        = IfVelocity();
    const bool         ifForwardVelocity = IfForwardVelocity( matrix );
    const bool         ifOnRunway        = mAircraft->IfCollisionRunway();
    const Degree       angleOfAttack     = ComputeAngleOfAttack( ifVelocity );
    const Speed        speed             = ComputeSpeed();
    const SpeedKPH     speedKph          = physics::conv::Speed2KPH( speed );  // speed (any direction)
    const SpeedKPH     speedKphWings     = speedKph;
    Vector3            normVelocity( 0.0f, 0.0f, 0.0f );
    try { normVelocity = Normalize( mWorld.mVelocity ); } catch(...) { }  // exception if zero velocity (ok)

    //..........................................................................
    // Compute thrust:
    //
    // Magnitude of thrust is a given variable.
    // Convert magnitude to a 3D vector.
    // A = F / M : m/s/s = (kg*m/s/s) / kg

ASSERT( mSpecs.mMass.FP() > 0 );
    mWorld.mThrustForce = AircraftAxisThrust(matrix) * mThrustMag.FP();  // Vector3 * fp
    mWorld.mThrustAccel = mWorld.mThrustForce / mSpecs.mMass.FP();

    //..........................................................................
    // Compute drag:
    //
    // Drag is a function of speed, lift, AofA.
    // Direction of drag is opposite of velocity.

    if ( EX( ifVelocity ) )
    {
        // Factor in drag caused by speed.
        ASSERT( mSpecs.mMaxSpeed.FP() > 0 );
        fp dragFactor = speed.FP() / mSpecs.mMaxSpeed.FP();

        // Factor in drag caused by angle-of-attack of wings.
        // Let small AofA be negligible.
        ASSERT( mSpecs.mDragAofALimit > 0 );
        ASSERT( mSpecs.mDragAofADegree > 0 );
        dragFactor += Min<fp>( mSpecs.mDragAofALimit, ABS(angleOfAttack) / mSpecs.mDragAofADegree );

        // Factor in drag caused by brakes.
        // Prevent brakes causing backward movement.
        // Wheel brakes produce stronger drag force than air brakes (flaps).
        if ( IfBrakes() )
        {
            if ( ifForwardVelocity )
            {
                dragFactor += IfWheelBrakes() ? WHEEL_BRAKES_DRAG_FACTOR :
                                                AIR_BRAKES_DRAG_FACTOR;
            }
            else  // stopped or moving backwards
            {
                // Stop reverse movement on runway caused by brakes.
                // If drag was computed better, this wouldn't be needed.
                if ( ifOnRunway )
                {
                    dragFactor = 0.0f;
                    mWorld.mVelocity.set( 0.0f, 0.0f, 0.0f );
                }
            }
        }

        // Multiply drag factor by max thrust.
        // At max speed, drag factor would be 1.0 to counteract max thrust.
        ASSERT( mSpecs.mMaxThrustMag.FP() > 0 );
        ASSERT( mSpecs.mMass.FP() > 0 );
        const fp dragForceMag = mSpecs.mMaxThrustMag.FP() * dragFactor;

        // Compute 3D vector of drag force in opposite direction of velocity.
        ASSERT( Distance(normVelocity) > 0.0 );
        ASSERT( Distance(normVelocity) < 1.000001f );
        const Vector3 normDragForceDir = -normVelocity;  // opposite direction
        mWorld.mDragForce[XX] = normDragForceDir[XX] * dragForceMag;  // Vector3 * fp
        mWorld.mDragForce[YY] = normDragForceDir[YY] * dragForceMag;
        mWorld.mDragForce[ZZ] = normDragForceDir[ZZ] * dragForceMag;
        mWorld.mDragAccel = mWorld.mDragForce / mSpecs.mMass.FP();  // A = F / M : m/s/s = (kg*m/s/s) / kg
    }

    //..........................................................................
    // Compute lift:
    //
    // Lift is a function of speed-over-wings and angle-of-attack.

    if ( EX( ifVelocity ) )
    {
        // Compute factor of lift from speed-over-wings.
        ASSERT( mSpecs.mLiftoffSpeed.FP() > 0 );
        ASSERT( mSpecs.mRangeOfLiftFactorSpeed > 0 );
        fp liftFactorSpeed = Min( 1.5f,
                                  ABS(speedKphWings.FP()) / mSpecs.mLiftoffSpeed.FP() );
           liftFactorSpeed = Range( liftFactorSpeed,
                                   -mSpecs.mRangeOfLiftFactorSpeed,
                                    mSpecs.mRangeOfLiftFactorSpeed );

        // Compute factor of lift caused by angle-of-attack.
        ASSERT( mSpecs.mMaxAofA > 0 );
        ASSERT( mSpecs.mRangeOfLiftFactorAofA > 0 );
        fp liftFactorAofA = angleOfAttack / mSpecs.mMaxAofA;
           liftFactorAofA = Range( liftFactorAofA,
                                  -mSpecs.mRangeOfLiftFactorAofA,
                                   mSpecs.mRangeOfLiftFactorAofA );

        // Combine lift factors.
        ASSERT( mSpecs.mRangeOfLiftFactor > 0 );
        fp liftFactor = liftFactorSpeed * (1.0f + liftFactorAofA);
           liftFactor = Range( liftFactor,
                              -mSpecs.mRangeOfLiftFactor,
                               mSpecs.mRangeOfLiftFactor );

        // Compute lift force and acceleration as 3D vectors.
        const fp liftForceMag = liftFactor * (mSpecs.mMass.FP() * defs::ACCELERATION_GRAVITY_SCALAR);
        mWorld.mLiftForce = AircraftAxisLift(matrix) * liftForceMag;   // Vector3 * fp
        mWorld.mLiftAccel = mWorld.mLiftForce / mSpecs.mMass.FP();  // A = F / M : m/s/s = (kg*m/s/s) / kg
    }

    //..........................................................................
    // Compute weight:

    // Rotate force of gravity thru an "up vector" from point on world.
    const WorldVertex gravityDir     = GET_WORLD().ComputeUpVector( spherePosition );  // normalize
    const fp          weightForceMag = mSpecs.mMass.FP() * -defs::ACCELERATION_GRAVITY_SCALAR;
    ASSERT( Distance(gravityDir) < 1.000001f );  // should be normalized
    mWorld.mWeightForce = gravityDir * weightForceMag;  // Vector3 * fp
    mWorld.mWeightAccel = mWorld.mWeightForce / mSpecs.mMass.FP();  // A = F / M : m/s/s = (kg*m/s/s) / kg

    //..........................................................................
    // Add the net accelerations to velocity.

    mWorld.mNetAccel = mWorld.mThrustAccel
                     + mWorld.mDragAccel
                     + mWorld.mLiftAccel
                     + mWorld.mWeightAccel;

    // Divide net acceleration to account for this tick being < 1 second.
    mWorld.mNetAccel = mWorld.mNetAccel * timeRatio;

    // Add to velocity.
    mWorld.mVelocity += mWorld.mNetAccel;

    //..........................................................................
    // Contact force from this Aircraft being above another object.
    //
    // Do this after computing net acceleration but before commiting motion.
    // A more realistic way is to compute momentum then an opposing contact force,
    // but this simpler way of clearing vertical velocity in local,world spaces
    // produces equivalent results.

    if ( ifOnRunway )
    {
        // LIMITATION/ASSUMPTION:
        // This is limited to handling the contact force from a level runway.
        //
        // Cancel downward velocity and acceleration (not force which remains in effect).
        // Downward is aligned with force of gravity.
        // If downward velocity is too high, indicate crash.
        if ( IfDownwardVelocity() )
        {
            const SpeedKPH downwardSpeedKPH( physics::conv::Speed2KPH(Speed(ABS(mWorld.mVelocity[ALT]))) );

            mWorld.mVelocity[ALT] = 0.0f;
            mWorld.mNetAccel[ALT] = 0.0f;

            // At this point, velocity is definitely downward (IfDownwardVelocity() was true).
            // Ignore if aircraft is parked.
            if ( mAircraft->GetState() != Aircraft::eState_PARKED
             and downwardSpeedKPH > PHYSICS_CONF.mMaxLandingSpeedVertical )
            {
                mAircraft->SetCollision( Object::COLLISION_FATAL );
            }
        }
    }

    //..........................................................................
    // Commit change in motion.
    //
    // Velocity is in meters/second.
    // Convert meters to simulation distance.
    // And convert seconds to simulation time (elapsed ticks).

    if ( EX( physics::conv::Speed2KPH(ComputeSpeed()) > SpeedKPH(SPEED_KPH_MINIMAL) ) )
    {
        Vector3 velocitySim( world::conv::Meters2Sim(Meter(mWorld.mVelocity[XX])) * timeRatio,
                             world::conv::Meters2Sim(Meter(mWorld.mVelocity[YY])) * timeRatio,
                             world::conv::Meters2Sim(Meter(mWorld.mVelocity[ZZ])) * timeRatio );
        // Appy simulation speed (-speed arg).
        const fp simulationSpeed = PhysicsControl::GetSimulationSpeed();
        matrix[Ox] += velocitySim[XX] * simulationSpeed;
        matrix[Oy] += velocitySim[YY] * simulationSpeed;
        matrix[Oz] += velocitySim[ZZ] * simulationSpeed;
        mAircraft->SetMatrix( matrix, Aircraft::TRANSLATION );  // SetMatrix() called for translation
    }
    else
    {
        // Speed is extremely low.  For numerical stability, zero all forces/accelerations.
        mWorld.Reset();
    }

    return true;
}

/*****************************************************************************
 * Gradually cause a pitch up/down rotation while a Aircraft is landing. 
 *
 * @param   millisecElapsed
 *          Total milliseconds between now and when program was started.
 * @return  False if animation wasn't done (physics disabled etc).
 *****************************************************************************/
bool
AircraftPhysics::AnimateAircraftLanding( const Milliseconds millisecElapsed )
{
// TODO
    return true;
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  AircraftPhysics : speed  ////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set speed.
 *****************************************************************************/
void
AircraftPhysics::SetSpeed( const Speed speed )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Velocity is [Speed,Speed,Speed].  Speed is meter/sec.
    mWorld.mVelocity = AircraftAxisThrust(mAircraft->GetMatrix()) * speed.FP();  // Vector3 * fp
}

/*****************************************************************************
 * Compute speed.
 *****************************************************************************/
Speed
AircraftPhysics::ComputeSpeed( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Velocity is [Speed,Speed,Speed].  Speed is meter/sec.
    return Speed( Distance( mWorld.mVelocity ) );  // convert to scalar (magnitude)
}

/*****************************************************************************
 * Compute maximum speed of aircraft at current altitude.
 *****************************************************************************/
Speed
AircraftPhysics::ComputeMaxSpeed( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Altitude should be a factor (unimplemented).
    ASSERT( mSpecs.mMaxSpeed.FP() > 0 );
    return physics::conv::KPH2Speed( mSpecs.mMaxSpeed );
}

/*****************************************************************************
 * @return If Aircraft is moving.
 *****************************************************************************/
bool
AircraftPhysics::IfVelocity( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    const fp speed = Distance(mWorld.mVelocity);
    return physics::conv::Speed2KPH(Speed(speed)) > SPEED_KPH_MINIMAL;
}

/*****************************************************************************
 * @return If Aircraft is moving forward.
 *****************************************************************************/
bool
AircraftPhysics::IfForwardVelocity( const Matrix& aircraftMatrix )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    if ( IfVelocity() )
    {
        // In world space, compute angle between aircraft's thrust vector
        // and aircraft's velocity.
        return DotProduct3( AircraftAxisThrust(aircraftMatrix), mWorld.mVelocity ) > 0.0f;
    }
    else
    {
        // Isn't moving.
        return false;
    }
}

/*****************************************************************************
 * @return If Aircraft is moving downward.
 *****************************************************************************/
bool
AircraftPhysics::IfDownwardVelocity( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    return GET_WORLD().IfDownward( mWorld.mVelocity[ALT] );
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////  AircraftPhysics : thrust  /////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set current thrust (magnitude).
 *****************************************************************************/
void
AircraftPhysics::SetThrust( const Newton1 thrustMag )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    mThrustMag = thrustMag;
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  AircraftPhysics : drag  /////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * @return True if wheel brakes.
 *****************************************************************************/
bool
AircraftPhysics::IfWheelBrakes( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Cannot be inline because of Aircraft class is forward-declared.
    return mBrakes and mAircraft->IfCollisionRunway();
}

////////////////////////////////////////////////////////////////////////////////
/////////////////  AircraftPhysics : fundamental forces  ///////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Return a normalized vector of an Aircraft's matrix.
 * Vector3 is produced from matrix elements in transposed order (vertical).
 * Normalized in case matrix is scaled.
 * Drag and weight are omitted since they aren't aligned with a aircraft.
 *****************************************************************************/
Vector3
AircraftPhysics::AircraftAxisThrust( const Matrix& m )
{
    return Normalize<Vector3,fp>( m[Xz], m[Yz], m[Zz] ) * fp(THRUST_DIR);
}

Vector3
AircraftPhysics::AircraftAxisLift( const Matrix& m )
{
    return Normalize<Vector3,fp>( m[Xy], m[Yy], m[Zy] ) * fp(LIFT_DIR);
}

////////////////////////////////////////////////////////////////////////////////
///////////////////  AircraftPhysics : angle-of-attack  ////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * @return Angle-of-attack of wings.
 *****************************************************************************/
Degree
AircraftPhysics::ComputeAngleOfAttack( const bool ifVelocity )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // If velocity vector is zero, angle is undefined (return zero).
    if ( EX( ifVelocity ) )
    {
        // Compute angle between Aircraft's Y axis and velocity.
        // If AoA is 0 then this angle will be 90 degrees so subtract 90 degrees.
        const Matrix m = mAircraft->GetMatrix();
        const Vector3 axis_y( m[Xy], m[Yy], m[Zy] );
        Radian rad = Angle3( axis_y, Vector3(mWorld.mVelocity) );
        return Rad2Deg( rad - math::RADIAN_90 );
    }
    else
    {
        return Degree(0.0f);
    }
}

/*****************************************************************************
 * When aircraft is moving slowly, control surfaces barely work.
 * Known deficiency:
 * Airspeed's direction, not just magnitude, should be a factor.
 *****************************************************************************/
fp
AircraftPhysics::ComputeRotationFactor( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    SpeedKPH speed     = physics::conv::Speed2KPH( ComputeSpeed() );
    SpeedKPH speedLow  = SpeedKPH(0);
    SpeedKPH speedHigh = SpeedKPH(60);
ASSERT( speedLow < speedHigh );

    if ( speed < SpeedKPH(10) )
    {
        return 0.0f;  // below formula may produce NaN if speed=0
    }
    else if ( speed >= speedHigh )
    {
        return 1.0f;
    }
    else
    {
        // Limit rotation until at a range near the stall/liftoff speed.
        // Allow minimal rotation even at very low speeds.
        // No rotation if aircraft is in contact with runway.
        fp rotationFactor = FractionInRange( speed.FP(), speedLow.FP(), speedHigh.FP() );
        return rotationFactor;
    }
}

/*****************************************************************************
 * Compute rate of roll,pitch,yaw.
 * Rate is affected by speed.
 *****************************************************************************/
Degree
AircraftPhysics::ComputeRollRate( void )
{
ASSERT( mSpecs.mRollRate > 0 );

    return mSpecs.mRollRate * ComputeRotationFactor();
}

Degree
AircraftPhysics::ComputePitchRate( void )
{
ASSERT( mSpecs.mPitchRate > 0 );

    return mSpecs.mPitchRate * ComputeRotationFactor();
}

Degree
AircraftPhysics::ComputeYawRate( void )
{
ASSERT( mSpecs.mYawRate > 0 );

    return mSpecs.mYawRate * ComputeRotationFactor();
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////  AircraftPhysics : turning  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Determine if the user seems to be doing a normal banked turn.
 * @returns { eNotTurning, eTurningLeft, eTurningRight }
 *****************************************************************************/
AircraftPhysics::eTurning
AircraftPhysics::ComputeTurningDir( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    // Determine whether turning left or right by computing the 1D angle
    // between Y coordinate (Z coordinate in world) of the local X axis and (0,1,0).
    const Matrix m = mAircraft->GetMatrix();
    const Degree deg = Rad2Deg( Radian( std::acos( m[Zx] ) ) ) - 90.0f;
ASSERT( mSpecs.mTurnDegreeLow > 0.0f );
ASSERT( mSpecs.mTurnDegreeHigh > 0.0f );
    if ( (ABS(deg) >= mSpecs.mTurnDegreeLow)
     and (ABS(deg) <= mSpecs.mTurnDegreeHigh) )
    {
        return deg < 0.0f ? eTurningLeft : eTurningRight;
    }
    else
    {
        return eNotTurning;
    }
}

/*****************************************************************************
 * @return True if the player seems to be doing a banked turn.
 *****************************************************************************/
bool
AircraftPhysics::IfTurning( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);

    const eTurning turning = ComputeTurningDir();
    return (turning == eTurningLeft)
        or (turning == eTurningRight);
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////  AircraftPhysics : stalling  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * @return True if stalling.
 *****************************************************************************/
bool
AircraftPhysics::IfStall( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);
ASSERT( mSpecs.mMaxAofA > Degree(0) );

    // Don't stall if physics is disabled or aircraft is sitting on runway.
    if ( PhysicsControl::IfStallEnabled()
     and IfEnabled()
     and not mAircraft->IfCollisionRunway() )
    {
        const Degree aoa = ComputeAngleOfAttack();
        return ABS(aoa) > mSpecs.mMaxAofA;
    }
    else
    {
        return false;
    }
}

/*****************************************************************************
 * Compute fraction indicating the amount of stalling.
 * Might return > 1.0 for severe stall.
 *****************************************************************************/
fp
AircraftPhysics::ComputeStall( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT_PHYSICS);
ASSERT( mSpecs.mMaxAofA > Degree(0) );

    return ABS(ComputeAngleOfAttack()) / mSpecs.mMaxAofA;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////  AircraftPhysics::MotionSpace  //////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor
 *****************************************************************************/
AircraftPhysics::MotionSpace::MotionSpace( void )
{
    Reset();
}

void
AircraftPhysics::MotionSpace::Reset( void )
{
    mThrustForce.set(0.0f,0.0f,0.0f);
    mThrustAccel.set(0.0f,0.0f,0.0f);

    mDragForce.set(0.0f,0.0f,0.0f);
    mDragAccel.set(0.0f,0.0f,0.0f);

    mLiftForce.set(0.0f,0.0f,0.0f);
    mLiftAccel.set(0.0f,0.0f,0.0f);

    mWeightForce.set(0.0f,0.0f,0.0f);
    mWeightAccel.set(0.0f,0.0f,0.0f);

    mNetAccel.set(0.0f,0.0f,0.0f);

    mVelocity.set(0.0f,0.0f,0.0f);
}

} // namespace physics
