//////////////////////////////////////////////////////////////////////////////
//
// Asura_ServerNode_GamesceneSpline.cpp
//
// Created by NJD - 16 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

#ifdef ASURA_USE_AXON

// header
#include "Entity/Locations/Spline/GamesceneSpline/Asura_ServerNode_GamesceneSpline.h"

// asura
#include "Core/Asura_Graph.h"
#include "Core/Memory/Asura_ResourceAllocator.h"
#include "Cutscene/Asura_Cutscene_Track.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/Asura_ServerNode_GamesceneNode.h"
#include "Network/Asura_Message.h"
#if defined(ASURA_USE_STREAMING)
#include "Streaming/Asura_Streaming_System.h"
#endif	// defined(ASURA_USE_STREAMING)

// axon
//#include "Behaviour/BehaviourTypes/Axon_Behaviour_Gamescene.h"

//Tools
#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif

//////////////////////////////////////////////////////////////////////////////
//
// Asura_ServerNode_GamesceneSpline
//
//////////////////////////////////////////////////////////////////////////////

Asura_Hashed_List< Asura_ServerNode_GamesceneSpline > Asura_ServerNode_GamesceneSpline::s_xGamesceneSplineList;

Asura_ServerNode_GamesceneSpline::Asura_ServerNode_GamesceneSpline(Asura_Guid uGuid)
: GAMESCENE_PARENT()
, ENTITY_PARENT( uGuid )
, m_uGamesceneSplineFlags( 0 )
, m_pxSpeedOverTimeGraph(NULL)
, m_fTotalTimeRequired( 0.f )
, m_fDurationOverride( 0.f )
, m_uNumDistanceTimeGraphEntries( 0 )
, m_pxDistanceTimeGraphPoints(NULL)
, m_uPauseCondition( ASURA_GAMESCENE_PAUSE_NONE )
, m_uPauseRangeActorGuid( ASURA_GUID_UNREGISTERED )
, m_fPauseRangeFromActor( 0.f )
{
    Classification = AsuraEntityClass_GamesceneSpline;
	Asura_ResourcePushType(ASURA_RESOURCE_GAMESCENE);
	m_pxSpeedOverTimeGraph = new Asura_Graph<float>(16, 1.75f, 1.75f);
	m_pxSpeedOverTimeGraph->SetClampingValues(true, false, 0.0f, Asura_Maths::LargeFloat);
	m_pxDistanceTimeGraphPoints = 0;
	Asura_ResourcePopType();

	s_xGamesceneSplineList.AddToList( this );
}

Asura_ServerNode_GamesceneSpline::~Asura_ServerNode_GamesceneSpline()
{
	Asura_ResourcePushType(ASURA_RESOURCE_GAMESCENE);
	{
		delete m_pxSpeedOverTimeGraph;
		Asura_ResourceDeallocateArray(m_pxDistanceTimeGraphPoints);
	}
	Asura_ResourcePopType();

	s_xGamesceneSplineList.RemoveFromList( this );
}

void Asura_ServerNode_GamesceneSpline::CreateFromChunkStream( Asura_Guid uGuid, Asura_Chunk_Stream& xStream )
{
    Asura_ServerNode_GamesceneSpline* pxNode = new Asura_ServerNode_GamesceneSpline( uGuid );
    if ( pxNode )
    {
        pxNode->ReadFromChunkStream( xStream );
    }
}

