// Tools_PSP_Export.cpp
//
// Created by KJFL - 05/02/2004 17:24:29
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.

#include "Core/Asura.h"
// This file's header.
#include "PSP/Tools_PSP_Export.h"

// Asura headers
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Logfile.h"
#include "Animation/Asura_Hierarchy.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "ChunkSystem/Asura_Chunk_ResourceFile.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Light/Asura_GI_Solution.h"
#include "Mesh/Asura_Mesh.h"
#include "PSP/ChunkSystem/Asura_Chunk_ResourceFile_PSP.h"
#include "PSP/Rendering/Asura_PSP_VertexFormat.h"

// Tools headers
#include "BotanicalCreation/Tools_BotanicalCreation_Rendering.h"
#include "Botanicals/Asura_Botanical_Game_Render_Type.h"
#include "ImageProcessing/Tools_PSP_Texture_Writer.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "MeshProcessing/Tools_EnvironmentProcessor.h"
#include "MeshProcessing/Tools_SmoothSkin_Processor.h"
#include "MeshProcessing/Tools_SmoothSkin_Processor_PSP.h"
#include "MeshProcessing/Tools_ObjectProcessor.h"
#include "MeshProcessing/Tools_HierarchyProcessor.h"
#include "PC/FileSystem/Tools_FileSystem.h"
#include "Rendering/Asura_Direct3D.h"
#include "Skybox/Tools_Mode_Skybox.h"

#if defined( ASURA_USE_BOTANICALS )
#include "Botanicals/Asura_Botanicals_InstanceManager.h"
#include "Botanicals/Asura_Botanicals_TemplateManager.h"
#include "PSP/Botanicals/Asura_PSP_Botanicals_Shared.h"
#include "Shapes/Asura_Shape_System.h"

#endif // #if defined( ASURA_USE_BOTANICALS )

/* Enaqble debug, it is value, not definition, based */
#define PG_STRIP_DEBUG 0

/* Keep whole long strips or split them into substrips */
static const bool g_bKeepWholeStrips = false;

/*
	Thresholds for the substrip splitting test.
	'g_uSubCountRealMin'
		substrip must contain at least these many
		''real'' indices.
	'g_uSubCountRealMax'
		split if substrip contains more than these
		many ''real'' indices.
	'g_fDiagSqMax'
		split if substrip's AABB has a diagonal whose
		square length is greater than this.
*/

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

static const u_int g_uSubCountRealMin = 20;
static const u_int g_uSubCountRealMax = 80;
static const float g_fDiagSqMax = 90.0f;

// (SB - 18/01/07) Default PSP environment vertex format
unsigned short Tools_PSP_Export::s_usEnvironmentVertexFormat = ASURA_SCEGU_TEXTURE_USHORT|ASURA_SCEGU_COLOR_PF5650|ASURA_SCEGU_NORMAL_SHORT|ASURA_SCEGU_VERTEX_FLOAT;
//unsigned short Tools_PSP_Export::s_usEnvironmentVertexFormat = ASURA_SCEGU_TEXTURE_USHORT|ASURA_SCEGU_COLOR_PF8888|ASURA_SCEGU_VERTEX_FLOAT;
unsigned short Tools_PSP_Export::s_usSecondPassEnvironmentVertexFormat = ASURA_SCEGU_TEXTURE_USHORT|ASURA_SCEGU_COLOR_PF4444|ASURA_SCEGU_NORMAL_SHORT|ASURA_SCEGU_VERTEX_FLOAT;

// (SB - 14/03/07) Flags for the chunk
unsigned int Tools_PSP_Export::s_uCharacterExportFlags = Tools_PSP_Export::CharacterBitOne;
unsigned int Tools_PSP_Export::s_uHierarchyExportFlags = 0;

// Should use Asura_String when writing chunks streams, because the stream
// takes care of the 4-byte padding for us

const Asura_String xBOTANICALS_RESOURCE_FILENAME( "Botanicals" );
static const char g_szEnvResourceFilename[] = "Env";

/*
	We may want to re-enable, or make optional - depends on speed/memory
	trade-off. Latest informatiom from KOR is that it is not worth it.
	Also, the substripping does 1 index/1 vertex, so not very useful for
	that, or even tested.
	KOR - 07/04/2006 - bear in mind that the guardband clipped assumes non-indexed
	data, and will need to be re-written if we ever switch back to indexed.
*/
#if 0
	#define INDEXED_MODE (1)
#endif

struct Tools_Substrip
{
	u_int m_uStart, m_uCount;
};

/*
	Given a strip compute the number and optionally store the start
	and element count of substrips arising out of some kind of splitting
	test.
*/
static void Tools_PSP_Export_StripScan(
	const Tools_EnvironmentProcessor_Strip* const pxStrip,
	Tools_Substrip* const pxSub,const u_int uSubSize,u_int* const puSubCount
)
{
	const u_int uStripIndexCount = pxStrip->m_uNumberOfIndices;

#if PG_STRIP_DEBUG
	Asura_OutputToDebugger_Retail((
		"scanning %3d,%-2d: indices %u, max substrips %u\n",
		pxStrip->m_uModule,pxStrip->m_iMaterialID,
		uStripIndexCount,uSubSize
	));
#endif

	if (uStripIndexCount == 0)
	{
		*puSubCount = 0;
		return;
	}

	Asura_Assert(uStripIndexCount >= 3,
		"Valid strips must have at least 3 indices");

	const u_short* const pusIn = pxStrip->m_pusIndices;

	/* Substrips discovered so far */
	u_int uSubCount = 0;

	/* Current substrip offset, elements */
	u_int uSubThisStart;
	u_int uSubThisCount;
	u_int uSubThisCountGlue;

	/* State of the previous vertex, if any */
	u_int uIndexPrev;
	bool bGluePrev;

	const Tools_EnvironmentProcessor_Vertex* pxVertPrev;
	Asura_AVector3 xVertPrevPos;

	/* Accumulated AABB this substrip */
	Asura_AVector3 xSubBoxMin;
	Asura_AVector3 xSubBoxMax;

	for (u_int v = 0; v < uStripIndexCount; v++)
	{
		if (v == 0 || uSubThisCount == 0)
		{
			uIndexPrev = pxStrip->m_pusIndices[v];
			bGluePrev = false;

			pxVertPrev = &pxStrip->m_pxVertices[uIndexPrev];
			xVertPrevPos = Asura_AVector3(pxVertPrev->m_xPosition);

			xSubBoxMin = Asura_AVector3(xVertPrevPos);
			xSubBoxMax = Asura_AVector3(xVertPrevPos);

			uSubThisStart = v;
			uSubThisCount = 1;
			uSubThisCountGlue = 0;
		}
		else
		{
			const u_int uIndexThis = pusIn[v];
			const bool bGlueThis = uIndexThis == uIndexPrev;

			const Tools_EnvironmentProcessor_Vertex* const pxVertThis
				= &pxStrip->m_pxVertices[uIndexThis];
			const Asura_AVector3 xVertThisPos(pxVertThis->m_xPosition);

			xSubBoxMin.SelectLT(xVertThisPos,xSubBoxMin,
				xVertThisPos,xSubBoxMin);
			xSubBoxMax.SelectGT(xVertThisPos,xSubBoxMax,
				xVertThisPos,xSubBoxMax);

			const Asura_AVector3 xSubBoxExt = xSubBoxMax - xSubBoxMin;
			const float fDiagSq = xSubBoxExt.Magnitude();
			const u_int uSubThisCountReal = uSubThisCount-uSubThisCountGlue;

			const bool bDoSplit =
				/* only split just before glue, and if not too short */
				((bGlueThis && !bGluePrev)
					&& (uSubThisCountReal >= g_uSubCountRealMin))
				/* split if the AABB is big or the index count is too big */
				&& ((fDiagSq > g_fDiagSqMax)
					|| (uSubThisCountReal > g_uSubCountRealMax));

			if (!bDoSplit)
			{
				/* Continue accumulating */
				uSubThisCount += 1;
				uSubThisCountGlue += static_cast<u_int>(bGlueThis);

				uIndexPrev = uIndexThis;
				bGluePrev = bGlueThis;

				pxVertPrev = pxVertThis;
				xVertPrevPos = xVertThisPos;
			}
			else
			{
			#if defined ASURA_ASSERT
				Asura_Assert(
					v > 0 && pusIn[v-1] == pusIn[v],
					"When splitting, must be at first glue"
				);
			#endif

			#if PG_STRIP_DEBUG
				if (pxSub == 0)
				{
					Asura_OutputToDebugger_Retail((
						"  split: start %u count %u diagsq %f realcount %u"
						" (%d: %d, %d: %d, %d: %d, %d: %d)\n",
						uSubThisStart,uSubThisCount,fDiagSq,uSubThisCountReal,
						v-1,pusIn[v-1],v,pusIn[v],v+1,pusIn[v+1],v+2,pusIn[v+2]
					));
				}
			#endif
				if (pxSub != 0)
				{
					Tools_Substrip* const pxS = &pxSub[uSubCount];
					pxS->m_uStart = uSubThisStart;
					pxS->m_uCount = uSubThisCount;
				}

				uSubCount++;

				u_int uSkip = 0;

				Asura_Assert(v >= 2,"Must be at least on 3rd index");
				Asura_Assert(pusIn[v-2] != pusIn[v-1],"non glue preceding");
				Asura_Assert(pusIn[v-1] == pusIn[v],"this is glue");
				Asura_Assert(v+2 < uStripIndexCount,"not end of strip");

				if (uStripIndexCount && pusIn[v+1] == pusIn[v+2])
				{
					/*
						We are on the first of two glue indices, as in
							0 1 2 2* 3 3 4 5
						and the next iteration should start at the second
						'3'.
					*/
					uSkip += 1;
				}
				else
				{
					/*
						We are on the one glue index, but that is also
						the starting index of the next triangle, as in
							0 1 2 2* 3 4
						but on the next iteration we need to be still
						on this index.
					*/
					uSkip -= 1;
				}

				/*
					In a CW strip even triangles are CW and odd are CCW.
					If we are splitting just before an odd triangle, leave
					a ''degenerate'' first tringle which is then CW. Ugly.
				*/
				if ((v & 1) == 1)
				{
					uSkip -= 1;

				#if 0 && PG_STRIP_DEBUG
					if (pxSub == 0)
					{
						Asura_OutputToDebugger_Retail((
							"  split: at %d, starting at %d instead of %d\n",
							v,v+uSkip+1,v+uSkip+1+1
						));
					}
				#endif
				}

				v += uSkip; /* plus 1 on loop */
				uSubThisCount = 0;
			}
		}
	}

	/*
		Any incomplete last substrip has to be completed.
	*/
	if (uSubThisCount != 0)
	{
		if (pxSub != 0)
		{
			Tools_Substrip* const pxS = &pxSub[uSubCount];
			pxS->m_uStart = uSubThisStart;
			pxS->m_uCount = uSubThisCount;
		}

		uSubCount++;
	}

#if PG_STRIP_DEBUG
	Asura_OutputToDebugger_Retail((
		"  last: start %d count %d\n",
		uSubThisStart,uSubThisCount
	));
#endif

	*puSubCount = uSubCount;
}

/*
	Copy vertices comprising various substrips from one strip
	to separate strips.
*/
static void Tools_PSP_Export_StripCopy(
	const Tools_EnvironmentProcessor_Strip *const pxUnsplit,
	const Tools_Substrip* const paxSubstrips,const u_int uSubstripsCount,
	Tools_EnvironmentProcessor_Strip *const pxSplits,
	u_int* const puSubstripsIndexCount,u_int* const puSubstripsVertexCount
)
{
	u_int uSubstripsIndexCount = 0;
	u_int uSubstripsVertexCount = 0;
	u_int uSubstripsTriCount = 0;

	for (u_int s = 0; s < uSubstripsCount; s++)
	{
		const u_int uStart = paxSubstrips[s].m_uStart;
		const u_int uCount = paxSubstrips[s].m_uCount;

		Tools_EnvironmentProcessor_Strip *const pxSubstrip = &pxSplits[s];

		pxSubstrip->m_uModule = pxUnsplit->m_uModule;
		pxSubstrip->m_iMaterialID = pxUnsplit->m_iMaterialID;
		pxSubstrip->m_iUnalteredMaterialID = pxUnsplit->m_iUnalteredMaterialID;

		uSubstripsIndexCount += (pxSubstrip->m_uNumberOfIndices = uCount);
		uSubstripsVertexCount += (pxSubstrip->m_uNumberOfVertices = uCount);
		uSubstripsTriCount += (pxSubstrip->m_uNumberOfTriangles = uCount-2);
		
		/*
			We should really be sharing the vertex array among all
			strips derived from the same mesh. One day...
		*/
		pxSubstrip->m_pusIndices = new u_short[uCount];
 		pxSubstrip->m_pxVertices = new Tools_EnvironmentProcessor_Vertex[uCount];

		for (u_int v = 0; v < uCount; v++)
		{
			const u_int uIndex = pxUnsplit->m_pusIndices[uStart+v];
			pxSubstrip->m_pxVertices[v] = pxUnsplit->m_pxVertices[uIndex];
			pxSubstrip->m_pusIndices[v] = static_cast<u_short>(v);
		}
	}

	*puSubstripsIndexCount = uSubstripsIndexCount;
	*puSubstripsVertexCount = uSubstripsVertexCount;
}

