//
// Tools_HierarchyProcessor_MultiMaterial_NormalMapped.cpp
//
// Created by BM - 15/04/2003 20:48:18
//
// Copyright (C) 2003 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "MeshProcessing/Tools_HierarchyProcessor_MultiMaterial_NormalMapped.h"

// Asura Platform-independent
#include "Animation/Asura_Hierarchy.h"
#include "Core/Asura_Material_System.h"
#include "Core/Asura_TextureFlags.h" // For ASURA_TEXTURE_NONE
#include "Mesh/Asura_Mesh.h"
#include "Mesh/Asura_Mesh_System.h"

// Tools
#include "Load&Save/Tools_SaveAsura.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Materials/Tools_MaterialResponse_System.h"
#include "MeshEdit/Tools_Mode_MeshEdit.h"
#include "MeshProcessing/Asura_TriStripper.h"
#include "MeshProcessing/Tools_TangentSpace.h"
#include "MeshProcessing/Tools_MeshRestartIndex.h"
#include "Misc/Tools_HashTable.h"

template<typename T>
void swap(T& x, T& y)
{
	T temp = x;
	x = y;
	y = temp;
}

List<Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip*>	Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_xProcessedStrips;
u_int																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_uTotalNumberOfVertices;
u_int																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_uTotalNumberOfIndices;
u_int																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_uTotalNumberOfTriangles;
u_int																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_uTotalNumberOfStrips;
bool																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_bBeenInitialised = false;
bool																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_bRequiresVertexAlpha = false;
const Asura_Hierarchy_Skin*											Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_pxCurrentSkin = NULL;

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
Asura_Hash_ID														Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_auCurrentMaterialResponse[2] = {ASURA_HASH_ID_UNSET,ASURA_HASH_ID_UNSET};
#else
	int																Tools_HierarchyProcessor_MultiMaterial_NormalMapped::s_iCurrentMaterial = ASURA_MATERIAL_NONE;
#endif

bool Tools_HierarchyProcessor_MultiMaterial_NormalMapped::MaterialSelectorFunction(Asura_Mesh* pxMesh, int iPolyIndex)
{
	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		if( pxMesh->TextureDataValid )
		{
			return s_auCurrentMaterialResponse[0] == pxMesh->TextureDataPtr[ iPolyIndex ].m_auMaterialResponseHashID[0]
				&& s_auCurrentMaterialResponse[1] == pxMesh->TextureDataPtr[ iPolyIndex ].m_auMaterialResponseHashID[1];
		}
		else
		{
			return s_auCurrentMaterialResponse[0] == ASURA_HASH_ID_UNSET && s_auCurrentMaterialResponse[1] == ASURA_HASH_ID_UNSET;
		}

	#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		if(pxMesh->TextureDataValid)
		{
			return s_iCurrentMaterial == pxMesh->TextureDataPtr[iPolyIndex].m_iMaterialID;
		}
		else
		{
			return s_iCurrentMaterial == ASURA_MATERIAL_NONE;
		}

	#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
}