void Asura_ServerNode_GamesceneSpline::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
    // Because the gamescene parent was not included in the chunk stuff from the outset the parent
    // initialisation has had to be moved into the versioning stuff. Once the oldest version where
    // the gamescene parent was not included has been deprecated we can move the parent stuff back
    // out again.

    int iVersion;
    xStream >> iVersion;

    Asura_ResourcePushType( ASURA_RESOURCE_GAMESCENE );
    if ( iVersion == CURRENT_CHUNK_VERSION || iVersion == 1 )
    {
        // Parents.
        ENTITY_PARENT::ReadFromChunkStream( xStream );
        GAMESCENE_PARENT::ReadFromChunkStream( xStream );

        // Us.
        Asura_ReadFromStream_Bitfield_Wrapper< u_int > xFlagsBitfield( m_uGamesceneSplineFlags );
        xStream >> xFlagsBitfield;

        xStream >> m_uPauseCondition;
        if ( m_uPauseCondition != ASURA_GAMESCENE_PAUSE_NONE )
        {
            xStream >> m_uPauseRangeActorGuid;
#if defined( ASURA_USE_STREAMING ) && !defined( ASURA_TOOLS )
            m_uPauseRangeActorGuid	= Asura_Streaming_System::MakeCurrentMasterZoneGuid( m_uPauseRangeActorGuid );
#endif	// defined( ASURA_USE_STREAMING ) && !defined( ASURA_TOOLS )
            xStream  >> m_fPauseRangeFromActor;
        }

        delete m_pxSpeedOverTimeGraph;
        m_pxSpeedOverTimeGraph = new Asura_Graph< float >(xStream);
#ifdef ASURA_TOOLS
		if (m_bDistanceVsTimeGraph)
		{
			m_pxSpeedOverTimeGraph->SetClampingValues( true, true, 0.0f, 1.0f );
		} else {
			m_pxSpeedOverTimeGraph->SetClampingValues( true, false, 0.0f, Asura_Maths::LargeFloat );
		}
#endif //ASURA_TOOLS
        xStream >> m_fTotalTimeRequired;
		if (iVersion < 2)
		{
			m_fDurationOverride = m_fTotalTimeRequired;
			m_bDurationOverride = false;
		} else {
			xStream >> m_fDurationOverride;
		}
#ifdef ASURA_ASSERT
        u_int uNumDistanceTimeGraphEntries = m_uNumDistanceTimeGraphEntries;
#endif
        xStream >> m_uNumDistanceTimeGraphEntries;
        Asura_Assert( ( m_uNumDistanceTimeGraphEntries == uNumDistanceTimeGraphEntries ) || ( uNumDistanceTimeGraphEntries == 0 ), "Number of distance time graph entries has changed since last load." );
        Asura_Assert( m_uNumDistanceTimeGraphEntries > 1, "This value should be at least 2" );
        // Don't allocate again if they exist. Only needed in this chunk as only checkpoint saves should manage this twice (and checkpoint saves by definition always use the latest chunk).
        if (!m_pxDistanceTimeGraphPoints)
        {
            Asura_ResourceAllocateArray( m_pxDistanceTimeGraphPoints, m_uNumDistanceTimeGraphEntries );
        }
        for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
        {
            xStream >> m_pxDistanceTimeGraphPoints[i].m_fDistance;
            xStream >> m_pxDistanceTimeGraphPoints[i].m_fCumulativeTime;
        }
    }
    else if ( iVersion == 0 )
    {
        // Parent.
        ENTITY_PARENT::ReadFromChunkStream( xStream );

        // Us.
        Asura_ReadFromStream_Bitfield_Wrapper< u_int > xFlagsBitfield( m_uGamesceneSplineFlags );
        xStream >> xFlagsBitfield;

        xStream >> m_uPauseCondition;
        if ( m_uPauseCondition != ASURA_GAMESCENE_PAUSE_NONE )
        {
            xStream >> m_uPauseRangeActorGuid;
#if defined( ASURA_USE_STREAMING ) && !defined( ASURA_TOOLS )
            m_uPauseRangeActorGuid	= Asura_Streaming_System::MakeCurrentMasterZoneGuid( m_uPauseRangeActorGuid );
#endif	// defined( ASURA_USE_STREAMING ) && !defined( ASURA_TOOLS )
            xStream  >> m_fPauseRangeFromActor;
        }

        delete m_pxSpeedOverTimeGraph;
        m_pxSpeedOverTimeGraph = new Asura_Graph< float >(xStream);
#ifdef ASURA_TOOLS
        m_pxSpeedOverTimeGraph->SetClampingValues( true, false, 0.0f, Asura_Maths::LargeFloat );
#endif //ASURA_TOOLS
        xStream >> m_fTotalTimeRequired;
#ifdef ASURA_ASSERT
        u_int uNumDistanceTimeGraphEntries = m_uNumDistanceTimeGraphEntries;
#endif
        xStream >> m_uNumDistanceTimeGraphEntries;
        Asura_Assert( ( m_uNumDistanceTimeGraphEntries == uNumDistanceTimeGraphEntries ) || ( uNumDistanceTimeGraphEntries == 0 ), "Number of distance time graph entries has changed since last load." );
        Asura_Assert( m_uNumDistanceTimeGraphEntries > 1, "This value should be at least 2" );
        // Don't allocate again if they exist. Only needed in this chunk as only checkpoint saves should manage this twice (and checkpoint saves by definition always use the latest chunk).
        if (!m_pxDistanceTimeGraphPoints)
        {
            Asura_ResourceAllocateArray( m_pxDistanceTimeGraphPoints, m_uNumDistanceTimeGraphEntries );
        }
        for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
        {
            xStream >> m_pxDistanceTimeGraphPoints[i].m_fDistance;
            xStream >> m_pxDistanceTimeGraphPoints[i].m_fCumulativeTime;
        }
    }
    else
    {
        Asura_Assertf( false, ( "Asura_ServerNode_GamesceneSpline::InitialiseFromChunkStream - unhandled chunk version (%d)", iVersion ) );
    }
    Asura_ResourcePopType();
}