/*
	Given a pointer to a pointer to an array of strips, replace that
	array with one with the same or more strips. Existing strips are
	split if they are too long and/or if their AABB becomes too big, or
	other metrics that help with culling and avoiding having too many
	degenerate triangles.
*/
static void Tools_PSP_Export_SplitCreate(
	const Tools_EnvironmentProcessor_Strip* const pxUnsplit,
	const u_int uUnsplitCount,
	Tools_EnvironmentProcessor_Strip** const ppxSplit,
	u_int* const puSplitCount,
	u_int* const puIndexCount,u_int* const puVertexCount
)
{
	/*
		For each unsplit strip, the number of strips in which it will be
		split.
	*/
	u_int* const uUnsplitSubCount = new u_int[uUnsplitCount];

	/* Total number of resulting split strips, >= 'uUnsplitCount' */
	u_int uSplitSize = 0;

	/*
		First we need to estimate how many output strips we might have.
	*/
	for (u_int s = 0; s < uUnsplitCount; s++)
	{
		Tools_PSP_Export_StripScan(&pxUnsplit[s],0,0,&uUnsplitSubCount[s]);
		uSplitSize += uUnsplitSubCount[s];
	}

#if PG_STRIP_DEBUG
	Asura_OutputToDebugger_Retail((
		"unsplit count: %d, split count estimate: %d\n",
		uUnsplitCount,uSplitSize
	));
#endif

	/*
		The output array of split strips.
	*/
	Tools_EnvironmentProcessor_Strip* const pxSplit
		= new Tools_EnvironmentProcessor_Strip[uSplitSize];
	u_int uSplitCount = 0;
	u_int uSplitIndexCount = 0;
	u_int uSplitVertexCount = 0;

	/*
		Now we can take each input strip, and split it into substrips
		that get appended to the output array of split strips.
	*/
	for (u_int s = 0; s < uUnsplitCount; s++)
	{
		const u_int uSubstripsSize = uUnsplitSubCount[s];
		Tools_Substrip* const paxSubstrips
			= new Tools_Substrip[uSubstripsSize];
		u_int uSubstripsCount;
		u_int uSubstripsIndexCount;
		u_int uSubstripsVertexCount;

		Tools_PSP_Export_StripScan(&pxUnsplit[s],
			paxSubstrips,uSubstripsSize,
			&uSubstripsCount
		);

		Asura_Assert(uSubstripsCount == uSubstripsSize,
			"count of substrips must be equal to estimated");

		Tools_PSP_Export_StripCopy(&pxUnsplit[s],
			paxSubstrips,uSubstripsCount,&pxSplit[uSplitCount],
			&uSubstripsIndexCount,&uSubstripsVertexCount
		);

		uSplitCount += uSubstripsCount;
		uSplitIndexCount += uSubstripsIndexCount;
		uSplitVertexCount += uSubstripsVertexCount;

		delete [] paxSubstrips;
	}

	delete[] uUnsplitSubCount;

	*ppxSplit = pxSplit;
	*puSplitCount = uSplitCount;
	*puIndexCount = uSplitIndexCount;
	*puVertexCount = uSplitVertexCount;
}

static void Tools_PSP_Export_SplitDelete(
	Tools_EnvironmentProcessor_Strip* const pxSplit,u_int uSplitCount
)
{
	(void) uSplitCount;
	delete[] pxSplit;
}

