1 /*****************************************************************************
2  * $LastChangedDate: 2011-08-20 12:07:38 -0400 (Sat, 20 Aug 2011) $
3  * @file
4  * @author  Jim E. Brooks  http://www.palomino3d.org
5  * @brief   Aircraft base class.
6  * TODO/FIXME: Factor this class.
7  * TODO/FIXME: The lower-level object module shouldn't depend on program module.
8  *//*
9  * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
10  *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
11  *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
12  *****************************************************************************/
13 
14 #define OBJECT_AIRCRAFT_CC 1
15 #include "base/module.hh"
16 #include "base/random.hh"
17 #include "base/clamp.hh"
18 using namespace base;
19 #include "math/module.hh"
20 #include "math/funcs_trig.hh"
21 using namespace math;
22 #include "graph/module.hh"
23 #include "graph/model_cache.hh"
24 #include "graph/subgraph.hh"
25 using namespace graph;
26 #include "control/module.hh"
27 #include "control/defs_axis.hh"
28 #include "control/command.hh"
29 #include "control/events.hh"
30 using namespace control;
31 #include "physics/module.hh"
32 #include "physics/physics_control.hh"
33 using namespace physics;
34 #include "fx/module.hh"
35 #include "fx/fx.hh"
36 using namespace fx;
37 #include "sound/module.hh"
38 using namespace sound;
39 #include "world/module.hh"
40 using namespace world;
41 #include "program/module.hh"
42 #include "program/conf.hh"
43 #include "program/carrier.hh"
44 using namespace program;
45 #include "object/module.hh"
46 #include "object/defs.hh"
47 #include "object/aircraft.hh"
48 
49 namespace object {
50 
51 INTERN const Milliseconds   AIRCRAFT_EXPLOSION_LIFETIME( 5 * 60 * 1000 );  // 5 minutes
52 INTERN const fp             MANEUVER_LIFTOFF_DELTA = 20.f;
53 INTERN const Degree         STALL_DEGREE_ANIMATION( 0.1f );
54 INTERN const Milliseconds   STATE_UPDATE_FREQ( 500 );
55 INTERN const Meter          MAX_ALT_RENDER_EXPLODING_AIRCRAFT( 30.0f );
56 
57 // Abbrev.
58 #define AIRCRAFT_GEO_POSITION() \
59     (world::conv::WorldVertex2GeoVertex( GetPosition() ))
60 
61 ////////////////////////////////////////////////////////////////////////////////
62 ///////////////////////////////  Aircraft  /////////////////////////////////////
63 ////////////////////////////////////////////////////////////////////////////////
64 
65 /*****************************************************************************
66  * ctor/dtor.
67  *****************************************************************************/
Aircraft(shptr<Graph> graph,const WorldVertex & pos,const AircraftSpecs & specs)68 Aircraft::Aircraft( shptr<Graph> graph, const WorldVertex& pos, const AircraftSpecs& specs )
69 :   Parent(graph,pos),
70     mSavedMatrix(),
71     mPhysics(this,specs),
72     mState(eState_PARKED),
73     mStateUpdateTime(0),
74     mOnRunway(false),
75     mCoordinatedTurnEnabled(false),
76     mCoordinatedTurnExecuting(false),
77     mThrottle(0.0f),
78     mLandingGearDown(false),
79     mShadowCaster(false)
80 {
81     SET_TYPESIG(this,TYPESIG_AIRCRAFT);
82 
83     // The rotation list orients 3D models how the simulator expects (pitch,yaw,roll).
84     PROGRAM_CONF.mAircraftRotationList.Apply( *this );
85     mSavedMatrix = GetMatrix();  // save after apply rotations
86 
87     SetThrottle( 0.0f );
88     SetLandingGear( true );
89 }
90 
~Aircraft()91 Aircraft::~Aircraft()
92 {
93     INVALIDATE_TYPESIG(this,TYPESIG_AIRCRAFT);
94 }
95 
96 /*****************************************************************************
97  * Load a 3D model according to AircraftSpecs.
98  * @param   specs
99  * @param   loadCopy
100  *          If false, return a possibly shared 3D model.
101  *          If true, return an independent copy.
102  *          For dynamic aircraft, nodes should be copies so that propellers etc
103  *          can be rotated independently without affecting other aircraft.
104  *****************************************************************************/
105 shptr<Graph>  // CLASS_METHOD
LoadModel(const AircraftSpecs & specs,const bool loadCopy)106 Aircraft::LoadModel( const AircraftSpecs& specs, const bool loadCopy )
107 {
108 //CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
109 ASSERT( not specs.mModelFile.empty() );
110 ASSERT( specs.mLength > Meter(0) );
111 ASSERT( specs.mModelScale > 0 );
112 
113     // Load 3D model.
114     shptr<Graph> graph = \
115     ModelCache::GetInstance().LoadModel( specs.mModelFile,
116                                          specs.mLength,         // meters
117                                          specs.mModelOffset,    // meters
118                                          specs.mModelRotationList,
119                                          specs.mModelScale,
120                                          loadCopy );
121     return graph;
122 }
123 
124 /*****************************************************************************
125  * Reset.
126  *****************************************************************************/
127 void
Reset(void)128 Aircraft::Reset( void )
129 {
130 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
131 
132     Parent::Reset();
133 
134     // Restore saved matrix.
135     SetMatrix( mSavedMatrix, Dyna::PLACEMENT );
136 
137     // In case Graph was disabled (set invisible) for explosion.
138     GetGraph()->Enable( true );
139 
140     SetState( eState_PARKED );  // derived Reset() can reassign
141     SetThrottle( 0.0f );
142     EnableBrakes( false );
143 
144     // Clear collision.
145     SetCollision( Object::COLLISION_NONE );
146 
147     // Reset physics (stop movement).
148     mPhysics.Reset();
149 
150     // Reset ailerons, rudder, etc.
151     RotateControlSurfaces( AXIS_ROLL, 0.0f );
152     RotateControlSurfaces( AXIS_PITCH, 0.0f );
153     RotateControlSurfaces( AXIS_YAW, 0.0f );
154 
155     // Let it fly again.
156     GetPhysics().Enable( true );
157 }
158 
159 /*****************************************************************************
160  * Set position of Aircraft.
161  *****************************************************************************/
162 void
SetPosition(const WorldVertex & position)163 Aircraft::SetPosition( const WorldVertex& position )
164 {
165 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
166 
167     Parent::SetPosition( position );
168 }
169 
170 /*****************************************************************************
171  * Translate matrix.
172  *****************************************************************************/
173 void
Translate(uint axis,fp inc)174 Aircraft::Translate( uint axis, fp inc )
175 {
176 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
177 
178     Parent::Translate( axis, inc );
179 }
180 
181 void
Translate(const Vector3 & v)182 Aircraft::Translate( const Vector3& v )
183 {
184 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
185 
186     Parent::Translate( v );
187 }
188 
189 /*****************************************************************************
190  * Prevent rotation while aircraft is in contact with runway.
191  *****************************************************************************/
192 void
Rotate(uint axis,Radian rad)193 Aircraft::Rotate( uint axis, Radian rad )
194 {
195 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
196 
197     // Prevent rotation while aircraft is in contact with runway.
198     if ( not PhysicsControl::IfEnabled()
199       // or mPhysics.ComputeVelocity()[ZZ] > 0.25f )  // lifting
200          or physics::conv::Speed2KPH(mPhysics.ComputeSpeed()) > SpeedKPH(5) )
201     {
202         Parent::Rotate( axis, rad );
203     }
204 }
205 
206 /*****************************************************************************
207  * Set matrix.
208  *****************************************************************************/
209 void
SetMatrix(const Matrix & matrix,Dyna::eMatrixChange change)210 Aircraft::SetMatrix( const Matrix& matrix, Dyna::eMatrixChange change )
211 {
212 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
213 
214     Parent::SetMatrix( matrix, change );
215 
216     // Broadcast matrix as separate quaternion and position.
217     // Replay can decide which is a change (avoid recording repeated commands).
218     EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandQuaternion( Quaternion(matrix) ) ) );
219     EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandPosition( AIRCRAFT_GEO_POSITION() ) ) );
220 }
221 
222 /*****************************************************************************
223  * Rotate by a degree according to physics (airframe), amount of control input, and time.
224  *
225  * This is called by the joystick/keyboard handlers that control the "current" Craft.
226  *
227  * For example, if the user pulls all the way left on a joystick
228  * which is polled every 20 milliseconds:
229  * PhysicalRotate( AXIS_ROLL, -1.0f, Milliseconds(20) )
230  * This would rotate the aircraft at its maximum rate-of-roll.
231  * The rate-of-roll (degree/second) would be divided down for 20 milliseconds.
232  *
233  * @param   axis
234  * @param   controlFraction
235  *          {-1.0,..,1.0}
236  * @param   controlFreq
237  *          The polling frequency of joystick/keyboard input.
238  *****************************************************************************/
239 void
PhysicalRotate(uint axis,fp controlFraction,Milliseconds controlFreq)240 Aircraft::PhysicalRotate( uint axis, fp controlFraction, Milliseconds controlFreq )
241 {
242 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
243 ASSERT_AXIS3( axis );
244 ASSERT( ABS(controlFraction) < 1.1f );
245 ASSERT( controlFreq > Milliseconds(0) );
246 ASSERT( controlFreq < Milliseconds(100) );
247 
248     // Don't rotate after fatal collision.
249     if ( IfCollisionFatal() )
250         return;
251 
252     // Don't rotate at low speeds on runway.
253     const Speed manueveringSpeed = \
254         GetPhysics().ComputeLiftoffSpeed() - physics::conv::KPH2Speed(SpeedKPH(MANEUVER_LIFTOFF_DELTA));
255     if ( IfCollisionRunway() and (GetPhysics().ComputeSpeed() < manueveringSpeed ) )
256         return;
257 
258     // Get the maximum rotation rate (degree/sec) of this axis.
259     Degree deg = 0.0f;
260     switch ( axis )
261     {
262         case AXIS_ROLL:  deg = mPhysics.ComputeRollRate();  break;
263         case AXIS_PITCH: deg = mPhysics.ComputePitchRate(); break;
264         case AXIS_YAW:   deg = mPhysics.ComputeYawRate();   break;
265     }
266     ASSERT( FP_GE<fp>(deg,0.0f) );  // might be zero degree
267 
268     // Multiply by the amount of control input.
269     // Pitching up/down all-the-way: fraction = +-1.0
270     deg = deg * controlFraction;
271 
272     // Divide since rate is measured in seconds
273     // but this rotation occurs in milliseconds.
274     deg = deg * (controlFreq.FPX() / 1000.0f);
275 
276     // Directly rotate.
277     const Radian rad = Deg2Rad( deg );
278     Rotate( axis, rad );
279 
280     // Coordinated turn:
281     // Match yaw with pitch (absolute, not physical/adjusted) during a coordinated turn.
282     // Only for pitch up, not pitch down, as pitch up tightens the turns.
283     mCoordinatedTurnExecuting = false;
284     if ( mCoordinatedTurnEnabled
285      and axis == AXIS_PITCH
286      and IfSameSign<fp>(deg,Control::GetInstance().GetPitchUpDir()) )
287     {
288         AircraftPhysics::eTurning turning = mPhysics.ComputeTurningDir();
289         const Radian radYaw = fp(rad) * 1.05f;
290         if ( turning == AircraftPhysics::eTurningLeft )
291         {
292             mCoordinatedTurnExecuting = true;
293             Rotate( AXIS_YAW, ABS(radYaw) * Control::GetInstance().GetYawLeftDir() );
294         }
295         else if ( turning == AircraftPhysics::eTurningRight )
296         {
297             mCoordinatedTurnExecuting = true;
298             Rotate( AXIS_YAW, ABS(radYaw) * Control::GetInstance().GetYawRightDir() );
299         }
300     }
301 
302     // Animate 3D model: rotate control surfaces (ailerons, rudder).
303     RotateControlSurfaces( axis, controlFraction );
304 
305     // Broadcast command was executed.
306     EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandPhysicalRotate(axis,controlFraction,controlFreq) ) );
307 }
308 
309 /*****************************************************************************
310  * Set throttle.
311  * throttle = {0.0,..,1.0}
312  * @param   throttle
313  *          This method will clamp if necessary.
314  *****************************************************************************/
315 void
SetThrottle(const fp throttle)316 Aircraft::SetThrottle( const fp throttle )
317 {
318 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
319 
320     mThrottle = Clamp1( throttle );
321 
322     // Set thrust of physics model.
323     mPhysics.SetThrust( mPhysics.ComputeMaxThrust() * Newton1(mThrottle) );
324 
325     // Broadcast command was executed.
326     EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandThrottle(throttle) ) );
327 }
328 
329 /*****************************************************************************
330  * Enable/disable brakes.
331  *****************************************************************************/
332 void
EnableBrakes(const bool enable)333 Aircraft::EnableBrakes( const bool enable )
334 {
335 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
336 
337     // AircraftPhysics determines magnitude of drag force
338     // depending on air-brakes or wheel-brakes.
339     GetPhysics().EnableBrakes( enable );
340 
341     // Broadcast command was executed.
342     EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandBrakes(enable) ) );
343 }
344 
345 bool
IfBrakes(void)346 Aircraft::IfBrakes( void )
347 {
348 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
349 
350     return GetPhysics().IfBrakes();
351 }
352 
353 bool
IfWheelBrakes(void)354 Aircraft::IfWheelBrakes( void )
355 {
356 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
357 
358     return GetPhysics().IfWheelBrakes();
359 }
360 
361 bool
IfAirBrakes(void)362 Aircraft::IfAirBrakes( void )
363 {
364 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
365 
366     return GetPhysics().IfAirBrakes();
367 }
368 
369 /*****************************************************************************
370  * Enable/disable landing gear.
371  * SUBTLE: This becomes a NOP if speed is zero!
372  *****************************************************************************/
373 void
SetLandingGear(const bool down)374 Aircraft::SetLandingGear( const bool down )
375 {
376 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
377 
378     // IfCanSetLandingGear() was already called by derivative.
379 
380     mLandingGearDown = down;
381 
382     // Broadcast command was executed.
383     EVENT_CONTROL_COMMAND.Broadcast( CommandEvent( this, new CommandLandingGear(down) ) );
384 
385     // (Let Lua decide when to play landing-gear sound)
386 }
387 
388 /*****************************************************************************
389  * Prevent silliness such as retracting landing gear on runway.
390  *****************************************************************************/
391 bool
IfCanSetLandingGear(const bool down)392 Aircraft::IfCanSetLandingGear( const bool down )
393 {
394 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
395 
396     return down or IfFlying();
397 }
398 
399 /*****************************************************************************
400  * @return True if landing gear is extended down.
401  *****************************************************************************/
402 bool
IfLandingGear(void)403 Aircraft::IfLandingGear( void )
404 {
405 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
406 
407     return mLandingGearDown;
408 }
409 
410 /*****************************************************************************
411  * Coordinated-turn methods.
412  *****************************************************************************/
413 void
EnableCoordinatedTurn(const bool enable)414 Aircraft::EnableCoordinatedTurn( const bool enable )
415 {
416 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
417 
418     mCoordinatedTurnEnabled = enable;
419 }
420 
421 bool
IfCoordinatedTurnEnabled(void) const422 Aircraft::IfCoordinatedTurnEnabled( void ) const
423 {
424 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
425 
426     return mCoordinatedTurnEnabled;
427 }
428 
429 bool
IfCoordinatedTurnExecuting(void) const430 Aircraft::IfCoordinatedTurnExecuting( void ) const
431 {
432 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
433 
434     return mCoordinatedTurnExecuting;
435 }
436 
437 /*****************************************************************************
438  * Animation.
439  *****************************************************************************/
440 void
Tick(const Milliseconds millisecElapsed)441 Aircraft::Tick( const Milliseconds millisecElapsed )
442 {
443 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
444 
445     // First clear collision flag (unless fatal)
446     // to detect when aircraft flies-off runway/carrier.
447     if ( not IfCollisionFatal() )
448         SetCollision( Object::COLLISION_NONE );
449 
450     // Does collision-detection.
451     Parent::Tick( millisecElapsed );
452 
453     // This updates some states that aren't updated by AircraftPhysics.
454     UpdateState( millisecElapsed );
455 
456     // Physics model will move (and possibly rotate) this Aircraft.
457     mPhysics.Tick( millisecElapsed );
458 
459     // Stalling?
460     if ( mPhysics.IfStall() )
461     {
462         AnimateStall();
463     }
464 
465     // Update mOnRunway flag.
466     // Normally, aircraft is colliding into runway/carrier.
467     if ( not IfCollision() )
468         mOnRunway = false;
469 }
470 
471 /*****************************************************************************
472  * The physics model determines one cause of a collision
473  * but other causes are possible.
474  *****************************************************************************/
475 void
HandleCollisions(const Dyna::Colliders & colliders)476 Aircraft::HandleCollisions( const Dyna::Colliders& colliders )
477 {
478 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
479 
480     // Determine if a collision, detected earlier, is fatal.
481     if ( not IfCollisionFatal() )
482     {
483         for ( Dyna::Colliders::const_iterator iter = colliders.begin();
484               iter != colliders.end();
485               ++iter )
486         {
487             // AircraftPhysics catches if Aircraft collides too fast into carrier.
488             // At this point, Aircraft has either "safely collided" into carrier
489             // or collided into another Object.
490             shptr<Object> collider = *iter;
491             CHECK_TYPESIG(collider,TYPESIG_OBJECT);
492             if ( collider->GetName() == "NimitzCarrier" )
493             {
494                 // Aircraft collided into carrier.
495                 // Colliding into runway is safe.
496                 // But did Aircraft collide into another part of the carrier?
497                 SafePtr<Carrier> carrier = static_cast<Carrier*>( collider.PTR() );
498                 CHECK_TYPESIG(carrier,TYPESIG_CARRIER);
499                 if ( carrier->IfRunwayAltitude(GetPosition()) )
500                 {
501                     // False collision.
502                   //SetCollision( COLLISION_NONE );  // else aircraft will fall-thru carrier deck
503                     mOnRunway = true;
504                 }
505                 else
506                 {
507                     // Crashed below flight deck or into the tower.
508                     SetCollision( COLLISION_FATAL );
509                 }
510             }
511             else
512             {
513                 // Collided into another Object.
514                 SetCollision( COLLISION_FATAL );
515             }
516         }
517     }
518 }
519 
520 /*****************************************************************************
521  * Respond to fatal collision.
522  *****************************************************************************/
523 void
SetCollision(const Object::eCollision collision)524 Aircraft::SetCollision( const Object::eCollision collision )
525 {
526 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
527 
528     // If transition to fatal collision, make an explosion once.
529     if ( collision == COLLISION_FATAL and not IfCollisionFatal() )
530     {
531         // Visual explosion.
532         // Careful, if this Aircraft is a zombie, its radius is zero.
533         const fp radius = (GetRadius() > 0.01f) ? GetRadius() : world::conv::Meters2Sim( Meter(8.0f) );
534         GET_FX().MakeExplosion( AIRCRAFT_EXPLOSION_LIFETIME, GetPosition(), radius );
535 
536         // Play a crashing sound.
537         if ( this == GET_CURRENT_CRAFT().PTR() )
538         {
539             StopEngineSound();
540             GET_SOUND().Play( "crash.wav.gz", sound::defs::PLAY_ONCE );
541         }
542 
543         // Stop engine/propeller.
544         SetThrottle( 0.0f );
545 
546         // Was aircraft hit up in the sky?
547         const SphereVertex sphPos = world::conv::WorldVertex2SphereVertex( GetPosition() );
548         if ( sphPos.mAlt > MAX_ALT_RENDER_EXPLODING_AIRCRAFT )
549         {
550             // Make aircraft object invisible if it explodes up in the sky.
551             // Ideally should animate bits-and-pieces of aircraft that fall down.
552             GetGraph()->Enable( false );
553         }
554     }
555 
556     // Afterwards.
557     Parent::SetCollision( collision );
558 }
559 
560 /*****************************************************************************
561  * Update Aircraft for states that aren't computed elsewhere.
562  *****************************************************************************/
563 void
UpdateState(const Milliseconds millisecElapsed)564 Aircraft::UpdateState( const Milliseconds millisecElapsed )
565 {
566 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
567 
568     // Dampen state-transitions by updating at a low frequency (a few seconds).
569     // A reason is to avoid fluttering between TAKEOFF and FLYING.
570     if ( millisecElapsed - mStateUpdateTime < STATE_UPDATE_FREQ )
571         return;
572 
573     // Update timestamp.
574     mStateUpdateTime = millisecElapsed;
575 
576     // Vars.
577     AircraftPhysics& aircraftPhysics = GetPhysics();
578     const Speed   speed        = aircraftPhysics.ComputeSpeed();
579     const bool    onRunway     = IfCollisionRunway();
580 
581     // State transitions.
582     switch ( GetState() )
583     {
584         case eState_PARKED:
585         {
586             // PARKED --> TAKEOFF
587             if ( speed > SPEED_MINIMAL )
588                 SetState( eState_TAKEOFF );
589         }
590         break;
591 
592         case eState_TAKEOFF:
593         {
594             // TAKEOFF --> FLYING
595             // TAKEOFF --> PARKED (rare, if pilot stopped during takeoff)
596             if ( not onRunway )
597                 SetState( eState_FLYING );
598             else if ( speed < SPEED_MINIMAL )
599                 SetState( eState_PARKED );
600         }
601         break;
602 
603         case eState_FLYING:
604         {
605             // FLYING --> LANDING
606             if ( onRunway )
607                 SetState( eState_LANDING );
608         }
609         break;
610 
611         case eState_LANDING:
612         {
613             // LANDING --> FLYING (pilot missed landing on carrier runway)
614             // LANDING --> PARKED
615             if ( not onRunway )
616                 SetState( eState_FLYING );
617             else if ( speed < SPEED_MINIMAL )
618                 SetState( eState_PARKED );
619         }
620         break;
621 
622         default:  // NOP
623         break;
624     }
625 }
626 
627 /*****************************************************************************
628  * Animate the aerodynamic stalling of a Aircraft.
629  *****************************************************************************/
630 void
AnimateStall(void)631 Aircraft::AnimateStall( void )
632 {
633 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
634 
635     // Aircraft should begin to slightly spin when slight below stall-speed,
636     // then gradually spin worse as speed declines.
637     // Conversely, spinning should decrease as speed increases.
638     // AircraftPhysics model will compute 1.0 for maximum stall (or more if excessive stall).
639     fp stallFactor = mPhysics.ComputeStall();
640     stallFactor = std::sin( stallFactor * math::RADIAN_90 );
641     stallFactor += Random::random_f( 0.35f );
642     Rotate( AXIS_PITCH,  stallFactor * Deg2Rad(STALL_DEGREE_ANIMATION * Degree(3.00f)) * GET_CONTROL().GetPitchDownDir() );
643     Rotate( AXIS_YAW,    stallFactor * Deg2Rad(STALL_DEGREE_ANIMATION * Degree(1.00f)) );
644     Rotate( AXIS_ROLL,  -stallFactor * Deg2Rad(STALL_DEGREE_ANIMATION * Degree(2.00f)) );
645 }
646 
647 /*****************************************************************************
648  * Aircraft states (parked, flying, etc).
649  *****************************************************************************/
650 void
SetState(const eState state)651 Aircraft::SetState( const eState state )
652 {
653 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
654 
655     mState = state;
656 }
657 
658 Aircraft::eState
GetState(void)659 Aircraft::GetState( void )
660 {
661 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
662 
663     return mState;
664 }
665 
666 /*****************************************************************************
667  * Collision methods.
668  *****************************************************************************/
669 bool
IfCollisionRunway(void)670 Aircraft::IfCollisionRunway( void )
671 {
672 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
673 
674     return mOnRunway;
675 }
676 
677 /*****************************************************************************
678  * For state-sorting.
679  *****************************************************************************/
680 NodeSort
GetNodeSort(void)681 Aircraft::GetNodeSort( void )
682 {
683 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
684 ASSERT( GetSpecs().mModelShader != "" );
685 
686     NodeSort::Attribs::Int attribBits = mShadowCaster ? NodeSort::Attribs::SHADOW_CASTER : 0;
687     return NodeSort( ShaderName(GetSpecs().mModelShader), NodeSort::Attribs(attribBits) );
688 }
689 
690 /*****************************************************************************
691  * @return Height in world space of Object.
692  *****************************************************************************/
693 fp
GetHeight(void)694 Aircraft::GetHeight( void )
695 {
696 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
697 
698     return world::conv::Meters2Sim( GetSpecs().mHeight );
699 }
700 
701 /*****************************************************************************
702  * Play engine sound.  Controlled by Lua.
703  *****************************************************************************/
704 void
PlayEngineSound(const fp volume)705 Aircraft::PlayEngineSound( const fp volume )
706 {
707 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
708 
709     if ( this == GET_CURRENT_CRAFT().PTR() )
710     {
711         // Play sound for piston or jet engine.
712         if ( IfHasPropeller() )
713         {
714             // If no engine sound yet, play engine-cranking sound,
715             // unless playing begin flying.
716             if ( not GET_SOUND().IfPlaying( "piston.wav.gz" )
717              and physics::conv::Speed2KPH(GetPhysics().ComputeSpeed()) < SpeedKPH(2) )
718             {
719                 GET_SOUND().Play( "piston_cough.wav.gz", sound::defs::PLAY_ONCE, 1.0f );
720             }
721 
722             GET_SOUND().Play( "piston.wav.gz", sound::defs::PLAY_LOOP, volume );
723         }
724         else
725         {
726             GET_SOUND().Play( "jet.wav.gz", sound::defs::PLAY_LOOP, volume );
727         }
728     }
729 }
730 
731 /*****************************************************************************
732  * Stop engine sound.
733  *****************************************************************************/
734 void
StopEngineSound(void)735 Aircraft::StopEngineSound( void )
736 {
737 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
738 
739     // These loop.
740     if ( this == GET_CURRENT_CRAFT().PTR() )
741     {
742         GET_SOUND().Stop( "piston.wav.gz" );
743         GET_SOUND().Stop( "jet.wav.gz" );
744     }
745 }
746 
747 /*****************************************************************************
748  * Set this aircraft as the shadow caster.
749  * Should be called prior to SceneGraph::AttachObject() else no effect.
750  *****************************************************************************/
751 void
SetShadowCaster(const bool enable)752 Aircraft::SetShadowCaster( const bool enable )
753 {
754 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
755 
756     // Will affect Aircraft::GetNodeSort() which is called by SceneGraph::AttachObject().
757     mShadowCaster = enable;
758 }
759 
760 /*****************************************************************************
761  * @return True if Aircraft is flying.
762  *****************************************************************************/
763 bool
IfFlying(void)764 Aircraft::IfFlying( void )
765 {
766 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
767 
768     return physics::conv::Speed2KPH(GetPhysics().ComputeSpeed()) > SpeedKPH(10);
769 }
770 
771 /*****************************************************************************
772  * Subroutines for rotating control surfaces.
773  * Rotating rudders of jets is more difficult because they're inclined backwards.
774  *****************************************************************************/
775 void
RotateAileron(Subgraph & subgraph,const fp controlFraction,const eAileron aileron)776 Aircraft::RotateAileron( Subgraph& subgraph, const fp controlFraction, const eAileron aileron )
777 {
778 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
779 
780     RotateAileron( subgraph, controlFraction, aileron, GetSpecs().mAileronOffset );
781 }
782 
783 void
RotateAileron(Subgraph & subgraph,const fp controlFraction,const eAileron aileron,const Vector3 & aileronOffset)784 Aircraft::RotateAileron( Subgraph& subgraph, const fp controlFraction, const eAileron aileron, const Vector3& aileronOffset )
785 {
786 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
787 
788     const AircraftSpecs& specs = GetSpecs();
789     subgraph.Reset();
790     subgraph.Translate( aileronOffset );                                    // move along fuselage
791     subgraph.Rotate( MODEL_AXIS_YAW, specs.mAileronAngle * int(aileron) );  // in case wing is swept (jets)
792     subgraph.Rotate( MODEL_AXIS_PITCH,
793                      specs.mAileronROF * controlFraction * int(aileron) );  // "aileron" = +-1
794     subgraph.Rotate( MODEL_AXIS_YAW, -specs.mAileronAngle * int(aileron) ); // undo
795     subgraph.Translate( -aileronOffset );                                   // undo
796 }
797 
798 void
RotateElevator(Subgraph & subgraph,const fp controlFraction)799 Aircraft::RotateElevator( Subgraph& subgraph, const fp controlFraction )
800 {
801 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
802 
803     const AircraftSpecs& specs = GetSpecs();
804     subgraph.Reset();
805     subgraph.Translate( specs.mElevatorOffset );                                // move along fuselage
806     subgraph.Rotate( MODEL_AXIS_PITCH, specs.mElevatorROF * controlFraction );  // ROF : "radian of freedom"
807     subgraph.Translate( -specs.mElevatorOffset );                               // undo
808 }
809 
810 void
RotateRudder(Subgraph & subgraph,const fp controlFraction,const eRudder rudder)811 Aircraft::RotateRudder( Subgraph& subgraph, const fp controlFraction, const eRudder rudder )
812 {
813 CHECK_TYPESIG(this,TYPESIG_AIRCRAFT);
814 
815     const AircraftSpecs& specs = GetSpecs();
816     const Vector3& rudderOffset = (rudder == SINGLE_RUDDER or rudder == LEFT_RUDDER)
817                                 ? specs.mRudderOffset : specs.mRudder2Offset;
818     subgraph.Reset();
819     subgraph.Translate( rudderOffset );                                     // move along fuselage
820     subgraph.Rotate( MODEL_AXIS_PITCH, -specs.mRudderAngle );               // in case rudder is inclined (jets)
821     subgraph.Rotate( MODEL_AXIS_YAW, -controlFraction * specs.mRudderROF ); // ROF : "radian of freedom"
822     subgraph.Rotate( MODEL_AXIS_PITCH, specs.mRudderAngle );                // undo
823     subgraph.Translate( -rudderOffset );                                    // undo
824 }
825 
826 } // namespace object
827