/*****************************************************************************
 * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Aircraft base class.
 * TODO/FIXME: Factor this class.
 * TODO/FIXME: The lower-level object module shouldn't depend on program module.
 *//*
 * 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 OBJECT_AIRCRAFT_CC 1
#include "base/module.hh"
#include "base/random.hh"
#include "base/clamp.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
using namespace math;
#include "graph/module.hh"
#include "graph/model_cache.hh"
#include "graph/subgraph.hh"
using namespace graph;
#include "control/module.hh"
#include "control/defs_axis.hh"
#include "control/command.hh"
#include "control/events.hh"
using namespace control;
#include "physics/module.hh"
#include "physics/physics_control.hh"
using namespace physics;
#include "fx/module.hh"
#include "fx/fx.hh"
using namespace fx;
#include "sound/module.hh"
using namespace sound;
#include "world/module.hh"
using namespace world;
#include "program/module.hh"
#include "program/conf.hh"
#include "program/carrier.hh"
using namespace program;
#include "object/module.hh"
#include "object/defs.hh"
#include "object/aircraft.hh"

namespace object {

INTERN const Milliseconds   AIRCRAFT_EXPLOSION_LIFETIME( 5 * 60 * 1000 );  // 5 minutes
INTERN const fp             MANEUVER_LIFTOFF_DELTA = 20.f;
INTERN const Degree         STALL_DEGREE_ANIMATION( 0.1f );
INTERN const Milliseconds   STATE_UPDATE_FREQ( 500 );
INTERN const Meter          MAX_ALT_RENDER_EXPLODING_AIRCRAFT( 30.0f );

// Abbrev.
#define AIRCRAFT_GEO_POSITION() \
    (world::conv::WorldVertex2GeoVertex( GetPosition() ))

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  Aircraft  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Aircraft::Aircraft( shptr<Graph> graph, const WorldVertex& pos, const AircraftSpecs& specs )
:   Parent(graph,pos),
    mSavedMatrix(),
    mPhysics(this,specs),
    mState(eState_PARKED),
    mStateUpdateTime(0),
    mOnRunway(false),
    mCoordinatedTurnEnabled(false),
    mCoordinatedTurnExecuting(false),
    mThrottle(0.0f),
    mLandingGearDown(false),
    mShadowCaster(false)
{
    SET_TYPESIG(this,TYPESIG_AIRCRAFT);

    // The rotation list orients 3D models how the simulator expects (pitch,yaw,roll).
    PROGRAM_CONF.mAircraftRotationList.Apply( *this );
    mSavedMatrix = GetMatrix();  // save after apply rotations

    SetThrottle( 0.0f );
    SetLandingGear( true );
}

Aircraft::~Aircraft()
{
    INVALIDATE_TYPESIG(this,TYPESIG_AIRCRAFT);
}

/*****************************************************************************
 * Load a 3D model according to AircraftSpecs.
 * @param   specs
 * @param   loadCopy
 *          If false, return a possibly shared 3D model.
 *          If true, return an independent copy.
 *          For dynamic aircraft, nodes should be copies so that propellers etc
 *          can be rotated independently without affecting other aircraft.
 *****************************************************************************/
shptr<Graph>  // CLASS_METHOD
Aircraft::LoadModel( const AircraftSpecs& specs, const bool loadCopy )
{
//CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
ASSERT( not specs.mModelFile.empty() );
ASSERT( specs.mLength > Meter(0) );
ASSERT( specs.mModelScale > 0 );

    // Load 3D model.
    shptr<Graph> graph = \
    ModelCache::GetInstance().LoadModel( specs.mModelFile,
                                         specs.mLength,         // meters
                                         specs.mModelOffset,    // meters
                                         specs.mModelRotationList,
                                         specs.mModelScale,
                                         loadCopy );
    return graph;
}

/*****************************************************************************
 * Reset.
 *****************************************************************************/
