//
// Tools_Polygon_Selection.cpp
//
// Created by Kevin Lea - 19/07/2001 11:38:02
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Misc/Tools_Polygon_Selection.h"
#include "Misc/Tools_Module_Element.h"

#include "Rendering/Asura_Module.h"
#include "Mesh/Asura_Mesh.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Interface.h"
#include "Interface/Asura_Cursor.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_UserInput.h"
#include "Core/Asura_HashTree.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Render.h"
#include "Camera/Asura_Camera.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Load&Save/Tools_ModeDataFile.h"
#include "Misc/Tools_LOS.h"
#include "Misc/Tools_Adjacency.h"
#include "Texturing/Tools_Texturing_Find.h"
#include "Misc/Tools_Prompted_Selection.h"
#include "Misc/Tools_StringUtil.h"



#include "Debugging/Asura_Assert.h"

#include "Tools_Overlays.h"
#include "Misc/Tools_Help.h"

#include "Misc/Tools_Render.h"
#include "Misc/Tools_Render_Options.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Rendering/Tools_BatchRenderPolygonOutline.h"

static u_int g_uSelectionFrameStamp;

///////////////////////////////////////////////////////////////////////////////////////
/*--------------**
** Help Strings **
**--------------*/

static const char* PolygonSelectionHelpStrings [] =
{
	"LMB :         Select polygon,element or object",
	"Shift+LMB :   Deselect polygon,element or object",
	"",
	"During polygon or object selection - ",
	"Left Ctrl :   Enables drag selection. Polygons will be selected if all vertices are in rectangle.",
	"Right Ctrl :  Enables drag selection. Polygons will be selected if any vertices are in rectangle.",
	"",
	"During UV shell selection - ",
	"Ctrl+LMB : Select UV shell based on secondary UV coordinates.",
	"",
	"L :           Clear selection",
	"",
	"Shift+Z :     Undo selection",
	"Shift+Y :     Redo selection",
	"Shift+< :     Undo selection",
	"Shift+> :     Redo selection",
	NULL,
};


///////////////////////////////////////////////////////////////////////////////////////
/*-------------------**
** ItemSelectionList **
**-------------------*/

ItemSelectionList::ItemSelectionList(int numitems)
{
	numItems = numitems;
	numSelected = 0;	
	iterationPos = 0;

	Selectable = true;
	
	if(numItems && Selectable)
	{
		SelectedItems = new int[numitems];
		ItemIsSelected = new int[numitems];

		for(unsigned i=0;i<numItems;i++)
		{
			ItemIsSelected[i]=-1;
		}
	}
	else
	{
		SelectedItems = 0;
		ItemIsSelected = 0;
	}
}


ItemSelectionList::~ItemSelectionList()
{
	delete [] SelectedItems;
	delete [] ItemIsSelected;
}

void ItemSelectionList::Add(int index)
{
	if(!Selectable) return;

		//make sure not already selected
	if(IsSelected(index)) return;
	
	//log the addition of this item
	ItemBeingSelected(index);

	//add index to the selection array
	SelectedItems[numSelected] = index;
	//and record which index in the array this went to
	ItemIsSelected[index] = numSelected;
	
	numSelected++;	
}

void ItemSelectionList::Remove(int index)
{
	if(!Selectable) return;

	if(!IsSelected(index)) return;

	//log the removal of this item
	ItemBeingDeselected(index);
	
	//find the slot used by this item
	int slot = ItemIsSelected[index];

	//swap the last item in the list down into this slot
	SelectedItems[slot] = SelectedItems[numSelected-1];
	ItemIsSelected[SelectedItems[slot]] = slot;

	//mark this item as unselected
	ItemIsSelected[index] = -1;

	numSelected--;
}

void ItemSelectionList::SelectAll()
{
	if(!Selectable) return;
	
	for(unsigned i=0;i<numItems;i++)
	{
		Add(i);
	}
}

void ItemSelectionList::ClearList()
{	 
	if(!Selectable) return;

	for(unsigned i=0;i<numSelected;i++)
	{
		ItemBeingDeselected(SelectedItems[i]);
		
		ItemIsSelected[SelectedItems[i]] = -1;
		SelectedItems[i] = -1;
	}

	numSelected = 0;
}

int ItemSelectionList::Current()
{
	Asura_Assert(iterationPos>=0 && iterationPos<numSelected,"Iteration through selection is out of bounds"); 
	return SelectedItems[iterationPos];
}

bool ItemSelectionList::IsSelected(int index)
{
	if(!Selectable) return FALSE;
    Asura_Assert(index>=0 && index<(int)numItems,"Polygon index is out of bounds");
	return ItemIsSelected[index]!=-1;
}

////////////////////////////////////////////////////////////////////////////////////////
/*----------------------**
** PolygonSelectionList **
**----------------------*/

PolygonSelectionList::PolygonSelectionList(int module,int numpoly)
:ItemSelectionList(numpoly)
{
	module_index = module;
}


void PolygonSelectionList::ItemBeingSelected(int index)
{
	Tools_Polygon_Selection::SelectionUndo.LogPolySelected(module_index,index);
	g_uSelectionFrameStamp = Asura_Timers::GetFrameNumber();
}

void PolygonSelectionList::ItemBeingDeselected(int index)
{
	Tools_Polygon_Selection::SelectionUndo.LogPolyDeselected(module_index,index);
	g_uSelectionFrameStamp = Asura_Timers::GetFrameNumber();
}


////////////////////////////////////////////////////////////////////////////////////////
/*----------------------**
** PolygonSelectionUndo **
**----------------------*/

void PolygonSelectionUndo::ProcessUndoData(void* data)
{
	PolygonSelectionUndoData* undo_data = (PolygonSelectionUndoData*) data;
	
	switch(undo_data->undo_type)
	{
		case PolygonSelectionUndoData::UndoType_Added :
			//deselect the polygon
			Tools_Polygon_Selection::DeselectPolygon(undo_data->module_index,undo_data->poly_index);
			//convert the undo data
			undo_data->undo_type = PolygonSelectionUndoData::UndoType_Removed;
			break;


		case PolygonSelectionUndoData::UndoType_Removed :
			//select the polygon
			Tools_Polygon_Selection::SelectPolygon(undo_data->module_index,undo_data->poly_index);
			//convert the undo data
			undo_data->undo_type = PolygonSelectionUndoData::UndoType_Added;
			break;

		default :
			Asura_Assert(0,"Unexpected undo type for polygon selection");
			break;
	}
}

void PolygonSelectionUndo::LogPolySelected(int module,int poly)
{
	PolygonSelectionUndoData* undo = (PolygonSelectionUndoData*) AllocateNextUndoData();
	if(!undo) return;

	undo->undo_type = PolygonSelectionUndoData::UndoType_Added;
	undo->module_index = module;
	undo->poly_index = poly;
}

void PolygonSelectionUndo::LogPolyDeselected(int module,int poly)
{
	PolygonSelectionUndoData* undo = (PolygonSelectionUndoData*) AllocateNextUndoData();
	if(!undo) return;

	undo->undo_type = PolygonSelectionUndoData::UndoType_Removed;
	undo->module_index = module;
	undo->poly_index = poly;
}

////////////////////////////////////////////////////////////////////////////////////////
/*-------------------------**
** Tools_Polygon_Selection **
**-------------------------*/

//static member variables
int 					Tools_Polygon_Selection::NumberOfModules;
int 					Tools_Polygon_Selection::NumSelectedModules;
PolygonSelectionList** 	Tools_Polygon_Selection::Selection = NULL;
Tools_Module_Elements** Tools_Polygon_Selection::ElementData = NULL;
PolygonSelectionUndo	Tools_Polygon_Selection::SelectionUndo;
bool 					Tools_Polygon_Selection::SelectionLocked;
bool 					Tools_Polygon_Selection::ShowSelection;
int 					Tools_Polygon_Selection::SelectionMode;
int 					Tools_Polygon_Selection::CurrentIterationModule;
void  					(*Tools_Polygon_Selection::SelectionClearedCallback)();

bool 					Tools_Polygon_Selection::ButtonDownForSelection;
Asura_Vector_2			Tools_Polygon_Selection::SelectionOverlayPosition = Asura_Vector_2(3.0f,6.0f);
bool					Tools_Polygon_Selection::SelectionOverlayOpen;
u_int					Tools_Polygon_Selection::s_uSelectionOverlayMinimised = 0;
PolygonID 				Tools_Polygon_Selection::HighlitPolygon;//polygon mouse is currently over
bool					Tools_Polygon_Selection::s_bActive = false;
bool					Tools_Polygon_Selection::s_bLimitSelectionToSameMaterial = false;
bool					Tools_Polygon_Selection::s_bLimitSelectionToSameObject = false;
bool					Tools_Polygon_Selection::s_bMaterialValid = false;

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
	Asura_Hash_ID		Tools_Polygon_Selection::s_uMaterialResponseHashID = ASURA_HASH_ID_UNSET;
#else
	int					Tools_Polygon_Selection::s_iMaterial;
#endif

bool					Tools_Polygon_Selection::s_bDragSelection = false;
Asura_Vector_2			Tools_Polygon_Selection::s_xDragStart;
Asura_Vector_2			Tools_Polygon_Selection::s_xDragCurrent;
bool					Tools_Polygon_Selection::s_bAllowBackfaceSelection = false;

float					Tools_Polygon_Selection::s_fPlaneAngleTolerance = 8.0f;
float					Tools_Polygon_Selection::s_fCurveAngleTolerance = 20.0f;

// (SB - 12/12/06) Support for 'Select by polygon data' option
bool					Tools_Polygon_Selection::s_bLimitSelectionToSamePolygonData = false;
unsigned int			Tools_Polygon_Selection::s_uPolyData = 0;

// (SB - 29/01/07) Support for 'Require shared edge' option
bool					Tools_Polygon_Selection::s_bRequireSharedEdge = false;

void Tools_Polygon_Selection::Initialise()
{
	Asura_Assert(!Selection,"Polygon selection has already been initialised");

	
	NumberOfModules = Asura_Module_System::GetNumberOfModules();

	//allocate a PolygonSelectionList for each module
	Selection=new PolygonSelectionList*[NumberOfModules];
	ElementData = new Tools_Module_Elements*[NumberOfModules];
	for(int i=0;i<NumberOfModules;i++)
	{
		//get the mesh so we can find the number of polygons
		Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(i);
		Asura_Assert(mesh,"Failed to get the mesh for the module");
		
		Selection[i]=new PolygonSelectionList(i,mesh->NumberOfPolygons);

		ElementData[i] = new Tools_Module_Elements(i);
	}
	SelectionLocked = false;
	ShowSelection = true;
	SelectionMode = SelectionMode_Polygon;
	NumSelectedModules = 0;
	SelectionClearedCallback = NULL;
	ButtonDownForSelection = false;
	HighlitPolygon.ModuleIndex = -1;

	//load values from .dat file
	TransferModeData(TOOLSMODEDATAFILE_LOAD);
	
	SelectionOverlayOpen = true;


	SelectionUndo.Initialise();

}

