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

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

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

// header
#include "Entity/Locations/Spline/Tools_EntityNode_Spline.h"

// asura
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_UserInput.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Entity/Locations/Spline/Asura_ServerNode_Spline.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Mesh/Asura_Mesh.h"
#include "Network/Asura_Message.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"

//platform asura
#include "Rendering/Asura_PlatformRenderObject.h"

//tools
#include "Entity/Tools_Entity_Data.h"
#include "Entity/Locations/Spline/Tools_Object_NodeSplinePoint.h"
#include "EntityTemplate/Tools_Entity_Template_Object.h"
#include "Interface/Tools_ContextMenu_System.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_LOS.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Objects/Tools_Object_System.h"
#include "Objects/Tools_ObjectSystem_Param.h"

#if ASURA_TOOLS_USE_NEW_MESSAGE_EDITOR
#include "Entity/Tools_MessageEditor.h"
#endif

//////////////////////////////////////////////////////////////////////////////
//
// Tools_Object_SplineEventPoint
//
//////////////////////////////////////////////////////////////////////////////

Tools_Object_SplineEventPoint::Tools_Object_SplineEventPoint( Asura_ServerNode_Spline* pxParentGameSpline,
															  Tools_EntityNode_Spline* pxParentToolsSpline,
															  u_int uIndex )
: m_pxParentGameSpline( pxParentGameSpline )
, m_pxParentToolsSpline( pxParentToolsSpline )
, m_uIndex( uIndex )
, m_fDistance( pxParentGameSpline->GetSplineEventDistance( uIndex ) )
, m_usMessageBlock( pxParentGameSpline->GetSplineEventMessageBlock( uIndex ) )
, m_uFlags (pxParentGameSpline->GetSplineEventFlags( uIndex ) )
, m_xName( pxParentGameSpline->GetSplineEventName( uIndex ) )
, m_fSplineLength( pxParentGameSpline->GetTotalLength() )
, m_bChanged( false )
, m_bSnappingToCursor( false )
{
	m_pxParentGameSpline->GetPositionFromDistance( m_fDistance, Position );
	Orientation.Identity();

	if ( m_xName.IsBlank() )
	{
		m_xName = pxParentToolsSpline->GetName() + Asura_String( " Event " );
		Asura_String xIndexString;
		Asura_StringUtil::ToString( uIndex, xIndexString );
		m_xName += xIndexString;
	}

	SetMesh( "ToolsShape_SplineEvent" );
}

void Tools_Object_SplineEventPoint::UpdatePositionFromAsuraData()
{
	PARENT::UpdatePositionFromAsuraData();
	m_pxParentGameSpline->GetPositionFromDistance( m_fDistance, Position );
}

Asura_Guid Tools_Object_SplineEventPoint::GetGuid() const
{
	// Message editing stuff needs a guid.
	return m_pxParentGameSpline->GetGuid();
}

void Tools_Object_SplineEventPoint::Destroy()
{
	Asura_StaticMessage_System::DeleteMessageBlock( m_usMessageBlock );
	m_usMessageBlock = ASURA_STATICMESSAGE_EMPTYBLOCK;
	m_pxParentGameSpline->FlagSplineEventForDeletion( m_uIndex );
	m_pxParentToolsSpline->RemoveFromEventList( this );
	// We need to recreate the spline event objects because the indices may well be wrong now.
	m_pxParentToolsSpline->RequestRecreateSplineEventObjects();
	Tools_SaveAsura::SetEntityModified();

	PARENT::Destroy();
}

void Tools_Object_SplineEventPoint::OnSelection()
{
	PARENT::OnSelection();
	m_fSplineLength = m_pxParentGameSpline->GetTotalLength();
}

void Tools_Object_SplineEventPoint::OnDeselection()
{
	m_bSnappingToCursor = false;
	SetScale( 1.0f );
	PARENT::OnDeselection();
}

void Tools_Object_SplineEventPoint::Update()
{
	PARENT::Update();
	UpdatePositionFromAsuraData();
}

void Tools_Object_SplineEventPoint::UpdateWhileSelected()
{
	PARENT::UpdateWhileSelected();

	UpdateRenderMeshScaling();

	if ( m_bSnappingToCursor )
	{
		int iPolyIndex, iModuleIndex;
		Asura_Vector_3 xCursorPosition;
		if ( Tools_LOS::FindPolyAndPositionUnderCursor( iPolyIndex, iModuleIndex, xCursorPosition ) )
		{
			// Iterate along the spline until we find the nearest position.
            float fNearestSq = Asura_Maths::LargeFloat;
            const float fInc = m_fSplineLength / 250.0f;
			Asura_Vector_3 xSplinePosition;
			for ( float fDistance = 0.0f; fDistance < m_fSplineLength; fDistance += fInc )
			{
				m_pxParentGameSpline->GetPositionFromDistance( fDistance, xSplinePosition );
				const float fSq = ( xCursorPosition - xSplinePosition ).MagnitudeSquared();
				if ( fSq < fNearestSq )
				{
                    fNearestSq  = fSq;
					m_fDistance = fDistance;
				}
			}

			m_fDistance = Asura_Maths::ClampToRange( m_fDistance, 0.0f, m_fSplineLength );
			Tools_SaveAsura::SetEntityModified();
		}

		if ( Asura_UserInput::DebouncedKeyPressed( ASURA_KEYID_MOUSEBUTTON1 ) )
		{
			m_bSnappingToCursor = false;
		}
	}
	if ( m_bChanged )
	{
		m_bChanged = false;
		Tools_SaveAsura::SetEntityModified();
	}
}