/*
	Write the given strips, containing the given number of indices
	and vertices, to the given file handle.

	Note: Indexed mode probably is a bit broken.
*/
void Tools_PSP_Export_EnvWrite(const HANDLE hOutput,
	const Tools_EnvironmentProcessor_Strip* const pxStrips,
	const u_int uNumberOfStrips,
	u_int uNumberOfIndices,u_int uNumberOfVertices
)
{
#define WRITEOUTDATA(a,b) WriteFile( hOutput, a, b, &bytesWritten, 0 );

	const u_int uNumberOfMeshes
		= Tools_EnvironmentProcessor::GetNumberOfProcessedMeshes();

	const Tools_EnvironmentProcessor_Strip** const ppxSecondPassStrips
		 = new const Tools_EnvironmentProcessor_Strip*[uNumberOfStrips];
	u_int uNumberOfSecondPassStrips	= 0;
	u_int uFileSize = 0;

	// Count the dual-pass strips...
	for(u_int u = 0; u < uNumberOfStrips; u++)
	{
		const Tools_EnvironmentProcessor_Strip&	xStrip = pxStrips[u];
	#if 0 && PG_STRIP_DEBUG
		{
			Asura_OutputToDebugger_Retail((
				"strip %3u: Indices %u, Triangles %u\n",
				u,xStrip.m_uNumberOfIndices,xStrip.m_uNumberOfTriangles
			));
		}
	#endif

		if(Asura_Material_System::IsMaterialDualTexture(xStrip.m_iUnalteredMaterialID))
		{
			ppxSecondPassStrips[uNumberOfSecondPassStrips++] = &xStrip;
			uNumberOfIndices								+= xStrip.m_uNumberOfIndices;
#ifdef INDEXED_MODE
			uNumberOfVertices								+= xStrip.m_uNumberOfVertices;
#endif
		}
	}
	
	// KJFL - 2004-02-05 17:52:58 - okay, for the PSP we'll be exporting
	//
	// Number Of Meshes (u_int)
	uFileSize += sizeof(u_int);

	// Total number of strips
	// Total number of dual-pass strips
	uFileSize += sizeof(u_int) * 2;

	// Total number of vertices
	// Total number of indices
	uFileSize += sizeof(u_int) * 2;

	// Each strip will contain:
	// NumberOfIndices (u_int)
	// StartIndex (u_int)
 	// StartVertex (u_int)
	// MaterialID (int)
	// Module (u_int)
	uFileSize += (sizeof(u_int) * 5) * (uNumberOfStrips + uNumberOfSecondPassStrips);

	// Need to work out the size of vertices per strip for variable vertex format!
	//int iVertexSize = Asura_PSP_VertexFormat::CalculateVertexSize(Tools_PSP_Export::GetEnvironmentVertexFormat());
	for (u_int i = 0; i < uNumberOfStrips; i++)
	{
		uFileSize += Asura_PSP_VertexFormat::CalculateVertexSize(Tools_PSP_Export::GetEnvironmentVertexFormat()) * pxStrips[i].m_uNumberOfIndices;
	}
	for (u_int i = 0; i < uNumberOfSecondPassStrips; i++)
	{
		uFileSize += Asura_PSP_VertexFormat::CalculateVertexSize(Tools_PSP_Export::GetSecondPassEnvironmentVertexFormat()) * ppxSecondPassStrips[i]->m_uNumberOfIndices;
	}

	// KJFL - 2004-02-05 17:52:54 - chunk size is all the data, 'filename' & header
	int uChunkSize = uFileSize + 4 + sizeof(Asura_Chunk_ResourceFile);

	Asura_Chunk_ResourceFile xResourceFile;

	xResourceFile.Create
	(
		uChunkSize,
		0,
		ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
		ASURA_RESOURCEFILE_TYPE_PSP_ENVIRONMENT,
		uFileSize,
		1 // (SB - 18/01/07) Version 1 adds variable vertex format
	);

	unsigned long bytesWritten;

	WRITEOUTDATA( &xResourceFile, sizeof(Asura_Chunk_ResourceFile) );
	WRITEOUTDATA( g_szEnvResourceFilename, 4 );

	WRITEOUTDATA( &uNumberOfMeshes, sizeof(u_int) );
	WRITEOUTDATA( &uNumberOfStrips, sizeof(u_int) );
	WRITEOUTDATA( &uNumberOfSecondPassStrips, sizeof(u_int) );
#ifdef INDEXED_MODE
	WRITEOUTDATA( &uNumberOfVertices, sizeof(u_int) );
	WRITEOUTDATA( &uNumberOfIndices, sizeof(u_int) );
#else
	// One vertex for every index
	WRITEOUTDATA( &uNumberOfIndices, sizeof(u_int) );
	// No indices
	u_int uZero = 0;
	WRITEOUTDATA( &uZero, sizeof(u_int) );
#endif

#if 0
	// KJFL - 2004-02-05 17:52:48 - all mesh descriptions in one go, so
	// they can be loaded into one memory block
	u_int uStripStart = 0;

	for (u_int u=0; u<uNumberOfMeshes; u++)
	{
		const Tools_EnvironmentProcessor_Mesh* pxMesh = Tools_EnvironmentProcessor::GetProcessedMesh(u);

		WRITEOUTDATA( &(pxMesh->m_uNumberOfStrips), sizeof(u_int) );
		WRITEOUTDATA( &(uStripStart), sizeof(u_int) );

		uStripStart += pxMesh->m_uNumberOfStrips;
	}
#endif

	// KJFL - 2004-02-05 17:52:40 - all strip descriptions in one go, so
	// they can be loaded into one memory block
	u_int uStartIndex = 0;
	u_int uCurrentVertex = 0;
	
	// ART - 23-01-2006 - Adding support for dual-pass texturing...
	for (u_int s=0; s<(uNumberOfStrips + uNumberOfSecondPassStrips); s++)
	{
		const bool								bSecondPass	= (s >= uNumberOfStrips);
		const Tools_EnvironmentProcessor_Strip&	xStrip		= (bSecondPass)
															? *ppxSecondPassStrips[s - uNumberOfStrips]
															: pxStrips[s];

		// (SB - 18/01/07) Exporting vertex format in high order 16 bits of m_uNumberOfIndices, which shouldn't be greater than 65536 anyway
		{
			unsigned short usTemp;
			// NumberOfIndices (u_short)
			usTemp = static_cast<unsigned short>(xStrip.m_uNumberOfIndices);
			WRITEOUTDATA( &(usTemp), sizeof(u_short) );
			// Vertex format (u_short)
			if (bSecondPass)
			{
				usTemp = Tools_PSP_Export::GetSecondPassEnvironmentVertexFormat();
			}
			else
			{
				usTemp = Tools_PSP_Export::GetEnvironmentVertexFormat();
			}
			WRITEOUTDATA( &(usTemp), sizeof(u_short) );
		}
		// StartIndex (u_int)
		WRITEOUTDATA( &(uStartIndex), sizeof(u_int) );
 		// StartVertex (u_int)
		WRITEOUTDATA( &(uCurrentVertex), sizeof(u_int) );
		// MaterialID (int)
		WRITEOUTDATA( &(xStrip.m_iMaterialID), sizeof(int) );
		// Module number (u_int)
		WRITEOUTDATA( &(xStrip.m_uModule), sizeof(u_int) );

		uStartIndex += xStrip.m_uNumberOfIndices;
		uCurrentVertex += xStrip.m_uNumberOfVertices;
	}

	const bool bColourStrips = Tools_Level_Export::ShouldDebugColourSubstrips();
	unsigned int uDebugColour;
	// KJFL - 2004-02-05 17:52:08 -  all the vertex data in one go
	// ART - 23-01-2006 - Adding support for dual-pass texturing...
	for (u_int s=0; s<(uNumberOfStrips + uNumberOfSecondPassStrips); s++)
	{
		const bool								bSecondPass	= (s >= uNumberOfStrips);
		const Tools_EnvironmentProcessor_Strip&	xStrip		= (bSecondPass)
															? *ppxSecondPassStrips[s - uNumberOfStrips]
															: pxStrips[s];

		// Colour strip debugger uses a different colour every strip
		if (bColourStrips)
		{
			uDebugColour = Asura_Random::GetIntU();
		}

#ifdef INDEXED_MODE
		for (u_int v=0; v<xStrip.m_uNumberOfVertices; v++)
		{
#else
		for (u_int uIndex = 0; uIndex < xStrip.m_uNumberOfIndices; ++uIndex)
		{
			u_int v = xStrip.m_pusIndices[uIndex];
#endif
			// KOR - 16/08/2004 - converted from float to unsigned short
			// The standard multiplier seems to be 32768.
			// However, to allow texture repeating, we scale the coordinates by 128 on the PSP,
			// and correspondingly divide the multiplier here. 32768 / 128 = 256
			Asura_Assert(xStrip.m_pxVertices[v].m_fU >= 0.f && xStrip.m_pxVertices[v].m_fV >= 0.f, "PSP cannot currently handle negative texture co-ords.");

			size_t xSize;

			// Colour strip debugger needs to change colour between substrips ie. degenerates
			if (bColourStrips && uIndex && xStrip.m_pusIndices[uIndex] == xStrip.m_pusIndices[uIndex - 1])
			{
				uDebugColour = Asura_Random::GetIntU();
			}

			unsigned short usVertexFormat;
			if (bSecondPass)
			{
				usVertexFormat = Tools_PSP_Export::GetSecondPassEnvironmentVertexFormat();
			}
			else
			{
				usVertexFormat = Tools_PSP_Export::GetEnvironmentVertexFormat();
			}

			// Write out UVs if required
			if ((xSize = Asura_PSP_VertexFormat::GetSizeOfTexCoord(usVertexFormat)) != 0)
			{
				float fInU;
				float fInV;
				unsigned char aucOutU[4];
				unsigned char aucOutV[4];

				if (bSecondPass)
				{
					fInU = xStrip.m_pxVertices[v].m_fU2;
					fInV = xStrip.m_pxVertices[v].m_fV2;
				}
				else
				{
					fInU = xStrip.m_pxVertices[v].m_fU;
					fInV = xStrip.m_pxVertices[v].m_fV;
				}

				Asura_PSP_VertexFormat::ConvertTexcoordToSpecifiedFormat(aucOutU, fInU, usVertexFormat);
				Asura_PSP_VertexFormat::ConvertTexcoordToSpecifiedFormat(aucOutV, fInV, usVertexFormat);
				WRITEOUTDATA( aucOutU, xSize );
				WRITEOUTDATA( aucOutV, xSize );
			}

			// Write out colour data if required
			if ((xSize = Asura_PSP_VertexFormat::GetSizeOfColour(usVertexFormat)) != 0)
			{
				unsigned int uInRGBA;
				// Colour strip debugger needs to change colour
				if(bColourStrips)
				{
					uInRGBA = uDebugColour & 0x00ffffff;
				}
				else
				{
					uInRGBA = xStrip.m_pxVertices[v].m_uDiffuseRGBA;
					unsigned int uAlpha = (uInRGBA & 0xff000000) >> 24;
					uAlpha = 255 - uAlpha;
					uInRGBA = (uInRGBA & 0x00ffffff) | (uAlpha << 24);
				}
				unsigned char aucOutRGBA[4];
				Asura_PSP_VertexFormat::ConvertVertexColourToSpecifiedFormat(aucOutRGBA, uInRGBA, usVertexFormat);
				WRITEOUTDATA( aucOutRGBA, xSize );
			}

			// Write out normal if required
			if ((xSize = Asura_PSP_VertexFormat::GetSizeOfNormalComponent(usVertexFormat)) != 0)
			{
				float afNormal[3];
				afNormal[0] = xStrip.m_pxVertices[v].m_xNormal.x;
				afNormal[1] = xStrip.m_pxVertices[v].m_xNormal.y;
				afNormal[2] = xStrip.m_pxVertices[v].m_xNormal.z;
				unsigned char aucNormal[3][4];
				Asura_PSP_VertexFormat::ConvertNormalComponentToSpecifiedFormat(aucNormal[0], afNormal[0], usVertexFormat);
				Asura_PSP_VertexFormat::ConvertNormalComponentToSpecifiedFormat(aucNormal[1], afNormal[1], usVertexFormat);
				Asura_PSP_VertexFormat::ConvertNormalComponentToSpecifiedFormat(aucNormal[2], afNormal[2], usVertexFormat);
				WRITEOUTDATA( aucNormal[0], xSize );
				WRITEOUTDATA( aucNormal[1], xSize );
				WRITEOUTDATA( aucNormal[2], xSize );
			}

			// Write out vertex position
			{
				float afPosition[3];
				afPosition[0] = xStrip.m_pxVertices[v].m_xPosition.x;
				afPosition[1] = xStrip.m_pxVertices[v].m_xPosition.y;
				afPosition[2] = xStrip.m_pxVertices[v].m_xPosition.z;
				unsigned char aucPosition[3][4];
				Asura_PSP_VertexFormat::ConvertVertexComponentToSpecifiedFormat(aucPosition[0], afPosition[0], usVertexFormat);
				Asura_PSP_VertexFormat::ConvertVertexComponentToSpecifiedFormat(aucPosition[1], afPosition[1], usVertexFormat);
				Asura_PSP_VertexFormat::ConvertVertexComponentToSpecifiedFormat(aucPosition[2], afPosition[2], usVertexFormat);
				xSize = Asura_PSP_VertexFormat::GetSizeOfVertexComponent(usVertexFormat);
				WRITEOUTDATA( aucPosition[0], xSize );
				WRITEOUTDATA( aucPosition[1], xSize );
				WRITEOUTDATA( aucPosition[2], xSize );
			}
		}
	}

#ifdef INDEXED_MODE
	// KJFL - 2004-02-05 17:53:46 - and then all the index data in one go
	for (u_int s=0; s<pxMesh->m_uNumberOfStrips; s++)
	{
		const Tools_EnvironmentProcessor_Mesh* pxMesh = Tools_EnvironmentProcessor::GetProcessedMesh(xStrip.m_uModule);
		const Tools_EnvironmentProcessor_Strip& xStrip = pxMesh->m_pxStrips[s];

		for (u_int v=0; v<xStrip.m_uNumberOfIndices; v++)
		{
			u_short usIndex = static_cast<u_short>(xStrip.m_pusIndices[v]);
			WRITEOUTDATA( &usIndex, sizeof(u_short) );
		}
	}
#endif

	delete[](ppxSecondPassStrips);

#undef WRITEOUTDATA
}

/**
 * Write to a file handle a binary representation of the environment.
 *
 * For every module and its mesh, write out the related strips in a
 * suitable order. By default each mesh is written out as a single
 * large strip (actually oen per material).
 */
void Tools_PSP_Export::ExportEnvironment(HANDLE hOutput)
{
	// RDM - 15/06/06 - testing removal of duplicate vertices
	Tools_EnvironmentProcessor::Initialise( true );

	if (Tools_Level_Export::IsAbortFlagSet())
	{
		return;
	}

	const u_int uStripCount
		= Tools_EnvironmentProcessor::GetTotalNumberOfStrips();
	const u_int uIndexCount
		= Tools_EnvironmentProcessor::GetTotalNumberOfIndices();
	/* This only really needed '#ifdef INDEXED_MODE' */
	const u_int uVertexCount
		= Tools_EnvironmentProcessor::GetTotalNumberOfVertices();

	const Tools_EnvironmentProcessor_Strip* const pxStrips
		= Tools_EnvironmentProcessor::GetStripsSortedByMaterial();

	#if PG_STRIP_DEBUG
		Asura_OutputToDebugger_Retail((
			"env export: strips %u, indices %u\n",
			uStripCount,uIndexCount
		));
	#endif

	/*
		Because of various programming/art issues, we may need smaller
		strips than those that come out naturally of a module's mesh.
	*/
	if (g_bKeepWholeStrips)
	{
	#if PG_STRIP_DEBUG
		Asura_OutputToDebugger_Retail((
			"unstripped: strips %u, indices %u, vertices %u\n",
			uStripCount,uIndexCount,uVertexCount
		));
	#endif
		Tools_PSP_Export_EnvWrite(hOutput,pxStrips,uStripCount,
			uIndexCount,uVertexCount);
	}
	else
	{
		Tools_EnvironmentProcessor_Strip* pxSplit;
		u_int uSplitCount,uSplitIndexCount,uSplitVertexCount;

		Tools_PSP_Export_SplitCreate(pxStrips,uStripCount,
			&pxSplit,&uSplitCount,&uSplitIndexCount,&uSplitVertexCount);

	#if PG_STRIP_DEBUG
		Asura_OutputToDebugger_Retail((
			"split: strips %u, indices %u, vertices %u\n",
			uSplitCount,uSplitIndexCount,uSplitVertexCount
		));
	#endif
		Tools_PSP_Export_EnvWrite(hOutput,pxSplit,uSplitCount,
			uSplitIndexCount,uSplitVertexCount);

		Tools_PSP_Export_SplitDelete(pxSplit,uSplitCount);
	}
		

	Tools_EnvironmentProcessor::Shutdown();
}

bool Tools_PSP_Export::ExportTexture( const char* szInputName, const char* szOutputName, const int iTextureIndex, const TextureConversionData& xConvertData)
{
	bool bSuccess = false;

	// check output filename is valid
 	Asura_Assertf(!Tools_FileSystem::IsReadOnly(szOutputName),("%s is readonly. It will be made writeable.",szOutputName));
	Tools_FileSystem::MakeWriteable(szOutputName);

	
	// attempt to load in the texture to an 8888 format
	D3DXIMAGE_INFO		xImageInfo;
	Direct3D_Texture*	pxTexture = 0;
	Direct3D_Surface*	pxSurface = 0;
	D3DSURFACE_DESC		xSurfaceDesc;
	
	HRESULT xResult =	D3DXCreateTextureFromFileExA
					(
						Asura_Direct3D::D3DDevice,
						szInputName,
						D3DX_DEFAULT,//width
						D3DX_DEFAULT,//height
						D3DX_FROM_FILE,//MipLevels
						0,//Usage
						D3DFMT_A8R8G8B8,//format
						D3DPOOL_SYSTEMMEM,//Pool
						D3DX_FILTER_NONE,//Filter
						D3DX_FILTER_NONE,//MipFilter
						0,//ColorKey
						&xImageInfo,//pSourceInfo
						0,//pPalette
						&pxTexture
					);	

	if(FAILED(xResult))
	{
		Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error %x loading %s",xResult,szInputName);
		return false;
	}

	Tools_PSP_Texture_Writer xWriter;
	
	//Copy each mip level into the image writer
	for(u_int uMip=0; uMip < xImageInfo.MipLevels; uMip++)
	{
	
		//get the surface
		xResult = pxTexture->GetSurfaceLevel(uMip,&pxSurface);
		if(FAILED(xResult))
		{
			Asura_Assert(0,"Failed to get texture surface");
			goto Clearup;
		}

		//get the surface description
		xResult = pxSurface->GetDesc(&xSurfaceDesc);
		if(FAILED(xResult))
		{
			Asura_Assert(0,"Failed to get texture surface description");
			goto Clearup;
		}

		//check its in the format requested
		if(xSurfaceDesc.Format != D3DFMT_A8R8G8B8)
		{
			Asura_Assert(0,"Failed to load texture as D3DFMT_A8R8G8B8");
			goto Clearup;
		}

		//warn if width/height have changed
		if(uMip == 0)
		{
			if(xImageInfo.Width!=xSurfaceDesc.Width || xImageInfo.Height!=xSurfaceDesc.Height)
			{
				Tools_Level_Export::AddMessage(Tools_Level_Export::MT_WARNING,"Image resized from %dx%d to %dx%d by loaders",xImageInfo.Width,xImageInfo.Height,xSurfaceDesc.Width,xSurfaceDesc.Height);
			}
		}



		{
			// lock the surface so we can access the pixel data
			D3DLOCKED_RECT xRect;
			xResult = pxSurface->LockRect(&xRect,0,D3DLOCK_READONLY);
			if(FAILED(xResult))
			{
				Asura_Assert( false, "Failed to lock the texture surface" );
				goto Clearup;
			}
	
			xWriter.Initialise(uMip, xSurfaceDesc.Width, xSurfaceDesc.Height, xRect.Pitch, xRect.pBits);

			// unlock it
			xResult = pxSurface->UnlockRect();
			Asura_Assert( !FAILED(xResult), "Error unlocking surface" );

			if(pxSurface) pxSurface->Release();
			pxSurface = 0;

		}
	}
	
	bSuccess = xWriter.WriteOutFile(szOutputName, xConvertData, Tools_SkyboxOverlay::IsSkyboxTexture(iTextureIndex));

Clearup:
	if(pxSurface) pxSurface->Release();
	if(pxTexture) pxTexture->Release();

	return bSuccess;
}

bool Tools_PSP_Export::ExportCharacter( HANDLE hOutput, Asura_Hierarchy_Skin* pxSkin )
{
	Tools_SmoothSkin_Processor xProcessor;
	//turn this off for now on psp, as renderer doesn't support it.
	xProcessor.SetIgnoreIDFromPolyFlags(true);
    if( !xProcessor.Initialise(pxSkin) ) { return false; }

	Tools_SmoothSkin_PSP_Processor xPSPProcessor;
    if( !xPSPProcessor.Initialise(&xProcessor) ) { return false; }

	const u_int uTotalNumberOfVertices = xPSPProcessor.GetTotalNumberOfIndices();

	const u_int uNumberOfGroups = xPSPProcessor.GetNumberOfGroups();

	#define WRITEOUTDATA(a,b) WriteFile( hOutput, a, b, &bytesWritten, 0 );
	
	// multiply by 4 to get size in bytes
	u_int uFileNameSize = (pxSkin->Name.GetSizeInInts()<<2);
	
	u_int uFileSize = 0;
		
	// PSP smooth skin format
	// 
	// char * length rounded up to nearest 4 bytes		- Name of skin (size rounded up to nearest 4 bytes)
	// u_int											- Total number of vertices
	// u_int											- Number of groups
	uFileSize += uFileNameSize;
	uFileSize += (sizeof(u_int) * 2);
	//
	// Per group:
	// int												- Material
	// u_int											- Number of vertices
	// u_int											- Start vertex offset
	// u_char[4]										- Bone ids

	uFileSize += (sizeof (u_int) * 4) * uNumberOfGroups;
	//
	// Tools_SmoothSkin_PSP_Vertex * total num verts	- Vertex data for all groups
	// u_short * total num indices						- Index data for all groups
	if (s_uCharacterExportFlags & CharacterColoursReplaceNormals)
	{
		uFileSize += sizeof(Tools_SmoothSkin_PSP_Vertex_Coloured) * uTotalNumberOfVertices;
	}
	else
	{
		uFileSize += sizeof(Tools_SmoothSkin_PSP_Vertex_Normal) * uTotalNumberOfVertices;
	}

	// Chunk size is all the data + 'filename' (which is the skin name again) + header
	int uChunkSize = uFileSize + uFileNameSize + sizeof(Asura_Chunk_ResourceFile);

	Asura_Chunk_ResourceFile xResourceFile;

    // (NY - 01.03.2007) version number bumped to indicate new vert scaling (8m limit rather than 4) - to 1
	// (SB - 13/03/2007) version number bumped again to add support for colours instead of normals
	xResourceFile.Create
	(
		uChunkSize,
		s_uCharacterExportFlags,
		ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
		ASURA_RESOURCEFILE_TYPE_PSP_CHARACTER,
		uFileSize,
		2
	);

	unsigned long bytesWritten = 0;

	// chunk header with 'filename'
	WRITEOUTDATA( &xResourceFile, sizeof(Asura_Chunk_ResourceFile) );
	WRITEOUTDATA( pxSkin->Name.GetString(),uFileNameSize );

	// skin name again, this time for the PSP skin system
	WRITEOUTDATA( pxSkin->Name.GetString(),uFileNameSize );

	WRITEOUTDATA( &uTotalNumberOfVertices, sizeof(u_int) );
	WRITEOUTDATA( &uNumberOfGroups, sizeof(u_int) );

	// all the group definitions in one go

	u_int uStartVertex = 0;
	for (u_int u=0; u<uNumberOfGroups; u++)
	{
		const Tools_SmoothSkin_PSP_Group* pxGroup = xPSPProcessor.GetGroup(u);

		const u_int uNumberOfVerticesInGroup = pxGroup->m_uNumberOfIndices;
	
		WRITEOUTDATA( &(pxGroup->m_iMaterial), sizeof(int) );
		WRITEOUTDATA( &uNumberOfVerticesInGroup, sizeof(u_int) );

		WRITEOUTDATA( &uStartVertex, sizeof(u_int) );
	
		WRITEOUTDATA( (pxGroup->m_aucBoneIDs), sizeof(u_int) );

		uStartVertex += uNumberOfVerticesInGroup;
	}

	// *all* the vertices in one go
	for (u_int u=0; u<uNumberOfGroups; u++)
	{
		const Tools_SmoothSkin_PSP_Group* pxGroup = xPSPProcessor.GetGroup(u);
		const u_int uNumberOfIndicesInGroup = pxGroup->m_uNumberOfIndices;

		for (u_int v=0; v<uNumberOfIndicesInGroup; v++)
		{
			const u_int uIndex = pxGroup->m_puIndices[v];

			// (SB - 14/03/07) Now supporting two vertex formats
			if (s_uCharacterExportFlags & CharacterColoursReplaceNormals)
			{
				Tools_SmoothSkin_PSP_Vertex_Coloured xOutVertex;
				xOutVertex.m_aucWeight[0] = pxGroup->m_pxVertices[uIndex].m_aucWeight[0];
				xOutVertex.m_aucWeight[1] = pxGroup->m_pxVertices[uIndex].m_aucWeight[1];
				xOutVertex.m_aucWeight[2] = pxGroup->m_pxVertices[uIndex].m_aucWeight[2];
				xOutVertex.m_aucWeight[3] = pxGroup->m_pxVertices[uIndex].m_aucWeight[3];
				xOutVertex.m_usU = pxGroup->m_pxVertices[uIndex].m_usU;
				xOutVertex.m_usV = pxGroup->m_pxVertices[uIndex].m_usV;
				xOutVertex.m_uRGBAColour = pxGroup->m_pxVertices[uIndex].m_uRGBAColour;
				xOutVertex.m_asPosition[0] = pxGroup->m_pxVertices[uIndex].m_asPosition[0];
				xOutVertex.m_asPosition[1] = pxGroup->m_pxVertices[uIndex].m_asPosition[1];
				xOutVertex.m_asPosition[2] = pxGroup->m_pxVertices[uIndex].m_asPosition[2];
				WRITEOUTDATA( &xOutVertex, sizeof(Tools_SmoothSkin_PSP_Vertex_Coloured) );
			}
			else
			{
				Tools_SmoothSkin_PSP_Vertex_Normal xOutVertex;
				xOutVertex.m_aucWeight[0] = pxGroup->m_pxVertices[uIndex].m_aucWeight[0];
				xOutVertex.m_aucWeight[1] = pxGroup->m_pxVertices[uIndex].m_aucWeight[1];
				xOutVertex.m_aucWeight[2] = pxGroup->m_pxVertices[uIndex].m_aucWeight[2];
				xOutVertex.m_aucWeight[3] = pxGroup->m_pxVertices[uIndex].m_aucWeight[3];
				xOutVertex.m_usU = pxGroup->m_pxVertices[uIndex].m_usU;
				xOutVertex.m_usV = pxGroup->m_pxVertices[uIndex].m_usV;
				xOutVertex.m_asNormal[0] = pxGroup->m_pxVertices[uIndex].m_asNormal[0];
				xOutVertex.m_asNormal[1] = pxGroup->m_pxVertices[uIndex].m_asNormal[1];
				xOutVertex.m_asNormal[2] = pxGroup->m_pxVertices[uIndex].m_asNormal[2];
				xOutVertex.m_asPosition[0] = pxGroup->m_pxVertices[uIndex].m_asPosition[0];
				xOutVertex.m_asPosition[1] = pxGroup->m_pxVertices[uIndex].m_asPosition[1];
				xOutVertex.m_asPosition[2] = pxGroup->m_pxVertices[uIndex].m_asPosition[2];
				WRITEOUTDATA( &xOutVertex, sizeof(Tools_SmoothSkin_PSP_Vertex_Normal) );
			}
		}
	
	}


	#undef WRITEOUTDATA


	return true;
}

bool Tools_PSP_Export::ExportObject( HANDLE hOutput, Asura_Mesh* pxMesh, const Asura_Hash_ID uHashID, const int iMaterialIndex )
{
	// initialise and stripify mesh
	if(!Tools_ObjectProcessor::Initialise( pxMesh, iMaterialIndex ))
	{
		return false;
	}

	#define WRITEOUTDATA(a,b) WriteFile( hOutput, a, b, &bytesWritten, 0 );

	const Asura_String& xName = pxMesh->Name;

	u_int uFileNameSize = (xName.GetSizeInInts()<<2);
	
	u_int uFileSize = 0;
	
	// Hash_ID
	uFileSize += sizeof(Asura_Hash_ID);
	
	// For each object strip:
	// NumberOfTris (u_int)
	// NumberOfVertices (u_int)
	// NumberOfIndices (u_int)
	// MaterialID (int)
	uFileSize += (sizeof(u_int) * 4) ;


	// And then each vertex will be (until optimised!) sizeof(Tools_ObjectProcessor_Vertex)
	uFileSize += (sizeof(Tools_ObjectProcessor_Vertex) * Tools_ObjectProcessor::GetTotalNumberOfVertices());

	// And each index will be (u_short)
	uFileSize += (sizeof(u_short) * Tools_ObjectProcessor::GetTotalNumberOfIndices());

	// LOD min distance and hash ID added. 
	uFileSize += sizeof(float) + sizeof(Asura_Hash_ID);

	// Chunk size is all the data, 'filename' & header
	int uChunkSize = uFileSize + uFileNameSize + sizeof(Asura_Chunk_ResourceFile);
	Asura_Chunk_ResourceFile xResourceFile;

	xResourceFile.Create
	(
		uChunkSize,
		0,
		ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
		ASURA_RESOURCEFILE_TYPE_PSP_OBJECT,
		uFileSize
	);
	
	xResourceFile.Version = 0;

	unsigned long bytesWritten;

	WRITEOUTDATA( &xResourceFile, sizeof(Asura_Chunk_ResourceFile) );
	WRITEOUTDATA( xName.GetString(),uFileNameSize );

	
	WRITEOUTDATA( &uHashID, sizeof(u_int) );

	{
		const Tools_ObjectProcessor_Strip& xStrip = *Tools_ObjectProcessor::GetProcessedStrip();		

		WRITEOUTDATA( &(xStrip.m_uNumberOfTriangles), sizeof(u_int) );
		WRITEOUTDATA( &(xStrip.m_uNumberOfVertices), sizeof(u_int) );
		WRITEOUTDATA( &(xStrip.m_uNumberOfIndices), sizeof(u_int) );
		WRITEOUTDATA( &(xStrip.m_iMaterialID), sizeof(int) );
	}
	
	// Now all the vertex data in one go. Order determined by PSP GPU.
	{
		const Tools_ObjectProcessor_Strip& xStrip = *Tools_ObjectProcessor::GetProcessedStrip();		
	
		for (u_int v=0; v<xStrip.m_uNumberOfVertices; v++)
		{
			// uvs
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fU), sizeof(float) );
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fV), sizeof(float) );

			// normal
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fNX), sizeof(float) );
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fNY), sizeof(float) );
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fNZ), sizeof(float) );

			// position
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fX), sizeof(float) );
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fY), sizeof(float) );
			WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fZ), sizeof(float) );
		}	
	}

	// assuming object has only one strip...
	{	
		const Tools_ObjectProcessor_Strip& xStrip = *Tools_ObjectProcessor::GetProcessedStrip();		
		
		for (u_int v=0; v<xStrip.m_uNumberOfIndices; v++)
		{
			u_short usIndex = (u_short)(xStrip.m_pusIndices[v]);
			WRITEOUTDATA( &usIndex, sizeof(u_short) );
		}
	}
	
	// LOD min distance and hash ID
	Asura_Hash_ID uLODHash = pxMesh->m_pxNextLowerMesh ? Asura_GetHashID(pxMesh->m_pxNextLowerMesh->Name.GetString()) : ASURA_HASH_ID_UNSET;

	WRITEOUTDATA(&pxMesh->m_fMinDistance,sizeof(float));
	WRITEOUTDATA(&uLODHash,sizeof(Asura_Hash_ID));

	#undef WRITEOUTDATA

	// wrap up and delete all the temp data
	Tools_ObjectProcessor::Shutdown();

	return true;
}