void Tools_Polygon_Selection::Shutdown()
{

	//save values to .dat file
	TransferModeData(TOOLSMODEDATAFILE_SAVE);
	
	for(int i=0;i<NumberOfModules;i++)
	{
		delete Selection[i];
		delete ElementData[i];
	}
	delete [] Selection;
	delete [] ElementData;
	Selection = NULL;

	SelectionUndo.Shutdown();

	//unhide any polygons that were hidden
	Unhide();
}

void Tools_Polygon_Selection::Activate()
{
	s_bActive = true;

	SelectionClearedCallback = NULL;
	HighlitPolygon.ModuleIndex = -1;

	CreateSelectionOverlay();

	//add help
	Tools_Help::AddHelpTopic("Polygon Selection",PolygonSelectionHelpStrings);

	Tools_Render_Options::ActivateHideFlag(ToolsRenderFlag_PolygonSelectionHide);

	Tools_Texturing_Find::Initialise();

}

void Tools_Polygon_Selection::Deactivate()
{
	Tools_Texturing_Find::Shutdown();


	Tools_Render_Options::DeactivateHideFlag(ToolsRenderFlag_PolygonSelectionHide);

	Tools_Help::RemoveHelpTopic("Polygon Selection");
	CloseSelectionOverlay();

	s_bActive = false;
}

void Tools_Polygon_Selection::TransferModeData(int mode)
{
	//open the file
	Tools_ModeDataFile file("Selection.dat",mode);

	//tranfer the data
	//(none of these should be removed , and new data should go on the end, otherwise
	//the files will get screwed up)
	bool bJunk = false;
	if( false == file.GetError())
	{
		file.Transfer(SelectionOverlayPosition);
		file.Transfer(bJunk);
		file.Transfer(s_uSelectionOverlayMinimised);
	}
}


void Tools_Polygon_Selection::Update(int update_mode /*= PolygonSelectionUpdate_Standard*/)
{

	//Flag the selection undo to start a new undo batch
	SelectionUndo.MarkUndoStart();

	//if the mouse button has been release , reset the ButtonHeldForSelection flag
	if(!Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1))
	{
		ButtonDownForSelection = false;
	}

	//reset the highlit polygon
	HighlitPolygon.ModuleIndex = -1;
	
	bool bMouseSelectionEnabled = false;
	if(update_mode != PolygonSelectionUpdate_NoSelection && !Tools_Prompted_Selection::IsSelecting())
	{
		//act upon keyboard input
		HandleKeyboard();

		if(update_mode == PolygonSelectionUpdate_Standard)
		{
			//and handle selection using the mouse
			HandleMouseSelection();
			bMouseSelectionEnabled = true;
		}
	}
	if(!bMouseSelectionEnabled) s_bDragSelection = false;
	
}

void Tools_Polygon_Selection::Render()
{
	//Show the currently selected polygons
	if(ShowSelection)
	{
		HighlightSelection();
	}

	if(HighlitPolygon.ModuleIndex>=0)
	{
		HighlightPolygon(HighlitPolygon);
	}

}

void Tools_Polygon_Selection::RenderUI()
{

	if(s_bDragSelection)
	{
		//determine the colour for the lines
		float fBright = Asura_Maths::Sine( Asura_Timers::GetLocalUnscaledTime() * 2.0f );
		int iBright = Asura_Maths::FloatToInt(255.0f*fBright*fBright);
		unsigned int uColour = 0xff000000 + (iBright) + (iBright<<8) + (iBright<<16);

		//draw drag rectangle

		Asura_Vector_2 xStart,xEnd;
		xEnd = s_xDragStart;
	
		xStart = xEnd;
		xEnd.x = s_xDragCurrent.x;
		Asura_Render::Render2DLine(xStart,xEnd,uColour);

		xStart = xEnd;
		xEnd.y = s_xDragCurrent.y;
		Asura_Render::Render2DLine(xStart,xEnd,uColour);

		xStart = xEnd;
		xEnd.x = s_xDragStart.x;
		Asura_Render::Render2DLine(xStart,xEnd,uColour);

		xStart = xEnd;
		xEnd.y = s_xDragStart.y;
		Asura_Render::Render2DLine(xStart,xEnd,uColour);
	}

}

unsigned int Tools_Polygon_Selection::CountSelectedPolygons()
{
	if (NULL == Selection)
	{
		return 0;
	}

	unsigned int uTotal = 0;
	for (int iModuleID = 0; iModuleID < NumberOfModules; iModuleID++)
	{
		uTotal += Selection[iModuleID]->NumSelected();
	}
	return uTotal;
}