void Tools_Object_SplineEventPoint::AddOverlayControls( Asura_Overlay* pxOverlay, Asura_Vector_2& xOffset )
{
	PARENT::AddOverlayControls( pxOverlay, xOffset );

	Asura_Dialog* pxDialogue = new Asura_Function_Dialog( "Rename", xOffset, Rename, reinterpret_cast< int >( this ) );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Numerical_Dialog( "Distance Along Spline", xOffset, &m_fDistance, 0.0f, m_fSplineLength, 0.1f, "%0.2f", &m_bChanged );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_FlagTickbox_Dialog("Trigger When Reversed", xOffset, &m_uFlags, TOOLS_SPLINEEVENTFLAG_TRIGGERWHENREVERSED, &m_bChanged);
	pxDialogue->SetHelpText("This will cause the event to trigger when the spline is played in reverse.\nOnly applies to things like lift splines that can be played in both directions.");
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Function_Dialog( "Snap To Cursor", xOffset, StartSnapToCursor, reinterpret_cast< int >( this ) );
	pxDialogue->SetHelpText( "Snap the position to the nearest point on the spline to the mouse cursor." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	Asura_String xMessageString( "Edit Event Messages (" );
	Asura_String xNumberString;
	const int    iNumMessages = Asura_StaticMessage_System::GetNumberOfMessagesInBlock( m_pxParentGameSpline->GetSplineEventMessageBlock( m_uIndex ) );
	Asura_StringUtil::ToString( iNumMessages, xNumberString );
	xMessageString += xNumberString + ")";

	pxDialogue = new Asura_Function_Dialog( xMessageString.GetString(), xOffset, EditMessages, reinterpret_cast< int >( this ) );
	pxDialogue->SetHelpText( "Edit the messages that are sent from this event." );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialogue = new Asura_Function_Dialog( "Edit Link Properties", xOffset, EditLinkProperties, reinterpret_cast< int >( this ) );
	pxDialogue->SetHelpText( "Select and edit the properties of the spline that owns this event point" );
	pxOverlay->AddDialog( pxDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

bool Tools_Object_SplineEventPoint::OwnsThisMessageBlock( unsigned short usMessageBlockID )
{
	return ( m_usMessageBlock == usMessageBlockID );
}

void Tools_Object_SplineEventPoint::UpdateRenderMeshScaling()
{
	SetScale( Tools_EntityNode_Spline::GetMeshScaleFactor( Position ) );
}

void Tools_Object_SplineEventPoint::Rename( int iThis )
{
	Tools_Object_SplineEventPoint* pxThis = reinterpret_cast< Tools_Object_SplineEventPoint* >( iThis );
	Tools_Object_System::AskForName( pxThis->m_xName, "Rename Spline Event:", pxThis, false, pxThis->GetMyNamePrefix() );
	Tools_SaveAsura::SetEntityModified();
}

void Tools_Object_SplineEventPoint::EditMessages( int iThis )
{
	Tools_Object_SplineEventPoint* pxThis = reinterpret_cast< Tools_Object_SplineEventPoint* >( iThis );

	#if ASURA_TOOLS_USE_NEW_MESSAGE_EDITOR
	Tools_MessageEditor::StartMessageEditingMode(&pxThis->m_usMessageBlock);
	#else
	Asura_String xPromptString( "Edit Messages For " );
	xPromptString += pxThis->m_xName;
	Tools_Object_System::EditMessageBlock( xPromptString.GetString(), &pxThis->m_usMessageBlock );
	#endif
}

void Tools_Object_SplineEventPoint::StartSnapToCursor( int iThis )
{
	Tools_Object_SplineEventPoint* pxThis = reinterpret_cast< Tools_Object_SplineEventPoint* >( iThis );
	pxThis->m_bSnappingToCursor = true;
}

void Tools_Object_SplineEventPoint::EditLinkProperties( int iThis )
{
	Tools_Object_SplineEventPoint* pxThis = reinterpret_cast< Tools_Object_SplineEventPoint* >( iThis );
	Tools_Object_System::SelectObject( pxThis->m_pxParentToolsSpline );
}

//////////////////////////////////////////////////////////////////////////////
//
// Tools_EntityNode_Spline
//
//////////////////////////////////////////////////////////////////////////////

Tools_EntityNode_Spline* Tools_EntityNode_Spline::s_pxThisToolsSpline = NULL;

Tools_EntityNode_Spline::Tools_EntityNode_Spline( Asura_Entity* pxEntity )
: PARENT( pxEntity )
, m_xSourceNodePosition( Asura_Maths::ZeroVector3 )
, m_xTargetNodePosition( Asura_Maths::ZeroVector3 )
, m_bSourceNodeIsSmoothed( false )
, m_bTargetNodeIsSmoothed( false )
, m_uNewSourceNode( ASURA_GUID_UNREGISTERED )
, m_uNewTargetNode( ASURA_GUID_UNREGISTERED )
, m_bRecreateSplineEventObjects( false )
, m_bRenderSplinePath( true )
{
	Type = TOT_Entity_Spline;

    SetMesh( "ToolsShape_CutsceneTrackPoint" );

    UpdateToolsSpline( GetGameSpline()->GetSourceGuid(), GetGameSpline()->GetTargetGuid() );
    CreateAllToolsPathObjects( -1, false );
	CreateAllSplineEventObjects();
}

bool Tools_EntityNode_Spline::IsBaseOrDerivativeOf( int iToolsObjectType ) const
{
    if ( iToolsObjectType == TOT_Entity_Spline )
    {
        return true;
    }
    else
    {
        return PARENT::IsBaseOrDerivativeOf( iToolsObjectType );
    }
}

void Tools_EntityNode_Spline::OnSelection()
{
	s_pxThisToolsSpline = this;

    PARENT::OnSelection();

	// We may have been editing the spline path points, in which case we will have shown the ends.
	// Make sure they're hidden again.
	SetShouldRenderPath( m_bRenderSplinePath );
}

void Tools_EntityNode_Spline::UpdateWhileSelected()
{
	PARENT::UpdateWhileSelected();

	UpdateRenderMeshScaling();

	if ( m_uNewSourceNode != ASURA_GUID_UNREGISTERED )
	{
		EstablishNewSourceNode();
		m_uNewSourceNode = ASURA_GUID_UNREGISTERED;
		Tools_SaveAsura::SetEntityModified();
	}
	if ( m_uNewTargetNode != ASURA_GUID_UNREGISTERED )
	{
		EstablishNewTargetNode();
		m_uNewTargetNode = ASURA_GUID_UNREGISTERED;
		Tools_SaveAsura::SetEntityModified();
	}
}

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

	if ( m_bRecreateSplineEventObjects )
	{
		m_bRecreateSplineEventObjects = false;
		CreateAllSplineEventObjects();
	}
}

void Tools_EntityNode_Spline::OnDeselection()
{
	PARENT::OnDeselection();

	SetScale( 1.0f );
	for ( CLIF< Tools_Object_NodeSplinePoint* > xIt( &m_xListAttachedPoints ); !xIt.done(); xIt.next() )
	{
		xIt()->SetScale( 1.0f );
	}
	for ( Asura_List_It< Tools_Object_SplineEventPoint > xIt( &m_xEventPoints ); !xIt.Done(); xIt.Next() )
	{
		xIt.GetCurrent()->SetScale( 1.0f );
	}

    s_pxThisToolsSpline = NULL;
}

bool Tools_EntityNode_Spline::CanBeMoved()
{
    // We can be moved if both our source and target nodes are selected (and hence being moved with us).
    const Tools_Entity_Object* pxSourceNode = Tools_Entity_Object::FindEntity( GetGameSpline()->GetSourceGuid() );
    const Tools_Entity_Object* pxTargetNode = Tools_Entity_Object::FindEntity( GetGameSpline()->GetTargetGuid() );
    return ( pxSourceNode && pxTargetNode && pxSourceNode->IsGroupSelected() && pxTargetNode->IsGroupSelected() );
}

void Tools_EntityNode_Spline::SetShouldRenderPath( const bool bRender )
{
	m_bRenderSplinePath = bRender;

	if ( m_bRenderSplinePath )
	{
		CreateAllToolsPathObjects( -1, false );
	}
	else
	{
		RemoveAllToolsPathObjects();
	}
}

void Tools_EntityNode_Spline::Create()
{
	if (Tools_Object_System::SelectedObjectList.size() != 2)
	{
		Message_Overlay xOverlay("You need to have two nodes selected to place the spline between.");
		xOverlay.DoModal();		
		return;
	}

	const Asura_Guid uSourceGuid = Tools_Object_System::SelectedObjectList.first_entry()->GetGuid();
	const Asura_Guid uTargetGuid = Tools_Object_System::SelectedObjectList.last_entry()->GetGuid();

    Asura_ServerEntity_Node* pxSourceNode = Asura_ServerEntity_Node::GetList()->FindEntity( uSourceGuid );
    Asura_ServerEntity_Node* pxTargetNode = Asura_ServerEntity_Node::GetList()->FindEntity( uTargetGuid );

    if (!(pxSourceNode && pxTargetNode))
	{
        Message_Overlay xOverlay("You need to have two nodes selected to place the spline between.");
		xOverlay.DoModal();		
		return;
	}

	if ((pxSourceNode->IsBaseOrDerivativeOf( AsuraEntityClass_GamesceneBase ) ) ||
		(pxTargetNode->IsBaseOrDerivativeOf( AsuraEntityClass_GamesceneBase )))
	{
		Message_Overlay xOverlay("Only gamescene splines can be attached to gamescene nodes.");
		xOverlay.DoModal();		
		return;
	}

    // Get a new GUID
    const Asura_Guid uGuid = Asura_Entity_System::GetCreatedByToolsGuid();

    // Set up tools data and auto-create a unique name (prompt for new name if there is a clash).
    Tools_Entity_Data* pxToolsData = CreateToolsDataWithDefaultName( "Spline", "Spline", uGuid, GetNamePrefix() );
    if ( !pxToolsData ) { return; }

    // Create the game specfic server object and set its data.
    Asura_ServerNode_Spline* pxGameNode = new Asura_ServerNode_Spline( uGuid );
    if ( !pxGameNode ) { return; }

    pxGameNode->ToolsData = pxToolsData;

    Asura_Vector_3 xPosition;
    GetSplineRenderPosition(xPosition, uSourceGuid, uTargetGuid);
    pxGameNode->SetPosition( xPosition );
    pxGameNode->SetSourceGuid(uSourceGuid);
    pxGameNode->SetTargetGuid(uTargetGuid);
    pxGameNode->CreateBasePoints();
    pxSourceNode->AddNewSpline( pxGameNode->GetGuid() );

    // Create the game specific tools object and select it.
    Tools_EntityNode_Spline* pxToolsNode = new Tools_EntityNode_Spline( pxGameNode );
    if ( !pxToolsNode ) { return; }

    pxToolsNode->Position = pxGameNode->GetPosition();

    Tools_Object_System::SelectObject( pxToolsNode );

    Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_DEFAULT, "Created new Spline" );
    Tools_SaveAsura::SetEntityModified();
}

void Tools_EntityNode_Spline::RemoveAllToolsPathObjects()
{
	while (m_xListAttachedPoints.size())
	{
		m_xListAttachedPoints.first_entry()->DestroyToolsObjectOnly();
		m_xListAttachedPoints.delete_first_entry();
	}
}

void Tools_EntityNode_Spline::AddAllToolsPathObjectsToList( List< Tools_Object* >& xList )
{
	for (CLIF <Tools_Object_NodeSplinePoint*> xIt(&m_xListAttachedPoints); !xIt.done(); xIt.next())
	{
		xList.add_entry( xIt() );
	}
}

void Tools_EntityNode_Spline::CreateAllToolsPathObjects( const int iSelectIndexAfter /*= -1*/, const bool bShowEndPoints /*= true*/ )
{
	// Remove all of the old tools objects.
	RemoveAllToolsPathObjects();

    // Create the tools objects for the link points.
    for (u_int u = 1; u < GetGameSpline()->GetNumberOfPoints() - 1; ++u)
    {
		const bool bIsEndPoint = (u == 1 || u == GetGameSpline()->GetNumberOfPoints() - 2);
        Tools_Object_NodeSplinePoint* pxPoint = new Tools_Object_NodeSplinePoint(GetGameSpline(), this, u, bIsEndPoint);
        m_xListAttachedPoints.add_entry(pxPoint);
        if (iSelectIndexAfter + 1 == static_cast< int >( u ) )
        {
            Tools_Object_System::SelectObject( pxPoint );
        }
		else if ( !bShowEndPoints && bIsEndPoint )
		{
			pxPoint->SetHiddenFlag( ObjectHideFlag_User, true );
		}
    }
}

void Tools_EntityNode_Spline::AddSplineEvent( int iThis )
{
	Tools_EntityNode_Spline* pxThis = reinterpret_cast< Tools_EntityNode_Spline* >( iThis );

	const u_int uNewIndex = pxThis->GetGameSpline()->AddSplineEvent();
	pxThis->CreateAllSplineEventObjects( &uNewIndex );
	Tools_SaveAsura::SetEntityModified();
}

void Tools_EntityNode_Spline::RemoveFromEventList( Tools_Object_SplineEventPoint* pxPoint )
{
	m_xEventPoints.RemoveFromList( pxPoint );
}

void Tools_EntityNode_Spline::CreateAllSplineEventObjects( const u_int* puIndexToSelect /*= NULL*/ )
{
	DeleteAllSplineEventObjects();

	//Clearup any spline events that were flagged for deletion before creating the new objects.
	GetGameSpline()->RemoveDeletedSplineEvents();

	for ( u_int u = 0; u < GetGameSpline()->GetNumSplineEvents(); ++u )
	{
		Tools_Object_SplineEventPoint* pxPoint = new Tools_Object_SplineEventPoint( GetGameSpline(), this, u );
		m_xEventPoints.AddToList( pxPoint );

		if ( puIndexToSelect != NULL && u == *puIndexToSelect )
		{
			Tools_Object_System::SelectObject( pxPoint );
		}
	}
}

void Tools_EntityNode_Spline::DeleteAllSplineEventObjects()
{
	while ( !m_xEventPoints.IsEmpty() )
	{
		delete m_xEventPoints.PopHeadObject();
	}
}

void Tools_EntityNode_Spline::DestroyAllSplineEventObjects()
{
	while ( !m_xEventPoints.IsEmpty() )
	{
		m_xEventPoints.GetHeadObject()->Destroy();
	}
}

void Tools_EntityNode_Spline::Destroy()
{
	RemoveAllToolsPathObjects();
	DestroyAllSplineEventObjects();

	// Before destruction, remove the link from the source node.
	// Also update splines adjacent to the source and target nodes, in case we're smoothing over nodes or whatever.
	Tools_Entity_Node* pxSourceToolsNode = Tools_Entity_Node::FindEntity( GetGameSpline()->GetSourceGuid() );
	if ( pxSourceToolsNode )
	{
		pxSourceToolsNode->GetGameNode()->RemoveSpline( GetGuid() );
		pxSourceToolsNode->UpdateAdjacentSplines();
	}

	// Set our entity as destroyed early (it's normally done up the hierarchy) so that it doesn't factor
	// in the next updating of adjacent splines.
	Entity->SetAsDestroyed();

	Tools_Entity_Node* pxTargetToolsNode = Tools_Entity_Node::FindEntity( GetGameSpline()->GetTargetGuid() );
	if ( pxTargetToolsNode )
	{
		pxTargetToolsNode->UpdateAdjacentSplines();
	}

	PARENT::Destroy();
}

bool Tools_EntityNode_Spline::ObjectIsInLOS(const Asura_Vector_3& xAlpha, const Asura_Vector_3& xBeta, float& fLowest)
{
	Asura_Vector_3 xTemp(Orientation.e13, Orientation.e23, Orientation.e33);
	float fScale = xTemp.Magnitude();

	if(!Mesh) return false;

	Asura_Mesh* pxMesh = Mesh;

	if(!pxMesh->BoundingBoxDataValid)
	{
		pxMesh->CalculateBoundingBox();
	}

	//check distance
	if((xAlpha-Position).Magnitude() > pxMesh->BoundingRadius+fLowest)
	{
		return false;
	}

	//sphere intersection test
	Asura_Vector_3 xCentre = Position + pxMesh->BoundingBox.CalculateCentre()*Orientation;
	float fIntersectionDistance = Asura_Maths::LargeFloat;
	if(	Asura_Maths::RayIntersectsSphere(xAlpha, xBeta, xCentre, pxMesh->BoundingRadius * fScale, &fIntersectionDistance))
	{
		if(fIntersectionDistance < fLowest)
		{
			fLowest = fIntersectionDistance;
			return true;
		}
	}
	return false;
}

void Tools_EntityNode_Spline::AddChildObjectsToTemplate(Tools_Entity_TemplateInstance_Object* pxTemplateInstance)
{
    PARENT::AddChildObjectsToTemplate( pxTemplateInstance );

    for (CLIF <Tools_Object_NodeSplinePoint*> xIt(&m_xListAttachedPoints); !xIt.done(); xIt.next())
    {
        pxTemplateInstance->AddChild( xIt() );
    }
}

void Tools_EntityNode_Spline::AddContextMenuItems( Tools_ContextMenu& xMenu, const Tools_ContextMenu_Context& xContext )
{
	PARENT::AddContextMenuItems( xMenu, xContext );

	Tools_ContextMenu_Section* pxSection = xMenu.GetSection( TOOLS_CONTEXTMENU_SECTION_SELECTION );
	Tools_ContextMenu_Item* pxItem = NULL;

	pxItem = new Tools_ContextMenu_Item_Function( "Select target node", SelectTargetNode, reinterpret_cast< int >( this ) );
	pxSection->AddItem( pxItem );

	pxItem = new Tools_ContextMenu_Item_Function( "Select source node", SelectSourceNode, reinterpret_cast< int >( this ) );
	pxSection->AddItem( pxItem );

	AddSelectStructureContextControls( xMenu, xContext );
}

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

	// Check the source and target nodes exist.
	const Asura_ServerEntity_Node* pxSourceNode = Asura_ServerEntity_Node::FindEntity( GetGameSpline()->GetSourceGuid() );
	if ( !pxSourceNode )
	{
		bOk = false;
		Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Objects", new Tools_MessageLogAction_GotoEntity( GetGuid() ),
			"Spline %u cannot find its source node - it will now be deleted.", GetGuid() );
		SetAsDestroyed();
	}
	const Asura_ServerEntity_Node* pxTargetNode = Asura_ServerEntity_Node::FindEntity( GetGameSpline()->GetTargetGuid() );
	if ( !pxTargetNode )
	{
		bOk = false;
		Tools_MessageLog::AddMessage( Tools_MessageLog::VERIFICATION, "Objects", new Tools_MessageLogAction_GotoEntity( GetGuid() ),
			"Spline %u cannot find its target node - it will now be deleted.", GetGuid() );
		SetAsDestroyed();
	}

	return bOk;
}