bool Tools_PSP_Export::ExportObjectHierarchy( HANDLE hOutput, const Asura_Hierarchy_Skin* pxSkin )
{
	// initialise and stripify mesh
	if(!Tools_HierarchyProcessor::Initialise(pxSkin))
	{
		return false;
	}

	#define WRITEOUTDATA(a,b) WriteFile( hOutput, a, b, &bytesWritten, 0 );

	Asura_String xName = pxSkin->Name;

	u_int uNumberOfStrips = Tools_HierarchyProcessor::GetTotalNumberOfStrips();

	u_int uFileNameSize = (xName.GetSizeInInts()<<2);
	
	u_int uFileSize = 0;

	
	// sizeof(Asura_Hash_ID)							- Hash_ID of hierarchy for later identification in-game
	uFileSize += sizeof(Asura_Hash_ID);

	// u_int											- Number of strips 
	// u_int											- Total number of vertices
	// u_int											- Total number of indices
	uFileSize += (sizeof(u_int) * 3);
		
	// Per object strip
	// int												- Material
	// u_int											- Number of tris
	// u_int											- Start vertex offset
	// u_int											- Start index offset
	uFileSize += (sizeof(u_int) * 4) * uNumberOfStrips;

	// Vertex size is...
	// ...with normals: 32 bytes
	// float			- U
	// float			- V
	// float[3]			- Normal
	// float[3]			- Vertex
	// ...with colours: 24 bytes
	// float			- U
	// float			- V
	// u_int			- Colour
	// float[3]			- Vertex

	// Short vertex size is...
	// ...with normals: 16 bytes
	// short			- U
	// short			- V
	// short[3]			- Normal
	// short[3]			- Vertex
	// ...with colours: 14 bytes
	// short			- U
	// short			- V
	// u_int			- Colour
	// short[3]			- Vertex

	// Find out whether to use shorts, and if so, which scale factor to go with.

	// Find the max. 0-centred
	float fMaxRange = 0.0f;	

	for( u_int u= 0; u< uNumberOfStrips; u++)
	{		
		const Tools_HierarchyProcessor_Strip& xStrip = *Tools_HierarchyProcessor::GetProcessedStrip(u);	
	
		for (u_int v=0; v<xStrip.m_uNumberOfVertices; v++)
		{
			if (fMaxRange < xStrip.m_pxVertices[v].m_fX)
			{
				fMaxRange =  (xStrip.m_pxVertices[v].m_fX);
			}
			else if (fMaxRange < -xStrip.m_pxVertices[v].m_fX)
			{
				fMaxRange = -(xStrip.m_pxVertices[v].m_fX);
			}

			if (fMaxRange < xStrip.m_pxVertices[v].m_fY)
			{
				fMaxRange =  (xStrip.m_pxVertices[v].m_fY);
			}
			else if (fMaxRange < -xStrip.m_pxVertices[v].m_fY)
			{
				fMaxRange = -(xStrip.m_pxVertices[v].m_fY);
			}

			if (fMaxRange < xStrip.m_pxVertices[v].m_fZ)
			{
				fMaxRange =  (xStrip.m_pxVertices[v].m_fZ);
			}
			else if (fMaxRange < -xStrip.m_pxVertices[v].m_fZ)
			{
				fMaxRange = -(xStrip.m_pxVertices[v].m_fZ);
			}				
		}	
	}
	
	// Potentially we could add in multiple scaling levels? What I've done here is consistent with how the smoothskins handle it (see
	// Tools_SmoothSkin_PSP_Vertex::Set ), but I'm not quite happy with the inclusion of magic numbers here and on the PSP side when they're
	// scaled up again. If we ever do add in multiple scaling levels, perhaps the sensible method of handling such would be to save out the
	// scale factor as a short, costing 2 bytes but ensuring that we don't have to keep those two disparate code files in sync.
	// For now, if the short scale here is changed, the scaling code in Asura_PSP_ObjectHierarchyRenderer::Render needs to be changed also.
	bool bShortVerts = (fMaxRange < 2.0f);
	u_short usShortScale = 2;

	const bool bColoursReplaceNormals = (s_uHierarchyExportFlags & HierarchyColoursReplaceNormals);	

	size_t xVertexSize;


	

	if(bShortVerts)
	{
		xVertexSize = (bColoursReplaceNormals ? 14 : 16);		
		SetHierarchyVertsAsShorts(true);
	}
	else
	{
		xVertexSize = (bColoursReplaceNormals ? 24 : 32);
		SetHierarchyVertsAsShorts(false);
	}
	

	// And then each vertex will be (until optimised!)
	uFileSize += (xVertexSize * Tools_HierarchyProcessor::GetTotalNumberOfVertices());

	// And each index will be (u_short)
	uFileSize += (sizeof(u_short) * Tools_HierarchyProcessor::GetTotalNumberOfIndices());

	// Chunk size is all the data, 'filename' & header
	int uChunkSize = uFileSize + uFileNameSize + sizeof(Asura_Chunk_ResourceFile);
	Asura_Chunk_ResourceFile xResourceFile;

	xResourceFile.Create
	(
		uChunkSize,
		s_uHierarchyExportFlags,
		ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
		ASURA_RESOURCEFILE_TYPE_PSP_OBJECTHIERARCHY,
		uFileSize,
		2 // (SB - 21/03/07) Bumped to add optional colour to vertices - to 1
		  // (MC - 03/01/08) Bumped to allow vertices to optionally use shorts
	);

	unsigned long bytesWritten;

	u_int uTotalNumberOfVertices = Tools_HierarchyProcessor::GetTotalNumberOfVertices();
	u_int uTotalNumberOfIndices = Tools_HierarchyProcessor::GetTotalNumberOfIndices();

	WRITEOUTDATA( &xResourceFile, sizeof(Asura_Chunk_ResourceFile) );
	WRITEOUTDATA( xName.GetString(),uFileNameSize );

	// now the actual PSP data

	Asura_Hash_ID uHash = pxSkin->m_uNameHashID;
	WRITEOUTDATA( &(uHash), sizeof(Asura_Hash_ID) );

	WRITEOUTDATA( &(uNumberOfStrips), sizeof(u_int) );
	WRITEOUTDATA( &(uTotalNumberOfVertices), sizeof(u_int));
	WRITEOUTDATA( &(uTotalNumberOfIndices), sizeof(u_int));

	u_int uStartIndex = 0;
	u_int uStartVertex = 0;

	for( u_int u= 0; u< uNumberOfStrips; u++)
	{
		const Tools_HierarchyProcessor_Strip& xStrip = *Tools_HierarchyProcessor::GetProcessedStrip(u);		

		// KJFL - 2004-04-08 10:28:54 - if we want hierarchies with different materials per strip, we'll need to 
		// make a conversion list here. For now we can set the material ID to zero, and the first material will be used
		//WRITEOUTDATA( &(xStrip.m_iMaterialID), sizeof(int) );
		
		int iMaterialID = 0;

        // (MP - 11.07.2006) If we don't have any geometry - eg. an object
        // hierarchy effectively being used as a dummy for positioning or
        // orienting something else - then set this to have no material, so
        // that we don't try and convert it on load.
        if( uNumberOfStrips == 1 && !uTotalNumberOfVertices && !uTotalNumberOfIndices )
        {
            iMaterialID = ASURA_MATERIAL_NONE;
        }

		WRITEOUTDATA( &(iMaterialID), sizeof(int) );
		WRITEOUTDATA( &(xStrip.m_uNumberOfTriangles), sizeof(u_int) );
		
		WRITEOUTDATA( &(uStartVertex), sizeof(u_int) );
		WRITEOUTDATA( &(uStartIndex), sizeof(u_int) );
	
		uStartIndex  += xStrip.m_uNumberOfIndices;
		uStartVertex += xStrip.m_uNumberOfVertices;
	}

	// write out all the vertices in one go
	for( u_int u= 0; u< uNumberOfStrips; u++)
	{
		const Tools_HierarchyProcessor_Strip& xStrip = *Tools_HierarchyProcessor::GetProcessedStrip(u);		
	
		for (u_int v=0; v<xStrip.m_uNumberOfVertices; v++)
		{
			if (bShortVerts)
			{
				u_short usU, usV;
				short sX, sY, sZ;

				// If you use 32768, it appears this overflows for some textures, which then render upside-down. This, however, works.
				usU = static_cast<u_short>(xStrip.m_pxVertices[v].m_fU * 32767.9f);
				usV = static_cast<u_short>(xStrip.m_pxVertices[v].m_fV * 32767.9f);

				float fShortMax = 32768.f / usShortScale;

				sX = static_cast<short>(xStrip.m_pxVertices[v].m_fX * fShortMax);
				sY = static_cast<short>(xStrip.m_pxVertices[v].m_fY * fShortMax);
				sZ = static_cast<short>(xStrip.m_pxVertices[v].m_fZ * fShortMax);

				if (bColoursReplaceNormals)
				{
					WRITEOUTDATA( &(usU), sizeof(u_short) );
					WRITEOUTDATA( &(usV), sizeof(u_short) );
	
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_uRGBAColour), sizeof(u_int) );
	
					WRITEOUTDATA( &(sX), sizeof(short) );
					WRITEOUTDATA( &(sY), sizeof(short) );
					WRITEOUTDATA( &(sZ), sizeof(short) );
				}
				else
				{
					short sNX, sNY, sNZ;

					sNX = static_cast<short>(xStrip.m_pxVertices[v].m_fNX * 16384.f);
					sNY = static_cast<short>(xStrip.m_pxVertices[v].m_fNY * 16384.f);
					sNZ = static_cast<short>(xStrip.m_pxVertices[v].m_fNZ * 16384.f);

					WRITEOUTDATA( &(usU), sizeof(u_short) );
					WRITEOUTDATA( &(usV), sizeof(u_short) );
	
					WRITEOUTDATA( &(sNX), sizeof(short) );
					WRITEOUTDATA( &(sNY), sizeof(short) );
					WRITEOUTDATA( &(sNZ), sizeof(short) );
		
					WRITEOUTDATA( &(sX), sizeof(short) );
					WRITEOUTDATA( &(sY), sizeof(short) );
					WRITEOUTDATA( &(sZ), sizeof(short) );
				}
			}
			else
			{
				if (bColoursReplaceNormals)
				{
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fU), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fV), sizeof(float) );
	
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_uRGBAColour), sizeof(u_int) );
	
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fX), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fY), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fZ), sizeof(float) );
				}
				else
				{
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fU), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fV), sizeof(float) );
	
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fNX), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fNY), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fNZ), sizeof(float) );
		
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fX), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fY), sizeof(float) );
					WRITEOUTDATA( &(xStrip.m_pxVertices[v].m_fZ), sizeof(float) );
				}
			}
		}	
	}
	
	// write out all the index data
	for (u_int s=0; s< uNumberOfStrips; s++)
	{
		const Tools_HierarchyProcessor_Strip& xStrip = *Tools_HierarchyProcessor::GetProcessedStrip(s);

		for (u_int v=0; v<xStrip.m_uNumberOfIndices; v++)
		{
			u_short usIndex = static_cast<u_short>(xStrip.m_pusIndices[v]);
			WRITEOUTDATA( &usIndex, sizeof(u_short) );
		}
	}

	// wrap up and delete all the temp data
	Tools_HierarchyProcessor::Shutdown();

	#undef WRITEOUTDATA

	return true;
}

