//////////////////////////////////////////////////////////////////////////////
//
// Axon_BehaviourParams_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/BehaviourParamsTypes/Axon_BehaviourParams_GS2Spline.h"

// asura
#include "Animation/Asura_Animation.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/Asura_ServerNode_GS2Node.h"
#include "Entity/Locations/Spline/GamesceneSpline/Asura_ServerNode_GS2Spline.h"
#ifdef ASURA_TOOLS
#include "Interface/Asura_Dialog.h"
#include "Rendering/Asura_PlatformRenderObject.h"
#endif

// axon
#include "AnimTemplates/Axon_Gamescene_AnimTemplate_System.h"
#include "Behaviour/BehaviourTypes/Axon_Behaviour_GS2Actor.h"

// tools
#ifdef ASURA_TOOLS
#include "AnimTemplates/Tools_AnimTemplates_Play.h"
#include "AnimTemplates/Tools_Mode_AnimTemplates.h"
#include "Entity/Tools_Message_Editing.h"
#include "Entity/Locations/OrientedNode/GamesceneNode/Tools_EntityNode_GS2Node.h"
#include "Entity/Locations/Spline/GamesceneSpline/Tools_EntityNode_GS2Spline.h"
#include "Objects/Axon/Tools_Object_Axon_Interface.h"
#endif

//////////////////////////////////////////////////////////////////////////////
//
// Axon_BehaviourParams_GS2Spline
//
//////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_TOOLS
TOOLS_DECLARE_SHARED_PARAM( Asura_String, 0x2cbf83a8, GS2Spline_Param_AnimSetString ); // Asura_GetHashID( "GS2Spline_Param_AnimSetString" )
TOOLS_DECLARE_SHARED_PARAM( int,          0x1d70f3b8, GS2Spline_Param_AnimSetInt );    // Asura_GetHashID( "GS2Spline_Param_AnimSetInt" )
TOOLS_DECLARE_SHARED_PARAM( int,   0xfe5431af, GS2Spline_Param_TraverseMode );    // Asura_GetHashID( "GS2Spline_Param_TraverseMode" )
TOOLS_DECLARE_SHARED_PARAM( bool,  0x07b50211, GS2Spline_Param_UseArrivalRange ); // Asura_GetHashID( "GS2Spline_Param_UseArrivalRange" )
TOOLS_DECLARE_SHARED_PARAM( float, 0x74f5a47e, GS2Spline_Param_ArrivalRange );    // Asura_GetHashID( "GS2Spline_Param_ArrivalRange" )
#endif

Axon_BehaviourParams_GS2Spline::Axon_BehaviourParams_GS2Spline( const Axon_BehaviourParams_GS2Spline& xSplineParams )
: PARENT( xSplineParams )
, m_uAnimSet( xSplineParams.m_uAnimSet )
, m_iTraverseMode( xSplineParams.m_iTraverseMode )
, m_fArrivalRange( xSplineParams.m_fArrivalRange )
#ifdef ASURA_TOOLS
, m_bUseArrivalRange( xSplineParams.m_bUseArrivalRange )
, m_bSetStartFromEnd( xSplineParams.m_bSetStartFromEnd )
, m_bSetEndFromStart( xSplineParams.m_bSetEndFromStart )
, m_bPreviewing( false )
, m_fPreviewTime( 0.0f )
, m_fPreviewDistance( 0.0f )
, m_pxPreviewAnimation( NULL )
, m_pxPreviewAnimTemplate( NULL )
, m_xPreviewOrient( Asura_Maths::IdentityMatrix3x3 )
, m_xPreviewPosition( Asura_Maths::ZeroVector3 )
, m_xPrevPreviewPosition( Asura_Maths::ZeroVector3 )
, m_fPreviewPrevFollowSurfaceDistance( Asura_Maths::LargeFloat )
#endif
{
}

Axon_BehaviourParams_GS2Spline::Axon_BehaviourParams_GS2Spline( const u_int uBehaviourType )
: PARENT( AXON_BP_TYPE_GS2_SPLINE, uBehaviourType )
, m_uAnimSet( uAXON_ANIMATION_SEQUENCE_ID_NONE )
, m_iTraverseMode( AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED )
, m_fArrivalRange( 0.0f )
#ifdef ASURA_TOOLS
, m_bUseArrivalRange( false )
, m_bSetStartFromEnd( false )
, m_bSetEndFromStart( false )
, m_bPreviewing( false )
, m_fPreviewTime( 0.0f )
, m_fPreviewDistance( 0.0f )
, m_pxPreviewAnimation( NULL )
, m_pxPreviewAnimTemplate( NULL )
, m_xPreviewOrient( Asura_Maths::IdentityMatrix3x3 )
, m_xPreviewPosition( Asura_Maths::ZeroVector3 )
, m_xPrevPreviewPosition( Asura_Maths::ZeroVector3 )
, m_fPreviewPrevFollowSurfaceDistance( 0.0f )
#endif
{
}

