//
// Tools_PS3_Export.cpp
//
// Created by KJFL - 24 June 2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "PS3/Tools_PS3_Export.h"

// Engine independent.
#include "Animation/Asura_Hierarchy.h"
#include "Botanical/Asura_Direct3D_Botanicals_Game_Render.h"
#include "Botanical/Asura_Direct3D_Botanicals_Game_Render_Manager.h"
#include "Botanicals/Asura_Botanical_Game_Render_Manager.h"
#include "Botanicals/Asura_Botanical_Game_Render_Type.h"
#include "Botanicals/Asura_Botanical_Game_Render_Type_BasicInteractive.h"
#include "Botanicals/Asura_Botanicals_InstanceManager.h"
#include "Botanicals/Asura_Botanicals_TemplateManager.h"
#include "ChunkSystem/Asura_Chunk_ResourceFile.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Asura_HalfFloat.h"
#include "Core/Asura_LinkList.h"
#include "Core/Asura_List.h"
#include "Core/Asura_MemoryManagement_Enums.h"
#include "Core/Asura_TextureManagement.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Module.h"

// Engine platform-specific.
#include "ChunkSystem/Asura_Chunk_Stream_WindowsFile.h"
#include "Rendering/Asura_Direct3D.h"

// Engine PS3.
#include "PS3/ChunkSystem/Asura_Chunk_ResourceFile_PS3.h"
#include "PS3/ChunkSystem/Asura_Chunk_ResourceFile_PS3_Botanicals.h"

// Tools.
#include "BotanicalCreation/Tools_BotanicalCreation_BotanicalShape.h"
#include "BotanicalCreation/Tools_BotanicalCreation_Rendering.h"
#include "ImageProcessing/Tools_PS3_Texture_Writer.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_TextureConversionData.h"
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
#include "Materials/Tools_MaterialResponse_System.h"
#endif
#include "MeshProcessing/Tools_AAEdges.h"
#include "MeshProcessing/Tools_EnvironmentProcessor.h"
#include "MeshProcessing/Tools_HierarchyProcessor_MultiMaterial_NormalMapped.h"
#include "MeshProcessing/Tools_MeshRestartIndex.h"
#include "MeshProcessing/Tools_ObjectProcessor_NormalMapped.h"
#include "MeshProcessing/Tools_SmoothSkin_Processor.h"
#include "MeshProcessing/Tools_SmoothSkin_Processor_PS3.h"
#include "Misc/list_tem.hpp"
#include "Misc/Tools_Adjacency.h"
#include "Misc/Tools_Timed_Messages.h"
#include "PC/FileSystem/Tools_FileSystem.h"

#ifdef ASURA_USE_STATIC_DECAL_SYSTEM
#include "SpecialFX/Asura_Decal_Template.h"
#include "Decals/Tools_StaticDecal_System.h"
#endif

// M A C R O S ///////////////////////////////////////////////////////////////

/******************************************************************************/
/* Macros                                                                     */
/******************************************************************************/
#define VECTOR_TO_UINT_PS3( xVector )								\
(																	\
	( ( static_cast< u_int >( xVector.x * 511.0f ) & 0x7FF ) <<  0)	\
  + ( ( static_cast< u_int >( xVector.y * 511.0f ) & 0x7FF ) << 11)	\
  + ( ( static_cast< u_int >( xVector.z * 511.0f ) & 0x3FF ) << 22)	\
)

//#define MERGE_STRIPS

// C O N S T A N T S /////////////////////////////////////////////////////////

const Asura_String xENV_RESOURCE_FILENAME( "Env" );
const Asura_String xBOTANICALS_RESOURCE_FILENAME( "Botanicals" );
const Asura_String xSTATICDECALS_RESOURCE_FILENAME( "StaticDecals" );

// T Y P E S /////////////////////////////////////////////////////////////////

struct MultiStripBuffer
{
    u_int uFirstStripIndex;
    u_int uLastStripIndex;
    u_int uNumEntries;      // Total number of vertices or indices.
};

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

/******************************************************************************/
/* Tools_PS3_Export Static Class Functions                                */
/******************************************************************************/

// (AAM - 12.06.2006) Uncomment this to merge strips with the same material across modules,
// leaving just one strip for each material in the environment. Eventually this might move
// up into independent code if it's a win on other platforms. Downside is dynamic lights
// have an even bigger hit since each strip's BB becomes potentially much larger.

//#define MERGE_STRIPS

#if defined MERGE_STRIPS

static bool CopyStrip( Tools_EnvironmentProcessor_Strip& xDest, const Tools_EnvironmentProcessor_Strip& xSource )
{
	delete[] xDest.m_pusIndices;
	delete[] xDest.m_pxVertices;

	xDest = xSource;

	// Copy indices

	xDest.m_pusIndices = new u_short[ xSource.m_uNumberOfIndices ];
	if( !xDest.m_pusIndices ) return false;

	memcpy( xDest.m_pusIndices, xSource.m_pusIndices, sizeof( u_short ) * xSource.m_uNumberOfIndices );

	// Copy vertices

	xDest.m_pxVertices = new Tools_EnvironmentProcessor_Vertex[ xSource.m_uNumberOfVertices ];
	if( !xDest.m_pxVertices ) return false;

	memcpy( xDest.m_pxVertices, xSource.m_pxVertices, sizeof( Tools_EnvironmentProcessor_Vertex ) * xSource.m_uNumberOfVertices );

	return true;
}

static bool AppendStrip( Tools_EnvironmentProcessor_Strip& xDest, const Tools_EnvironmentProcessor_Strip& xSource )
{
	// Allocate memory for the merged data

	const u_int uTotalNumIndices = xDest.m_uNumberOfIndices + xSource.m_uNumberOfIndices + 1;

	u_short* pusNewIndices = new u_short[ uTotalNumIndices ];
	if( !pusNewIndices ) return false;

	Tools_EnvironmentProcessor_Vertex* pxNewVertices = new Tools_EnvironmentProcessor_Vertex[ xDest.m_uNumberOfVertices + xSource.m_uNumberOfVertices ];
	if( !pxNewVertices )
	{
		delete[] pusNewIndices;
		return false;
	}

	u_short usRestartValue;
	Tools_MeshRestartIndex::IsValidForCurrentPlatform( usRestartValue );

	// Append the new set of indices

	memcpy( pusNewIndices, xDest.m_pusIndices, sizeof( u_short ) * xDest.m_uNumberOfIndices );
	pusNewIndices[ xDest.m_uNumberOfIndices ] = usRestartValue;
	memcpy( pusNewIndices + xDest.m_uNumberOfIndices + 1, xSource.m_pusIndices, sizeof( u_short ) * xSource.m_uNumberOfIndices );

	// Patch up the index values in the second set

	for( u_int u = xDest.m_uNumberOfIndices + 1; u < uTotalNumIndices; ++u )
	{
		if( pusNewIndices[u] != usRestartValue )
		{
			pusNewIndices[u] = static_cast< u_short >( pusNewIndices[u] + xDest.m_uNumberOfVertices );
		}
	}

	delete[] xDest.m_pusIndices;
	xDest.m_pusIndices = pusNewIndices;

	// Append the new set of vertices

	memcpy( pxNewVertices, xDest.m_pxVertices, sizeof( Tools_EnvironmentProcessor_Vertex ) * xDest.m_uNumberOfVertices );
	memcpy( pxNewVertices + xDest.m_uNumberOfVertices, xSource.m_pxVertices, sizeof( Tools_EnvironmentProcessor_Vertex ) * xSource.m_uNumberOfVertices );

	delete[] xDest.m_pxVertices;
	xDest.m_pxVertices = pxNewVertices;

	// Increment counts

	xDest.m_uNumberOfIndices += xSource.m_uNumberOfIndices + 1;
	xDest.m_uNumberOfTriangles += xSource.m_uNumberOfTriangles;
	xDest.m_uNumberOfVertices += xSource.m_uNumberOfVertices;

	return true;
}

static Tools_EnvironmentProcessor_Strip* MergeStrips( const Tools_EnvironmentProcessor_Strip* const pxOriginalStrips, const u_int uNumOriginalStrips, u_int& uNumNewStrips )
{
	// Merge all strips with the same material

	Tools_EnvironmentProcessor_Strip* pxNewStrips = new Tools_EnvironmentProcessor_Strip[ uNumOriginalStrips ];
	if( !pxNewStrips ) return NULL;

	if( !CopyStrip( pxNewStrips[0], pxOriginalStrips[0] ) ) return NULL;

	u_int uStripIndex = 0;
	u_int uOriginalStripIndex = 1;

	while( uOriginalStripIndex < uNumOriginalStrips )
	{
		const Tools_EnvironmentProcessor_Strip& xPreviousOriginalStrip = pxOriginalStrips[ uOriginalStripIndex - 1 ];
		const Tools_EnvironmentProcessor_Strip& xThisOriginalStrip = pxOriginalStrips[ uOriginalStripIndex ];

		if( Asura_Material_System::CompareMaterials( xPreviousOriginalStrip.m_iUnalteredMaterialID, xThisOriginalStrip.m_iUnalteredMaterialID ) == 0
		 && pxNewStrips[ uStripIndex ].m_uNumberOfVertices + xThisOriginalStrip.m_uNumberOfVertices < 65536 )
		{
			// Append this to the previous strip

			if( !AppendStrip( pxNewStrips[ uStripIndex ], xThisOriginalStrip ) ) return NULL;
		}
		else
		{
			// Start off a new entry

			++uStripIndex;

			if( !CopyStrip( pxNewStrips[ uStripIndex ], xThisOriginalStrip ) ) return NULL;
		}

		++uOriginalStripIndex;
	}

	uNumNewStrips = uStripIndex + 1;

	return pxNewStrips;
}

static int GetEnvShaderOrderingForMaterial( int iMaterialID )
{
    int iOrder = 0;

    // The sorting here is fairly arbitrary. We're interested in minimising
    // changes rather than ordering by more expensive shaders. That isn't
    // necessary when we're doing a z pass first at runtime anyway.
    //
    // Obviously this code is highly dependent on PS3 runtime code and must
    // be kept up to date relative to it.

    // First check things that mean a shader switch.
    {
        enum
        {
            SHADERFEATURE_DUAL_DIFFUSE    = 1 << 0,
            SHADERFEATURE_DUAL_NORMAL     = 1 << 1,
            SHADERFEATURE_PARALLAXMAPPING = 1 << 2,
            SHADERFEATURE_SPECULAR        = 1 << 3,
        };

        int iFeatures = 0;

        if( Asura_Material_System::IsMaterialDualTexture(iMaterialID) )
        {
            iFeatures |= SHADERFEATURE_DUAL_DIFFUSE;
        }
        
        if( Asura_Material_System::IsMaterialDualNormalMap(iMaterialID) )
        {
            iFeatures |= SHADERFEATURE_DUAL_NORMAL;
        }

        if( Asura_Material_System::IsMaterialParallaxMapping(iMaterialID) )
        {
            iFeatures |= SHADERFEATURE_PARALLAXMAPPING;
        }

        if( Asura_Material_System::IsMaterialSpecularSharp(iMaterialID) || Asura_Material_System::IsMaterialSpecularSoft(iMaterialID) )
        {
            iFeatures |= SHADERFEATURE_SPECULAR;
        }

        // Stick this value in the upper 16 bits, since it's the most
        // significant.
        iOrder |= iFeatures << 16;
    }

    // Check things that mean a parameter change.
    {
        enum
        {
            PARAMETER_SELFILLUM      = 1 << 0,
            PARAMETER_OUTPUTTEXALPHA = 1 << 1,
        };

        int iParameters = 0;

        if( Asura_Material_System::IsMaterialSelfIlluminating(iMaterialID) )
        {
            iParameters |= PARAMETER_SELFILLUM;
        }

        if( Asura_Material_System::IsMaterialPS2AdditiveAlpha(iMaterialID) || Asura_Material_System::IsMaterialAlphaDecal(iMaterialID) )
        {
            iParameters |= PARAMETER_OUTPUTTEXALPHA;
        }

        iOrder |= iParameters;
    }

    return iOrder;
}