void Tools_Polygon_Selection::SelectPolygon(int module_index,int polygon_index)
{
	Asura_Assert(module_index>=0 && module_index<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");
	
	if(SelectionLocked) return;

	//don't allow selection of hidden polygons
	if(!IsSelectable(module_index,polygon_index))
	{
		return;
	}

	if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
	{
		//make sure polygon has correct material
		#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

			if( GetMaterialResponseHashID( module_index, polygon_index ) != s_uMaterialResponseHashID )
			{
				return;
			}

		#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

			if(GetMaterialID(module_index,polygon_index) != s_iMaterial)
			{
				return;
			}

		#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
	}
	
	
	//mark this polygon as selected in the list for the module
	if(!Selection[module_index]->NumSelected())
	{
		NumSelectedModules++;
	}
	Selection[module_index]->Add(polygon_index);
}

void Tools_Polygon_Selection::DeselectPolygon(int module_index,int polygon_index)
{
	Asura_Assert(module_index>=0 && module_index<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");

	if(SelectionLocked) return;

	if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
	{
		//make sure polygon has correct material

		#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

			if( GetMaterialResponseHashID( module_index, polygon_index ) != s_uMaterialResponseHashID )
			{
				return;
			}

		#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

			if(GetMaterialID(module_index,polygon_index) != s_iMaterial)
			{
				return;
			}

		#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
	}


	//mark this polygon as deselected in the list for the module
	if(Selection[module_index]->IsSelected(polygon_index))
	{
		Selection[module_index]->Remove(polygon_index);
		if(!Selection[module_index]->NumSelected())
		{
			NumSelectedModules--;
			if(!NumSelectedModules)
			{
				//some things get reset when the selection is cleared
				if(SelectionClearedCallback) SelectionClearedCallback();
			}
		}
	}
}

void Tools_Polygon_Selection::SelectObject(int module_index)
{
	Asura_Assert(module_index>=0 && module_index<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");
	
	if(SelectionLocked) return;

	int NumSelectedBefore = Selection[module_index]->NumSelected();

	for(int i=0;i<Selection[module_index]->NumItems();i++)
	{
		//select all non-hidden polygons
		if((IsSelectable(module_index,i)))
		{
			if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
			{
				//make sure polygon has correct material
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if( GetMaterialResponseHashID( module_index, i ) != s_uMaterialResponseHashID )
					{
						continue;
					}

				#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if(GetMaterialID(module_index,i) != s_iMaterial)
					{
						continue;
					}

				#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
			}
			
			Selection[module_index]->Add(i);
		}
	}

	if(!NumSelectedBefore && Selection[module_index]->NumSelected())
	{
		NumSelectedModules++;
	}

}

void Tools_Polygon_Selection::DeselectObject(int module_index)
{
	Asura_Assert(module_index>=0 && module_index<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");

	if(SelectionLocked) return;

	//clear the selection list for the module
	if(Selection[module_index]->NumSelected())
	{
		if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
		{
			for(int i=0;i<Selection[module_index]->NumItems();i++)
			{
				DeselectPolygon(module_index,i);
			}
		}
		else
		{
			Selection[module_index]->ClearList();
			
			NumSelectedModules--;
			if(!NumSelectedModules)
			{
				//some things get reset when the selection is cleared
				if(SelectionClearedCallback) SelectionClearedCallback();
			}
		}

	}
}

void Tools_Polygon_Selection::SelectElement(int module_index,int polygon_index)
{
	Asura_Assert(module_index>=0 && module_index<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");
	
	if(SelectionLocked) return;

	int NumSelectedBefore = Selection[module_index]->NumSelected();

	//loop through each polygon in the element till we get back to the start
	int first_polygon_index = polygon_index;
	do
	{
		//select this polygon (if not hidden)
		if((IsSelectable(module_index,polygon_index)))
		{
			bool bSelect = true;
			if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
			{
				//make sure polygon has correct material
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if( GetMaterialResponseHashID( module_index, polygon_index ) != s_uMaterialResponseHashID )
					{
						bSelect = false;
					}

				#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if(GetMaterialID(module_index,polygon_index) != s_iMaterial)
					{
						bSelect = false;
					}

				#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
			}
			if(bSelect)
			{
				Selection[module_index]->Add(polygon_index);
			}
		}

		//get the next polygon
		polygon_index = ElementData[module_index]->GetNextPolygonInElement(polygon_index);
	}while(polygon_index!=first_polygon_index);

	if(!NumSelectedBefore && Selection[module_index]->NumSelected())
	{
		NumSelectedModules++;
	}

}

void Tools_Polygon_Selection::DeselectElement(int module_index,int polygon_index)
{
	Asura_Assert(module_index>=0 && module_index<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");

	if(SelectionLocked) return;

	//mark this polygon as deselected in the list for the module
	if(Selection[module_index]->NumSelected())
	{
		//loop through each polygon in the element till we get back to the start
		int first_polygon_index = polygon_index;
		do
		{
			bool bDeselect = true;
			if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
			{
				//make sure polygon has correct material
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if( GetMaterialResponseHashID( module_index, polygon_index ) != s_uMaterialResponseHashID )
					{
						bDeselect = false;
					}

				#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if(GetMaterialID(module_index,polygon_index) != s_iMaterial)
					{
						bDeselect = false;
					}

				#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
			}
			//deselect this polygon
			if(bDeselect)
			{
				Selection[module_index]->Remove(polygon_index);
			}
			//get the next polygon
			polygon_index = ElementData[module_index]->GetNextPolygonInElement(polygon_index);
		}while(polygon_index!=first_polygon_index);

		//have we deselected everything from this module?
		if(!Selection[module_index]->NumSelected())
		{
			NumSelectedModules--;
			if(!NumSelectedModules)
			{
				//some things get reset when the selection is cleared
				if(SelectionClearedCallback) SelectionClearedCallback();
			}
		}
	}
}

void Tools_Polygon_Selection::SelectPlane(const PolygonID& polygon,bool bCurve)
{
	if(SelectionLocked) return;

	//get a list of polygons in the plane
	List<PolygonID> polygonList;
	ListPolygonsInPlane(polygon,polygonList,bCurve,bCurve ? s_fCurveAngleTolerance : s_fPlaneAngleTolerance);

	//and select them all
	for(LIF<PolygonID> plif(&polygonList);!plif.done();plif.next())
	{
		if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
		{
			//make sure polygon has correct material
			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if( GetMaterialResponseHashID( plif().ModuleIndex, plif().PolyIndex ) != s_uMaterialResponseHashID )
				{
					continue;
				}

			#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if(GetMaterialID(plif().ModuleIndex,plif().PolyIndex) != s_iMaterial)
				{
					continue;
				}

			#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
		}

		// (SB - 12/12/06) Support for 'Select by polygon data' option
		if (s_bLimitSelectionToSamePolygonData)
		{
			if (GetPolyData(plif().ModuleIndex, plif().PolyIndex) != s_uPolyData)
			{
				continue;
			}
		}

		SelectPolygon(plif().ModuleIndex,plif().PolyIndex);
	}
}

void Tools_Polygon_Selection::DeselectPlane(const PolygonID& polygon,bool bCurve)
{
	if(SelectionLocked) return;

	//get a list of polygons in the plane
	List<PolygonID> polygonList;
	ListPolygonsInPlane(polygon,polygonList,bCurve,bCurve ? s_fCurveAngleTolerance : s_fPlaneAngleTolerance);

	//and deselect them all
	for(LIF<PolygonID> plif(&polygonList);!plif.done();plif.next())
	{
		if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
		{
			//make sure polygon has correct material
			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if( GetMaterialResponseHashID( plif().ModuleIndex, plif().PolyIndex ) != s_uMaterialResponseHashID )
				{
					continue;
				}

			#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if(GetMaterialID(plif().ModuleIndex,plif().PolyIndex) != s_iMaterial)
				{
					continue;
				}

			#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
		}

		// (SB - 12/12/06) Support for 'Select by polygon data' option
		if (s_bLimitSelectionToSamePolygonData)
		{
			if (GetPolyData(plif().ModuleIndex, plif().PolyIndex) != s_uPolyData)
			{
				continue;
			}
		}

		DeselectPolygon(plif().ModuleIndex,plif().PolyIndex);
	}
}

void Tools_Polygon_Selection::SelectFloor(const PolygonID& polygon)
{
	if(SelectionLocked) return;

	//get a list of floor polygons connected to this
	List<PolygonID> polygonList;
	ListConnectedFloorPolygons(polygon,polygonList);

	//and select them all
	for(LIF<PolygonID> plif(&polygonList);!plif.done();plif.next())
	{
		if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
		{
			//make sure polygon has correct material
			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if( GetMaterialResponseHashID( plif().ModuleIndex, plif().PolyIndex ) != s_uMaterialResponseHashID )
				{
					continue;
				}

			#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if(GetMaterialID(plif().ModuleIndex,plif().PolyIndex) != s_iMaterial)
				{
					continue;
				}

			#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
		}

		// (SB - 12/12/06) Support for 'Select by polygon data' option
		if (s_bLimitSelectionToSamePolygonData)
		{
			if (GetPolyData(plif().ModuleIndex, plif().PolyIndex) != s_uPolyData)
			{
				continue;
			}
		}

		SelectPolygon(plif().ModuleIndex,plif().PolyIndex);
	}
}

void Tools_Polygon_Selection::DeselectFloor(const PolygonID& polygon)
{
	if(SelectionLocked) return;

	//get a list of floor polygons connected to this
	List<PolygonID> polygonList;
	ListConnectedFloorPolygons(polygon,polygonList);

	//and deselect them all
	for(LIF<PolygonID> plif(&polygonList);!plif.done();plif.next())
	{
		if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
		{
			//make sure polygon has correct material
			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if( GetMaterialResponseHashID( plif().ModuleIndex, plif().PolyIndex ) != s_uMaterialResponseHashID )
				{
					continue;
				}

			#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

				if(GetMaterialID(plif().ModuleIndex,plif().PolyIndex) != s_iMaterial)
				{
					continue;
				}

			#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
		}

		// (SB - 12/12/06) Support for 'Select by polygon data' option
		if (s_bLimitSelectionToSamePolygonData)
		{
			if (GetPolyData(plif().ModuleIndex, plif().PolyIndex) != s_uPolyData)
			{
				continue;
			}
		}

		DeselectPolygon(plif().ModuleIndex,plif().PolyIndex);
	}
}

void Tools_Polygon_Selection::SelectUVShell(const PolygonID& xPolygon)
{
	if(SelectionLocked) return;

	int iChannel = Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL) ? 1 : 0;

	//get a list of polygons connected by UVs
	List<PolygonID> xPolygonList;
	ListPolygonsInUVShell(iChannel,xPolygon,xPolygonList);

	//and select them all
	for(LIF<PolygonID> xPolyLIF(&xPolygonList);!xPolyLIF.done();xPolyLIF.next())
	{
		//(Note that restricting selection to the same material etc is handled by the function that builds the list)
		SelectPolygon(xPolyLIF().ModuleIndex,xPolyLIF().PolyIndex);
	}
}

void Tools_Polygon_Selection::DeselectUVShell(const PolygonID& xPolygon)
{
	if(SelectionLocked) return;

	int iChannel = Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL) ? 1 : 0;

	//get a list of polygons connected by UVs
	List<PolygonID> xPolygonList;
	ListPolygonsInUVShell(iChannel,xPolygon,xPolygonList);

	//and select them all
	for(LIF<PolygonID> xPolyLIF(&xPolygonList);!xPolyLIF.done();xPolyLIF.next())
	{
		//(Note that restricting selection to the same material etc is handled by the function that builds the list)
		DeselectPolygon(xPolyLIF().ModuleIndex,xPolyLIF().PolyIndex);
	}
}


void Tools_Polygon_Selection::InvertSelection(int iModuleIndex)
{
	Asura_Assert(iModuleIndex>=0 && iModuleIndex<NumberOfModules,"Module index out of bounds");
	Asura_Assert(Selection,"Selection handling not properly initalised");
	
	if(SelectionLocked) return;

	int iNumSelectedBefore = Selection[iModuleIndex]->NumSelected();

	for(int i=0;i<Selection[iModuleIndex]->NumItems();i++)
	{
		//invert selection on all non-hidden polygons
		if((IsSelectable(iModuleIndex,i)))
		{
			if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
			{
				//make sure polygon has correct material
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if( GetMaterialResponseHashID( iModuleIndex, i ) != s_uMaterialResponseHashID )
					{
						continue;
					}

				#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if(GetMaterialID(iModuleIndex,i) != s_iMaterial)
					{
						continue;
					}

				#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
			}
			
			// (SB - 12/12/06) Support for 'Select by polygon data' option
			if (s_bLimitSelectionToSamePolygonData)
			{
				if (GetPolyData(iModuleIndex, i) != s_uPolyData)
				{
					continue;
				}
			}

			if(Selection[iModuleIndex]->IsSelected(i))
				Selection[iModuleIndex]->Remove(i);
			else
				Selection[iModuleIndex]->Add(i);
		}
	}

	if(!iNumSelectedBefore && Selection[iModuleIndex]->NumSelected())
	{
		NumSelectedModules++;
	}
	else if(iNumSelectedBefore && !Selection[iModuleIndex]->NumSelected())
	{
		NumSelectedModules--;
	}
}

void Tools_Polygon_Selection::InvertAll()
{
	if(SelectionLocked) return;

	for(int i=0;i<NumberOfModules;i++)
	{
		InvertSelection(i);
	}
}




void Tools_Polygon_Selection::ListPolygonsInPlane(const PolygonID& startPolygon,List<PolygonID>& outputList,bool bCurve,float fToleranceDeg)
{
	//initialise the list
	while(outputList.size()) outputList.delete_first_entry();
	outputList.add_entry(startPolygon);

	//get the polygon normal and position the initial polygon
	Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(startPolygon.ModuleIndex);
	if(!mesh->AreNormalsValid()) mesh->CreateNormals();
	Asura_Vector_3 xInitialNormal = mesh->NormalsDataPtr[startPolygon.PolyIndex].FaceNormal;
	Asura_Vector_3 modulePosition;
	Asura_Module_System::GetPositionOfModule(startPolygon.ModuleIndex,modulePosition);
	Asura_Vector_3 position = modulePosition + mesh->VertexDataPtr[mesh->PolygonDataPtr[startPolygon.PolyIndex].VertexID[0]].Position;

	//get tolerance level for dot product test
	float fCosTolerance = Asura_Maths::Cosine(Asura_Maths::DegToRad(fToleranceDeg));


	Asura_HashTree xHashTree;

	//work out from the original polygon finding all linked polygons in the list of planar polygons
	for(LIF<PolygonID> olif(&outputList);!olif.done();olif.next())
	{
		//get normal for this polygon
		Asura_Mesh* pxMesh1 = Asura_Module_System::GetMeshForModule(olif().ModuleIndex);
		if(!pxMesh1->AreNormalsValid()) pxMesh1->CreateNormals();
		Asura_Vector_3& xNormal1 = pxMesh1->NormalsDataPtr[olif().PolyIndex].FaceNormal;
		
		//get the list of adjacent polygons
		const Tools_Polygon_Adjacency* pxPolyAdj = Tools_Adjacency::GetPolygonAdjacencies(olif().ModuleIndex,olif().PolyIndex);
		for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			// ignore adjacencies between zones. RAR 02/05/2006
			if(pxAdj->IsBetweenZones()) continue;
			
			int polyIndex = pxAdj->GetPolygonIndex();
			int moduleIndex = pxAdj->GetModuleIndex();

			if(s_bLimitSelectionToSameObject)
			{
				//ignore polygons in other modules
				if(moduleIndex != startPolygon.ModuleIndex) continue;
			}

			//check to see if the polygon is planar with the original polygon
			Asura_Mesh* pxMesh2 = Asura_Module_System::GetMeshForModule(moduleIndex);
			if(!pxMesh2->AreNormalsValid()) pxMesh2->CreateNormals();
			Asura_Vector_3& xNormal2 = pxMesh2->NormalsDataPtr[polyIndex].FaceNormal;

			if(bCurve)
			{
				//compare angle to previous polygon
				if(xNormal1*xNormal2<fCosTolerance) continue;
			}
			else
			{
				//compare angle to first polygon
				if(xInitialNormal*xNormal2<fCosTolerance) continue;
			}

			//has this polygon already been added?
			Asura_Hash_ID uHash = (Asura_Hash_ID)((moduleIndex<<16) + polyIndex);

			//(reverse bit ordering to better balance hash tree)
			uHash = ((uHash & 0x55555555) << 1 | (uHash & 0xaaaaaaaa) >> 1); 
			uHash = ((uHash & 0x33333333) << 2 | (uHash & 0xcccccccc) >> 2); 
			uHash = ((uHash & 0x0f0f0f0f) << 4 | (uHash & 0xf0f0f0f0) >> 4); 
			uHash = ((uHash & 0x00ff00ff) << 8 | (uHash & 0xff00ff00) >> 8); 

			int iJunkIndex;
			if(!xHashTree.FindNodeIndexByHash(uHash,true,iJunkIndex))
			{
				//add to the list then
				PolygonID polyID(moduleIndex,polyIndex);
				outputList.add_entry(polyID);
			}
		}
	}	
}

static const float MIN_STEP_NORMAL          = -0.1f;
static const float MAX_STEP_NORMAL          =  0.1f;
static const float MAX_STEP_HEIGHT          =  0.4f;
static const float ZERO_POLY_AREA_THRESHOLD = 0.00001f;

static inline bool IsFloorPoly( const Asura_Mesh* pxMesh, int iPolyIdx )
{
    return( pxMesh->NormalsDataPtr[iPolyIdx].FaceNormal.y < TOOLS_MAX_FLOOR_NORMAL );
}

static inline bool IsStepPoly( const Asura_Mesh* pxMesh, int iPolyIdx )
{
    const Asura_Vector_3& xFaceNrm = pxMesh->NormalsDataPtr[iPolyIdx].FaceNormal;
    return( xFaceNrm.y > MIN_STEP_NORMAL && xFaceNrm.y < MAX_STEP_NORMAL );
}

static void ProcessPolysAdjacentToPotentialStep( const Asura_Mesh* pxStepPolyMesh,
                                                 int iStepPolyIdx,
                                                 const Tools_Polygon_Adjacency* pxStepPolyAdj,
                                                 float* pfFloorMinY,
                                                 float* pfFloorMaxY )
{
	for(const Tools_Adjacent_Polygon* pxAdj = pxStepPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
    {
		//ignore adjacencies to other zones RAR 02/05/2006
		if(pxAdj->IsBetweenZones()) continue;

        int iFloorPolyModuleIdx = pxAdj->GetModuleIndex();
        int iFloorPolyPolyIdx   = pxAdj->GetPolygonIndex();

        // Verify it's indeed a floor poly.
        const Asura_Mesh* pxFloorPolyMesh = Asura_Module_System::GetMeshForModule( iFloorPolyModuleIdx );
        if( !IsFloorPoly(pxFloorPolyMesh, iFloorPolyPolyIdx) ) { continue; }

        // Ignore zero-area (or near zero-area) polys since they can cause
        // similar problems to those described below.
        if( pxFloorPolyMesh->CalculatePolyArea(iFloorPolyPolyIdx) < ZERO_POLY_AREA_THRESHOLD ) { continue; }

        // We require at least two shared vertices, which should be an edge
        // in well-formed mesh. Dodgy ASCII art time:
        //
        //          B_____
        //          /\ 
        //     A__C/  \ 
        //      \  \   \____
        //       \  \  /D
        //        \__\/
        //            E
        //
        // That's supposed to show (in left-to-right order) floor, step,
        // floor.
        //
        // I can't really illustrate it with ASCII but imagine there's also
        // an edge between a and b, ie. forming tri ABC. Unless we require
        // two shared vertices, that would be regarded as a step since it
        // shares a single vertex with the top floor poly.
        //
        // (MP - 31.03.2003) Unfortunately this gets a little more
        // complicated if we're dealing with a tri rather than a quad. Unless
        // we do some extra checks, we'd miss tri CDE above as being a step
        // poly.
        if( pxAdj->GetNumSharedVertices() < 2 )
        {
            // If the potential step poly is a quad then we can now disregard
            // this floor poly since they don't share enough vertices.
            if( pxStepPolyMesh->PolygonDataPtr[iStepPolyIdx].IsQuad() ) { continue; }

            // Otherwise if the potential step is a tri then we need to look
            // for an adjacent tri that completes the quad. For example,
            // using the above diagram, if we've got CDE then we need to find
            // CBD.
            bool bFoundSuitableOtherTri = false;

            const Asura_Vector_3& xStepFaceNrm = pxStepPolyMesh->NormalsDataPtr[iStepPolyIdx].FaceNormal;

            // Look through step tri's other adjacencies.
			for(const Tools_Adjacent_Polygon* pxAdj2 = pxStepPolyAdj->GetFirstAdjacency(); pxAdj2; pxAdj2 = pxAdj2->GetNext())
            {
                // Don't consider the floor poly again.
                if( pxAdj2 == pxAdj ) { continue; }

				//ignore adjacencies to other zones RAR 02/05/2006
				if(pxAdj2->IsBetweenZones()) continue;


                if( pxAdj2->GetNumSharedVertices() < 2 ) { continue; }

                int iOtherTriModuleIdx = pxAdj2->GetModuleIndex();
                int iOtherTriPolyIdx   = pxAdj2->GetPolygonIndex();

                // Verify it's indeed a tri.
                const Asura_Mesh* pxOtherTriMesh = Asura_Module_System::GetMeshForModule( iOtherTriModuleIdx );
                if( pxOtherTriMesh->PolygonDataPtr[iOtherTriPolyIdx].IsQuad() ) { continue; }

                // Check it (roughly) shares a normal with our original tri.
                if( !xStepFaceNrm.ApproximatelyEquals(pxOtherTriMesh->NormalsDataPtr[iOtherTriPolyIdx].FaceNormal) ) { continue; }

                // Check this other tri shares two vertices with the floor
                // poly.
                const Tools_Polygon_Adjacency* pxOtherTriAdj = Tools_Adjacency::GetPolygonAdjacencies( iOtherTriModuleIdx, iOtherTriPolyIdx );
                if( pxOtherTriAdj->GetNumSharedVertices(iFloorPolyModuleIdx, iFloorPolyPolyIdx) < 2 ) { continue; }

                // Ok.
                bFoundSuitableOtherTri = true;
                break;
            }

            if( !bFoundSuitableOtherTri ) { continue; }
        }

	    const Asura_Mesh_Polygon& xFloorPolyData = pxFloorPolyMesh->PolygonDataPtr[iFloorPolyPolyIdx];
        for( int iVtx = 0; iVtx < xFloorPolyData.NumberOfVertices(); ++iVtx )
	    {
		    float fY = pxFloorPolyMesh->VertexDataPtr[xFloorPolyData.VertexID[iVtx]].Position.y;
            if( fY < *pfFloorMinY ) { *pfFloorMinY = fY; }
            if( fY > *pfFloorMaxY ) { *pfFloorMaxY = fY; }
	    }
    }
}

void Tools_Polygon_Selection::ListConnectedFloorPolygons(const PolygonID& startPolygon,List<PolygonID>& outputList)
{
	//initialise the list
    outputList.empty();

	//check the starting polygon is okay
	Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(startPolygon.ModuleIndex);
	if(!mesh->AreNormalsValid()) mesh->CreateNormals();
	if( !IsFloorPoly(mesh, startPolygon.PolyIndex) )
	{
		return;
	}

	outputList.add_entry(startPolygon);

	List<PolygonID> queue;
	queue.add_entry(startPolygon);



	//work out from the original polygon finding all linked polygons that can be counted as floor
	for(LIF<PolygonID> qlif(&queue);!qlif.done();qlif.next())
	{

		Asura_Mesh* mesh1 = Asura_Module_System::GetMeshForModule(qlif().ModuleIndex);
		if(!mesh1->AreNormalsValid()) mesh1->CreateNormals();

		bool bQueuePolyIsFloor = IsFloorPoly( mesh1, qlif().PolyIndex );
		
		//get the list of adjacent polygons
		const Tools_Polygon_Adjacency* pxPolyAdj = Tools_Adjacency::GetPolygonAdjacencies(qlif().ModuleIndex,qlif().PolyIndex);
		for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			//ignore adjacencies between zones. RAR 02/05/2006
			if(pxAdj->IsBetweenZones()) continue;

			int iAdjModuleIndex = pxAdj->GetModuleIndex();
			int iAdjPolyIndex   = pxAdj->GetPolygonIndex();

			if(s_bLimitSelectionToSameObject)
			{
				//ignore polygons in other modules
				if(iAdjModuleIndex != startPolygon.ModuleIndex) continue;
			}


			PolygonID polyID(iAdjModuleIndex,iAdjPolyIndex);
			if(queue.contains(polyID)) continue;

			Asura_Mesh* pxAdjPolyMesh = Asura_Module_System::GetMeshForModule( iAdjModuleIndex );
            if( !pxAdjPolyMesh->AreNormalsValid() ) { pxAdjPolyMesh->CreateNormals(); }

			//check the polygons normal
			if( IsFloorPoly(pxAdjPolyMesh, iAdjPolyIndex) )
			{
				//floor polygon , add to the list
				queue.add_entry(polyID);
				outputList.add_entry(polyID);
			}
			else if( IsStepPoly(pxAdjPolyMesh, iAdjPolyIndex) )
			{
				//possibly a step polygon
				//If the polygon that it is connected to is not a floor poly then we don't care
                if( !bQueuePolyIsFloor ) { continue; }

				//check the height of the polygon
				Asura_Mesh_Polygon* xAdjPoly = &pxAdjPolyMesh->PolygonDataPtr[iAdjPolyIndex];
				float miny = Asura_Maths::LargeFloat;
				float maxy = -Asura_Maths::LargeFloat;

				for(int v=0;v<xAdjPoly->NumberOfVertices();v++)
				{
					float y = pxAdjPolyMesh->VertexDataPtr[xAdjPoly->VertexID[v]].Position.y;
					if(y<miny) miny = y;
					if(y>maxy) maxy = y;
				}

				if( maxy - miny < MAX_STEP_HEIGHT )
				{
					//check for adjcacent floor like polygons above and below
					float minfloory = maxy;
					float maxfloory = miny;

                    ProcessPolysAdjacentToPotentialStep( pxAdjPolyMesh,
                                                         iAdjPolyIndex,
                                                         Tools_Adjacency::GetPolygonAdjacencies(iAdjModuleIndex, iAdjPolyIndex),
                                                         &minfloory,
                                                         &maxfloory );

					if(minfloory<=miny && maxfloory>=maxy)
					{
						queue.add_entry(polyID);
						outputList.add_entry(polyID);
					}
				}
			}
		}
	}	
}



void Tools_Polygon_Selection::SelectAll()
{
	if(SelectionLocked) return;

	for(int i=0;i<NumberOfModules;i++)
	{
		SelectObject(i);
	}
}

void Tools_Polygon_Selection::DeselectAll()
{
	Asura_Assert(Selection,"Selection handling not properly initalised");
	if(SelectionLocked) return;

	for(int i=0;i<NumberOfModules;i++)
	{
		Selection[i]->ClearList();
	}
	NumSelectedModules = 0;
	
	//some things get reset when the selection is cleared
	if(SelectionClearedCallback) SelectionClearedCallback();
}

void Tools_Polygon_Selection::HideSelection()
{
	//go through the selection hiding everything
	for(int i=0;i<NumberOfModules;i++)
	{
		if(Selection[i]->NumSelected())
		{
			for(Selection[i]->StartIteration();!Selection[i]->Done();Selection[i]->Next())
			{
				Tools_Render_Options::AddRenderFlagPolygon(i,Selection[i]->Current(),ToolsRenderFlag_PolygonSelectionHide);
			}
		}
	}

	//clear the selection
	DeselectAll();
}

void Tools_Polygon_Selection::HideUnselected()
{
	for(int i=0;i<NumberOfModules;i++)
	{
		for(int j=0;j<Selection[i]->NumItems();j++)
		{
			if(!Selection[i]->IsSelected(j))
			{
				Tools_Render_Options::AddRenderFlagPolygon(i,j,ToolsRenderFlag_PolygonSelectionHide);
			}
		}
	}
}


void Tools_Polygon_Selection::Unhide()
{
	for(int i=0;i<NumberOfModules;i++)
	{
		Tools_Render_Options::RemoveRenderFlagFromAllPolygonsInModule(i,ToolsRenderFlag_PolygonSelectionHide);
	}
}


void Tools_Polygon_Selection::UnhideAndSelect()
{
	for(int i=0;i<NumberOfModules;i++)
	{
		Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(i);

		for(int j=0;j<mesh->NumberOfPolygons;j++)
		{
			if(Tools_Render_Options::GetRenderFlags(i,j) & ToolsRenderFlag_PolygonSelectionHide)
			{
				//unhide this polygon
				Tools_Render_Options::RemoveRenderFlagPolygon(i,j,ToolsRenderFlag_PolygonSelectionHide);
				//and select it
				SelectPolygon(i,j);

			}
		}
	}
}

void Tools_Polygon_Selection::StartIteration()
{
	//locate the first module with anything selected
	for(CurrentIterationModule = 0;CurrentIterationModule<NumberOfModules;CurrentIterationModule++)
	{
		if(Selection[CurrentIterationModule]->NumSelected())
		{
			Selection[CurrentIterationModule]->StartIteration();
			return;
		}
	}
}

bool Tools_Polygon_Selection::GetNextSelected(PolygonID* polygon)
{
	if(CurrentIterationModule >= NumberOfModules) return false;

	//get the current entry from the iteration
	polygon->ModuleIndex = CurrentIterationModule;
	polygon->PolyIndex = Selection[CurrentIterationModule]->Current();

	//now need to move the iteration on to the next selected entry
	Selection[CurrentIterationModule]->Next();
	if(Selection[CurrentIterationModule]->Done())
	{
		//done with that module , move onto the next module
		for(CurrentIterationModule++;CurrentIterationModule<NumberOfModules;CurrentIterationModule++)
		{
			if(Selection[CurrentIterationModule]->NumSelected())
			{
				Selection[CurrentIterationModule]->StartIteration();
				break;
			}
		}
	}
	return true;
}

bool Tools_Polygon_Selection::GetRandomSelectedPolygon(PolygonID *pxPolygon)
{
	int iNumberOfPolys = 0;
	for(int i=0;i<NumberOfModules;i++)
	{
		iNumberOfPolys += Selection[i]->NumSelected();
	}
	if (!iNumberOfPolys)
		return false;
	Asura_Assert(iNumberOfPolys > 0, "Sanity check");
	int iIndex = Asura_Random::GetIntU() % iNumberOfPolys;
	for (int i=0;i<NumberOfModules;i++)
	{
		if (iIndex >= Selection[i]->NumSelected())
			iIndex -= Selection[i]->NumSelected();
		else
		{
			pxPolygon->ModuleIndex = i;
			pxPolygon->PolyIndex = Selection[i]->GetIndex(iIndex);
			Asura_Assert(pxPolygon->PolyIndex >= 0, "Sanity check");
			return true;
		}
	}
	Asura_Assert(0, "Logic error");
	return false;
}


bool Tools_Polygon_Selection::IsSelected(const PolygonID& polygon)
{
	Asura_Assert(Selection,"Selection handling not properly initalised");
	Asura_Assert(polygon.ModuleIndex>=0 && polygon.ModuleIndex<NumberOfModules,"Module index out of bounds");
	
	return Selection[polygon.ModuleIndex]->IsSelected(polygon.PolyIndex);
	
}

int Tools_Polygon_Selection::CountSelectedInModule(int moduleIndex)
{
	Asura_Assert(moduleIndex>=0 && moduleIndex<NumberOfModules,"Module index out of bounds");
	
	return Selection[moduleIndex]->NumSelected();
}

bool Tools_Polygon_Selection::IsModuleCompletelySelected(int iModuleIndex)
{
	Asura_Assert(iModuleIndex>=0 && iModuleIndex<NumberOfModules,"Module index out of bounds");

	if(!Selection[iModuleIndex]->NumSelected()) return false;
	
	//consider module to be completely selected if all non-hidden polygons are selected.
	for(int i=0;i<Selection[iModuleIndex]->NumItems();i++)
	{
		if((IsSelectable(iModuleIndex,i)))
		{
			if(!Selection[iModuleIndex]->IsSelected(i))
			{
				return false;
			}
		}
	}
	return true;	
}

void Tools_Polygon_Selection::ListSelectedInModule(int iModuleIndex,List<int>& xOutputList)
{
	Asura_Assert(iModuleIndex>=0 && iModuleIndex<NumberOfModules,"Module index out of bounds");

	for(int i=0;i<Selection[iModuleIndex]->NumSelected();i++)
	{
		xOutputList.add_entry(Selection[iModuleIndex]->GetIndex(i));
	}
}

Asura_Vector_3 Tools_Polygon_Selection::GetCentreOfSelection()
{
	if(!NumSelectedModules) return Asura_Maths::ZeroVector3;

	Asura_Bounding_Box xBB;
	xBB.Reset();

	//get BB of each poly
	PolygonID xPoly;
	StartIteration();

	while(GetNextSelected(&xPoly))
	{
		Asura_Vector_3 xModulePos = Asura_Module_System::GetPositionOfModule(xPoly.ModuleIndex);
		const Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(xPoly.ModuleIndex);
		
		const Asura_Mesh_Polygon* pxPoly = &pxMesh->PolygonDataPtr[xPoly.PolyIndex];

		for(int i=0;i<pxPoly->NumberOfVertices();i++)
		{
			xBB.Set(xModulePos + pxMesh->VertexDataPtr[pxPoly->VertexID[i]].Position);
		}
	}
	return xBB.CalculateCentre();
	
}

void Tools_Polygon_Selection::HighlightSelection()
{
	// iterate through selected polygons, giving them a red outline
	Asura_Assert(Selection,"Selection handling not properly initalised");
	
	const u_int uNumberOfModules = static_cast<u_int>(NumberOfModules);

	const u_int uColour = 0xffff0000;

	// start batched renderer
	Tools_BatchRenderPolygonOutline::BeginBatch();

	for( u_int uModule=0; uModule<uNumberOfModules; ++uModule )
	{
		// does this module have anything selected?
		PolygonSelectionList* pxSelection = Selection[uModule];

		if( pxSelection->NumSelected() )
		{
			// are there any other reasons to reject the module as a whole?

			// module hidden
			if (!Asura_Module_System::IsModuleVisible( uModule )) continue;

			// module too far from camera
			if(!Tools_Render_Options::IsModuleWithinViewDistance( uModule )) continue;

			// ok, per-poly now
			for( pxSelection->StartIteration(); !pxSelection->Done(); pxSelection->Next() )
			{
				u_int uPoly = pxSelection->Current();
				Tools_BatchRenderPolygonOutline::AddOutlineToBatch( uPoly, uModule, uColour );
			}
		}
	}
	
	// end batched renderer
	Tools_BatchRenderPolygonOutline::EndBatch();
}


void Tools_Polygon_Selection::HighlightPolygon(PolygonID& polygon)
{
//	Asura_Text_Output::Print(30,10,0xffff0000,0,"Module: %d  Poly: %d",polygon.ModuleIndex,polygon.PolyIndex);
	if(!Tools_Render::HighlightPolyOrObjectUnderCursor) return;

	float fAlpha = Asura_Maths::Sine( Asura_Timers::GetLocalUnscaledTime() * 2.0f );
	fAlpha = 255.0f * fAlpha * fAlpha;

	unsigned int colour = 0x00ffffff + (Asura_Maths::FloatToInt( fAlpha )<<24);
	
	Tools_Render::RenderPolygonOutline(polygon.PolyIndex,polygon.ModuleIndex,colour,true);
}


void Tools_Polygon_Selection::HandleMouseSelection()
{
	// Kevin Lea - 22/06/2001 12:52:58 - if the user is doing anything with overlays or menus,
	// ignore any mousebutton/key presses etc. 
	if (Asura_Interface::UsingMenusOrOverlays()) return;
	
	// Kevin Lea - 22/06/2001 16:24:57 - allow the user to select/deselect polys
	if (!SelectionLocked)
	{
	
		//see if the mouse is over a polygon
		PolygonID polygon;
	   	bool overPoly = GetPolygonUnderMouse(polygon);
	
		if (overPoly) 		
		{
			//remember this polygon , so we can highlight it during Render()
			if(IsSelectable(polygon.ModuleIndex,polygon.PolyIndex))
			{
				HighlitPolygon = polygon;
			}

			if(s_bLimitSelectionToSameMaterial)
			{
				//find texture on polygon mouse is over
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
					s_uMaterialResponseHashID = GetMaterialResponseHashID( polygon.ModuleIndex, polygon.PolyIndex );
				#else
					s_iMaterial = GetMaterialID(polygon.ModuleIndex,polygon.PolyIndex);
				#endif

				s_bMaterialValid = true;
			}

			// (SB - 12/12/06) Support for 'Select by polygon data' option
			if (s_bLimitSelectionToSamePolygonData)
			{
				s_uPolyData = GetPolyData(polygon.ModuleIndex,polygon.PolyIndex);
			}
		}

		int iX,iY;
		Asura_Cursor::GetScreenSpaceCoords(iX,iY);
		
		//check for starting drag selection
		if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL) && Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1))
		{
			if(SelectionMode == SelectionMode_Polygon || SelectionMode == SelectionMode_Object)
			{
				s_bDragSelection = true;
				s_xDragStart.x = (float)iX;
				s_xDragStart.y = (float)iY;
			}
		}

		if(s_bDragSelection)
		{
			s_xDragCurrent.x = (float)iX;
			s_xDragCurrent.y = (float)iY;
			if(!Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
			{
				//released control , so abort drag selection
				s_bDragSelection = false;

			}
			else if(!Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1))
			{
				//finished dragging , so select/deselect all in rectangle
				if(SelectionMode == SelectionMode_Polygon)
				{
					DragPolygonSelection(s_xDragStart,s_xDragCurrent,Asura_UserInput::KeyPressed(ASURA_KEYID_LEFTCTRL),!Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT));				
				}
				else if(SelectionMode == SelectionMode_Object)
				{
					DragObjectSelection(s_xDragStart,s_xDragCurrent,Asura_UserInput::KeyPressed(ASURA_KEYID_LEFTCTRL),!Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT));				
				}

				s_bDragSelection = false;
			}
		}
		else
		{
			//if over a polygon , check for selection/deselection
	 		if (Asura_UserInput::KeyPressed(ASURA_KEYID_MOUSEBUTTON1))
			{
				if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1) || ButtonDownForSelection)
				{
					ButtonDownForSelection = true;
					if (overPoly)
					{
						if (Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
						{
							switch(SelectionMode)
							{
								case SelectionMode_Polygon :
									DeselectPolygon(polygon.ModuleIndex,polygon.PolyIndex);
									break;
								case SelectionMode_Object :
									DeselectObject(polygon.ModuleIndex);
									break;
								case SelectionMode_Element :
									DeselectElement(polygon.ModuleIndex,polygon.PolyIndex);
									break;
								case SelectionMode_Plane :
									DeselectPlane(polygon,false);
									break;
								case SelectionMode_Curve :
									DeselectPlane(polygon,true);
									break;
								case SelectionMode_Floor :
									DeselectFloor(polygon);
									break;
								case SelectionMode_UV :
									DeselectUVShell(polygon);
									break;
								case SelectionMode_Invert :
									//button must be debounced when using invert selection
									if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1))
									{
										InvertSelection(polygon.ModuleIndex);
									}
									break;
								
							}
						}
						else
						{
							switch(SelectionMode)
							{
								case SelectionMode_Polygon :
									SelectPolygon(polygon.ModuleIndex,polygon.PolyIndex);
									break;
								case SelectionMode_Object :
									SelectObject(polygon.ModuleIndex);
									break;
								case SelectionMode_Element :
									SelectElement(polygon.ModuleIndex,polygon.PolyIndex);
									break;
								case SelectionMode_Plane :
									SelectPlane(polygon,false);
									break;
								case SelectionMode_Curve :
									SelectPlane(polygon,true);
									break;
								case SelectionMode_Floor :
									SelectFloor(polygon);
									break;
								case SelectionMode_UV :
									SelectUVShell(polygon);
									break;
								case SelectionMode_Invert :
									//button must be debounced when using invert selection
									if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1))
									{
										InvertSelection(polygon.ModuleIndex);
									}
									break;
								
							}

						}
					}

				}
			}
		}
		s_bMaterialValid = false;
	}
}