void Tools_EntityNode_Spline::OnSplinePointMoved( const int iIndex )
{
	if ( GetGameSpline()->IsSmoothingPoint( iIndex ) )
	{
		GetGameSpline()->SmoothingPointsChanged();
	}
}

void Tools_EntityNode_Spline::GetSplineRenderPosition(Asura_Vector_3& xPosition, const Asura_Guid uSource, const Asura_Guid uTarget)
{
    const Asura_ServerEntity_Node* pxSourceNode = Asura_ServerEntity_Node::GetList()->FindEntity( uSource );
    const Asura_ServerEntity_Node* pxTargetNode = Asura_ServerEntity_Node::GetList()->FindEntity( uTarget );
	Asura_Assert(pxSourceNode, "Tools_EntityNode_Spline::GetSplineRenderPosition - failed to find source node");
	Asura_Assert(pxTargetNode, "Tools_EntityNode_Spline::GetSplineRenderPosition - failed to find target node");
	if (pxSourceNode && pxTargetNode)
	{
        Asura_Vector_3 xSourceToTarget	   = pxTargetNode->GetPosition() - pxSourceNode->GetPosition();
		const float	   fLengthBetweenNodes = xSourceToTarget.Normalise();
		xPosition = pxSourceNode->GetPosition() + ( xSourceToTarget * Asura_Maths::Min( fLengthBetweenNodes * 0.2f, 5.0f ) );
	}
    else
    {
        xPosition = Asura_Maths::ZeroVector3;
    }
}