static int GetEnvRenderOrderingForMaterial( int iMaterialID )
{
    enum RENDERORDER
    {
        RENDERORDER_OPAQUE,
        RENDERORDER_ALPHADECAL,
        RENDERORDER_TRANSPARENT,
        RENDERORDER_ADDITIVE,
    };

    if( Asura_Material_System::IsMaterialPS2AdditiveAlpha(iMaterialID) )
    {
        return RENDERORDER_ADDITIVE;
    }
    else if( Asura_Material_System::IsMaterialTransparent(iMaterialID) )
    {
        return RENDERORDER_TRANSPARENT;
    }
    else if( Asura_Material_System::IsMaterialAlphaDecal(iMaterialID) )
    {
        return RENDERORDER_ALPHADECAL;
    }
    else
    {
        return RENDERORDER_OPAQUE;
    }
}


static int GetTextureOrderingForMaterial( int iMaterialID )
{
    // This ordering won't necessarily be optimal in terms of resultant
    // switches. However, texture switches seem to be one of our more minor
    // costs and this ought to at least be better than whatever random order
    // the strips would be in otherwise.

    int iOrder = 0;

    iOrder += Asura_Material_System::GetTextureID( iMaterialID, 0 );
    iOrder += ASURA_MAX_NO_OF_TEXTURES;

    iOrder += Asura_Material_System::GetNormalMapID( iMaterialID, 0 );
    iOrder += ASURA_MAX_NO_OF_TEXTURES;

    iOrder += Asura_Material_System::GetTextureID( iMaterialID, 1 );
    iOrder += ASURA_MAX_NO_OF_TEXTURES;

    iOrder += Asura_Material_System::GetNormalMapID( iMaterialID, 1 );
    iOrder += ASURA_MAX_NO_OF_TEXTURES;

    return iOrder;
}


static int CompareEnvStrips( const void* pvStrip0, const void* pvStrip1 )
{
	const Tools_EnvironmentProcessor_Strip* pxStrip0 = static_cast<const Tools_EnvironmentProcessor_Strip*>( pvStrip0 );
	const Tools_EnvironmentProcessor_Strip* pxStrip1 = static_cast<const Tools_EnvironmentProcessor_Strip*>( pvStrip1 );

    const int iMaterialID0 = pxStrip0->m_iUnalteredMaterialID;
    const int iMaterialID1 = pxStrip1->m_iUnalteredMaterialID;

    // Early out for identical material IDs.
    if( iMaterialID0 == iMaterialID1 ) { return 0; }

    // First we have to respect render order.
    {
        int iRenderOrder0 = GetEnvRenderOrderingForMaterial( iMaterialID0 );
        int iRenderOrder1 = GetEnvRenderOrderingForMaterial( iMaterialID1 );

        if( iRenderOrder0 != iRenderOrder1 )
        {
            return iRenderOrder0 - iRenderOrder1;
        }
    }

    // Next, we care most about switching shader or shader constants.
    {
        int iShaderOrder0 = GetEnvShaderOrderingForMaterial( iMaterialID0 );
        int iShaderOrder1 = GetEnvShaderOrderingForMaterial( iMaterialID1 );

        if( iShaderOrder0 != iShaderOrder1 )
        {
            return iShaderOrder0 - iShaderOrder1;
        }
    }

    // Finally, try and reduce minimise texture switches.
    {
        int iTextureOrder0 = GetTextureOrderingForMaterial( iMaterialID0 );
        int iTextureOrder1 = GetTextureOrderingForMaterial( iMaterialID1 );

        if( iTextureOrder0 != iTextureOrder1 )
        {
            return iTextureOrder0 - iTextureOrder1;
        }
    }

    // Nothing to choose between them. Just order by material ID.
    // (MP - 28.06.2006) TODO: We probably want to avoid getting here by
    // removing duplicate materials, etc.
    return iMaterialID0 - iMaterialID1;
}

#endif // defined MERGE_STRIPS
// C L A S S E S /////////////////////////////////////////////////////////////

#if defined( ASURA_USE_BOTANICALS)
class Tools_PS3_Export_Botanical_Template
{
public:
	const Asura_Botanicals_Template* m_pxTemplate;

	int m_iModuleIndex;
	int m_iShapeIndex;

	Asura_LinkList< const Asura_Botanicals_Instance*> m_xListInstances;
};
#endif

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_PS3_Export::EnvironmentVertex::WriteToChunkStream
// Author       : Kevin Floyer-Lea
// Date         : 22-Sep-2005
//
// Description  : Writes this vertex to the specified chunk-stream.
//
// Parameters   : A reference to the chunk-stream to write the data to.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Tools_PS3_Export::EnvironmentVertex::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << m_xPosition;

	Asura_Endian::SwapBytes((char*)&m_uDiffuse, sizeof(m_uDiffuse));
	xStream << m_uDiffuse;
	
	xStream << m_uNormal;
	xStream << m_uTangent;
	xStream << m_uBinormal;
	xStream << m_uTangent2;
	xStream << m_uBinormal2;

	xStream << m_xU;
	xStream << m_xV;
	xStream << m_xU2;
	xStream << m_xV2;

	xStream << Asura_WriteToStream_Array_Wrapper< Asura_HalfFloat >( 4, m_axSHRedCoefficients );
	xStream << Asura_WriteToStream_Array_Wrapper< Asura_HalfFloat >( 4, m_axSHGreenCoefficients );
	xStream << Asura_WriteToStream_Array_Wrapper< Asura_HalfFloat >( 4, m_axSHBlueCoefficients );
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_PS3_Export::ObjectVertex::WriteToChunkStream
// Author       : Kevin Floyer-Lea
// Date         : 22-Sep-2005
//
// Description  : Writes this vertex to the specified chunk-stream.
//
// Parameters   : A reference to the chunk-stream to write the data to.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Tools_PS3_Export::ObjectVertex::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	xStream << m_xPosition;
	xStream << m_xAlpha;

	xStream << m_xU;
	xStream << m_xV;
	xStream << m_xU2;
	xStream << m_xV2;

	xStream << m_uNormal;
	xStream << m_uTangent;
	xStream << m_uBinormal;
}

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// Tools_PS3_Export
//

//Keep information about a what strips use the current shader and the shader data
class Asura_PS3_Env_Strip_Shader_Sort
{
public:
	Asura_PS3_Env_Strip_Shader_Sort( u_int uMaximumNumberOfStrips )
	{
		m_uShaderID			= ASURA_HASH_ID_UNSET;
		m_iStrips			= 0;
		m_puStrips			= new u_int[ uMaximumNumberOfStrips ];
		Asura_Assert( m_puStrips, "Out of memory" );
	}

	~Asura_PS3_Env_Strip_Shader_Sort()
	{
		CleanUp();
	}

	void CleanUp( void )
	{
		delete[] m_puStrips;
		m_puStrips			= NULL;
		m_uShaderID			= ASURA_HASH_ID_UNSET;
		m_iStrips			= 0;
	}

	Asura_Hash_ID GetShaderID( void )				{ return m_uShaderID; }
	void SetShaderID( Asura_Hash_ID uShaderID )		{ m_uShaderID = uShaderID; }

	int	GetNumberOfStrips( void )					{ return m_iStrips; }


	u_int* GetStrips( void )						{ return m_puStrips; }
	u_int GetStrip( u_int uStrip )					{ return m_puStrips[ uStrip ]; }


	void AddStrip( u_int uStrip )
	{
		m_puStrips[ m_iStrips ] = uStrip;
		++m_iStrips;
	}

protected:
	Asura_Hash_ID	m_uShaderID;
	int				m_iStrips;
	u_int*			m_puStrips;
};

static const Tools_EnvironmentProcessor_Strip* g_pxStrips = NULL;

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

static int Asura_PS3_CompareMaterialHash( const void *pC1, const void *pC2 )
{
	const u_int uIndex1 = *(const u_int*)(pC1);
	const u_int uIndex2 = *(const u_int*)(pC2);
	const Tools_EnvironmentProcessor_Strip& xStrip1 = g_pxStrips[ uIndex1 ];	
	const Tools_EnvironmentProcessor_Strip& xStrip2 = g_pxStrips[ uIndex2 ];	

	if( xStrip1.m_auMaterialResponseHashID[ 0 ] > xStrip2.m_auMaterialResponseHashID[ 0 ] )
	{
		return 1;
	}
	else if( xStrip1.m_auMaterialResponseHashID[ 0 ] < xStrip2.m_auMaterialResponseHashID[ 0 ] )
	{
		return -1;
	}
	else if( xStrip1.m_auMaterialResponseHashID[ 1 ] > xStrip2.m_auMaterialResponseHashID[ 1 ] )
	{
		return 1;
	}
	else if( xStrip1.m_auMaterialResponseHashID[ 1 ] < xStrip2.m_auMaterialResponseHashID[ 1 ] )
	{
		return -1;
	}
	return 0;
}

#endif //#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM


void Tools_PS3_Export::ExportEnvironment( HANDLE hOutput )
{
	// go grab the environment
	bool bShouldRemoveDuplicateVertices = true; // TODO
	bool bShouldStripify = true;
	bool bShouldSubdivide = true;	// TODO
	Tools_EnvironmentProcessor::Initialise( bShouldRemoveDuplicateVertices, bShouldStripify, bShouldSubdivide );
	Asura_List<Asura_PS3_Env_Strip_Shader_Sort> xSortedStrips;

	// but quit if the user pressed abort during the environment stripify etc.
	if (!Tools_Level_Export::IsAbortFlagSet())
	{
		// get the environment in strips, sorted by material / shader etc

		const Tools_EnvironmentProcessor_Strip* const pxOriginalStrips = Tools_EnvironmentProcessor::GetStripsSortedByMaterial();
		const u_int uNumOriginalStrips = Tools_EnvironmentProcessor::GetTotalNumberOfStrips();
		u_int uNumberOfStrips;

#if defined MERGE_STRIPS

		g_pxStrips = MergeStrips( pxOriginalStrips, uNumOriginalStrips, uNumberOfStrips );

		if( !g_pxStrips )
		{
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Unable to merge environment strips." );
			Tools_EnvironmentProcessor::Shutdown();
			Tools_Level_Export::SetAbortFlag();

			return;
		}

#else
		g_pxStrips = pxOriginalStrips;
		uNumberOfStrips = uNumOriginalStrips;
#endif


		Asura_Chunk_Stream_WindowsFile xStream(hOutput);

		xStream.WriteResourceChunk_Start( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,	ASURA_RESOURCEFILE_TYPE_PS3_ENVIRONMENT, ASURA_PS3_ENVIRONMENT_CHUNK_VERSION, 0, xENV_RESOURCE_FILENAME );
		
		// Write the data...
		xStream << uNumberOfStrips;
		

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		for( u_int s=0; s<uNumberOfStrips; ++s )
		{
			const Tools_EnvironmentProcessor_Strip& xStrip = g_pxStrips[s];	
			Asura_Hash_ID uHash = Tools_PS3_ShaderExport::AddEnvironmentShader( xStrip.m_auMaterialResponseHashID[0], xStrip.m_auMaterialResponseHashID[1] );

			Asura_PS3_Env_Strip_Shader_Sort* pxStrip;
			bool bFound = false;
			for( int uShader = 0; uShader < xSortedStrips.GetNumEntries(); ++uShader )
			{
				pxStrip = xSortedStrips.GetEntryByIndex( uShader );
				if( pxStrip->GetShaderID() == uHash )
				{
					bFound = true;
					break;
				}
			}
			if( !bFound )
			{
				pxStrip = new Asura_PS3_Env_Strip_Shader_Sort( uNumberOfStrips );
				xSortedStrips.AddToList( pxStrip );
			}
			if( pxStrip->GetShaderID() != uHash )
			{
				pxStrip->SetShaderID( uHash );
			}
			pxStrip->AddStrip( s );
			Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_COMPILING_ENVIRONMENT_SHADERS, "Compiling Environment shaders...", s * 100 / uNumberOfStrips );
		}

		for( int uShader = 0; uShader < xSortedStrips.GetNumEntries(); ++uShader )
		{
			Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( uShader );
			qsort( pxStrip->GetStrips(), pxStrip->GetNumberOfStrips(), sizeof(u_int), Asura_PS3_CompareMaterialHash );
			Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_SORTING_STRIPS, "Sorting strips based on shader and material hash...", uShader * 100 / xSortedStrips.GetNumEntries() );
		}
		
		
		//////////////////////////////////////////////////////////////////////////
		// Find and map edges used for anti-aliasing
		Tools_AAEdges xEdges;
		//
		// Find edges
		{
			Asura_Bounding_Box xBB;
			xBB.Reset();
			for( int iShader = 0; iShader < xSortedStrips.GetNumEntries(); iShader++ )
			{
				Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( iShader );
				for( int i = 0; i < pxStrip->GetNumberOfStrips(); i++ )
				{
					const Tools_EnvironmentProcessor_Strip& xStrip = g_pxStrips[ pxStrip->GetStrip( i ) ];	
					for ( u_int v = 0; v < xStrip.m_uNumberOfVertices; ++v )
					{
						xBB.Set( xStrip.m_pxVertices[v].m_xPosition );
					}
				}
			}

			xEdges.Create( xBB, uNumberOfStrips );
		}
		// associate AA edges with specific strips
		{
			xEdges.BeginStripsMapping();
			int iStripId = uNumberOfStrips-1;
			for( int iShader = xSortedStrips.GetNumEntries()-1; iShader >= 0; iShader-- )
			{
				Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( iShader );
				for( int i = pxStrip->GetNumberOfStrips()-1; i >= 0; i-- )
				{
					Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_COMPILING_ENVIRONMENT_SHADERS, "Mapping Anti-Alias edges...", 100 - ((iStripId * 100) / uNumberOfStrips) );
					xEdges.MapStrip( g_pxStrips[ pxStrip->GetStrip( i ) ], iStripId );
					iStripId--;
				}
			}
			xEdges.EndStripsMapping();
		}
		//////////////////////////////////////////////////////////////////////////


		u_int uNumVertexBuffers = 0;
		u_int auNumVerticesInBuffer[256]; //enough for 16 million vertices.
		Asura_Mem::Set( auNumVerticesInBuffer, 0, 256*sizeof(u_int) );

		u_int uAAEdgeOffset = 0;
		int iTotalNumVertices = 0;
		int iStripId = 0;
		for( int s = 0, uShader = 0; uShader < xSortedStrips.GetNumEntries(); ++uShader )
		{
			Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( uShader );
			for( int i = 0; i < pxStrip->GetNumberOfStrips(); ++i, ++s )
			{
				const Tools_EnvironmentProcessor_Strip& xStrip = g_pxStrips[ pxStrip->GetStrip( i ) ];	
				
				if ( auNumVerticesInBuffer[uNumVertexBuffers] + xStrip.m_uNumberOfVertices > 65535 )
				{	
					uNumVertexBuffers++;
				}
				
				// scan through strip vertices and find bounding box
				Asura_Bounding_Box xStripBB;

				xStripBB.Reset();
				for ( u_int v = 0; v < xStrip.m_uNumberOfVertices; ++v )
				{
					xStripBB.Set( xStrip.m_pxVertices[v].m_xPosition );
				}

				xStream << pxStrip->GetShaderID();
				
				xStream << uNumVertexBuffers;
				
				xStream << xStrip.m_uNumberOfVertices;
				xStream << xStrip.m_uNumberOfIndices;
				xStream << xStrip.m_auMaterialResponseHashID[0];
				xStream << xStrip.m_auMaterialResponseHashID[1];

				xStream << xStrip.m_uModule;
				xStream << xStrip.m_uNumberOfTriangles;

				// Export AA edges info
				const u_int	uNumStripEdges = xEdges.GetNumberOfEdgesForStrip( iStripId );
				xStream << uAAEdgeOffset*2;
				xStream << uNumStripEdges*2;
				uAAEdgeOffset += uNumStripEdges;

				xStream << xStripBB;
				
				iTotalNumVertices += xStrip.m_uNumberOfVertices;
				auNumVerticesInBuffer[uNumVertexBuffers] += xStrip.m_uNumberOfVertices;
				iStripId++;
			}
		}
		uNumVertexBuffers++;

		Asura_Assertf( (int)uAAEdgeOffset == xEdges.GetTotalNumberOfEdgeIndices()/2, ( "Edge count mismatched, this is impossible & might crash the game! count %d, num %d", uAAEdgeOffset, xEdges.GetTotalNumberOfEdgeIndices()/2 ) );

		// Vertex Header - number of verts in each buffer
		xStream << uNumVertexBuffers;
		for ( u_int i=0; i < uNumVertexBuffers; i++ )
		{
			Asura_Assertf( auNumVerticesInBuffer[i] < 65536, ( "auNumVerticesInBuffer[i] too big or garbage (0x%08x)", auNumVerticesInBuffer[i] ) );
		
			xStream << auNumVerticesInBuffer[i];
		}

		// Output Vertices
		for( int s = 0, uShader = 0; uShader < xSortedStrips.GetNumEntries(); ++uShader )
		{
			Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( uShader );
			for( int i = 0; i < pxStrip->GetNumberOfStrips(); ++i, ++s )
			{
				const Tools_EnvironmentProcessor_Strip& xStrip = g_pxStrips[ pxStrip->GetStrip( i ) ];	

#if defined( ASURA_ASSERT)
				const u_int uModuleIndex = xStrip.m_uModule;
				const u_int uModuleCount = Asura_Module_System::GetNumberOfModules();
				const Asura_Mesh* pxModuleMesh = uModuleIndex < uModuleCount ? (Asura_Module_System::GetModuleData( uModuleIndex)->Mesh) : NULL;
				const char* pxModuleName = (NULL != pxModuleMesh) ? (pxModuleMesh->Name.GetString()) : "Unknown";
#endif

				for (u_int v = 0; v < xStrip.m_uNumberOfVertices; ++v )
				{
					const Tools_EnvironmentProcessor_Vertex&	xVertex	= xStrip.m_pxVertices [v];
					EnvironmentVertex							xOutputVertex;

					// position
					xOutputVertex.m_xPosition = xVertex.m_xPosition;

					// diffuse
					xOutputVertex.m_uDiffuse = xVertex.m_uDiffuseRGBA;

					// uvs
					xOutputVertex.m_xU	= xVertex.m_fU;
					xOutputVertex.m_xV	= xVertex.m_fV;

					xOutputVertex.m_xU2	= xVertex.m_fU2;
					xOutputVertex.m_xV2	= xVertex.m_fV2;

					// normal/tangent/binormal
					Asura_Assertf( 1e-3f < xVertex.m_xNormal.MagnitudeSquared(), ("Normal Length too small on mesh '%s'", pxModuleName));
					xOutputVertex.m_uNormal = VECTOR_TO_UINT_PS3( xVertex.m_xNormal );

#ifdef ASURA_TARGET_SM3
					Asura_Assertf( 1e-3f < xVertex.m_xTangent.MagnitudeSquared(), ("Tangent Length too small on mesh '%s'", pxModuleName));
					Asura_Assertf( 1e-3f < xVertex.m_xBinormal.MagnitudeSquared(), ("Binormal Length too small on mesh '%s'", pxModuleName));
					Asura_Assertf( 1e-3f < xVertex.m_xTangent2.MagnitudeSquared(), ("Tangent Length too small on mesh '%s'", pxModuleName));
					Asura_Assertf( 1e-3f < xVertex.m_xBinormal2.MagnitudeSquared(), ("Binormal Length too small on mesh '%s'", pxModuleName));

					xOutputVertex.m_uTangent = VECTOR_TO_UINT_PS3( xVertex.m_xTangent );
					xOutputVertex.m_uBinormal = VECTOR_TO_UINT_PS3( xVertex.m_xBinormal );
					xOutputVertex.m_uTangent2 = VECTOR_TO_UINT_PS3( xVertex.m_xTangent2 );
					xOutputVertex.m_uBinormal2 = VECTOR_TO_UINT_PS3( xVertex.m_xBinormal2 );

					// Need to swizzle the spherical harmonics

					// SH Red
					xOutputVertex.m_axSHRedCoefficients[0] = xVertex.m_xDiffuseSH.m_afRedCoefficients[1];
					xOutputVertex.m_axSHRedCoefficients[1] = xVertex.m_xDiffuseSH.m_afRedCoefficients[2];
					xOutputVertex.m_axSHRedCoefficients[2] = xVertex.m_xDiffuseSH.m_afRedCoefficients[3];
					xOutputVertex.m_axSHRedCoefficients[3] = xVertex.m_xDiffuseSH.m_afRedCoefficients[0];

					// SH Green
					xOutputVertex.m_axSHGreenCoefficients[0] = xVertex.m_xDiffuseSH.m_afGreenCoefficients[1];
					xOutputVertex.m_axSHGreenCoefficients[1] = xVertex.m_xDiffuseSH.m_afGreenCoefficients[2];
					xOutputVertex.m_axSHGreenCoefficients[2] = xVertex.m_xDiffuseSH.m_afGreenCoefficients[3];
					xOutputVertex.m_axSHGreenCoefficients[3] = xVertex.m_xDiffuseSH.m_afGreenCoefficients[0];

					// SH Blue
					xOutputVertex.m_axSHBlueCoefficients[0] = xVertex.m_xDiffuseSH.m_afBlueCoefficients[1];
					xOutputVertex.m_axSHBlueCoefficients[1] = xVertex.m_xDiffuseSH.m_afBlueCoefficients[2];
					xOutputVertex.m_axSHBlueCoefficients[2] = xVertex.m_xDiffuseSH.m_afBlueCoefficients[3];
					xOutputVertex.m_axSHBlueCoefficients[3] = xVertex.m_xDiffuseSH.m_afBlueCoefficients[0];
#endif

					// Write this vertex...
					xOutputVertex.WriteToChunkStream( xStream );
				}
				Tools_Level_Export::UpdateStatus( Tools_Level_Export::EXPORT_STAGE_EXPORTING_ENVIRONMENT, "Exporting environment...", s * 100 / uNumberOfStrips );
			}
		}

		// output indices
		Tools_LinkedAntiAliasEdge* pxEdge = xEdges.GetSortedEdges();
		iStripId = 0;
		uint16 uStartVertex = 0;
		for( int uShader = 0; uShader < xSortedStrips.GetNumEntries(); ++uShader )
		{
			Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( uShader );

			for( int i = 0; i < pxStrip->GetNumberOfStrips(); ++i)
			{
				const Tools_EnvironmentProcessor_Strip& xStrip = g_pxStrips[ pxStrip->GetStrip( i ) ];	

				if ( (u_int)uStartVertex + xStrip.m_uNumberOfVertices > 65535 )
				{
					// this strip will be in the next vertex buffer.
					uStartVertex = 0;
				}

				for (u_int j = 0; j < xStrip.m_uNumberOfIndices; ++j)
				{
					uint16 uIndex = (xStrip.m_pusIndices[j]==0xffff) ? 0xffff : xStrip.m_pusIndices[j] + uStartVertex;
				
					xStream << uIndex;
				}

				// update indices on AA edges to match those in strip data.
				Asura_Assertf( xEdges.GetNumberOfEdgesForStrip(iStripId) == 0 || pxEdge->m_iParentIndex == iStripId, ( "This strip expects %d anti-alias edges, but strip id (%d) does not match sorted edge's strip id (%d)", xEdges.GetNumberOfEdgesForStrip(iStripId), iStripId, pxEdge->m_iParentIndex ) );

				while ( pxEdge && pxEdge->m_iParentIndex == iStripId )
				{
					pxEdge->m_auIdx[0] = pxEdge->m_auIdx[0] + uStartVertex;
					pxEdge->m_auIdx[1] = pxEdge->m_auIdx[1] + uStartVertex;

					pxEdge = pxEdge->m_pxNext;
				}
				
				iStripId++;

				uStartVertex = uStartVertex + (uint16)xStrip.m_uNumberOfVertices;
			}
		}

		// Output edges for Anti-Aliasing
		xStream << (int)xEdges.GetTotalNumberOfEdgeIndices();

		int iEdgeCount = 0;
		pxEdge = xEdges.GetSortedEdges();
		while ( pxEdge )
		{
			xStream << pxEdge->m_auIdx[0];
			xStream << pxEdge->m_auIdx[1];
			pxEdge = pxEdge->m_pxNext;
			iEdgeCount++;
		}
		Asura_Assertf( iEdgeCount == xEdges.GetTotalNumberOfEdgeIndices()/2, ( "Sanity check failed, environment anti-alias data is corrupted. (wrote %d edges, expected %d)", iEdgeCount, xEdges.GetTotalNumberOfEdgeIndices()/2 ) );

		for( int uShader = 0; uShader < xSortedStrips.GetNumEntries(); ++uShader )
		{
			Asura_PS3_Env_Strip_Shader_Sort* pxStrip = xSortedStrips.GetEntryByIndex( uShader );
			delete pxStrip;
			pxStrip = NULL;
		}