void Tools_Polygon_Selection::HandleKeyboard()
{
	if(Asura_Overlay_System::OverlaysHaveKeyboardFocus()) return;
	if(Asura_UserInput::KeyPressed(ASURA_KEYID_ALT))
	{
		if (Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_F2))
		{
			ShowSelection = !ShowSelection;
		}
		
	}
	else if(Asura_UserInput::KeyPressed(ASURA_KEYID_CTRL))
	{
	}
	else if(Asura_UserInput::KeyPressed(ASURA_KEYID_SHIFT))
	{
		if( Asura_UserInput::DebouncedKeyPressed( 'z' ) || Asura_UserInput::DebouncedKeyPressed( ',' )) // 'shift+z' or 'shift+<' for selection undo
		{
			Undo();
		}
		if ( Asura_UserInput::DebouncedKeyPressed( 'y' ) || Asura_UserInput::DebouncedKeyPressed( '.' )) // 'shift+y' or 'shift+>' for selection redo
		{
			Redo();
		}
	}
	else
	{
		if(Asura_UserInput::DebouncedKeyPressed('l'))// 'L' for clear selection
		{
			Tools_Polygon_Selection::DeselectAll();
		}

		//number keys are shorcuts for selecting the mode
		for(int i=0;i<8;i++)
		{
			if(Asura_UserInput::DebouncedKeyPressed(i+'1'))
			{
				SelectionMode = i;
			}
		}
	}
}