bool Tools_HierarchyProcessor_MultiMaterial_NormalMapped::Initialise(const Asura_Hierarchy_Skin* pxSkin, bool bStripify /*=true*/)
{
	if(!pxSkin)
	{
		Asura_Assert(pxSkin,"no skin to process");
		return false;
	}

	s_pxCurrentSkin = pxSkin;

	//Tools_Level_Export::UpdateStatus("Stripifying Hierarchy", 0);
	
	// reset bone positions etc. (hack because of const pointer from original export) 	
	Asura_Hierarchy_Skin* pxSkinCopy = (Asura_Hierarchy_Skin* ) pxSkin;
	pxSkinCopy->CalculateSkinNormalData();
	
	//reset totals
	s_uTotalNumberOfTriangles = 0;
	s_uTotalNumberOfVertices  = 0;
	s_uTotalNumberOfIndices   = 0;
	s_uTotalNumberOfStrips	  = 0;

	s_bRequiresVertexAlpha = false;
	
	// Enumerate materials

	List<Asura_Mesh*> xMeshList;

	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		List< Tools_PolygonMaterialResponseIDs > xUsedMaterials;
	#else
		List<int> xUsedMaterials;
	#endif

	for (int u=0; u<pxSkin->NumberOfBones; u++)
	{		
		Asura_Mesh* pxMesh = pxSkin->SectionData[u].Mesh;

		if(pxMesh)
		{
			xMeshList.add_entry(pxMesh);

			if(pxMesh->TextureDataValid)
			{
				for(int iPoly = 0; iPoly < pxMesh->NumberOfPolygons; ++iPoly)
				{
					#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
						Tools_PolygonMaterialResponseIDs xMatIDs(pxMesh->TextureDataPtr[ iPoly ].m_auMaterialResponseHashID[0],pxMesh->TextureDataPtr[ iPoly ].m_auMaterialResponseHashID[1]);
					
						if( !xUsedMaterials.contains( xMatIDs ) )
						{
							xUsedMaterials.add_entry( xMatIDs );
						}
						
					#else
						if(!xUsedMaterials.contains(pxMesh->TextureDataPtr[iPoly].m_iMaterialID))
						{
							xUsedMaterials.add_entry(pxMesh->TextureDataPtr[iPoly].m_iMaterialID);
						}
					#endif
				}
			}
			else
			{
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
					// Just leave the material unset
					Tools_PolygonMaterialResponseIDs xMatIDs(ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET);

					if(!xUsedMaterials.contains(xMatIDs))
					{
						xUsedMaterials.add_entry(xMatIDs);
					}
				#else
					// assume the mesh has ASURA_MATERIAL_NONE
					if(!xUsedMaterials.contains(ASURA_MATERIAL_NONE))
					{
						xUsedMaterials.add_entry(ASURA_MATERIAL_NONE);
					}
				#endif
			}
		}		
	}

	// build a strip for each material

	Asura_Mesh* pxTempMesh = Asura_Mesh_System::GetMesh("tempMultiMaterialCollectorMesh", true);

	// We do two passes; first process opaque strips, then transparent ones.
	// This ensures they get rendered roughly correctly

	for( u_int uPass = 0; uPass < 2; ++uPass )
	{
		for(int iMaterial = 0; iMaterial < xUsedMaterials.size(); ++iMaterial)
		{
			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
				// See if we should be handling this material in this pass

				const Tools_MaterialResponse* const pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID( xUsedMaterials[ iMaterial ].GetMaterial(0) );
				const bool bTranslucent = (pxMaterial && pxMaterial->GetBlendMode() != Asura_MaterialResponse::BLEND_MODE_OPAQUE);

				if( uPass == 0 && bTranslucent || uPass == 1 && !bTranslucent ) continue;

			#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

				// See if we should be handling this material in this pass

				const int iMatID = xUsedMaterials[ iMaterial ];
				const bool bTranslucent = Asura_Material_System::IsMaterialTransparent( iMatID )
									   || Asura_Material_System::IsMaterialPS2AdditiveAlpha( iMatID );

				if( ((uPass == 0) && bTranslucent) || ((uPass == 1) && !bTranslucent) ) continue;

			#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

			// Do the processing

			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
				s_auCurrentMaterialResponse[0] = xUsedMaterials[ iMaterial ].GetMaterial(0);
				s_auCurrentMaterialResponse[1] = xUsedMaterials[ iMaterial ].GetMaterial(1);
			#else
				s_iCurrentMaterial = xUsedMaterials[iMaterial];
			#endif

			for(unsigned short iBone = 0; iBone < pxSkin->NumberOfBones; ++iBone)
			{
				if(!pxSkin->SectionData[iBone].Mesh) continue;

				// extract polygons with the correct material from this bone's mesh into tempMesh
				Tools_Mode_MeshEdit::CopyMesh(pxSkin->SectionData[iBone].Mesh, pxTempMesh, MaterialSelectorFunction);

				if(!pxTempMesh->NumberOfPolygons) continue; // no polys with this material at this bone

				// Split everything down into triangles
				int* piPolyConv1, *piPolyConv2;
				Tools_Mode_MeshEdit::UnmergePolygons(pxTempMesh, (bool (*)(Asura_Mesh*, int))NULL, &piPolyConv1, &piPolyConv2);
				delete[] piPolyConv1;
				delete[] piPolyConv2;
				Asura_Assert(pxTempMesh->NumberOfPolygons == pxTempMesh->NumberOfTriangles, "Didn't unmerge everything");

				// create a strip from these polygons
				MeshProcess(pxTempMesh, iBone, bStripify);
			}
		}
	}

	Asura_Mesh_System::DeleteMesh(pxTempMesh);

	s_bBeenInitialised = true;

	//Tools_Level_Export::UpdateStatus("Stripifying Hierarchy", 100);
	return true;
}

