//
// Tools_Mode_Attractors.cpp
//
// Created by JGP 02/05/06
//
// Copyright (C) 2006 Rebellion (Derby), All Rights Reserved.
//

#include "Core/Asura.h"

#ifdef ASURA_USE_ATTRACTORS

// This file's header
#include "Tools_Mode_Attractors.h"

// Asura headers
#include "Camera/Asura_Camera.h"
#include "Core/Asura_UserInput.h"
#include "Interface/Asura_Menubar_System.h"
#include "Interface/Asura_Menubar.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Dialog.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Module.h"

//Tools
#include "Tools_Object_Attractor_Node.h"
#include "Tools_Attractor.h"
#include "Tools_Attractor_System.h"
#include "Tools_Attractor_Node.h"
#include "Tools_Overlays.h"
#include "Entity/Tools_Entity_Object.h"
#include "Entity/Tools_Entity_PhysicsObject.h"
#include "Entity/Tools_SecondaryEntityFile_System.h"
#include "Load&Save/Tools_ModeDataFile.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_Adjacency.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Misc/Tools_DynamicArray.h"
#include "Misc/Tools_Edge_Selection.h"
#include "Objects/Tools_Object.h"
#include "Objects/Tools_Object_System.h"


// static this pointer
Tools_Mode_Attractors* Tools_Mode_Attractors::s_pxThis = NULL;

// static variables
Asura_Vector_2 Tools_Mode_Attractors::s_xOverlayPosition( 3.0f, 14.0f );
Asura_Vector_2 Tools_Mode_Attractors::s_xDisplayFilterPosition( 3.0f, 14.0f );
Asura_Hash_ID Tools_Mode_Attractors::s_xDefaultBlueprintHash;
Asura_TemplateHashTree<bool> Tools_Mode_Attractors::s_xBluePrintTypesVisible;
bool Tools_Mode_Attractors::s_bUseDefaultRadius = false;
float Tools_Mode_Attractors::s_fDefaultRadius = 1.0f;


///////////////////////////////////////////////////////////////////////////////////////
// Construct and destruct
///////////////////////////////////////////////////////////////////////////////////////


Tools_Mode_Attractors::Tools_Mode_Attractors()
{
	// store this pointer
	// for static access
	s_pxThis = this;
	m_xMenuHandle = ASURA_INVALID_HANDLE;

}


Tools_Mode_Attractors::~Tools_Mode_Attractors()
{
	s_pxThis = NULL;
}


///////////////////////////////////////////////////////////////////////////////////////
// Tools_Mode functions
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Mode_Attractors::InitialiseMode()
{
	//load values from .dat file
	TransferModeData(TOOLSMODEDATAFILE_LOAD);

	//create the menubar
	m_xMenuHandle = Asura_Menubar_System::CreateMenuSection();
	
	Asura_Menubar_Title* pxTitle = Asura_Menubar_System::AddTitle(m_xMenuHandle,"Overlays");
    pxTitle->AddItem("Entity Manager [Ctrl+E]",   Tools_SecondaryEntityFile_System::ToggleOverlay, Tools_SecondaryEntityFile_System::OverlayIsOn);


	m_bEdgeSelectionMode = false;

	Tools_Object_System::EnableAllControls( true );
	Tools_Edge_Selection::Activate();

	Tools_Attractor_System* const pxAttractorSystem = Tools_Attractor_System::GetInstance();

	//update all attractors from the attactor types
	pxAttractorSystem->UpdateAttractorsFromType();
	pxAttractorSystem->SyncAttractorsWithControllerEntities();
	
	// first create tools
	// objects for all the
	// attractors
	Tools_Object_Attractor_Node::CreateAttractorObjects();

	//create all the objects

	Tools_Entity_Object::CreateAllObjects();

	//and hide them
//	for( LIF<Tools_Object*> xObjectLIF( &Tools_Object_System::ObjectList ); !xObjectLIF.done(); xObjectLIF.next() )
//	{
//		if ( xObjectLIF()->Type != TOT_AttractorNode )
//		{
//			xObjectLIF()->HiddenFlags |= ObjectHideFlag_Mode;
//		}
//	}

//	Tools_Object_System::SetSelectionFilter( TOT_AttractorNode );

	//update visibility of static objects
	
//	for( Asura_Hashed_List_It<Tools_Entity_PhysicsObject> xIt( Tools_Entity_PhysicsObject::GetList() ); !xIt.Done(); xIt.Next() )
//	{
//		xIt.GetCurrent()->HiddenFlags &= ~ObjectHideFlag_Mode;
//	}

	// now initialise the 
	// overlay
	InitialiseOverlay();
	Tools_SecondaryEntityFile_System::CreateOverlay();
	Tools_SecondaryEntityFile_System::SelectInitialEntityFile();

}