void Asura_ServerNode_GamesceneSpline::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	// (TI - 5.9.2004) - DON'T FORGET TO UPDATE ApplyChangesForTemplateInstance IF YOU CHANGE STUFF.

	// Version
	xStream << static_cast< int >( CURRENT_CHUNK_VERSION );

	// Parent
	ENTITY_PARENT::WriteToChunkStream( xStream );
    GAMESCENE_PARENT::WriteToChunkStream( xStream );

	// Our data
    Asura_WriteToStream_Bitfield_Wrapper< u_int > xFlagsBitfield( m_uGamesceneSplineFlags );
    xFlagsBitfield.Add( m_bIsConvoyLink,              1 );
    xFlagsBitfield.Add( m_bIsFleeLink,                1 );
    xFlagsBitfield.Add( m_bTravelBackwards,           1 );
    xFlagsBitfield.Add( m_bTakeOrientationFromPoints, 1 );
    xFlagsBitfield.Add( m_bUseSplineAdjustment,       1 );
    xFlagsBitfield.Add( m_bTakeSpeedFromAnimation,    1 );
	xFlagsBitfield.Add( m_bUsePathFinding,		      1 );
	xFlagsBitfield.Add( m_bDistanceVsTimeGraph,		  1 );
	xFlagsBitfield.Add( m_bDurationOverride,		  1 );
    xStream << xFlagsBitfield;

    xStream << m_uPauseCondition;
    if ( m_uPauseCondition != ASURA_GAMESCENE_PAUSE_NONE )
    {
        xStream << m_uPauseRangeActorGuid;
        xStream << m_fPauseRangeFromActor;
    }

    bool bSamplesOnly = true;
#ifdef ASURA_TOOLS
    bSamplesOnly = Tools_Level_Export::IsExport();
#endif //ASURA_TOOLS
    m_pxSpeedOverTimeGraph->WriteToChunkStream( xStream, bSamplesOnly );

	xStream << m_fTotalTimeRequired;
	xStream << m_fDurationOverride;
	xStream << m_uNumDistanceTimeGraphEntries;
	for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; ++i )
	{
		xStream << m_pxDistanceTimeGraphPoints[i].m_fDistance;
		xStream << m_pxDistanceTimeGraphPoints[i].m_fCumulativeTime;
	}

	// (TI - 5.9.2004) - DON'T FORGET TO UPDATE ApplyChangesForTemplateInstance IF YOU CHANGE STUFF.
}

