//
// Tools_Texturing_Find.cpp
//
// Created by Kevin Lea - 05/10/2001 16:51:22
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Animation/Asura_TextureAnimation.h"
#include "Rendering/Asura_Module.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Mesh/Asura_Mesh.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Cursor.h"
#include "Interface/Asura_Key_Combination.h"
#include "Camera/Asura_Camera.h"
#include "Rendering/Asura_RenderStates.h"
#include "Rendering/Asura_Viewport.h"
#include "Rendering/Asura_Text_Output.h"
#include "Rendering/Asura_Render.h"
#include "Debugging/Asura_Assert.h"

#include "Core/Asura_Material_System.h"


#include "Misc/Tools_Render_Options.h"
#include "Tools_Overlays.h"
#include "Texturing/Tools_Texturing_Find.h"
#include "Materials/Tools_MaterialResponse_System.h"
#include "Materials/Tools_Overlay_PickMaterialResponse.h"
#include "MeshProcessing/Tools_TangentSpace.h"
#include "Misc/Tools_MessageLog.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Texturing/Tools_PickTexture.h"
#include "Load&Save/Tools_ModeDataFile.h"
#include "Texturing/Tools_Texture_Management.h"
#include "Flags/Tools_Mode_Flags.h"

///////////////////////////////////////////////////////////////////////////////
/*----------------------**
** Tools_Texturing_Find **
**----------------------*/

//static members
List<PolygonID>			Tools_Texturing_Find::FindPolygonList;
bool					Tools_Texturing_Find::SelectOnFind = true;
bool					Tools_Texturing_Find::s_bClearCurrentSelection = false;
LIL<PolygonID>			Tools_Texturing_Find::FindPolygonLIL(&Tools_Texturing_Find::FindPolygonList);
Asura_Vector_2 			Tools_Texturing_Find::FindOverlayPosition = Asura_Vector_2(23.0f,10.0f);
bool					Tools_Texturing_Find::FindOverlayOpen = false;
u_int					Tools_Texturing_Find::s_uFindOverlayMinimised = 0;
float 					Tools_Texturing_Find::s_fTexelPerMetreThreshold = 50.0f;
bool					Tools_Texturing_Find::s_bIncludeObjectPolygons = false;
float					Tools_Texturing_Find::s_fFindBySize_Min = 0;
float					Tools_Texturing_Find::s_fFindBySize_Max = 0.01f;
bool					Tools_Texturing_Find::s_bFindBySize_UseMax = true;
bool					Tools_Texturing_Find::s_bTangentError_CheckAllLoadedMeshes = false;

void Tools_Texturing_Find::Initialise()
{
	//load values from .dat file
	TransferModeData(TOOLSMODEDATAFILE_LOAD);
	
	CreateFindOverlay();
}

void Tools_Texturing_Find::Shutdown()
{
	//save values to .dat file
	TransferModeData(TOOLSMODEDATAFILE_SAVE);
	
	//clear out the list of found polygons
	while(FindPolygonList.size())
	{
		FindPolygonList.delete_first_entry();
	}
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_TEXTURING_FIND);
}

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

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

void Tools_Texturing_Find::ToggleFindOverlay(void)
{
	FindOverlayOpen = !FindOverlayOpen;

	if (FindOverlayOpen)
	{
		s_uFindOverlayMinimised = 0;
		CreateFindOverlay();
	}
	else
	{
		Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_TEXTURING_FIND);
	}
}
bool Tools_Texturing_Find::FindOverlayIsOn(void)
{
	return FindOverlayOpen;
}