#else
Asura_Assert( false, "Non material response exports on PS3 are not suported" );
#endif


#if defined MERGE_STRIPS
delete[] g_pxStrips;
#endif

		xStream.WriteResourceChunk_End();

		Tools_EnvironmentProcessor::Shutdown	();
	}

	xSortedStrips.ClearList();
}

bool Tools_PS3_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_CubeTexture*	pxCubeTexture = 0;
    Direct3D_Surface*		pxSurface = 0;
    D3DSURFACE_DESC			xSurfaceDesc;
	Direct3D_Surface*		pxSurfaces[6];
	D3DSURFACE_DESC			xSurfacesDesc[6];
	bool bCubemap			= false;

	//pass the image data to the texture writer
	bool bTextureWriterSucess = false;
	nvTextureFormats eTextureFormat = kDXT5;
	for( int i = 0; i < 6; ++i )
	{
		pxSurfaces[i] = NULL;
	}
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	const u_int uTextureNameHash	= Asura_Texture_Management::GetHashIDFromIndex( iTextureIndex );
	const u_int uUsage				= Tools_MaterialResponse_System::GetTextureUsage( uTextureNameHash );


#if (0)
	//BW I want to move to this system, more like the Xbox, but there's an issue trying to process single channel textures into the correct form.
	//BW I'm going to need to investigate this properly (Mantis #61892).
	const bool bNormalMap			= (uUsage & Tools_MaterialResponse_System::TU_NORMAL_CHANNELS)	!= 0;	// do we need to output
	const bool bAlpha				= (uUsage & Tools_MaterialResponse_System::TU_SINGLE_CHANNEL)	!= 0;	// do we need to output an alpha/single channel?
	const bool bColour				= (uUsage & Tools_MaterialResponse_System::TU_COLOUR_CHANNELS)	!= 0;	// do we need to output RGB channels?
																										 
	if		( bNormalMap )	{ eTextureFormat = kDXT5_NM; }
	else if	( bColour )		{ eTextureFormat = bAlpha ? kDXT5 : kDXT1; }
	else if	( bAlpha )
	{
		eTextureFormat = (xConvertData.m_iTextureFlags & AsuraTextureFlag_HighQualityCompression)
			? kL8	//B8 uncompressed format
			: kA8;	//DXT1_A8
	}
	else					{ eTextureFormat = kDXT5; } // Not used by the material response system. Might be corona, PFX etc. Just pick DXT5
#else
	const bool bNormalMap			= (uUsage & Tools_MaterialResponse_System::TU_NORMAL_CHANNELS)	!= 0;
	const bool bAlpha				= (uUsage & Tools_MaterialResponse_System::TU_TRANSPARENCY)	!= 0;
	const bool bSingleChannel		= (uUsage & Tools_MaterialResponse_System::TU_SINGLE_CHANNEL)	!= 0;
	const bool bColour				= (uUsage & Tools_MaterialResponse_System::TU_COLOUR_CHANNELS)	!= 0;

	if		( bNormalMap )	{ eTextureFormat = kDXT5_NM; }
	else if	( bColour )		{ eTextureFormat = bAlpha ? kDXT5 : kDXT1; }
	else if	( bAlpha )		{ eTextureFormat = kA8; }
	else if ( bSingleChannel ) { eTextureFormat = kL8; } // Asura_PS3_TextureManagement maps this to all 4 channels.
#endif