void Asura_ServerNode_GamesceneSpline::GetAnimTemplateStartPos( Asura_Vector_3 &xPosition, Asura_Matrix_3x3& xOrientation ) const
{
	Asura_ServerNode_GamesceneNode* pxSourceNode = Asura_ServerNode_GamesceneNode::FindEntity( GetSourceGuid() );
	if(!pxSourceNode)
	{
		Asura_Assert(false, "Failed to find source gamescene node entity for animation template start position");
		return;
	}
	xPosition = pxSourceNode->GetPosition();
	
	Asura_ServerNode_GamesceneNode* pxTargetNode = Asura_ServerNode_GamesceneNode::FindEntity( GetTargetGuid() );
	if(!pxTargetNode)
	{
		Asura_Assert(false, "Failed to find target gamescene node entity for animation template start position");
		return;
	}
	
	Asura_Vector_3 xEndPosition = pxTargetNode->GetPosition();
	Asura_Maths::OrientationFromDirection(xOrientation, xEndPosition - xPosition);
}

float Asura_ServerNode_GamesceneSpline::GetAverageLinkSpeed() const
{
	return GetTotalLinkTime() > 0.001f ? GetTotalLength() / GetTotalLinkTime() : Asura_Maths::LargeFloat;
}

float Asura_ServerNode_GamesceneSpline::GetPositionFromTime( const float fTimeElapsed, Asura_Vector_3& xOutput, Asura_Quat *pxOutputOrientation ) const
{
	float fProportion = GetProportionFromTime(fTimeElapsed);
	GetPositionFromProportion( fProportion, xOutput, pxOutputOrientation );
	if ( fTimeElapsed > m_pxDistanceTimeGraphPoints[ m_uNumDistanceTimeGraphEntries - 1 ].m_fCumulativeTime )
	{
		return fTimeElapsed - m_pxDistanceTimeGraphPoints[ m_uNumDistanceTimeGraphEntries - 1 ].m_fCumulativeTime;
	}
	return 0.0f;
}

void Asura_ServerNode_GamesceneSpline::GetPreviousTrackPointPos( Asura_Vector_3& xPosition, const float fTimeOnTrack ) const
{
	//find the section that we are on
	float fProportion = fTimeOnTrack / m_fTotalTimeRequired;
	int iSection;
	const int iNumTrackPoints = GetNumberOfPoints();
	for( iSection = 0; iSection < iNumTrackPoints - 3; iSection++ )
	{
		if( GetCumulativeLength( iSection + 1 ) >= fProportion ) break;
	}
	xPosition = GetPointPosition(iSection);
}

float Asura_ServerNode_GamesceneSpline::GetSpeedFromTime(const float fTimeElapsed) const
{
	float fProportion = GetProportionFromTime(fTimeElapsed);
	if (m_bDistanceVsTimeGraph) {
		Asura_Assert(0, "Unimplemented!");
		return 1.0f;
	} else if (m_bDurationOverride) {
		return m_pxSpeedOverTimeGraph->GetValueAtTime_Discrete(fProportion) * m_fTotalTimeRequired / m_fDurationOverride;
	} else {
		return m_pxSpeedOverTimeGraph->GetValueAtTime_Discrete(fProportion);
	}
}

float Asura_ServerNode_GamesceneSpline::GetFinalSpeed() const
{
	return GetSpeedFromTime(1.0f);
}

float Asura_ServerNode_GamesceneSpline::GetTransitionEntryTime() const
{
	float fEntryTime;
	float fControlPointTime;
	GetTransitionTimes( fEntryTime, fControlPointTime );
	return fEntryTime;
}

void Asura_ServerNode_GamesceneSpline::GetTransitionPoints(Asura_Vector_3& xEntryPoint, Asura_Vector_3& xControlPoint) const
{
    float fEntryTime;
    float fControlPointTime;
    GetTransitionTimes(fEntryTime, fControlPointTime);
    float fExcess = GetPositionFromTime(fEntryTime, xEntryPoint);
    Asura_Assert(fExcess == 0.0f, "Asura_ServerNode_GamesceneSpline::GetTransitionPoints - Sanity check");
    fExcess = GetPositionFromTime(fControlPointTime, xControlPoint);
    Asura_Assert(fExcess == 0.0f, "Asura_ServerNode_GamesceneSpline::GetTransitionPoints - Sanity check");
}