void Tools_Texturing_Find::CreateFindOverlay(void)
{
	if(!FindOverlayOpen) return;

	Asura_Overlay* newOverlay;
	newOverlay = new Asura_Overlay("Find",false,&FindOverlayPosition,&s_uFindOverlayMinimised);
	
	
	Asura_Dialog* newDialog;
	Asura_Vector_2 offset = Asura_Vector_2(1.0f,3.0f);
			
	newDialog = new Asura_Tickbox_Dialog("Select On Find",offset,&SelectOnFind);
	newDialog->SetHelpText("Add all found polygons to the current selection whenever any 'Find' operation is performed.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Tickbox_Dialog("Clear Existing Selection",offset,&s_bClearCurrentSelection);
	newDialog->SetHelpText("Deselect all polygons whenever any find operation is performed.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Tickbox_Dialog("Include Object Polygons",offset,&s_bIncludeObjectPolygons);
	newDialog->SetHelpText("Allows object polygons that have been merged into the environment to be found.\nThese polygons will just be added to the 'find' list , they won't be selected.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::LargeSpacing();

	#if !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

		// (AAM -13.02.2007) Doesn't really make sense with new materials, since we could easily create
		// plenty that don't use any textures. Search for "blank" polies below instead
		newDialog = new Asura_Function_Dialog("Find Untextured",offset,FindUntexturedPolygons);
		newDialog->SetHelpText("Find all polygons with no assigned texture.");
		newOverlay->AddDialog(newDialog);
		offset.y+=Asura_Dialog::DefaultSpacing();

	#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

	newDialog = new Asura_Function_Dialog("Find Texture",offset,FindTexturedPolygons);
	newDialog->SetHelpText("Find all polygons using a specific texture.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

		newDialog = new Asura_Function_Dialog( "Find Blank Polygons", offset, FindBlankMaterial );
		newDialog->SetHelpText( "Find all polygons with no assigned material." );
		newOverlay->AddDialog( newDialog );
		offset.y += Asura_Dialog::DefaultSpacing();

		newDialog = new Asura_Function_Dialog( "Find Material", offset, FindMaterial );

	#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
		newDialog = new Asura_Function_Dialog("Find Material",offset,FindMaterial);
	#endif

	newDialog->SetHelpText("Find all polygons using a specific material");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Function_Dialog("Find Illegal PS2 Coordinates",offset,FindIllegalPsx2Coordinates);
	newDialog->SetHelpText("Find all polygons with coordinates that cover too large a range for the PS2.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Function_Dialog("Find Tangent Errors",offset,FindTangentErrors);
	newDialog->SetHelpText("Finds polygons that cause warning messages when calculating their tangents and binormals.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Tickbox_Dialog("Check All Loaded Meshes",offset,&s_bTangentError_CheckAllLoadedMeshes);
	newDialog->SetHelpText("Causes all loaded meshes to be checked for tangent/binormal errors , instead of just the 'module' meshes.\nIt isn't possible to goto the polygons in the other meshes , but a report of the mesh names will be given to help with tracking them down in their files.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::LargeSpacing();


	newDialog = new Asura_Function_Dialog("Find 8 Bit Transparent",offset,Find8BitTransparent);
	newDialog->SetHelpText("Finds all polygons that have both 8 bit alpha and transparency.\nThis combination isn't allowed on the environment.\nPolygons marked as don't render or don't export will be ignored.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();
		

	newDialog = new Asura_Function_Dialog("Find Polygon Flag",offset,FindPolygonFlag);
	newDialog->SetHelpText("Find all polygons that have a specified polygon flag.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();


	newDialog = new Asura_Function_Dialog("Find By Texture Repeat",offset,FindTextureRepeat);
	newDialog->SetHelpText("Find polygons based on the number of repeats of texture used.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Function_Dialog("Find Stretched",offset,FindPolygonsWithStretchedTexture);
	newDialog->SetHelpText("Find polygons that have overly stretched UV coordinates.\nUses the Texel Per Metre setting below for determining what counts as stretched.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Numerical_Dialog("Texel Per Metre",offset,&s_fTexelPerMetreThreshold,0,9999.0f,1.0f,"%.1f");
	newDialog->SetHelpText("Texel per metre threshold for finding stretched textures.\nAny polygons lower than this will be selected.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::LargeSpacing();

	newDialog = new Asura_Function_Dialog("Find By Size",offset,FindPolygonsBySize);
	newDialog->SetHelpText("Find polygons whose size is between the minimum and maximum values specified below.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Numerical_Dialog("Min Size",offset,&s_fFindBySize_Min,0,999.0f,1.0f,"%.3f");
	newDialog->SetHelpText("The minimum size of polygon to find in metres. This value is squared to determine the minimum polygon area.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Numerical_Dialog("Max Size",offset,&s_fFindBySize_Max,0,999.0f,1.0f,"%.3f");
	newDialog->SetHelpText("The maximum size of polygon to find in metres. This value is squared to determine the maximum polygon area.");
	newOverlay->AddDialog(newDialog);

	offset.x = newDialog->m_xMaxCoord.x+1.0f;
	newDialog = new Asura_Tickbox_Dialog("Use",offset,&s_bFindBySize_UseMax);
	newDialog->SetHelpText("Use the maximum size paramater for 'Find By Size'.");
	newOverlay->AddDialog(newDialog);
	offset.x = 1.0f;
	offset.y+=Asura_Dialog::LargeSpacing();



	newDialog = new Asura_Dialog("Goto Next",offset);
	newOverlay->AddDialog(newDialog);
	offset.x = newDialog->m_xMaxCoord.x+1.0f;

	newDialog = new Asura_Function_Dialog("Polygon",offset,NextFoundPolygon);
	newDialog->SetHelpText("Goto the next polygon in the list of polygons found by the last Find operation.");
	newOverlay->AddDialog(newDialog);
	offset.x = newDialog->m_xMaxCoord.x+1.0f;

	newDialog = new Asura_Function_Dialog("Module",offset,NextFoundModule);
	newDialog->SetHelpText("Skip to the first polygon in the next module in the list of polygons found by the last Find operation.");
	newOverlay->AddDialog(newDialog);
	offset.x = 1.0f;
	offset.y+=Asura_Dialog::DefaultSpacing();

	newDialog = new Asura_Dialog("Goto Prev",offset);
	newOverlay->AddDialog(newDialog);
	offset.x = newDialog->m_xMaxCoord.x+1.0f;

	newDialog = new Asura_Function_Dialog("Polygon",offset,PreviousFoundPolygon);
	newDialog->SetHelpText("Goto the previous polygon in the list of polygons found by the last Find operation.");
	newOverlay->AddDialog(newDialog);
	offset.x = newDialog->m_xMaxCoord.x+1.0f;

	newDialog = new Asura_Function_Dialog("Module",offset,PreviousFoundModule);
	newDialog->SetHelpText("Skip to the last polygon in the previous module in the list of polygons found by the last Find operation.");
	newOverlay->AddDialog(newDialog);
	offset.x = 1.0f;
	offset.y+=Asura_Dialog::DefaultSpacing();


	newDialog = new Asura_Function_Dialog("Show List",offset,ShowFindList);
	newDialog->SetHelpText("Shows a list of all modules that have polygons in the find list ,along with the number of polygons for each.");
	newOverlay->AddDialog(newDialog);
	offset.y+=Asura_Dialog::DefaultSpacing();

	
	newOverlay->m_uID = TOOLS_OVERLAY_TEXTURING_FIND;

	Asura_Overlay_System::SubmitNewOverlay(newOverlay,false);
}

int Tools_Texturing_Find::FindPolygons(Tools_PolygonFind* pxFind, u_int uFlags)
{
	//empty out the current list
	while(FindPolygonList.size()) FindPolygonList.delete_first_entry();

	int iMeshesFound = 0;
	int iHiddenCount = 0;
	int iObjectPolyCount = 0;

	int iTotalFound = 0;


	//check each polygon of each mesh
	for(int i=0;i<Asura_Module_System::GetNumberOfModules();i++)
	{
		bool bFoundPolyInMesh = false;
		
		//get the mesh
		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);

		//check those polys
		for(int j=0;j<pxMesh->NumberOfPolygons;j++)
		{
			//Should this polygon be included in the find list?
			if(pxFind->IncludePolygon(pxMesh,j) || pxFind->IncludePolygon(i,j))
			{
				iTotalFound++;
			
				if(pxMesh->IsMergedObjectPolygon(j) && !s_bIncludeObjectPolygons)
				{
					iObjectPolyCount++;
				}
				else if(Tools_Render_Options::IsHidden(i,j))
				{
					iHiddenCount++;
				}
				else
				{
					//add to the list of found polygons
					FindPolygonList.add_entry(PolygonID(i,j));
					if(!bFoundPolyInMesh)
					{
						//increment the number of pxMeshes that contain found polygons
						iMeshesFound++;
						bFoundPolyInMesh = true;
					}
				}
			}
			
		}
	}

	
	//select all the polygons in the list if we are supposed to
	if(SelectOnFind && !(uFlags & TOOLS_FINDPOLYGONS_DONTCHANGESELECTION))
	{
		if(s_bClearCurrentSelection)
		{
			Tools_Polygon_Selection::DeselectAll();
		}
		for(LIF<PolygonID> find_lif(&FindPolygonList);!find_lif.done();find_lif.next())
		{
		
			Tools_Polygon_Selection::SelectPolygon(find_lif().ModuleIndex,find_lif().PolyIndex);
		}
	}

	//say how many polygons we found
	if(!(uFlags & TOOLS_FINDPOLYGONS_QUIET))
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"Found %d polygons in %d meshes",FindPolygonList.size(),iMeshesFound);
		pxFind->DisplayMessage();
		if(iHiddenCount)
		{
			Tools_Timed_Message_System::AddMessage(0xffffff,"%d polygons were ignored because they were hidden",iHiddenCount);
		}
		if(iObjectPolyCount)
		{
			Tools_Timed_Message_System::AddMessage(0xffffff,"%d polygons were found in merged objects",iObjectPolyCount);
		}
	}

	if(FindPolygonList.size())
	{
		//reset the iterator
		FindPolygonLIL.restart();
		FindPolygonLIL.previous();//so the first 'next' will take it to the first in the list
	}

	return iTotalFound;
}

void Tools_Texturing_Find::NextFoundPolygon()
{
	if(FindPolygonList.size())
	{
		FindPolygonLIL.next();
		GotoPolygon(FindPolygonLIL());
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"No polygons in the Find list");
	}
}

void Tools_Texturing_Find::PreviousFoundPolygon()
{
	if(FindPolygonList.size())
	{
		FindPolygonLIL.previous();
		GotoPolygon(FindPolygonLIL());
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"No polygons in the Find list");
	}
}

void Tools_Texturing_Find::NextFoundModule()
{
	if(FindPolygonList.size())
	{
		int iPrevModule = FindPolygonLIL().ModuleIndex;
		//go through list to next module
		for(int i=0;i<FindPolygonList.size();i++,FindPolygonLIL.next())
		{
			if(iPrevModule != FindPolygonLIL().ModuleIndex) break;
		}
		//and goto it
		GotoPolygon(FindPolygonLIL());
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"No polygons in the Find list");
	}
}

void Tools_Texturing_Find::PreviousFoundModule()
{
	if(FindPolygonList.size())
	{
		int iPrevModule = FindPolygonLIL().ModuleIndex;
		//go through list to previous module
		for(int i=0;i<FindPolygonList.size();i++,FindPolygonLIL.previous())
		{
			if(iPrevModule != FindPolygonLIL().ModuleIndex) break;
		}
		//and goto it
		GotoPolygon(FindPolygonLIL());
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"No polygons in the Find list");
	}
}


void Tools_Texturing_Find::GotoPolygon(const PolygonID& polygon)
{
	Asura_Assert(polygon.ModuleIndex>=0 && polygon.ModuleIndex<Asura_Module_System::GetNumberOfModules(),"Module index out of bounds");
	Asura_Vector_3 new_location(0,0,0);
	Asura_Euler_3 new_orientation(0,0,0);

	//get the mesh and polygon data for this poly
	Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(polygon.ModuleIndex);
	Asura_Mesh_Polygon* poly = &mesh->PolygonDataPtr[polygon.PolyIndex];
	Asura_Vector_3  module_loc;
	Asura_Module_System::GetPositionOfModule(polygon.ModuleIndex,module_loc);

	//calculate the polygon's centre
	Asura_Vector_3 centre(0,0,0);
	int num_verts = poly->NumberOfVertices();
	for(int i=0;i<num_verts;i++)
	{
		centre += mesh->VertexDataPtr[poly->VertexID[i]].Position;
	}

	//get the average , and add on the module's location
	centre /= (float)num_verts;
	centre += module_loc;


	//get the polygon's normal
	if(!mesh->AreNormalsValid()) mesh->CreateNormals();
	Asura_Vector_3 normal = mesh->NormalsDataPtr[polygon.PolyIndex].FaceNormal;

	//put the 'player' a few of  metres away from the poly centre
	new_location = centre + normal*4.0f;

	normal = -normal;
	//determine the orientation that will face in the opposite direction to the normal
	new_orientation.y = Asura_Maths::ArcTangent(normal.x,normal.z);
	
	float xz_magnitude = Asura_Maths::Sqrt(normal.x*normal.x+normal.z*normal.z);

	new_orientation.x = Asura_Maths::ArcTangent(-normal.y,xz_magnitude);

	//update the player/camera
	Asura_Camera::SetPosition(new_location);

	Asura_Matrix_3x3 matrix;
	new_orientation.ToMatrix(matrix);
	Asura_Camera::SetOrientation(matrix);

	Tools_Timed_Message_System::AddMessage(0xffffff,"Mesh %d , Polygon %d",polygon.ModuleIndex,polygon.PolyIndex);
	
}

static enum
{
	MODULESORT_NAME_UP,
	MODULESORT_NAME_DOWN,
	MODULESORT_TOTAL_UP,
	MODULESORT_TOTAL_DOWN,
	MODULESORT_OBJ_UP,
	MODULESORT_OBJ_DOWN,
} g_eModuleSortMode = MODULESORT_TOTAL_DOWN;

struct ToolsFindModuleInfo
{
	int m_iIndex;
	Asura_Mesh* m_pxMesh;
	int m_iTotalPolys;
	int m_iObjPolys;
};

int PolygonFindScreenSort(const void* m1,const void* m2)
{
	ToolsFindModuleInfo* pxModule1 = (ToolsFindModuleInfo*)m1;
	ToolsFindModuleInfo* pxModule2 = (ToolsFindModuleInfo*)m2;

	switch(g_eModuleSortMode)
	{
		case MODULESORT_NAME_UP:
			return pxModule1->m_pxMesh->Name.Compare(pxModule2->m_pxMesh->Name);
			break;
		case MODULESORT_NAME_DOWN:
			return -pxModule1->m_pxMesh->Name.Compare(pxModule2->m_pxMesh->Name);
			break;
		case MODULESORT_TOTAL_UP:
			return pxModule1->m_iTotalPolys - pxModule2->m_iTotalPolys;
			break;
		case MODULESORT_TOTAL_DOWN:
			return pxModule2->m_iTotalPolys - pxModule1->m_iTotalPolys;
			break;
		case MODULESORT_OBJ_UP:
			return pxModule1->m_iObjPolys - pxModule2->m_iObjPolys;
			break;
		case MODULESORT_OBJ_DOWN:
			return pxModule2->m_iObjPolys - pxModule1->m_iObjPolys;
			break;
	}

	return 0;
}


void Tools_Texturing_Find::ShowFindList()
{
	if(!FindPolygonList.size())
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"No polygons in the Find list");
		return;
	}

	int iFirstShownModule = 0;
	int iMouseOverIndex = -1;
	
	
	const float fNameOffsetX = 10;
	const float fTotalOffsetX = 25;
	const float fObjectOffsetX = 35;
	const float fSecondColumnX = 50;

	//work out how many columns and rows we can display
	//get the screen size , and convert to print coordinates
	float fPrintWidth = (float)Asura_Viewport::GetWidth();
	float fPrintHeight = (float)Asura_Viewport::GetHeight();

	Asura_Text_Output::ConvertPixelsToPrintCoords(fPrintWidth,fPrintHeight);

	int iNumColumns = (int)(fPrintWidth/fSecondColumnX);
	int iNumModulesPerColumn = (int)(fPrintHeight - 10.0f);

	int iNumModules = Asura_Module_System::GetNumberOfModules();

	//build an array of module info , allow enough for all modules
	ToolsFindModuleInfo* pxModuleInfo = new ToolsFindModuleInfo[iNumModules];

	bool bNeedObjectColumn = false;

	//get all the polygons from the find list... each module's polygons should appear
	//in a block
	int iIndex = -1;
	for(LIF<PolygonID> xPolyLIF(&FindPolygonList);!xPolyLIF.done();xPolyLIF.next())
	{
		//moved onto a different module?
		if(iIndex==-1 || pxModuleInfo[iIndex].m_iIndex != xPolyLIF().ModuleIndex)
		{
			iIndex++;
			pxModuleInfo[iIndex].m_iIndex = xPolyLIF().ModuleIndex;
			pxModuleInfo[iIndex].m_pxMesh = Asura_Module_System::GetMeshForModule(xPolyLIF().ModuleIndex);
			pxModuleInfo[iIndex].m_iTotalPolys = 0;
			pxModuleInfo[iIndex].m_iObjPolys = 0;
		}
		pxModuleInfo[iIndex].m_iTotalPolys++;
		if(pxModuleInfo[iIndex].m_pxMesh->IsMergedObjectPolygon(xPolyLIF().PolyIndex))
		{
			pxModuleInfo[iIndex].m_iObjPolys++;
			bNeedObjectColumn = true;
		}
	}
	//reset the number of modules down to the number in the list
	iNumModules = iIndex+1;

	
	qsort(pxModuleInfo,iNumModules,sizeof(ToolsFindModuleInfo),PolygonFindScreenSort);
	
	do
	{
		Asura_Windows::CheckForWindowsMessages();
		Asura_UserInput::Update();
		Asura_Cursor::OncePerFrameUpdate();

		//get the current mouse position
		int mx,my;
		Asura_Cursor::GetScreenSpaceCoords(mx,my);
		float fPrintMX = (float)mx;
		float fPrintMY = (float)my;
		Asura_Text_Output::ConvertPixelsToPrintCoords(fPrintMX,fPrintMY);

		//determine which module entry the mouse is over
		iMouseOverIndex = -1;
		if(fPrintMY>=1 && fPrintMY<1+iNumModulesPerColumn)
		{
			iMouseOverIndex = (int)(fPrintMY-1);
			if(fPrintMX>=fSecondColumnX) iMouseOverIndex+=iNumModulesPerColumn;
		}

		//check user input
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_MOUSEBUTTON1))
		{
			if(iMouseOverIndex >= 0)
			{
				//goto the module the mouse is over
				int iIndex = iMouseOverIndex + iFirstShownModule;
				if(iIndex<iNumModules)
				{
					int iModule = pxModuleInfo[iIndex].m_iIndex;

					//advance the find list to a polygon in this module
					int iCount = 0;
					for(FindPolygonLIL.restart();iCount<FindPolygonList.size();FindPolygonLIL.next(),iCount++)
					{
						if(FindPolygonLIL().ModuleIndex == iModule)
						{
							GotoPolygon(FindPolygonLIL());
							break;
						}
					}
					break;
				}
			}
			else if(fPrintMY < 1.0f)
			{
				//determine column clicked on
				float fX = (float)((int)fPrintMX % (int) fSecondColumnX);

				
					
				if(fX>fNameOffsetX-4.0f && fX<fNameOffsetX+4.0f)
				{
					//clicked on name column
					g_eModuleSortMode = (g_eModuleSortMode==MODULESORT_NAME_UP) ? MODULESORT_NAME_DOWN : MODULESORT_NAME_UP;
				}
				else if(fX>fTotalOffsetX-4.0f && fX<fTotalOffsetX+4.0f)
				{
					//clicked on total column
					g_eModuleSortMode = (g_eModuleSortMode==MODULESORT_TOTAL_UP) ? MODULESORT_TOTAL_DOWN : MODULESORT_TOTAL_UP;
				}
				else if(fX>fObjectOffsetX-4.0f && fX<fObjectOffsetX+4.0f && bNeedObjectColumn)
				{
					//clicked on object column
					g_eModuleSortMode = (g_eModuleSortMode==MODULESORT_OBJ_UP) ? MODULESORT_OBJ_DOWN : MODULESORT_OBJ_UP;
				}
				
				qsort(pxModuleInfo,iNumModules,sizeof(ToolsFindModuleInfo),PolygonFindScreenSort);
			}
		}

		if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_LEFT))
		{
			//show previous modules
			iFirstShownModule -= iNumModulesPerColumn*iNumColumns;
			if(iFirstShownModule<0)
			{
				iFirstShownModule = ((iNumModules-1)/(iNumModulesPerColumn*iNumColumns))*(iNumModulesPerColumn*iNumColumns);
			}
		}
		
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_RIGHT))
		{
			//show next modules
			iFirstShownModule += iNumModulesPerColumn*iNumColumns;
			if(iFirstShownModule>=iNumModules)
			{
				iFirstShownModule = 0;
			}
		}
		

		//begin rendering
		Asura_Render::BeginFrame();

		//first draw a big black poly over everything that has taken place so far...
		float width = (float)Asura_Viewport::GetWidth();
		float height = (float)Asura_Viewport::GetHeight();
		Asura_Render::Overlay.SetRectXY(0,0,width,height);
		Asura_Render::Overlay.SetRectUV(0,0,1,1);
		Asura_Render::Overlay.TranslucencyMode = ASURA_TRANSLUCENCY_OFF;
		Asura_Render::Overlay.TextureIndex = ASURA_TEXTURE_NONE;
		Asura_Render::Overlay.Colour=0xff000000;
		Asura_Render::Overlay.Render();
		Asura_Render::FlushDisplay();

		
		//display the column headings
		int i;
		for(i=0;i<iNumColumns;i++)
		{
			float x = (float)(fSecondColumnX*i);
			Asura_Text_Output::Print(x+fNameOffsetX,0,0xff00ff00,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,"Name");
			Asura_Text_Output::Print(x+fTotalOffsetX,0,0xff00ff00,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,"Polys");
			if(bNeedObjectColumn)
			{
				Asura_Text_Output::Print(x+fObjectOffsetX,0,0xff00ff00,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,"Obj Polys");
			}
		}

		
		//display the polygon counts
		for(i=0;i<iNumModulesPerColumn*iNumColumns;i++)
		{
			int mn = i+iFirstShownModule;
			if(mn>=iNumModules) break;


			//get the mesh for the name
			Asura_Mesh* pxMesh = pxModuleInfo[mn].m_pxMesh;
			int iTotalPolys = pxModuleInfo[mn].m_iTotalPolys;
			int iObjPolys = pxModuleInfo[mn].m_iObjPolys;
			
			float x = (i/iNumModulesPerColumn)*fSecondColumnX;
			float y = (float)(1+i%iNumModulesPerColumn);
			u_int uColour = (i == iMouseOverIndex) ? 0xffff0000 : 0xffffffff;

			
			Asura_Text_Output::Print(x+fNameOffsetX,y,uColour,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,pxMesh->Name.GetString());
			Asura_Text_Output::Print(x+fTotalOffsetX,y,uColour,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,"%d",iTotalPolys);
			if(bNeedObjectColumn)
			{
				Asura_Text_Output::Print(x+fObjectOffsetX,y,uColour,ASURA_TEXT_FORMAT_CENTREJUSTIFIED,"%d",iObjPolys);
			}
		}

		//show some instructions
		Asura_Text_Output::Print(1,fPrintHeight-6,0xffffffff,0,"Click on module to go to it");
		Asura_Text_Output::Print(1,fPrintHeight-5,0xffffffff,0,"Click on heading to change sorting");
		Asura_Text_Output::Print(1,fPrintHeight-4,0xffffffff,0,"Escape : Exit");
		Asura_Text_Output::Print(1,fPrintHeight-3,0xffffffff,0,"Left Arrow : Show previous modules");
		Asura_Text_Output::Print(1,fPrintHeight-2,0xffffffff,0,"Right Arrow : Show next modules");

		//render cursor and finish
		Asura_Cursor::SetType(ASURA_CURSOR_CROSSHAIR);
		Asura_Cursor::Render();
		Asura_Render::EndFrame();
  
	}while(!Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_ESCAPE));

	delete [] pxModuleInfo;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
