//////////////////////////////////////////////////////////////////////////////
//
// Axon_Behaviour_GS2Spline.cpp
//
// Copyright (C) 2008 Rebellion. All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

// core
#include "Core/Asura.h"

#ifdef ASURA_USE_AXON

// header
#include "Behaviour/BehaviourTypes/Axon_Behaviour_GS2Spline.h"

// asura
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_Timers.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/Asura_ServerNode_GS2Node.h"
#include "Entity/Locations/Spline/GamesceneSpline/Asura_ServerNode_GS2Spline.h"
#include "Network/Asura_Message.h"

// axon
#include "AnimTemplates/Axon_Gamescene_AnimTemplate_System.h"
#include "Behaviour/BehaviourParamsTypes/Axon_BehaviourParams_GS2Spline.h"
#include "Behaviour/BehaviourTypes/Axon_Behaviour_GS2Actor.h"
#include "Behaviour/BehaviourTypes/Axon_Behaviour_Types.h"
#include "Core/Axon_Brain.h"
#include "Core/Axon_Messages.h"

//////////////////////////////////////////////////////////////////////////////
//
// Axon_Behaviour_GS2Spline
//
//////////////////////////////////////////////////////////////////////////////

Axon_Behaviour_GS2Spline::Axon_Behaviour_GS2Spline( Axon_Brain* pxBrain )
: PARENT( AXON_BEHAVIOUR_TYPE_GS2_SPLINE, pxBrain )
, m_iState( UNSET )
, m_uAnimSet( uAXON_ANIMATION_SEQUENCE_ID_NONE )
, m_iTraverseMode( AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED )
, m_fArrivalRange( 0.0f )
, m_fRemainingTimeslice( 0.0f )
, m_uSplineEventIndex( 0 )
, m_uAnimQueryFailCount( 0 )
, m_fAnimSetDuration( 0.0f )
, m_xAnimStartPos( Asura_Maths::ZeroVector3 )
, m_xAnimStartQOri( Asura_Maths::IdentityQuat )
, m_xAnimCurrPosOffset( Asura_Maths::ZeroVector3 )
, m_xAnimCurrQOriOffset( Asura_Maths::IdentityQuat )
, m_fDistanceAlongSpline( 0.0f )
, m_fTimeAlongSpline( 0.0f )
, m_fPrevFollowSurfaceDistance( Asura_Maths::LargeFloat )
, m_pxGetToBehaviour( NULL )
{
}

Axon_Behaviour_GS2Spline::Axon_Behaviour_GS2Spline( const u_int uBehaviourType, Axon_Brain* pxBrain )
: PARENT( uBehaviourType, pxBrain )
, m_iState( UNSET )
, m_uAnimSet( uAXON_ANIMATION_SEQUENCE_ID_NONE )
, m_iTraverseMode( AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED )
, m_fArrivalRange( 0.0f )
, m_fRemainingTimeslice( 0.0f )
, m_uSplineEventIndex( 0 )
, m_uAnimQueryFailCount( 0 )
, m_fAnimSetDuration( 0.0f )
, m_xAnimStartPos( Asura_Maths::ZeroVector3 )
, m_xAnimStartQOri( Asura_Maths::IdentityQuat )
, m_xAnimCurrPosOffset( Asura_Maths::ZeroVector3 )
, m_xAnimCurrQOriOffset( Asura_Maths::IdentityQuat )
, m_fDistanceAlongSpline( 0.0f )
, m_fTimeAlongSpline( 0.0f )
, m_fPrevFollowSurfaceDistance( Asura_Maths::LargeFloat )
, m_pxGetToBehaviour( NULL )
{
}