bool Axon_BehaviourParams_GS2Spline::IsBaseOrDerivativeOf( const u_int uParamsType ) const
{
	return ( uParamsType == AXON_BP_TYPE_GS2_SPLINE ) ? true : PARENT::IsBaseOrDerivativeOf( uParamsType );
}

void Axon_BehaviourParams_GS2Spline::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
	int iVersion;
	xStream >> iVersion;

	PARENT::ReadFromChunkStream( xStream );

	if ( iVersion == CURRENT_CHUNK_VERSION )
	{
		xStream >> m_uAnimSet;
		xStream >> m_iTraverseMode;
		xStream >> m_fArrivalRange;
	}
	else
	{
		Asura_Assertf( false, ( "Axon_BehaviourParams_GS2Spline::ReadFromChunkStream - unhandled chunk version (%d)", iVersion ) );
	}

#ifdef ASURA_TOOLS
	m_bUseArrivalRange = ( m_fArrivalRange > 0.005f );
#endif
}

void Axon_BehaviourParams_GS2Spline::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << CURRENT_CHUNK_VERSION;

	PARENT::WriteToChunkStream( xStream );

	xStream << m_uAnimSet;
	xStream << m_iTraverseMode;
	xStream << m_fArrivalRange;
}

#ifdef ASURA_TOOLS
const char* Axon_BehaviourParams_GS2Spline::s_pszTraverseModeStrings[ AXON_GS2_STM_NUM_CORE_TRAVERSE_MODES ] =
{
	"Anim Movement",
	"Spline Path, Anim Speed",
	"Spline Path, Spline Speed",
	"Free Movement"
};

void Axon_BehaviourParams_GS2Spline::Render()
{
	extern void Render_Sphere( const Asura_Vector_3& xCentre, float fRadius, int iColour );

	PARENT::Render();

	if ( m_bPreviewing )
	{
		if ( m_pxPreviewAnimation )
		{
			m_pxPreviewAnimation->Render( m_xPreviewPosition, m_xPreviewOrient, ANIMTIME_USEGAMETIME );
		}
		else
		{
			Asura_Mesh* pxMeshToRender = GetToolsNode()->GetLODMesh();
			if ( pxMeshToRender )
			{
				Asura_Direct3D_ObjectRenderer::ToolsRender( pxMeshToRender, m_xPreviewPosition, m_xPreviewOrient, false );
			}
		}
	}

	if ( m_bUseArrivalRange && GetToolsNode()->IsGroupSelected() )
	{
		if ( GetToolsNode()->IsBaseOrDerivativeOf( TOT_Entity_GS2Spline ) )
		{
			Tools_EntityNode_GS2Spline* pxToolsSpline = static_cast< Tools_EntityNode_GS2Spline* >( GetToolsNode() );
			Render_Sphere( pxToolsSpline->GetTargetNodePosition(), m_fArrivalRange, 0xff00ff00 );
		}
	}
}

void Axon_BehaviourParams_GS2Spline::Update()
{
	PARENT::Update();

	if ( m_bSetStartFromEnd || m_bSetEndFromStart )
	{
		Tools_EntityNode_GS2Spline* pxToolsSpline = Tools_EntityNode_GS2Spline::FindEntity( GetToolsNode()->GetGuid() );
		if ( pxToolsSpline )
		{
			Asura_String xSkinName( Tools_Object_Axon_Interface::GetInstance()->GetSkinNameFromGuid( pxToolsSpline->GetOwnerGuid() ) );
			//## If the skin name is blank we should prompt for a skin.

			Asura_Vector_3 xPositionOffset;
			Asura_Quat	   xQOrientOffset;
			if ( Tools_Object_Axon_Interface::GetInstance()->GetAnimSetTotalMovementOffset( m_uAnimSet, xSkinName, xPositionOffset, xQOrientOffset ) )
			{
				if ( m_bSetStartFromEnd )
				{
					pxToolsSpline->SetSourcePosOriFromTarget( xPositionOffset, xQOrientOffset );
				}
				else if ( m_bSetEndFromStart )
				{
					pxToolsSpline->SetTargetPosOriFromSource( xPositionOffset, xQOrientOffset );
				}
			}
		}

		m_bSetStartFromEnd = m_bSetEndFromStart = false;
	}
}