/*-----------------**
** Find by texture **
**-----------------*/

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
//For selection the material slots to search for the texture in
class Tools_ModalOverlay_GetTextureChannels : public Tools_Modal_Overlay
{
public :
	Tools_ModalOverlay_GetTextureChannels();
	bool DoModal();

	u_int GetSlots() {return s_uTextureSlots;}
	u_int GetChannels() {return s_uTextureChannels;}

private :
	virtual void InitialiseOverlay(Asura_Overlay* pxOverlay);
	virtual void Update();

	int m_iSlotSelectionIndex;
	int m_iChannelSelectionIndex;
	bool m_bInputDebounced;

	static void OkaySelected(){s_bOkaySelected = true;}
	static void CancelSelected(){s_bOkaySelected = false;}

	static bool IsSlotSelected(int iSlot);
	static void SetSlotSelected(int iSlot,bool bSet);
	static bool IsChannelSelected(int iChannel);
	static void SetChannelSelected(int iChannel,bool bSet);

	static u_int s_uTextureSlots;
	static u_int s_uTextureChannels;
	static bool s_bOkaySelected;
};

u_int Tools_ModalOverlay_GetTextureChannels::s_uTextureSlots;
u_int Tools_ModalOverlay_GetTextureChannels::s_uTextureChannels;
bool Tools_ModalOverlay_GetTextureChannels::s_bOkaySelected;