void Tools_Mode_Attractors::ShutdownMode()
{
	//save values to .dat file
	TransferModeData(TOOLSMODEDATAFILE_SAVE);

	// turn off edge selection if it's on

	Tools_Edge_Selection::Deactivate();

	Tools_Object_Attractor_Node::DeleteAttractorObjects();
	Tools_Object_System::DeleteAllModeSpecific();
	Tools_Object_System::EnableAllControls( false );
	Tools_Object_System::ClearSelectionFilter();

	// get rid of the overlay
	Tools_SecondaryEntityFile_System::CloseOverlay();
	CloseOverlay();

	//shutdown the menu
	if(m_xMenuHandle != ASURA_INVALID_HANDLE)
	{
		Asura_Menubar_System::DestroyMenuSection(m_xMenuHandle);
		m_xMenuHandle = ASURA_INVALID_HANDLE;
	}


}

void Tools_Mode_Attractors::TransferModeData(int iMode)
{
	//open the file
	Tools_ModeDataFile xFile("EnvHookMode.dat",iMode);

	//transfer the data
	//(none of these should be removed , and new data should go on the end, otherwise
	//the xFiles will get screwed up)
	xFile.Transfer(s_xOverlayPosition);
	xFile.Transfer(Tools_Object_Attractor_Node::s_fScaleWhenCloseDist);
	xFile.Transfer(s_xDisplayFilterPosition);

	xFile.Transfer(Tools_SecondaryEntityFile_System::s_xOverlayPosition);
	xFile.Transfer(Tools_SecondaryEntityFile_System::s_bOverlayIsOn);
	xFile.Transfer(Tools_SecondaryEntityFile_System::s_xTreeDialogSize);
	xFile.Transfer(Tools_SecondaryEntityFile_System::s_xFilterListDialogSize);

}



void Tools_Mode_Attractors::QuickSave()
{
	if(Tools_SaveAsura::IsEntityModified()) Tools_SaveAsura::SaveEntities();
	Tools_SaveAsura::SaveAttractors();
}

bool Tools_Mode_Attractors::IsModified()
{
	return Tools_SaveAsura::IsAttractorsModified() || Tools_SaveAsura::IsEntityModified();
}


void Tools_Mode_Attractors::Update()
{

	HandleKeyboard();

	if ( m_bEdgeSelectionMode )
	{
		Tools_Object_System::DeselectAllObjects();
		Tools_Object_System::EnableAllControls( false );
		Tools_Edge_Selection::Update( EdgeSelectionUpdate_Standard );
	}
	else
	{
		Tools_Object_System::EnableAllControls( true );
		Tools_Edge_Selection::Update( EdgeSelectionUpdate_NoSelection );
	}

	Tools_SecondaryEntityFile_System::Update();
}


void Tools_Mode_Attractors::HandleKeyboard()
{
	if ( ( Asura_Overlay_System::OverlaysHaveKeyboardFocus() ) || ( Asura_UserInput::KeyPressed( ASURA_KEYID_ALT ) ) )
	{
		return;
	}

	if ( Asura_UserInput::KeyPressed( ASURA_KEYID_CTRL ) )
 	{
		if ( Asura_UserInput::DebouncedKeyPressed( 't' ) )
		{
			//ctrl+t created attractor from edge , or creates a node depending on whether edge selection is on
			if(m_bEdgeSelectionMode)
			{
				OnMakeEdgeAttractorNode();
			}
			else
			{
				OnAddAttractorNode();
			}
		}
		if(Asura_UserInput::DebouncedKeyPressed('e'))
		{
			Tools_SecondaryEntityFile_System::ToggleOverlay();
		}

	}
	else if ( Asura_UserInput::KeyPressed( ASURA_KEYID_SHIFT ) )
	{
	}
	else
	{
		if ( Asura_UserInput::DebouncedKeyPressed( 'e' ) )
		{
			m_bEdgeSelectionMode = !m_bEdgeSelectionMode;
		}
	}
}


void Tools_Mode_Attractors::RenderWorld_PostToneMap()
{
	if ( m_bEdgeSelectionMode )
	{
		Tools_Edge_Selection::Render();
	}
}

bool *Tools_Mode_Attractors::GetAttractorTypeVisibilityFlag(Asura_Hash_ID uHashID)
{
	bool *pbVisible = s_xBluePrintTypesVisible.GetDataByHash(uHashID);
	if (!pbVisible)
	{
		pbVisible = s_xBluePrintTypesVisible.AddNode(uHashID);
		Asura_Assert(pbVisible, "Error in HashTree, cant find node in tree or add new item!");
		*pbVisible = true;
	}
	return pbVisible;
}

bool Tools_Mode_Attractors::IsAttractorTypeVisible(Asura_Hash_ID uHashID)
{
	bool *pbVisible = GetAttractorTypeVisibilityFlag(uHashID);
	return pbVisible ? *pbVisible : true;
}

