/*****************************************************************************
 * $Id: eng_eye.cc,v 1.117 2006/11/20 00:35:30 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Eye (viewpoint) 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_EYE_CC 1
#include "eng.hh"
#include "eng_eye.hh"

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  Eye  ////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Construct Eye object in a dummy quadrant (safer than passing q=NULL).
 * This is a special case for Eye being a static object (pre-World stage).
 *****************************************************************************/
Eye::Eye( void )
: ObjectData( new Quadrant(Array<3>(),1,1,false) )  // dummy quadrant on heap to avoid
{                                                   // undefined static init order (Eye vs its Quadrant)
	// NOP
}

/*****************************************************************************
 * Initialize object.
 *****************************************************************************/
void
Eye::Init( QuadrantPtrConst q, const Position& pos )
{
	CHECK_TYPESIG(q,TYPESIG_QUADRANT);
	// -- initialize bases --
	Parent::Init();
	// -- Object members --
	SetQuadrant( q );
	SetLink( q->AddObject( this ) );
	// -- Dyna members --
	SetPosition( pos );				// assigns m[Oxyz]
	CopyMatrix( mXmOrig, mXm );
	mAxisDir[XX]		= 1;
	mAxisDir[YY]		= 1;
	mAxisDir[ZZ]		= 1;
	mYmin				= EYE_MIN_ALTITUDE;
	mManeuverability[AXIS_PITCH] = 35.0 / 1000.0;  // degree/millisecond
	mManeuverability[AXIS_YAW]   = 20.0 / 1000.0;
	mManeuverability[AXIS_ROLL]  = 90.0 / 1000.0;
	// -- Eye members --
	mInitialized		= true;
	mEyeMode			= eEyeMode_DEFAULT;
    TransposeMatrix( mXmTranspose, mXm );
    mRollDeg            = Degree(0.0);
	ASSERT( mYmin >= 0 ); // must be positive
	FinalConstruction();
   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::eEyeChange_TOTAL );
}

/*****************************************************************************
 * Reset object.
 * Purpose is to be used as part of resetting scene, not as a full initialization.
 *****************************************************************************/
void
Eye::Reset( void )
{
	Parent::Reset();
	CopyMatrix( mXm, mXmOrig );
	SyncMatrix();
	GetQuadrant()->DynaMoved( *this );	// changing matrix is a translation
	mSpeed = 0;
	SetThrottle( 0 );
   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::eEyeChange_TOTAL );
}

/*****************************************************************************
 * For switching the viewpoint.
 * Save the current state of the eye and then load it.
 *****************************************************************************/
void
Eye::Save( EyeState& state )
{
	// --- converse of Eye::Switch() load incoming -----------
	CopyMatrix( state.mXm, mXm );
	state.mEyeMode = mEyeMode;
	// --- converse of Eye::Switch() load incoming -----------
}
void
Eye::Switch( EyeState& incoming, EyeState& outgoing )
{
	// Save current state to outgoing object.
	Save( outgoing );

	// Load state from incoming object.
	// --- converse of Eye::Save() -----------------------------
	CopyMatrix( mXm, incoming.mXm );
	mEyeMode = incoming.mEyeMode;
	// --- converse of Eye::Save() -----------------------------

	// Synchronize/update.
	SyncMatrix();
	Translate( AXIS_X, 0.0 ); // to induce update
   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::eEyeChange_TOTAL );
}

/*****************************************************************************
 * Translate eye/viewpoint.
 *****************************************************************************/
void
Eye::Translate( uint axis, fp inc, bool byChasePlane )
{
ASSERT( CHECK_AXIS(axis) );
//-- Craft/Eye similar --
	TranslateMatrixFixed( axis, (fpx)inc, mXm );
	SyncMatrix();
	Fence();
	GetQuadrant()->DynaMoved( *this );
//-- Craft/Eye similar --
	// Sorter needs to know the direction of translation.
	// Moving forward, sorting is usually skipped, but not if moving reverse.
   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::EyeChangeEnumTranslate( axis, inc < 0 ? -1 : 1 ) );
}