void Tools_EntityNode_Spline::UpdateToolsSpline(const Asura_Guid uSourceNode, const Asura_Guid uTargetNode)
{
	// Set position
	Asura_Vector_3 xRenderPosition;
	GetSplineRenderPosition(xRenderPosition, uSourceNode, uTargetNode);
	GetGameSpline()->SetPosition(xRenderPosition);
	Position = xRenderPosition;

	// Set node positions.
	Asura_ServerEntity_Node* pxSourceNode = Asura_ServerEntity_Node::GetList()->FindEntity( uSourceNode );
	Asura_ServerEntity_Node* pxTargetNode = Asura_ServerEntity_Node::GetList()->FindEntity( uTargetNode );
	Asura_Assert(pxSourceNode, "Failed to find source node");
	Asura_Assert(pxTargetNode, "Failed to find target node");
    if ( !pxSourceNode || !pxTargetNode ) { return; }
    m_xSourceNodePosition = pxSourceNode->GetPosition();
    m_xTargetNodePosition = pxTargetNode->GetPosition();

	// Set orientation.
	Asura_Vector_3 xDiff = m_xTargetNodePosition - m_xSourceNodePosition;
	const float fDistance = xDiff.Normalise();
	if (fDistance)
	{
		Asura_Maths::OrientationFromDirection( Orientation, xDiff );
	}
	else
	{
		Orientation.Identity();
	}
}