///////////////////////////////////////////////////////////////////////////////////////
// Initialise / Close Overlay
///////////////////////////////////////////////////////////////////////////////////////

//drop down list for selecting an attractor type
class Tools_Attractor_Type_Dialog : public Asura_Hash_DropDownList_Dialog
{
public :
	Tools_Attractor_Type_Dialog(Asura_Vector_2& xOffset,int iNumberOfLines,Asura_Hash_ID* pxHashID,const char* szName = NULL)
		:Asura_Hash_DropDownList_Dialog(xOffset)
	{
		SetOnlyUpdateSelectionIndexOnClick(true);
		Initialise(xOffset,iNumberOfLines,pxHashID,0,szName);
	}

protected :
	virtual u_int GetNumberOfHashValues() const
	{
		return Tools_Attractor_System::GetInstance()->GetNumAttractorTypes();
	}

	virtual const char* GetNameByIndex(int iIndex) const
	{
		const Tools_Attractor_Type* pxType = Tools_Attractor_System::GetInstance()->GetAttractorTypeByIndex(iIndex);
		return pxType ? pxType->m_xName.GetString() : "*Error*";
	}
};
void Tools_Mode_Attractors::CloseDisplayFilterOverlay()
{
	// if it exists already, shut it down
	if ( Asura_Overlay_System::DoesOverlayExist( TOOLS_OVERLAY_ATTRACTORS_SETFILTER ) )
	{
		Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_ATTRACTORS_SETFILTER );
	}
}


void Tools_Mode_Attractors::ShowAllAttractorTypes()
{
	bool *pbVisible = GetAttractorTypeVisibilityFlag(0);
	if (pbVisible)
	{
		*pbVisible = true;
	}

	u_int uNumAttractorTypes = Tools_Attractor_System::GetInstance()->GetNumAttractorTypes();
	for (u_int u = 0; u < uNumAttractorTypes; ++u)
	{
		const Tools_Attractor_Type* pxType = Tools_Attractor_System::GetInstance()->GetAttractorTypeByIndex(u);
		Asura_String xTypeName =  pxType ? pxType->m_xName.GetString() : "*Error*";
		Asura_Hash_ID uHashID  = xTypeName.GetHash();

		bool *pbVisible = GetAttractorTypeVisibilityFlag(uHashID);
		if (pbVisible)
		{
			*pbVisible = true;
		}
	}
}
void Tools_Mode_Attractors::HideAllAttractorTypes()
{
	bool *pbVisible = GetAttractorTypeVisibilityFlag(0);
	if (pbVisible)
	{
		*pbVisible = false;
	}

	u_int uNumAttractorTypes = Tools_Attractor_System::GetInstance()->GetNumAttractorTypes();
	for (u_int u = 0; u < uNumAttractorTypes; ++u)
	{
		const Tools_Attractor_Type* pxType = Tools_Attractor_System::GetInstance()->GetAttractorTypeByIndex(u);
		Asura_String xTypeName =  pxType ? pxType->m_xName.GetString() : "*Error*";
		Asura_Hash_ID uHashID  = xTypeName.GetHash();

		bool *pbVisible = GetAttractorTypeVisibilityFlag(uHashID);
		if (pbVisible)
		{
			*pbVisible = false;
		}
	}
}
void Tools_Mode_Attractors::InvertAttractorTypesVisible()
{
	bool *pbVisible = GetAttractorTypeVisibilityFlag(0);
	if (pbVisible)
	{
		*pbVisible = !*pbVisible;
	}

	u_int uNumAttractorTypes = Tools_Attractor_System::GetInstance()->GetNumAttractorTypes();
	for (u_int u = 0; u < uNumAttractorTypes; ++u)
	{
		const Tools_Attractor_Type* pxType = Tools_Attractor_System::GetInstance()->GetAttractorTypeByIndex(u);
		Asura_String xTypeName =  pxType ? pxType->m_xName.GetString() : "*Error*";
		Asura_Hash_ID uHashID  = xTypeName.GetHash();

		bool *pbVisible = GetAttractorTypeVisibilityFlag(uHashID);
		if (pbVisible)
		{
			*pbVisible = !*pbVisible;
		}
	}

}