#endif // defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	HRESULT xResult =   D3DXCreateTextureFromFileExA
                    (
                        Asura_Direct3D::D3DDevice,
                        szInputName,
                        D3DX_DEFAULT,//width
                        D3DX_DEFAULT,//height
                        1,//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;
    }

	if( xImageInfo.Format == D3DFMT_A8 || xImageInfo.Format == D3DFMT_L8 || xImageInfo.Format == D3DFMT_A16B16G16R16F )
	{
		pxTexture->Release();
		pxTexture = NULL;

		HRESULT xResult =   D3DXCreateTextureFromFileEx
			(
			Asura_Direct3D::D3DDevice,
			szInputName,
			D3DX_DEFAULT,//width
			D3DX_DEFAULT,//height
			1,//MipLevels
			0,//Usage
			D3DFMT_A8R8G8B8,//format
			D3DPOOL_SYSTEMMEM,//Pool
			D3DX_FILTER_NONE,//Filter
			D3DX_FILTER_NONE,//MipFilter
			0,//ColorKey
			&xImageInfo,//pSourceInfo
			0,//pPalette
			&pxTexture
			);
		switch ( xImageInfo.Format )
		{
			case D3DFMT_A16B16G16R16F:
				eTextureFormat = kA16B16G16R16F;
				break;
			case D3DFMT_A8:
				eTextureFormat = kA8;
				break;
			case D3DFMT_L8:
// what's this about?
// how does xbox DXT5A this?
// different (better) converter?
				eTextureFormat = kL8;
				break;
		}
		if(FAILED(xResult))
		{
			Tools_Level_Export::AddMessage(Tools_Level_Export::MT_ERROR,"Error %x loading %s",xResult,szInputName);
			return false;
		}
	}

	if( xImageInfo.ResourceType == D3DRTYPE_CUBETEXTURE )
	{
		pxTexture->Release();
		pxTexture = NULL;

		HRESULT xResult =   D3DXCreateCubeTextureFromFileEx
			(
			Asura_Direct3D::D3DDevice,
			szInputName,
			D3DX_DEFAULT,//width
			1,//MipLevels
			0,//Usage
			D3DFMT_A8R8G8B8,//format
			D3DPOOL_SYSTEMMEM,//Pool
			D3DX_FILTER_NONE,//Filter
			D3DX_FILTER_NONE,//MipFilter
			0,//ColorKey
			&xImageInfo,//pSourceInfo
			0,//pPalette
			&pxCubeTexture
			);  
		bCubemap = true;
		//get the surface
		D3DLOCKED_RECT xRects[6];
		for( int i = 0; i < 6; ++i )
		{
			xResult = pxCubeTexture->GetCubeMapSurface( (D3DCUBEMAP_FACES)i, 0, &pxSurfaces[ i ] );
			if( FAILED( xResult ) )
			{
				Asura_Assert(0,"Failed to get texture surface");
				goto Clearup;
			}

			//get the surface description
			xResult = pxSurfaces[i]->GetDesc(&xSurfacesDesc[i]);
			if(FAILED(xResult))
			{
				Asura_Assert(0,"Failed to get texture surface description");
				goto Clearup;
			}

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

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

			// lock the surface so we can access the pixel data
			xResult = pxSurfaces[i]->LockRect(&xRects[i],0,D3DLOCK_READONLY);
			if(FAILED(xResult))
			{
				Asura_Assert( false, "Failed to lock the texture surface" );
				goto Clearup;
			}
		}
		bool bAlphaOnly = false;

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		if( bAlpha && !bColour && !bNormalMap )
		{
			bAlphaOnly = true;
		}
#endif
		Tools_PS3_Texture_Writer xWriter( xSurfacesDesc[0].Width, xSurfacesDesc[0].Height, xRects[0].Pitch, xRects[0].pBits, xRects[1].pBits, xRects[2].pBits, xRects[3].pBits, xRects[4].pBits, xRects[5].pBits, &xConvertData, eTextureFormat, bTextureWriterSucess, Tools_PS3_Texture_Writer::MAXIMUM_NUMBER_OF_IMAGES, bAlphaOnly );
		Asura_Assertf( true == bTextureWriterSucess, ("PS3 Texture Writer Failed to Create PS3 version of '%s'", szInputName));

		bSuccess = xWriter.WriteOutFile(szOutputName);

		// unlock it
		for(int i = 0; i < 6; ++i )
		{
			xResult = pxSurfaces[ i ]->UnlockRect();
			Asura_Assert( !FAILED(xResult), "Error unlocking surface" );
		}
	}
	else
	{
		//get the surface
		xResult = pxTexture->GetSurfaceLevel(0,&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(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;
        }
    
		// START JAT 02/11/07 - copy how Tools_Level_Export::CreateDDSFile works so we get consistency between PC, 360 & PS3
		// Some textures exist outside of the material response system so we have to make our own judgements!
		// This is horrible and ignores what the material response system has chosen / needs - it just gets overridden!
		// see if this surface has any alpha
		bool bHasAlpha = false;
		bool bBinaryAlpha = true;
		u_char* pucLineStart = (u_char*)xRect.pBits;
		if( xImageInfo.Format != D3DFMT_A8 && xImageInfo.Format != D3DFMT_L8 && xImageInfo.Format != D3DFMT_A16B16G16R16F )
		{
			for( u_int y = 0; y < xSurfaceDesc.Height && bBinaryAlpha; y++, pucLineStart += xRect.Pitch )
			{
				u_int* puColour = (u_int*)pucLineStart;
				for( u_int x = 0; x < xSurfaceDesc.Width && bBinaryAlpha; x++, puColour++ )
				{
					if ( (*puColour >> 24 ) != 0xFF )
					{
						bHasAlpha = true;
						if ( ( *puColour >> 24 )!=0 )
						{
							bBinaryAlpha = false;
						}
					}
				}
			}
		}
		{
			// If told to use 1-bit alpha but we have alpha in the image then change to use 8-bit alpha
			if ( ( eTextureFormat == kDXT1 ) && ( bHasAlpha == true ) )
			{
				eTextureFormat = kDXT5;
			}
		}
		// END JAT 02/11/07 - copy how Tools_Level_Export::CreateDDSFile works so we get consistency between PC, 360 & PS3

		bool bAlphaOnly = false;

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		if( bAlpha && !bColour && !bNormalMap )
		{
			bAlphaOnly = true;
		}
#endif
		if( xImageInfo.Format == D3DFMT_A8 || xImageInfo.Format == D3DFMT_L8 )
		{
			bAlphaOnly = true;
		}
		Tools_PS3_Texture_Writer xWriter( xSurfaceDesc.Width, xSurfaceDesc.Height, xRect.Pitch, xRect.pBits, &xConvertData, eTextureFormat, bTextureWriterSucess, Tools_PS3_Texture_Writer::MAXIMUM_NUMBER_OF_IMAGES, bAlphaOnly );
        
        Asura_Assertf( true == bTextureWriterSucess, ("PS3 Texture Writer Failed to Create PS3 version of '%s'", szInputName));
    
        bSuccess = xWriter.WriteOutFile(szOutputName);

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


Clearup:
    if( pxSurface )		pxSurface->Release();
    if( pxTexture )		pxTexture->Release();
	for( int i = 0; i < 6; ++i )
	{
		if( pxSurfaces[i] ) pxSurfaces[i]->Release();
	}
	if( pxCubeTexture )	pxCubeTexture->Release();

    return bSuccess;
}


bool Tools_PS3_Export::ExportCharacter( HANDLE hOutput, Asura_Hierarchy_Skin* pxSkin )
{
    Tools_SmoothSkin_Processor xProcessor;
    xProcessor.SetIncludeNormalMapData( true );
    if( !xProcessor.Initialise(pxSkin) ) { return false; }

    Tools_SmoothSkin_Processor_PS3 xPS3Processor;
    if( !xPS3Processor.Initialise(xProcessor) ) { return false; }

    if( !xPS3Processor.OutputToChunk(hOutput) ) { return false; }

    return true;
}


// Find sticky-out edges and mark them to be anti-aliased
// caller must free *ppuOutput
int Tools_PS3_Export::FindAntiAliasEdges( const Asura_Mesh* pxMesh, uint16** ppuOutput ) 
{
	if ( !pxMesh )
	{
		// marine's pistol has a null mesh - this doesn't seem to cause a problem.
		// EP.9.DEC.2008 I blocked this assert and put message cast before calling of this function in one case.
		// Asura_Assert( 0, "FindAntiAliasEdges - this mesh is NULL, invalid object perhaps?" );
		return 0;
	}

	if (!Tools_Adjacency::IsInitialised())
	{
		Tools_Adjacency::Initialise();
	}
	
	Tools_MeshAdjacency xMeshAdjancy; 
	xMeshAdjancy.Initialise( pxMesh );
	
	uint16* puFoundEdges = new uint16[pxMesh->NumberOfPolygons*4*2]; //enough for every poly to be a quad with it's own edges
	uint16* puOutEdge = puFoundEdges;
	int iTotalNumEdges = 0;
	
	for ( int iPolyA=0; iPolyA < pxMesh->NumberOfPolygons; iPolyA++ )
	{
		const Tools_Polygon_Adjacency* pxPolyAdj = xMeshAdjancy.GetPolygonAdjacencies( iPolyA );
		
		uint16 uPointsA[] =
		{
			pxMesh->PolygonDataPtr[iPolyA].VertexID[0],
			pxMesh->PolygonDataPtr[iPolyA].VertexID[1],
			pxMesh->PolygonDataPtr[iPolyA].VertexID[2],
			pxMesh->PolygonDataPtr[iPolyA].VertexID[3], // 0xffff if it's a triangle
		};

		u_int uNumPointsA = pxMesh->PolygonDataPtr[iPolyA].NumberOfVertices();

		bool abRawEdges[4] = { true,true,true,true }; //flag per edge, assume they're raw until an adjacent poly for each is found. Numbering: [0] = point0 to point1, ... [n] = pointn to point0, where n is 3 or 4

		int iNumAdjacentEdgePolys = 0;
		//int iNumberOfAdjacentPolys = (pxPolyAdj->GetNumAdjacentPolygons());
		for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
		{
			// ignore polys that only meet at a vertex rather than an edge
			if (pxAdj->GetNumSharedVertices() < 2) continue;
			
			iNumAdjacentEdgePolys++;

			const int iPolyB = pxAdj->GetPolygonIndex();

			iTotalNumEdges++;

			uint16 uPointsB[] =
			{
				pxMesh->PolygonDataPtr[iPolyB].VertexID[0],
				pxMesh->PolygonDataPtr[iPolyB].VertexID[1],
				pxMesh->PolygonDataPtr[iPolyB].VertexID[2],
				pxMesh->PolygonDataPtr[iPolyB].VertexID[3], // 0xffff if it's a triangle
			};

			u_int uNumPointsB = pxMesh->PolygonDataPtr[iPolyB].NumberOfVertices();


			// every edge where these polys meet is not a raw edge
			for ( u_int i=0; i < uNumPointsA; i++ )
			{
				if ( abRawEdges[i] ) // skip the ones we've already marked as not-raw
				{
					uint16 p0 = uPointsA[i];
					uint16 p1 = uPointsA[(i+1)%uNumPointsA];
					for ( u_int j=0; j < uNumPointsB; j++ )
					{
						if ( ( p0 == uPointsB[j] && p1 == uPointsB[(j+1)%uNumPointsB] )
							|| ( p1 == uPointsB[j] && p0 == uPointsB[(j+1)%uNumPointsB] )  )
						{
							abRawEdges[i] = false;
						}
					}
				}
			}


			if ( iPolyB < iPolyA ) continue; // we've already done this poly 


			// decide if this edges needs anti-aliasing, using several criteria
			bool bAAEdge = false;
			
			// are materials or normals discontinuous?
			bAAEdge = bAAEdge || pxMesh->GetSmoothingGroup( iPolyA ) != pxMesh->GetSmoothingGroup( iPolyB );

// this test is invalid, there's a table of per-poly data. It's not vital that we AA the edges between textures (artists will usually make them look seamless anyway)
// 			bAAEdge = bAAEdge || pxMesh->GetTextureInfoForExport( iPolyA ) != pxMesh->GetTextureInfoForExport( iPolyB );

			// is this edge convex?
			if ( !bAAEdge )
			{
				Asura_Vector_3 xA1 = pxMesh->VertexDataPtr[uPointsA[1]].Position - pxMesh->VertexDataPtr[uPointsA[0]].Position;
				Asura_Vector_3 xA2 = pxMesh->VertexDataPtr[uPointsA[2]].Position - pxMesh->VertexDataPtr[uPointsA[0]].Position;
				xA1.Normalise();
				xA2.Normalise();
				Asura_Vector_3 xAFaceNormal = (xA1^xA2);
				xAFaceNormal.Normalise();

				//120,000 from 144,000, meaning this is not removing the 50% I expect.
				// still getting all the damn edges in the cave
				// test convex (points in B are behind points in A)
				Asura_Vector_3 xAB = pxMesh->VertexDataPtr[uPointsB[0]].Position + pxMesh->VertexDataPtr[uPointsB[1]].Position + pxMesh->VertexDataPtr[uPointsB[2]].Position;
				xAB /= 3.0f;
				xAB -= pxMesh->VertexDataPtr[uPointsA[0]].Position;
				float fDot = xAB * xAFaceNormal;
				if ( fDot < 0.0f )
				{
					Asura_Vector_3 xB1 = pxMesh->VertexDataPtr[uPointsB[1]].Position - pxMesh->VertexDataPtr[uPointsB[0]].Position;
					Asura_Vector_3 xB2 = pxMesh->VertexDataPtr[uPointsB[2]].Position - pxMesh->VertexDataPtr[uPointsB[0]].Position;
					xB1.Normalise();
					xB2.Normalise();
					Asura_Vector_3 xBFaceNormal = (xB1^xB2);
					xBFaceNormal.Normalise();

					// test angle
					fDot = xAFaceNormal*xBFaceNormal;
					if (fDot < 0.996f)	bAAEdge	= true; //about 5 degrees
				}

			}



			if (!bAAEdge) continue;

			// add every shared edge to our list
			for ( u_int i=0; i < uNumPointsA; i++ )
			{
				uint16 p0 = uPointsA[i];
				uint16 p1 = uPointsA[(i+1)%uNumPointsA];
				for ( u_int j=0; j < uNumPointsB; j++ )
				{
					if ( ( p0 == uPointsB[j] && p1 == uPointsB[(j+1)%uNumPointsB] )
						|| ( p1 == uPointsB[j] && p0 == uPointsB[(j+1)%uNumPointsB] )  )
					{
						*(puOutEdge++) = p0;
						*(puOutEdge++) = p1;
					}
				}
			}

		}
		
		// output any raw edges
		for ( u_int i=0; i < uNumPointsA; i++ )
		{
			if ( abRawEdges[i] )
			{
				*(puOutEdge++) = uPointsA[i];
				*(puOutEdge++) = uPointsA[(i+1)%uNumPointsA];
			}
		}
		
	}
	
	int iNumIndices = (puOutEdge - puFoundEdges);

//	Tools_Level_Export::AddMessage( Tools_Level_Export::MT_INFORMATION, "Found %d/%d edges for anti-aliasing", iNumIndices/2, iTotalNumEdges );

	//v1 - just output the line list, we'll switch to line strips later.
	*ppuOutput = puFoundEdges;

	return iNumIndices;
}


bool Tools_PS3_Export::ExportObjectHierarchy( HANDLE hOutput, const Asura_Hierarchy_Skin* pxSkin )
{
    // initialise and stripify mesh
    if (Tools_HierarchyProcessor_MultiMaterial_NormalMapped::Initialise(pxSkin))
    {
        u_short             usRestartIndex;

		const bool			bUseRestartIndex	= Tools_MeshRestartIndex::IsValidForCurrentPlatform (usRestartIndex);
		const Asura_String	xName				= pxSkin->Name;
	
		const u_int			uNumberOfStrips		= Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfStrips();
		const u_int			uNumberOfVertices	= Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfVertices();
		const u_int			uNumberOfIndices	= Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfIndices();
		
		u_int				uStartIndex			= 0;
		u_int				uStartVertex		= 0;
        
		Asura_Chunk_Memory_Stream xStream;

		xStream.WriteResourceChunk_Start( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC, ASURA_RESOURCEFILE_TYPE_PS3_OBJECTHIERARCHY, ASURA_PS3_OBJECTHIERARCHY_CHUNK_VERSION, 0, xName );
		
		xStream << pxSkin->m_uNameHashID;
		xStream << uNumberOfStrips;
		xStream << uNumberOfVertices;
		xStream << uNumberOfIndices;

		Tools_HierarchyProcessor_MultiMaterial_NormalMapped::ConvertMaterialsForExport();

		// Write each strip in-turn...
		for( u_int uStrip = 0; uStrip < uNumberOfStrips; ++uStrip )
		{
			const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip* pxStrip = Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetProcessedStrip( uStrip );
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
			Asura_Hash_ID uHash = Tools_PS3_ShaderExport::AddObjectShader( pxStrip->m_auMaterialResponseHashID[0], pxStrip->m_auMaterialResponseHashID[1] );
			xStream << uHash;
#endif
			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
				xStream << pxStrip->m_auMaterialResponseHashID[0];
				xStream << pxStrip->m_auMaterialResponseHashID[1];
			#else
				Asura_Assert( false, "Non material response exports not supported on PS3" );
			#endif
			xStream << static_cast<u_int>(pxStrip->m_usBoneIndex);
			xStream << uStartIndex;
			xStream << pxStrip->m_uNumberOfIndices;
			uStartIndex += pxStrip->m_uNumberOfIndices;
			xStream << pxStrip->m_uNumberOfTriangles;
		}

		Asura_Assert	((uNumberOfIndices == uStartIndex), "Mismatch in the number of indices for the current object-hierarchy.");

		ObjectVertex* pxOutputVertices = new ObjectVertex[uNumberOfVertices];
		ObjectVertex* pxCurrentOutputVert = pxOutputVertices;

		// Write out all of the vertices in one go...
		for (u_int s = 0; s < uNumberOfStrips; s++)
		{
			const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip&	xStrip = *Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetProcessedStrip (s);

			// Write-out the vertices for this strip...
			for (u_int v = 0; v < xStrip.m_uNumberOfVertices; v++)
			{
				const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex& xVertex = xStrip.m_pxVertices[v];

				ObjectVertex& xOutputVertex = *pxCurrentOutputVert;

				// position
				xOutputVertex.m_xPosition = xVertex.m_xPosition;

				// uvs
				xOutputVertex.m_xU = xVertex.m_fU0;
				xOutputVertex.m_xV = xVertex.m_fV0;
				xOutputVertex.m_xU2 = xVertex.m_fU1;
				xOutputVertex.m_xV2 = xVertex.m_fV1;
				xOutputVertex.m_xAlpha = xVertex.m_fAlpha;

				// normal/tangent/binormal
				xOutputVertex.m_uNormal = VECTOR_TO_UINT_PS3( xVertex.m_xNormal );
				xOutputVertex.m_uTangent = VECTOR_TO_UINT_PS3( xVertex.m_xTangent );
				xOutputVertex.m_uBinormal = VECTOR_TO_UINT_PS3( xVertex.m_xBinormal );

				// Write this vertex...
				xOutputVertex.WriteToChunkStream( xStream );
				
				pxCurrentOutputVert++;
			}
		}
		
		Asura_Assert( pxCurrentOutputVert == &pxOutputVertices[uNumberOfVertices], "vert count mismatch - could be causing big problems to object anti-aliasing" );

		
		// Write out all of the indices in one go...
		for (u_int s = 0; s < uNumberOfStrips; s++)
		{
			const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip&	xStrip = *Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetProcessedStrip (s);

			// Need to add the offset to each index - write them individually...
			for (u_int i = 0; i < xStrip.m_uNumberOfIndices; i++)
			{
				u_short	usIndex	= xStrip.m_pusIndices [i];
				
				// Is this a restart-index?
				if (!bUseRestartIndex || (usIndex != usRestartIndex))
				{
					// No - add the start index and validate...
					usIndex	= usIndex + static_cast<u_short>(uStartVertex);

					Asura_Assert	((xStrip.m_uNumberOfVertices > xStrip.m_pusIndices [i]), "Object-hierarchy index out of range.");
				}

				// Write out this index...
				xStream << usIndex;
			}

			// Step onto the next index...
			uStartVertex  += xStrip.m_uNumberOfVertices;
		}
		

		// Find edges for anti-aliasing
		if ( uNumberOfStrips > 0 )
		{
			xStream << pxSkin->NumberOfBones; // the number anti-aliasing blocks that follow
			
			for (int u=0; u < pxSkin->NumberOfBones; u++)
			{		
				uint16* puEdgeIndices;
				Asura_Mesh* pxMesh = pxSkin->SectionData[u].Mesh;
				
				int iNumEdgeIndices = 0;

				// NULL meshes are valid: they help with animation and to provide places to attach marker points, but obviously they have 0 anti-alias edges.
				if( pxMesh != NULL )
				{
					iNumEdgeIndices = FindAntiAliasEdges( pxMesh, &puEdgeIndices );
				}

				if ( iNumEdgeIndices > 0 )
				{
					xStream << iNumEdgeIndices;

					// build a table to remap the original mesh indices to any instance of that vertex in the strip.
					// obviously this is only good for the position, since different instances of a vertex can have different normals, UV's, etc.
					// we need this for the next step (anti-aliased edges)
					// todo: ideally this list should be created when we originally stripify the mesh, so we can do it without searching. But it's not slow enough to worry about.
					uint16* auRemap = new uint16[pxMesh->NumberOfVertices];
					for ( uint16 i=0; i < pxMesh->NumberOfVertices; i++ )
					{
						auRemap[i] = 0xffff;
						for ( uint16 j=0; j < uNumberOfVertices; j++ )
						{
							if ( pxMesh->VertexDataPtr[i].Position == pxOutputVertices[j].m_xPosition )
							{
								auRemap[i] = j;
								break;
							}
						}

 						// this fires a few times, presumably a few polygons from the mesh aren't being exported, there's no visible issue.
//	 					Asura_Assertf( auRemap[i] != 0xffff, ("Edge Anti-Aliasing: could not remap vertex %d/%d, <%f,%f,%f>", i, pxMesh->NumberOfVertices, pxMesh->VertexDataPtr[i].Position.GetX(), pxMesh->VertexDataPtr[i].Position.GetY(), pxMesh->VertexDataPtr[i].Position.GetZ() ) );
					}

					for ( int i=0; i < iNumEdgeIndices; i++ )
					{
//	 					Asura_Assertf( auRemap[puEdgeIndices[i]] != 0xffff, ( "remap value is uninitialised (old-vert %d, idxidx %d)", puEdgeIndices[i], i ) );
						xStream << auRemap[puEdgeIndices[i]];
					}

					delete[] auRemap;
					delete[] puEdgeIndices;
				}
				else
				{
					xStream << 0;
				}
			}
		}
		

		delete[] pxOutputVertices;


		// Wrap up and delete all the temp data...

		xStream.WriteResourceChunk_End();

		const void* pData = xStream.GetStartAddress();
		u_int uChunkSize = xStream.GetPosition();
		unsigned long uBytesWritten = 0;

		WriteFile														(hOutput, pData, uChunkSize, &uBytesWritten, NULL);
	
		Tools_HierarchyProcessor_MultiMaterial_NormalMapped::Shutdown	();
		return															(true);
	}
	else
	{
		return	(false);
	}
}

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

bool Tools_PS3_Export::ExportMaterialResponses( Asura_SaveBlock& xSaveBlock, Asura_HashTree* const pxMaterialsToExport, Asura_HashTree* const pxSlaveMaterialsToExport )
{
	Asura_Chunk_SaveBlock_Stream xStream( &xSaveBlock );
	
	const Asura_String xDUMMYNAME( "Material Response Texture" );

	xStream.WriteResourceChunk_Start( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC, ASURA_RESOURCEFILE_TYPE_PS3_MATERIALRESPONSE, ASURA_PS3_MATERIALRESPONSE_CHUNK_VERSION, 0, xDUMMYNAME );

	int iNumberOfMaterialsToExport = pxMaterialsToExport->GetNumberOfNodes();

	Asura_Hash_ID* const puExportHashes = new Asura_Hash_ID[ iNumberOfMaterialsToExport ];
	Asura_Assert( puExportHashes, "Failed to allocate material response export array." );
	if( !puExportHashes ) return false;

	pxMaterialsToExport->MakeHashList( puExportHashes );
	
	xStream << iNumberOfMaterialsToExport;

	// export texture lines for main materials
	for( int i = 0; i < iNumberOfMaterialsToExport; ++i )
	{
		const Tools_MaterialResponse* const pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID( puExportHashes[i] );

		if( pxMaterial )
		{
			pxMaterial->WriteTextureLineToStream( xStream );

		}
		else
		{
			Asura_Assertf( false, ("Material to export (0x%x) couldn't be found.", puExportHashes[i]) );
		}
	}


	// export PS3-specific info for all materials (including slaves)
	int iNumberOfSlavesToExport = pxSlaveMaterialsToExport->GetNumberOfNodes();

	xStream << iNumberOfMaterialsToExport + iNumberOfSlavesToExport;

	for( int i = 0; i < iNumberOfMaterialsToExport + iNumberOfSlavesToExport; ++i )
	{
		Asura_Hash_ID uCurrHash;
		if( i < iNumberOfMaterialsToExport )
		{
			uCurrHash = pxMaterialsToExport->FindHashIDByIndex(i);
		}
		else
		{
			uCurrHash = pxSlaveMaterialsToExport->FindHashIDByIndex( i - iNumberOfMaterialsToExport );
		}

		const Tools_MaterialResponse* const pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID( uCurrHash );
		const Asura_MaterialResponse* const pxAsuraMaterial = Asura_MaterialResponse_System::GetMaterialByHashID( uCurrHash );

		// Write material hash, so we know which material this additional info corresponds to
		// This is a bit sucky, but the only other option is to create & read big arrays in the chunk loading code, so we might as well store that info here.
		xStream << uCurrHash;

		// Write shader hash for this material
		xStream << Tools_PS3_ShaderExport::CreatePartialHashID( pxMaterial, pxAsuraMaterial );
	}
	
	xStream.WriteResourceChunk_End();

	delete [] puExportHashes;

	return true;
}
#endif // ASURA_USE_MATERIAL_RESPONSE_SYSTEM


#if defined ASURA_USE_BOTANICALS

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_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
//					uFileSize - Reference to file size that will be incremented
//								as appropriate for this export
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PS3_Export::FindUsedBotanicalsTemplatesAndShapes( u_int& uNumUsedTemplates, Asura_LinkList< Tools_PS3_Export_Botanical_Template >& xBlockTemplateList,
															 u_int& uNumUsedShapes, Asura_LinkList< const Tools_BotanicalShape* >& xUsedShapeList)
{
	const int iNumInstances = Asura_Botanicals_InstanceManager::GetNumInstances();

	// 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;

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

		if( pxCurrTemplate->eType != ASURA_BOTANICALS_TEMPLATE_BOTANICALSHAPE // No point supporting shape-based botanicals on 360
			|| pxCurrTemplate->bMergeRenderMeshWithEnv
			|| !pxCurrTemplate->pxBotanicalShape )
		{
			continue;
		}

		int iModuleIdx = pxCurrInstance->iModuleIdx;
		if ( ( iModuleIdx < 0 ) || ( iModuleIdx >= Asura_Module_System::GetNumberOfModules() ) )
		{
			Asura_Direct3D_BGR_Manager& xBGRManager = Asura_Direct3D_Botanicals_Game_Render::GetDirect3DBGRManager();
			Asura_Assertf( false, ( "Invalid module index %d exporting template[%d] '%s' Instance %d\n", 
						   iModuleIdx, pxCurrInstance->iTemplateIdx, pxCurrTemplate->xTemplateName.GetString(), ( iCurrInstanceTimes100/100 ) ) );
			iModuleIdx = xBGRManager.GetModuleIndexFromPosition( pxCurrInstance->xPosition, pxCurrInstance->xOrientation);
			pxCurrInstance->iModuleIdx = iModuleIdx;
		}

		Tools_PS3_Export_Botanical_Template* pxBlockTemplate = NULL;

		{
			for( Asura_ListElt< Tools_PS3_Export_Botanical_Template >* pxCurrBlockTemplate = xBlockTemplateList.Front(); NULL != pxCurrBlockTemplate; pxCurrBlockTemplate = pxCurrBlockTemplate->Next())
			{
				Tools_PS3_Export_Botanical_Template& xBlockTemplate = pxCurrBlockTemplate->Data();

				if( iModuleIdx != xBlockTemplate.m_iModuleIndex)
				{
					continue;
				}

				if( pxCurrTemplate != xBlockTemplate.m_pxTemplate)
				{
					continue;
				}

				pxBlockTemplate = &xBlockTemplate;
				break;
			}
		}

		if( NULL != pxBlockTemplate)
		{
			Asura_Assert( iModuleIdx == pxBlockTemplate->m_iModuleIndex, "Export has a bug");
			Asura_Assert( pxCurrTemplate == pxBlockTemplate->m_pxTemplate, "Export has a bug");
			pxBlockTemplate->m_xListInstances.Push_Front( pxCurrInstance);
			continue;
		}

		const Tools_BotanicalShape* const pxToolsShape = pxCurrTemplate->pxBotanicalShape->GetToolsShape();
		int iShapeIndex = 0;

		for( Asura_ListElt< const Tools_BotanicalShape*>* pxItCurrShape = xUsedShapeList.Front(); NULL != pxItCurrShape; pxItCurrShape = pxItCurrShape->Next())
		{
			const Tools_BotanicalShape* pxCurrShape = pxItCurrShape->Data();

			if( pxToolsShape == pxCurrShape)
			{
				break;
			}

			iShapeIndex += 1;
		}

		if( uNumUsedShapes <= static_cast< u_int>( iShapeIndex))
		{
			xUsedShapeList.Push_Back( pxToolsShape );
			++uNumUsedShapes;
		}

		{
#if defined( ASURA_ASSERT)
			int iShapeIndexLast = 0;
#endif
			bool bInserted = false;
			for( Asura_ListElt< Tools_PS3_Export_Botanical_Template >* pxCurrBlockTemplate = xBlockTemplateList.Front(); NULL != pxCurrBlockTemplate; pxCurrBlockTemplate = pxCurrBlockTemplate->Next())
			{
				Tools_PS3_Export_Botanical_Template& xBlockTemplate = pxCurrBlockTemplate->Data();

#if defined( ASURA_ASSERT)
				Asura_Assert( iShapeIndexLast <= xBlockTemplate.m_iShapeIndex, "BlockTemplates are not in Shape order");
				iShapeIndexLast = xBlockTemplate.m_iShapeIndex;
#endif
				if( xBlockTemplate.m_iShapeIndex < iShapeIndex)
				{
					continue;
				}

				xBlockTemplateList.Insert_Before( pxCurrBlockTemplate, Tools_PS3_Export_Botanical_Template());
				pxBlockTemplate = &(pxCurrBlockTemplate->Prev()->Data());
				bInserted = true;
				break;
			}

			if( false == bInserted)		
			{
				xBlockTemplateList.Push_Back( Tools_PS3_Export_Botanical_Template());
				pxBlockTemplate = &(xBlockTemplateList.Back()->Data());
			}
		}

		pxBlockTemplate->m_iModuleIndex = iModuleIdx;
		pxBlockTemplate->m_iShapeIndex = iShapeIndex;
		pxBlockTemplate->m_pxTemplate = pxCurrTemplate;
		pxBlockTemplate->m_xListInstances.Push_Front( pxCurrInstance);

		++uNumUsedTemplates;
	}

	u_int uCurrShapeTimes100 = 0;

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

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

		const Tools_BotanicalShape* const pxCurrShape = pxCurrShapeListEntry->Data();

		const int iNumElements = pxCurrShape->GetNumElements();

		// Loop over each element

		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;
			}

			const int iNumLODs = pxCurrElement->GetNumLODs();

			// 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( *pxCurrLOD );
						break;
					}

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

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

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

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_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_PS3_Export::IncrementExportSize_Geometry( const Tools_BotanicalElementLOD& xElementLOD )
{
	if( !xElementLOD.m_pxMesh || !xElementLOD.m_pxMesh->TextureDataValid )
	{
		Asura_Assert( false, "Invalid parameter passed to Tools_PS3_Export::IncrementExportSize_Geometry()." );
		return;
	}

	if( !Tools_ObjectProcessor_NormalMapped::Initialise( xElementLOD.m_pxMesh, 0, true, Tools_TriStripper::OUTPUT_TRILIST ) ) return;

	Tools_ObjectProcessor_NormalMapped::Shutdown();
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_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_PS3_Export::IncrementExportSize_Billboards( const Tools_BotanicalElementLOD& xElementLOD )
{
	if( !xElementLOD.m_pxMesh || !xElementLOD.m_pxMesh->TextureDataValid )
	{
		Asura_Assert( false, "Invalid parameter passed to Tools_PS3_Export::IncrementExportSize_Billboards()." );
		return;
	}

}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_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_PS3_Export::IncrementExportSize_Imposter( const Tools_BotanicalElementLOD& xElementLOD )
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_Export::ExportBotanicalsElementLOD_Geometry()
//
// Exports the given geometric botanicals LOD to a chunk stream
//
// Parameters:		xStream - Output chunk stream
//					xElementLOD - Geometry LOD to process
//					piMaterialConversionList - Conversion array mapping from
//											   full material IDs to indices into
//											   the botanicals material chunk.
//											   Must be ASURA_MAX_NO_OF_MATERIALS
//											   in size
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

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

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

	// Strip out the evil 'double sided' flag - as the botanicals have proper double-sided lighting!
	for(int i=0;i<xElementLOD.m_pxMesh->NumberOfPolygons;i++)
	{		
		if(xElementLOD.m_pxMesh->PolygonFlagDataValid)
		{
			if (xElementLOD.m_pxMesh->PolygonFlagDataPtr[i].Flags & AsuraPolygonFlag_DoubleSided)
			{
				xElementLOD.m_pxMesh->PolygonFlagDataPtr[i].Flags-=AsuraPolygonFlag_DoubleSided;
			}
		}				
	}

	if( !Tools_ObjectProcessor_NormalMapped::Initialise( xElementLOD.m_pxMesh, iMaterialID, true, Tools_TriStripper::OUTPUT_TRISTRIP_WITH_RESET_INDICES ) ) return;
	const Tools_ObjectProcessor_NormalMapped_Strip& xStrip = *Tools_ObjectProcessor_NormalMapped::GetProcessedStrip();


#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	xStream << xElementLOD.m_pxMesh->TextureDataPtr[0].m_auMaterialResponseHashID[0];
	Asura_Hash_ID uHash = Tools_PS3_ShaderExport::AddBotanicalsShader( xElementLOD.m_pxMesh->TextureDataPtr[0].m_auMaterialResponseHashID[0] );
	xStream << uHash;
#else
	Asura_Assert( false, "Not supported" );
#endif
	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_NormalMapped_Vertex& xVertex = xStrip.m_pxVertices[u];

		xStream << xVertex.m_xPosition;
		xStream << static_cast< Asura_HalfFloat >( xVertex.m_fU );
		xStream << static_cast< Asura_HalfFloat >( xVertex.m_fV );

		xStream << VECTOR_TO_UINT_PS3( xVertex.m_xNormal );
		xStream << VECTOR_TO_UINT_PS3( xVertex.m_xTangent );
		xStream << VECTOR_TO_UINT_PS3( xVertex.m_xBinormal );

#if defined( ASURA_BOTANICAL_SUPPORT_INTERACTIVE)
		const float fBoneWeight = pxBoneWeightData->CalculateBoneWeight( Asura_AVector3( xVertex.m_xPosition));
#else
		const float fBoneWeight = 1.0f;
#endif
		xStream << static_cast< float>( fBoneWeight);
	}

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

	Tools_ObjectProcessor_NormalMapped::Shutdown();
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_Export::ExportBotanicalsElementLOD_Billboards()
//
// Exports the given billboards botanicals LOD to a chunk stream
//
// Parameters:		xStream - Output chunk stream
//					xElementLOD - Billboards LOD to process
//					piMaterialConversionList - Conversion array mapping from
//											   full material IDs to indices into
//											   the botanicals material chunk.
//											   Must be ASURA_MAX_NO_OF_MATERIALS
//											   in size
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

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


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

	// We'll assume only one material per LOD mesh
