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