void Tools_Mode_Attractors::ShowDisplayFilterOverlay()
{
	CloseDisplayFilterOverlay();

	// create an overlay
	Asura_Overlay* pxOverlay = new Asura_Overlay( "Env Hooks Display Filter", false, &s_xDisplayFilterPosition );
	pxOverlay->m_uID = TOOLS_OVERLAY_ATTRACTORS_SETFILTER;
	Asura_Assert( pxOverlay != NULL, "Cant create env hooks overlay" );

	Asura_Vector_2 xOffset( 1.0f, 1.0f );
	Asura_Dialog* pxDialog;

	Asura_Subwindow_Dialog *pxSubWindowDialog = new Asura_Subwindow_Dialog(Asura_Vector_2( 1.0f, 3.0f ), 25, 14, false);
	pxOverlay->AddDialog(pxSubWindowDialog);

	{
		Asura_String xTypeName =  "*None*";
		Asura_Hash_ID uHashID  = 0;
		pxDialog = new Asura_Tickbox_Dialog( xTypeName.GetString(), xOffset, GetAttractorTypeVisibilityFlag(uHashID));
		pxSubWindowDialog->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();

	}

	u_int uNumAttractorTypes = Tools_Attractor_System::GetInstance()->GetNumAttractorTypes();
	for (u_int u = 0; u < uNumAttractorTypes; ++u)
	{
		const Tools_Attractor_Type* pxType = Tools_Attractor_System::GetInstance()->GetAttractorTypeByIndex(u);
		Asura_String xTypeName =  pxType ? pxType->m_xName.GetString() : "*Error*";
		Asura_Hash_ID uHashID  = xTypeName.GetHash();
		pxDialog = new Asura_Tickbox_Dialog( xTypeName.GetString(), xOffset, GetAttractorTypeVisibilityFlag(uHashID));
		pxSubWindowDialog->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();
	}
	xOffset.y = 14.0f + 3.0f + Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog( "Hide All", xOffset, HideAllAttractorTypes);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();
	pxDialog = new Asura_Function_Dialog( "Show All", xOffset, ShowAllAttractorTypes);
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();
	pxDialog = new Asura_Function_Dialog( "Invert", xOffset, InvertAttractorTypesVisible);
	pxOverlay->AddDialog( pxDialog );
	xOffset.x += 20;

	pxDialog = new Asura_Function_Dialog( "Close", xOffset, CloseDisplayFilterOverlay);
	pxOverlay->AddDialog( pxDialog );

	// submit the overlay
	Asura_Overlay_System::SubmitNewOverlay( pxOverlay, false );	

}


void Tools_Mode_Attractors::InitialiseOverlay()
{
	
	CloseOverlay();

	// create an overlay
	Asura_Overlay* pxOverlay = new Asura_Overlay( "Env Hooks", false, &s_xOverlayPosition );
	pxOverlay->m_uID = TOOLS_OVERLAY_ATTRACTORS_MAIN;
	Asura_Assert( pxOverlay != NULL, "Cant create env hooks overlay" );
	
	Asura_Vector_2 xOffset( 1.0f, 3.0f );
	Asura_Dialog* pxDialog;

	pxDialog = new Asura_Function_Dialog( "Add Env Hook Node [Ctrl+T]", xOffset, _OnAddAttractorNode, NULL, _IsAddAttractorNodeEnabled );
	pxDialog->SetHelpText("Adds a new node to the selected env hook , or creates the first node in a new env hook if none is selected.\nShortcut : Ctrl+T while edge selection is off.");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog( "Make Edge Env Hook [Ctrl+T]", xOffset, _OnMakeEdgeAttractorNode, NULL, _IsMakeEdgeAttractorNodeEnabled );
	pxDialog->SetHelpText("Creates env hooks from the selected edges.\nShortcut : Ctrl+T whlle doing edge selection.");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog( "Edge Selection Mode [E]", xOffset, &m_bEdgeSelectionMode );
	pxDialog->SetHelpText("Enables edge selection.");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	Tools_Object_System::AddMissionFileTickbox(pxOverlay, xOffset);

	xOffset.y += Asura_Dialog::LargeSpacing()-Asura_Dialog::DefaultSpacing();


	if(Tools_SaveAsura::IsEditingMission())
	{
		pxDialog = new Asura_Function_Dialog( "Find By ID (Common)", xOffset, _FindByID );
		pxDialog->SetHelpText("Finds a node from a node or env hook ID , checking the IDs of env hooks in the common file.");
		pxOverlay->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::DefaultSpacing();

		pxDialog = new Asura_Function_Dialog( "Find By ID (Mission)", xOffset, _FindByID_MissionFile );
		pxDialog->SetHelpText("Finds a node from a node or env hook ID , checking the IDs of env hooks in the mission file.");
		pxOverlay->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::LargeSpacing();

	}
	else
	{
		pxDialog = new Asura_Function_Dialog( "Find By ID", xOffset, _FindByID );
		pxDialog->SetHelpText("Finds a node from a node or env hook ID.");
		pxOverlay->AddDialog( pxDialog );
		xOffset.y += Asura_Dialog::LargeSpacing();
	}

	pxDialog = new Asura_Numerical_Dialog("Scale Distance", xOffset, &Tools_Object_Attractor_Node::s_fScaleWhenCloseDist, 0.0f, 999.0f, 1.0f, "%.0f");
	pxDialog->SetHelpText("The tools objects for the env hooks will be scaled down when closer to the camera than this distance.\nThis setting has no effect outside of tools.");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing() * 1.5f;



	pxDialog = new Asura_Numerical_Dialog( "", xOffset, &s_fDefaultRadius, 0.0f, 100.0f, 0.5f, "%0.2f");
	pxDialog->SetHelpText("If enabled this determines the default radius to use for newly created env hooks.\nSome env hook types may enforce a specific radius , in which case this will be ignored.");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = pxDialog->m_xMaxCoord.x;
	pxDialog = new Asura_Tickbox_Dialog("Default Radius",xOffset,&s_bUseDefaultRadius);
	pxDialog->SetHelpText("Determines whether the default radius should be used for all newly created env hooks");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	

	pxDialog = new Tools_Attractor_Type_Dialog(xOffset, Tools_Attractor_System::GetInstance()->GetNumAttractorTypes(), &s_xDefaultBlueprintHash, "Default Type");
	pxDialog->SetHelpText("Sets The default blueprint used by new Env Hooks");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Filter by type", xOffset, ShowDisplayFilterOverlay);
	pxDialog->SetHelpText("Allows Env hooks to be hidden based on their type (blueprint)");
	pxOverlay->AddDialog( pxDialog );
	xOffset.y += Asura_Dialog::DefaultSpacing();


	// submit the overlay
	Asura_Overlay_System::SubmitNewOverlay( pxOverlay, false );	

}


