//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "stdafx.h"
#include "GlobalFunctions.h"
#include "HelperInfo.h"
#include "MapAnimator.h"
#include "MapDoc.h"
#include "MapEntity.h"
#include "MapWorld.h"
#include "../common/KeyFrame/KeyFrame.h"


IMPLEMENT_MAPCLASS( CMapAnimator );


//-----------------------------------------------------------------------------
// Purpose: Factory function. Used for creating a CMapKeyFrame from a set
//			of string parameters from the FGD file.
// Input  : *pInfo - Pointer to helper info class which gives us information
//				about how to create the class.
// Output : Returns a pointer to the class, NULL if an error occurs.
//-----------------------------------------------------------------------------
CMapClass *CMapAnimator::CreateMapAnimator(CHelperInfo *pHelperInfo, CMapEntity *pParent)
{
	return(new CMapAnimator);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMapAnimator::CMapAnimator()
{
	IdentityMatrix( m_CoordFrame );
	m_bCurrentlyAnimating = false;

	m_pCurrentKeyFrame = this;

	m_iPositionInterpolator = m_iRotationInterpolator = m_iTimeModifier = 0;
	m_nKeysChanged = 0;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CMapAnimator::~CMapAnimator()
{
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : CMapClass *
//-----------------------------------------------------------------------------
CMapClass *CMapAnimator::Copy(bool bUpdateDependencies)
{
	CMapAnimator *pNew = new CMapAnimator;
	pNew->CopyFrom(this, bUpdateDependencies);
	return pNew;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pObj - 
// Output : CMapClass
//-----------------------------------------------------------------------------
CMapClass *CMapAnimator::CopyFrom(CMapClass *pObj, bool bUpdateDependencies)
{
	CMapKeyFrame::CopyFrom(pObj, bUpdateDependencies);
	CMapAnimator *pFrom = dynamic_cast<CMapAnimator*>( pObj );
	ASSERT( pFrom != NULL );

	memcpy( m_CoordFrame.Base(), pFrom->m_CoordFrame.Base(), sizeof(m_CoordFrame) );
	m_bCurrentlyAnimating = false;
	m_pCurrentKeyFrame = NULL;	// keyframe it's currently at

	m_iTimeModifier = pFrom->m_iTimeModifier;
	m_iPositionInterpolator = pFrom->m_iPositionInterpolator;
	m_iRotationInterpolator = pFrom->m_iRotationInterpolator;

	return this;
}

//-----------------------------------------------------------------------------
// Purpose: Returns a coordinate frame to render in, if the entity is animating
// Input  : matrix - 
// Output : returns true if a new matrix is returned, false if it is invalid
//-----------------------------------------------------------------------------
bool CMapAnimator::GetTransformMatrix( matrix4_t& matrix )
{
	// are we currently animating?
	if ( m_bCurrentlyAnimating )
	{
		memcpy( matrix.Base(), m_CoordFrame.Base(), sizeof(m_CoordFrame) );
		return true;
	}

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Notifies that the entity this is attached to has had a key change
// Input  : key - 
//			value - 
//-----------------------------------------------------------------------------
void CMapAnimator::OnParentKeyChanged( LPCSTR key, LPCSTR value )
{
	if ( !stricmp(key, "TimeModifier") )
	{
		m_iTimeModifier = atoi( value );
	}
	else if ( !stricmp(key, "PositionInterpolator") )
	{
		m_iPositionInterpolator = atoi( value );

		// HACK: Force everything in the path to update. Better to follow our path and update only it.
		UpdateAllDependencies(this);
	}
	else if ( !stricmp(key, "RotationInterpolator") )
	{
		m_iRotationInterpolator = atoi( value );
	}

	m_nKeysChanged++;

	CMapKeyFrame::OnParentKeyChanged( key, value );
}



//-----------------------------------------------------------------------------
// Purpose: Gets the current and previous keyframes for a given time.
// Input  : time - time into sequence
//			pKeyFrame - receives current keyframe pointer
//			pPrevKeyFrame - receives previous keyframe pointer
// Output : time remaining after the thing has reached this key
//-----------------------------------------------------------------------------
float CMapAnimator::GetKeyFramesAtTime( float time, CMapKeyFrame *&pKeyFrame, CMapKeyFrame *&pPrevKeyFrame )
{
	pKeyFrame = this; 
	pPrevKeyFrame = this;

	float outTime = time;

	while ( pKeyFrame )
	{
		if ( pKeyFrame->MoveTime() > outTime )
		{
			break;
		}

		// make sure this anim has enough time
		if ( pKeyFrame->MoveTime() < 0.01f )
		{
			outTime = 0.0f;
			break;
		}

		outTime -= pKeyFrame->MoveTime();
		
		pPrevKeyFrame = pKeyFrame;
		pKeyFrame = pKeyFrame->NextKeyFrame();
	}

	return outTime;
}


//-----------------------------------------------------------------------------
// Purpose: creates a new keyframe at the specified time
// Input  : time - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
CMapEntity *CMapAnimator::CreateNewKeyFrame( float time )
{
	// work out where we are in the animation
	CMapKeyFrame *key;
	CMapKeyFrame *pPrevKey;
	float partialTime = GetKeyFramesAtTime( time, key, pPrevKey );

	CMapEntity *pCurrentEnt = dynamic_cast<CMapEntity*>( key->Parent );

	// check to see if we're direction on a key frame
	Vector posOffset( 0, 0, 0 );
	if ( partialTime == 0 )
	{
		// create this new key frame slightly after the current one, and offset
		posOffset[0] = 64;
	}

	// get our orientation and position at this time
	Vector vOrigin;
	QAngle angles;
	Quaternion qAngles;
	GetAnimationAtTime( key, pPrevKey, partialTime, vOrigin, qAngles, m_iPositionInterpolator, m_iRotationInterpolator );
	QuaternionAngles( qAngles, angles );

	// create the new map entity
	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
	CMapWorld *pWorld = pDoc->GetMapWorld();

	CMapEntity *pNewEntity = new CMapEntity;

	Vector newPos;
	VectorAdd( vOrigin, posOffset, newPos );
	pNewEntity->SetPlaceholder( TRUE );
	pNewEntity->SetOrigin( newPos );
	pNewEntity->SetClass( "keyframe_track" );

	char buf[128];
	sprintf( buf, "%f %f %f", angles[0], angles[1], angles[2] );
	pNewEntity->SetKeyValue( "angles", buf );

	// link it into the keyframe list

	// take over this existing next keyframe pointer
	const char *nextKeyName = pCurrentEnt->GetKeyValue( "NextKey" );
	if ( nextKeyName )
	{
		pNewEntity->SetKeyValue( "NextKey", nextKeyName );
	}
		
	// create a new unique name for this ent
	char newName[128];
	const char *oldName = pCurrentEnt->GetKeyValue( "targetname" );
	if ( !oldName || oldName[0] == 0 )
		oldName = "keyframe";

	CMapEntity::GenerateNewTargetname( oldName, newName, 127 );
	pNewEntity->SetKeyValue( "targetname", newName );

	// point the current entity at the newly created one
	pCurrentEnt->SetKeyValue( "NextKey", newName );

	// copy any relevant values
	const char *keyValue = pCurrentEnt->GetKeyValue( "parentname" );
	if ( keyValue )
		pNewEntity->SetKeyValue( "parentname", keyValue );

	keyValue = pCurrentEnt->GetKeyValue( "MoveSpeed" );
	if ( keyValue )
		pNewEntity->SetKeyValue( "MoveSpeed", keyValue );

	return(pNewEntity);
}


//-----------------------------------------------------------------------------
// Purpose: stops CMapKeyframe from doing it's auto-connect behavior when cloning
// Input  : pClone - 
//-----------------------------------------------------------------------------
void CMapAnimator::OnClone( CMapClass *pClone, CMapWorld *pWorld, CMapObjectList &OriginalList, CMapObjectList &NewList )
{
	CMapClass::OnClone( pClone, pWorld, OriginalList, NewList );
}

//-----------------------------------------------------------------------------
// Purpose: calculates the position of an animating object at a given time in
//			it's animation sequence
// Input  : animTime - 
//			*newOrigin - 
//			*newAngles - 
//-----------------------------------------------------------------------------
void CMapAnimator::GetAnimationAtTime( float animTime, Vector& newOrigin, Quaternion &newAngles )
{
	// setup the animation, given the time

	// get our new position & orientation
	float newTime, totalAnimTime = GetRemainingTime();
	animTime /= totalAnimTime;

	// don't use time modifier until we work out what we're going to do with it
	//Motion_CalculateModifiedTime( animTime, m_iTimeModifier, &newTime );
	newTime = animTime;

	// find out where we are in the keyframe sequence based on the time
	CMapKeyFrame *pPrevKeyFrame;
	float posTime = GetKeyFramesAtTime( newTime * totalAnimTime, m_pCurrentKeyFrame, pPrevKeyFrame );

	// find the position from that keyframe
	GetAnimationAtTime( m_pCurrentKeyFrame, pPrevKeyFrame, posTime, newOrigin, newAngles, m_iPositionInterpolator, m_iRotationInterpolator );
}


//-----------------------------------------------------------------------------
// Purpose: calculates the position of the animating object between two keyframes
// Input  : currentKey - 
//			pPrevKey - 
//			partialTime - 
//			newOrigin - 
//			newAngles - 
//			posInterpolator - 
//			rotInterpolator - 
//-----------------------------------------------------------------------------
void CMapAnimator::GetAnimationAtTime( CMapKeyFrame *currentKey, CMapKeyFrame *pPrevKey, float partialTime, Vector& newOrigin, Quaternion &newAngles, int posInterpolator, int rotInterpolator )
{
	// calculate the proportion of time to be spent on this keyframe
	float animTime;
	if ( currentKey->MoveTime() < 0.01 )
	{
		animTime = 1.0f;
	}
	else
	{
		animTime = partialTime / currentKey->MoveTime();
	}

	ASSERT( animTime >= 0.0f && animTime <= 1.0f );

	IPositionInterpolator *pInterp = currentKey->SetupPositionInterpolator( posInterpolator );

	// setup interpolation keyframes
	Vector keyOrigin;
	Quaternion keyAngles;
	pPrevKey->GetOrigin( keyOrigin );
	pPrevKey->GetQuatAngles( keyAngles );
	pInterp->SetKeyPosition( -1, keyOrigin );
	Motion_SetKeyAngles  ( -1, keyAngles );

	currentKey->GetOrigin( keyOrigin );
	currentKey->GetQuatAngles( keyAngles );
	pInterp->SetKeyPosition( 0, keyOrigin );
	Motion_SetKeyAngles  ( 0, keyAngles );

	currentKey->NextKeyFrame()->GetOrigin( keyOrigin );
	currentKey->NextKeyFrame()->GetQuatAngles( keyAngles );
	pInterp->SetKeyPosition( 1, keyOrigin );
	Motion_SetKeyAngles  ( 1, keyAngles );

	currentKey->NextKeyFrame()->NextKeyFrame()->GetOrigin( keyOrigin );
	currentKey->NextKeyFrame()->NextKeyFrame()->GetQuatAngles( keyAngles );
	pInterp->SetKeyPosition( 2, keyOrigin );
	Motion_SetKeyAngles  ( 2, keyAngles );

	// get our new interpolated position
	// HACK HACK - Hey Brian, look here!!!!
	Vector hackOrigin;
	pInterp->InterpolatePosition( animTime, hackOrigin );

	newOrigin[0] = hackOrigin[0];
	newOrigin[1] = hackOrigin[1];
	newOrigin[2] = hackOrigin[2];
	Motion_InterpolateRotation( animTime, rotInterpolator, newAngles );
}


//-----------------------------------------------------------------------------
// Purpose: Builds the animation transformation matrix for the entity if it's animating
//-----------------------------------------------------------------------------
void CMapAnimator::UpdateAnimation( float animTime )
{
	// only animate if the doc is animating and we're selected
	if ( !CMapDoc::GetActiveMapDoc()->IsAnimating() || !Parent->IsSelected() )
	{
		// we're not animating
		m_bCurrentlyAnimating = false;
		return;
	}

	m_bCurrentlyAnimating = true;

	Vector newOrigin;
	Quaternion newAngles;
	GetAnimationAtTime( animTime, newOrigin, newAngles );

	matrix4_t mat, tmpMat;
	Vector ourOrigin;
	GetOrigin( ourOrigin );
	IdentityMatrix( mat );

	// build us a matrix
	// T(newOrigin)R(angle)T(-ourOrigin)
	IdentityMatrix( m_CoordFrame ) ;

	// transform back to the origin
	for ( int i = 0; i < 3; i++ )
	{
		m_CoordFrame[i][3] = -ourOrigin[i];
	}
	
	// Apply interpolated Rotation
	QuaternionMatrix( newAngles, mat );

	ConcatTransforms( mat, m_CoordFrame, tmpMat );

	// transform back to our new position
	IdentityMatrix( mat );
	for ( i = 0; i < 3; i++ )
	{
		mat[i][3] = newOrigin[i];
	}

	ConcatTransforms( mat, tmpMat, m_CoordFrame );
}


//-----------------------------------------------------------------------------
// Purpose: Rebuilds the line path between keyframe entities
//			samples the interpolator function to get an approximation of the curve
//-----------------------------------------------------------------------------
void CMapAnimator::RebuildPath( void )
{
	CMapClass *pWorld = GetWorldObject( this );
	if ( !pWorld )
	{
		// Sometimes the object isn't linked back into the world yet when RebuildPath() 
		// is called... we will be get called again when needed, but may cause an incorrect
		// (linear-only) path to get drawn temporarily.
		return;
	}

	//
	// Build the path forward from the head. Keep a list of nodes we've visited to
	// use in detecting circularities.
	//
	CMapObjectList VisitedList;
	CMapKeyFrame *pCurKey = this;
	while ( pCurKey != NULL )
	{
		VisitedList.AddTail( pCurKey );

		//
		// Attach ourselves as this keyframe's animator.
		//
		pCurKey->SetAnimator( this );

		//
		// Get the entity parent of this keyframe so we can query keyvalues.
		//
		CMapEntity *pCurEnt = dynamic_cast<CMapEntity *>( pCurKey->GetParent() );
		if ( !pCurEnt )
		{
			return;
		}

		//
		// Find the next keyframe in the path.
		//
		CMapEntity *pNextEnt = pWorld->FindChildByKeyValue( "targetname", pCurEnt->GetKeyValue( "NextKey" ) );
		CMapKeyFrame *pNextKey = NULL;

		if ( pNextEnt )
		{
			pNextKey = pNextEnt->GetChildOfType( ( CMapKeyFrame * )NULL );
			pCurKey->SetNextKeyFrame(pNextKey);
		}
		else
		{
			pCurKey->SetNextKeyFrame( NULL );
		}

		pCurKey = pNextKey;

		//
		// If we detect a circularity, stop.
		//
		if ( VisitedList.Find( pCurKey ) )
		{
			break;
		}
	}

	//
	// Now traverse the path again building the spline points, once again checking
	// the visited list for circularities.
	//
	VisitedList.RemoveAll();
	pCurKey = this;
	CMapKeyFrame *pPrevKey = this;
	while ( pCurKey != NULL )
	{
		VisitedList.AddTail( pCurKey );

		pCurKey->BuildPathSegment(pPrevKey);

		pPrevKey = pCurKey;
		pCurKey = pCurKey->m_pNextKeyFrame;

		//
		// If we detect a circularity, stop.
		//
		if ( VisitedList.Find( pCurKey ) )
		{
			break;
		}
	}
}