void Axon_BehaviourParams_GS2Spline::StartPreview()
{
	PARENT::StartPreview();

	ResetPreviewVariables();

	Tools_EntityNode_GS2Spline* pxToolsGS2Spline = Tools_EntityNode_GS2Spline::FindEntity( GetToolsNode()->GetGuid() );
	if ( !pxToolsGS2Spline ) { return; }

	pxToolsGS2Spline->PreviewSetIsOccupied( true );
	Tools_EntityNode_GS2Node* pxTargetToolsGS2Node = Tools_EntityNode_GS2Node::FindEntity( pxToolsGS2Spline->GetGameGS2Spline()->GetTargetGuid() );
	if ( pxTargetToolsGS2Node )
	{
		pxTargetToolsGS2Node->PreviewIncPendingCount();
	}

	if ( m_iTraverseMode == AXON_GS2_STM_ANIM_MOVEMENT || m_iTraverseMode == AXON_GS2_STM_FREE_MOVEMENT )
	{
		m_xPreviewPosition = pxToolsGS2Spline->GetSourceNodePosition();
		const Asura_Vector_3 xSourceToTarget = pxToolsGS2Spline->GetTargetNodePosition() - m_xPreviewPosition;
		Asura_Maths::OrientationFromDirection( m_xPreviewOrient, xSourceToTarget );
		m_xPrevPreviewPosition = m_xPreviewPosition - xSourceToTarget;
	}
	else
	{
		Asura_ServerNode_GS2Spline* pxGameGS2Spline = pxToolsGS2Spline->GetGameGS2Spline();
		if ( !pxGameGS2Spline ) { return; }
		Asura_Quat xOrientQ;
		pxGameGS2Spline->GetPosAndOriFromDistance( 0.0f, m_xPreviewPosition, xOrientQ );
		xOrientQ.ToMatrix( m_xPreviewOrient );
		Asura_Vector_3 xForwards = Asura_Vector_3( 0.0f, 0.0f, 1.0f );
		xForwards = xOrientQ.RotateVector( xForwards );
		m_xPrevPreviewPosition = m_xPreviewPosition - xForwards;
	}

	const Asura_String& xSkinName = pxToolsGS2Spline->GetOwnerSkinName();
	if ( xSkinName.IsBlank() ) { return; }

	if ( m_uAnimSet == uAXON_ANIMATION_SEQUENCE_ID_NONE )
	{
		if ( !GetToolsNode()->GetLODMesh() ) { return; }
	}
	else
	{
		m_pxPreviewAnimTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( m_uAnimSet );
		if ( m_pxPreviewAnimTemplate )
		{
			m_pxPreviewAnimation = new Tools_AnimTemplates_Preview();
			m_pxPreviewAnimation->Initialise();
			m_pxPreviewAnimation->Play( xSkinName, ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET,
				Tools_Object_Axon_Interface::GetInstance()->GetPreviewScoringData(),
				m_pxPreviewAnimTemplate, m_xPreviewPosition, m_xPreviewOrient );
		}				

		if ( !m_pxPreviewAnimation ) { return; }
	}

	m_bPreviewing = true;
}