Tools_ModalOverlay_GetTextureChannels::Tools_ModalOverlay_GetTextureChannels()
:Tools_Modal_Overlay("Find texture in which slots?")
{
}

bool Tools_ModalOverlay_GetTextureChannels::DoModal()
{
	s_uTextureSlots = 0xffffffff;
	s_uTextureChannels = 0xffffffff;
	s_bOkaySelected = false;
	m_iSlotSelectionIndex = 0;
	m_iChannelSelectionIndex = 0;
	m_bInputDebounced = !Asura_UserInput::DebouncedHasAnyKeyBeenPressed();
	ShowOverlay();

	return s_bOkaySelected;
}

void Tools_ModalOverlay_GetTextureChannels::InitialiseOverlay(Asura_Overlay* pxOverlay)
{
	ASURA_STATIC_ASSERT( Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS == 10 );
	static const char* const s_aszTextureSlotNames[Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS] = 
	{
		"Albedo",
		"Normal Map",
		"Specular Map",
		"Transparency Map",
		"Self Illumination Map",
		"Environment Map",
		"Reflectivity Map",
		"Parallax Map",
		"Albedo Detail Map",
		"Normal Detail Map",
	};

	static const char* const s_aszTextureChannelNames[2] =
	{
		"Primary",
		"Secondary",
	};

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

	pxDialog = new Asura_Dialog("Material texture slot :",xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	
	pxDialog = new Asura_List_Dialog(xOffset, s_aszTextureSlotNames, &m_iSlotSelectionIndex, Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS, Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS, NULL);
	((Asura_List_Dialog*)pxDialog)->EnableMultipleSelection(true, IsSlotSelected, SetSlotSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 2.0f;

	pxDialog = new Asura_Dialog("UV/Texture channel :",xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	
	pxDialog = new Asura_List_Dialog(xOffset, s_aszTextureChannelNames, &m_iChannelSelectionIndex, 2, 2, NULL);
	((Asura_List_Dialog*)pxDialog)->EnableMultipleSelection(true, IsChannelSelected, SetChannelSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 2.0f;

	pxDialog = new Asura_Function_Dialog("Okay",xOffset,OkaySelected,&pxOverlay->m_bShouldClose);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=2.0f;

	pxDialog = new Asura_Function_Dialog("Cancel",xOffset,CancelSelected,&pxOverlay->m_bShouldClose);
	pxOverlay->AddDialog(pxDialog);
}

void Tools_ModalOverlay_GetTextureChannels::Update()
{
	if(m_bInputDebounced)
	{
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_ENTER))
		{
			GetOverlay()->m_bShouldClose = true;			
			OkaySelected();
			return;
		}
		if(Asura_UserInput::DebouncedKeyPressed(ASURA_KEYID_ESCAPE))
		{
			GetOverlay()->m_bShouldClose = true;			
			CancelSelected();
			return;
		}
	}
	else
	{
		m_bInputDebounced = !Asura_UserInput::DebouncedHasAnyKeyBeenPressed();
	}		
}

bool Tools_ModalOverlay_GetTextureChannels::IsSlotSelected(int iSlot)
{
	return (s_uTextureSlots & (1<<iSlot)) ? true : false;
}

void Tools_ModalOverlay_GetTextureChannels::SetSlotSelected(int iSlot, bool bSet)
{
	if(bSet)
		s_uTextureSlots |= (1<<iSlot);
	else
		s_uTextureSlots &=~ (1<<iSlot);
}

bool Tools_ModalOverlay_GetTextureChannels::IsChannelSelected(int iChannel)
{
	return (s_uTextureChannels & (1<<iChannel)) ? true : false;
}

void Tools_ModalOverlay_GetTextureChannels::SetChannelSelected(int iChannel, bool bSet)
{
	if(bSet)
		s_uTextureChannels |= (1<<iChannel);
	else
		s_uTextureChannels &=~ (1<<iChannel);
}

#endif


class Tools_PolygonFind_Textured : public Tools_PolygonFind
{
public :
	Tools_PolygonFind_Textured(int iTextureID,int iChannelsToCheck, int iSlotsToCheck = 0)
	{
		m_iTextureID = iTextureID;
		m_iChannelsToCheck = iChannelsToCheck;
		m_iSlotsToCheck = iSlotsToCheck;
	}
	
private :

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

	virtual bool IncludePolygon( Asura_Mesh* pxMesh, int iPolyIndex )
	{
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[ iPolyIndex ];
		for(int iMat=0; iMat<TOOLS_NUM_MATERIALRESPONSE_PER_POLY; iMat++)
		{
			if(!(m_iChannelsToCheck & (1<<iMat))) continue;

			Tools_MaterialResponse* const pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID( pxTex->m_auMaterialResponseHashID[iMat] );

			if( !pxMaterial ) continue;

			for( u_int u = 0; u < Asura_MaterialResponse::NUMBER_OF_TEXTURE_SLOTS; ++u )
			{
				if( m_iSlotsToCheck & (1 << u) )
				{
					if( pxMaterial->GetTexture(u) == m_iTextureID ) return true;
				}
			}
		}

		return false;
	}

#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		//Check polygon for desired texture
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[iPolyIndex];
	
		for(int iChan=0;iChan<ASURA_MATERIAL_MAXTEXTURES;iChan++)
		{
			if(m_iChannelsToCheck & (1<<iChan))
			{
				// Check diffuse textures
				if(Asura_Material_System::GetTextureID(pxTex->m_iMaterialID,iChan) == m_iTextureID)
				{
					return true;
				}

				// RAR 31/10/2006 Check normal map
				if(m_iTextureID != ASURA_TEXTURE_NONE)
				{
					if(Asura_Material_System::GetNormalMapID(pxTex->m_iMaterialID,iChan) == m_iTextureID)
					{
						return true;
					}
				}
				
				// VGP 14Feb2006 Check specular maps
				// RAR 15/06/2006 Don't check the specular texture when searching for untextured polygons
				if(m_iTextureID != ASURA_TEXTURE_NONE)
				{
					if(Asura_Material_System::GetSpecularMapID(pxTex->m_iMaterialID,iChan) == m_iTextureID)
					{
						return true;
					}
				}
			}
		}
		return false;
	}

#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

	int m_iTextureID;
	int m_iChannelsToCheck;
	int m_iSlotsToCheck;
};

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

void Tools_Texturing_Find::FindTexturedPolygons()
{
	// Ask for the texture to find
	const int iTexture = Tools_PickTexture::PickTexture( "Find polygons using which texture?", ASURA_TEXTURE_NONE, PICKTEXTURE_ALLTEXTURES );
	if( iTexture == ASURA_TEXTURE_NONE ) return;

	// Find out which channels to check
	Tools_ModalOverlay_GetTextureChannels xOverlay;
	if(!xOverlay.DoModal()) return;

	Tools_PolygonFind_Textured xFind( iTexture, xOverlay.GetChannels(), xOverlay.GetSlots() );
	FindPolygons( &xFind );
}

#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

void Tools_Texturing_Find::FindUntexturedPolygons()
{
	Tools_PolygonFind_Textured xFind(ASURA_TEXTURE_NONE,0x00000001);
	FindPolygons(&xFind);
}

void Tools_Texturing_Find::FindTexturedPolygons()
{
	//ask for the texture to find
	int texture = Tools_PickTexture::PickTexture("Find polygons using which texture?",ASURA_TEXTURE_NONE,PICKTEXTURE_PRIMARY|PICKTEXTURE_SECONDARY|PICKTEXTURE_BUMP);
	if(texture==ASURA_TEXTURE_NONE) return;
	
	//find out which channels to check
	ListSelection_Overlay xOverlay("Find texture in which channels?");
	xOverlay.AddItem("All",0xffffffff);
	xOverlay.AddItem("Primary",0x00000001);
	xOverlay.AddItem("Secondary",0x00000002);

	if( !xOverlay.DoModal() ) { return; }
	
	Tools_PolygonFind_Textured xFind(texture,xOverlay.GetSelectionData());
	FindPolygons(&xFind);
}

#endif

/////////////////////////////////////////////////////////////////////////////////////////////////
/*------------------**
** Find by material **
**------------------*/

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

void Tools_Texturing_Find::FindBlankMaterial()
{
	Tools_PolygonFind_Material xFind( ASURA_HASH_ID_UNSET, 0x00000001 );
	FindPolygons( &xFind );
}

void Tools_Texturing_Find::FindMaterial()
{
	Tools_Overlay_PickMaterialResponse xOverlay("Select material to find");

	static Asura_Hash_ID s_uMaterialHash = 0;
	
	if( !xOverlay.DoModal(s_uMaterialHash) ) return;
	s_uMaterialHash = xOverlay.GetSelection();

	FindMaterial(s_uMaterialHash);
}

void Tools_Texturing_Find::FindMaterial(Asura_Hash_ID uMaterial)
{
	//find out which channels to check
	ListSelection_Overlay xListOverlay("Find material in which channels?");
	xListOverlay.AddItem("All",0xffffffff);
	xListOverlay.AddItem("Primary",0x00000001);
	xListOverlay.AddItem("Secondary",0x00000002);
	if(!xListOverlay.DoModal()) return;



	Tools_PolygonFind_Material xFind( uMaterial, xListOverlay.GetSelectionData() );
	FindPolygons( &xFind );

}

#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

void Tools_Texturing_Find::FindMaterial()
{
	int iMaterialID = Tools_PickTexture::PickMaterial("Find polygons using which material?");
	if(iMaterialID!=ASURA_MATERIAL_NONE)
	{
		Tools_PolygonFind_Material xFind(iMaterialID);
		FindPolygons(&xFind);
	}
}

#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

/////////////////////////////////////////////////////////////////////////////////////////////////
/*------------------**
** Find illegal PS2 **
**------------------*/
class Tools_PolygonFind_IllegalPS2 : public Tools_PolygonFind
{
	
private :
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		//get the texturing data
		Asura_Mesh_Polygon* pxPoly = &pxMesh->PolygonDataPtr[iPolyIndex];
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[iPolyIndex];

		int iNumVert = pxPoly->NumberOfVertices();

		bool bOk = true;

		for(int iTx=0;iTx<ASURA_MATERIAL_MAXTEXTURES;iTx++)
		{
			int iTextureID = Asura_Material_System::GetTextureID(pxTex->m_iMaterialID,iTx);

			if(iTextureID == ASURA_TEXTURE_NONE) continue;

			//determine the allowed range of coordinates based on the texture size
			int range = 8;//for -4 to +4
			int textureWidth,textureHeight;
			//if the texture is smaller than 512x512 then a greater range is allowed
			if(Tools_Texture_Management::GetTextureSize(iTextureID,textureWidth,textureHeight))
			{
				int size = max(textureWidth,textureHeight);
				if(size>0 && size<512)
				{
					while((size<<1) <= 512)
					{
						size<<=1;
						range<<=1;
					}
				}
				
			}


			float minU = Asura_Maths::LargeFloat;
			float minV = Asura_Maths::LargeFloat;
			float maxU = -Asura_Maths::LargeFloat;
			float maxV = -Asura_Maths::LargeFloat;

			//find the min and max uvs for this polygon
			for(int k=0;k<iNumVert;k++)
			{
				if(minU>pxTex->U[iTx][k]) minU=pxTex->U[iTx][k];
				if(minV>pxTex->V[iTx][k]) minV=pxTex->V[iTx][k];
				if(maxU<pxTex->U[iTx][k]) maxU=pxTex->U[iTx][k];
				if(maxV<pxTex->V[iTx][k]) maxV=pxTex->V[iTx][k];
			}

           	// (MH - 08.10.2002) check that we found mins and maxs (fails if UVs are NaN)
           	if ( (minU > maxU) || (minV > maxV))
           	{
           	    bOk = false;
           	}
           	else
           	{
			    //work out the shift to get the mins in the range (-halfRange,-halfRange+1]
			    float halfRange = (float) range/2;
			    
			    float uShift = -(halfRange-1)-(float)(int)minU;
			    if((uShift+minU-1)>-halfRange) uShift--;
			    //Asura_Assert(uShift+minU>-(halfRange) && uShift+minU<=-(halfRange),"")

			    float vShift = -(halfRange-1)-(float)(int)minV;
			    if((vShift+minV-1)>-halfRange) vShift--;
			    //Asura_Assert(vShift+minV>-halfRange && vShift+minV<=-(halfRange-1),"")

			    //see if the maxiums would be in an acceptable range after that
			    if(maxU+uShift>=halfRange || maxV+vShift>=halfRange)
			    {
				    bOk = false;
			    }
           	}
		}
		return !bOk;
	}

	virtual void DisplayMessage()
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"With out of range uv values");
	}

};