void Tools_Mode_Attractors::CloseOverlay()
{
	// if it exists already, shut it down
	if ( Asura_Overlay_System::DoesOverlayExist( TOOLS_OVERLAY_ATTRACTORS_MAIN ) )
	{
		Asura_Overlay_System::CloseOverlays( TOOLS_OVERLAY_ATTRACTORS_MAIN );
	}
}


///////////////////////////////////////////////////////////////////////////////////////
// Callbacks
///////////////////////////////////////////////////////////////////////////////////////


void Tools_Mode_Attractors::OnAddAttractorNode()
{
	Tools_Attractor_Node* pxSelectedNode = 0;
	if(Tools_Object_System::SelectedObject && Tools_Object_System::SelectedObject->Type == TOT_AttractorNode)
	{
		Tools_Object_Attractor_Node* const pxToolsNode = static_cast<Tools_Object_Attractor_Node*>( Tools_Object_System::SelectedObject );
		pxSelectedNode = pxToolsNode->GetNode();
			
	}

	AddNewAttractorNode( pxSelectedNode );
}


bool Tools_Mode_Attractors::IsAddAttractorNodeEnabled()
{
	int iNumberOfSelectedObjects = Tools_Object_System::SelectedObjectList.size();

	if ( iNumberOfSelectedObjects == 0 )
	{
		return true;
	}
	else if ( iNumberOfSelectedObjects == 1 )
	{
		Tools_Object* const pxToolsObject = Tools_Object_System::SelectedObjectList[ 0 ];
		if ( pxToolsObject->Type != TOT_AttractorNode )
		{
			return false;
		}

		Tools_Object_Attractor_Node* const pxToolsNode = static_cast<Tools_Object_Attractor_Node*>( pxToolsObject );
		
		// if we've already got a next and a previous then we can't insert
		if ( ( pxToolsNode->GetNode()->GetPrev() ) && ( pxToolsNode->GetNode()->GetNext() ) )
		{
			return false;
		}

		// otherwise everything is okay

		return true;
	}

	return false;
}


struct EdgeAttractorElement
{
	EdgeAttractorElement() {}
	EdgeAttractorElement( int m, int v, Tools_Attractor_Node* n ) { iModule = m; iVert = v; pxNode = n; }
	int iModule;
	int iVert;
	Tools_Attractor_Node* pxNode;
};