void Tools_EntityNode_Spline::GetBoundingBoxForModLinkTest(Asura_Bounding_Box& xOutputBB) const
{
	GetBoundingBox(xOutputBB);
	xOutputBB.Displace(Position);
	xOutputBB.Set(m_xSourceNodePosition);
	xOutputBB.Set(m_xTargetNodePosition);
	for (CLIF <Tools_Object_NodeSplinePoint*> xIt(&m_xListAttachedPoints); !xIt.done(); xIt.next())
	{
		xOutputBB.Set(xIt()->Position);
	}
}

Asura_ServerNode_Spline* Tools_EntityNode_Spline::GetGameSpline() const
{
	return static_cast< Asura_ServerNode_Spline* >( Entity );
}

bool Tools_EntityNode_Spline::ShouldBeRendered()
{
	if ( PARENT::ShouldBeRendered() )
	{
		return true;
	}

	// Also render if any of our path points are selected, any of our events are selected, or our source or target node is selected.
	for ( CLIF< Tools_Object_NodeSplinePoint* > xIt( &m_xListAttachedPoints ); !xIt.done(); xIt.next() )
	{
		if ( xIt()->IsSelected() )
		{
			return true;
		}
	}
	for ( Asura_List_It< Tools_Object_SplineEventPoint > xIt( &m_xEventPoints ); !xIt.Done(); xIt.Next() )
	{
		if ( xIt.GetCurrent()->IsSelected() )
		{
			return true;
		}
	}
	const Tools_Object* pxSourceToolsObject = Tools_Object_System::GetObjectFromGuid( GetGameSpline()->GetSourceGuid() );
	if ( pxSourceToolsObject && pxSourceToolsObject->IsSelected() )
	{
		return true;
	}
	const Tools_Object* pxTargetToolsObject = Tools_Object_System::GetObjectFromGuid( GetGameSpline()->GetTargetGuid() );
	if ( pxTargetToolsObject && pxTargetToolsObject->IsSelected() )
	{
		return true;
	}

	return false;
}