void Tools_Texturing_Find::FindIllegalPsx2Coordinates()
{
	Tools_PolygonFind_IllegalPS2 xFind;
	FindPolygons(&xFind);
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/*------------------------**
** Find stretched texture **
**------------------------*/

class Tools_PolygonFind_StretchedTexture : public Tools_PolygonFind
{
public :
	Tools_PolygonFind_StretchedTexture(float fTexelPerMetreThreshold)
	{
		m_fTexelPerMetreThreshold = fTexelPerMetreThreshold;
	}
private :
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		return Tools_Texturing_Find::GetTexelPerMetre(pxMesh,iPolyIndex)<=m_fTexelPerMetreThreshold;
	}
	
	virtual void DisplayMessage()
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"With stretched textures");
	}

	float m_fTexelPerMetreThreshold;
};


void Tools_Texturing_Find::FindPolygonsWithStretchedTexture()
{
	FindPolygonsWithStretchedTexture(s_fTexelPerMetreThreshold);
}

void Tools_Texturing_Find::FindPolygonsWithStretchedTexture(float fTexelPerMetreThreshhold)
{
	Tools_PolygonFind_StretchedTexture xFind(fTexelPerMetreThreshhold);
	FindPolygons(&xFind);
}

float Tools_Texturing_Find::GetTexelPerMetre(const Asura_Mesh* pxMesh,int iPoly)
{
	float fMin = Asura_Maths::LargeFloat;

	const Asura_Mesh_Polygon* pxPoly = &pxMesh->PolygonDataPtr[iPoly];
	const Asura_Mesh_TextureInfo * pxTex = &pxMesh->TextureDataPtr[iPoly];
	if(pxTex->m_iMaterialID == ASURA_MATERIAL_NONE) return 0;
	
	int iNumVerts = pxPoly->NumberOfVertices();

	for(int iChan = 0;iChan<ASURA_MATERIAL_MAXTEXTURES;iChan++)
	{
		//get the texture used ,s we can get the width/height
		int iTextureID = Asura_Material_System::GetTextureID(pxTex->m_iMaterialID,iChan);
		if(iTextureID == ASURA_TEXTURE_NONE) continue;

		int iWidth,iHeight;
		if(!Tools_Texture_Management::GetTextureSize(iTextureID,iWidth,iHeight))
		{
			iWidth = 512;
			iHeight = 512;
		}


		for(int i=0;i<iNumVerts;i++)
		{
			for(int j=1;j<=2;j++)
			{
				Asura_Vector_3 xPos1 = pxMesh->VertexDataPtr[pxPoly->VertexID[i]].Position;
				Asura_Vector_3 xPos2 = pxMesh->VertexDataPtr[pxPoly->VertexID[(i+j)%iNumVerts]].Position;

				float fUDiff = pxTex->U[iChan][i] - pxTex->U[iChan][(i+j)%iNumVerts];
				float fVDiff = pxTex->V[iChan][i] - pxTex->V[iChan][(i+j)%iNumVerts];

				float fUTexels = fUDiff * (float) iWidth;
				float fVTexels = fVDiff * (float) iHeight;

				float fDist = (xPos1-xPos2).Magnitude();
				float fTexels = Asura_Maths::Sqrt((fUTexels*fUTexels)+(fVTexels*fVTexels));

				if(fDist>0.001f)
				{
					float fTexelPerMetre = fTexels/fDist;
					if(fTexelPerMetre<fMin)
					{
						fMin = fTexelPerMetre;
					}
				}
			}
		}
	}


	return fMin;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
/*---------------**
** Find  by size **
**---------------*/
class Tools_PolygonFind_Size : public Tools_PolygonFind
{
public :
	Tools_PolygonFind_Size(float fMinSize,float fMaxSize,bool bUseMax)
	{
		m_fMinSize = fMinSize;
		m_fMaxSize = fMaxSize;
		m_fMinSquared = m_fMinSize*m_fMinSize;
		m_fMaxSquared = m_fMaxSize*m_fMaxSize;
		m_bUseMax = bUseMax;
	}
private :
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		float fArea = pxMesh->CalculatePolyArea(iPolyIndex);
		if(fArea<m_fMinSquared) return false;
		if(m_bUseMax && fArea>m_fMaxSquared) return false;
		return true;
	}
	
	virtual void DisplayMessage()
	{
		if(m_bUseMax)
			Tools_Timed_Message_System::AddMessage(0xffffff,"With area between %0.3fX%0.3f and %0.3fX%0.3f metre squared",m_fMinSize,m_fMinSize,m_fMaxSize,m_fMaxSize);
		else
			Tools_Timed_Message_System::AddMessage(0xffffff,"With area greater than %0.3fX%0.3f metre squared",m_fMinSize,m_fMinSize);
	}

	float m_fMinSquared;
	float m_fMaxSquared;
	float m_fMinSize;
	float m_fMaxSize;
	bool m_bUseMax;
};