bool Axon_BehaviourParams_GS2Spline::UpdatePreview()
{
	if ( !m_bPreviewing )
	{
		return false;
	}

	if ( !PARENT::UpdatePreview() )
	{
		return false;
	}

	Tools_EntityNode_GS2Spline* pxToolsGS2Spline = Tools_EntityNode_GS2Spline::FindEntity( GetToolsNode()->GetGuid() );
	if ( !pxToolsGS2Spline )
	{
		return false;
	}

	Asura_ServerNode_GS2Spline* pxGameGS2Spline = pxToolsGS2Spline->GetGameGS2Spline();
	if ( !pxGameGS2Spline )
	{
		return false;
	}

	m_fPreviewTime += Asura_Timers::GetGameTimeSlice();

	bool bDone = false;

	Asura_Vector_3 xNewPosition;
	Asura_Matrix_3x3 xNewOrient;

	// Fallback to a safe traverse mode if we don't have everything we need.
	int iSafeTraverseMode = m_iTraverseMode;
	switch ( m_iTraverseMode )
	{
		case AXON_GS2_STM_ANIM_MOVEMENT:
		{
			if ( !m_pxPreviewAnimTemplate || !m_pxPreviewAnimation ) { iSafeTraverseMode = AXON_GS2_STM_FREE_MOVEMENT; }
			break;
		}

		case AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED:
		{
			if ( !m_pxPreviewAnimTemplate ) { iSafeTraverseMode = AXON_GS2_STM_FREE_MOVEMENT; }
			break;
		}

		case AXON_GS2_STM_SPLINE_PATH_SPLINE_SPEED:
		case AXON_GS2_STM_FREE_MOVEMENT:
		default:
		{
			break;
		}
	}

	switch ( iSafeTraverseMode )
	{
		case AXON_GS2_STM_ANIM_MOVEMENT:
		{
			const Asura_ServerNode_GS2Node* pxSourceNode = Asura_ServerNode_GS2Node::FindEntity( pxGameGS2Spline->GetSourceGuid() );
			if ( !pxSourceNode ) { return false; }

			// The preview animation takes care of the movement and orientation for us.
			xNewPosition = pxSourceNode->GetPosition();
			pxSourceNode->GetOrientation( xNewOrient );

			bDone = ( m_fPreviewTime > m_pxPreviewAnimTemplate->GetDuration( NULL ) );

			break;
		}

		case AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED:
		{
			Asura_Vector_3 xAnimVelocity;
			m_pxPreviewAnimTemplate->GetMovementVelocity( m_fPreviewTime, NULL, xAnimVelocity );
			const float fMovementSpeed    = xAnimVelocity.Magnitude();
			const float fRequiredDistance = m_fPreviewDistance + ( fMovementSpeed * Asura_Timers::GetGameTimeSlice() );
			Asura_Quat xPreviewOrientQ;
			m_fPreviewDistance = pxGameGS2Spline->GetPositionFromDistance( fRequiredDistance, xNewPosition, &xPreviewOrientQ );
			if ( pxGameGS2Spline->ShouldUsePointOrientation() )
			{
				xPreviewOrientQ.ToMatrix( xNewOrient );
			}
			else
			{
				OrientationFromOffset( m_xPrevPreviewPosition, xNewPosition, xNewOrient );
			}

			if ( pxGameGS2Spline->ShouldFollowSurface() )
			{
				Asura_Vector_3 xDown( 0.0f, 1.0f, 0.0f );
				xDown = xPreviewOrientQ.RotateVector( xDown );

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

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

			bDone = ( m_fPreviewDistance < fRequiredDistance );

			break;
		}

		case AXON_GS2_STM_SPLINE_PATH_SPLINE_SPEED:
		{
			Asura_Quat xPreviewOrientQ;
			const float fExcessTime = pxGameGS2Spline->GetPositionFromTime( m_fPreviewTime, xNewPosition, &xPreviewOrientQ );
			if ( pxGameGS2Spline->ShouldUsePointOrientation() )
			{
				xPreviewOrientQ.ToMatrix( xNewOrient );
			}
			else
			{
				OrientationFromOffset( m_xPrevPreviewPosition, xNewPosition, xNewOrient );
			}

			if ( pxGameGS2Spline->ShouldFollowSurface() )
			{
				Asura_Vector_3 xDown( 0.0f, 1.0f, 0.0f );
				xDown = xPreviewOrientQ.RotateVector( xDown );

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

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

			bDone = ( fExcessTime > 0.0f );

			break;
		}

		case AXON_GS2_STM_FREE_MOVEMENT:
		{
			// Travel at 5 m/s directly to the end of the spline.
			const Asura_Vector_3& xStartPosition = pxToolsGS2Spline->GetSourceNodePosition();
			Asura_Vector_3 xSourceToTarget = pxToolsGS2Spline->GetTargetNodePosition() - xStartPosition;
			const float fLinkLength = xSourceToTarget.Normalise();
			const float fRequiredDistance = m_fPreviewTime * 5.0f;
			m_fPreviewDistance = Asura_Maths::Min( fLinkLength, fRequiredDistance );

			xNewPosition = xStartPosition + xSourceToTarget * m_fPreviewDistance;
			Asura_Maths::OrientationFromDirection( xNewOrient, xSourceToTarget );

			bDone = ( m_fPreviewDistance < fRequiredDistance );

			break;
		}
	}

	if ( m_pxPreviewAnimation && !m_pxPreviewAnimation->IsPlaying() )
	{
		bDone = true;
	}

	m_xPrevPreviewPosition = m_xPreviewPosition;
	m_xPreviewOrient = xNewOrient;
	m_xPreviewPosition = xNewPosition;

	return ( !bDone );
}

void Axon_BehaviourParams_GS2Spline::StopPreview()
{
	Tools_EntityNode_GS2Spline* pxToolsGS2Spline = Tools_EntityNode_GS2Spline::FindEntity( GetToolsNode()->GetGuid() );
	if ( pxToolsGS2Spline )
	{
		pxToolsGS2Spline->PreviewSetIsOccupied( false );

		Tools_EntityNode_GS2Node* pxTargetToolsGS2Node = Tools_EntityNode_GS2Node::FindEntity( pxToolsGS2Spline->GetGameGS2Spline()->GetTargetGuid() );
		if ( pxTargetToolsGS2Node )
		{
			pxTargetToolsGS2Node->PreviewDecPendingCount();
		}
	}

	ResetPreviewVariables();
	m_bPreviewing = false;

	PARENT::StopPreview();
}

void Axon_BehaviourParams_GS2Spline::SetToolsNode( Tools_Entity_Node* pxToolsNode )
{
	PARENT::SetToolsNode( pxToolsNode );

	if ( GetToolsNode() )
	{
		Asura_Assert( pxToolsNode->IsBaseOrDerivativeOf( TOT_Entity_GS2Spline ), "Axon_BehaviourParams_GS2Spline::SetToolsNode - bad node type" );
		static_cast< Tools_EntityNode_Spline* >( GetToolsNode() )->SetShouldRenderPath( m_iTraverseMode != AXON_GS2_STM_FREE_MOVEMENT );

		const bool bAllowPathEditing = ( m_iTraverseMode != AXON_GS2_STM_FREE_MOVEMENT ) && ( m_iTraverseMode != AXON_GS2_STM_ANIM_MOVEMENT );
		const bool bAllowGraphEditing = ( m_iTraverseMode == AXON_GS2_STM_SPLINE_PATH_SPLINE_SPEED );

		static_cast< Tools_EntityNode_GS2Spline* >( GetToolsNode() )->SetAllowPathEditing( bAllowPathEditing );
		static_cast< Tools_EntityNode_GS2Spline* >( GetToolsNode() )->SetAllowGraphEditing( bAllowGraphEditing );
	}
}

void Axon_BehaviourParams_GS2Spline::AddOverlayControls( Asura_Tabs_Dialog* pxTabsDialog, Asura_Vector_2& xOffset )
{
	PARENT::AddOverlayControls( pxTabsDialog, xOffset );

	Asura_Dialog* pxNewDialog = new Asura_Function_Dialog( "Anim Set: ", &g_xParam_GS2Spline_Param_AnimSetString, xOffset, PickAnimSet, reinterpret_cast< int >( this ) );
	pxTabsDialog->AddDialog( pxNewDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialog = new Asura_Dialog( "Move Using:", xOffset );
	pxNewDialog->SetHelpText( "The method the actor will use to move along this spline." );
	pxTabsDialog->AddDialog( pxNewDialog );
	xOffset.x += 8.0f;

	const int iNumStrings = sizeof( s_pszTraverseModeStrings ) / sizeof( s_pszTraverseModeStrings[ 0 ] );
	Asura_Dialog* pxNewDDLDialog =
		new Asura_DropDown_List_Dialog( xOffset, s_pszTraverseModeStrings, &g_xParam_GS2Spline_Param_TraverseMode, iNumStrings, iNumStrings, NULL );
	pxNewDDLDialog->SetHelpText( "The method the actor will use to move along this spline." );
	pxTabsDialog->AddDialog( pxNewDDLDialog );
	xOffset.x -= 8.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	if ( m_iTraverseMode == AXON_GS2_STM_FREE_MOVEMENT )
	{
		pxNewDialog = new Asura_SharedTickbox_Dialog( "Use Arrival Range:", xOffset, &g_xParam_GS2Spline_Param_UseArrivalRange );
		pxNewDialog->SetHelpText( "If ticked the actor will pick a random point within the range to arrive at." );
		pxTabsDialog->AddDialog( pxNewDialog );
		xOffset.x += 15.0f;

		pxNewDialog = new Asura_Numerical_Dialog( "", xOffset, &g_xParam_GS2Spline_Param_ArrivalRange, 0.0f, 10000.0f, 0.25f, "%0.2f" );
		pxNewDialog->SetIsEnabledFunction( IsUsingRange, reinterpret_cast< int >( this ) );
		pxTabsDialog->AddDialog( pxNewDialog );
		xOffset.x -= 15.0f;
		xOffset.y += Asura_Dialog::DefaultSpacing();
	}
	else if ( m_iTraverseMode == AXON_GS2_STM_ANIM_MOVEMENT )
	{
		pxNewDialog = new Asura_Function_Dialog( "Set Spline End From Start", xOffset, SetEndFromStart, reinterpret_cast< int >( this ), &GetToolsNode()->Changed );
		pxNewDialog->SetHelpText( "Sets the spline end node to the correct position for the chosen anim relative to the start node." );
		pxTabsDialog->AddDialog( pxNewDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		pxNewDialog = new Asura_Function_Dialog( "Set Spline Start From End", xOffset, SetStartFromEnd, reinterpret_cast< int >( this ), &GetToolsNode()->Changed );
		pxNewDialog->SetHelpText( "Sets the spline start node to the correct position for the chosen anim relative to the end node." );
		pxTabsDialog->AddDialog( pxNewDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();
	}
}

void Axon_BehaviourParams_GS2Spline::AddFilesToExportList( List< Asura_String >& xNameList ) const
{
	PARENT::AddFilesToExportList( xNameList );

	// Add the animation files from the anim template to the export list.
	const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( m_uAnimSet );
	if ( pxTemplate )
	{
		pxTemplate->AddFilesToExportList( xNameList );
	}
}

bool Axon_BehaviourParams_GS2Spline::GetParameter( const Asura_Hash_ID uParamID, Tools_Parameter* pxParam ) const
{
	switch ( uParamID )
	{
		case g_uHash_GS2Spline_Param_AnimSetString:
		{
			if ( m_uAnimSet == uAXON_ANIMATION_SEQUENCE_ID_NONE )
			{
				pxParam->SetString( "*none*" );
			}
			else
			{
				const Axon_Gamescene_AnimTemplate* pxTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( m_uAnimSet );
				if ( pxTemplate )
				{
					pxParam->SetString( pxTemplate->m_xTemplateName );
				}
				else
				{
					pxParam->SetString( "NOT FOUND" );
				}
			}
			return true;
		}

		case g_uHash_GS2Spline_Param_AnimSetInt:
			// This is only ever set, not got, because of the way shared function dialogues work.
			return true;

		case g_uHash_GS2Spline_Param_TraverseMode:
			pxParam->SetInt( m_iTraverseMode );
			return true;

		case g_uHash_GS2Spline_Param_UseArrivalRange:
			pxParam->SetBool( m_bUseArrivalRange );
			return true;

		case g_uHash_GS2Spline_Param_ArrivalRange:
			pxParam->SetFloat( m_fArrivalRange );
			return true;

		default:
			return PARENT::GetParameter( uParamID, pxParam );
	}
}

bool Axon_BehaviourParams_GS2Spline::SetParameter( const Asura_Hash_ID uParamID, const Tools_Parameter* pxParam )
{
	switch ( uParamID )
	{
		case g_uHash_GS2Spline_Param_AnimSetString:
			// This is only ever got, not set, because of the way shared function dialogues work.
			return true;

		case g_uHash_GS2Spline_Param_AnimSetInt:
		{
			m_uAnimSet = pxParam->GetInt();
			if ( GetToolsNode()->IsSelected() )
			{
				GetToolsNode()->RequestRefreshOverlay();
			}
			GetToolsNode()->SetEntityModified();
			return true;
		}

		case g_uHash_GS2Spline_Param_TraverseMode:
		{
			m_iTraverseMode = pxParam->GetInt();

			static_cast< Tools_EntityNode_Spline* >( GetToolsNode() )->SetShouldRenderPath( m_iTraverseMode != AXON_GS2_STM_FREE_MOVEMENT );

			const bool bAllowPathEditing = ( m_iTraverseMode != AXON_GS2_STM_FREE_MOVEMENT ) && ( m_iTraverseMode != AXON_GS2_STM_ANIM_MOVEMENT );
			const bool bAllowGraphEditing = ( m_iTraverseMode == AXON_GS2_STM_SPLINE_PATH_SPLINE_SPEED );
			static_cast< Tools_EntityNode_GS2Spline* >( GetToolsNode() )->SetAllowPathEditing( bAllowPathEditing );
			static_cast< Tools_EntityNode_GS2Spline* >( GetToolsNode() )->SetAllowGraphEditing( bAllowGraphEditing );

			if ( m_iTraverseMode != AXON_GS2_STM_FREE_MOVEMENT )
			{
				m_bUseArrivalRange = false;
			}

			if ( GetToolsNode()->IsSelected() )
			{
				GetToolsNode()->RequestRefreshOverlay();
			}
			GetToolsNode()->SetEntityModified();
			return true;
		}

		case g_uHash_GS2Spline_Param_UseArrivalRange:
		{
			m_bUseArrivalRange = pxParam->GetBool();
			if ( !m_bUseArrivalRange )
			{
				m_fArrivalRange = 0.0f;
			}
			GetToolsNode()->SetEntityModified();
			return true;
		}

		case g_uHash_GS2Spline_Param_ArrivalRange:
			m_fArrivalRange = pxParam->GetFloat();
			return true;

		default:
			return PARENT::SetParameter( uParamID, pxParam );
	}
}

bool Axon_BehaviourParams_GS2Spline::Verify( const bool bAutomatic )
{
	bool bOk = PARENT::Verify( bAutomatic );

	// Check we have an anim set if we should.
	if ( m_iTraverseMode == AXON_GS2_STM_ANIM_MOVEMENT || m_iTraverseMode == AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED )
	{
		if ( m_uAnimSet == uAXON_ANIMATION_SEQUENCE_ID_NONE )
		{
			bOk = false;
			Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
				"GS2 spline %u uses anim movement but has no anim set.", GetToolsNode()->GetGuid() );
		}
	}

	// Check we have the right type of anim set.
	// Check the anim movement matches the start and end positions.
	// Check that our source and target nodes exist (but this should 'never happen'(TM)).
	if ( m_iTraverseMode == AXON_GS2_STM_ANIM_MOVEMENT )
	{
		// Should have a rigid anim template.
		const Axon_Gamescene_AnimTemplate* pxAnimationTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( m_uAnimSet );
		if ( pxAnimationTemplate )
		{
			pxAnimationTemplate->LoadAnims();
		}
		if ( !pxAnimationTemplate )
		{
			// We check for this below, so don't check here.
		}
		else if ( pxAnimationTemplate->m_uTemplateType != AXON_ATT_RIGIDMOVEMENT )
		{
			bOk = false;
			Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
				"GS2 spline %u uses anim movement but does not have a rigid anim set.", GetToolsNode()->GetGuid() );
		}
		else
		{
			Asura_Assert( GetToolsNode()->IsBaseOrDerivativeOf( TOT_Entity_GS2Spline ),
				"Axon_BehaviourParams_GS2Spline::Verify - associated tools node must be a GS2 spline" );
			const Tools_EntityNode_GS2Spline* pxToolsSpline = static_cast< Tools_EntityNode_GS2Spline* >( GetToolsNode() );
			const Asura_ServerNode_GS2Spline* pxSpline = pxToolsSpline->GetGameGS2Spline();
			const Asura_ServerNode_GS2Node* pxSourceNode = Asura_ServerNode_GS2Node::FindEntity( pxSpline->GetSourceGuid() );
			const Asura_ServerNode_GS2Node* pxTargetNode = Asura_ServerNode_GS2Node::FindEntity( pxSpline->GetTargetGuid() );
			if ( pxSourceNode && pxTargetNode )
			{
				// Check that the target node is at the correct position and orientation.
				Asura_String   xSkinName( Tools_Object_Axon_Interface::GetInstance()->GetSkinNameFromGuid( pxToolsSpline->GetOwnerGuid() ) );
				Asura_Vector_3 xPositionOffset;
				Asura_Quat	   xOrientOffsetQ;
				if ( Tools_Object_Axon_Interface::GetInstance()->GetAnimSetTotalMovementOffset( m_uAnimSet, xSkinName, xPositionOffset, xOrientOffsetQ ) )
				{
					const Asura_Vector_3 xToTarget = pxTargetNode->GetPosition() - pxSourceNode->GetPosition();
					const float          fToTarget = xToTarget.Magnitude();

					if ( Asura_Maths::Abs( xPositionOffset.Magnitude() - fToTarget ) > 0.01f )
					{
						bOk = false;
						Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
							"GS2 spline %u does not match the length of its rigid animation set.", GetToolsNode()->GetGuid() );
					}
					// Convert the link movement into source node space.
					Asura_Quat xSourceOrientQ;
					pxSourceNode->GetOrientation( xSourceOrientQ );
					Asura_Quat xInverseSourceOrientQ = xSourceOrientQ;
					xInverseSourceOrientQ.Invert();
					Asura_Vector_3 xToTargetLocalSpace;
					xToTargetLocalSpace = xInverseSourceOrientQ.RotateVector( xToTarget );
					if ( !xPositionOffset.ApproximatelyEquals( xToTargetLocalSpace, 0.001f ) )
					{
						bOk = false;
						Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
							"GS2 spline %u does not match the movement of its rigid animation set.", GetToolsNode()->GetGuid() );
					}

					// Orientation now.
					Asura_Quat xResultantOrientQ;
					xResultantOrientQ = xSourceOrientQ * xOrientOffsetQ;
					Asura_Quat xTargetOrientQ;
					pxTargetNode->GetOrientation( xTargetOrientQ );
					if ( !xResultantOrientQ.ApproximatelyEquals( xTargetOrientQ, 1.5e-4f ) ) // ~1°
					{
						bOk = false;
						Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
							"GS2 spline %u does not match the movement of its rigid animation set.", GetToolsNode()->GetGuid() );
					}
				}
			}
		}

		// Check we aren't being sent an anim change message.
		for ( Tools_StaticMessageIterator xMsgIt; !xMsgIt.Done(); xMsgIt.Next() )
		{
			const Asura_StaticMessage* pxMsg = xMsgIt.GetCurrent();
			if ( pxMsg->m_uSendTo == GetToolsNode()->GetGuid() && pxMsg->m_usMsgID == ASURA_MSG_AXON_GS2_ANIM_CHANGE )
			{
				bOk = false;
				Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
					"GS2 spline %u uses anim movement and is being sent an anim change message.", GetToolsNode()->GetGuid() );
			}
		}
	}
	else if ( m_iTraverseMode == AXON_GS2_STM_SPLINE_PATH_ANIM_SPEED )
	{
		// Should have a looping anim template.
		const Axon_Gamescene_AnimTemplate* pxAnimationTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( m_uAnimSet );
		if ( pxAnimationTemplate && pxAnimationTemplate->m_uTemplateType != AXON_ATT_LOOPINGMOVEMENT )
		{
			bOk = false;
			Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
				"GS2 spline %u uses spline path, anim speed but does not have a looping anim set.", GetToolsNode()->GetGuid() );
		}
	}
	else if ( m_iTraverseMode == AXON_GS2_STM_FREE_MOVEMENT )
	{
		const Asura_ServerNode_GS2Spline* pxLink = Asura_ServerNode_GS2Spline::FindEntity( GetToolsNode()->GetGuid() );
		if ( pxLink )
		{
			// Check we don't have any events on the spline, as we can't fire them off under free movement.
			if ( pxLink->GetNumSplineEvents() > 0 )
			{
				bOk = false;
				Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
					"GS2 spline %u uses free movement and has spline events. Events are not supported for free movement.", GetToolsNode()->GetGuid() );
			}
		}
	}

	// Check if the anim set still exists.
	if ( m_uAnimSet != uAXON_ANIMATION_SEQUENCE_ID_NONE )
	{
		if ( !Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( m_uAnimSet ) )
		{
			bOk = false;
			Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Gamescene", new Tools_MessageLogAction_GotoEntity( GetToolsNode()->GetGuid() ),
				"GS2 spline %u cannot find its anim set.", GetToolsNode()->GetGuid() );
		}
	}

	return bOk;
}

