/*****************************************************************************
 * $Id: mod_base_craft.cc,v 1.1 2006/11/24 21:56:57 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Craft class.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define MOD_BASE_CRAFT_CC 1
#include "mod_base_common.hh"
#include "mod_base_craft.hh"
#include "mod_chase_plane.hh"
#include "mod_base_combat.hh"
#include "mod_pilot.hh"
#include "gfx_random.hh"

#if TYPESIGS
#define CHECK_TYPESIGS_CRAFT() \
	CHECK_TYPESIG(this,TYPESIG_OBJECT); \
	CHECK_TYPESIG(GET_PTR(GetQuadrant()),TYPESIG_QUADRANT);
#else
#define CHECK_TYPESIGS_CRAFT()
#endif

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
Craft::Craft( QuadrantPtrConst q, const Position& pos, const vector<string>& fnameModelVec )
:	DynaModel(q,pos,fnameModelVec)
{
	CHECK_TYPESIG(q,TYPESIG_QUADRANT);
	SET_TYPESIG( this, TYPESIG_CRAFT );
	// Replace stub Pilot/ChasePlane made by base Dyna with real ones.
	delete mPilot;      mPilot      = new PilotModule( *this );
	delete mChasePlane; mChasePlane	= new ChasePlaneCraft( *this );
	mXm[Yy]				= -1.0;
	SyncMatrix();
	mYmin				= WORLD(10.0);
	CopyMatrix( mXmOrig, mXm );
	mExplosion			= 0;
	mPostTakeoff		= false;
	mGunCnt				= 1;
	// Call GetVertexCntMax() rather than GetVertexCnt() to get
	// a count of vertexs great enough to accomodate all polygon lists.
	const int vertexCntMax = GetVertexCntMax();
	ASSERT( vertexCntMax > 0 );
	mEyeVertexs			= new EyeVertex[   vertexCntMax ];
	mShadowWorldVertexs	= new WorldVertex[ vertexCntMax ];
	mShadowEyeVertexs	= new EyeVertex[   vertexCntMax ];
	SetNeedRecalc();
	FinalConstruction();
	DEBUG_CODE( CheckObjectCtor(); )
}
void
Craft::FinalConstruction( void )
{
	// nothing
}

/*****************************************************************************
 * Destructor.
 *****************************************************************************/