void Tools_EntityNode_Spline::Render()
{
	UpdateToolsSpline( GetGameSpline()->GetSourceGuid(), GetGameSpline()->GetTargetGuid() );

	RenderPath( 0xffffffff );
	Asura_Render::RenderLine( m_xSourceNodePosition, m_xTargetNodePosition, 0xffffffff );

	PARENT::Render();
}

void Tools_EntityNode_Spline::RenderPath( u_int uColour )
{
	if ( m_bRenderSplinePath )
	{
		GetGameSpline()->RenderLines( Asura_RGBA( 0xff404040 ), Asura_RGBA( uColour ) );
	}
}

void Tools_EntityNode_Spline::UpdateRenderMeshScaling()
{
	SetScale( GetMeshScaleFactor( Position ) );

	// Set the scale of all our associated objects also.
	for ( CLIF< Tools_Object_NodeSplinePoint* > xIt( &m_xListAttachedPoints ); !xIt.done(); xIt.next() )
	{
		xIt()->UpdateRenderMeshScaling();
	}
	for ( Asura_List_It< Tools_Object_SplineEventPoint > xIt( &m_xEventPoints ); !xIt.Done(); xIt.Next() )
	{
		xIt.GetCurrent()->UpdateRenderMeshScaling();
	}
}

void Tools_EntityNode_Spline::SetPointsToDefaultOrientations()
{
	const u_int uNumPoints = m_xListAttachedPoints.size();
	u_int uPointIndex = 0;
	for ( LIF< Tools_Object_NodeSplinePoint* > xPointIter( &m_xListAttachedPoints ); !xPointIter.done(); xPointIter.next() )
	{
		if ( uPointIndex != 0 && uPointIndex != uNumPoints - 1 )
		{
			xPointIter()->SetOrientationToTangent();
		}
		++uPointIndex;
	}
}