float Asura_ServerNode_GamesceneSpline::GetProportionFromTime(float fTimeElapsed) const
{
	Asura_Assert( m_pxDistanceTimeGraphPoints, "This should not be NULL" );
	// (AW - 09.07.2004) - TODO - Better search here could save much time.
	if ( fTimeElapsed >= m_pxDistanceTimeGraphPoints[ m_uNumDistanceTimeGraphEntries - 1 ].m_fCumulativeTime )
	{
		return 1.0f;
	}
	float fRet = 0.0f;
	for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
	{
		if ( m_pxDistanceTimeGraphPoints[i].m_fCumulativeTime > fTimeElapsed )
		{
			if ( i > 0 )
			{
				fRet = m_pxDistanceTimeGraphPoints[ i - 1 ].InterpolateValue( m_pxDistanceTimeGraphPoints[i], fTimeElapsed );
				break;
			}
		}
	}
	return fRet;
}

DEBUGVAR float dbg_fGamesceneLinkTransitionEntryTime = 1.0f;
DEBUGVAR float dbg_fGamesceneLinkTransitionControlPointTime = 2.0f;
void Asura_ServerNode_GamesceneSpline::GetTransitionTimes( float &fEntryTime, float &fControlPointTime ) const
{
	float fTotalTime = m_pxDistanceTimeGraphPoints[ m_uNumDistanceTimeGraphEntries - 1 ].m_fCumulativeTime;
	fEntryTime = dbg_fGamesceneLinkTransitionEntryTime;
	fControlPointTime = dbg_fGamesceneLinkTransitionControlPointTime;
	if ( fTotalTime < fControlPointTime )
	{
		fControlPointTime = fTotalTime;
		fEntryTime = fControlPointTime * 0.5f;
	}
}

bool Asura_ServerNode_GamesceneSpline::IsBaseOrDerivativeOf(u_short usClassification) const
{
	if (usClassification == AsuraEntityClass_GamesceneSpline)
	{
		return true;
	}
	return ENTITY_PARENT::IsBaseOrDerivativeOf(usClassification) || GAMESCENE_PARENT::IsBaseOrDerivativeOf(usClassification);
}

#ifdef ASURA_TOOLS

Asura_Quat& Asura_ServerNode_GamesceneSpline::GetTrackOrientation(const int iIndex)
{
	return GetPointOrientation(iIndex);
}

void Asura_ServerNode_GamesceneSpline::ChangedLinkPoints()
{
	//ENTITY_PARENT::ChangedLinkPoints();
	//ChangedDistanceTimeGraph();
	//UpdateNeedFollowGround();
}

void Asura_ServerNode_GamesceneSpline::UpdateNeedFollowGround()
{
	//if (!m_bFollowGround)
	//{
	//	// (AW - 12.04.2005) - Not following ground anyway.
	//	m_bToolsCancelledFollowGround = false;
	//	return;
	//}

	//const float fLinkLength = GetTotalLength();
	//for ( float fCurrentDistance = 0.0f; fCurrentDistance < fLinkLength; fCurrentDistance += 0.1f )
	//{
	//	Asura_Vector_3 xPosition;
	//	GetPositionFromDistance(fCurrentDistance, xPosition);
	//	float fDistanceToIntersection;
	//	if ( Axon_Behaviour_Gamescene::FollowGroundTest(xPosition, fDistanceToIntersection) )
	//	{
	//		if (Asura_Maths::Abs(fDistanceToIntersection) > 0.02f)
	//		{
	//			// (AW - 12.04.2005) - Follow ground makes a difference, don't disable.
	//			m_bToolsCancelledFollowGround = false;
	//			return;
	//		}
	//	}
	//}
	//// (AW - 12.04.2005) - Follow ground makes no difference at all, disable it.
	//m_bToolsCancelledFollowGround = true;
}

Asura_Graph<float>* Asura_ServerNode_GamesceneSpline::GetSpeedOverTimeGraph()
{
	return m_pxSpeedOverTimeGraph;
}