#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	const uint32 uMaterialID = xElementLOD.m_pxMesh->TextureDataPtr[0].m_auMaterialResponseHashID[0];
	xStream << uMaterialID;	
	Asura_Hash_ID uHash = Tools_PS3_ShaderExport::AddBotanicalsShader( uMaterialID );
	xStream << uHash;
#else	
	const int iMaterialID = xElementLOD.m_pxMesh->TextureDataPtr[0].m_iMaterialID;
	xStream << Asura_Material_System::ConvertIDToIndex( iMaterialID, piMaterialConversionList );
#endif

	xStream << iNumBillboards;

	// Per-billboard data

	for( int i = 0; i < iNumBillboards; ++i )
	{
		const Tools_Botanicals_Billboard& xBillboard = xRenderable.GetBillboard(i);
		// Position relative to instance location
		const Asura_HalfFloat axPos[4] = { xBillboard.xPos.x, xBillboard.xPos.y, xBillboard.xPos.z, 1.0f };
		// Normal (faces away from the cluster centre)
		const u_int uNormal = VECTOR_TO_UINT_PS3( xBillboard.xNormal );
		// Tangent Frame - Same at each vertex. But got to handle
		// possible U/V flips, so it's not the same for all billboards?	
		Asura_Vector_3 xTangent( ((xBillboard.fU[0] < xBillboard.fU[1]) ? +1.0f : -1.0f) , 0.0f, 0.0f);
		Asura_Vector_3 xBinormal( 0.0f, ((xBillboard.fV[0] < xBillboard.fV[1]) ? +1.0f : -1.0f) , 0.0f);

		const Asura_HalfFloat xHalfWidth = xBillboard.fWidth * 0.5f;
		const Asura_HalfFloat xHalfHeight = xBillboard.fHeight * 0.5f;
		const Asura_HalfFloat xU[2] = { xBillboard.fU[0], xBillboard.fU[1] };
		const Asura_HalfFloat xV[2] = { xBillboard.fV[0], xBillboard.fV[1] };

	// Per-vertex data
		Asura_HalfFloat xData[4][4] = {
			{ -xHalfWidth, -xHalfHeight, xU[0], xV[1] },//00
			{  xHalfWidth, -xHalfHeight, xU[1], xV[1] },//10
			{  xHalfWidth,  xHalfHeight, xU[1], xV[0] },//11
			{ -xHalfWidth,  xHalfHeight, xU[0], xV[0] },//01
		};

		for(int j = 0; j < 4; ++j )
		{
			xStream << xData[j][0]; xStream << xData[j][1]; //Position
			xStream << xData[j][2]; xStream << xData[j][3]; //UV

			xStream << Asura_WriteToStream_Array_Wrapper< Asura_HalfFloat >( 4, axPos );
			xStream << uNormal;
			xStream << VECTOR_TO_UINT_PS3(xTangent);	
			xStream << VECTOR_TO_UINT_PS3(xBinormal);	
#if defined( ASURA_BOTANICAL_SUPPORT_INTERACTIVE)
			const float fBoneWeight = pxBoneWeightData->CalculateBoneWeight( Asura_AVector3( xBillboard.xPos));
#else
			const float fBoneWeight = 1.0f;
#endif
			xStream << fBoneWeight;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_Export::ExportBotanicalsElementLOD_Imposter()
//
// Exports the given imposter botanicals LOD to a chunk stream
//
// Parameters:		xStream - Output chunk stream
//					xElementLOD - Imposter LOD to process
//					piMaterialConversionList - Conversion array mapping from
//											   full material IDs to indices into
//											   the botanicals material chunk.
//											   Must be ASURA_MAX_NO_OF_MATERIALS
//											   in size
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Tools_PS3_Export::ExportBotanicalsElementLOD_Imposter( Asura_Chunk_Stream& xStream, const Tools_BotanicalElementLOD& xElementLOD, const int* const piMaterialConversionList, const Tools_BGR_Type_BoneWeightCalc* pxBoneWeightData)
{
	// Number of different orientations defined for this imposter
	int iNumImposterAngles = xElementLOD.GetNumImposters();

	xStream << iNumImposterAngles;																 // 1					

	const Tools_Botanicals_Imposter *pxImp = xElementLOD.GetImposterByIndexConst(0);	

#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	uint32 uMaterialID = 0;
	if (pxImp)
	{
		if (pxImp->m_pxMesh)
		{		
			if (pxImp->m_pxMesh->TextureDataValid)
			{
				uMaterialID = pxImp->m_pxMesh->TextureDataPtr[0].m_auMaterialResponseHashID[0];
			}
			else
			{
				uMaterialID = ASURA_HASH_ID_UNSET;
			}
		}
	}

	xStream << uMaterialID;	
	Asura_Hash_ID uHash = Tools_PS3_ShaderExport::AddBotanicalsShader( uMaterialID );
	xStream << uHash;
#else
	int iMaterialID = 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;
#endif		

	float fAngleRange = 0.0f, fFadeRange = 0.0f;
	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");
		Asura_HalfFloat xPosition[ 4 ] = { pxImp->m_xBillboard.xPos.GetX(), pxImp->m_xBillboard.xPos.GetY(), pxImp->m_xBillboard.xPos.GetZ(), 1.0f };
		fAngleRange	= pxImp->m_fAngleRange	* ( Asura_Maths::Pi / 180.0f );
		fFadeRange	= pxImp->m_fFadeRange	* ( Asura_Maths::Pi / 180.0f );
		Asura_HalfFloat xAngleFadeSize[ 4 ] = { fAngleRange, fFadeRange, pxImp->m_xBillboard.fWidth, pxImp->m_xBillboard.fHeight };
		Asura_HalfFloat xUVs[ 4 ] = { pxImp->m_xBillboard.fU[0], pxImp->m_xBillboard.fV[0], pxImp->m_xBillboard.fU[1], pxImp->m_xBillboard.fV[1] };
		Asura_Vector_3 xTangent( ((pxImp->m_xBillboard.fU[0] < pxImp->m_xBillboard.fU[1]) ? +1.0f : -1.0f) , 0.0f, 0.0f);
		Asura_Vector_3 xBinormal( 0.0f, ((pxImp->m_xBillboard.fV[0] < pxImp->m_xBillboard.fV[1]) ? +1.0f : -1.0f) , 0.0f);
		Asura_HalfFloat xVerts[ 4 ][ 5 ] = 
		{
			{ -0.5f, -1.0f, xUVs[ 0 ], xUVs[ 1 ], 1.0f },
			{  0.5f, -1.0f, xUVs[ 2 ], xUVs[ 1 ], 1.0f },
			{  0.5f,  0.0f, xUVs[ 2 ], xUVs[ 3 ], 0.0f },
			{ -0.5f,  0.0f, xUVs[ 0 ], xUVs[ 3 ], 0.0f },
		};

		for( int j = 0; j < 4; ++j )
		{
			xStream << xVerts[ j ][ 0 ]; xStream << xVerts[ j ][ 1 ];
			xStream << xVerts[ j ][ 2 ]; xStream << xVerts[ j ][ 3 ];
			xStream << Asura_WriteToStream_Array_Wrapper< Asura_HalfFloat >( 3, xPosition );
			xStream << xVerts[ j ][ 4 ];
			xStream << Asura_WriteToStream_Array_Wrapper< Asura_HalfFloat >( 4, xAngleFadeSize );

			xStream << VECTOR_TO_UINT_PS3(pxImp->m_xBillboard.xNormal);		
			xStream << VECTOR_TO_UINT_PS3(xTangent);	
			xStream << VECTOR_TO_UINT_PS3(xBinormal);	
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_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_PS3_Export::ExportBotanicalsShapes( 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 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();
		xStream << iNumElements;

		// Loop over elements
#if defined( ASURA_BOTANICAL_SUPPORT_INTERACTIVE)
		Tools_BGR_Type_BoneWeightCalc xBoneWeightData;
		const Asura_BGR_Type_BasicInteractive* const pxTypeBasicInteractive = xBGRType.GetBasicInteractive();
		if( NULL != pxTypeBasicInteractive)
		{
			xBoneWeightData.SetupBoneWeight( *pxTypeBasicInteractive);
		}
#endif


		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;
			}

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

			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, piMaterialConversionList
#if defined( ASURA_BOTANICAL_SUPPORT_INTERACTIVE)
							, &xBoneWeightData
#else
							, NULL
#endif
							);
						break;
					}

				case TOOLS_BOTANICALS_ELEMENTTYPE_BILLBOARDS:
					{
						ExportBotanicalsElementLOD_Billboards( xStream, *pxCurrLOD, piMaterialConversionList
#if defined( ASURA_BOTANICAL_SUPPORT_INTERACTIVE)
							, &xBoneWeightData
#else
							, NULL
#endif
						 );
						break;
					}

				case TOOLS_BOTANICALS_ELEMENTTYPE_IMPOSTER:
					{
						ExportBotanicalsElementLOD_Imposter( xStream, *pxCurrLOD, piMaterialConversionList
#if defined( ASURA_BOTANICAL_SUPPORT_INTERACTIVE)
							, &xBoneWeightData
#else
							, NULL
#endif
						 );
						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);
					}
				}

				// 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;

				xStream << fMinDistance;
				xStream << fMinFullAlphaDistance;
				xStream << fMaxFullAlphaDistance;
				xStream << fMaxDistance;
			}

			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;
		}

		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);
		}