void Tools_Texturing_Find::FindPolygonsBySize()
{
	FindPolygonsBySize(s_fFindBySize_Min,s_fFindBySize_Max,s_bFindBySize_UseMax);
}

void Tools_Texturing_Find::FindPolygonsBySize(float fMinSize,float fMaxSize,bool bUseMax)
{
	Tools_PolygonFind_Size xFind(fMinSize,fMaxSize,bUseMax);
	FindPolygons(&xFind);
	
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/*--------------**
** Find by flag **
**--------------*/

class Tools_PolygonFind_Flag : public Tools_PolygonFind
{
public :
	Tools_PolygonFind_Flag(int iFlag)
	{
		m_iFlag = iFlag;
	}
private :
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		return (pxMesh->GetPolygonFlags(iPolyIndex) & m_iFlag) ? true : false;
	}

	int m_iFlag;
};

void Tools_Texturing_Find::FindPolygonFlag()
{
	//ask for the flag to search for
	ListSelection_Overlay xOverlay("Select flag to find");

	//add common polygon flags
	for(int i=0;i<Tools_Mode_Flags::GetNumberOfPolygonFlags();i++)
	{
		xOverlay.AddItem(Tools_Mode_Flags::GetPolygonFlagName(i),Tools_Mode_Flags::GetPolygonFlagValue(i));
	}
	//and project polygon flags
	for(int i=0;i<Tools_Mode_Flags::GetNumberOfProjectPolygonFlags();i++)
	{
		const char* pszName = Tools_Mode_Flags::GetProjectPolygonFlagName(i);
		int iValue = Tools_Mode_Flags::GetProjectPolygonFlagValue(i);

		if(pszName)
		{
			xOverlay.AddItem(pszName,iValue);
		}
	}

	if(!xOverlay.DoModal()) return;

	int iFlag = xOverlay.GetSelectionData();

	Tools_PolygonFind_Flag xFind(iFlag);
	FindPolygons(&xFind);
}