void
Aircraft::Reset( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    Parent::Reset();

    // Restore saved matrix.
    SetMatrix( mSavedMatrix, Dyna::PLACEMENT );

    // In case Graph was disabled (set invisible) for explosion.
    GetGraph()->Enable( true );

    SetState( eState_PARKED );  // derived Reset() can reassign
    SetThrottle( 0.0f );
    EnableBrakes( false );

    // Clear collision.
    SetCollision( Object::COLLISION_NONE );

    // Reset physics (stop movement).
    mPhysics.Reset();

    // Reset ailerons, rudder, etc.
    RotateControlSurfaces( AXIS_ROLL, 0.0f );
    RotateControlSurfaces( AXIS_PITCH, 0.0f );
    RotateControlSurfaces( AXIS_YAW, 0.0f );

    // Let it fly again.
    GetPhysics().Enable( true );
}

/*****************************************************************************
 * Set position of Aircraft.
 *****************************************************************************/
void
Aircraft::SetPosition( const WorldVertex& position )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    Parent::SetPosition( position );
}

/*****************************************************************************
 * Translate matrix.
 *****************************************************************************/
void
Aircraft::Translate( uint axis, fp inc )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    Parent::Translate( axis, inc );
}

void
Aircraft::Translate( const Vector3& v )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    Parent::Translate( v );
}

/*****************************************************************************
 * Prevent rotation while aircraft is in contact with runway.
 *****************************************************************************/
void
Aircraft::Rotate( uint axis, Radian rad )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // Prevent rotation while aircraft is in contact with runway.
    if ( not PhysicsControl::IfEnabled()
      // or mPhysics.ComputeVelocity()[ZZ] > 0.25f )  // lifting
         or physics::conv::Speed2KPH(mPhysics.ComputeSpeed()) > SpeedKPH(5) )
    {
        Parent::Rotate( axis, rad );
    }
}

/*****************************************************************************
 * Set matrix.
 *****************************************************************************/
void
Aircraft::SetMatrix( const Matrix& matrix, Dyna::eMatrixChange change )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    Parent::SetMatrix( matrix, change );

    // Broadcast matrix as separate quaternion and position.
    // Replay can decide which is a change (avoid recording repeated commands).
    EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandQuaternion( Quaternion(matrix) ) ) );
    EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandPosition( AIRCRAFT_GEO_POSITION() ) ) );
}

/*****************************************************************************
 * Rotate by a degree according to physics (airframe), amount of control input, and time.
 *
 * This is called by the joystick/keyboard handlers that control the "current" Craft.
 *
 * For example, if the user pulls all the way left on a joystick
 * which is polled every 20 milliseconds:
 * PhysicalRotate( AXIS_ROLL, -1.0f, Milliseconds(20) )
 * This would rotate the aircraft at its maximum rate-of-roll.
 * The rate-of-roll (degree/second) would be divided down for 20 milliseconds.
 *
 * @param   axis
 * @param   controlFraction
 *          {-1.0,..,1.0}
 * @param   controlFreq
 *          The polling frequency of joystick/keyboard input.
 *****************************************************************************/