void Tools_Polygon_Selection::ToggleSelectionOverlay(void)
{
	SelectionOverlayOpen = !SelectionOverlayOpen;

	if (SelectionOverlayOpen)
	{
		s_uSelectionOverlayMinimised = 0;
		CreateSelectionOverlay();
	}
	else
	{
		CloseSelectionOverlay();
	}
}
bool Tools_Polygon_Selection::SelectionOverlayIsOn(void)
{
	return SelectionOverlayOpen;
}

void Tools_Polygon_Selection::CreateSelectionOverlay(void)
{
	if(!SelectionOverlayOpen) return;
	if(Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_TEXTURING_SELECTION)) return;
	//strings for selection mode options
	static char* SelectionModeStrings[] =
	{
		"1)Polygon",
		"2)Object",
		"3)Element",
		"4)Plane",
		"5)Curve",
		"6)Floor",
		"7)UV Shell",
		"8)Invert",
	};

	static char* SelectionModeHelpStrings[] =
	{
		"Selects a single polygon",
		"Select all polygons in the object",
		"Starting from the clicked polygon , all connected polygons in the same object are selected.",
		"Starting from the clicked polygon , all connected polygons are selected if they lie on the same plane.",
		"Similar to plane selection , except each polygon need only lie within the angle tolerance of the previous connected polygon , rather than with the clicked polygon.",
		"Starting from the clicked polygon , all connected polygons that appear to be floor-like or step-like are selected.",
		"Selects all connected polygons that have continuous UV coordinates.\nIf control is held down then the selection will be based on the secondary UVs instead.",
		"Inverts the selection for the object clicked on.",
	};


	Asura_Overlay* pxOverlay;
	pxOverlay = new Asura_Overlay("Selection",false,&SelectionOverlayPosition,&s_uSelectionOverlayMinimised);
	
	Asura_Dialog* pxDialog;
	Asura_Vector_2 offset = Asura_Vector_2(1.0f,3.0f);

	
	//heading for selection mode list box
	pxDialog = new Asura_Dialog("Selection Mode :",offset);
	pxOverlay->AddDialog(pxDialog);
	offset.y+=1.0f;

	//selection mode list box
	Asura_List_Dialog* pxList = new Asura_List_Dialog(offset,SelectionModeStrings,&Tools_Polygon_Selection::SelectionMode,8,8,NULL);
	pxDialog = pxList;
	pxList->SetListHelpStrings(SelectionModeHelpStrings);
	pxOverlay->AddDialog(pxDialog);
	offset.y+=10.6f;
	
	pxDialog = new Asura_Tickbox_Dialog("Lock Selection",offset,&(Tools_Polygon_Selection::SelectionLocked));
	pxDialog->SetHelpText("Disables all controls for changing the current selection.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Show Selection (Alt+F2)",offset,&(Tools_Polygon_Selection::ShowSelection));
	pxDialog->SetHelpText("Highlight currently selected polygons with a red outline");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Select by Material",offset,&(Tools_Polygon_Selection::s_bLimitSelectionToSameMaterial));
	pxDialog->SetHelpText("This limits each selection/deselection operation to affecting polygons using the same material as the one clicked on.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Select by Polygon Data",offset,&(Tools_Polygon_Selection::s_bLimitSelectionToSamePolygonData));
	pxDialog->SetHelpText("This limits each selection/deselection operation to affecting polygons with the same project polygon data as the one clicked on. If you're not sure what this is or if it is used contact your projects lead programmer.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Tickbox_Dialog("Limit to Object",offset,&(Tools_Polygon_Selection::s_bLimitSelectionToSameObject));
	pxDialog->SetHelpText("This limits each selection/deselection operation to affecting polygons within the object clicked on.\nThe only selection methods that this will affect are Floor , Plane and Curve.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Tickbox_Dialog("Allow Backface Selection",offset,&s_bAllowBackfaceSelection);
	pxDialog->SetHelpText("Allows polygons to be selected by clicking on the reverse side as well.\nThis setting is redundant if 'No Back-Face Cull' has been enabled in the render options.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Numerical_Dialog("Plane Tol.",offset,&s_fPlaneAngleTolerance,0,180.0f,1.0f,"%.2f");
	pxDialog->SetHelpText("Tolerance level for plane selection in degrees.\nPolygons will only be selected if their normal is within this angle of the initial polygon");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Curve Tol.",offset,&s_fCurveAngleTolerance,0,180.0f,1.0f,"%.2f");
	pxDialog->SetHelpText("Tolerance level for curve selection in degrees.\nPolygons will only be selected if their normal is within this angle of the adjacent polygon");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::LargeSpacing();


	pxDialog = new Asura_Function_Dialog("Select All",offset,Tools_Polygon_Selection::SelectAll);
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Clear Selection (L)",offset,Tools_Polygon_Selection::DeselectAll);
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Grow",offset,Tools_Polygon_Selection::GrowSelection);
	pxDialog->SetHelpText("Expands current selection by selecting any polygon that is adjacent to a polygon that is already selected.\nThis is affected by the 'Limit to Object' and 'Select by Material' settings.");
	pxOverlay->AddDialog(pxDialog);

	offset.x = pxDialog->m_xMaxCoord.x + 1.0f;
	pxDialog = new Asura_Function_Dialog("Shrink",offset,Tools_Polygon_Selection::ShrinkSelection);
	pxDialog->SetHelpText("Shrinks current selection by deselecting any polygon that is adjacent to at least one unselected polygon.\nThis is affected by the 'Limit to Object' and 'Select by Material' settings.");
	pxOverlay->AddDialog(pxDialog);
	offset.x = 1.0f;
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Select By Name",offset,Tools_Polygon_Selection::SelectByName);
	pxDialog->SetHelpText("Selects all polygons in modules with names matching a given search string.\n* can be used as a wildcard in the search string.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Invert Selection",offset,Tools_Polygon_Selection::InvertAll);
	pxDialog->SetHelpText("Invert the selection across all modules.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Hide Selected",offset,Tools_Polygon_Selection::HideSelection);
	pxDialog->SetHelpText("Hide and deselect all selected polygons.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Hide Unselected",offset,Tools_Polygon_Selection::HideUnselected);
	pxDialog->SetHelpText("Hide all polygons that aren't selected.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();


	pxDialog = new Asura_Function_Dialog("Unhide all",offset,Tools_Polygon_Selection::Unhide);
	pxDialog->SetHelpText("Unhide previously hidden polygons.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Unhide and select",offset,Tools_Polygon_Selection::UnhideAndSelect);
	pxDialog->SetHelpText("Unhide and select previously hidden polygons");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();
	
	pxDialog = new Asura_Function_Dialog("Undo (Shift+Z)",offset,Tools_Polygon_Selection::Undo);
	pxDialog->SetHelpText("Polygon selection undo.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Redo (Shift+Y)",offset,Tools_Polygon_Selection::Redo);
	pxDialog->SetHelpText("Polygon selection redo.");
	pxOverlay->AddDialog(pxDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();
	
	pxOverlay->m_uID = TOOLS_OVERLAY_TEXTURING_SELECTION;

	Asura_Overlay_System::SubmitNewOverlay(pxOverlay,false);
}

void Tools_Polygon_Selection::CloseSelectionOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_TEXTURING_SELECTION);
}

void Tools_Polygon_Selection::Undo()
{
	if(!SelectionLocked)
	{
		if(SelectionUndo.Undo())
		{
			Tools_Timed_Message_System::AddMessage(0xffffff,"Selection Undo");
		}
	}
}
void Tools_Polygon_Selection::Redo()
{
	if(!SelectionLocked)
	{
		if(SelectionUndo.Redo())
		{
			Tools_Timed_Message_System::AddMessage(0xffffff,"Selection Redo");
		}
	}
}


void Tools_Polygon_Selection::HandlePolygonConversion(int module_index,int* PolyConv1,int* PolyConv2 /*=0*/)
{
	Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(module_index);
	Asura_Assert(mesh,"Failed to get the mesh for the module");

	//redo the element data
	delete ElementData[module_index];
	ElementData[module_index] = new Tools_Module_Elements(module_index);

	
	//create new Selection data

	PolygonSelectionList* NewSelection = new PolygonSelectionList(module_index,mesh->NumberOfPolygons);

	//run through the old selection , adding converted values to the new selection
	for(Selection[module_index]->StartIteration();!Selection[module_index]->Done();Selection[module_index]->Next())
	{
		int poly_index = Selection[module_index]->Current();
		if(PolyConv1 && PolyConv1[poly_index]!=-1)
		{
			NewSelection->Add(PolyConv1[poly_index]);
		}	
		if(PolyConv2 && PolyConv2[poly_index]!=-1)
		{
			NewSelection->Add(PolyConv2[poly_index]);
		}	
	}

	if(Selection[module_index]->NumSelected() && ! NewSelection->NumSelected())
	{
		NumSelectedModules--;	
	}

	//replace the selection data
	delete Selection[module_index];
	Selection[module_index] = NewSelection;

	if(HighlitPolygon.ModuleIndex == module_index)
	{
		//just clear the highlighted polygon for this frame , since the module has been altered
		HighlitPolygon.ModuleIndex = -1;
		HighlitPolygon.PolyIndex = -1;
	}
}

void Tools_Polygon_Selection::HandleMeshChange(int iModuleIndex)
{
	Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(iModuleIndex);
	Asura_Assert(pxMesh,"Failed to get the mesh for the module");

	//redo the element data
	delete ElementData[iModuleIndex];
	ElementData[iModuleIndex] = new Tools_Module_Elements(iModuleIndex);

	
	//create new Selection data , if the number of polygons has changed
	if(Selection[iModuleIndex]->NumItems() != pxMesh->NumberOfPolygons)
	{
		PolygonSelectionList* pxNewSelection = new PolygonSelectionList(iModuleIndex,pxMesh->NumberOfPolygons);

		if(Selection[iModuleIndex]->NumSelected() && ! pxNewSelection->NumSelected())
		{
			NumSelectedModules--;	
		}

		//replace the selection data
		delete Selection[iModuleIndex];
		Selection[iModuleIndex] = pxNewSelection;

		if(HighlitPolygon.ModuleIndex == iModuleIndex)
		{
			//just clear the highlighted polygon for this frame , since the module has been altered
			HighlitPolygon.ModuleIndex = -1;
			HighlitPolygon.PolyIndex = -1;
		}

		ClearUndo();
	}
}

void Tools_Polygon_Selection::DeleteModule(int iModuleIndex)
{
	Asura_Assert(iModuleIndex>=0 && iModuleIndex<NumberOfModules,"");

	DeselectObject(iModuleIndex);

	delete ElementData[NumberOfModules-1];
	if(iModuleIndex!=(NumberOfModules-1))
	{
		ElementData[iModuleIndex]->Reset();
	}

	delete Selection[iModuleIndex];
	Selection[iModuleIndex] = Selection[NumberOfModules-1];

	NumberOfModules--;
	ClearUndo();
}

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

Asura_Hash_ID Tools_Polygon_Selection::GetMaterialResponseHashID( const int iModule, const int iPolygon )
{
	const Asura_Mesh* const pxMesh = Asura_Module_System::GetMeshForModule( iModule );
	return pxMesh->TextureDataPtr[ iPolygon ].m_auMaterialResponseHashID[0];
}

#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

int Tools_Polygon_Selection::GetMaterialID(int iModule,int iPolygon)
{
	Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(iModule);
	return pxMesh->TextureDataPtr[iPolygon].m_iMaterialID;
}

#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
	
// (SB - 12/12/06) Support for 'Select by polygon data' option
unsigned int Tools_Polygon_Selection::GetPolyData(int iModule,int iPolygon)
{
	Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(iModule);
	return pxMesh->GetProjectPolygonData(iPolygon);
}

bool Tools_Polygon_Selection::IsSelectable(int iModuleIndex,int iPolygonIndex)
{
	//hidden polygons shouldn't be selected
	if(Tools_Render_Options::IsHidden(iModuleIndex,iPolygonIndex)) return false;

	//nor object polygons that have been merged into environment
	Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(iModuleIndex);
	if(pxMesh->IsMergedObjectPolygon(iPolygonIndex)) return false;
	return true;
}

////////////////////////////////////////////////////////////////////////////////////////
bool Tools_Polygon_Selection::GetPolygonUnderMouse(PolygonID& polygon)
{
	//get the z direction for the mouse
	Asura_Vector_3 zvec;
	Asura_Cursor::GetLOSDirection(zvec);

	// Kevin Lea - 22/06/2001 12:49:20 - and transform this direction into worldspace
	const Asura_Vector_3* cameraPosition = Asura_Camera::GetPosition();
	const Asura_Matrix_3x3* cameraOrientation = Asura_Camera::GetOrientation();

	zvec = zvec * (*cameraOrientation);

	Tools_LOS::SetIgnoreBFC(s_bAllowBackfaceSelection);
	bool bFoundPoly =  Tools_LOS::FindPolyInLOS(*cameraPosition,zvec,polygon.PolyIndex,polygon.ModuleIndex);
	Tools_LOS::SetIgnoreBFC(false);

	return bFoundPoly;
}

bool Tools_Polygon_Selection::GetHighlitPolygon(PolygonID& polygon)
{
	if(HighlitPolygon.ModuleIndex<0) return false;
	polygon = HighlitPolygon;
	return true;
}




void Tools_Polygon_Selection::DragPolygonSelection(const Asura_Vector_2& xStart,const Asura_Vector_2& xEnd,bool bAllVertsInRect,bool bSelect)
{
	float fLeft = (xStart.x < xEnd.x) ? xStart.x : xEnd.x;
	float fRight = (xStart.x > xEnd.x) ? xStart.x : xEnd.x;
	float fTop = (xStart.y < xEnd.y) ? xStart.y : xEnd.y;
	float fBottom = (xStart.y > xEnd.y) ? xStart.y : xEnd.y;
	
	Asura_Vector_3 xCamDir;
	Asura_Camera::GetForwardVector(xCamDir);
		
	for(int i=0;i<Asura_Module_System::GetNumberOfModules();i++)
	{
		
		
		Asura_Vector_3 xPosition = Asura_Module_System::GetPositionOfModule(i);
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);

				
		//check each polygon in this module , to see if its verts lie in the rectangle
		for(int j=0;j<pxMesh->NumberOfPolygons;j++)
		{
			if(!IsSelectable(i,j)) continue;
			
			Asura_Mesh_Polygon* pxPoly = &pxMesh->PolygonDataPtr[j];

			bool bInRect = bAllVertsInRect;
			int iNumVerts = pxPoly->NumberOfVertices();
			for(int k=0;k<iNumVerts;k++)
			{
				Asura_Vector_3 xVertPos = xPosition + pxMesh->VertexDataPtr[pxPoly->VertexID[k]].Position;
				Asura_Camera::ProjectWorldCoordsToScreenSpace(xVertPos,true);
				bool bVertInRect = true;
				if(xVertPos.z <= 0)
				{
					bVertInRect = false;
				}
				else if(xVertPos.x < fLeft || xVertPos.x > fRight || xVertPos.y < fTop || xVertPos.y > fBottom)
				{
					bVertInRect = false;
				}
				
				if(bAllVertsInRect)
				{
					//Need all verts in rect , and found one that wasn't
					if(!bVertInRect)
					{
						bInRect = false;
						break;
					}
				}
				else
				{
					//Need one vertex in rect , and found one that was
					if(bVertInRect)
					{
						bInRect = true;
						break;
					}
				}
			}

			
			if(bInRect)
			{
				int iFlags = pxMesh->PolygonFlagDataPtr ? pxMesh->PolygonFlagDataPtr[j].Flags : 0;
				bool bDoubleSided = (iFlags & AsuraPolygonFlag_DoubleSided) || Tools_Render_Options::NoBackFaceCull;
				
				
				if(!bDoubleSided && !s_bAllowBackfaceSelection)
				{
					//make sure polygon faces the camera
					if(!pxMesh->m_bFaceNormalsValid) pxMesh->CreateFaceNormals();
					float fDot = xCamDir * pxMesh->NormalsDataPtr[j].FaceNormal;
					if(fDot >= 0.0f) continue;
				}
				bSelect ? SelectPolygon(i,j) : DeselectPolygon(i,j);
			}
		}


	}
}

void Tools_Polygon_Selection::DragObjectSelection(const Asura_Vector_2& xStart,const Asura_Vector_2& xEnd,bool bAllVertsInRect,bool bSelect)
{
	float fLeft = (xStart.x < xEnd.x) ? xStart.x : xEnd.x;
	float fRight = (xStart.x > xEnd.x) ? xStart.x : xEnd.x;
	float fTop = (xStart.y < xEnd.y) ? xStart.y : xEnd.y;
	float fBottom = (xStart.y > xEnd.y) ? xStart.y : xEnd.y;
	
	Asura_Vector_3 xCamDir;
	Asura_Camera::GetForwardVector(xCamDir);
		
	for(int i=0;i<Asura_Module_System::GetNumberOfModules();i++)
	{
		
		
		Asura_Vector_3 xPosition = Asura_Module_System::GetPositionOfModule(i);
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);

				
		//check each vertex in this module , to see if they lie in the rectangle
		bool bInRect = bAllVertsInRect;

		for(int j=0;j<pxMesh->NumberOfVertices;j++)
		{
			
			Asura_Vector_3 xVertPos = xPosition + pxMesh->VertexDataPtr[j].Position;
			Asura_Camera::ProjectWorldCoordsToScreenSpace(xVertPos,true);
			bool bVertInRect = true;
			if(xVertPos.z <= 0)
			{
				bVertInRect = false;
			}
			else if(xVertPos.x < fLeft || xVertPos.x > fRight || xVertPos.y < fTop || xVertPos.y > fBottom)
			{
				bVertInRect = false;
			}
			
			if(bAllVertsInRect)
			{
				//Need all verts in rect , and found one that wasn't
				if(!bVertInRect)
				{
					bInRect = false;
					break;
				}
			}
			else
			{
				//Need one vertex in rect , and found one that was
				if(bVertInRect)
				{
					bInRect = true;
					break;
				}
			}
		}

		
		if(bInRect)
		{
			bSelect ? SelectObject(i) : DeselectObject(i);
		}

	}
}