///////////////////////////////////////////////////////////////////////////////////////////////
/*------------------------**
** Find 8 bit transparent **
**------------------------*/
class Tools_PolygonFind_8BitTrans : public Tools_PolygonFind
{
private :
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		//Ignore polygons that won't appear in the final render mesh
		if(pxMesh->GetPolygonFlags(iPolyIndex) & (AsuraPolygonFlag_DontRender|AsuraPolygonFlag_DontExport))
		{
			return false;
		}
		
		int iMaterialID = pxMesh->TextureDataPtr[iPolyIndex].m_iMaterialID;
		if(iMaterialID == ASURA_MATERIAL_NONE) return false;

		//is material flagged as transparent?
		if(!Asura_Material_System::IsMaterialTransparent(iMaterialID)) return false;

		//any textures in material with 8 bit alpha?
		bool b8Bit = false;
		for(int iChan=0;iChan<ASURA_MATERIAL_MAXTEXTURES;iChan++)
		{
			int iTextureID = Asura_Material_System::GetTextureID(iMaterialID,iChan);
			if(iTextureID == ASURA_TEXTURE_NONE) continue;

			if(Asura_Texture_Management::GetTextureFlags(iTextureID) & AsuraTextureFlag_8BitAlpha)
			{
				b8Bit = true;
			}
		}

		return b8Bit;
	}

	virtual void DisplayMessage()
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"With both 8 bit alpha and transparency flags");
	}

};


void Tools_Texturing_Find::Find8BitTransparent()
{
	Find8BitTransparent(false);
}

int Tools_Texturing_Find::Find8BitTransparent(bool bQuiet)
{
	Tools_PolygonFind_8BitTrans xFind;
	return FindPolygons(&xFind, bQuiet ? (TOOLS_FINDPOLYGONS_QUIET|TOOLS_FINDPOLYGONS_DONTCHANGESELECTION) : 0);
}

///////////////////////////////////////////////////////////////////////////////////////////////
/*------------------------**
** Find by texture repeat **
**------------------------*/


class Tools_PolygonFind_TextureRepeat : public Tools_PolygonFind
{

private :

	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[iPolyIndex];
		int iNumVerts = pxMesh->PolygonDataPtr[iPolyIndex].NumberOfVertices();

		for(int iChan=0;iChan<ASURA_MATERIAL_MAXTEXTURES;iChan++)
		{
			if(s_uChannels & (1<<iChan))
			{
				//Ignore if no texture set in this slot
				if(Asura_Material_System::GetTextureID(pxTex->m_iMaterialID,iChan) == ASURA_TEXTURE_NONE) continue;

				//get extents of UV coords
				float fMinU = pxTex->U[iChan][0];
				float fMaxU = pxTex->U[iChan][0];
				float fMinV = pxTex->V[iChan][0];
				float fMaxV = pxTex->V[iChan][0];

				for(int i=1;i<iNumVerts;i++)
				{
					if(fMinU > pxTex->U[iChan][i]) fMinU = pxTex->U[iChan][i];
					if(fMaxU < pxTex->U[iChan][i]) fMaxU = pxTex->U[iChan][i];
					if(fMinV > pxTex->V[iChan][i]) fMinV = pxTex->V[iChan][i];
					if(fMaxV < pxTex->V[iChan][i]) fMaxV = pxTex->V[iChan][i];
				}

				float fExtentU = fMaxU-fMinU;
				float fExtentV = fMaxV-fMinV;

				//which extents are in range?
				bool bUOkay = true;
				bool bVOkay = true;

				if(s_bUseMin)
				{
					if(fExtentU < s_fMin) bUOkay = false;
					if(fExtentV < s_fMin) bVOkay = false;
				}
				if(s_bUseMax)
				{
					if(fExtentU > s_fMax) bUOkay = false;
					if(fExtentV > s_fMax) bVOkay = false;
				}

				//ANDing or ORing?
				if(s_bBothExtents)
				{
					if(bUOkay && bVOkay) return true;
				}
				else
				{
					if(bUOkay || bVOkay) return true;
				}
			}
		}
		return false;
		
	}

	virtual void DisplayMessage()
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"With texture repeats within the given range");
	}


public :
	static float s_fMin;
	static float s_fMax;
	static bool s_bUseMin;
	static bool s_bUseMax;
	static bool s_bBothExtents;
	static u_int s_uChannels;
};

float Tools_PolygonFind_TextureRepeat::s_fMin = 0.0f;
float Tools_PolygonFind_TextureRepeat::s_fMax = 10.0f;
bool Tools_PolygonFind_TextureRepeat::s_bUseMin = true;
bool Tools_PolygonFind_TextureRepeat::s_bUseMax = true;
bool Tools_PolygonFind_TextureRepeat::s_bBothExtents = false;
u_int Tools_PolygonFind_TextureRepeat::s_uChannels = 0x1;


class Tools_TextureRepeatFind_Overlay : public Tools_Modal_Overlay
{
	public :
		Tools_TextureRepeatFind_Overlay()
		:Tools_Modal_Overlay("Find by texture repeat")
		{}

	private :
		virtual void InitialiseOverlay(Asura_Overlay* pxOverlay);
		
};