bool Tools_HierarchyProcessor_MultiMaterial_NormalMapped::HasHierarchyAnyNormalMappedMaterials(const Asura_Hierarchy_Skin* pxSkin)
{
	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		// Just always output normal data
		return true;
	#else

		if(!pxSkin)
		{
			Asura_Assert(pxSkin,"no skin to process");
			return false;
		}

		for (int u=0; u<pxSkin->NumberOfBones; u++)
		{		
			Asura_Mesh* pxMesh = pxSkin->SectionData[u].Mesh;

			if(pxMesh)
			{
				if(pxMesh->TextureDataValid)
				{
					for(int iPoly = 0; iPoly < pxMesh->NumberOfPolygons; ++iPoly)
					{
						int iNormalMapID = Asura_Material_System::GetNormalMapID(pxMesh->TextureDataPtr[iPoly].m_iMaterialID);
						if (iNormalMapID != ASURA_TEXTURE_NONE)
						{
							return true;
						}
					}
				}
			}		
		}

		return false;

	#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
}

void Tools_HierarchyProcessor_MultiMaterial_NormalMapped::ConvertMaterialsForExport()
{
	// (AAM - 11.12.2006) This doesn't mean anything under the material response system
	#if !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		List<Asura_Mesh*> xMeshList;

		for (int u=0; u<s_pxCurrentSkin->NumberOfBones; u++)
		{		
			Asura_Mesh* pxMesh = s_pxCurrentSkin->SectionData[u].Mesh;

			if(pxMesh)
			{
				xMeshList.add_entry(pxMesh);
			}
		}

		int aiMaterialMapping[ASURA_MAX_NO_OF_MATERIALS];
		Tools_SaveAsura::PrepareMeshMaterialConversionLists(xMeshList, aiMaterialMapping, 0);
		for(CLIF<Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip*> it(&s_xProcessedStrips); !it.done(); it.next())
		{
			Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip* pxStrip = it();
			const int iMaterialID = pxStrip->m_iMaterialID;

			int iMaterialArrayIdx = Asura_Material_System::TranslateMaterialIDToArrayIdx( iMaterialID );
			Asura_Assert( ASURA_MATERIAL_NONE == iMaterialArrayIdx || (0 <= iMaterialArrayIdx && iMaterialArrayIdx < ASURA_MAX_NO_OF_MATERIALS), "" );

			pxStrip->m_iMaterialID = ( iMaterialID != ASURA_MATERIAL_NONE ) ? aiMaterialMapping[iMaterialArrayIdx] : Tools_Level_Export::GetUntexturedMaterialIndex();
		}

	#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
}