void Tools_Polygon_Selection::SelectByName()
{
	//ask for name
	static char s_szName[100] = "";

	GetString_Overlay xOverlay("Enter search string for module(s) to select.",s_szName);
	if(!xOverlay.DoModal()) return;

	strcpy(s_szName,xOverlay.GetString());

	int iFound = 0;
	for(int i=0;i<NumberOfModules;i++)
	{
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);
		//have a match?
		if(Tools_StringUtil::WildcardMatch(pxMesh->Name.GetString(),s_szName))
		{
			SelectObject(i);
			iFound++;
		}
	}

	Tools_Timed_Message_System::AddMessage(0xffffff,"Found %d module(s)",iFound);

}

void Tools_Polygon_Selection::GrowSelection()
{
	if(SelectionLocked) return;

	List<PolygonID> xSelectionList;

	// get a list of all polygons adjacent to currently selected polygons
	PolygonID xPolyID;
	StartIteration();

	while(GetNextSelected(&xPolyID))
	{
		const Tools_Polygon_Adjacency* pxPolyAdj = Tools_Adjacency::GetPolygonAdjacencies(xPolyID.ModuleIndex,xPolyID.PolyIndex);
		for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			// ignore adjacencies between zones. RAR 21/07/2006
			if(pxAdj->IsBetweenZones()) continue;

			
			int iPolyIndex = pxAdj->GetPolygonIndex();
			int iModuleIndex = pxAdj->GetModuleIndex();

			PolygonID xAdjPoly(iModuleIndex,iPolyIndex);
			if(!IsSelected(xAdjPoly))
			{
				if(s_bLimitSelectionToSameObject)
				{
					// ignore polygons in other modules
					if(iModuleIndex != xPolyID.ModuleIndex) continue;
				}

				if(s_bLimitSelectionToSameMaterial)
				{
					// ignore polygons with different materials
					Asura_Mesh* pxMesh1 = Asura_Module_System::GetMeshForModule(xPolyID.ModuleIndex);
					Asura_Mesh* pxMesh2 = Asura_Module_System::GetMeshForModule(iModuleIndex);

					if(pxMesh1->TextureDataPtr[xPolyID.PolyIndex].m_iMaterialID != pxMesh2->TextureDataPtr[iPolyIndex].m_iMaterialID)
					{
						continue;
					}

				}

				// (SB - 12/12/06) Support for 'Select by polygon data' option
				if (s_bLimitSelectionToSamePolygonData)
				{
					// ignore polygons with different project polygon data
					Asura_Mesh* pxMesh1 = Asura_Module_System::GetMeshForModule(xPolyID.ModuleIndex);
					Asura_Mesh* pxMesh2 = Asura_Module_System::GetMeshForModule(iModuleIndex);

					if (pxMesh1->GetProjectPolygonData(xPolyID.PolyIndex) != pxMesh2->GetProjectPolygonData(iPolyIndex))
					{
						continue;
					}
				}

				// (SB - 29/01/07) Support for 's_bRequireSharedEdge' option
				if (s_bRequireSharedEdge)
				{
					// ignore polygons that only share one vertex
					if (pxPolyAdj->GetNumSharedVertices(xAdjPoly.ModuleIndex, xAdjPoly.PolyIndex) < 2)
					{
						continue;
					}
				}
				
				xSelectionList.add_entry(xAdjPoly);			
			}
		}
	}

	// select those polygons
	for(LIF<PolygonID> xSelectLIF(&xSelectionList);!xSelectLIF.done();xSelectLIF.next())
	{
		SelectPolygon(xSelectLIF().ModuleIndex,xSelectLIF().PolyIndex);
	}

}