void Tools_TextureRepeatFind_Overlay::InitialiseOverlay(Asura_Overlay* pxOverlay)
{
	Asura_Dialog* pxDialog;
	Asura_Vector_2 xOffset = Asura_Vector_2(1.0f,3.0f);

	pxDialog = new Asura_Tickbox_Dialog("Both Extents In Range",xOffset,&Tools_PolygonFind_TextureRepeat::s_bBothExtents);
	pxDialog->SetHelpText("If ticked then both the U and the V coordinates must be within the specified range.\nOtherwise only one out of the two need to be in range.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_Numerical_Dialog("Min Repeat",xOffset,&Tools_PolygonFind_TextureRepeat::s_fMin,0,999.0f,1.0f,"%.3f");
	pxDialog->SetHelpText("The minimum number of texture repeats to qualify.");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = 20.0f;
	pxDialog = new Asura_Tickbox_Dialog("Use",xOffset,&Tools_PolygonFind_TextureRepeat::s_bUseMin);
	pxDialog->SetHelpText("Use the minimum repeat value");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("Max Repeat",xOffset,&Tools_PolygonFind_TextureRepeat::s_fMax,0,999.0f,1.0f,"%.3f");
	pxDialog->SetHelpText("The maximum number of texture repeats to qualify");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x = 20.0f;
	pxDialog = new Asura_Tickbox_Dialog("Use",xOffset,&Tools_PolygonFind_TextureRepeat::s_bUseMax);
	pxDialog->SetHelpText("Use the maximum repeat value");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x = 1.0f;
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	
	pxDialog = new Asura_FlagTickbox_Dialog("Primary UVs",xOffset,&Tools_PolygonFind_TextureRepeat::s_uChannels,0x1);
	pxDialog->SetHelpText("Check the primary UVs for number of repeats");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Secondary UVs",xOffset,&Tools_PolygonFind_TextureRepeat::s_uChannels,0x2);
	pxDialog->SetHelpText("Check the secondary UVs for number of repeats");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y+=Asura_Dialog::DefaultSpacing();

    	
	pxDialog = new Asura_FunctionWithKeybinding_Dialog("Okay",xOffset,NULL,ASURA_KEYID_ENTER,&pxOverlay->m_bShouldClose);
	pxOverlay->AddDialog(pxDialog);
}

void Tools_Texturing_Find::FindTextureRepeat()
{
	//show options overlay
	Tools_TextureRepeatFind_Overlay xOverlay;
	xOverlay.ShowOverlay();

	//and do the find thing
	Tools_PolygonFind_TextureRepeat xFind;
	FindPolygons(&xFind);
}


///////////////////////////////////////////////////////////////////////////////////////////////
/*----------------------**
** Find by texture anim **
**----------------------*/


//Searches for polygons using materials that use a given texture anim
class Tools_PolygonFind_TextureAnim : public Tools_PolygonFind
{

public :

	Tools_PolygonFind_TextureAnim(Asura_Hash_ID uAnimID)
	{
		m_uAnimID = uAnimID;
	}
	
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		Asura_Mesh_TextureInfo* pxTex = &pxMesh->TextureDataPtr[iPolyIndex];

		#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		for(int iMatSlot=0; iMatSlot<TOOLS_NUM_MATERIALRESPONSE_PER_POLY; iMatSlot++)
		{
			Asura_Hash_ID uMatHash = pxTex->m_auMaterialResponseHashID[iMatSlot];
			if(uMatHash)
			{
				const Tools_MaterialResponse* pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID(uMatHash);
				if(pxMaterial && pxMaterial->GetTextureAnim() == m_uAnimID)
				{
					return true;
				}
			}

		}
		#else
		int iMaterialID = pxTex->m_iMaterialID;
		if(iMaterialID != ASURA_MATERIAL_NONE)
		{
			const Asura_Material* pxMaterial = Asura_Material_System::GetMaterial(iMaterialID);
			if(pxMaterial && pxMaterial->m_uAnimHash == m_uAnimID)
			{
				return true;
			}
		}
		#endif
		return false;
	}

	virtual void DisplayMessage()
	{
		const Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(m_uAnimID);
		if(pxAnim)
			Tools_Timed_Message_System::AddMessage(0xffffff,"Using '%s'",pxAnim->GetName());
		else
			Tools_Timed_Message_System::AddMessage(0xffffff,"Using texture anim 0x%x",m_uAnimID);
	}


public :
	Asura_Hash_ID m_uAnimID;
};



void Tools_Texturing_Find::FindTextureAnim(Asura_Hash_ID uAnimID)
{
	//and do the find thing
	Tools_PolygonFind_TextureAnim xFind(uAnimID);
	FindPolygons(&xFind);
}


///////////////////////////////////////////////////////////////////////////////////////////////
/*--------------------------------------------**
** Find polygons with tangent/binormal errors **
**--------------------------------------------*/


//Searches for polygons using that cause warning messages in Tools_TangentSpace
class Tools_PolygonFind_TangentError : public Tools_PolygonFind
{

public :

	Tools_PolygonFind_TangentError()
	{
		m_pxPrevMesh = 0;
		m_pxTangentSpace = 0;
	}
	~Tools_PolygonFind_TangentError()
	{
		delete m_pxTangentSpace;
	}
	
	virtual bool IncludePolygon(Asura_Mesh* pxMesh,int iPolyIndex)
	{
		if(pxMesh != m_pxPrevMesh)
		{
			if(!pxMesh->AreNormalsValid()) pxMesh->CreateVertexNormals();
			
			m_pxPrevMesh = pxMesh;
			delete m_pxTangentSpace;
			m_pxTangentSpace = 0;
			//find the module for this mesh
			for(int i=0; i<Asura_Module_System::GetNumberOfModules(); i++)
			{
				if(Asura_Module_System::GetMeshForModule(i) == pxMesh)
				{
					m_pxTangentSpace = new Tools_Mesh_TangentSpace;
					m_pxTangentSpace->Initialise(i);
					break;
				}
			}
		}
		if(!m_pxTangentSpace)
		{
			m_pxTangentSpace = new Tools_Mesh_TangentSpace;
			m_pxTangentSpace->Initialise(pxMesh);
		}


		const Asura_Mesh_Polygon* pxPoly = &pxMesh->PolygonDataPtr[iPolyIndex];
		int iNumVerts = pxPoly->NumberOfVertices();

		for(int iChannel=0; iChannel<ASURA_MATERIAL_MAXTEXTURES; iChannel++)
		{
			Asura_Vector_3 xTangent, xBinormal;
	   		for(int iVert=0; iVert<iNumVerts; iVert++)
			{
				bool bError = false;
				m_pxTangentSpace->GetTangentAndBinormal(iPolyIndex, 
														pxPoly->VertexID[iVert], 
														iChannel,
														pxMesh->NormalsDataPtr[iPolyIndex].VertexNormal[iVert],
														xTangent,
														xBinormal,
														&bError);

				if(bError)
				{
					return true;
				}

			}
			//May also be assert in CalculateTangentAndBinormalForPoly from getting a too small tangent or binormal
			//So check for that as well now..
			Tools_TangentSpace::CalculateTangentAndBinormalForPoly(pxMesh, iPolyIndex, iChannel, xTangent, xBinormal);
			if(xTangent.MagnitudeSquared() <= 1.0e-15f || xBinormal.MagnitudeSquared() <= 1.0e-15f)
			{
				return true;
			}

		}
		return false;
	}

	virtual void DisplayMessage()
	{
		Tools_Timed_Message_System::AddMessage(0xffffff,"With errors calculating tangents and binormals");
	}


private :
	Asura_Mesh* m_pxPrevMesh;
	Tools_Mesh_TangentSpace* m_pxTangentSpace;
};



void Tools_Texturing_Find::FindTangentErrors()
{
	Tools_PolygonFind_TangentError xFind;
	FindPolygons(&xFind);	 

	if(s_bTangentError_CheckAllLoadedMeshes)
	{
		bool bAnyErrors = false;
		int iLogPosition = Tools_MessageLog::GetNextID();

		for(int i=0; i<Asura_Mesh_System::GetNumberOfMeshes(); i++)
		{
			Asura_Mesh* pxMesh = Asura_Mesh_System::GetMesh(i);

			int iErrors = 0;
			for(int j=0; j<pxMesh->NumberOfPolygons; j++)
			{
				if(xFind.IncludePolygon(pxMesh, j))
				{
					iErrors++;
					bAnyErrors = true;
				}
			}
			if(iErrors)
			{
				Tools_MessageLog::AddMessage(Tools_MessageLog::VERIFICATION, "Tangent Error", "Found %d tangent error(s) in %s", iErrors, pxMesh->Name.GetString());
			}
		}
		if(bAnyErrors)
		{
			Tools_MessageLog::ShowOverlay(Tools_MessageLog::VERIFICATION, "Tangent Error",  iLogPosition);
		}
	}
}