void Asura_ServerNode_GamesceneSpline::SetDistanceVsTimeGraph( const bool bDistanceTimeGraph ) 
{
	if (bDistanceTimeGraph != (bool)m_bDistanceVsTimeGraph)
	{
		if (bDistanceTimeGraph)
		{
			if (!m_bDurationOverride || m_fDurationOverride < 0.001f)
			{
				m_fDurationOverride = GetTotalLength() / GetAverageLinkSpeed();
			}
			m_pxSpeedOverTimeGraph->Initialise(16, 0.0f, 1.0f, 0.0f, 1.f);
			m_pxSpeedOverTimeGraph->SetClampingValues(true, true, 0.0f, 1.0f);
		} else {
			m_pxSpeedOverTimeGraph->Initialise(16, GetAverageLinkSpeed(), GetAverageLinkSpeed(), 0.0f, 1.f);
			m_pxSpeedOverTimeGraph->SetClampingValues(true, false, 0.0f, Asura_Maths::LargeFloat);
		}
		m_bDistanceVsTimeGraph = bDistanceTimeGraph;
		ChangedDistanceTimeGraph();
	}
}

float Asura_ServerNode_GamesceneSpline::GetTimeFromProportion(float fProportion) const
{
	Asura_Assert( m_pxDistanceTimeGraphPoints, "This should not be NULL" );
	// (AW - 09.07.2004) - TODO - Better search here could save much time.
	if ( fProportion >= m_pxDistanceTimeGraphPoints[ m_uNumDistanceTimeGraphEntries - 1 ].m_fDistance )
	{
		return 1.0f;
	}
	for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
	{
		if ( m_pxDistanceTimeGraphPoints[i+1].m_fDistance > fProportion )
		{
			float fProp = (fProportion - m_pxDistanceTimeGraphPoints[ i].m_fDistance) / (m_pxDistanceTimeGraphPoints[i+1].m_fDistance - m_pxDistanceTimeGraphPoints[ i].m_fDistance);
			return Asura_Maths::Lerp(m_pxDistanceTimeGraphPoints[i].m_fCumulativeTime,  m_pxDistanceTimeGraphPoints[i+1].m_fCumulativeTime, fProp);
		}
	}
	return 1.0f;

}