void Tools_Polygon_Selection::ShrinkSelection()
{
	if(SelectionLocked) return;

	List<PolygonID> xDeselectionList;

	//get a list of all selected polygons that are adjacent to at least one unselected polygon
	PolygonID xPolyID;
	StartIteration();

	while(GetNextSelected(&xPolyID))
	{
		const Tools_Polygon_Adjacency* pxPolyAdj = Tools_Adjacency::GetPolygonAdjacencies(xPolyID.ModuleIndex,xPolyID.PolyIndex);
		for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			// ignore adjacencies between zones. RAR 21/07/2006
			if(pxAdj->IsBetweenZones()) continue;

			
			int iPolyIndex = pxAdj->GetPolygonIndex();
			int iModuleIndex = pxAdj->GetModuleIndex();

			PolygonID xAdjPoly(iModuleIndex,iPolyIndex);
			if(!IsSelected(xAdjPoly))
			{
				if(s_bLimitSelectionToSameObject)
				{
					//ignore polygons in other modules
					if(iModuleIndex != xPolyID.ModuleIndex) continue;
				}

#if 0
				if(s_bLimitSelectionToSameMaterial)
				{
					//ignore polygons with different materials
					Asura_Mesh* pxMesh1 = Asura_Module_System::GetMeshForModule(xPolyID.ModuleIndex);
					Asura_Mesh* pxMesh2 = Asura_Module_System::GetMeshForModule(iModuleIndex);

					if(pxMesh1->TextureDataPtr[xPolyID.PolyIndex].m_iMaterialID != pxMesh2->TextureDataPtr[iPolyIndex].m_iMaterialID)
					{
						continue;
					}

				}

				// (SB - 12/12/06) Support for 'Select by polygon data' option
				if (s_bLimitSelectionToSamePolygonData)
				{
					//ignore polygons with different project polygon data
					Asura_Mesh* pxMesh1 = Asura_Module_System::GetMeshForModule(xPolyID.ModuleIndex);
					Asura_Mesh* pxMesh2 = Asura_Module_System::GetMeshForModule(iModuleIndex);

					if (pxMesh1->GetProjectPolygonData(xPolyID.PolyIndex) != pxMesh2->GetProjectPolygonData(iPolyIndex))
					{
						continue;
					}
				}
#endif

				//Found a non-selected adjacent polygon
				xDeselectionList.add_entry(xPolyID);
				break;
			}
		}
	}

	//deselect those polygons
	for(LIF<PolygonID> xDeselectLIF(&xDeselectionList);!xDeselectLIF.done();xDeselectLIF.next())
	{
		DeselectPolygon(xDeselectLIF().ModuleIndex,xDeselectLIF().PolyIndex);
	}

}