bool Tools_HierarchyProcessor_MultiMaterial_NormalMapped::MeshProcess(Asura_Mesh* pxMesh, unsigned short usBoneIndex, bool bStripify)
{
	Asura_Assert(pxMesh,"no mesh passed to MeshProcess");

	//Allow for enough space in case all triangles are double sided. RAR 30/01/2004
	u_int uTotalIndices = pxMesh->NumberOfTriangles*3*2;
	u_int uNumberOfUniqueVertices = 0;
	u_int uIndex = 0;
	u_int uNumberOfTriangles = 0;

	Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex* pxWorkspaceVertices = new Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex[uTotalIndices];
	unsigned short* usIndexData = new unsigned short[uTotalIndices];
	
	Tools_HashTable< Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex>* pxVertexHashTable = new Tools_HashTable<Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex>( 1 << Asura_Maths::GetFloorLog2( uTotalIndices >> 1) );

	//Initialise tangent and binormal calculation
	//RAR 09/07/2007
	Tools_Mesh_TangentSpace xMeshTangentSpace;
	xMeshTangentSpace.Initialise(pxMesh);


	// fill up the workspace vertex buffer
	for (int iPoly=0; iPoly<pxMesh->NumberOfPolygons; iPoly++)
	{	
		int iIndexNumber[4];
		
		int iPolyFlags = 0;

		if (pxMesh->PolygonFlagDataPtr)
		{
			iPolyFlags = pxMesh->PolygonFlagDataPtr[iPoly].Flags;
		}

		if (iPolyFlags & AsuraPolygonFlag_DontRender) continue;

		bool bDoubleSided = (iPolyFlags & AsuraPolygonFlag_DoubleSided) ? true : false;

		//If double sided we will need to do 2 passes of adding the vertices , with
		//normals and vertex ordering being reversed for the second side of the polygons
		//RAR 30/01/2004
		for( int iMirror=0;iMirror < (bDoubleSided ? 2 : 1);iMirror++)
		{
			int v=0;
			do
			{
				if(!pxMesh->AreNormalsValid())
				{
					pxMesh->CreateNormals();
				}			
		
				int vID =  pxMesh->PolygonDataPtr[iPoly].VertexID[v];
				Asura_Vector_3& xPosition = pxMesh->VertexDataPtr[vID].Position;
				Asura_Vector_3& xNormal = pxMesh->NormalsDataPtr[iPoly].VertexNormal[v];

				Asura_Vector_3 xTangent, xBinormal;

				xMeshTangentSpace.GetTangentAndBinormal(iPoly, vID, 0, xNormal, xTangent, xBinormal);
				
				//Fill in the vertex in the next slot of the array of unique vertices.
				//If it turns out to be a duplicate it will be ignored overwritten by the next vertex we try instead
				//RAR 27/09/2007
				Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex& xVertexData = pxWorkspaceVertices[uNumberOfUniqueVertices];

				xVertexData.m_xPosition	= xPosition;
				xVertexData.m_xNormal	= xNormal;
				xVertexData.m_xTangent	= xTangent;
				xVertexData.m_xBinormal	= xBinormal;

				if(iMirror)
				{
					xVertexData.m_xNormal  *= -1.0f; 
				}
				
				const Asura_Mesh_TextureInfo* pxTexInfo = pxMesh->GetTextureInfoForExport(iPoly);
				xVertexData.m_fU0 = pxTexInfo->U[0][v];
				xVertexData.m_fV0 = pxTexInfo->V[0][v];
				xVertexData.m_fU1 = pxTexInfo->U[1][v];
				xVertexData.m_fV1 = pxTexInfo->V[1][v];

				xVertexData.m_fAlpha = pxMesh->TextureDataPtr[iPoly].m_afAlpha[v];

				#if !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
					if((xVertexData.m_fAlpha < 1.0f) && Asura_Material_System::IsMaterialTransparent(s_iCurrentMaterial))
					{
						s_bRequiresVertexAlpha = true;
					}
				#endif

				xVertexData.m_usBoneIndex = usBoneIndex;

				/*
				xVertexData.m_fU = pxMesh->TextureDataPtr[iPoly].U[v];
				xVertexData.m_fV = pxMesh->TextureDataPtr[iPoly].V[v];
				*/
				//xVertexData.m_fAlpha = pxMesh->TextureDataPtr[iPoly].m_afAlpha[v];

				//Add the vertex to the hash table , to determine if it is a duplicate
				const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex* pxVertexMatchFound = pxVertexHashTable->Insert(&xVertexData);

				if(pxVertexMatchFound == &xVertexData)
				{
					//Got back the entry we were adding , so it is a new vertex
					iIndexNumber[v] = uNumberOfUniqueVertices;
					uNumberOfUniqueVertices++;
				}
				else
				{
					//Got back an existing vertex.
					//Find its index in the unique vertex array
					u_int uUniqueVertexIndex = pxVertexMatchFound-pxWorkspaceVertices;
					Asura_Assert(uUniqueVertexIndex<uNumberOfUniqueVertices,"Error finding duplicate vertex. Index of found vertex is invalid");

					iIndexNumber[v] = uUniqueVertexIndex;

				}
				v++;
			}
		
			while(v<4 && pxMesh->PolygonDataPtr[iPoly].VertexID[v]!=0xffff);

			if(iMirror)
			{
				//reverse of double sided polygon , so reverse the vertex ordering.
				usIndexData[uIndex++] = (unsigned short)(iIndexNumber[2]);
				usIndexData[uIndex++] = (unsigned short)(iIndexNumber[1]);
				usIndexData[uIndex++] = (unsigned short)(iIndexNumber[0]);
	
				//take account of quads
				if (pxMesh->PolygonDataPtr[iPoly].IsQuad())
				{
					usIndexData[uIndex++] = (unsigned short)(iIndexNumber[0]);
					usIndexData[uIndex++] = (unsigned short)(iIndexNumber[3]);
					usIndexData[uIndex++] = (unsigned short)(iIndexNumber[2]);
				}
			}
			else
			{
				usIndexData[uIndex++] = (unsigned short)(iIndexNumber[0]);
				usIndexData[uIndex++] = (unsigned short)(iIndexNumber[1]);
				usIndexData[uIndex++] = (unsigned short)(iIndexNumber[2]);
	
				//take account of quads
				if (pxMesh->PolygonDataPtr[iPoly].IsQuad())
				{
					usIndexData[uIndex++] = (unsigned short)(iIndexNumber[2]);
					usIndexData[uIndex++] = (unsigned short)(iIndexNumber[3]);
					usIndexData[uIndex++] = (unsigned short)(iIndexNumber[0]);
				}
			}
			uNumberOfTriangles += pxMesh->PolygonDataPtr[iPoly].IsQuad() ? 2 : 1;
		}
	}


	if(!uNumberOfUniqueVertices)
	{
		//all polygons were marked don't render , or otherwise discarded. RAR 24/05/04
		delete [] usIndexData;
		delete [] pxWorkspaceVertices;
		delete pxVertexHashTable;

		return true;
	}

	
	// test
	for(u_int u=0; u<uIndex; u++)
	{
		Asura_Assert(usIndexData[u]>=0,"");
		Asura_Assert(usIndexData[u]<uNumberOfUniqueVertices,"");
	}

	u_int uNumberOfVertices = uNumberOfUniqueVertices;

	u_short* pusPermutationTable = 0;
	u_short* pusStripIndices = 0;
	u_int uNumberOfStrippedIndices = 0;

    if(bStripify)
	{
    	Tools_TriStripper::Initialise();
	    Tools_TriStripper::SetOutputType( Tools_TriStripper::OUTPUT_TRISTRIP_WITH_RESET_INDICES );

	    uNumberOfStrippedIndices = Tools_TriStripper::CreateStrips( uNumberOfTriangles, usIndexData, &pusStripIndices );

		Tools_TriStripper::ComputeVertexPermutation( uNumberOfStrippedIndices, pusStripIndices, uNumberOfVertices, &pusPermutationTable );

	    Tools_TriStripper::Shutdown();

		u_short usRestartValue;
		const bool bUseRestartIndex = Tools_MeshRestartIndex::IsValidForCurrentPlatform( usRestartValue );
		if( !bUseRestartIndex )
		{
			uNumberOfTriangles = uNumberOfStrippedIndices - 2;
		}

	}
	else
	{
		//not stripifying , so just copy across the triangle indices. RAR 14/08/2007
		uNumberOfStrippedIndices = uNumberOfTriangles*3;
		pusStripIndices = new u_short[uNumberOfStrippedIndices];

		Asura_Mem::Copy(pusStripIndices, usIndexData,sizeof(unsigned short)*uNumberOfStrippedIndices);
	}

	// create new strip for putting data in
	Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip* pxNewStrip = new Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip();
	s_xProcessedStrips.add_entry(pxNewStrip);

	pxNewStrip->m_pxVertices = new Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex[uNumberOfUniqueVertices];
	pxNewStrip->m_pusIndices = pusStripIndices;


	// bounding box data
	//s_pxProcessedStrip[uStrip].m_xBBMin.x = pxMesh->BoundingBox.MinX;
	//s_pxProcessedStrip[uStrip].m_xBBMin.y = pxMesh->BoundingBox.MinY;
	//s_pxProcessedStrip[uStrip].m_xBBMin.z = pxMesh->BoundingBox.MinZ;

	//s_pxProcessedStrip[uStrip].m_xBBMax.x = pxMesh->BoundingBox.MaxX;
	//s_pxProcessedStrip[uStrip].m_xBBMax.y = pxMesh->BoundingBox.MaxY;
	//s_pxProcessedStrip[uStrip].m_xBBMax.z = pxMesh->BoundingBox.MaxZ;

	// copy all the indexed vertex data
	for(u_int u=0; u<uNumberOfUniqueVertices; u++)
	{	
		u_short usIndex = pusPermutationTable ? pusPermutationTable[u] : (u_short) u;
		pxNewStrip->m_pxVertices[u] = pxWorkspaceVertices[usIndex];
	}	

	//s_pxProcessedStrip[uStrip].m_iMaterialID			= pxMesh->TextureDataPtr[0].m_iMaterialID;	
	pxNewStrip->m_uNumberOfIndices	= uNumberOfStrippedIndices;
	pxNewStrip->m_uNumberOfTriangles	= uNumberOfTriangles;
	pxNewStrip->m_uNumberOfVertices	= uNumberOfVertices;

	pxNewStrip->m_usBoneIndex = usBoneIndex;


	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		pxNewStrip->m_auMaterialResponseHashID[0] = s_auCurrentMaterialResponse[0];
		pxNewStrip->m_auMaterialResponseHashID[1] = s_auCurrentMaterialResponse[1];
	#else
		pxNewStrip->m_iMaterialID = s_iCurrentMaterial;
	#endif

	//s_bBeenInitialised = true;

	// BM - 02/04/2003 15:47:16 -  - We only have one texture per Hierarchy at 
	// the moment. If we ever have more than one this will need to be changed.
	// The conversion mechanism means that the exported material index will be 0
	//s_pxProcessedStrip[uStrip].m_iMaterialID = bHasMaterial ? 0 : -1;
	
	// add up totals
	s_uTotalNumberOfTriangles += uNumberOfTriangles;
	s_uTotalNumberOfVertices  += uNumberOfVertices;
	s_uTotalNumberOfIndices   += uNumberOfStrippedIndices;
	s_uTotalNumberOfStrips	  += 1;

	// delete temp data
	delete [] usIndexData;
	delete [] pxWorkspaceVertices;
	delete [] pusPermutationTable;

	delete pxVertexHashTable;

	return true;
}	