#endif

		xBGRType.Export( xStream);
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_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_PS3_Export::ExportBotanicalsTemplates( Asura_Chunk_Stream& xStream, const Asura_LinkList< Tools_PS3_Export_Botanical_Template >& xTemplateList,
													 const Asura_LinkList< const Tools_BotanicalShape* >& xShapeList )
{
	const u_int uNumTemplates = xTemplateList.Size();
	u_int uCurrTemplateTimes100 = 0;

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

	for( Asura_ListElt< Tools_PS3_Export_Botanical_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 Tools_PS3_Export_Botanical_Template& xCurrBlockTemplate = pxCurrTemplateElement->Data();
		const Asura_Botanicals_Template* const pxCurrTemplate = xCurrBlockTemplate.m_pxTemplate;

		Asura_Bounding_Box xBoundingBoxTemplate;

		xBoundingBoxTemplate.Reset();

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

#if 0
		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);
		}
#endif

#if 1
		for( const Asura_ListElt< const Asura_Botanicals_Instance* >* pxCurrInstanceElement = xInstanceListSave.Front(); pxCurrInstanceElement; pxCurrInstanceElement = pxCurrInstanceElement->Next() )
		{
			const Asura_Botanicals_Instance* const pxCurrInstance = pxCurrInstanceElement->Data();

			xBoundingBoxTemplate.Set( pxCurrInstance->xPosition);
		}