void Axon_Behaviour_GS2Spline::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	u_int uVersion;
	xStream >> uVersion;

	PARENT::ReadFromChunkStream( xStream );

	if ( uVersion == uCURRENT_CHUNK_VERSION )
	{
		xStream >> m_iState;
		xStream >> m_fTimeInState;
		xStream >> m_uAnimSet;
		xStream >> m_iTraverseMode;
		xStream >> m_fArrivalRange;
		xStream >> m_uSplineEventIndex;
		xStream >> m_uAnimQueryFailCount;

		xStream >> m_fAnimSetDuration;
		xStream >> m_xAnimStartPos;
		xStream >> m_xAnimStartQOri;
		xStream >> m_xAnimCurrPosOffset;
		xStream >> m_xAnimCurrQOriOffset;

		xStream >> m_fDistanceAlongSpline;
		xStream >> m_fTimeAlongSpline;
		xStream >> m_fPrevFollowSurfaceDistance;

		bool bChunkedGetToBehaviour;
		xStream >> bChunkedGetToBehaviour;
		if ( bChunkedGetToBehaviour )
		{
			m_pxGetToBehaviour = GetGSActorBehaviour()->CreateGetToBehaviour();
			if ( m_pxGetToBehaviour )
			{
				LinkChild( m_pxGetToBehaviour );
				m_pxGetToBehaviour->ReadFromChunkStream( xStream );
			}
			else
			{
				Asura_Assert( false, "Axon_Behaviour_GS2Spline::ReadFromChunkStream - couldn't create get to behaviour" );
				SetInError();
			}
		}
	}
	else if ( uVersion == 0 )
	{
		xStream >> m_iState;
		xStream >> m_fTimeInState;
		xStream >> m_uAnimSet;
		xStream >> m_iTraverseMode;
		xStream >> m_fArrivalRange;
		xStream >> m_uSplineEventIndex;
		xStream >> m_uAnimQueryFailCount;

		xStream >> m_fAnimSetDuration;
		xStream >> m_xAnimStartPos;
		xStream >> m_xAnimStartQOri;
		xStream >> m_xAnimCurrPosOffset;
		xStream >> m_xAnimCurrQOriOffset;

		xStream >> m_fDistanceAlongSpline;
		xStream >> m_fTimeAlongSpline;
		xStream >> m_fPrevFollowSurfaceDistance;

		bool bChunkedGetToBehaviour;
		xStream >> bChunkedGetToBehaviour;
		if ( bChunkedGetToBehaviour )
		{
			m_pxGetToBehaviour = GetGSActorBehaviour()->CreateGetToBehaviour();
			if ( m_pxGetToBehaviour )
			{
				LinkChild( m_pxGetToBehaviour );
				m_pxGetToBehaviour->ReadFromChunkStream( xStream );
			}
			else
			{
				Asura_Assert( false, "Axon_Behaviour_GS2Spline::ReadFromChunkStream - couldn't create get to behaviour" );
				SetInError();
			}
		}

		Asura_Vector_3  xTargetPosition;
		Asura_Quat      xInitialLerpOriQ;
		Asura_Vector_3  xInitialLerpPos;
		xStream >> xTargetPosition;
		xStream >> xInitialLerpOriQ;
		xStream >> xInitialLerpPos;
	}
	else
	{
		Asura_Assertf( false, ( "Axon_Behaviour_GS2Node::ReadFromChunkStream - unhandled chunk version (%u)", uVersion ) );
	}
}

void Axon_Behaviour_GS2Spline::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << uCURRENT_CHUNK_VERSION;

	PARENT::WriteToChunkStream( xStream );

	xStream << m_iState;
	xStream << m_fTimeInState;
	xStream << m_uAnimSet;
	xStream << m_iTraverseMode;
	xStream << m_fArrivalRange;
	xStream << m_uSplineEventIndex;
	xStream << m_uAnimQueryFailCount;

	xStream << m_fAnimSetDuration;
	xStream << m_xAnimStartPos;
	xStream << m_xAnimStartQOri;
	xStream << m_xAnimCurrPosOffset;
	xStream << m_xAnimCurrQOriOffset;

	xStream << m_fDistanceAlongSpline;
	xStream << m_fTimeAlongSpline;
	xStream << m_fPrevFollowSurfaceDistance;

	if ( m_pxGetToBehaviour )
	{
		xStream << true;
		m_pxGetToBehaviour->WriteToChunkStream( xStream );
	}
	else
	{
		xStream << false;
	}
}