void Tools_Mode_Attractors::OnMakeEdgeAttractorNode()
{

	Tools_DynamicArray<EdgeAttractorElement> xCreated;

	for( LIF<ModuleEdgeTriplet> xLIF( Tools_Edge_Selection::GetList() ); !xLIF.done(); xLIF.next() )
	{
		const ModuleEdgeTriplet& xEdge = xLIF();

		// Get positions of the edge verts

		const Asura_Mesh* const pxMesh = Asura_Module_System::GetMeshForModule( xEdge.m_iModuleIndex );

		Asura_Vector_3 xPosition;
		Asura_Module_System::GetPositionOfModule( xEdge.m_iModuleIndex, xPosition );

		const Asura_Vector_3 xPos1 = xPosition + pxMesh->VertexDataPtr[ xEdge.m_aiVertexIndex[0] ].Position;
		const Asura_Vector_3 xPos2 = xPosition + pxMesh->VertexDataPtr[ xEdge.m_aiVertexIndex[1] ].Position;

		// Search for already created nodes

		Tools_Attractor_Node* pxExisting1 = NULL;
		Tools_Attractor_Node* pxExisting2 = NULL;

		for ( unsigned int i = 0; i < xCreated.GetSize(); i++ )
		{
			if ( xCreated[ i ].iModule == xEdge.m_iModuleIndex )
			{
				if ( xCreated[ i ].iVert == xEdge.m_aiVertexIndex[0] )
				{
					pxExisting1 = xCreated[ i ].pxNode;
				}
				else if ( xCreated[ i ].iVert == xEdge.m_aiVertexIndex[1] )
				{
					pxExisting2 = xCreated[ i ].pxNode;
				}
			}
		}

		// check existing arn't mid points
		// otherwise don't use them

		if ( pxExisting1 )
		{
			if ( ( pxExisting1->GetPrev() ) && ( pxExisting1->GetNext() ) )
			{
				pxExisting1 = NULL;
			}
		}
		if ( pxExisting2 )
		{
			if ( ( pxExisting2->GetPrev() ) && ( pxExisting2->GetNext() ) )
			{
				pxExisting2 = NULL;
			}
		}

		// Now either 
		// (a) create a new attractor - if neither end is a valud
		// end point of an attractor.
		// (b) close the attractor - if both ends are on the same 
		// attractor
		// (c) combine the attractors if the points are the ends from
		// two different attractors.
		// (d) and (e) create a single node attached to one existing
		// node and attach it up
		// for new nodes store that we have created them for other 
		// edges in the list.

		if ( ( !pxExisting1 ) && ( !pxExisting2 ) )
		{
			pxExisting1 = AddNewAttractorNode( NULL, &xPos1 );
			pxExisting2 = AddNewAttractorNode( pxExisting1, &xPos2 );
			xCreated += EdgeAttractorElement( xEdge.m_iModuleIndex, xEdge.m_aiVertexIndex[0], pxExisting1 );
			xCreated += EdgeAttractorElement( xEdge.m_iModuleIndex, xEdge.m_aiVertexIndex[1], pxExisting2 );

			// In this case, calculate a normal from the edge, as this has yet to be defined in the attractor.
			// We need to find the faces either side of the edge.
			const Tools_Polygon_Adjacency* pxAdjacentTo1 = Tools_Adjacency::GetVertexAdjacencies(xEdge.m_iModuleIndex, xEdge.m_aiVertexIndex[0]);
			// Cycle through the first vertex's adjacent polys to find those also adjacent to the second
			int iPolyIndices[2];
			int iNumPolys = 0;
			for (const Tools_Adjacent_Polygon* pxAdj = pxAdjacentTo1->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
			{
				int iIndex = pxAdj->GetPolygonIndex();
				for (int j=0; j<pxMesh->PolygonDataPtr[iIndex].NumberOfVertices(); ++j)
				{
					if (pxMesh->PolygonDataPtr[iIndex].VertexID[j] == xEdge.m_aiVertexIndex[1])
					{
						iPolyIndices[iNumPolys++] = iIndex;
						if (iNumPolys == 2) { break; }
					}
				}

				if (iNumPolys == 2) { break; }
			}

			if (iNumPolys > 0)
			{
				// Get the normals of these polys
				Asura_Vector_3 xPolyNormals[2];
				for (int i=0; i<iNumPolys; ++i)
				{
					Asura_Vector_3 xDir1(pxMesh->VertexDataPtr[ pxMesh->PolygonDataPtr[iPolyIndices[i]].VertexID[1] ].Position - pxMesh->VertexDataPtr[ pxMesh->PolygonDataPtr[iPolyIndices[i]].VertexID[0] ].Position);
					Asura_Vector_3 xDir2(pxMesh->VertexDataPtr[ pxMesh->PolygonDataPtr[iPolyIndices[i]].VertexID[2] ].Position - pxMesh->VertexDataPtr[ pxMesh->PolygonDataPtr[iPolyIndices[i]].VertexID[1] ].Position);
					xPolyNormals[i] = xDir1 ^ xDir2;
					xPolyNormals[i].Normalise();
				}

				Asura_Vector_3 xNormal;
				if (iNumPolys == 1)
				{
					xNormal = xPolyNormals[0];
				}
				else
				{
					xNormal = (xPolyNormals[0] + xPolyNormals[1]) * 0.5f;
				}

				// Finally, convert this to our rotation.
				pxExisting1->GetAttractor()->SetNormal(xNormal);
			}
		}
		else if ( pxExisting1 && pxExisting2 )
		{
			if(pxExisting1->GetAttractor() == pxExisting2->GetAttractor())
			{
				pxExisting1->GetAttractor()->SetClosed( true );
			}
			else
			{
				//nodes are from the ends of two different attractors ,so glue them together
				Tools_Attractor* pxAttractor1 = pxExisting1->GetAttractor();
				Tools_Attractor* pxAttractor2 = pxExisting2->GetAttractor();

				bool bWasFirst1 = (pxAttractor1->GetFirstNode() == pxExisting1);
				bool bWasFirst2 = (pxAttractor2->GetFirstNode() == pxExisting2);

				Tools_Attractor_Node* pxNode1 = pxExisting1;
				Tools_Attractor_Node* pxNode2 = pxExisting2;

				while(pxNode2)
				{
					//get the 'next' node in the second attractor before fidddling with it
					Tools_Attractor_Node* pxNext2 = bWasFirst2 ? pxNode2->GetNext() : pxNode2->GetPrev();

					//link in with the nodes in the first attractor
					pxNode2->SetAttractorData(pxAttractor1);
					if(bWasFirst1)
					{
						pxNode1->SetPrevAndNext(pxNode2,pxNode1->GetNext());
						pxNode2->SetPrevAndNext(0,pxNode1);
					}
					else
					{
						pxNode1->SetPrevAndNext(pxNode1->GetPrev(),pxNode2);
						pxNode2->SetPrevAndNext(pxNode1,0);
					}
                    				

					//move onto the next node in the second attractor
					pxNode1 = pxNode2;
					pxNode2 = pxNext2;
				}

				//Fix up the first pointer if we were adding to the start
				if(bWasFirst1) pxAttractor1->SetFirstNode(pxNode1);
				
				//delete the nodeless second attractor
				pxAttractor2->SetFirstNode(0);
				Tools_Attractor_System::GetInstance()->DeleteAttractor(pxAttractor2);
                				
			}
		}
		else if ( pxExisting2 )
		{
			pxExisting1 = AddNewAttractorNode( pxExisting2, &xPos1 );
			xCreated += EdgeAttractorElement( xEdge.m_iModuleIndex, xEdge.m_aiVertexIndex[0], pxExisting1 );
		}
		else // if ( pxExisting1 )
		{
			pxExisting2 = AddNewAttractorNode( pxExisting1, &xPos2 );
			xCreated += EdgeAttractorElement( xEdge.m_iModuleIndex, xEdge.m_aiVertexIndex[1], pxExisting2 );
		}
	
	}
	
	// done now clear selection and move
	// to object select mode
	Tools_Edge_Selection::DeselectAll();
	m_bEdgeSelectionMode = false;

	//Check for any nodes that can be removed for being colinear
	for ( unsigned int i = 0; i < xCreated.GetSize(); i++ )
	{
		Tools_Attractor_Node* pxNode = xCreated[i].pxNode;

		const Tools_Attractor_Node* pxPrev = pxNode->GetPrevLooped();
		const Tools_Attractor_Node* pxNext = pxNode->GetNextLooped();

		if(!pxNext || !pxPrev) continue;
		
		//Is the middle node on a straight line from the previous to the next?
		Asura_Vector_3 xOffset1 = pxNode->GetPosition() - pxPrev->GetPosition();
		Asura_Vector_3 xOffset2 = pxNext->GetPosition() - pxNode->GetPosition();

		float fMag1 = xOffset1.Magnitude();
		float fMag2 = xOffset2.Magnitude();

		bool bDiscardNode = false;
		if(fMag1 < 0.002f || fMag2 < 0.002f)
		{
			//point is very close to the previous or next , so may as well ditch it
			bDiscardNode = true;
		}
		else
		{
			float fDot = (xOffset1/fMag1)*(xOffset2/fMag2);

			if(fDot > 0.995) bDiscardNode = true;
		}

		if(!bDiscardNode) continue;

		//destroying the tools object should sort everthing out
		pxNode->GetToolsNode()->Destroy();
		
		//remove from the created array
		xCreated[i] = xCreated[xCreated.GetSize()-1];
		xCreated.SetSize(xCreated.GetSize()-1);
		i--;
	}


	// deselect all objects then select all the ones
	// we've created

	Tools_Object_System::DeselectAllObjects();
	for ( unsigned int i = 0; i < xCreated.GetSize(); i++ )
	{
		Tools_Object_System::SelectObject( xCreated[ i ].pxNode->GetToolsNode(), true );
	}
}


bool Tools_Mode_Attractors::IsMakeEdgeAttractorNodeEnabled()
{
	return ( ( m_bEdgeSelectionMode ) && ( Tools_Edge_Selection::CountSelectedEdges() > 0 ) );
}


///////////////////////////////////////////////////////////////////////////////////////
// Functions
///////////////////////////////////////////////////////////////////////////////////////


Tools_Attractor_Node* const Tools_Mode_Attractors::AddNewAttractorNode( class Tools_Attractor_Node* const pxNode, const Asura_Vector_3* const pxPos )
{
	Tools_Attractor_System* const pxAttractorSystem = Tools_Attractor_System::GetInstance();
	
	// get the attractor (or
	// create a new one)

	Tools_Attractor* pxAttractor;
	
	if ( pxNode )
	{
		pxAttractor = pxNode->GetAttractor();
	}
	else
	{
		pxAttractor = new Tools_Attractor(Tools_Object_System::ShouldCreateNewObjectsInMissionFile());
		pxAttractorSystem->AddAttractor( pxAttractor );

		if (s_xDefaultBlueprintHash != ASURA_HASH_ID_UNSET)
		{
			pxAttractor->SetAttractorType(s_xDefaultBlueprintHash);
		}
		if(s_bUseDefaultRadius)
		{
			//Set the radius unless the attractor type used enforces its own radius
			const Tools_Attractor_Type* pxAttractorType = Tools_Attractor_System::GetInstance()->GetAttractorTypeByHash(s_xDefaultBlueprintHash);
			if(!pxAttractorType || !pxAttractorType->m_bMustBeDefaultRadius)
			{
				pxAttractor->SetRadius(s_fDefaultRadius);
			}
		}

	}

	// work out if we are start or end

	bool bAddBefore = false;
	if ( pxNode )
	{
		if ( pxNode->GetAttractor()->GetFirstNode() == pxNode )
		{
			bAddBefore = true;
		}
		else
		{
			Asura_Assert( pxNode->GetNext() == NULL, "Cant add to middle of a node" );
		}
	}

	// create the new node on the attractor

	Tools_Attractor_Node* const pxNewNode = new Tools_Attractor_Node( pxAttractor );
	pxAttractorSystem->AddAttractorNode( pxNewNode );

	//assign a new ID
	pxNewNode->SetID(pxAttractorSystem->GenerateID(pxAttractor->BelongsToMissionFile()));

	// set it's position

	if ( pxPos )
	{
		pxNewNode->SetPosition( *pxPos );
	}
	#if 1
	else
	{
		pxNewNode->SetPosition(*Asura_Camera::GetPosition());
	}
	#else
	else if ( pxNode )
	{
		Asura_Vector_3 xRight;
		Asura_Camera::GetRightVector( xRight );
		if ( bAddBefore )
		{
			xRight *= -1.0f;
		}
		pxNewNode->SetPosition( pxNode->GetPosition() + xRight );
	}
	else
	{
		Asura_Vector_3 xForward;
		Asura_Camera::GetForwardVector( xForward );
		pxNewNode->SetPosition( *Asura_Camera::GetPosition() + xForward );
	}
	#endif
	
	// create the tools node
	// for our node and add it
	// to the list

	Tools_Object_Attractor_Node* const pxToolsNode = new Tools_Object_Attractor_Node( pxNewNode );

	// set next and previous
	// for us and the one
	// that was passed in

	if ( pxNode )
	{
		if ( bAddBefore )
		{
			Asura_Assert( pxNode->GetPrev() == NULL, "AddNewAttractorNode called with node that already has a previous pointer" );
			pxNewNode->SetPrevAndNext( NULL, pxNode );
			pxNode->SetPrevAndNext( pxNewNode, pxNode->GetNext() );
		}
		else
		{
			Asura_Assert( pxNode->GetNext() == NULL, "AddNewAttractorNode called with node that already has a next pointer" );
			pxNewNode->SetPrevAndNext( pxNode, NULL );
			pxNode->SetPrevAndNext( pxNode->GetPrev(), pxNewNode );
		}
	}

	// set attractor first

	if ( ( !pxNode ) || ( bAddBefore ) )
	{
		pxAttractor->SetFirstNode( pxNewNode );
	}

	// select it

	Tools_Object_System::SelectObject( pxToolsNode );

	pxAttractor->SetModified();

	return pxNewNode;

}
void Tools_Mode_Attractors::FindByID()
{
	FindByIDMain(false);
}

void Tools_Mode_Attractors::FindByID_MissionFile()
{
	FindByIDMain(true);
}

void Tools_Mode_Attractors::FindByIDMain(bool bMissionFile)
{
	GetIntNumber_Overlay xOverlay("Enter ID of Env Hook/Node",0,0,0x7fffffff);
	if(!xOverlay.DoModal()) return;
	u_int uID = (u_int)xOverlay.GetNumber();

	if(!uID) return;
		
	if(bMissionFile) uID |= TOOLS_ATTRACTORID_MISSIONFILE;

	//search for a tools object for a node/attractor with this ID
	Tools_Object* pxFoundObject = 0;
	for(Tools_Object_Attractor_Node::NODE_LIST_IT xNodeLIF(Tools_Object_Attractor_Node::GetList());!xNodeLIF.done();xNodeLIF.next())
	{
		Tools_Attractor_Node* pxNode = xNodeLIF()->GetNode();

		if(pxNode->GetID() == uID || pxNode->GetAttractor()->GetID() == uID)
		{
			pxFoundObject = xNodeLIF();
			break;
		}
	}

	//goto and select the object
	if(pxFoundObject)
	{
		Tools_Object_System::GotoAndSelect(pxFoundObject);
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to find an env hook with ID %d",uID&~TOOLS_ATTRACTORID_MISSIONFILE);
	}
}


#endif // ASURA_USE_ATTRACTORS