void Tools_HierarchyProcessor_MultiMaterial_NormalMapped::Shutdown(void)
{
	if (s_bBeenInitialised)
	{
		// delete all the processed strips
		while(s_xProcessedStrips.size())
		{
			delete s_xProcessedStrips.last_entry();
			s_xProcessedStrips.delete_last_entry();
		}
	
		s_bBeenInitialised = false;
	}
	else
	{
		Asura_Assert( s_bBeenInitialised, "Not yet Initialised!" );
	}	
}

const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip* Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetProcessedStrip(u_int u)
{
	if (s_bBeenInitialised && u < s_uTotalNumberOfStrips)
	{
			return s_xProcessedStrips[u];
	}
	else
	{
		Asura_Assert( s_bBeenInitialised, "Not yet Initialised!" );
		return 0;
	}
}

u_int Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfVertices()
{
	if (s_bBeenInitialised)
	{
			return s_uTotalNumberOfVertices;
	}
	else
	{
		Asura_Assert( s_bBeenInitialised, "Not yet Initialised!" );
		return 0;
	}
}

u_int Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfIndices()
{
	if (s_bBeenInitialised)
	{
			return s_uTotalNumberOfIndices;
	}
	else
	{
		Asura_Assert( s_bBeenInitialised, "Not yet Initialised!" );
		return 0;
	}
}