bool Axon_Behaviour_GS2Spline::HandleMessage( const Asura_Message* pxMessage )
{
	if ( pxMessage->GetMsgID() == ASURA_MSG_AXON_GS2_ANIM_CHANGE )
	{
		if ( m_iState == FM_MOVING || m_iState == SPAS_MOVING || m_iState == SPSS_MOVING )
		{
			const Axon_Msg_GS2_Anim_Change* pxAnimChangeMsg = static_cast< const Axon_Msg_GS2_Anim_Change* >( pxMessage->GetData() );
			GetGSActorBehaviour()->PlayAnimSet( pxAnimChangeMsg->m_uAnimSet );
		}
		else
		{
			Asura_Assert( false, "Axon_Behaviour_GS2Spline::HandleMessage - can't service an anim change message in our current state" );
		}
		return true;
	}
	else
	{
		return PARENT::HandleMessage( pxMessage );
	}
}

void Axon_Behaviour_GS2Spline::HandleParams( const Axon_BehaviourParams* pxParams )
{
	PARENT::HandleParams( pxParams );

	Asura_Assert( pxParams->IsBaseOrDerivativeOf( AXON_BP_TYPE_GS2_SPLINE ), "Axon_Behaviour_GS2Spline::HandleParams - unexpected params type" );
	if ( pxParams->IsBaseOrDerivativeOf( AXON_BP_TYPE_GS2_SPLINE ) )
	{
		const Axon_BehaviourParams_GS2Spline* pxSplineParams = static_cast< const Axon_BehaviourParams_GS2Spline* >( pxParams );
		m_uAnimSet		= pxSplineParams->m_uAnimSet;
		m_iTraverseMode = pxSplineParams->m_iTraverseMode;
		m_fArrivalRange = pxSplineParams->m_fArrivalRange;
	}
}

bool Axon_Behaviour_GS2Spline::Activate()
{
	const bool bParentOk = PARENT::Activate();

	const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
	if ( pxLink )
	{
		GetGSActorBehaviour()->EnableOwnerPhysicsCollisions( pxLink->ShouldTestForCollisions() );
	}

	switch ( m_iTraverseMode )
	{
		case AXON_GS2_STM_ANIM_MOVEMENT:			SwitchToState( AM_MOVING );	  break;
		case AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED:	SwitchToState( SPAS_MOVING ); break;
		case AXON_GS2_STM_SPLINE_PATH_SPLINE_SPEED: SwitchToState( SPSS_MOVING ); break;
		case AXON_GS2_STM_FREE_MOVEMENT:			SwitchToState( FM_MOVING );	  break;
		default:
		{
			Asura_Assertf( false, ( "Axon_Behaviour_GS2Spline::Activate - unhandled traverse mode (%d)", m_iTraverseMode ) );
			SetInError();
			SwitchToState( FINISHED );
			break;
		}
	}

	return ( bParentOk && !IsInError() );
}

void Axon_Behaviour_GS2Spline::Deactivate()
{
	SwitchToState( FINISHED );
	PARENT::Deactivate();
}

void Axon_Behaviour_GS2Spline::PerFrameUpdate( const float fTimeslice )
{
	m_fRemainingTimeslice = fTimeslice;

	if ( IsInError() || IsFinished() )
	{
		return;
	}

	PARENT::PerFrameUpdate( fTimeslice );

	m_fTimeInState += fTimeslice;

	switch ( m_iState )
	{
		case UNSET:
		{
			break;
		}

		case AM_MOVING:
		{
			m_fRemainingTimeslice = UpdateAnimMovement( fTimeslice );
			break;
		}

		case SPAS_MOVING:
		{
			m_fRemainingTimeslice = UpdateSPASMovement( fTimeslice );
			break;
		}

		case SPSS_MOVING:
		{
			m_fRemainingTimeslice = UpdateSPSSMovement( fTimeslice );
			break;
		}

		case FM_MOVING:
		{
			UpdateFreeMovement();
			m_fRemainingTimeslice = 0.0f;
			break;
		}

		case FINISHED:
		{
			break;
		}

		default:
		{
			Asura_Assertf( false, ( "Axon_Behaviour_GS2Spline::PerFrameUpdate - bad state (%d)", m_iState ) );
			SetInError();
			SwitchToState( FINISHED );
			break;
		}
	}
}