void Tools_EntityNode_Spline::GetNewSourceNode( int iThis )
{
	Tools_EntityNode_Spline* pxThis = reinterpret_cast< Tools_EntityNode_Spline* >( iThis );
	Tools_ChooseObject* pxChooseObject = pxThis->CreateSourceNodeChooseObject( &s_pxThisToolsSpline->m_uNewSourceNode );
	if ( pxChooseObject )
	{
		Tools_Object_System::ChooseObject( "Pick the new source node.", pxChooseObject );
	}
}

void Tools_EntityNode_Spline::GetNewTargetNode( int iThis )
{
	Tools_EntityNode_Spline* pxThis = reinterpret_cast< Tools_EntityNode_Spline* >( iThis );
	Tools_ChooseObject* pxChooseObject = pxThis->CreateTargetNodeChooseObject( &s_pxThisToolsSpline->m_uNewTargetNode );
	if ( pxChooseObject )
	{
		Tools_Object_System::ChooseObject( "Pick the new target node.", pxChooseObject );
	}
}

void Tools_EntityNode_Spline::EditNodeLinkPathFromStart()
{
	// Make sure we show the end points.
	s_pxThisToolsSpline->CreateAllToolsPathObjects();
	Tools_Object_System::SelectObject(s_pxThisToolsSpline->m_xListAttachedPoints.first_entry());
}

void Tools_EntityNode_Spline::EditNodeLinkPathFromEnd()
{
	// Make sure we show the end points.
	s_pxThisToolsSpline->CreateAllToolsPathObjects();
	Tools_Object_System::SelectObject(s_pxThisToolsSpline->m_xListAttachedPoints.last_entry());
}