u_int Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfStrips()
{
	if (s_bBeenInitialised)
	{
			return s_uTotalNumberOfStrips;
	}
	else
	{
		Asura_Assert( s_bBeenInitialised, "Not yet Initialised!" );
		return 0;
	}
}

void Tools_HierarchyProcessor_MultiMaterial_NormalMapped::CalculateTangentSpaceVectors(const class Asura_Mesh* pxMesh, u_int uPoly, Asura_Vector_3& xTangent, Asura_Vector_3& xBinormal)
{
	Asura_Vector_3	axPositions[3];
	Asura_Vector_2	axTextureCoords[3];

	// Initialise the vertex-data...

	for (u_int u = 0; u < 3; u++)
	{
		u_int	uVertexID	= pxMesh->PolygonDataPtr[uPoly].VertexID[u];


		// Fill in the data for this vertex...

		axPositions[u]			= pxMesh->VertexDataPtr[uVertexID].Position;
		axTextureCoords[u].x	= pxMesh->TextureDataPtr[uPoly].U[0][u];
		axTextureCoords[u].y	= 1.0f - pxMesh->TextureDataPtr [uPoly].V[0][u];
	}

	// Calculate the tangent AND binormal...

	Tools_TangentSpace::CalculateTangentAndBinormal	(axPositions, axTextureCoords, xTangent, xBinormal);
}