void Asura_ServerNode_GamesceneSpline::ChangedDistanceTimeGraph()
{
	// (AW - 09.07.2004) - Some value has changed which means we need to update our distance-time graph.
	if ( m_pxDistanceTimeGraphPoints )
	{
		Asura_ResourcePushType( ASURA_RESOURCE_GAMESCENE );
		{
			Asura_ResourceDeallocateArray( m_pxDistanceTimeGraphPoints );
			m_pxDistanceTimeGraphPoints = NULL;
		}
		Asura_ResourcePopType();
	}

	Asura_ResourcePushType( ASURA_RESOURCE_TEMPORARY );
	{
		// (AW - 09.07.2004) - Work out cumulative distances down to a 5cm level.
		// This limits us to a 3.2km spline.
		float *pfProportionsAlongSpline;
		float fDistanceAlongSpline = 0.0f;
		Asura_Vector_3 xLastPosition;
		GetPositionFromProportion( 0.0f, xLastPosition );
		Asura_ResourceAllocateArray( pfProportionsAlongSpline, 65536 );
		pfProportionsAlongSpline[0] = 0.0f;
		int iCount = 1;
		float fDeltaLastTime = 0.01f;
		float fCumulativeDistance = 0.0f;
		const float fDistanceBetweenPoints = 0.05f;
		const float fDistanceBetweenPointsTolerance = 0.001f;
		float fApproximateCumulativeDistance = GetTotalLength();
		float fExpectedDelta = ( fApproximateCumulativeDistance > 0.00001 ) ? fDistanceBetweenPoints / fApproximateCumulativeDistance : fDistanceBetweenPoints;

		while ( fDistanceAlongSpline < 1.0f && iCount <= 65535 )
		{
			float fLowBoundary = fDistanceAlongSpline;
			float fHighBoundary = fLowBoundary + ( fExpectedDelta * 5.0f );
			fHighBoundary = Asura_Maths::Min( fHighBoundary, 1.0f );
			float fNewDistance = ( ( fLowBoundary + fHighBoundary ) * 0.5f );
			int iTimeout = 50;

			do
			{
				Asura_Vector_3 xNewPosition;
				GetPositionFromProportion( fNewDistance, xNewPosition );
				float fDistance = ( xLastPosition - xNewPosition ).Magnitude();
				if ( Asura_Maths::Abs( fDistance - fDistanceBetweenPoints ) > fDistanceBetweenPointsTolerance )
				{
					if ( fDistance > fDistanceBetweenPoints )
					{
						fHighBoundary = fNewDistance;
					}
					else
					{
						fLowBoundary = fNewDistance;
					}
					fNewDistance = ( ( fLowBoundary + fHighBoundary ) * 0.5f );
					fNewDistance = Asura_Maths::ClampToRange( fNewDistance, 0.0f, 1.0f );
				}
				else
				{
					pfProportionsAlongSpline[iCount] = fNewDistance;
					fDeltaLastTime = pfProportionsAlongSpline[iCount] - pfProportionsAlongSpline[ iCount - 1 ];
					fCumulativeDistance += 0.05f;
					fDistanceAlongSpline = fNewDistance;
					xLastPosition = xNewPosition;
					Asura_Assert( iCount < 65536, "Spline too long!!!" );
					iCount++;
					break;
				}
			}
			while( --iTimeout );

			if ( !iTimeout )
			{
				// (AW - 09.07.2004) - This timeout will fire often at the end of a spline but may 
				// also trigger if fExpectedDelta is too low or there is a very tight loop in the 
				// spline that messes things up.
				pfProportionsAlongSpline[iCount] = Asura_Maths::ClampToRange( fDistanceAlongSpline + fExpectedDelta, 0.0f, 1.0f );
				fDeltaLastTime = fExpectedDelta;
				fCumulativeDistance += 0.05f;
				fDistanceAlongSpline = pfProportionsAlongSpline[iCount];
				GetPositionFromProportion( pfProportionsAlongSpline[iCount], xLastPosition );
				Asura_Assert(iCount < 65536, "Spline too long!!!");
				iCount++;
			}

		}


		// (AW - 09.07.2004) - Figure out our average speed
		float fAverageSpeed = m_pxSpeedOverTimeGraph->GetAverageValue();
		Asura_Assert( fAverageSpeed > 0.01f && fAverageSpeed < 1000.0f, "Unexpected average speed" );
		fAverageSpeed = Asura_Maths::ClampToRange( fAverageSpeed, 0.01f, 1000.0f );

		// (AW - 09.07.2004) - Calculate time required and number of distance-time graph entries to start with.
		m_fTotalTimeRequired = fCumulativeDistance / fAverageSpeed;

		float fCorrectedTime;
		if (m_bDurationOverride || m_bDistanceVsTimeGraph)
		{
			fCorrectedTime = m_fDurationOverride;
		} else {
			fCorrectedTime = m_fTotalTimeRequired;
		}

		m_uNumDistanceTimeGraphEntries = static_cast< u_int >( Asura_Maths::FloatToInt_Chop( fCorrectedTime * 10.0f ) );
		m_uNumDistanceTimeGraphEntries = Asura_Maths::ClampToRange( m_uNumDistanceTimeGraphEntries, 2u, 4096u );

		// (AW - 09.07.2004) - Make an array of distances.
		Asura_DistanceTime_GraphPoint *pxTempArray;
		bool *pbNeeded;
		Asura_ResourceAllocateArray( pxTempArray, m_uNumDistanceTimeGraphEntries );
		Asura_ResourceAllocateArray( pbNeeded, m_uNumDistanceTimeGraphEntries );

		for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
		{
			pbNeeded[i] = true;
			float fProportionOfTime = ( static_cast< float >(i) / static_cast< float >( m_uNumDistanceTimeGraphEntries - 1 ) );
			pxTempArray[i].m_fCumulativeTime = fCorrectedTime * fProportionOfTime;

			float fDistanceTravelled;
			if (m_bDistanceVsTimeGraph)
			{
				fDistanceTravelled = m_pxSpeedOverTimeGraph->GetValueAtTime(fProportionOfTime) * GetTotalLength();
			} else {
				fDistanceTravelled = m_pxSpeedOverTimeGraph->GetAreaUnderGraph(fProportionOfTime) * m_fTotalTimeRequired;
			}

			int iTemp = 0;
			while (1)
			{
				fDistanceTravelled -= fDistanceBetweenPoints;
				if ( fDistanceTravelled < 0.0f )
				{
					Asura_Assert( iTemp <= ( iCount + 5 ), "Unexpectedly large error" );
					iTemp = Asura_Maths::ClampToRange( iTemp, 1, iCount );
					pxTempArray[i].m_fDistance = pfProportionsAlongSpline[ iTemp - 1 ];
					break;
				}
				iTemp++;
			}
		}
		Asura_ResourceDeallocateArray(pfProportionsAlongSpline);

		// (AW - 09.07.2004) - Figure out which array entries can be discarded.
		u_int uLastValidValue = 0;

		// (AW - 24/1/06) - This tolerance setting has a big effect on the memory usage.
		float fTolerance = m_bUseSplineAdjustment ? 0.001f : 0.01f; 

		for ( u_int i = 1; i < m_uNumDistanceTimeGraphEntries - 1; i++ )
		{
			float fMyValue = pxTempArray[i].m_fDistance;
			const Asura_DistanceTime_GraphPoint *pxPrevValue = &pxTempArray[ uLastValidValue ];
			const Asura_DistanceTime_GraphPoint *pxNextValue = &pxTempArray[ i + 1 ];
			float fCalculatedValue = pxPrevValue->InterpolateValue( *pxNextValue, pxTempArray[ i ].m_fCumulativeTime );

			if ( Asura_Maths::Abs( fCalculatedValue - fMyValue ) < fTolerance )
			{
				pbNeeded[i] = false;
			}
			else
			{
				uLastValidValue = i;
			}
		}

		// (AW - 09.07.2004) - Count the number of entries we need.
		u_int uNumRequiredValues = 0;
		for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
		{
			if ( pbNeeded[i] )
			{
				++uNumRequiredValues;
			}
		}
		Asura_Assert( uNumRequiredValues > 1, "Needs to be at least 2 values" );

		// (AW - 09.07.2004) - Create our cleaned array of graph entries.
		Asura_ResourcePushType(ASURA_RESOURCE_GAMESCENE);
		{
			Asura_ResourceAllocateArray( m_pxDistanceTimeGraphPoints, uNumRequiredValues );
		}
		Asura_ResourcePopType();
		u_int uCounter = 0;

		for ( u_int i = 0; i < m_uNumDistanceTimeGraphEntries; i++ )
		{
			if ( pbNeeded[i] )
			{
				m_pxDistanceTimeGraphPoints[uCounter] = pxTempArray[i];
				++uCounter;
			}
		}
		m_uNumDistanceTimeGraphEntries = uNumRequiredValues;
		Asura_ResourceDeallocateArray(pxTempArray);
		Asura_ResourceDeallocateArray(pbNeeded);
	}
	Asura_ResourcePopType();
}

void Asura_ServerNode_GamesceneSpline::ApplyChangesForTemplateInstance(Tools_EntityTemplateInstance* pxInstance)
{
    ENTITY_PARENT::ApplyChangesForTemplateInstance(pxInstance);
	m_uPauseRangeActorGuid = pxInstance->ConvertGuid( m_uPauseRangeActorGuid );
}

bool Asura_ServerNode_GamesceneSpline::CanBeIncludedInTemplate() const 
{
    return ( GetClassification() == AsuraEntityClass_GamesceneSpline );
}

#endif //ASURA_TOOLS

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

#endif // ASURA_USE_AXON