void Axon_Behaviour_GS2Spline::SwitchToState( int iState )
{
	ExitState( m_iState );
    m_iState       = iState;
	m_fTimeInState = 0.0f;
	EnterState( m_iState );
}

void Axon_Behaviour_GS2Spline::ExitState( int iState )
{
	switch ( iState )
	{
		case FM_MOVING:
		{
			if ( m_pxGetToBehaviour )
			{
				UnlinkChild( m_pxGetToBehaviour );
				m_pxGetToBehaviour->DoDeactivate();
				GetGSActorBehaviour()->DestroyGetToBehaviour( m_pxGetToBehaviour );
				m_pxGetToBehaviour = NULL;
			}

			GetGSActorBehaviour()->SetOwnerPhysicsToExtControl();
		} // fall through
		case AM_MOVING:
		case SPAS_MOVING:
		case SPSS_MOVING:
		{
			const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
			if ( pxLink )
			{
				// Fire off any remaining spline events.
				while ( m_uSplineEventIndex < pxLink->GetNumSplineEvents() )
				{
					pxLink->SendMessagesForSplineEvent( m_uSplineEventIndex );
					++m_uSplineEventIndex;
				}
			}
			GetGSActorBehaviour()->StopAnimSet();
			break;
		}

		case FINISHED:
		{
			SetFinished( false );
			break;
		}

		case UNSET:
		default:
		{
			break;
		}
	}
}

void Axon_Behaviour_GS2Spline::EnterState( int iState )
{
	switch ( iState )
	{
		case AM_MOVING:
		{
			GetGSActorBehaviour()->PlayAnimSet( m_uAnimSet );

			bool bOk = false;
			const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
			if ( pxLink )
			{
				const Asura_ServerNode_GS2Node* pxSourceNode = Asura_ServerNode_GS2Node::FindEntity( pxLink->GetSourceGuid() );
				if ( pxSourceNode )
				{
					m_xAnimStartPos = pxSourceNode->GetPosition();
					pxSourceNode->GetOrientation( m_xAnimStartQOri );
					bOk = true;
				}
			}

			m_fAnimSetDuration = GetGSActorBehaviour()->GetAnimSetDuration( m_uAnimSet );
			bOk &= ( m_fAnimSetDuration > 0.0f );

			if ( !bOk )
			{
				SetInError();
				SwitchToState( FINISHED );
			}

			break;
		}

		case FM_MOVING:
		{
			GetGSActorBehaviour()->PlayAnimSet( m_uAnimSet );
			GetGSActorBehaviour()->ResetOwnerPhysicsFromExtControl();

			bool bOk = false;
			Asura_ServerNode_GS2Spline* pxCurrentSpline = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
			Asura_ServerNode_GS2Node*   pxTargetNode    = NULL;
			if ( pxCurrentSpline )
			{
				pxTargetNode = Asura_ServerNode_GS2Node::FindEntity( pxCurrentSpline->GetTargetGuid() );
			}
			if ( pxTargetNode )
			{
				// Determine the target position from the target node and the follow ground stuff.
				Asura_Vector_3 xTargetPosition = pxTargetNode->GetPosition();
				if ( pxTargetNode->ShouldFollowSurface() )
				{
					Asura_Matrix_3x3 xTargetOrient;
					pxTargetNode->GetOrientation( xTargetOrient );
					const Asura_Vector_3 xDown = Asura_Vector_3( 0.0f, 1.0f, 0.0f ) * xTargetOrient;

					float fDistanceToIntersection = 0.0f;
					if ( Axon_Behaviour_GS2Actor::FollowSurfaceTest( xTargetPosition, xDown, fDistanceToIntersection ) )
					{
						xTargetPosition += xDown * fDistanceToIntersection;
					}
				}

				Axon_Behaviour_GS2Actor::GetToBehaviourParams xGTBParams;
				xGTBParams.SetNode( pxTargetNode );
				if ( m_fArrivalRange > 0.0f )
				{
					// Pick a random position near to the node. We assume the node is orientated to the floor.
					// Get a random vector in the world XZ plane.
					Asura_Vector_3 xRandomVector( 0.0f, 0.0f, Asura_Random::GetFloat( 0.0f, m_fArrivalRange ) );
					Asura_Quat xQYAxisRotation;
					xQYAxisRotation.FromRotation( Asura_Random::GetFloat( -Asura_Maths::Pi, Asura_Maths::Pi ), Asura_Vector_3( 0.0f, 1.0f, 0.0f ) );
					xRandomVector = xQYAxisRotation.RotateVector( xRandomVector );
					// Transform it into the node's local space.
					Asura_Quat xQNodeOrient;
					pxTargetNode->GetOrientation( xQNodeOrient );
					xRandomVector = xQNodeOrient.RotateVector( xRandomVector );

					xTargetPosition += xRandomVector;
					xGTBParams.SetPosition( xTargetPosition );
				}
				else
				{
					xGTBParams.SetPosition( xTargetPosition );
					pxTargetNode->GetOrientation( xGTBParams.m_xOrientation );
					xGTBParams.SetContains( Axon_Behaviour_GS2Actor::GetToBehaviourParams::ORIENTATION );
				}
				m_pxGetToBehaviour = GetGSActorBehaviour()->CreateGetToBehaviour( &xGTBParams );
				if ( m_pxGetToBehaviour )
				{
					// Don't activate this, as it'll be activated directly after us.
					LinkChild( m_pxGetToBehaviour );
					bOk = true;
				}
			}

			if ( !bOk )
			{
				SetInError();
				SwitchToState( FINISHED );
			}

			break;
		}

		case SPAS_MOVING:
		case SPSS_MOVING:
		{
			GetGSActorBehaviour()->PlayAnimSet( m_uAnimSet );
			break;
		}

		case UNSET:
		case FINISHED:
		{
			SetFinished();
			break;
		}

		default:
		{
			Asura_Assertf( false, ( "Axon_Behaviour_GS2Spline::EnterState - switching to bad state (%d)", m_iState ) );
			break;
		}
	}
}