#endif

		u_int uShapeIndex = 0;
		for( Asura_ListElt< const Tools_BotanicalShape* >* pxCurrShapeElement = xShapeList.Front();
			pxCurrShapeElement;
			pxCurrShapeElement = pxCurrShapeElement->Next() )
		{
			const Tools_BotanicalShape* pxShapeElement = pxCurrShapeElement->Data();
			if( pxShapeElement == pxCurrTemplate->pxBotanicalShape->GetToolsShape() ) 
			{
				break;
			}

			++uShapeIndex;
		}

		xStream << pxCurrTemplate->xTemplateName;
		xStream << uShapeIndex;
		xStream << xBoundingBoxTemplate;
		xStream << static_cast< uint32>(xInstanceListSave.Size());

		// Loop over instances that use this template
		// TODO: create a list of instances per template in the finding step
		for( Asura_ListElt< const Asura_Botanicals_Instance* >* pxCurrInstanceElement = xInstanceListSave.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 );

			// Output as 3x4
			for( u_int u = 0; u < 3; ++u )
			{
				for( u_int v = 0; v < 4; ++v )
				{
					xStream << static_cast< Asura_HalfFloat >( xInstanceMatrix.m[u][v] );
				}
			}

			// Static lighting

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

			xStream << static_cast< Asura_HalfFloat >( xSH.m_afRedCoefficients[1]  * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afRedCoefficients[2]  * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afRedCoefficients[3]  * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afRedCoefficients[0]  * pxCurrInstance->fBrightness );

			xStream << static_cast< Asura_HalfFloat >( xSH.m_afGreenCoefficients[1] * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afGreenCoefficients[2] * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afGreenCoefficients[3] * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afGreenCoefficients[0] * pxCurrInstance->fBrightness );

			xStream << static_cast< Asura_HalfFloat >( xSH.m_afBlueCoefficients[1] * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afBlueCoefficients[2] * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afBlueCoefficients[3] * pxCurrInstance->fBrightness );
			xStream << static_cast< Asura_HalfFloat >( xSH.m_afBlueCoefficients[0] * pxCurrInstance->fBrightness );

			// Alpha - this will be fixed up in the instance VB when LODs are calculated
			xStream << 1.0f;

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

			// Bone Position
			xStream << static_cast< float>( 0.0f);
			xStream << static_cast< float>( 0.0f);
			xStream << static_cast< float>( 0.0f);

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

			// Visible flag
			xStream << static_cast< bool >( false );

			// padding
			xStream << static_cast< bool >( false );

			// Module index
			xStream << static_cast< short >( pxCurrInstance->iModuleIdx );
			Asura_Assertf( ( pxCurrInstance->iModuleIdx >= 0 && pxCurrInstance->iModuleIdx < Asura_Module_System::GetNumberOfModules() ), ( "Invalid module index %d exporting template[%d] '%s'\n", 
						   pxCurrInstance->iModuleIdx, pxCurrInstance->iTemplateIdx, pxCurrTemplate->xTemplateName.GetString() ) );
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_Export::ExportBotanicals()
//
// Exports botanicals data to an PS3 level file
//
// Parameters:		hOutput - Handle to the output file
//					piMaterialConversionList - Material ID conversion list. Must
//											   be ASURA_MAX_NO_OF_MATERIALS in size
//
// Return value:	true iff successful
//
////////////////////////////////////////////////////////////////////////////////

bool Tools_PS3_Export::ExportBotanicals( const HANDLE hOutput, const int* const piMaterialConversionList )
{
	Asura_LinkList< Tools_PS3_Export_Botanical_Template > xTemplateExportList;
	Asura_LinkList< const Tools_BotanicalShape* > xShapeExportList;

	u_int uNumShapesToExport = 0;
	u_int uNumTemplatesToExport = 0;

	FindUsedBotanicalsTemplatesAndShapes( uNumTemplatesToExport, xTemplateExportList, uNumShapesToExport, xShapeExportList);

	// Create the buffer and streams

	Asura_SaveBlock* pxSaveBlock = new Asura_SaveBlock();

	{
		// DLH - 18 December 2006 16:22:48 - Create the Chunk Data in a SaveBlock
		// DLH - 18 December 2006 16:23:06 - This saves having to know what the export file size is before saving
		Asura_Chunk_SaveBlock_Stream xStream( pxSaveBlock);

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

		// Write the chunk header

		xStream.WriteChunk_Start( ASURA_CHUNK_RESOURCEFILE, ASURA_PS3_BOTANICALS_CHUNK_VERSION, 0 );

		// Version number

		xStream << ((uint32)(Asura_Chunk_ResourceFile_PS3_Botanicals::CURRENT_VERSION));
		xStream << static_cast< uint32>( Asura_BGR_Manager::s_uChunkVersion);

		// Export shapes

		xStream << uNumShapesToExport;
		ExportBotanicalsShapes( xStream, xShapeExportList, piMaterialConversionList );

		// Export templates

		xStream << uNumTemplatesToExport;
		ExportBotanicalsTemplates( xStream, xTemplateExportList, xShapeExportList );

		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_PS3_BOTANICALS,
			uBytesToWrite,
			ASURA_PS3_BOTANICALS_CHUNK_VERSION
			);

		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

	delete pxSaveBlock;

	return true;
}

#endif // defined ASURA_USE_BOTANICALS

////////////////////////////////////////////////////////////////////////////////
//
// Tools_PS3_Export::ExportStaticDecals()
//
// Exports the static decal geometry. Decal materials/textures are handled
// separately (and platform-independent)
//
// Parameters:		hOutput - Handle to the output file
//
// Return value:	true iff successful
//
////////////////////////////////////////////////////////////////////////////////

#if defined ASURA_USE_STATIC_DECAL_SYSTEM
bool Tools_PS3_Export::ExportStaticDecals( const HANDLE hOutput )
{

	// It's the familiar 'completely arbitrary fixed-size buffer' design pattern again...
	Asura_Chunk_Memory_Stream xStream(16 * 1024 * 1024);	

	xStream.WriteResourceChunk_Start( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC, ASURA_RESOURCEFILE_TYPE_PS3_STATICDECALS, ASURA_PS3_STATICDECALS_CHUNK_VERSION,0, xSTATICDECALS_RESOURCE_FILENAME );	

	// Kept the exported data very simple for now
	// Given more time, this could be improved - to enable better batching and/or less material/vb switching	
	int iNumBuffers = Tools_StaticDecal_System::GetNumDecalBuffers();
	
	xStream << iNumBuffers;
	for(int i=0; i<iNumBuffers; i++)
	{	
		Tools_StaticDecal_System::OptimizeBuffer(i);
		Tools_StaticDecal_Buffer* pxBuffer = Tools_StaticDecal_System::GetDecalBufferByIndex(i);		

		
		xStream << pxBuffer->m_iModuleIndex;
		xStream << pxBuffer->m_uDecalMaterial;
		xStream << pxBuffer->m_iNumVerts;
		xStream << pxBuffer->m_iNumIndices;
		xStream << pxBuffer->m_xAABB;

		// Output the verts
		for(int j=0; j<pxBuffer->m_iNumVerts; j++)
		{
			Tools_StaticDecal_Vertex *pxVert = &pxBuffer->m_pxVerts[j];
			
			xStream << pxVert->xPos;
			xStream << pxVert->fU;
			xStream << pxVert->fV;
			xStream << pxVert->fCreationTime; // always 0.0f - 'creation time' value when used on dynamic decals
			xStream << pxVert->fAlpha;

			

			uint32 uTmp = VECTOR_TO_UINT_PS3(pxVert->xNormal);
			xStream << uTmp;
			uTmp = VECTOR_TO_UINT_PS3(pxVert->xTangent);
			xStream << uTmp;
			uTmp = VECTOR_TO_UINT_PS3(pxVert->xBinormal);
			xStream << uTmp;
			
			xStream << Asura_HalfFloat(pxVert->xSHRed.GetX());
			xStream << Asura_HalfFloat(pxVert->xSHRed.GetY());
			xStream << Asura_HalfFloat(pxVert->xSHRed.GetZ());
			xStream << Asura_HalfFloat(pxVert->xSHRed.GetW());

			xStream << Asura_HalfFloat(pxVert->xSHGreen.GetX());
			xStream << Asura_HalfFloat(pxVert->xSHGreen.GetY());
			xStream << Asura_HalfFloat(pxVert->xSHGreen.GetZ());
			xStream << Asura_HalfFloat(pxVert->xSHGreen.GetW());

			xStream << Asura_HalfFloat(pxVert->xSHBlue.GetX());
			xStream << Asura_HalfFloat(pxVert->xSHBlue.GetY());
			xStream << Asura_HalfFloat(pxVert->xSHBlue.GetZ());
			xStream << Asura_HalfFloat(pxVert->xSHBlue.GetW());			
		}

		// Output the indices
		for(int j=0; j<pxBuffer->m_iNumIndices; j++)
		{
			uint16 usTmp = (uint16)pxBuffer->m_piIndices[j];			
			xStream << usTmp;
		}
	}


	xStream.WriteResourceChunk_End();
	
	unsigned long uBytesWritten = 0;
	const void* pData = xStream.GetStartAddress();
	u_int uChunkSize = xStream.GetPosition();
	WriteFile(hOutput, pData, uChunkSize, &uBytesWritten, NULL);

	return true;
}
#endif //ASURA_USE_STATIC_DECAL_SYSTEM