void
Aircraft::PhysicalRotate( uint axis, fp controlFraction, Milliseconds controlFreq )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
ASSERT_AXIS3( axis );
ASSERT( ABS(controlFraction) < 1.1f );
ASSERT( controlFreq > Milliseconds(0) );
ASSERT( controlFreq < Milliseconds(100) );

    // Don't rotate after fatal collision.
    if ( IfCollisionFatal() )
        return;

    // Don't rotate at low speeds on runway.
    const Speed manueveringSpeed = \
        GetPhysics().ComputeLiftoffSpeed() - physics::conv::KPH2Speed(SpeedKPH(MANEUVER_LIFTOFF_DELTA));
    if ( IfCollisionRunway() and (GetPhysics().ComputeSpeed() < manueveringSpeed ) )
        return;

    // Get the maximum rotation rate (degree/sec) of this axis.
    Degree deg = 0.0f;
    switch ( axis )
    {
        case AXIS_ROLL:  deg = mPhysics.ComputeRollRate();  break;
        case AXIS_PITCH: deg = mPhysics.ComputePitchRate(); break;
        case AXIS_YAW:   deg = mPhysics.ComputeYawRate();   break;
    }
    ASSERT( FP_GE<fp>(deg,0.0f) );  // might be zero degree

    // Multiply by the amount of control input.
    // Pitching up/down all-the-way: fraction = +-1.0
    deg = deg * controlFraction;

    // Divide since rate is measured in seconds
    // but this rotation occurs in milliseconds.
    deg = deg * (controlFreq.FPX() / 1000.0f);

    // Directly rotate.
    const Radian rad = Deg2Rad( deg );
    Rotate( axis, rad );

    // Coordinated turn:
    // Match yaw with pitch (absolute, not physical/adjusted) during a coordinated turn.
    // Only for pitch up, not pitch down, as pitch up tightens the turns.
    mCoordinatedTurnExecuting = false;
    if ( mCoordinatedTurnEnabled
     and axis == AXIS_PITCH
     and IfSameSign<fp>(deg,Control::GetInstance().GetPitchUpDir()) )
    {
        AircraftPhysics::eTurning turning = mPhysics.ComputeTurningDir();
        const Radian radYaw = fp(rad) * 1.05f;
        if ( turning == AircraftPhysics::eTurningLeft )
        {
            mCoordinatedTurnExecuting = true;
            Rotate( AXIS_YAW, ABS(radYaw) * Control::GetInstance().GetYawLeftDir() );
        }
        else if ( turning == AircraftPhysics::eTurningRight )
        {
            mCoordinatedTurnExecuting = true;
            Rotate( AXIS_YAW, ABS(radYaw) * Control::GetInstance().GetYawRightDir() );
        }
    }

    // Animate 3D model: rotate control surfaces (ailerons, rudder).
    RotateControlSurfaces( axis, controlFraction );

    // Broadcast command was executed.
    EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandPhysicalRotate(axis,controlFraction,controlFreq) ) );
}

/*****************************************************************************
 * Set throttle.
 * throttle = {0.0,..,1.0}
 * @param   throttle
 *          This method will clamp if necessary.
 *****************************************************************************/
void
Aircraft::SetThrottle( const fp throttle )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    mThrottle = Clamp1( throttle );

    // Set thrust of physics model.
    mPhysics.SetThrust( mPhysics.ComputeMaxThrust() * Newton1(mThrottle) );

    // Broadcast command was executed.
    EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandThrottle(throttle) ) );
}

/*****************************************************************************
 * Enable/disable brakes.
 *****************************************************************************/
void
Aircraft::EnableBrakes( const bool enable )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // AircraftPhysics determines magnitude of drag force
    // depending on air-brakes or wheel-brakes.
    GetPhysics().EnableBrakes( enable );

    // Broadcast command was executed.
    EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandBrakes(enable) ) );
}

bool
Aircraft::IfBrakes( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return GetPhysics().IfBrakes();
}

bool
Aircraft::IfWheelBrakes( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return GetPhysics().IfWheelBrakes();
}

bool
Aircraft::IfAirBrakes( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return GetPhysics().IfAirBrakes();
}

/*****************************************************************************
 * Enable/disable landing gear.
 * SUBTLE: This becomes a NOP if speed is zero!
 *****************************************************************************/
void
Aircraft::SetLandingGear( const bool down )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // IfCanSetLandingGear() was already called by derivative.

    mLandingGearDown = down;

    // Broadcast command was executed.
    EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandLandingGear(down) ) );

    // (Let Lua decide when to play landing-gear sound)
}

/*****************************************************************************
 * Prevent silliness such as retracting landing gear on runway.
 *****************************************************************************/
bool
Aircraft::IfCanSetLandingGear( const bool down )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return down or IfFlying();
}

/*****************************************************************************
 * @return True if landing gear is extended down.
 *****************************************************************************/
bool
Aircraft::IfLandingGear( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return mLandingGearDown;
}

/*****************************************************************************
 * Coordinated-turn methods.
 *****************************************************************************/
void
Aircraft::EnableCoordinatedTurn( const bool enable )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    mCoordinatedTurnEnabled = enable;
}

bool
Aircraft::IfCoordinatedTurnEnabled( void ) const
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return mCoordinatedTurnEnabled;
}

bool
Aircraft::IfCoordinatedTurnExecuting( void ) const
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return mCoordinatedTurnExecuting;
}