Craft::~Craft()
{
	DELETE_NULL( mPilot );
	DELETE_NULL( mChasePlane );
	DELETE_ARR_NULL( mEyeVertexs );
	DELETE_ARR_NULL( mShadowWorldVertexs );
	DELETE_ARR_NULL( mShadowEyeVertexs );
	// let ~Object() delete Detector object
	INVALIDATE_TYPESIG( this, TYPESIG_CRAFT );
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Reset object.
 * Purpose is to be used as part of resetting scene, not as a full initialization.
 *****************************************************************************/
void
Craft::Reset( void )
{
CHECK_TYPESIGS_CRAFT();
	Parent::Reset();
	CopyMatrix( mXm, mXmOrig );
	SyncMatrix();
	GetQuadrant()->DynaMoved( *this ); // restoring matrix is a translation
	SetAnimation( eAnimation_NONE );
	mSpeed			= 0;
	SetThrottle( 0 );
	SetNeedRecalc();
	mExplosion		= 0;
	mPostTakeoff	= false;
	mChasePlane->Reset();
}

/*****************************************************************************
 * For when Reset() is called.
 *****************************************************************************/
void
Craft::Save( void )
{
	CopyMatrix( mXmOrig, mXm );
}

/*****************************************************************************
 * Called when state is reloaded.
 *****************************************************************************/
void
Craft::Reload( const MatrixExt& xmReload )
{
	// Reset() is called which copies xm_orig[] over xm[].
	// Temporarily copy the reloaded xm[] into xm_orig[].
	// When the view is reset (Backspace), the two crafts
	// will be reset as if no reload was ever done.
	MatrixExt xmOrigCopy;
	CopyMatrix( xmOrigCopy,  mXmOrig   );
	CopyMatrix( mXm,         xmReload );
	CopyMatrix( mXmOrig,     xmReload );
	SyncMatrix();
	Reset();
	CopyMatrix( mXmOrig, xmOrigCopy );
}

/*****************************************************************************
 * Draw object.
 *****************************************************************************/
void
Craft::Draw( void )
{
CHECK_TYPESIGS_CRAFT();

	// See DrawObjects().
	if ( gEng.mView.GetRenderPass() != 2 )
		return;

	// Draw explosion?
	if ( UX( IfDestroyed() ) )
	{
		DrawExplosion();
		return;
	}

	// Draw shadow?
	if ( EX( gEng.IfAtLeastLodUser( eLodUser_MID ) ) )
		DrawShadow();

	// Draw polygons.
	GetPolygonList().Draw( this );
}

/*****************************************************************************
 * Draw shadow.
 *****************************************************************************/
void
Craft::DrawShadow( void )
{
CHECK_TYPESIGS_CRAFT();

	const fp alt = WORLD(500.0); // shadow rendered if altitude below this

	// Return if craft is too high.
	if ( mCm[Oy] >= alt )
		return;

	const uint vertexCnt = GetVertexCnt();

	// Transform local --> world (if craft was recently transformed).
	if ( EX(mNeedRecalcShadowWorldVertexs) )
	{
		mNeedRecalcShadowWorldVertexs = false;

		// Scale of shadow is a function of the altitude.
		// (Altitude must be < h at this point)
		fp s = (alt - mCm[Oy]) * (1/alt);
		s = s * s;

		// Transform local --> world.
		const LocalVertex* localVertexs = GetLocalVertexs();
		for ( uint i = 0; i < vertexCnt; ++i )
		{
			// Scale down model's shadow according to altitude.
			LocalVertex vl2;
			vl2.x = localVertexs[i].x * s;
			vl2.y = localVertexs[i].y * s;
			vl2.z = localVertexs[i].z * s;

			TRANSFORM_VERTEX( mShadowWorldVertexs[i], vl2, mCm );

			// Clamp shadow to ground-level.
			// GetGroundY() returns the Y coordinate of the terrain at a 3D point.
			mShadowWorldVertexs[i].y = gWorld.GetGroundY( mShadowWorldVertexs[i] );
		}
	}

	// Transform world --> eye.
	TRANSFORM_VERTEX( mShadowEyeVertexs, mShadowWorldVertexs, vertexCnt );

	// Draw polygons.
	static const RGBA rgbaShadow( 0.1, 0.1, 0.1, 0.6 );
	static DrawParm drawParm( eDraw(eDraw_BLEND|eDraw_NO_CULL|eDraw_NO_VERTEX_ARRAY), rgbaShadow );
	drawParm.SetEyeVertexs( mShadowEyeVertexs );
	GetPolygonList().Draw( this, drawParm );
}

/*****************************************************************************
 * Craft was destroyed.  Render explosions.
 *****************************************************************************/
void
Craft::DrawExplosion( void )
{
CHECK_TYPESIGS_CRAFT();
	::DrawExplosion( *this,
                     random_ui(5) + 25,
                     random_f(WORLD(15.0)) + WORLD(15.0),
                     WORLD(40.0) );
}

/*****************************************************************************
 * Translate an object.
 *****************************************************************************/
void
Craft::Translate( uint axis, fp inc )
{
CHECK_TYPESIGS_CRAFT();
	// Craft/Eye Translate() have similarities.

	// Translate.
	TranslateMatrixLocal( axis, (fpx)inc, mXm );
	SyncMatrix();
	bool belowGround = gWorld.IfBelowGround( GetPosition() ); // before Fence() correction
	Fence();
	GetQuadrant()->DynaMoved( *this );
	SetNeedRecalc();

	// Skip crash tests if the auto-pilot is probing (experimenting with maneuvers).
	if ( EX( ! mPilot->IfProbing() ) )
	{
		// Crashed into land?  (ignore during takeoff)
		if ( UX( belowGround && gModule.IfCollisions() && mPostTakeoff ) )
		{
			SetAnimation( eAnimation_COLLISION_CRASH_HARD );  // stop AUTO_TRANSLATE
			return;
		}
		else
		{
			mPostTakeoff = true;
		}

		// Collided into another object?
		if ( UX( CollisionDetection( inc ) ) )
		{
			TranslateMatrixLocal( axis, (fpx)-inc, mXm ); // undo translation
			SyncMatrix();
			AddAnimation( eAnimation_COLLISION_CRASH_HARD );
			return;
		}
	}
}

/*****************************************************************************
 * Test if craft collided into any other object.
 * @param	inc
 *          Z increment.  Should be negative for forward translation.
 *          Equals the distance between pre-translation and post-translation.
 *****************************************************************************/
bool
Craft::CollisionDetection( const fp inc )
{
CHECK_TYPESIGS_CRAFT();
	// Moving backwards?
	if ( UX( inc > 0.0 ) )
		return false; // moving backwards is too rare, just return

	// Collsion-detection disabled?
	if ( ! gModule.IfCollisions() )
		return false;

	// Translate the Craft along the vector from the matrix origin's
	// current state back to its previous state.
	//
	// | <--------- translation |
	//
	//           |   
    // *........ +--o- .......*
    // previous                current
    // origin                  origin

	bool collision			= false;
	const fpx craftDiameter = GetRadius() * 2.0;
	int strides				= 0;		// count every translation

	for ( fp z = -craftDiameter;		// overshoot by one for more accuracy/coverage
          z <= ABS(inc)+craftDiameter;
          z += craftDiameter, ++strides )
	{
		// Translate the Craft backwards by the Craft's diameter,
		// towards where it was before it was translated by "inc".
		TranslateMatrixLocal( AXIS_Z, craftDiameter, mXm );	// positive translates backwards
		SyncMatrix();

		// Collision at this point?
		collision = Parent::CollisionDetection();
		if ( UX( collision ) ) break;
	}

	// Undo all the translations (restore Craft's matrix).
	TranslateMatrixLocal( AXIS_Z, -craftDiameter*strides, mXm );
	SyncMatrix();

	return collision;
}

/*****************************************************************************
 * Let the user cheat by pressing a throttle key after a crash to revive his craft.
 *****************************************************************************/
void
Craft::Revive( void )
{
CHECK_TYPESIGS_CRAFT();
	mExplosion = 0;
	SetAnimation( eAnimation_AUTO_TRANSLATE );
}

/*****************************************************************************
 * Animation is driven by a timer.
 * See eng_dyna.hh about relationship of Animate() methods.
 *****************************************************************************/
int
Craft::Animate( void )
{
CHECK_TYPESIGS_CRAFT();

	// Cases not handled here are deferred to base Dyna::Animate().

	int redisplay = 0;
	redisplay |= (--mExplosion == 0); // redisplay when mExplosion==0 to undo red color

	if ( QueryAnimation( eAnimation_COLLISION_SHOTDOWN_HARD )
      || QueryAnimation( eAnimation_COLLISION_SHOTDOWN_SOFT ) )
	{
		// A shot-down craft will continue to move downward.
		const fpx stepDown = WORLD(15.0);
		TranslateMatrixLocal( AXIS_Y, stepDown, mXm );
		// Stop moving forward when it hits the ground.
		Position pos( mCm[Ox], ABS(mCm[Oy]) - ABS(stepDown), mCm[Oz] );
		if ( ! gWorld.IfBelowGround( pos ) ) TranslateMatrixLocal( AXIS_Z, stepDown, mXm );
		SyncMatrix();
		Fence();
		redisplay = true;
	}
	else if ( QueryAnimation( eAnimation_COLLISION_CRASH_HARD )
          ||  QueryAnimation( eAnimation_COLLISION_CRASH_SOFT ) )
	{
		// A crash stops movement animation (NOP).
	}
	else
	{
		// Defer to Dyna class.
		redisplay |= Parent::Animate();
	}

	return redisplay;
}

/*****************************************************************************
 * Throttle.
 *****************************************************************************/
void
Craft::SetThrottle( Percent percent )
{
	mThrottle = percent;

	// TODO: IN LIEUE OF PHYSICS ACCELERATION MODEL.
	static const fp speedTable[] =
	{
		 0.00,	// 0
		 0.50,	// 1
		 1.00,	// 2
		 2.50,	// 3
		 5.00,	// 4
		 7.50,	// 5
		10.00,	// 6
		15.00,	// 7
	    20.00,	// 8
		35.00,	// 9
		55.00,	// 10
	};

	fp speedFrac = speedTable[Min(10,percent/10)] * 2;

	// Slower if under pilot control.
	if ( QueryAnimation(eAnimation_PILOT_PURSUE) )
		speedFrac *= 0.1;

	mSpeed = speedFrac * fp(INC_TRANSLATE) / fp(DEFAULT_TIMER_MILSEC);
}

/*****************************************************************************
 * For each Craft object.
 *****************************************************************************/
void
ForEachCraft( void(*func)(CraftPtrConst,long), long userdata )
{
	for ( const Dlink<Craft>* dlink = gModule.mCraftDlist.ForStart();
          gModule.mCraftDlist.ForTest(dlink);
          gModule.mCraftDlist.ForNext(dlink) )
	{
		Craft* craft = dlink->mObj; CHECK_TYPESIG(craft,TYPESIG_CRAFT);
		func( craft, userdata );
	}
}

/*****************************************************************************
 * These events are broadcast from the engine.
 *****************************************************************************/
static void
DrawObjectsCraft( CraftPtrConst craft, UNUSED long userdata )
{
CHECK_TYPESIG(craft,TYPESIG_CRAFT);
	craft->Draw();
}
void
Listener_Craft_DrawObjects( void )
{
	ForEachCraft( DrawObjectsCraft, 0 );
}

static void
ResetSceneCraft( CraftPtrConst craft, UNUSED long userdata )
{
CHECK_TYPESIG(craft,TYPESIG_CRAFT);
	craft->Reset();
}
void
Listener_Craft_ResetScene( void )
{
	ForEachCraft( ResetSceneCraft, 0 );
}