float Axon_Behaviour_GS2Spline::UpdateAnimMovement( const float fTimeslice )
{
	if ( fTimeslice == 0.0f ) { return 0.0f; }

	const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
	if ( !pxLink )
	{
		SetInError();
		SwitchToState( FINISHED );
		return 0.0f;
	}

	Asura_Vector_3 xPositionOffset = Asura_Maths::ZeroVector3;
	Asura_Quat     xQOrientOffset  = Asura_Maths::IdentityQuat;
	if ( GetGSActorBehaviour()->GetOwnerAnimationMovementOffset( xPositionOffset, xQOrientOffset ) )
	{
		m_xAnimCurrPosOffset  = xPositionOffset;
		m_xAnimCurrQOriOffset = xQOrientOffset;

		m_uAnimQueryFailCount = 0;
	}
	else
	{
		xPositionOffset = m_xAnimCurrPosOffset;
		xQOrientOffset  = m_xAnimCurrQOriOffset;

		++m_uAnimQueryFailCount;
	}
	if ( m_uAnimQueryFailCount > 10 )
	{
		// We allow a number of frames to fail as our initial anim request may take a number of frames to action.
		SetInError();
		SwitchToState( FINISHED );
		return 0.0f;
	}
	xPositionOffset = m_xAnimStartQOri.RotateVector( xPositionOffset );

	if ( GetGSActorBehaviour()->SetOwnerPosition( m_xAnimStartPos + xPositionOffset, pxLink->ShouldIgnoreCollisions() ) )
	{
		Asura_Quat xResultantQOri = m_xAnimStartQOri * xQOrientOffset;
		Asura_Matrix_3x3 xOwnerOri;
		xResultantQOri.ToMatrix( xOwnerOri );
		GetGSActorBehaviour()->SetOwnerOrientation( xOwnerOri );

		// To detect when we've finished the anim movement keep the time on the link up to date.
		m_fTimeAlongSpline += fTimeslice;
		const float fProportionThroughAnimSet = m_fTimeAlongSpline / m_fAnimSetDuration;
		m_fDistanceAlongSpline = pxLink->GetTotalLength() * fProportionThroughAnimSet;

		// Fire off any events on this spline we may have passed.
		if ( m_uSplineEventIndex < pxLink->GetNumSplineEvents() && m_fDistanceAlongSpline >= pxLink->GetSplineEventDistance( m_uSplineEventIndex ) )
		{
			pxLink->SendMessagesForSplineEvent( m_uSplineEventIndex );
			++m_uSplineEventIndex;
		}

		const float fRemainingTimeslice = Asura_Maths::Max( m_fTimeAlongSpline - m_fAnimSetDuration, 0.0f );
		if ( fRemainingTimeslice > 0.0f )
		{
			// We have reached the end of the anim set.
			SwitchToState( FINISHED );
		}
		return fRemainingTimeslice;
	}
	else
	{
		return 0.0f;
	}
}