#if defined( ASURA_USE_BOTANICALS )

struct Asura_PSP_Botanicals_Instance_Info
{
	u_int uTypeIndex;	
	u_int uModuleIndex;	
};

struct Asura_PSP_Botanicals_LoD_Component
{
	Asura_PSP_Botanicals_Mesh_Type eType;
	u_int uNumTris;
	u_int uMaterialID;
	float fLoDMin;
	float fLoDMax;
	float fLoDFadeNear;
	float fLoDFadeFar;
	Asura_Mesh* pxMesh;
	Asura_Bounding_Box xBB;
};

void Tools_PSP_Export::ExportBotanicalsElementLOD_Geometry( Asura_Chunk_Stream& xStream, Asura_Mesh* pxElementLOD, const int* const piMaterialConversionList )
{
	if( !pxElementLOD || !pxElementLOD->TextureDataValid )
	{
		Asura_Assert( false, "Invalid parameter passed to Tools_PSP_Export::ExportBotanicalsElementLOD_Geometry()." );
		return;
	}

	// We'll assume only one material per LOD mesh so we can use the object processor
	const int iMaterialID = pxElementLOD->TextureDataPtr[0].m_iMaterialID;

	// Important - can't use tristrips because there's no way to insert correct degenerates between instances
	if( !Tools_ObjectProcessor::Initialise( pxElementLOD, iMaterialID ) ) return;

	const Tools_ObjectProcessor_Strip* xStrip = Tools_ObjectProcessor::GetProcessedStrip();

	xStream << Asura_Material_System::ConvertIDToIndex( xStrip->m_iMaterialID, piMaterialConversionList );
	xStream << xStrip->m_uNumberOfIndices;
	xStream << xStrip->m_uNumberOfTriangles;
	xStream << xStrip->m_uNumberOfVertices;

	for( u_int u = 0; u < xStrip->m_uNumberOfVertices; ++u )
	{
		const Tools_ObjectProcessor_Vertex& xVertex = xStrip->m_pxVertices[u];

		xStream << xVertex.m_fU;
		xStream << xVertex.m_fV;

		xStream << xVertex.m_fNX;
		xStream << xVertex.m_fNY;
		xStream << xVertex.m_fNZ;

		xStream << xVertex.m_fX;
		xStream << xVertex.m_fY;
		xStream << xVertex.m_fZ;
	}

	// Have to write out the indices as 32-bit for now, since there's no
	// single 16-bit component vertex declaration type

	for( u_int u = 0; u < xStrip->m_uNumberOfIndices; ++u )
	{
		xStream << xStrip->m_pusIndices[u];
	}

	Tools_ObjectProcessor::Shutdown();

}

void Tools_PSP_Export::ExportBotanicalsElementLOD_Billboards( Asura_Chunk_Stream& xStream, const Tools_BotanicalElementLOD& xElementLOD, const int* const piMaterialConversionList )
{
	if( !xElementLOD.m_pxMesh || !xElementLOD.m_pxMesh->TextureDataValid )
	{
		Asura_Assert( false, "Invalid parameter passed to Tools_Xbox360_Export::ExportBotanicalsElementLOD_Billboards()." );
		return;
	}

	// We'll assume only one material per LOD mesh
	const int iMaterialID = xElementLOD.m_pxMesh->TextureDataPtr[0].m_iMaterialID;

	const Tools_BotanicalRenderable_Billboards xRenderable( &xElementLOD );
	const int iNumBillboards = xRenderable.GetNumBillboards();
	const int iNumVertices = iNumBillboards * 4;

	xStream << Asura_Material_System::ConvertIDToIndex( iMaterialID, piMaterialConversionList );
	xStream << iNumBillboards; 
	xStream << iNumVertices;

	// Per-vertex data

	for( int i = 0; i < iNumBillboards; ++i )
	{
		const Tools_Botanicals_Billboard& xBillboard = xRenderable.GetBillboard(i);

		const float fHalfWidth = xBillboard.fWidth * 0.5f;
		const float fHalfHeight = xBillboard.fHeight * 0.5f;
		const float fU[2] = { xBillboard.fU[0], xBillboard.fU[1] };
		const float fV[2] = { xBillboard.fV[0], xBillboard.fV[1] };

		// Upper left corner
		xStream << fU[0] << fV[0];
		xStream << -fHalfWidth << -fHalfHeight;

		// Upper right corner
		xStream << fU[1] << fV[0];
		xStream << fHalfWidth << -fHalfHeight;

		// Lower left corner
		xStream << fU[0] << fV[1];
		xStream << -fHalfWidth << fHalfHeight;

		// Lower right corner
		xStream << fU[1] << fV[1];
		xStream << fHalfWidth << fHalfHeight;
	}

	// Per-billboard data

	for( int i = 0; i < iNumBillboards; ++i )
	{
		const Tools_Botanicals_Billboard& xBillboard = xRenderable.GetBillboard(i);

		// Position relative to instance location
		const float afPos[3] = { xBillboard.xPos.x, xBillboard.xPos.y, xBillboard.xPos.z };
		xStream << Asura_WriteToStream_Array_Wrapper< float >( 3, afPos );

		// Normal (faces away from the cluster centre)
		const float afNormal[3] = { xBillboard.xNormal.x, xBillboard.xNormal.y, xBillboard.xNormal.z };
		xStream << Asura_WriteToStream_Array_Wrapper< float >( 3, afNormal );
	}

}