/*****************************************************************************
 * Animation.
 *****************************************************************************/
void
Aircraft::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // First clear collision flag (unless fatal)
    // to detect when aircraft flies-off runway/carrier.
    if ( not IfCollisionFatal() )
        SetCollision( Object::COLLISION_NONE );

    // Does collision-detection.
    Parent::Tick( millisecElapsed );

    // This updates some states that aren't updated by AircraftPhysics.
    UpdateState( millisecElapsed );

    // Physics model will move (and possibly rotate) this Aircraft.
    mPhysics.Tick( millisecElapsed );

    // Stalling?
    if ( mPhysics.IfStall() )
    {
        AnimateStall();
    }

    // Update mOnRunway flag.
    // Normally, aircraft is colliding into runway/carrier.
    if ( not IfCollision() )
        mOnRunway = false;
}

/*****************************************************************************
 * The physics model determines one cause of a collision
 * but other causes are possible.
 *****************************************************************************/
void
Aircraft::HandleCollisions( const Dyna::Colliders& colliders )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // Determine if a collision, detected earlier, is fatal.
    if ( not IfCollisionFatal() )
    {
        for ( Dyna::Colliders::const_iterator iter = colliders.begin();
              iter != colliders.end();
              ++iter )
        {
            // AircraftPhysics catches if Aircraft collides too fast into carrier.
            // At this point, Aircraft has either "safely collided" into carrier
            // or collided into another Object.
            shptr<Object> collider = *iter;
            CHECK_TYPESIG(collider,TYPESIG_OBJECT);
            if ( collider->GetName() == "NimitzCarrier" )
            {
                // Aircraft collided into carrier.
                // Colliding into runway is safe.
                // But did Aircraft collide into another part of the carrier?
                SafePtr<Carrier> carrier = static_cast<Carrier*>( collider.PTR() );
                CHECK_TYPESIG(carrier,TYPESIG_CARRIER);
                if ( carrier->IfRunwayAltitude(GetPosition()) )
                {
                    // False collision.
                  //SetCollision( COLLISION_NONE );  // else aircraft will fall-thru carrier deck
                    mOnRunway = true;
                }
                else
                {
                    // Crashed below flight deck or into the tower.
                    SetCollision( COLLISION_FATAL );
                }
            }
            else
            {
                // Collided into another Object.
                SetCollision( COLLISION_FATAL );
            }
        }
    }
}

/*****************************************************************************
 * Respond to fatal collision.
 *****************************************************************************/
void
Aircraft::SetCollision( const Object::eCollision collision )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // If transition to fatal collision, make an explosion once.
    if ( collision == COLLISION_FATAL and not IfCollisionFatal() )
    {
        // Visual explosion.
        // Careful, if this Aircraft is a zombie, its radius is zero.
        const fp radius = (GetRadius() > 0.01f) ? GetRadius() : world::conv::Meters2Sim( Meter(8.0f) );
        GET_FX().MakeExplosion( AIRCRAFT_EXPLOSION_LIFETIME, GetPosition(), radius );

        // Play a crashing sound.
        if ( this == GET_CURRENT_CRAFT().PTR() )
        {
            StopEngineSound();
            GET_SOUND().Play( "crash.wav.gz", sound::defs::PLAY_ONCE );
        }

        // Stop engine/propeller.
        SetThrottle( 0.0f );

        // Was aircraft hit up in the sky?
        const SphereVertex sphPos = world::conv::WorldVertex2SphereVertex( GetPosition() );
        if ( sphPos.mAlt > MAX_ALT_RENDER_EXPLODING_AIRCRAFT )
        {
            // Make aircraft object invisible if it explodes up in the sky.
            // Ideally should animate bits-and-pieces of aircraft that fall down.
            GetGraph()->Enable( false );
        }
    }

    // Afterwards.
    Parent::SetCollision( collision );
}

/*****************************************************************************
 * Update Aircraft for states that aren't computed elsewhere.
 *****************************************************************************/