float Axon_Behaviour_GS2Spline::UpdateSPASMovement( const float fTimeslice )
{
	if ( fTimeslice == 0.0f ) { return 0.0f; }

	const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
	if ( !pxLink )
	{
		SetInError();
		SwitchToState( FINISHED );
		return 0.0f;
	}

    Asura_Vector_3 xPosition;
	Asura_Quat     xQOrientation;

	Asura_Vector_3 xAnimVelocity = Asura_Maths::ZeroVector3;
	if ( GetGSActorBehaviour()->GetOwnerAnimationVelocity( xAnimVelocity ) )
	{
		m_uAnimQueryFailCount = 0;
	}
	else
	{
		++m_uAnimQueryFailCount;
	}
	if ( m_uAnimQueryFailCount > 10 )
	{
		// We allow a number of frames to fail as our initial anim request may take a number of frames to action.
		SetInError();
		SwitchToState( FINISHED );
		return 0.0f;
	}
    const float fMovementSpeed    = xAnimVelocity.Magnitude();
	const float fRequiredDistance = m_fDistanceAlongSpline + ( fMovementSpeed * fTimeslice );
	const float fActualDistance   = pxLink->GetPositionFromDistance( fRequiredDistance, xPosition, &xQOrientation );

	// Update the spline position with our follow surface stuff if necessary.
	if ( pxLink->ShouldFollowSurface() )
	{
		Asura_Vector_3 xDown( 0.0f, 1.0f, 0.0f );
		xDown = xQOrientation.RotateVector( xDown );

		float fDistanceToIntersection = Asura_Maths::LargeFloat;
		if ( Axon_Behaviour_GS2Actor::FollowSurfaceTest( xPosition, xDown, fDistanceToIntersection ) )
		{
			m_fPrevFollowSurfaceDistance = fDistanceToIntersection;
		}
		else 
		{
			fDistanceToIntersection = m_fPrevFollowSurfaceDistance;
		}

		if ( fDistanceToIntersection != Asura_Maths::LargeFloat )
		{
			xPosition += xDown * fDistanceToIntersection;
		}
	}

	// If we fail to set the owner position we do not advance along the link, otherwise we will bink position when the obstruction is removed.
	if ( GetGSActorBehaviour()->SetOwnerPosition( xPosition, pxLink->ShouldIgnoreCollisions() ) )
	{
		if ( pxLink->ShouldUsePointOrientation() )
		{
			Asura_Matrix_3x3 xOrientation;
			xQOrientation.ToMatrix( xOrientation );
			GetGSActorBehaviour()->SetOwnerOrientation( xOrientation );
		}
		else
		{
			// If not using point orientation we orientate directly down the spline.
			Asura_Vector_3 xTangent;
			pxLink->GetTangentFromDistance( fRequiredDistance, xTangent );
			Asura_Matrix_3x3 xOrientation;
			Asura_Maths::OrientationFromDirection( xOrientation, xTangent );
			GetGSActorBehaviour()->SetOwnerOrientation( xOrientation );
		}
		m_fDistanceAlongSpline = fActualDistance;

		// Fire off any events on this spline we may have passed.
		if ( m_uSplineEventIndex < pxLink->GetNumSplineEvents() && m_fDistanceAlongSpline >= pxLink->GetSplineEventDistance( m_uSplineEventIndex ) )
		{
			pxLink->SendMessagesForSplineEvent( m_uSplineEventIndex );
			++m_uSplineEventIndex;
		}

		// Calculate any remaining timeslice.
		// For this we have to assume that the velocity over the distance is constant, which should be reasonable for small increments of time.
		const float fRemainingTimeslice = ( fRequiredDistance - fActualDistance ) / fMovementSpeed;
		if ( fRemainingTimeslice > 0.0f )
		{
			// We failed to use up our timeslice - we must have reached the end of the spline.
			SwitchToState( FINISHED );
		}
		return fRemainingTimeslice;
	}
	else
	{
		return 0.0f;
	}
}