void Tools_Polygon_Selection::ListPolygonsInUVShell(int iChannel, const PolygonID& xStartPolygon,List<PolygonID>& xOutputList)
{
	//initialise the list
    xOutputList.empty();


	xOutputList.add_entry(xStartPolygon);



	//work out from the original polygon finding all linked polygons that have continuous UV coordinates
	for(LIF<PolygonID> xPolyLIF(&xOutputList);!xPolyLIF.done();xPolyLIF.next())
	{

		Asura_Mesh* pxMesh1 = Asura_Module_System::GetMeshForModule(xPolyLIF().ModuleIndex);
		Asura_Vector_3 xModulePos1 = Asura_Module_System::GetPositionOfModule(xPolyLIF().ModuleIndex);
		const Asura_Mesh_Polygon* pxPoly1 = &pxMesh1->PolygonDataPtr[xPolyLIF().PolyIndex];

		if(!pxMesh1->TextureDataPtr) continue;
		const Asura_Mesh_TextureInfo* pxTex1 = &pxMesh1->TextureDataPtr[xPolyLIF().PolyIndex];

		//get the list of adjacent polygons
		const Tools_Polygon_Adjacency* pxPolyAdj = Tools_Adjacency::GetPolygonAdjacencies(xPolyLIF().ModuleIndex,xPolyLIF().PolyIndex);
		for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			//ignore adjacencies between zones. RAR 02/05/2006
			if(pxAdj->IsBetweenZones()) continue;

			int iAdjModuleIndex = pxAdj->GetModuleIndex();
			int iAdjPolyIndex   = pxAdj->GetPolygonIndex();

			if(s_bLimitSelectionToSameObject)
			{
				//ignore polygons in other modules
				if(iAdjModuleIndex != xStartPolygon.ModuleIndex) continue;
			}
		
			if(s_bLimitSelectionToSameMaterial && s_bMaterialValid)
			{
				//make sure polygon has correct material
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if( GetMaterialResponseHashID( iAdjModuleIndex, iAdjPolyIndex ) != s_uMaterialResponseHashID )
					{
						continue;
					}

				#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

					if(GetMaterialID(iAdjModuleIndex, iAdjPolyIndex) != s_iMaterial)
					{
						continue;
					}

				#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
			}

			// Support for 'Select by polygon data' option
			if (s_bLimitSelectionToSamePolygonData)
			{
				if (GetPolyData(iAdjModuleIndex, iAdjPolyIndex) != s_uPolyData)
				{
					continue;
				}
			}



			PolygonID xPolyID(iAdjModuleIndex,iAdjPolyIndex);
			if(xOutputList.contains(xPolyID)) continue;

			Asura_Mesh* pxMesh2 = Asura_Module_System::GetMeshForModule( iAdjModuleIndex );
			Asura_Vector_3 xModulePos2 = Asura_Module_System::GetPositionOfModule(iAdjModuleIndex);
			const Asura_Mesh_Polygon* pxPoly2 = &pxMesh2->PolygonDataPtr[iAdjPolyIndex];

			if(!pxMesh2->TextureDataPtr) continue;
			const Asura_Mesh_TextureInfo* pxTex2 = &pxMesh2->TextureDataPtr[iAdjPolyIndex];


			//search for a pair of shared vertices
			int iSharedFound = 0;
			int aiVert1[2];
			int aiVert2[2];

			for(int j=0;j<pxPoly1->NumberOfVertices() && iSharedFound<2;j++)
			{
				Asura_Vector_3 xPos1 = xModulePos1 + pxMesh1->VertexDataPtr[pxPoly1->VertexID[j]].Position;

				for(int k=0;k<pxPoly2->NumberOfVertices();k++)
				{
					Asura_Vector_3 xPos2 = xModulePos2 + pxMesh2->VertexDataPtr[pxPoly2->VertexID[k]].Position;
					
					if(Tools_Adjacency::IsDistanceWithinTolerance(xPos2-xPos1))
					{
						// found a matching vertex
						aiVert1[iSharedFound] = j;
						aiVert2[iSharedFound++] = k;
						break;
					}
				}
			}

			if(iSharedFound != 2) continue;

			//check the difference in the uv coords
			float fUDiff = pxTex1->U[iChannel][aiVert1[0]] - pxTex2->U[iChannel][aiVert2[0]];
			float fVDiff = pxTex1->V[iChannel][aiVert1[0]] - pxTex2->V[iChannel][aiVert2[0]];
			float fUDiff2 = pxTex1->U[iChannel][aiVert1[1]] - pxTex2->U[iChannel][aiVert2[1]];
			float fVDiff2 = pxTex1->V[iChannel][aiVert1[1]] - pxTex2->V[iChannel][aiVert2[1]];

			//make sure the differences for the 2 matching vertices correspond
			if(Asura_Maths::Abs(fUDiff-fUDiff2)>0.01f) continue;
			if(Asura_Maths::Abs(fVDiff-fVDiff2)>0.01f) continue;

			//get the uv difference rounded to the nearest integer
			float fUDiffInt = (float)(int)(fUDiff + (fUDiff>=0 ? 0.5f : -0.5f));
			float fVDiffInt = (float)(int)(fVDiff + (fVDiff>=0 ? 0.5f : -0.5f));

			//make sure the differences are close to being integers
			if(Asura_Maths::Abs(fUDiff-fUDiffInt)>0.01f) continue;
			if(Asura_Maths::Abs(fVDiff-fVDiffInt)>0.01f) continue;
		

			//texturing appears to be connected , so add this polygon
			xOutputList.add_entry(xPolyID);
			
		}
	}	
}

u_int Tools_Polygon_Selection::GetSelectionFrameStamp()
{
	return g_uSelectionFrameStamp;
}