void Tools_PSP_Export::ExportBotanicalsElementLOD_Imposter( Asura_Chunk_Stream& xStream, const Tools_BotanicalElementLOD& xElementLOD, const int* const piMaterialConversionList )
{
	int iNumImposterAngles = xElementLOD.GetNumImposters();
	
	int iMaterialID = 0;
	const Tools_Botanicals_Imposter *pxImp = xElementLOD.GetImposterByIndexConst(0);	
	if (pxImp)
	{
		if (pxImp->m_pxMesh)
		{		
			if (pxImp->m_pxMesh->TextureDataValid)
			{
				iMaterialID = pxImp->m_pxMesh->TextureDataPtr[0].m_iMaterialID;
			}
			else
			{
				iMaterialID = ASURA_MATERIAL_NONE;
			}
		}
	}
	iMaterialID = Asura_Material_System::ConvertIDToIndex( iMaterialID, piMaterialConversionList ); // 2 x4bytes
	xStream << iMaterialID;
		
	xStream << iNumImposterAngles;																 // 1					

	for(int i=0;i<iNumImposterAngles;i++)
	{
		const Tools_Botanicals_Imposter *pxImp = xElementLOD.GetImposterByIndexConst(i);
		Asura_Assert(pxImp->m_bValid, "Attempting to export invalid/uninitialised imposter\n");
			
		xStream << pxImp->m_xBillboard.xPos;						// 3
		xStream << pxImp->m_xBillboard.xNormal;						// 6
		xStream << pxImp->m_xBillboard.fWidth;						// 7
		xStream << pxImp->m_xBillboard.fHeight;						// 8
		xStream << pxImp->m_xBillboard.fU[0];						// 9
		xStream << pxImp->m_xBillboard.fU[1];						// 10
		xStream << pxImp->m_xBillboard.fV[0];						// 11
		xStream << pxImp->m_xBillboard.fV[1];						// 12
		xStream << pxImp->m_fAngleRange;							// 13
		xStream << pxImp->m_fFadeRange;								// 14 x4bytes
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::ExportShapes()
//
// Exports a list of (botanicals) shapes to a chunk stream
//
// Parameters:		xStream - Reference to output chunk stream
//					xShapeList - Reference to list of shapes
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::ExportShapes( Asura_Chunk_Stream& xStream, const Asura_LinkList< const Asura_Shape* >& xShapeList, const int* const piMaterialConversionList  )
{
	const u_int uNumShapes = xShapeList.Size();
	u_int uCurrShapeTimes100 = 0;

	for( Asura_ListElt< const Asura_Shape* >* pxCurrShapeListEntry = xShapeList.Front();
		 pxCurrShapeListEntry;
		 pxCurrShapeListEntry = pxCurrShapeListEntry->Next() )
	{
		Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_BOTANICALS_EXPORTING_SHAPES, "Botanicals: Exporting shapes...", uCurrShapeTimes100 / uNumShapes );
		uCurrShapeTimes100 += 100;

		const Asura_Shape* const pxCurrShape = pxCurrShapeListEntry->Data();
		if( !pxCurrShape ) continue; 

		xStream << pxCurrShape->GetShapeHashID(); 

		xStream << static_cast< u_int >( TOOLS_BOTANICALS_ELEMENTTYPE_1SIDED_MESH ); // Ensure 32-bit

		Asura_Bounding_Box xOverallBB;
		xOverallBB.Reset();

		Asura_Mesh* pxCurrMesh = pxCurrShape->GetRenderData()->GetMesh();
		Asura_Assert(pxCurrMesh, "Trying to export a botanical shape with no geometry"); 

		const unsigned int uNUMBER_OF_LODS = pxCurrMesh->GetNumberOfDetailLevels(); 
		Asura_Assert(uNUMBER_OF_LODS == 1, "Shape exporter for botanicals doesn't support multiple levels of detail");

		Asura_Mesh* pxCurrLOD = pxCurrMesh->GetDetailMeshFromIndex(0); 

		ExportBotanicalsElementLOD_Geometry( xStream, pxCurrLOD, piMaterialConversionList ); 

		if( pxCurrLOD )
		{
			Asura_Assert( pxCurrLOD->VertexDataValid, "Botanicals mesh vertex data isn't valid." );

			for( int k = 0; k < pxCurrLOD->NumberOfVertices; ++k )
			{
				xOverallBB.Set( pxCurrLOD->VertexDataPtr[k].Position );
			}
		}

		const Asura_Vector_3 xBSCentre = xOverallBB.CalculateCentre();
		const float fBSRadius = xOverallBB.CalculateRadius();

		// Assume elements' vertices are roughly centred horizontally
		xStream << xBSCentre.y;
		xStream << fBSRadius;
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::ExportBotanicalsShapes()
//
// Exports a list of botanicals shapes to a chunk stream
//
// Parameters:		xStream - Reference to output chunk stream
//					xShapeList - Reference to list of botanicals shapes
//					piMaterialConversionList - Array for converting materal IDs
//											   to export indices. Must be
//											   ASURA_MAX_NO_OF_MATERIALS in size
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::ExportBotanicalShapes( Asura_Chunk_Stream& xStream, const Asura_LinkList< const Tools_BotanicalShape* >& xShapeList, const int* const piMaterialConversionList )
{
	const u_int uNumShapes = xShapeList.Size();
	u_int uCurrShapeTimes100 = 0;

	for( Asura_ListElt< const Tools_BotanicalShape* >* pxCurrShapeListEntry = xShapeList.Front();
		 pxCurrShapeListEntry;
		 pxCurrShapeListEntry = pxCurrShapeListEntry->Next() )
	{
		Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_BOTANICALS_EXPORTING_SHAPES, "Botanicals: Exporting botanical shapes...", uCurrShapeTimes100 / uNumShapes );
		uCurrShapeTimes100 += 100;

		float fShapeBoundingRadius = 0;
		Asura_Bounding_Box xShapeBoundingBox;
		xShapeBoundingBox.Reset();

		const Tools_BotanicalShape* const pxCurrShape = pxCurrShapeListEntry->Data();
		Tools_BGR_Type xBGRType( *pxCurrShape);
		xStream << Asura_GetHashID( pxCurrShape->GetName() );

		const int iNumElements = pxCurrShape->GetNumElements();
		int iToExport = 0;
		for( int i = 0; i < iNumElements; ++i )
		{
			const Tools_BotanicalElement* const pxCurrElement = pxCurrShape->GetElementByIndex(i);
			if( !pxCurrElement )
			{
				Asura_Assert( false, "Botanicals element is invalid." );
				continue;
			}
			switch( pxCurrElement->m_eType )
			{
				case TOOLS_BOTANICALS_ELEMENTTYPE_1SIDED_MESH:
				case TOOLS_BOTANICALS_ELEMENTTYPE_2SIDED_MESH:
				case TOOLS_BOTANICALS_ELEMENTTYPE_BILLBOARDS:
				case TOOLS_BOTANICALS_ELEMENTTYPE_IMPOSTER:
					++iToExport;
			}
		}

		xStream << iToExport;

		// Loop over elements

		for( int i = 0; i < iNumElements; ++i )
		{
			const Tools_BotanicalElement* const pxCurrElement = pxCurrShape->GetElementByIndex(i);
			if( !pxCurrElement )
			{
				Asura_Assert( false, "Botanicals element is invalid." );
				continue;
			}
			switch( pxCurrElement->m_eType )
			{
				case TOOLS_BOTANICALS_ELEMENTTYPE_1SIDED_MESH:
				case TOOLS_BOTANICALS_ELEMENTTYPE_2SIDED_MESH:
				case TOOLS_BOTANICALS_ELEMENTTYPE_BILLBOARDS:
				case TOOLS_BOTANICALS_ELEMENTTYPE_IMPOSTER:
					break;
				default:
					continue;
			}

			u_int uType = pxCurrElement->m_eType;// Ensure 32-bit

			xStream << uType;

			const int iNumLODs = pxCurrElement->GetNumLODs();
			xStream << iNumLODs;

#if defined( ASURA_BOTANICAL_SUPPORT_SECTION_VERSIONS) && 1 == ASURA_BOTANICAL_SUPPORT_SECTION_VERSIONS
			xStream << xBGRType.GetSectionID( (pxCurrShape->GetSectionByIndex( i))->m_xName.GetHash());
			xStream << xBGRType.GetVersionID( (pxCurrShape->GetVersionByIndex( i))->m_xName.GetHash());
#endif
			Asura_Bounding_Box xOverallBB;
			xOverallBB.Reset();

			// Loop over LODs

			for( int j = 0; j < iNumLODs; ++j )
			{
				const Tools_BotanicalElementLOD* const pxCurrLOD = pxCurrElement->GetLODByIndex(j);
				if( !pxCurrLOD )
				{
					Asura_Assert( false, "Botanicals LOD is invalid." );
					continue;
				}

				switch( pxCurrElement->m_eType )
				{
					case TOOLS_BOTANICALS_ELEMENTTYPE_1SIDED_MESH:
					case TOOLS_BOTANICALS_ELEMENTTYPE_2SIDED_MESH:
					{
						ExportBotanicalsElementLOD_Geometry( xStream, pxCurrLOD->m_pxMesh, piMaterialConversionList );
						break;
					}

					case TOOLS_BOTANICALS_ELEMENTTYPE_BILLBOARDS:
					{
						ExportBotanicalsElementLOD_Billboards( xStream, *pxCurrLOD, piMaterialConversionList );
						break;
					}

					case TOOLS_BOTANICALS_ELEMENTTYPE_IMPOSTER:
					{
						ExportBotanicalsElementLOD_Imposter( xStream, *pxCurrLOD, piMaterialConversionList );
						break;
					}

					default:
					{
						Asura_Assert( false, "Unknown botanicals element type." );
						break;
					}
				}

				if( pxCurrLOD->m_pxMesh )
				{
					Asura_Assert( pxCurrLOD->m_pxMesh->VertexDataValid, "Botanicals mesh vertex data isn't valid." );

					for( int k = 0; k < pxCurrLOD->m_pxMesh->NumberOfVertices; ++k )
					{
						const Asura_Vector_3& xPosition = pxCurrLOD->m_pxMesh->VertexDataPtr[k].Position;
						xOverallBB.Set( xPosition);

						const float fOriginRadius = xPosition.Magnitude();
						fShapeBoundingRadius = Asura_Maths::Max( fOriginRadius, fShapeBoundingRadius);
					}
				}
			}

			const Asura_Vector_3 xBSCentre = xOverallBB.CalculateCentre();
			const float fBSRadius = xOverallBB.CalculateRadius();

			xShapeBoundingBox.Set( xOverallBB);

			// Assume elements' vertices are roughly centred horizontally
			xStream << xBSCentre.y;
			xStream << fBSRadius;

			// Append fade data

			for( int j = 0; j < iNumLODs; ++j )
			{
				const Tools_BotanicalElementLOD* const pxCurrLOD = pxCurrElement->GetLODByIndex(j);
				if( !pxCurrLOD )
				{
					Asura_Assert( false, "Botanicals LOD is invalid." );
					continue;
				}

				// In Tools the values are higher for nearer distances

				const float fMinDistance = (1.0f - pxCurrLOD->m_fLod1) / pxCurrShape->m_fInvMaxDrawDistance;
				const float fMinFullAlphaDistance = (1.0f - pxCurrLOD->m_fLod1 + pxCurrLOD->m_fFadeLength1) / pxCurrShape->m_fInvMaxDrawDistance;
				const float fMaxDistance = (1.0f - pxCurrLOD->m_fLod0) / pxCurrShape->m_fInvMaxDrawDistance;
				const float fMaxFullAlphaDistance = (1.0f - pxCurrLOD->m_fLod0 - pxCurrLOD->m_fFadeLength0) / pxCurrShape->m_fInvMaxDrawDistance;
				const u_int uNumVisibleInstances = 0;

				xStream << fMinDistance;
				xStream << fMinFullAlphaDistance;
				xStream << fMaxFullAlphaDistance;
				xStream << fMaxDistance;
				xStream << uNumVisibleInstances;
			}
		}
		xBGRType.SetBoundingBox( xShapeBoundingBox);
		xBGRType.SetBoundingRadius( fShapeBoundingRadius);
		
#if defined( ASURA_BOTANICAL_SUPPORT_DYNAMIC_DATA) && 1 == ASURA_BOTANICAL_SUPPORT_DYNAMIC_DATA
		{
			Asura_BGR_DynamicMemoryLayout& xDynamicDataLayout = xBGRType.GetDynamicDataLayout();
			
			xDynamicDataLayout.SetupDynamicData( xBGRType);
		}
		xBGRType.Export( xStream);
#endif	
	}
}
////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::ExportBotanicalsTemplates()
//
// Exports a list of botanicals templates to a chunk stream
//
// Parameters:		xStream - Reference to output chunk stream
//					xTemplateList - Reference to list of botanicals templates
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::ExportBotanicalsTemplates( Asura_Chunk_Stream& xStream, 
												  const Asura_LinkList< const Asura_Botanicals_Template* >& xTemplateList,
												  const Asura_LinkList< const Asura_Shape* >& xShapeList,											  
												  const Asura_LinkList< const Tools_BotanicalShape* >& xBotanicalShapeList )
{
	const u_int uNumTemplates = xTemplateList.Size();
	u_int uCurrTemplateTimes100 = 0;

	const Asura_LinkList< Asura_Botanicals_Instance* >& xInstanceList = Asura_Botanicals_InstanceManager::GetInstanceList();

	for( Asura_ListElt< const Asura_Botanicals_Template* >* pxCurrTemplateElement = xTemplateList.Front();
		 pxCurrTemplateElement;
		 pxCurrTemplateElement = pxCurrTemplateElement->Next() )
	{
		Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_BOTANICALS_EXPORTING_TEMPLATES, "Botanicals: Exporting templates...", uCurrTemplateTimes100 / uNumTemplates );
		uCurrTemplateTimes100 += 100;

		const Asura_Botanicals_Template* const pxCurrTemplate = pxCurrTemplateElement->Data();

		Asura_Bounding_Box xBoundingBoxTemplate;
		
		xBoundingBoxTemplate.Reset();

		// DLH - 15 December 2006 15:06:59 - Create a list of just the Instances required
//		Asura_LinkList< const Asura_Botanicals_Instance*> xInstanceListSave;

		for( Asura_ListElt< Asura_Botanicals_Instance* >* pxCurrInstanceElement = xInstanceList.Front(); pxCurrInstanceElement; pxCurrInstanceElement = pxCurrInstanceElement->Next() )
		{
			const Asura_Botanicals_Instance* const pxCurrInstance = pxCurrInstanceElement->Data();
			if( pxCurrInstance->iTemplateUID != pxCurrTemplate->iUID )
			{
				continue;
			}
			
//			xInstanceListSave.Push_Back( pxCurrInstance);
			
			xBoundingBoxTemplate.Set( pxCurrInstance->xPosition);
		}

		u_int uShapeIndex = 0;

		if( pxCurrTemplate->eType == ASURA_BOTANICALS_TEMPLATE_SHAPE )
		{
			// Check if this instance it is a standard shape (no lods)
			for( Asura_ListElt< const Asura_Shape* >* pxCurrShapeElement = xShapeList.Front();
				pxCurrShapeElement;
				pxCurrShapeElement = pxCurrShapeElement->Next() )
			{
				const Asura_Shape* pxTemplateShape = Asura_Shape_System::GetShape(pxCurrTemplate->uShapeID);
				if( pxCurrShapeElement->Data() == pxTemplateShape) break;

				++uShapeIndex;
			}
		}
		else if( pxCurrTemplate->eType == ASURA_BOTANICALS_TEMPLATE_BOTANICALSHAPE )
		{
			uShapeIndex = xShapeList.Size(); 

			// Check if this instance is a botanical shape (lods)
			for( Asura_ListElt< const Tools_BotanicalShape* >* pxCurrShapeElement = xBotanicalShapeList.Front();
				pxCurrShapeElement;
				pxCurrShapeElement = pxCurrShapeElement->Next() )
			{
				if( pxCurrTemplate->pxBotanicalShape && pxCurrShapeElement->Data() == pxCurrTemplate->pxBotanicalShape->GetToolsShape() ) break;

				++uShapeIndex;
			}
		}

		{
			xStream << uShapeIndex;
			xStream << xBoundingBoxTemplate;
			xStream << Asura_Botanicals_InstanceManager::GetNumInstancesOfTemplateByUID( pxCurrTemplate->iUID );
		}

		// Loop over instances that use this template
		// TODO: create a list of instances per template in the finding step

		for( Asura_ListElt< Asura_Botanicals_Instance* >* pxCurrInstanceElement = xInstanceList.Front();
			 pxCurrInstanceElement;
			 pxCurrInstanceElement = pxCurrInstanceElement->Next() )
		{
			const Asura_Botanicals_Instance* const pxCurrInstance = pxCurrInstanceElement->Data();
			if( pxCurrInstance->iTemplateUID != pxCurrTemplate->iUID ) continue;


			// Construct a world matrix for each instance

			D3DXMATRIX xInstanceMatrix, xTemp;

			// Scaling

			D3DXMatrixScaling( &xInstanceMatrix, pxCurrInstance->fScale, pxCurrInstance->fScale, pxCurrInstance->fScale );

			// Rotation

			Asura_Mem::Set( &xTemp, 0, sizeof( xTemp ) );

			for( u_int u = 0; u < 3; ++u )
			{
				for( u_int v = 0; v < 3; ++v )
				{
					xTemp.m[u][v] = pxCurrInstance->xOrientation.Element[v][u];
				}
			}

			xTemp._44 = 1.0f;

			D3DXMatrixMultiply( &xInstanceMatrix, &xInstanceMatrix, &xTemp );

			// Translation

			D3DXMatrixTranslation( &xTemp, pxCurrInstance->xPosition.x, pxCurrInstance->xPosition.y, pxCurrInstance->xPosition.z );
			D3DXMatrixMultiplyTranspose( &xInstanceMatrix, &xInstanceMatrix, &xTemp );

			// Command to start world matrix upload
			xStream << 0x3A000000; 

			// Output orientation as 3x3
			for( u_int u = 0; u < 3; ++u )
			{
				for( u_int v = 0; v < 3; ++v )
				{
					xStream << (0x3B000000 | (*reinterpret_cast<const u_int*>(&xInstanceMatrix.m[v][u]) >> 8));
				}
			}

			// Output position vector
			for( u_int u = 0; u < 3; ++u )
			{
				xStream << (0x3B000000 | (*reinterpret_cast<const u_int*>(&xInstanceMatrix.m[u][3]) >> 8));
			}

			// Command to return to main display list after uploading the world matrix
			xStream << 0x0B000000; 

			// Static lighting

			Asura_SphericalHarmonics_Order2_RGB xSH;
			Asura_GI_Solution::GetStaticIrradiance( xSH, pxCurrInstance->xPosition );

			xStream << xSH.m_afRedCoefficients[1];
			xStream << xSH.m_afRedCoefficients[2];
			xStream << xSH.m_afRedCoefficients[3];
			xStream << xSH.m_afRedCoefficients[0];

			xStream << xSH.m_afGreenCoefficients[1];
			xStream << xSH.m_afGreenCoefficients[2];
			xStream << xSH.m_afGreenCoefficients[3];
			xStream << xSH.m_afGreenCoefficients[0];

			xStream << xSH.m_afBlueCoefficients[1];
			xStream << xSH.m_afBlueCoefficients[2];
			xStream << xSH.m_afBlueCoefficients[3];
			xStream << xSH.m_afBlueCoefficients[0];

			// Scale factor, needed for billboard clusters
			xStream << pxCurrInstance->fScale;

			// Output position as floats too, to avoid conversions when doing instance frustum cull
			xStream << pxCurrInstance->xPosition;

			// Output module information
			xStream << pxCurrInstance->iModuleIdx; 
		}
	}
}

bool Tools_PSP_Export::ExportBotanicals( HANDLE hOutput, const int* const piMaterialConversionList )
{
	// Don't output the chunk if no data to export
	const u_int uMaxNumBotanicalsInstances = Asura_Botanicals_InstanceManager::GetNumInstances();
	if ( uMaxNumBotanicalsInstances == 0 )
	{
		return true;
	}

	Asura_LinkList< const Asura_Botanicals_Template* > xTemplateExportList;
	Asura_LinkList< const Asura_Shape* > xShapeExportList;
	Asura_LinkList< const Tools_BotanicalShape* > xBotanicalShapeExportList;

	u_int uNumShapesToExport = 0;
	u_int uNumBotanicalShapesToExport = 0;
	u_int uNumTemplatesToExport = 0;

	u_int uFileSize = sizeof( uNumShapesToExport ) + sizeof( uNumBotanicalShapesToExport ) + sizeof( uNumTemplatesToExport );
//	u_int uFileNameSize = xBOTANICALS_RESOURCE_FILENAME.GetSizeInInts() * 4; 

	FindUsedBotanicalsTemplatesAndShapes( uNumTemplatesToExport, xTemplateExportList, uNumShapesToExport, xShapeExportList, uNumBotanicalShapesToExport, xBotanicalShapeExportList, uFileSize );

	// Chunk size is all the data, 'filename' & header
//	u_int uChunkSize = uFileSize + uFileNameSize + sizeof(Asura_Chunk_ResourceFile);
//	Asura_Chunk_ResourceFile xResourceFile;

	Asura_SaveBlock* pxSaveBlock = new Asura_SaveBlock();
/*
	void* const pData = Asura_Allocate( uChunkSize );
	if( !pData )
	{
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Failed to allocate memory for botanicals chunk (needed %u bytes).", uChunkSize );
		return false;
	}

	Asura_Chunk_Memory_Stream xStream( uChunkSize, pData );
*/
	Asura_Chunk_SaveBlock_Stream xStream( pxSaveBlock);

	xStream.SetSwapEndian( Tools_Level_Export::ShouldExportBigEndian() );

	// Write the chunk header

	xStream.WriteChunk_Start( ASURA_CHUNK_RESOURCEFILE, 0, 0 );

//	xStream << static_cast< u_int >( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC ); // Ensure these enums come out 32-bit
//	xStream << static_cast< u_int >( ASURA_RESOURCEFILE_TYPE_PSP_BOTANICALS );
//	xStream << uFileSize;
//	xStream << xBOTANICALS_RESOURCE_FILENAME;

	
	xStream << uNumShapesToExport; 
	xStream << uNumBotanicalShapesToExport; 
	xStream << uNumTemplatesToExport; 

	ExportShapes( xStream, xShapeExportList, piMaterialConversionList ); 
	
	ExportBotanicalShapes( xStream, xBotanicalShapeExportList, piMaterialConversionList ); 

	ExportBotanicalsTemplates( xStream, xTemplateExportList, xShapeExportList, xBotanicalShapeExportList ); 

	xStream.WriteChunk_End(); 

	// Write the in-memory chunk data to the export file in one call

#define WRITEOUTDATA(a,b) WriteFile( hOutput, a, b, &bytesWritten, 0 );

	{
		// DLH - 18 December 2006 16:23:26 - Extra the chunk data and save to the file
		const uint32 uBytesHeaderSize = sizeof( Asura_Chunk_Header) + ASURA_FILE_ID_NUM_CHARS;
		uint32 uBytesToWrite = pxSaveBlock->GetSize() - uBytesHeaderSize;
		const void* pDataStart = pxSaveBlock->GetStartPtr() + uBytesHeaderSize;
		const u_int uBytesResourceFilename = 4 * xBOTANICALS_RESOURCE_FILENAME.GetSizeInInts();
		uint32 uChunkSize = uBytesToWrite + uBytesResourceFilename + sizeof( Asura_Chunk_ResourceFile);

		Asura_Chunk_ResourceFile* pxResourceFile = new Asura_Chunk_ResourceFile;

		pxResourceFile->Create
			(
			uChunkSize,
			0,
			ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
			ASURA_RESOURCEFILE_TYPE_PSP_BOTANICALS,
			uBytesToWrite
			);

		unsigned long bytesWritten;

		WRITEOUTDATA( pxResourceFile, sizeof(Asura_Chunk_ResourceFile) );
		const char* szStringResourceFilename = xBOTANICALS_RESOURCE_FILENAME.GetString();
		WRITEOUTDATA( szStringResourceFilename,  uBytesResourceFilename);

		WRITEOUTDATA( pDataStart, uBytesToWrite);

		delete pxResourceFile;
	}

#undef WRITEOUTDATA

//	Asura_Deallocate( pData );
	delete pxSaveBlock;

//	if( !bSuccess || uNumBytesWritten != uBytesToWrite )
//	{
//		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Failed to write botanicals chunk." );
//		return false;
//	}

	return true; 
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::FindUsedBotanicalsTemplatesAndShapes()
//
// Produces lists of botanicals templates actually used in the current level
// and the botanicals shapes they use. Also computes file size required for this
// export data
//
// Parameters:		uNumUsedTemplates - Reference to output number of templates
//										that are in the list
//					xUsedTemplateList - Reference to output list of templates
//										used in the level
//					uNumUsedShapes - Reference to output number of shapes that
//									 are in the list
//					xUsedShapeList - Reference to output list of shapes used by
//									 templates in the level
//					uNumUsedBotanicalShapes - Reference to output number of botanical 
//									 shapes that are in the list
//					xUsedBotanicalsShapeList - Reference to output list of botanical shapes 
//									 used by templates in the level
//					uFileSize - Reference to file size that will be incremented
//								as appropriate for this export
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::FindUsedBotanicalsTemplatesAndShapes( u_int& uNumUsedTemplates, Asura_LinkList< const Asura_Botanicals_Template* >& xUsedTemplateList,
															 u_int& uNumUsedShapes, Asura_LinkList< const Asura_Shape* >& xUsedShapeList,
															 u_int& uNumUsedBotanicalShapes, Asura_LinkList< const Tools_BotanicalShape* >& xUsedShapeBotanicalList,
															 u_int& uFileSize )
{
	const int iNumInstances = Asura_Botanicals_InstanceManager::GetNumInstances();

	const u_int uInstanceSize = 
								sizeof( u_int )				// Start world matrix load command
							  + sizeof( float ) * 3 * 4		// Transform
							  + sizeof( u_int )				// Return command after uploading matrix
							  + sizeof( float ) * 4 * 3		// Spherical harmonic for static lighting
							  + sizeof( float )				// Scale, needed by billboards
							  + sizeof( Asura_Vector_3 )	// Position as floats, used for CPU-side frustum cull
							  + sizeof( int );				// Module index

	// Look through all instances and gather the used templates and shapes

	int iCurrInstanceTimes100 = 0;

	const Asura_LinkList< Asura_Botanicals_Instance* >& xInstanceList = Asura_Botanicals_InstanceManager::GetInstanceList();

	for( Asura_ListElt< Asura_Botanicals_Instance* >* pxCurrInstanceListEntry = xInstanceList.Front();
		 pxCurrInstanceListEntry;
		 pxCurrInstanceListEntry = pxCurrInstanceListEntry->Next() )
	{
		Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_BOTANICALS_FIND_USED_TEMPLATES_AND_SHAPES, "Botanicals: Finding used templates and shapes...", iCurrInstanceTimes100 / iNumInstances );
		iCurrInstanceTimes100 += 100;

		const Asura_Botanicals_Instance* const pxCurrInstance = pxCurrInstanceListEntry->Data();
		const Asura_Botanicals_Template* const pxCurrTemplate = Asura_Botanicals_TemplateManager::GetTemplate( pxCurrInstance->iTemplateIdx );

		if ( pxCurrTemplate->bMergeRenderMeshWithEnv 
		|| ( pxCurrTemplate->eType == ASURA_BOTANICALS_TEMPLATE_BOTANICALSHAPE	&& !pxCurrTemplate->pxBotanicalShape )
		|| ( pxCurrTemplate->eType == ASURA_BOTANICALS_TEMPLATE_SHAPE			&&  pxCurrTemplate->uShapeID == ASURA_HASH_ID_UNSET ) )
		
		{
			continue;
		}

		uFileSize += uInstanceSize;

		if( xUsedTemplateList.Find( pxCurrTemplate ) ) continue;

		xUsedTemplateList.Push_Back( pxCurrTemplate );
		++uNumUsedTemplates;

		// TODO: add this instance to a list for this template

		if( pxCurrTemplate->eType == ASURA_BOTANICALS_TEMPLATE_SHAPE )
		{
			const Asura_Shape* pxShape = Asura_Shape_System::GetShape(pxCurrTemplate->uShapeID);
			Asura_Assertf( pxShape, ("Error: could not find shape '%s' (ID '0x%x') used by botanical template '%s'. Does the file exist?", pxCurrTemplate->xShapeName.GetString(), pxCurrTemplate->uShapeID, pxCurrTemplate->xTemplateName.GetString() ));

			if ( !xUsedShapeList.Find( pxShape ) )
			{
				xUsedShapeList.Push_Back( pxShape ); 
				++uNumUsedShapes; 
			}

		}
		else
		if( pxCurrTemplate->eType == ASURA_BOTANICALS_TEMPLATE_BOTANICALSHAPE )
		{
			const Tools_BotanicalShape* const pxToolsShape = pxCurrTemplate->pxBotanicalShape->GetToolsShape();

			if( !xUsedShapeBotanicalList.Find( pxToolsShape ) )
			{
				xUsedShapeBotanicalList.Push_Back( pxToolsShape );
				++uNumUsedBotanicalShapes;
			}
		}
		else
		{
			Asura_Assert(false, "Unknown botanical type");
		}
	}

	u_int uCurrShapeTimes100 = 0;

	// Now that we know which shapes are in the level, calculate their export file size

	for( Asura_ListElt< const Asura_Shape* >* pxCurrShapeListEntry = xUsedShapeList.Front();
		 pxCurrShapeListEntry;
		 pxCurrShapeListEntry = pxCurrShapeListEntry->Next() )
	{
		const Asura_Shape* const pxCurrShape =  pxCurrShapeListEntry->Data();
		if( !pxCurrShape ) continue;
		
		Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_BOTANICALS_CALC_SHAPE_EXPORT_SIZE, "Botanicals: calculating shape export size...", uCurrShapeTimes100 / uNumUsedShapes );
		uCurrShapeTimes100 += 100;

		uFileSize += sizeof( Asura_Hash_ID ); // Shape name hash ID
		uFileSize += sizeof( u_int ); // Number of instances of this template

		IncrementExportSize_Shape( uFileSize, *pxCurrShape ); 
	}

	// Do the same thing for botanical shapes
	for( Asura_ListElt< const Tools_BotanicalShape* >*  pxCurrBotanicalShapeListEntry = xUsedShapeBotanicalList.Front();
		  pxCurrBotanicalShapeListEntry;
		  pxCurrBotanicalShapeListEntry =  pxCurrBotanicalShapeListEntry->Next() )
	{
		const Tools_BotanicalShape* const pxCurrShape =  pxCurrBotanicalShapeListEntry->Data();
		if( !pxCurrShape ) continue;

		Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_BOTANICALS_CALC_SHAPE_EXPORT_SIZE, "Botanicals: calculating shape export size...", uCurrShapeTimes100 / uNumUsedBotanicalShapes );
		uCurrShapeTimes100 += 100;

		uFileSize += sizeof( Asura_Hash_ID ); // Shape name hash ID
		uFileSize += sizeof( u_int ); // Number of instances of this template

		IncrementExportSize_BotanicalShape( uFileSize, *pxCurrShape ); 
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::IncrementExportSize_Shape()
//
// Calculates the size required to export a given shape and
// increments the passed count by that much
//
// Parameters:		uFileSize - File size. Will be incremented as appropriate
//					xShape - Shape to process
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::IncrementExportSize_Shape( u_int& uFileSize, const Asura_Shape& xShape )
{
	uFileSize += 4 * 4; // Shape ID + Type + Bounding box height + Bounding box radius (in bytes)
	IncrementExportSize_Geometry( uFileSize, xShape.GetRenderData()->GetMesh() ); 
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::IncrementExportSize_BotanicalShape()
//
// Calculates the size required to export a given botanical shape and
// increments the passed count by that much
//
// Parameters:		uFileSize - File size. Will be incremented as appropriate
//					xBotanicalShape - Botanical shape to process
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////
void Tools_PSP_Export::IncrementExportSize_BotanicalShape( u_int& uFileSize, const Tools_BotanicalShape& xBotanicalShape )
{
	// Loop over each element
	const int iNumElements = xBotanicalShape.GetNumElements(); 

	uFileSize += sizeof( Asura_Hash_ID ); // Shape name hash ID
	uFileSize += sizeof( iNumElements ); // Number of instances of this template 
	uFileSize += sizeof( u_int ) * iNumElements; // Element type

	for( int i = 0; i < iNumElements; ++i )
	{
		const Tools_BotanicalElement* const pxCurrElement = xBotanicalShape.GetElementByIndex(i);

		if( !pxCurrElement )
		{
			Asura_Assert( false, "Botanicals element is invalid." );
			continue;
		}

		const int iNumLODs = pxCurrElement->GetNumLODs();

		uFileSize += sizeof( iNumLODs )
					+ sizeof( float ) * 2; // Bounding sphere data

		// Loop over each LOD level

		for( int j = 0; j < iNumLODs; ++j )
		{
			const Tools_BotanicalElementLOD* const pxCurrLOD = pxCurrElement->GetLODByIndex(j);
			if( !pxCurrLOD )
			{
				Asura_Assert( false, "Botanicals LOD is invalid." );
				continue;
			}

			switch( pxCurrElement->m_eType )
			{
				case TOOLS_BOTANICALS_ELEMENTTYPE_1SIDED_MESH:
				case TOOLS_BOTANICALS_ELEMENTTYPE_2SIDED_MESH:
				{
					IncrementExportSize_Geometry( uFileSize, pxCurrLOD->m_pxMesh );
					break;
				}

				case TOOLS_BOTANICALS_ELEMENTTYPE_BILLBOARDS:
				{
					IncrementExportSize_Billboards( uFileSize, *pxCurrLOD );
					break;
				}

				case TOOLS_BOTANICALS_ELEMENTTYPE_IMPOSTER:
				{
					IncrementExportSize_Imposter( uFileSize, *pxCurrLOD );
					break;
				}

				default:
				{
					Asura_Assert( false, "Unknown botanical element type." );
					break;
				}
			}

			// Fade data
			uFileSize += sizeof( float ) * 4	// Fade data
					   + sizeof( u_int );		// Number of visible instances. Not really needed at export, but easier to use one structure
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::IncrementExportSize_Geometry()
//
// Calculates the size required to export a given geometric botanicals LOD and
// increments the passed count by that much
//
// Parameters:		uFileSize - File size. Will be incremented as appropriate
//					xElementLOD - Geometry LOD to process
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::IncrementExportSize_Geometry( u_int& uFileSize, Asura_Mesh* pxElementLOD )
{
	// Size of the geometry (in bytes)
	const unsigned int uVERTEX_SIZE = (3 + 3 + 2) * 4; // Position + normal + UV

	// Size of each index element
	const unsigned int uINDEX_SIZE = 2; // Index are shorts (2 bytes)

	unsigned int uShapeSize = 4 * 4; // Material ID + Number of indices + Number of vertices + Number of triangles 

	// We'll assume only one material per LOD mesh so we can use the object processor
	const int iMaterialID = pxElementLOD->TextureDataPtr[0].m_iMaterialID;

	if( !Tools_ObjectProcessor::Initialise( pxElementLOD, iMaterialID ) ) return;

	const Tools_ObjectProcessor_Strip* pxStrip = Tools_ObjectProcessor::GetProcessedStrip();

	uShapeSize += pxStrip->m_uNumberOfVertices * uVERTEX_SIZE; 
	uShapeSize += pxStrip->m_uNumberOfIndices * uINDEX_SIZE; 

	uFileSize += uShapeSize; // Convert shape size to bytes

	Tools_ObjectProcessor::Shutdown();
	

}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::IncrementExportSize_Billboards()
//
// Calculates the size required to export a given billboards botanicals LOD and
// increments the passed count by that much
//
// Parameters:		uFileSize - File size. Will be incremented as appropriate
//					xElementLOD - Billboards LOD to process
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::IncrementExportSize_Billboards( u_int& uFileSize, const Tools_BotanicalElementLOD& xElementLOD )
{
	uFileSize += 3 * sizeof( u_int ); // Material ID, number of billboards, number of vertices
	
	const Tools_BotanicalRenderable_Billboards xRenderable( &xElementLOD );
	unsigned int uNumberOfBillboards = xRenderable.GetNumBillboards(); 

	uFileSize += ( uNumberOfBillboards * 4 * 4 * sizeof( float ));	// 4 vertices per billboard, 4 floats per vertex: X, Y, U, V

	uFileSize += ( uNumberOfBillboards * 3 * sizeof( float )); // Billboard position
	uFileSize += ( uNumberOfBillboards * 3 * sizeof( float )); // Billboard normal
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PSP_Export::IncrementExportSize_Imposter()
//
// Calculates the size required to export a given imposter botanicals LOD and
// increments the passed count by that much
//
// Parameters:		uFileSize - File size. Will be incremented as appropriate
//					xElementLOD - Imposter LOD to process
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PSP_Export::IncrementExportSize_Imposter( u_int& uFileSize, const Tools_BotanicalElementLOD& xElementLOD )
{
}


#endif // #if defined( ASURA_USE_BOTANICALS )