/*****************************************************************************
 * Rotate eye/viewpoint.
 *****************************************************************************/
void
Eye::Rotate( uint axis, Degree deg )
{
ASSERT( CHECK_AXIS(axis) );
// Allow degrees outside 0..360 (still mathematically correct).

	// Rotate matrix.
	RotateMatrixFixedDegree( axis, deg, mXm );
	SyncMatrix();

    // For Billboard class, accumulate the degree of roll.
	// Billboards need to roll with the eye to maintain their illusion.
    if ( axis == AXIS_ROLL ) mRollDeg += deg; // mRollDeg is for Billboard class

   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::EyeChangeEnumRotate( axis ) );
}

/*****************************************************************************
 * Load the eye's matrix from another matrix.
 *****************************************************************************/
void
Eye::LoadMatrix( const MatrixExt& xm )
{
	CopyMatrix( mXm, xm );
	mXm[Ox] = NEG( mXm[Ox] );	// eye matrix is negative relative to other matrixs
	mXm[Oy] = NEG( mXm[Oy] );
	mXm[Oz] = NEG( mXm[Oz] );
	SyncMatrix();
   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::eEyeChange_TOTAL );
}

/*****************************************************************************
 * 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
Eye::SetOrigin( uint Oxyz, fp coord )
{
ASSERT( Oxyz >= Ox && Oxyz <= Oz );
ASSERT( coord >= 0 );
	// Eye matrix has negative origin.
	mXm[Oxyz] = NEG( coord );		// Ox or Oy or Oz
	SyncMatrix();
   	gEngineEvent.mEyeChange.Broadcast( EngineEvents::eEyeChange_TOTAL );
}

/*****************************************************************************
 * Keep object within the world.
 *****************************************************************************/
void
Eye::Fence( void )
{
	// The base Fence() will work even though the Eye origin is negative
	// as Eye::GetPosition() and Eye::SetPosition(), which Dyna::Fence() calls,
	// use absolute values.
	Parent::Fence();
}

/*****************************************************************************
 * Animate.  Driven by a timer.
 *****************************************************************************/
int
Eye::Animate( void )
{
	// Cases not handled here are deferred to base Dyna::Animate().
	int redisplay = 0;
    if ( QueryAnimation( eAnimation_CHASE_PLANE ) )
    {
    	// Subtlety: If Eye is current, the eye won't really chase itself,
		// as Eye has a dummy ChasePlaneBase object.
		redisplay = gEng.GetCurrent()->mChasePlane->Chase();
    }
    else
    {
    	redisplay = Parent::Animate();
	}
	return redisplay;
}

/*****************************************************************************
 * @return True if the eye/viewpoint has moved the specified distance
 * (if so then an object needs to be recalculated).
 *
 * prevEye (must not be an auto var!) is the vertex of the eye/viewpoint
 * the last time of an object's vertexs were calculated.
 * prevEye should be initialized with (0,0,0) and will be updated by
 * this function when it returns true.
 *****************************************************************************/
bool
Eye::IsRecalcNeeded( EyeVertex& prevEye/*IN/OUT*/, fp distThreshold )
{
	// Fast but less accurate than true distance formula.
	// X,Z (not Y) is added since the XZ position is important, not altitude,
	// to the currently implemented ground object procedures (this may change!).
	fp dist =   mCm[Ox] - prevEye.x
              + mCm[Oz] - prevEye.z;
	dist = ABS(dist);
	if (    (prevEye.x + prevEye.y + prevEye.z) == 0
         || dist > distThreshold )
	{
		prevEye.x = mCm[Ox];	// update
		prevEye.y = mCm[Oy];
		prevEye.z = mCm[Oz];
		return true;
	}
	else
	{
		return false;
	}
}