Axon_BehaviourParams* Axon_BehaviourParams_GS2Spline::Clone() const
{
	return new Axon_BehaviourParams_GS2Spline( *this );
}

void Axon_BehaviourParams_GS2Spline::ResetPreviewVariables()
{
	m_bPreviewing = false;
	m_fPreviewTime = 0.0f;
	m_fPreviewDistance = 0.0f;
	delete m_pxPreviewAnimation;
	m_pxPreviewAnimation = NULL;
	m_pxPreviewAnimTemplate = NULL;
	m_xPreviewPosition = Asura_Maths::ZeroVector3;
	m_xPreviewOrient = Asura_Maths::IdentityMatrix3x3;
	m_xPrevPreviewPosition = Asura_Maths::ZeroVector3;
	m_fPreviewPrevFollowSurfaceDistance = Asura_Maths::LargeFloat;
}

void Axon_BehaviourParams_GS2Spline::OrientationFromOffset( const Asura_Vector_3& xP0, const Asura_Vector_3& xP1, Asura_Matrix_3x3& xOrientation )
{
	Asura_Vector_3 xOffset = xP1 - xP0;
	if ( xOffset.MagnitudeSquared() > 0.0f )
	{
		Asura_Maths::OrientationFromDirection( xOrientation, xOffset );
	}
	else
	{
		xOrientation.Identity();
	}
}