float Axon_Behaviour_GS2Spline::UpdateSPSSMovement( const float fTimeslice )
{
	if ( fTimeslice == 0.0f ) { return 0.0f; }

	const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetGSActorBehaviour()->GetCurrentEntityGuid() );
	if ( !pxLink )
	{
		SetInError();
		SwitchToState( FINISHED );
		return 0.0f;
	}

	Asura_Vector_3 xPosition;
	Asura_Quat     xQOrientation;

	const float fRequiredTime = m_fTimeAlongSpline + fTimeslice;
	const float fExcessTime   = pxLink->GetPositionFromTime( fRequiredTime, xPosition, &xQOrientation );

	// Update the spline position with our follow surface stuff if necessary.
	if ( pxLink->ShouldFollowSurface() )
	{
		Asura_Vector_3 xDown( 0.0f, 1.0f, 0.0f );
		xDown = xQOrientation.RotateVector( xDown );

		float fDistanceToIntersection = Asura_Maths::LargeFloat;
		if ( Axon_Behaviour_GS2Actor::FollowSurfaceTest( xPosition, xDown, fDistanceToIntersection ) )
		{
			m_fPrevFollowSurfaceDistance = fDistanceToIntersection;
		}
		else 
		{
			fDistanceToIntersection = m_fPrevFollowSurfaceDistance;
		}

		if ( fDistanceToIntersection != Asura_Maths::LargeFloat )
		{
			xPosition += xDown * fDistanceToIntersection;
		}
	}

	if ( GetGSActorBehaviour()->SetOwnerPosition( xPosition, pxLink->ShouldIgnoreCollisions() ) )
	{
		if ( pxLink->ShouldUsePointOrientation() )
		{
			Asura_Matrix_3x3 xOrientation;
			xQOrientation.ToMatrix( xOrientation );
			GetGSActorBehaviour()->SetOwnerOrientation( xOrientation );
		}
		else
		{
			// If not using point orientation we orientate directly down the spline.
			Asura_Vector_3 xTangent;
			pxLink->GetTangentFromTime( fRequiredTime, xTangent );
			Asura_Matrix_3x3 xOrientation;
			Asura_Maths::OrientationFromDirection( xOrientation, xTangent );
			GetGSActorBehaviour()->SetOwnerOrientation( xOrientation );
		}

		const float fTimesliceUsed = fTimeslice - fExcessTime;
		m_fTimeAlongSpline += fTimesliceUsed;
		const float fProportionAlongSpline = pxLink->GetProportionFromTime( m_fTimeAlongSpline );
		m_fDistanceAlongSpline = pxLink->GetTotalLength() * fProportionAlongSpline;

		// Fire off any events on this spline we may have passed.
		if ( m_uSplineEventIndex < pxLink->GetNumSplineEvents() && m_fDistanceAlongSpline >= pxLink->GetSplineEventDistance( m_uSplineEventIndex ) )
		{
			pxLink->SendMessagesForSplineEvent( m_uSplineEventIndex );
			++m_uSplineEventIndex;
		}

		const float fRemainingTimeslice = fExcessTime;
		if ( fRemainingTimeslice > 0.0f )
		{
			// We failed to use up our timeslice - we must have reached the end of the spline.
			SwitchToState( FINISHED );
		}
		return fRemainingTimeslice;
	}
	else
	{
		return 0.0f;
	}
}

void Axon_Behaviour_GS2Spline::UpdateFreeMovement()
{
	if ( !m_pxGetToBehaviour || m_pxGetToBehaviour->IsInError() )
	{
		SetInError();
		SwitchToState( FINISHED );
	}
	else if ( m_pxGetToBehaviour->IsFinished() )
	{
		SwitchToState( FINISHED );
	}
}

//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////

#endif // ASURA_USE_AXON