void
Aircraft::UpdateState( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // Dampen state-transitions by updating at a low frequency (a few seconds).
    // A reason is to avoid fluttering between TAKEOFF and FLYING.
    if ( millisecElapsed - mStateUpdateTime < STATE_UPDATE_FREQ )
        return;

    // Update timestamp.
    mStateUpdateTime = millisecElapsed;

    // Vars.
    AircraftPhysics& aircraftPhysics = GetPhysics();
    const Speed   speed        = aircraftPhysics.ComputeSpeed();
    const bool    onRunway     = IfCollisionRunway(); 

    // State transitions.
    switch ( GetState() )
    {
        case eState_PARKED:
        {
            // PARKED --> TAKEOFF
            if ( speed > SPEED_MINIMAL )
                SetState( eState_TAKEOFF );
        }
        break;

        case eState_TAKEOFF:
        {
            // TAKEOFF --> FLYING
            // TAKEOFF --> PARKED (rare, if pilot stopped during takeoff)
            if ( not onRunway )
                SetState( eState_FLYING );
            else if ( speed < SPEED_MINIMAL )
                SetState( eState_PARKED );
        }
        break;

        case eState_FLYING:
        {
            // FLYING --> LANDING
            if ( onRunway )
                SetState( eState_LANDING );
        }
        break;

        case eState_LANDING:
        {
            // LANDING --> FLYING (pilot missed landing on carrier runway)
            // LANDING --> PARKED
            if ( not onRunway )
                SetState( eState_FLYING );
            else if ( speed < SPEED_MINIMAL )
                SetState( eState_PARKED );
        }
        break;

        default:  // NOP
        break;
    }
}

/*****************************************************************************
 * Animate the aerodynamic stalling of a Aircraft.
 *****************************************************************************/
void
Aircraft::AnimateStall( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // Aircraft should begin to slightly spin when slight below stall-speed,
    // then gradually spin worse as speed declines.
    // Conversely, spinning should decrease as speed increases.
    // AircraftPhysics model will compute 1.0 for maximum stall (or more if excessive stall).
    fp stallFactor = mPhysics.ComputeStall();
    stallFactor = std::sin( stallFactor * math::RADIAN_90 );
    stallFactor += Random::random_f( 0.35f );
    Rotate( AXIS_PITCH,  stallFactor * Deg2Rad(STALL_DEGREE_ANIMATION * Degree(3.00f)) * GET_CONTROL().GetPitchDownDir() );
    Rotate( AXIS_YAW,    stallFactor * Deg2Rad(STALL_DEGREE_ANIMATION * Degree(1.00f)) );
    Rotate( AXIS_ROLL,  -stallFactor * Deg2Rad(STALL_DEGREE_ANIMATION * Degree(2.00f)) );
}

/*****************************************************************************
 * Aircraft states (parked, flying, etc).
 *****************************************************************************/
void
Aircraft::SetState( const eState state )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    mState = state;
}

Aircraft::eState
Aircraft::GetState( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return mState;
}

/*****************************************************************************
 * Collision methods.
 *****************************************************************************/
bool
Aircraft::IfCollisionRunway( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return mOnRunway;
}

/*****************************************************************************
 * For state-sorting.
 *****************************************************************************/
NodeSort
Aircraft::GetNodeSort( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
ASSERT( GetSpecs().mModelShader != "" );

    NodeSort::Attribs::Int attribBits = mShadowCaster ? NodeSort::Attribs::SHADOW_CASTER : 0;
    return NodeSort( ShaderName(GetSpecs().mModelShader), NodeSort::Attribs(attribBits) );
}

/*****************************************************************************
 * @return Height in world space of Object.
 *****************************************************************************/
fp
Aircraft::GetHeight( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return world::conv::Meters2Sim( GetSpecs().mHeight );
}

/*****************************************************************************
 * Play engine sound.  Controlled by Lua.
 *****************************************************************************/