bool Axon_BehaviourParams_GS2Spline::IsUsingRange( int iThis )
{
	Axon_BehaviourParams_GS2Spline* pxThis = reinterpret_cast< Axon_BehaviourParams_GS2Spline* >( iThis );
	return ( pxThis->m_bUseArrivalRange );
}

void Axon_BehaviourParams_GS2Spline::SetStartFromEnd( int iThis )
{
	Axon_BehaviourParams_GS2Spline* pxThis = reinterpret_cast< Axon_BehaviourParams_GS2Spline* >( iThis );
	pxThis->m_bSetStartFromEnd = true;
}

void Axon_BehaviourParams_GS2Spline::SetEndFromStart( int iThis )
{
	Axon_BehaviourParams_GS2Spline* pxThis = reinterpret_cast< Axon_BehaviourParams_GS2Spline* >( iThis );
	pxThis->m_bSetEndFromStart = true;
}

void Axon_BehaviourParams_GS2Spline::PickAnimSet( int iThis )
{
	Axon_BehaviourParams_GS2Spline* pxThis = reinterpret_cast< Axon_BehaviourParams_GS2Spline* >( iThis );

	const u_int uTypeBitfield =
		( pxThis->m_iTraverseMode == AXON_GS2_STM_ANIM_MOVEMENT ) ?
		( 1 << AXON_ATT_RIGIDMOVEMENT ) :
		( 1 << AXON_ATT_LOOPINGMOVEMENT );
	u_int uSequenceID;
	if ( Tools_AnimTemplates::ChooseAnimTemplate( uSequenceID, uTypeBitfield, uAXON_ANIMATION_SEQUENCE_ID_NONE, pxThis->m_uAnimSet, true ) )
	{
		// Setting this will cause SetParameter to be called with this parameter hash on all selected objects.
		g_xParam_GS2Spline_Param_AnimSetInt.SetValue( uSequenceID );
	}
}
#endif

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

#endif // ASURA_USE_AXON