void Tools_EntityNode_Spline::EstablishNewSourceNode()
{
	if ( m_uNewSourceNode == ASURA_GUID_UNREGISTERED ) { return; }

	Asura_ServerEntity_Node* pxOldSourceNode = Asura_ServerEntity_Node::FindEntity( GetGameSpline()->GetSourceGuid() );
	Asura_ServerEntity_Node* pxNewSourceNode = Asura_ServerEntity_Node::FindEntity( m_uNewSourceNode );
	if ( !pxOldSourceNode || !pxNewSourceNode ) { return; }

	// Remove old link.
	pxOldSourceNode->RemoveSpline( GetGameSpline()->GetGuid() );
	Tools_Entity_Node* pxOldToolsSourceNode = Tools_Entity_Node::FindEntity( pxOldSourceNode->GetGuid() );
	if ( pxOldToolsSourceNode )
	{
		pxOldToolsSourceNode->UpdateAdjacentSplines();
	}

	// Create new one.
	GetGameSpline()->SetSourceGuid( m_uNewSourceNode );
	pxNewSourceNode->AddNewSpline( GetGameSpline()->GetGuid() );
	Tools_Entity_Node* pxNewToolsSourceNode = Tools_Entity_Node::FindEntity( pxNewSourceNode->GetGuid() );
	if ( pxNewToolsSourceNode )
	{
		pxNewToolsSourceNode->UpdateAdjacentSplines();
	}
}

void Tools_EntityNode_Spline::EstablishNewTargetNode()
{
	if ( m_uNewTargetNode == ASURA_GUID_UNREGISTERED ) { return; }

	Tools_Entity_Node* pxOldToolsTargetNode = Tools_Entity_Node::FindEntity( GetGameSpline()->GetTargetGuid() );
	Tools_Entity_Node* pxNewToolsTargetNode = Tools_Entity_Node::FindEntity( m_uNewTargetNode );
	if ( !pxOldToolsTargetNode || !pxNewToolsTargetNode ) { return; }

	GetGameSpline()->SetTargetGuid( m_uNewTargetNode );
	pxOldToolsTargetNode->UpdateAdjacentSplines();
	pxNewToolsTargetNode->UpdateAdjacentSplines();
}

void Tools_EntityNode_Spline::UpdateSplinePoint(const int iIndex, const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrientation)
{
	const Asura_Quat xQuat(xOrientation);

	// Find the appropriate spline point object and update its data.
	Tools_Object_NodeSplinePoint* pxPoint = m_xListAttachedPoints.get_entry( iIndex - 1 );
	pxPoint->SetPosition( xPosition );
	pxPoint->SetOrientation( xOrientation );

	// Ensure the spline's start and end nodes are up-to-date...
	if(iIndex <= 1)
	{
		const Asura_Guid			uGuid	= GetGameSpline()->GetSourceGuid();
		Asura_ServerEntity_Node*	pxNode	= Asura_ServerEntity_Node::FindEntity(uGuid);

		pxNode->SetPosition(xPosition);
	}
	else
	{
		const int	iNumNodes	= GetGameSpline()->GetNumberOfPoints();

		if(iIndex >= (iNumNodes - 2))
		{
			const Asura_Guid			uGuid	= GetGameSpline()->GetTargetGuid();
			Asura_ServerEntity_Node*	pxNode	= Asura_ServerEntity_Node::FindEntity(uGuid);

			pxNode->SetPosition(xPosition);
		}
	}
}

float Tools_EntityNode_Spline::GetMeshScaleFactor( const Asura_Vector_3& xPosition )
{
	// Normal scaling at 15 metres.
	static const float s_fBaselineDistance = 15.0f;
	// Scale factor per metre.
	static const float s_fScalingFactor = 0.125f;

	const float fDistanceFromCamera = ( xPosition - *Asura_Camera::GetPosition() ).Magnitude();
	const float fScaling			= 1.0f + ( ( fDistanceFromCamera - s_fBaselineDistance ) * s_fScalingFactor );
	const float fClampedScaling     = Asura_Maths::ClampToRange( fScaling, 1.0f, 10.0f );

	return fClampedScaling;
}

void Tools_EntityNode_Spline::SelectSourceNode( int iThis )
{
	Tools_EntityNode_Spline* pxToolsSpline = reinterpret_cast< Tools_EntityNode_Spline* >( iThis );
	const Asura_Guid uSourceNodeGuid = pxToolsSpline->GetGameSpline()->GetSourceGuid();
	Tools_Object_System::SelectObject( Tools_Object_System::GetObjectFromGuid( uSourceNodeGuid ) );
}

void Tools_EntityNode_Spline::SelectTargetNode( int iThis )
{
	Tools_EntityNode_Spline* pxToolsSpline = reinterpret_cast< Tools_EntityNode_Spline* >( iThis );
	const Asura_Guid uTargetNodeGuid = pxToolsSpline->GetGameSpline()->GetTargetGuid();
	Tools_Object_System::SelectObject( Tools_Object_System::GetObjectFromGuid( uTargetNodeGuid ) );
}

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