/*****************************************************************************
 * $Id: eng_dyna.cc,v 1.164 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Dyna (dynamic object) class.
 * @remarks	- Quadrant::DynaMoved() must be called after a Dyna object is moved
 *			  in case it has moved from one quadrant into another.
 *			  Movement can occur by translation, changing position, or changing matrix.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_DYNA_CC 1
#include "eng.hh"
#include "eng_dyna.hh"
#include "eng_import.hh"
#include "gfx_random.hh"

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Pilot  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
Pilot::Pilot( Dyna& owner )
:   mOwner(owner)
{
	CHECK_TYPESIG(&owner,TYPESIG_DYNA);
}

/*****************************************************************************
 * Animation is driven by a timer.
 * See eng_dyna.hh about relationship of Animate() methods.
 *****************************************************************************/
int
Pilot::Animate( eAnimate animate )
{
	CHECK_TYPESIG(&mOwner,TYPESIG_DYNA);

	switch ( animate )
	{
		case eAnimate_AUTO_TRANSLATE:
			// Fly forward (one step per frame).
			mOwner.Step();
			return true;
		break;

		case eAnimate_NONE:
			// NOP
		break;

		default:
			ASSERT(0); // a derived class should've handled this case
		break;
	}
	return false;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Dyna  //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

Ptr<Dyna>   Dyna::mgInputOwner;
Matrix      Dyna::mWEm;					// to save memory, changes every a frame

/*****************************************************************************
 * Constructors.
 *****************************************************************************/
Dyna::Dyna( void )
:   mCm(mSm)
{
	SET_TYPESIG(this,TYPESIG_DYNA);
	Init();
	DEBUG_CODE( CheckObjectCtor(); )
}

/*****************************************************************************
 * Initialize object.
 *****************************************************************************/
void
Dyna::Init( void )
{
	// -- init base members --
	Parent::Init();
	// -- init class members --
	mAnimate			= eAnimate_NONE;
	IdentityMatrix( mXm );
	IdentityMatrix( mSm );
	SyncMatrix();
	mAxisDir[XX]		=  1;
	mAxisDir[YY]		= -1; // -1 for any non-eye Dyna object
	mAxisDir[ZZ]		= -1; // -1 for any non-eye Dyna object
	mYmin				= DYNA_MIN_ALTITUDE;
	mSpeed				= 0;
	mThrottle			= 0;
	mManeuverability[AXIS_X] =  60.0 / 1000.0 * 0.8;	// pitch rate (degree/millisecond)
	mManeuverability[AXIS_Y] =  45.0 / 1000.0 * 0.8;	// yaw rate
	mManeuverability[AXIS_Z] =  90.0 / 1000.0 * 0.8;	// roll rate
	mResponse			= 1.0;
	mPilot				= new Pilot( *this );		// Pilot class is a stub
	mChasePlane			= new ChasePlane( *this );	// ChasePlane class is a stub
	mFrameWorldEyeMatrix = FRAME_CNT_INVALID;
	ASSERT( mYmin >= 0 ); // must be positive
}
Dyna::~Dyna()
{
	INVALIDATE_TYPESIG( this, TYPESIG_DYNA );
	DELETE_NULL( mPilot );
	DELETE_NULL( mChasePlane );
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Combine the dyna and eye matrix using matrix multiplication.
 *****************************************************************************/
const Matrix&
Dyna::GetWorldEyeMatrix( void )
{
	// Combine local/world and world/eye transformations into one using matrix multiplication.
	//
	// [ x ] * [ Xx Xy Xz Tx ] = [ x2 ] * [ 1 0 0 Tx ] = [ x3 ] * [ Xx Xy Xz 0 ]
	// [ y ]   [ Yx Yy Yz Ty ]   [ y2 ]   [ 0 1 0 Ty ]   [ y3 ]   [ Yx Yy Yz 0 ]
	// [ z ]   [ Zx Zy Zz Tz ]   [ z2 ]   [ 0 0 1 Tz ]   [ z3 ]   [ Zx Zy Zz 0 ]
	// [ 1 ]   [  0  0  0  1 ]   [ 1  ]   [ 0 0 0  1 ]   [  1 ]   [  0  0  0 1 ]
	//                                    t1                      t3

	static Matrix t1, t2, t3;

	// Avoid re-calculating the WorldEye matrix for the same Dyna in the same frame.
	const uint frame = gEng.mView.GetViewFrameCnt();
	if ( mFrameWorldEyeMatrix != frame )
	{
		// mCm[]:  Dyna world matrix         (local-->world)
		// mWEm[]: combined world/eye matrix (local-->eye)

		IdentityMatrix( t1 );						// fill t1 matrix
		t1[Ox] = gEng.mEye.mCm[Ox];
		t1[Oy] = gEng.mEye.mCm[Oy];
		t1[Oz] = gEng.mEye.mCm[Oz];
		MultiplyMatrix( t2, mCm, t1 );				// t2 = matrixWorld * t1

		CopyMatrix( t3, gEng.mEye.mCm );			// fill t3 matrix
		t3[Ox] = t3[Oy] = t3[Oz] = 0.0;
		MultiplyMatrix( mWEm, t2, t3 );				// matrixWorldEye = t2 * t3

		mFrameWorldEyeMatrix = frame;				// remember it was calculated
	}

	return mWEm;
}

/*****************************************************************************
 * Assign a matrix origin coordinate.
 * @param	Oyxz
 *			Matrix element index {Ox,Oy,Oz}.
 * @param	coord
 *			Value to assign to matrix's Ox,Oy,Oz.  Must be positive.
 *****************************************************************************/
void
Dyna::SetOrigin( uint Oxyz, fp coord )
{
ASSERT( Oxyz >= Ox && Oxyz <= Oz );
ASSERT( coord >= 0 );
	mXm[Oxyz] = coord;			// Ox or Oy or Oz
	SyncMatrix();
}

/*****************************************************************************
 * Compensate for dropped frames under program control but not human control.
 * Eg the player's Craft could annoyingly flip 180' when the system falters.
 *****************************************************************************/
int
Dyna::GetMilsecForDyna( void )
{
	// GetMilsecTimerFreq() returns a constant value.
	// GetMilsecFrame() returns a dynamic value compensating to dropped frames.
	return (gEng.GetCurrent() == this) ? gEng.GetMilsecTimerFreq() : gEng.GetMilsecFrame();
}

/*****************************************************************************
 * Incremental rotation.
 * Compensates for dropped frames.
 * @param axis
 * @param dir
 *        +-1
 *****************************************************************************/
void
Dyna::RotateInc( uint axis, int dir )
{
ASSERT( CHECK_AXIS(axis) );
ASSERT( ABS(dir) == 1 );
	// Compensate for dropped frames.
	fp inc = dir * mManeuverability[axis] * GetMilsecForDyna();
	Rotate( axis, inc );	// calls derived method
}

/*****************************************************************************
 * Incremental translation.
 * Compensates for dropped frames.
 * @param axis
 * @param dir
 *        +1 forward or -1 backward
 * @param stride
 *        If != 0, overrides GetStride().
 *****************************************************************************/
void
Dyna::TranslateInc( uint axis, int dir, fp stride )
{
ASSERT( CHECK_AXIS(axis) );
ASSERT( ABS(dir) == 1 );
	fp inc = (stride != 0) ? stride : GetStride( axis );
	inc *= dir;
	Translate( axis, inc );
}
fp
Dyna::GetStride( uint axis )
{
	fp inc = mSpeed * GetMilsecForDyna();
	//if ( inc == 0 ) inc = INC_TRANSLATE;
	inc *= mAxisDir[axis];	// eye's Z axis is negative
	return inc;
}
// Move forward (translate Z axis).
void
Dyna::Step( void )
{
	TranslateInc( AXIS_Z, 1 );
}

/*****************************************************************************
 * Keep Dyna within the World.
 *****************************************************************************/
void
Dyna::Fence( void )
{
BUSY_BEGIN_ASSERT;
	// World::Fence() might correct the position by reference, but defers
	// actually changing the object's position to the caller (this).
	Position pos = GetPosition();
	if ( gWorld.Fence( *this, pos ) )
	{
		// World::Fence() might set Y=0 which would visually place
		// a Dyna within the ground, so increase to Y=mYmin.
		if ( pos.y < mYmin ) pos.y = mYmin;

		// Change Dyna position which might move it into another Quadrant.
		SetPosition( pos );
		GetQuadrant()->DynaMoved( *this, pos );    // changing position is translation
	}
BUSY_END;
}

/*****************************************************************************
 * Animate.  Driven by a timer.
 * See eng_dyna.hh for relation of Animate() methods.
 *****************************************************************************/
int
Dyna::Animate( void )
{
	// Defer to Pilot object.
	return mPilot->Animate( GetAnimation() );
}

/*****************************************************************************
 * Toggle animation.
 *****************************************************************************/
void
Dyna::ToggleAnimation( eAnimate animate1, eAnimate animate2 )
{
	if ( mAnimate == animate1 )
		mAnimate = animate2;
	else
		mAnimate = animate1;
}

/*****************************************************************************
 * Throttle.
 *****************************************************************************/
void
Dyna::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;
	mSpeed = speedFrac * fp(INC_TRANSLATE) / fp(DEFAULT_TIMER_MILSEC);
}
Percent
Dyna::GetThrottle( void )
{
	return mThrottle;
}

/*****************************************************************************
 * Methods to switch which object an input device controls.
 * The input device(s) is typically a keyboard and/or joystick.
 *****************************************************************************/
void
Dyna::ClaimInput( void )
{
	mgInputOwner = this;
}
// Return true if any input device(s) is/are controlling this object.
bool
Dyna::HaveInput( void )
{
	return mgInputOwner == this;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  DynaMesh  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

DynaPtr				DynaMesh::mWorldVertexsSavedOwner;
const WorldVertex*	DynaMesh::mWorldVertexsSaved;
fp					DynaMesh::mWorldVertexsSaved0x;

/*****************************************************************************
 * Ctor/dtor.
 *****************************************************************************/
DynaMesh::DynaMesh( QuadrantPtrc q )
:   ObjectMesh(q)
{
	Init();
}
void
DynaMesh::Init( void )
{
	mNormals = NULL;		// allocation delayed til GetWorldNormals()
	DEBUG_CODE( CheckObjectCtor(); )
}
DynaMesh::~DynaMesh()
{
	DELETE_ARR_NULL( mNormals );
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Generic GetWorldVertexs() for Dyna objects.
 * Dyna objects without vertexs should override this to return &unused_WorldVertex
 * and GetVertexCnt() { return 0; }.
 *****************************************************************************/
const WorldVertex*
DynaMesh::GetWorldVertexs( void )
{
RETURN_IF_SAVED_DYNA_WORLD_VERTEXS // unless not saved this frame
	// World vertexs of Dyna object shouldn't be cached because they're dynamic.
	uint			   vertexCnt    = GetVertexCnt();
    const LocalVertex* localVertexs = GetLocalVertexs();
	WorldVertex*	   worldVertexs = gEng.mTempArrayWorldVertex.Get( vertexCnt );
	// local --> world
	TRANSFORM_VERTEX( worldVertexs, localVertexs, vertexCnt, mCm );
SAVE_DYNA_WORLD_VERTEXS( worldVertexs )
	return worldVertexs;
}

/*****************************************************************************
 * Transform local vertexs to eye vertexs.
 * local --> world --> eye
 *****************************************************************************/
const EyeVertex*
DynaMesh::GetEyeVertexs( void )
{
	const uint         vcnt = GetVertexCnt();
	const LocalVertex* lv   = GetLocalVertexs();
	EyeVertex*         ev   = gEng.mTempArrayEyeVertex.Get( vcnt );
	// local --> world --> eye
	TRANSFORM_VERTEX( ev, lv, vcnt, GetWorldEyeMatrix() );
	return ev;
}

/*****************************************************************************
 * Since normals are calculated from world vertexs by Object::CalcNormals(),
 * the normals of Dyna objects must be recomputed every frame and cannot be cached,
 * since Dyna world vertexs change.
 * The base Object::GetWorldNormals() must be override because it caches
 * the results of calculating normals.
 *****************************************************************************/
Normal*
DynaMesh::GetWorldNormals( void )
{
	// Allocation of normals array is delayed til this point
	// which is when PolygonList should exist.
	if ( UX(mNormals == NULL) ) mNormals = new Normal[ GetPolygonCnt() + 2 ];
	CalcNormals( mNormals );
	return mNormals;
}

/*****************************************************************************
 * @return True if this Dyna has crashed into another object.
 *****************************************************************************/
bool
DynaMesh::CollisionDetection( const Dyna* gunner ) // gunner arg can be omitted
{
	// Is this Dyna indestructible?
	if ( ! IfDetectorEnabled() ) return false;

CHECK_TYPESIG( GetQuadrant(), TYPESIG_QUADRANT );
	// Iterate thru the list of Detector objects in the quadrant
	// this Dyna object is in.
	const Dlist<Detector>& dlist = GetQuadrant()->GetDetectorDlist();
CHECK_TYPESIG( &dlist, TYPESIG_LIB_DLIST );
	for ( const Dlink<Detector>* dlink = dlist.ForStart(); dlist.ForTest(dlink); dlist.ForNext(dlink) )
	{
CHECK_TYPESIG( dlink, TYPESIG_LIB_DLINK );
		Detector* detector = dlink->mObj;
		// Prevent Dyna object colliding into itself
		// or a gunner's own tracers hitting the gunner itself.
		if (     &static_cast<const Object&>(detector->GetOwner()) == static_cast<const Object*>(this)
              || &static_cast<const Object&>(detector->GetOwner()) == static_cast<const Object*>(gunner) )
			continue;
		if ( detector->Collided( *this ) ) return true;
	}
	return false;
}