void
Aircraft::PlayEngineSound( const fp volume )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    if ( this == GET_CURRENT_CRAFT().PTR() )
    {
        // Play sound for piston or jet engine.
        if ( IfHasPropeller() )
        {
            // If no engine sound yet, play engine-cranking sound,
            // unless playing begin flying.
            if ( not GET_SOUND().IfPlaying( "piston.wav.gz" )
             and physics::conv::Speed2KPH(GetPhysics().ComputeSpeed()) < SpeedKPH(2) )
            {
                GET_SOUND().Play( "piston_cough.wav.gz", sound::defs::PLAY_ONCE, 1.0f );
            }
    
            GET_SOUND().Play( "piston.wav.gz", sound::defs::PLAY_LOOP, volume );
        }
        else
        {
            GET_SOUND().Play( "jet.wav.gz", sound::defs::PLAY_LOOP, volume );
        }
    }
}

/*****************************************************************************
 * Stop engine sound.
 *****************************************************************************/
void
Aircraft::StopEngineSound( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // These loop.
    if ( this == GET_CURRENT_CRAFT().PTR() )
    {
        GET_SOUND().Stop( "piston.wav.gz" );
        GET_SOUND().Stop( "jet.wav.gz" );
    }
}

/*****************************************************************************
 * Set this aircraft as the shadow caster.
 * Should be called prior to SceneGraph::AttachObject() else no effect.
 *****************************************************************************/
void
Aircraft::SetShadowCaster( const bool enable )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    // Will affect Aircraft::GetNodeSort() which is called by SceneGraph::AttachObject().
    mShadowCaster = enable;
}

/*****************************************************************************
 * @return True if Aircraft is flying.
 *****************************************************************************/
bool
Aircraft::IfFlying( void )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    return physics::conv::Speed2KPH(GetPhysics().ComputeSpeed()) > SpeedKPH(10);
}

/*****************************************************************************
 * Subroutines for rotating control surfaces.
 * Rotating rudders of jets is more difficult because they're inclined backwards.
 *****************************************************************************/
void
Aircraft::RotateAileron( Subgraph& subgraph, const fp controlFraction, const eAileron aileron )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    RotateAileron( subgraph, controlFraction, aileron, GetSpecs().mAileronOffset );
}

void
Aircraft::RotateAileron( Subgraph& subgraph, const fp controlFraction, const eAileron aileron, const Vector3& aileronOffset )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    const AircraftSpecs& specs = GetSpecs();
    subgraph.Reset();
    subgraph.Translate( aileronOffset );                                    // move along fuselage
    subgraph.Rotate( MODEL_AXIS_YAW, specs.mAileronAngle * int(aileron) );  // in case wing is swept (jets)
    subgraph.Rotate( MODEL_AXIS_PITCH,
                     specs.mAileronROF * controlFraction * int(aileron) );  // "aileron" = +-1
    subgraph.Rotate( MODEL_AXIS_YAW, -specs.mAileronAngle * int(aileron) ); // undo
    subgraph.Translate( -aileronOffset );                                   // undo
}

void
Aircraft::RotateElevator( Subgraph& subgraph, const fp controlFraction )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    const AircraftSpecs& specs = GetSpecs();
    subgraph.Reset();
    subgraph.Translate( specs.mElevatorOffset );                                // move along fuselage
    subgraph.Rotate( MODEL_AXIS_PITCH, specs.mElevatorROF * controlFraction );  // ROF : "radian of freedom"
    subgraph.Translate( -specs.mElevatorOffset );                               // undo
}

void
Aircraft::RotateRudder( Subgraph& subgraph, const fp controlFraction, const eRudder rudder )
{
CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);

    const AircraftSpecs& specs = GetSpecs();
    const Vector3& rudderOffset = (rudder == SINGLE_RUDDER or rudder == LEFT_RUDDER)
                                ? specs.mRudderOffset : specs.mRudder2Offset;
    subgraph.Reset();
    subgraph.Translate( rudderOffset );                                     // move along fuselage
    subgraph.Rotate( MODEL_AXIS_PITCH, -specs.mRudderAngle );               // in case rudder is inclined (jets)
    subgraph.Rotate( MODEL_AXIS_YAW, -controlFraction * specs.mRudderROF ); // ROF : "radian of freedom"
    subgraph.Rotate( MODEL_AXIS_PITCH, specs.mRudderAngle );                // undo
    subgraph.Translate( -rudderOffset );                                    // undo
}

} // namespace object
