/**
*
* \file Tools_Level_Export.cpp
*
* Created by Kevin Lea - 29/11/2002 11:23:31
*
* Tools_Level_Export_Textures.cpp
* Splitted into separate file 12/11/2008
*
* Copyright (C) 2002 Rebellion, All Rights Reserved.
*
* \brief
*
**/
#include "Core/Asura.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"

// Asura
#include "Animation/Asura_Animation.h"
#include "ChunkSystem/Asura_Chunk_Entity.h"
#include "ChunkSystem/Asura_Chunk_ResourceFile.h"
#include "ChunkSystem/Asura_Chunk_Texture.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Cutscene/Asura_Cutscene_System.h"
#include "Debugging/Asura_Debug_Timers.h"
#include "Mesh/Asura_Env_System.h"
#include "Mesh/Asura_Mesh_System.h"
#include "Physics/Asura_Physics_Environment.h"
#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Viewport.h"
#include "SpecialFX/Asura_StaticCorona_System.h"

// Tools
#include "Entity/Tools_Entity_Object.h"
#include "Hierarchy/Tools_Animation_Files.h"
#include "ImageProcessing/Tools_NvTextureTools.h"
#include "ImageProcessing/Tools_Tim2_Writer.h"
#include "ImageProcessing/Tools_Xbox360_Texture_Writer.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Load&Save/Tools_BatchProcessing.h"
#include "Load&Save/Tools_TextureConversionData.h"
#include "Load&Save/Tools_ResourceExportLog.h"
#include "Misc/Tools_IniFile.h"
#include "MeshProcessing/Tools_MeshAdjacency.h"
#include "PC/OS/Tools_OS.h"
#include "PSP/Tools_PSP_Export.h"
#include "PS3/Tools_PS3_Export.h"
#include "Shapes/Tools_Shapes.h"
#include "Skybox/Tools_Mode_Skybox.h"
#include "Texturing/Tools_Texture_Management.h"
#include "Wii/Tools_Wii_Textures.h"
#include "Xbox360/Tools_Xbox360_Export.h"

//#define TOOLS_DO_NVIDIA_PERFORMANCE_TEST

#include "NVidia/NVTT/nvtt.h"

#include "ddraw.h"
#include <process.h>

#ifdef ASURA_USE_WATER
#include "ChunkSystem/Asura_Chunk_Water.h"
#include "Rendering/Asura_Water.h"
#endif

#if defined ( ASURA_USE_TEXTURE_STREAMING )
#include "Core/Asura_Texture_Streaming.h"
#include "ChunkSystem/Asura_Chunk_Texture_Streaming.h"
#endif

#ifdef ASURA_USE_BILLBOARD_PLANTS
#include "SpecialFX/Asura_BillboardPlant_System.h"
#include "SpecialFX/Asura_BillboardPlant_Batch.h"
#include "SpecialFX/Asura_BillboardPlant_TemplateStore.h"
#endif //ASURA_USE_BILLBOARD_PLANTS

#include "Particles/Tools_Mode_PFX.h"



#ifdef ASURA_USE_BOTANICALS
#include "Botanicals/Asura_Botanicals_InstanceManager.h"
#include "Botanicals/Asura_Botanicals_TemplateManager.h"
#include "Botanical/Asura_Direct3D_Botanicals_Game_Render.h"
#include "Botanicals/Tools_Mode_Botanicals.h"
#endif // #ifdef ASURA_USE_BOTANICALS

#ifdef ASURA_USE_CARPET
#include "Carpet/Asura_Carpet_MeshManager.h"
#include "Carpet/Asura_Carpet_StyleManager.h"
#include "Carpet/Asura_Direct3D_Carpet_Game_Render.h"
#include "Carpet/Tools_Carpet_Mesh.h"
#endif //ASURA_USE_CARPET

#ifdef ASURA_USE_MATERIAL_RESPONSE_SYSTEM 
#include "Core/Asura_MaterialResponse_System.h"
#include "ChunkSystem/Asura_Chunk_MaterialResponse.h"
#include "Materials/Tools_MaterialResponse_System.h"
#endif

#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER -1
#endif

#ifdef TOOLS_DO_NVIDIA_PERFORMANCE_TEST
#include "Debugging/Asura_Profiling_System.h"

static float s_fTotalDuration = 0.0f;
static Asura_Handle s_iPerformanceHandle;
static bool s_bIsTimerCreated = false;
#endif

// S T A T I C    I N I T /////////////////////////////////////////////////////////

static HANDLE DDSHandle = INVALID_HANDLE_VALUE;
//// GetTIM2Proportions() is slow so keep a look-up table for the
//// AdjustUVCoordinatesMain() stuff.

// G L O B A L S /////////////////////////////////////////////////////////
extern u_int g_auMaxTIM2Dimensions[];

// F U N C T I O N S /////////////////////////////////////////////////////////////
extern BOOL WriteFileString( HANDLE hFile, const Asura_String &xString, DWORD* puBytesWritten);

template <typename T> static T *LookupTexel(D3DLOCKED_RECT *pRect, u_int uTexelSize, u_int x, u_int y)
{
	return (T*)(((char*)pRect->pBits) + y * pRect->Pitch + x * uTexelSize);
}

// Takes a 8bit gamma 2.2 value and returns a 16bit linear value
static u_short Xbox360_PiecewiseLinearDegamma(unsigned char v)
{
	u_short v_lin;

	if (v < 64)
	{
		v_lin = v;
	}
	else if (v < 96)
	{
		v_lin = 64 + (v-64)*2;
	}
	else if (v < 192)
	{
		v_lin = 128 + (v-96)*4;
	}
	else
	{
		v_lin = 513 + (v-192)*8;
	}
	//v_lin is now in 10bit linear space

	// convert 10 bit to 16 bit
	v_lin = (v_lin << 6) | (v_lin >> 4);

	return v_lin;
}

//#ifndef TOOLS_USE_NEW_NVIDIA_LIBRARIES

static int DDSWrite_OutputNumber;
static int DDSWrite_MipLevelsToDrop;
static bool DDSWrite_GenerateMipMaps;

//called by nvDXTcompress
void WriteDTXnFile(DWORD count, const void * buffer)
{
	unsigned long bytesWritten;

	if(DDSWrite_OutputNumber == 0)
	{
		//first output is the 4 byte identifier , leave as is
		Asura_Assert(count==4,"");

		WriteFile(DDSHandle,buffer,count,&bytesWritten,0);
	}
	if(DDSWrite_OutputNumber == 1)
	{
		//this should be the surface description , may need to fiddle with it a bit
		Asura_Assert(count==sizeof(DDSURFACEDESC2),"");
		DDSURFACEDESC2 surfaceDesc = *(DDSURFACEDESC2*)buffer;

		if(DDSWrite_MipLevelsToDrop)
		{
			Asura_Assert(DDSWrite_MipLevelsToDrop<(int)surfaceDesc.dwMipMapCount,"");
			//adjust the necessary entries to take into account that we are dropping mipmaps
			surfaceDesc.dwWidth>>=DDSWrite_MipLevelsToDrop;
			surfaceDesc.dwHeight>>=DDSWrite_MipLevelsToDrop;
			surfaceDesc.dwLinearSize>>=DDSWrite_MipLevelsToDrop*2;
			surfaceDesc.dwMipMapCount -= DDSWrite_MipLevelsToDrop;
		}
		if(!DDSWrite_GenerateMipMaps)
		{
			surfaceDesc.dwMipMapCount = 0;
			surfaceDesc.dwFlags&=~DDSD_MIPMAPCOUNT ;
		}

		//write out the modified header
		WriteFile(DDSHandle,&surfaceDesc,count,&bytesWritten,0);

	}
	else if(DDSWrite_OutputNumber > 1)
	{
		int mipIndex =  DDSWrite_OutputNumber - 2;
		bool writeThisMip = true;

		//if dropping mip levels , ignore the earlier ones
		if(mipIndex<DDSWrite_MipLevelsToDrop)
		{
			writeThisMip = false;
		}
		//if not generating mip maps , then we only want one level
		if(!DDSWrite_GenerateMipMaps && mipIndex!=DDSWrite_MipLevelsToDrop)
		{
			writeThisMip = false;
		}

		if(writeThisMip)
		{
			WriteFile(DDSHandle,buffer,count,&bytesWritten,0);
		}
	}

	DDSWrite_OutputNumber++;
}

static NV_ERROR_CODE DxtMipMapCallback(const void* pPixelData, size_t uCount, const MIPMapData* pxMipMapData, void* pUserData)
{
	WriteDTXnFile(uCount, pPixelData);
	return NV_OK;
}

static void InitialiseDDSOutput(int mipLevelsToDrop,bool generateMipMaps)
{
	DDSWrite_OutputNumber = 0;
	DDSWrite_MipLevelsToDrop = mipLevelsToDrop;
	DDSWrite_GenerateMipMaps = generateMipMaps;

}
//#endif

//#ifdef TOOLS_USE_NEW_NVIDIA_LIBRARIES
// This class declares the functions that will be called by the NVidia texture tool libraries.  We are given the compressed data
// stream by the libraries, which we then write to disk as a DDS file.

class MipmapOutputHandler : public nvtt::OutputHandler
{
	public:
		MipmapOutputHandler (const bool bGenerateMipmaps, const int iMipmapLevelsToDrop, const HANDLE pDDSHandle, const bool bIs8BitAlphaOnly)
		{
			m_uOutputNumber = 0;
			m_iMipmapLevelsToDrop = iMipmapLevelsToDrop;
			m_bGenerateMipmaps = bGenerateMipmaps;
			m_bIsHeaderWritten = false;
			m_pDDSHandle = pDDSHandle;
			m_bIs8BitAlphaOnly = bIs8BitAlphaOnly;
		}

		virtual void beginImage (int size, int width, int height, int depth, int face, int miplevel)
		{
			m_iMipLevel = miplevel;
		}

		virtual bool writeData (const void* pData, int size)
		{
			unsigned long bytesWritten;

			if (!m_bIsHeaderWritten)
			{
				// Modify the header if necessary

				Asura_Assert(size == sizeof (DDSURFACEDESC2) + 4, "");
				DDSURFACEDESC2 xSurfaceDesc = *(DDSURFACEDESC2 *) ((byte *) pData + 4);

				if (m_iMipmapLevelsToDrop)
				{
					Asura_Assert (m_iMipmapLevelsToDrop < (int) xSurfaceDesc.dwMipMapCount, "");
					//adjust the necessary entries to take into account that we are dropping mipmaps
					xSurfaceDesc.dwWidth >>= m_iMipmapLevelsToDrop;
					xSurfaceDesc.dwHeight >>= m_iMipmapLevelsToDrop;
					xSurfaceDesc.dwLinearSize >>= m_iMipmapLevelsToDrop * 2;
					xSurfaceDesc.dwMipMapCount -= m_iMipmapLevelsToDrop;
				}

				if (!m_bGenerateMipmaps)
				{
					xSurfaceDesc.dwMipMapCount = 0;
					xSurfaceDesc.dwFlags&=~DDSD_MIPMAPCOUNT;
				}

				if(m_bIs8BitAlphaOnly)
				{
					xSurfaceDesc.ddpfPixelFormat.dwFlags = DDPF_ALPHA;
				}

				WriteFile (m_pDDSHandle, pData, 4, &bytesWritten, 0);
				WriteFile (m_pDDSHandle, &xSurfaceDesc, sizeof (DDSURFACEDESC2), &bytesWritten, 0);

				m_bIsHeaderWritten = true;
			}
			else
			{
				// This isn't the header we're writing, so decide whether we're dropping this mipmap level or not

				bool bWriteThisMip = true;

				//if dropping mip levels , ignore the earlier ones
				if (m_iMipLevel < m_iMipmapLevelsToDrop)
				{
					bWriteThisMip = false;
				}

				//if not generating mip maps, then we only want one level
				if(!m_bGenerateMipmaps && m_iMipLevel != m_iMipmapLevelsToDrop)
				{
					bWriteThisMip = false;
				}

				if (bWriteThisMip)
				{
					WriteFile (m_pDDSHandle, pData, size, &bytesWritten, 0);
				}
			}

			return true;
		}

	private:
		u_int m_uOutputNumber;
		int m_iMipLevel;
		int m_iMipmapLevelsToDrop;
		bool m_bGenerateMipmaps;
		bool m_bIsHeaderWritten;
		bool m_bIs8BitAlphaOnly;
		HANDLE m_pDDSHandle;
};
//#endif

// TOOLS_LEVEL_EXPORT TEXTURES RELATED FUNCTIONS /////////////

#if defined(ASURA_USE_TEXTURE_STREAMING)
void Tools_Level_Export::GetExportedLevelTextureStreamingFilename(const Asura_String& xEnvironmentFilename, Asura_String& xOutputFilename)
{
	const char*		szPlatformName	= GetPlatformName(s_eExportType);
	const char*		szPlatformExt	= GetPlatformExtension();

	Asura_String	xName;

	//get the level name without path/extension
	xEnvironmentFilename.SplitFilename(NULL, &xName, NULL);

	if (szPlatformName && szPlatformExt)
	{
		char acFilename[MAX_PATH];
		Asura_PrintfToBfr(acFilename, "..\\%s_Release\\envs\\%s%s_textures", MAX_PATH, true, szPlatformName, xName.GetString(), szPlatformExt);
		xOutputFilename	= acFilename;
	}
	else
	{
		xOutputFilename = xName;
		xOutputFilename += ".unknown_platform_textures";
	}
}

bool Tools_Level_Export::ExportStreamingTexturesFile( void )
{
	Asura_String xEnvironmentFilename = (Tools_SaveAsura::IsEditingMission() && !MissionExport_IsExportingEnvironmentOnly())? Tools_SaveAsura::GetMissionFilenameWithoutExtension() : m_xLevelNameBackup;

	Asura_String	xTextureStreamingFilename;
	const char*		szFilename;

	// Get the filename we're to export to...
	GetExportedLevelTextureStreamingFilename(xEnvironmentFilename, xTextureStreamingFilename);
	szFilename	= xTextureStreamingFilename.GetString();

	// The above will include the redirection to the platform folder (i.e. "..\*_Release\"), but we need to remove that in the version we store in the texture streaming chunk.
	// There are assumptions elsewhere in tools that the exported level files go into a folder called "envs", so we'll make the same assumption here.
	const char* szInternalFilename = strstr(szFilename, "envs");
	if (szInternalFilename)
	{
		Asura_Texture_Streaming_System::Tools_SetTextureStreamingFilename(szInternalFilename);
	}
	else
	{
		AddMessage(MT_ERROR, "Failed to export %s - streaming texture files must be exported within the \"envs\" directory.", szFilename);
	}

	HANDLE saveHandle = INVALID_HANDLE_VALUE;
	bool bOK = true;

   	#define CHECK( expr ) \
   	    if( !(expr) ) { bOK = false; goto End; }
	

	//open the file to write to
	saveHandle = CreateFile(szFilename,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0);
	if(saveHandle == INVALID_HANDLE_VALUE)
	{
		AddMessage( MT_ERROR, "Failed to open \"%s\" for writing.", szFilename );
		CHECK( false );
	}


	DWORD uPositionInFile = 0;

	//write the header
	WriteFile(saveHandle,ASURA_UNCOMPRESSED_FILE_ID,ASURA_FILE_ID_NUM_CHARS,&uPositionInFile,0);

   
	//add any extra textures...

	UpdateStatus(EXPORT_STAGE_STREAMING_TEXTURES, "Streaming Textures", 0);

	const u_int uNumberOfTextures = Asura_Texture_Streaming_System::GetNumberOfStreamingTextures();

	for( u_int u=0; u<uNumberOfTextures; ++u )
	{
		Asura_Streaming_Texture* const pxTexture = Asura_Texture_Streaming_System::GetStreamingTexture( u );
		int iTextureIndex = pxTexture->GetTextureIndex();

		const Asura_String* pxTextureName = Asura_Texture_Management::GetTextureName(iTextureIndex);
		if(!pxTextureName)
		{
			Asura_Assert( false, "Code bug: Failed to get name of texture being exported" );
			continue;
		}
		
		//add this texture
		s_iPercent = (u*100)/uNumberOfTextures;
	
		UpdateProgress();


		{
			const char* filename = pxTextureName->GetString();

			Asura_String OutputFilename;
			TextureConversionData xOutputConversionData;
			bool bSRGB = 
				pxTexture->GetTextureUsage() == Asura_MaterialResponse::ALBEDO_TEXTURE ||
				pxTexture->GetTextureUsage() == Asura_MaterialResponse::ENVIRONMENT_MAP;

			if( GenerateTextureIfNecessary(filename, iTextureIndex,false, bSRGB, &OutputFilename,&xOutputConversionData) )
			{
				HANDLE file = CreateFile(OutputFilename.GetString(),GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
				if(file==INVALID_HANDLE_VALUE)
				{
					AddMessage(MT_ERROR,"Failed to open %s",OutputFilename.GetString());
		   
					CHECK( false );
				}

				//allocate a buffer , and read in the file
				int trueFileSize = GetFileSize(file,0);
				int fileSize = ((trueFileSize+3)/4)*4;
				char* buffer = new char[fileSize];
				unsigned long bytesRead;
				ReadFile(file,buffer,trueFileSize,&bytesRead,0);
				CloseHandle(file);
			
				Asura_String xBaseFilenameForResource = "\\graphics";
				xBaseFilenameForResource += filename;

				Asura_String xActualFilenameForResource;

				xActualFilenameForResource += xBaseFilenameForResource;
		
				uPositionInFile += sizeof(Asura_Chunk_ResourceFile) + (xActualFilenameForResource.GetSizeInInts()*4);

				pxTexture->SetPositionInFile( uPositionInFile );
				pxTexture->SetFileSize( fileSize );

				const int iTotalSize = sizeof(Asura_Chunk_ResourceFile) + (xActualFilenameForResource.GetSizeInInts()*4) + (fileSize);

				DWORD uBytesWritten;
				//create the chunk header
				//RAR 15/05/2006 Texture flags now get stored in the sub type (was previously unused for textures)
				Asura_Chunk_ResourceFile xChunk;
				u_int uFlags=pxTexture->GetChunkFlags();
				if( s_eExportType == Export_Xbox360 && OutputFilename.EndsWith(".xpr"))
				{
					uFlags |= Asura_Chunk_ResourceFile::TF_XBOX360_TEXTURE_XPR;
				}

				pxTexture->SetChunkFlags(uFlags);
				xChunk.Create(iTotalSize, uFlags, ASURA_RESOURCEFILE_TYPE_TEXTURE, xOutputConversionData.m_iTextureFlags, fileSize);
			
				WriteFile(saveHandle,&xChunk,sizeof(xChunk),&uBytesWritten,0);

				// add archivename
				WriteFileString(saveHandle,xActualFilenameForResource.GetString(),&uBytesWritten);

				// add contents of file
				WriteFile(saveHandle,buffer,fileSize,&uBytesWritten,0);

				delete [] buffer;

				uPositionInFile += fileSize;
			}
			else
			{
				//Missing texture , so just fill in the entry with zeroes to allow the rest of the file to work
				//RAR 10/10/2008
				pxTexture->SetPositionInFile(0);
				pxTexture->SetFileSize(0);

			}
		}
	}

	//Add the terminator
    {
	    int Terminator;
	    Terminator = 0;
		DWORD uBytesWritten;
		BOOL bOK = WriteFile(saveHandle,&Terminator,4,&uBytesWritten,0);
        if( !bOK || 4 != uBytesWritten )
        {
            AddMessage( MT_ERROR, "ERROR: %s.", Tools_OS::GetLastSystemError() );
            CHECK( false );
        }
    }

End :
	
	// Clear the progress message.
	UpdateStatus(EXPORT_STAGE_END, NULL, 0);

	if( saveHandle != INVALID_HANDLE_VALUE )
    {
	    CloseHandle(saveHandle);
    }

	if( bOK )
	{
		// KJFL - 14 March 2008 - can't compress the file, because we want to stream
		// to specific positions inside it.
		/*
		if(!IsCompressionDisabled() && ShouldCompressExportedFiles())
		{
			AddMessage( MT_INFORMATION, "Compressing \"%s\"...", szFilename );
			CompressFile(szFilename);
		}
	*/	
		AddMessage( MT_INFORMATION, "Successfully exported \"%s\" (%d textures).",szFilename, uNumberOfTextures);
	}
	else
	{
		DeleteFile(szFilename);
        AddMessage( MT_ERROR, "Failed to export \"%s\".", szFilename );
	}


	#undef CHECK

    return bOK;
}
#endif // ASURA_USE_TEXTURE_STREAMING


void Tools_Level_Export::AdjustUVCoordinatesMain(Asura_Mesh* pxMesh,int* piResetPolygonCount,int* piDodgyPolygonCount)
{
	//Create a copy of the UVs
	pxMesh->GenerateProcessedTextureInfo();

	Tools_MeshAdjacency xMeshAdj;
	xMeshAdj.Initialise(pxMesh);

	bool bFirstDodgyPolyInMesh = true;

	//allocate array for keepin track of processed polygons
	bool* pbPolyDone = new bool[pxMesh->NumberOfPolygons];

	for(int iChan=0;iChan<ASURA_MATERIAL_MAXTEXTURES;iChan++)
	{
		//reset polygon done array
		for(int i=0;i<pxMesh->NumberOfPolygons;i++)
		{
			pbPolyDone[i] = false;
			int iPolygonFlags = pxMesh->PolygonFlagDataValid ? pxMesh->PolygonFlagDataPtr[i].Flags : 0;
			if(iPolygonFlags & (AsuraPolygonFlag_DontExport | AsuraPolygonFlag_DontRender))
			{
				pbPolyDone[i] = true;//ignore polygons that aren't going to be exported
			}
		}

		for(int i=0;i<pxMesh->NumberOfPolygons;i++)
		{
			//polygon dealt with yet?
			if(pbPolyDone[i]) continue;
			pbPolyDone[i] = true;

			//textured?
			Asura_Mesh_TextureInfo* pxTex = &pxMesh->m_pxProcessedTextureInfo[i]; 
			int iTextureID = Asura_Material_System::GetTextureID(pxTex->m_iMaterialID,iChan);
			bool bTextured = (iTextureID != ASURA_TEXTURE_NONE);

			int iRange;
			bool bCentredAroundZero;

			if(s_eExportType == Export_Wii)
			{
				//Wii is allowed coordinates in range [-32,31 + 1023/1024 )
				iRange = 32;
				bCentredAroundZero = true;
			}
			else if(s_eExportType == Export_PSP)
			{
				//PSP is allowed coordinates in range [0,128)
				//(in practice , since its sharing code with the PS2 adjustment
				//we will probably end up in range (0,128) )
				iRange = 128;
				bCentredAroundZero = false;
			}
			else if(s_eExportType == Export_PC)
			{
				//Range is (-10,10) for PC.
				//Not sure if its open or closed , but doing as open range anyway
				iRange = 20;
				bCentredAroundZero = true;
			}
			else if(s_eExportType == Export_PS2)
			{
				// Determine the allowed range of coordinates based on the
				// texture size. Provided we're using STs, the PS2 can handle
				// a range of (-2048, 2048) for the resultant UVs (ie. after
				// multiplication with the texture size and division by Q.) 
				// We're also 
				iRange = 8;
				bCentredAroundZero = true;
				if(bTextured)
				{
				    Asura_Assert( 0 <= iTextureID && iTextureID < ASURA_MAX_NO_OF_TEXTURES, "" );
				    u_int uMaxDim = g_auMaxTIM2Dimensions[iTextureID];

				    if( 0xFFFFFFFFu == uMaxDim )
				    {
				        u_int uW, uH;
				        if( !GetTIM2Proportions(iTextureID, &uW, &uH, false) )
				        {
							#ifdef ASURA_ASSERT
							const Asura_String* pxTextureName = Asura_Texture_Management::GetTextureName(iTextureID);
							#endif
							Asura_Assertf( false, ("Couldn't get TIM2 dimensions for texture '%s'.", pxTextureName ? pxTextureName->GetString() : "unknown"));
							//use a default texture size , so that we don't get spammed by error messages
							uW = 256;
							uH = 256;
				            
				        }

				        uMaxDim = g_auMaxTIM2Dimensions[iTextureID] = Asura_Maths::Max( uW, uH );
				    }

					if( uMaxDim <= 256 )
					{
				        // Can only shift up once, regardless of size, since
				        // we're constrained by the fact that the STs we're
				        // writing are 1:3:12 format and therefore restricted to
				        // (-8, 8).
				        iRange <<= 1;
					}
				    else if( uMaxDim > 512 )
				    {
				        // Not really supporting 1024s but just to be paranoid...
				        iRange >>= 1;
				    }
				}
			}
			else
			{
				//For other platforms , adjust the UV coordinates to be continous if possible , but
				//allow a much larger range of coordinates.
				bCentredAroundZero = false;
				iRange = 10000;
			}
			Asura_Mesh_Polygon* pxPoly = &pxMesh->PolygonDataPtr[i];
			int iNumVerts = pxPoly->NumberOfVertices();

			float fPolyMinU = Asura_Maths::LargeFloat;
			float fPolyMinV = Asura_Maths::LargeFloat;
			float fPolyMaxU = -Asura_Maths::LargeFloat;
			float fPolyMaxV = -Asura_Maths::LargeFloat;

			bool bResetCoords = false;

			//get range of coords used by polygon
			for(int j=0;j<iNumVerts;j++)
			{
				float fU = pxTex->U[iChan][j];
				float fV = pxTex->V[iChan][j];

				fPolyMinU = Asura_Maths::Min(fPolyMinU,fU);
				fPolyMaxU = Asura_Maths::Max(fPolyMaxU,fU);

				fPolyMinV = Asura_Maths::Min(fPolyMinV,fV);
				fPolyMaxV = Asura_Maths::Max(fPolyMaxV,fV);

				if(fU<-1e8 || fU>1e8 || fV<-1e8 || fV>1e8)
				{
					//stupidly large coordinates , just reset to 0
					bResetCoords = true;
				}
			}

			//get uv coordinate range as ints
			int iMinU = (int) fPolyMinU;
			if((float)iMinU >= fPolyMinU) iMinU--;
			int iMaxU = (int) fPolyMaxU;
			if((float)iMaxU <= fPolyMaxU) iMaxU++;

			int iMinV = (int) fPolyMinV;
			if((float)iMinV >= fPolyMinV) iMinV--;
			int iMaxV = (int) fPolyMaxV;
			if((float)iMaxV <= fPolyMaxV) iMaxV++;


			if(bResetCoords)
			{
				for(int j=0;j<pxPoly->NumberOfVertices();j++)
				{
					pxTex->U[iChan][j] = 0;
					pxTex->V[iChan][j] = 0;
				}
				if(bTextured)//not really much point in warning about bad UVs on untextured polygons
				{
					if(piResetPolygonCount) *piResetPolygonCount++;
				}
				continue;
			}

			bool bOutOfRangeCoords = false;
			if((iMaxV-iMinV > iRange) || (iMaxU - iMinU > iRange))
			{
				//coordinates cover too large a range
				if(bFirstDodgyPolyInMesh)
				{
	    	    	Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "\nUV Coordinates for  %s too large",pxMesh->Name.GetString());
					bFirstDodgyPolyInMesh = false;
				}
	    	    Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "Poly %d :  MinU %.3f , MaxU %.3f , MinV %.3f , MaxV %.3f",i,fPolyMinU,fPolyMaxU,fPolyMinV,fPolyMaxV);
				bOutOfRangeCoords = true;
				if(bTextured)//not really much point in warning about bad UVs on untextured polygons
				{
					if(piDodgyPolygonCount)*piDodgyPolygonCount++;
				}

			}

			//start list with this polygon ,and search out for connected polygons
			//with the same texture
			List<int> xPolyList;
			xPolyList.add_entry(i);

			if(!bOutOfRangeCoords)
			{
				for(LIF<int> xPolyLIF(&xPolyList);!xPolyLIF.done();xPolyLIF.next())
				{
					//check for adjacent polygons with the same texture , that haven't been dealt
					//with yet
					const Tools_Polygon_Adjacency* pxPolyAdj = xMeshAdj.GetPolygonAdjacencies(xPolyLIF());

					Asura_Mesh_Polygon* pxPoly1 = &pxMesh->PolygonDataPtr[xPolyLIF()];
					Asura_Mesh_TextureInfo* pxTex1 = &pxMesh->m_pxProcessedTextureInfo[xPolyLIF()];
					int iNumVerts1 = pxPoly1->NumberOfVertices();

					for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
					{
						if(pxAdj->GetNumSharedVertices() != 2) continue;

						int iPoly = pxAdj->GetPolygonIndex();
						if(pbPolyDone[iPoly]) continue;

						Asura_Mesh_Polygon* pxPoly2 = &pxMesh->PolygonDataPtr[iPoly];
						Asura_Mesh_TextureInfo* pxTex2 = &pxMesh->m_pxProcessedTextureInfo[iPoly];
						int iNumVerts2 = pxPoly2->NumberOfVertices();

						int iTextureID2 = Asura_Material_System::GetTextureID(pxTex2->m_iMaterialID,iChan);
						if(iTextureID2 != iTextureID) continue;

						//Check to see if the connecting UVs match to within an integer multiple ,and adjust to match
						//if they do
						int iSharedFound = 0;
						int aiVert1[2];
						int aiVert2[2];

						for(int k=0;k<iNumVerts1 && iSharedFound<2;k++)
						{
							for(int l=0;l<iNumVerts2;l++)
							{
								if(pxPoly1->VertexID[k] == pxPoly2->VertexID[l])
								{
									// found a matching vertex
									aiVert1[iSharedFound] = k;
									aiVert2[iSharedFound++] = l;
									break;
								}
							}
						}
						Asura_Assert(iSharedFound==2,"Failed to find shared vertices in adjacent polygons");
						if(iSharedFound != 2) continue;


						//check the difference in the uv coords
						float uDiff = pxTex1->U[iChan][aiVert1[0]] - pxTex2->U[iChan][aiVert2[0]];
						float vDiff = pxTex1->V[iChan][aiVert1[0]] - pxTex2->V[iChan][aiVert2[0]];
						float uDiff2 = pxTex1->U[iChan][aiVert1[1]] - pxTex2->U[iChan][aiVert2[1]];
						float vDiff2 = pxTex1->V[iChan][aiVert1[1]] - pxTex2->V[iChan][aiVert2[1]];

						//make sure the differences for the 2 matching vertices correspond
						if(Asura_Maths::Abs(uDiff-uDiff2)>0.01f) continue;
						if(Asura_Maths::Abs(vDiff-vDiff2)>0.01f) continue;

						//get the uv difference rounded to the nearest integer
						float uDiffInt = (float)(int)(uDiff + (uDiff>=0 ? 0.5f : -0.5f));
						float vDiffInt = (float)(int)(vDiff + (vDiff>=0 ? 0.5f : -0.5f));

						//make sure the differences are close to being integers
						if(Asura_Maths::Abs(uDiff-uDiffInt)>0.01f) continue;
						if(Asura_Maths::Abs(vDiff-vDiffInt)>0.01f) continue;

	
						//the coordinates match.
						//adjust the uv coordinates

						if(uDiffInt || vDiffInt)
						{
							for(int k=0;k<iNumVerts2;k++)
							{
								pxTex2->U[iChan][k]+=uDiffInt;
								pxTex2->V[iChan][k]+=vDiffInt;
							}
						}



						//get range of coordinates for polygon
						fPolyMinU = Asura_Maths::LargeFloat;
						fPolyMinV = Asura_Maths::LargeFloat;
						fPolyMaxU = -Asura_Maths::LargeFloat;
						fPolyMaxV = -Asura_Maths::LargeFloat;


						bResetCoords = false;
						for(int k=0;k<pxPoly2->NumberOfVertices();k++)
						{
							float fU = pxTex2->U[iChan][k];
							float fV = pxTex2->V[iChan][k];

							fPolyMinU = Asura_Maths::Min(fPolyMinU,fU);
							fPolyMaxU = Asura_Maths::Max(fPolyMaxU,fU);

							fPolyMinV = Asura_Maths::Min(fPolyMinV,fV);
							fPolyMaxV = Asura_Maths::Max(fPolyMaxV,fV);

							if(fU<-1e8 || fU>1e8 || fV<-1e8 || fV>1e8)
							{
								//stupidly large coordinates , just reset to 0
								bResetCoords = true;
							}
						}
						if(bResetCoords) continue;

						//get overall range as ints
						int iMinUCombined = Asura_Maths::Min(iMinU,(int)fPolyMinU);
						int iMaxUCombined = Asura_Maths::Max(iMaxU,(int)fPolyMaxU);
						int iMinVCombined = Asura_Maths::Min(iMinV,(int)fPolyMinV);
						int iMaxVCombined = Asura_Maths::Max(iMaxV,(int)fPolyMaxV);

						if((float)iMinUCombined >= fPolyMinU) iMinUCombined--;
						if((float)iMaxUCombined <= fPolyMaxU) iMaxUCombined++;
						if((float)iMinVCombined >= fPolyMinV) iMinVCombined--;
						if((float)iMaxVCombined <= fPolyMaxV) iMaxVCombined++;



						if((iMaxVCombined-iMinVCombined > iRange) || (iMaxUCombined - iMinUCombined > iRange))
						{
							continue;
						}

						//okay , this polygons coordinates are in range , so add to the list
						iMinU = iMinUCombined;
						iMaxU = iMaxUCombined;
						iMinV = iMinVCombined;
						iMaxV = iMaxVCombined;
						
						pbPolyDone[iPoly] = true;
						xPolyList.add_entry(iPoly);
					}
				}
			}

			//We should now have a list of polyons with the same texture whose coordinates can all be
			//covered by the allowed texture range.
			float fShiftU,fShiftV;
			if(bCentredAroundZero)
			{
				//Determine the shift required to actually get these coordinates to within -Range/2 , +Range/2
				fShiftU = -(float)((iMinU+iMaxU)/2);
				fShiftV = -(float)((iMinV+iMaxV)/2);
			}
			else
			{
				//PSP coordinates start at 0
				fShiftU = -(float)iMinU;
				fShiftV = -(float)iMinV;
			}

			//apply this shift to all the polygons in the list
			for(LIF<int> xPolyLIF(&xPolyList);!xPolyLIF.done();xPolyLIF.next())
			{
				int iPoly = xPolyLIF();	

				Asura_Mesh_Polygon* pxPoly2 = &pxMesh->PolygonDataPtr[iPoly];
				Asura_Mesh_TextureInfo* pxTex2 = &pxMesh->m_pxProcessedTextureInfo[iPoly];

				for(int j=0;j<pxPoly2->NumberOfVertices();j++)
				{
					pxTex2->U[iChan][j] += fShiftU;
					pxTex2->V[iChan][j] += fShiftV;

					if(!bOutOfRangeCoords)
					{
						//make sure the final coords are in fact okay
						const float fEpsilon = 1e-6f;

						float fRangeMin,fRangeMax;

						if(bCentredAroundZero)
						{
							fRangeMin = -iRange/2.0f;
							fRangeMax = iRange/2.0f;
						}
						else
						{
							fRangeMin = 0.0f;
							fRangeMax = (float)iRange;
						}

						//If coordinates are only out by a small amount , then correct them , otherwise
						//complain,
						if(pxTex2->U[iChan][j] < fRangeMin)
						{
							if(pxTex2->U[iChan][j] >= (fRangeMin-fEpsilon))
							{
								pxTex2->U[iChan][j] = fRangeMin;
							}
							else
							{
								Asura_Assert(0,"Unexpected error adjusting UVs for PSP/PS2/XBox");
							}
						}
						if(pxTex2->U[iChan][j] > fRangeMax)
						{
							if(pxTex2->U[iChan][j] <= (fRangeMax+fEpsilon))
							{
								pxTex2->U[iChan][j] = fRangeMax;
							}
							else
							{
								Asura_Assert(0,"Unexpected error adjusting UVs for PSP/PS2/XBox");
							}
						}
						
						if(pxTex2->V[iChan][j] < fRangeMin)
						{
							if(pxTex2->V[iChan][j] >= (fRangeMin-fEpsilon))
							{
								pxTex2->V[iChan][j] = fRangeMin;
							}
							else
							{
								Asura_Assert(0,"Unexpected error adjusting UVs for PSP/PS2/XBox");
							}
						}
						if(pxTex2->V[iChan][j] > fRangeMax)
						{
							if(pxTex2->V[iChan][j] <= (fRangeMax+fEpsilon))
							{
								pxTex2->V[iChan][j] = fRangeMax;
							}
							else
							{
								Asura_Assert(0,"Unexpected error adjusting UVs for PSP/PS2/XBox");
							}
						}
					}
				}
			}
		}
	}
	delete [] pbPolyDone;

}

void Tools_Level_Export::AdjustUVCoordinates(Asura_Mesh* pxMesh)
{
	Asura_Assert(This,"");
	This->AdjustUVCoordinatesMain(pxMesh);
}

void Tools_Level_Export::AdjustAllUVCoordinates()
{
	UpdateStatus( EXPORT_STAGE_ADJUSTING_UV, "Adjusting UVs...", -1 );

	int dodgyPolygonCount = 0;
	int resetPolygonCount = 0;
	m_iNumberOfUntexturedPolygons = 0;
	m_iNumberOfPolygonsWithMissingTexture = 0;

	for(int i=0;i<Asura_Module_System::GetNumberOfModules();i++)
	{
		if(IsAbortFlagSet()) break;
		
		s_iPercent = i*100/Asura_Module_System::GetNumberOfModules();

		Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(i);
		Asura_Assert(mesh,"Failed to get the mesh for the module");

		AdjustUVCoordinatesMain(mesh,&resetPolygonCount,&dodgyPolygonCount);

		

		//count untextured polygons
		for(int j=0;j<mesh->NumberOfPolygons;j++)
		{
			int iPolygonFlags = mesh->PolygonFlagDataValid ? mesh->PolygonFlagDataPtr[j].Flags : 0;
			if(iPolygonFlags & (AsuraPolygonFlag_DontExport | AsuraPolygonFlag_DontRender))
			{
				continue;//ignore polygons that aren't going to be exported
			}
			
			
			Asura_Mesh_TextureInfo* tinfo = &mesh->TextureDataPtr[j];
			int iTextureID = Asura_Material_System::GetTextureID(tinfo->m_iMaterialID);

			if(iTextureID == ASURA_TEXTURE_NONE)
			{
				m_iNumberOfUntexturedPolygons++;
			}
			else if(!Asura_Texture_Management::IsTextureLoaded(iTextureID))
			{
				m_iNumberOfPolygonsWithMissingTexture++;
			}
		}

	}

	if(dodgyPolygonCount||resetPolygonCount)
	{
		if(resetPolygonCount)Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "%d polygons had stupidly large texture coordinates.These have been reset.",resetPolygonCount);
		if(dodgyPolygonCount)Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Found invalid texture coordinates on %d polygons. See logfile.",dodgyPolygonCount);
	    Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "");//(add a blank line)
	}

	if(m_iNumberOfUntexturedPolygons)
	{
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Found %d untextured polygons.",m_iNumberOfUntexturedPolygons);
	}

	if(m_iNumberOfPolygonsWithMissingTexture)
	{
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Found %d polygons using a missing texture.",m_iNumberOfPolygonsWithMissingTexture);
	}

	UpdateStatus(EXPORT_STAGE_END, 0, 0);
}

void Tools_Level_Export::AdjustUVCoordinates(Asura_Hierarchy_Skin* pxSkin)
{
	if(pxSkin->IsSkinnedModel())
	{
		//Disabled alteration of UVs for skinned models , since they need to be clamped to a different range (0 to 1)
		//However , these should already be in the correct range anyway. RAR 18/06/03
		#if 0 
		Asura_Mesh* pxMesh = Asura_Mesh_System::GetMesh(pxSkin->Name.GetString());
		if(pxMesh) AdjustUVCoordinatesMain(pxMesh);
		#endif
	}
	else
	{
		if(s_eExportType == Export_PSP)
		{
			//Similarly PSP object hierarchies are restricted to a different range (again 0 to 1 I think).
			//These should also hopefully be correct without alteration anyway. RAR  30/06/2008
			return;
		}

		for(int i=0;i<pxSkin->NumberOfBones;i++)
		{
			if(pxSkin->SectionData[i].Mesh)
			{
				AdjustUVCoordinatesMain(pxSkin->SectionData[i].Mesh);
			}
		}
	}	
}

void Tools_Level_Export::AddEnvironmentTextures(HANDLE saveHandle)
{
	DealWithEnvironmentTextures(saveHandle, false);
}
	
void Tools_Level_Export::ExcludeEnvironmentTextures()
	{
	DealWithEnvironmentTextures(INVALID_HANDLE_VALUE, true);
			}
			
	
void Tools_Level_Export::DealWithEnvironmentTextures(HANDLE saveHandle, bool bExclude)
{
	TOOLS_RESOURCEEXPORTLOG("Environment",0,false);

	//create an array to get the texture names
	const Asura_String** nameList = new const Asura_String*[ASURA_MAX_NO_OF_TEXTURES];
	//initialise array
	for(int i=0;i<ASURA_MAX_NO_OF_TEXTURES;i++)
	{
		nameList[i] = NULL;	
	}

	//and get those names
	Asura_Texture_Management::MakeNameList(nameList);

	//Initialise array for determining used textuers
	bool* pbTextureUsed = new bool[ASURA_MAX_NO_OF_TEXTURES];
	for(int i=0;i<ASURA_MAX_NO_OF_TEXTURES;i++)
	{
		pbTextureUsed[i] = false;	
	}

	const Asura_Environment* pxEnv = Asura_Environment_System::GetEnvironment();
	Asura_Assert(pxEnv, "");

	int iNumberOfTexturesUsed = 0;

	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM && !defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

	{
		UpdateStatus( EXPORT_STAGE_ADDING_ENVIRONMENT_MATERIALS, "Adding environment materials...", -1 );

		for( int i = 0; i < Asura_Module_System::GetNumberOfModules() && !IsAbortFlagSet(); ++i )
		{
			s_iPercent = i * 100 / Asura_Module_System::GetNumberOfModules();
			UpdateProgress();

			//Ignore any modules that don't match the current light set
			//RAR 12/12/2007
			if(!ShouldIncludeModuleInExport(i))
			{
				continue;
			}
			


			Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);
			for( int p = 0; p < pxMesh->NumberOfPolygons; ++p )
			{
				bool bIncludeTextures = true;
				if( pxMesh->PolygonFlagDataValid && (pxMesh->PolygonFlagDataPtr[p].Flags & (AsuraPolygonFlag_DontExport| AsuraPolygonFlag_DontRender)) )
				{
					if(pxMesh->PolygonFlagDataPtr[p].Flags & AsuraPolygonFlag_DontExport)
					{
						//ignore material for polygons that won't be exported
						continue;
					}
					if(pxMesh->PolygonFlagDataPtr[p].Flags & AsuraPolygonFlag_DontRender)
					{
						//Hidden polygon.
						//Still need the materials to be exported since the collision mesh refers to them , but
						//may not need the textures.
						//Check to see if the primary material has a texture animation that can make
						//the polygon visible
						//RAR 07/12/2007
						bIncludeTextures = false;
						Asura_Hash_ID uMaterialHash = pxMesh->TextureDataPtr[p].m_auMaterialResponseHashID[0];
						const Tools_MaterialResponse* pxMaterial = Tools_MaterialResponse_System::GetMaterialByHashID(uMaterialHash);
						if(pxMaterial && pxMaterial->GetTextureAnim())
						{
							Asura_Hash_ID uAnimID = pxMaterial->GetTextureAnim();
							if(uAnimID)
							{
								const Asura_TextureAnimation* pxAnim = Asura_TextureAnimation_System::GetAnimationByHash(uAnimID);
								if(pxAnim && (pxAnim->GetMaterialFlagsRemovedByAnimation() & AsuraMaterialFlag_DontRender))
								{
									//The material's animation can remove the don't render flag , so the textures are needed
									bIncludeTextures = true;
								}
							}
						}
					}
				}

				for(int j=0;j<TOOLS_NUM_MATERIALRESPONSE_PER_POLY;j++)
				{
					if(bExclude)
					{
						if(bIncludeTextures)
						{
							AddMaterialToIgnore(pxMesh->TextureDataPtr[p].m_auMaterialResponseHashID[j]);
						}
					}
					else
					{
						AddMaterialToExport( saveHandle, pxMesh->TextureDataPtr[p].m_auMaterialResponseHashID[j], bIncludeTextures);
					}
				}
			}
		}

		//add reflectance texture
		{
			Asura_String xTextureName = Asura_Render::GetReflectanceMapName();
			int iIndex = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
	   		if(iIndex != ASURA_TEXTURE_NONE)
			{
				if(!pbTextureUsed[iIndex])
				{
					pbTextureUsed[iIndex] = true;
					iNumberOfTexturesUsed++;
				}
			}
		}
	}
	#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY

		UpdateStatus( EXPORT_STAGE_FINDING_ENVIRONMENT_TEXTURES, "Finding environment textures...", -1 );

	//check each module mesh for used textures
	for(int i=0;i<Asura_Module_System::GetNumberOfModules() && !IsAbortFlagSet();i++)
	{
			s_iPercent = i * 100 / Asura_Module_System::GetNumberOfModules();
			UpdateProgress();

			//Ignore any modules that don't match the current light set
			//RAR 12/12/2007
			if(!ShouldIncludeModuleInExport(i))
			{
				continue;
			}

		Asura_Mesh* pxMesh = Asura_Module_System::GetMeshForModule(i);
		for(int p=0; p<pxMesh->NumberOfPolygons; p++)
		{
           if( pxMesh->PolygonFlagDataValid && (pxMesh->PolygonFlagDataPtr[p].Flags & (AsuraPolygonFlag_DontExport| AsuraPolygonFlag_DontRender)) )
			{
				//ignore textures for polygons that won't ne rendered/exported
				continue;
			}
            
            // albedo textures
            for( u_int uPass = 0; uPass < 2; ++uPass )
            {
			    int iTextureIndex = Asura_Material_System::GetTextureID( pxMesh->TextureDataPtr[p].m_iMaterialID, uPass );
			    if( ASURA_TEXTURE_NONE != iTextureIndex && !pbTextureUsed[iTextureIndex] )
			    {
				    pbTextureUsed[iTextureIndex] = true;
				    iNumberOfTexturesUsed++;
			    }
            }

			// normal maps
			for( u_int uPass = 0; uPass < 2; ++uPass )
            {
				int iTextureIndex = Asura_Material_System::GetNormalMapID( pxMesh->TextureDataPtr[p].m_iMaterialID, uPass );
			    if( ASURA_TEXTURE_NONE != iTextureIndex && !pbTextureUsed[iTextureIndex] )
			    {
				    pbTextureUsed[iTextureIndex] = true;
				    iNumberOfTexturesUsed++;
			    }
            }

			// Specular maps
			for( u_int uPass = 0; uPass < 2; ++uPass )
            {
				int iTextureIndex = Asura_Material_System::GetSpecularMapID( pxMesh->TextureDataPtr[p].m_iMaterialID, uPass );
			    if( ASURA_TEXTURE_NONE != iTextureIndex && !pbTextureUsed[iTextureIndex] )
			    {
				    pbTextureUsed[iTextureIndex] = true;
				    iNumberOfTexturesUsed++;
			    }
            }
		}
	}

	
	//add reflectance texture
	{
		Asura_String xTextureName = Asura_Render::GetReflectanceMapName();
		int iIndex = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
	   	if(iIndex != ASURA_TEXTURE_NONE)
		{
			if(!pbTextureUsed[iIndex])
			{
				pbTextureUsed[iIndex] = true;
				iNumberOfTexturesUsed++;
			}
		}
	}
	
	#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM || defined ASURA_MATERIAL_RESPONSE_CHARACTERS_ONLY
	if(!IsAbortFlagSet())
	{
		// AW - 11/04/03 - Add textures from entities which have no mesh,
		// but do have textures (e.g. Glass).
		UpdateStatus( EXPORT_STAGE_FINDING_MESHLESS_ENTITY_TEXTURES, "Finding meshless entity textures...", -1 );
    	for( Asura_Hashed_List_It<Tools_Entity_Object> xIt(Tools_Entity_Object::GetList()); !xIt.Done(); xIt.Next() )
		{
 			Asura_String xTextureName = (xIt.GetCurrent())->GetTextureNameForExport();
			if(xTextureName.IsBlank()) continue;
	
			int iIndex = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
		   	if(iIndex != ASURA_TEXTURE_NONE)
			{
				if(!pbTextureUsed[iIndex])
				{
					pbTextureUsed[iIndex] = true;
					iNumberOfTexturesUsed++;
				}
			}
		}
	}

	if(!IsAbortFlagSet())
	{
		//add static corona textures. RAR 01/05/03
		UpdateStatus( EXPORT_STAGE_FINDING_CORONA_TEXTURES, "Finding corona textures...", -1 );
		Asura_NameTree xTextureTree;
		Asura_StaticCorona_System::BuildTextureTree(&xTextureTree);
		if(xTextureTree.GetNumberOfNodes())
		{
			const Asura_String** ppxNameList = new const Asura_String*[xTextureTree.GetNumberOfNodes()];
			xTextureTree.MakeNameList(ppxNameList);
			for(int i=0;i<xTextureTree.GetNumberOfNodes();i++)
			{
				s_iPercent = i * 100 / xTextureTree.GetNumberOfNodes();
				UpdateProgress();

				if(ppxNameList[i]->IsBlank()) continue;
				int iIndex = Asura_Texture_Management::GetTextureIndex(ppxNameList[i]->GetString());
				if(iIndex != ASURA_TEXTURE_NONE)
				{
					if(!pbTextureUsed[iIndex])
					{
						pbTextureUsed[iIndex] = true;
						iNumberOfTexturesUsed++;
					}
				}
			}
			delete [] ppxNameList;
		}
	}
	
	#ifdef ASURA_USE_BILLBOARD_PLANTS
	if(!IsAbortFlagSet())
	{
		// add billboard plant textures to the used textures.
		UpdateStatus( EXPORT_STAGE_FINDING_BILLBOARD_PLANT_TEXTURES, "Finding billboard plant textures...", -1 );
		for (unsigned i=0;i<Asura_BillboardPlant_System::m_uNumberOfBillboardPlantBatches;i++)
		{
			s_iPercent = i * 100 / Asura_BillboardPlant_System::m_uNumberOfBillboardPlantBatches;
			UpdateProgress();

			Asura_BillboardPlant_Batch* pxBatch = Asura_BillboardPlant_System::m_ppxBillboardPlantBatches[i];
			int iPlantIndex = Asura_BillboardPlant_TemplateStore::GetIndexFromUniqueID(pxBatch->m_uPlantType);
			if (iPlantIndex == -1) continue;
			Asura_BillboardPlant_Template* pxTemplate = Asura_BillboardPlant_TemplateStore::GetTemplate(iPlantIndex);
			if (!pxTemplate) continue;
			Asura_String xTextureName(pxTemplate->m_szTextureName);
			if(xTextureName.IsBlank()) continue;

			int iIndex = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
		   	if(iIndex != ASURA_TEXTURE_NONE)
			{
				if(!pbTextureUsed[iIndex])
				{
					pbTextureUsed[iIndex] = true;
					iNumberOfTexturesUsed++;
				}
			}
		}
	}
	#endif //ASURA_USE_BILLBOARD_PLANTS

	#ifdef ASURA_USE_CARPET
	if(!IsAbortFlagSet())
	{
		if( ShouldExportCarpetTextures() )
		{
			const int iDefaultCarpetTextureFlags = AsuraTextureFlag_8BitAlpha | AsuraTextureFlag_DontPremultiplyAlphaInPaletteGeneration | AsuraTextureFlag_PS2DontHalveSize;

			UpdateStatus( EXPORT_STAGE_FINDING_CARPET_TEXTURES, "Finding carpet textures...", -1 );

			// Add the carpet textures to the list
			// Loop over all the carpet triangles making a list of styles which are active
			bool* pbStyleActive = new bool[Asura_Carpet_StyleManager::GetNumStyles()];
			Asura_LinkList<Asura_Carpet_Triangle>& xTriangleList = Asura_Carpet_MeshManager::GetTriangleList();
			for (Asura_ListElt<Asura_Carpet_Triangle>* pxElt = xTriangleList.Front(); pxElt; pxElt = pxElt->Next() )
			{		
				const Asura_Carpet_Triangle* const pxTri = &pxElt->Data();
				const int iStyle = Asura_Carpet_StyleManager::FindStyleFromUID( pxTri->iStyleUID );

				// Ignore if the style doesn't exist
				if ( iStyle < 0 )
				{
					continue;
				}
				// Ignore styles we have already considered
				if ( pbStyleActive[iStyle] == true )
				{
					continue;
				}
				// Only consider triangles with non-zero quads in them
				const Asura_Carpet_Style* const pxStyle = Asura_Carpet_StyleManager::GetStyle( iStyle );
				int iNumQuads = Asura_Maths::FloatToInt( pxTri->fArea * pxStyle->fDensity );
				if ( iNumQuads <= 0 )
				{
					continue;
				}
				pbStyleActive[iStyle] = true;
				// Get the style and its texture name then add that texture to the list
				const Asura_Carpet_Style* const pxCarpetStyle = Asura_Carpet_StyleManager::GetStyle( iStyle );
				const Asura_String& xTextureName = pxCarpetStyle->xTextureName;
				if ( xTextureName.IsBlank() ) 
				{
					continue;
				}
				int iTexID = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
				if ( iTexID != ASURA_TEXTURE_NONE )
				{
					if( !pbTextureUsed[iTexID] )
					{
						pbTextureUsed[iTexID] = true;
						iNumberOfTexturesUsed++;
						// Set the texture flags for this texture
						Asura_Texture_Management::SetTextureFlags( iTexID, iDefaultCarpetTextureFlags );
					}
				}
			}
			delete[] pbStyleActive;
		}
	}
	#endif //ASURA_USE_CARPET

	//add water textures
	#ifdef ASURA_USE_WATER
	if(!IsAbortFlagSet())
	{
		const Asura_Water_System::Env_Water_Controls& xControl = Asura_Water_System::GetEnvController();
		for(int i=0;i<3;i++)
		{
			const Asura_String& xTextureName = xControl.m_axToolsTextureNames[i];
			if(xTextureName.IsBlank()) continue;
	
			int iIndex = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
		   	if(iIndex != ASURA_TEXTURE_NONE)
			{
				if(!pbTextureUsed[iIndex])
				{
					pbTextureUsed[iIndex] = true;
					iNumberOfTexturesUsed++;
				}
			}
		}
	}
	#endif

	#if defined ASURA_USE_BOTANICALS
	// Export the materials required for the botancial shapes
	// The order these materials are exported needs to match
	// the order the material are requested when the data is loaded into the game at runtime
	if(!IsAbortFlagSet())
	{
		if(!bExclude)
		{
			if( ShouldExportBotanicalTextures() )
			{
				AddBotanicalsTextures( saveHandle );
			}	
		}
	}
	#endif

	if(!IsAbortFlagSet())
	{
		UpdateStatus( EXPORT_STAGE_ADDING_ENVIRONMENT_TEXTURES, "Adding environment textures...", -1 );
	
		//export the textures found
		UpdateProgress();
		int iNumberOfTexturesOutput = 0;
		for(int i = 0;i<ASURA_MAX_NO_OF_TEXTURES && !IsAbortFlagSet();i++)
		{
			if(!pbTextureUsed[i]) continue;
			
			s_iPercent = iNumberOfTexturesOutput++*100/iNumberOfTexturesUsed;
			if(nameList[i])
			{
				
				UpdateProgress();
				if(bExclude)
				{
					//prevent the environment textures from being included with the mission
					AddTextureToIgnore(nameList[i]->GetString());
				}
				else
				{
					//add this texture
					AddTexture(saveHandle,*nameList[i],i, true);
				}
				
			}
		}
		UpdateStatus(EXPORT_STAGE_END, 0, 0);
	}

	delete [] nameList;
	delete [] pbTextureUsed;

}


void Tools_Level_Export::AddSkyboxTextures(HANDLE pSaveHandle)
{
	TOOLS_RESOURCEEXPORTLOG("Skybox",0,false);

	//Make sure skybox textures have been loaded before doing anything else.
	//(They may not have been  if doing a batch export)
	// This may be irrelevant now i've redone this function actually , but no harm in leaving this. RAR 09/03/2006
	for(int i=0;i<Asura_Skybox::ASURA_SKYBOX_NUM_TEXTURES;i++)
	{
		Asura_String xTextureName = Asura_Skybox::GetTexture(i);
		if(xTextureName.IsBlank()) continue;
	
		Asura_Texture_Management::AddTexture(xTextureName.GetString());
	   		
	}
    
	UpdateStatus(EXPORT_STAGE_SKYBOX_TEXTURES, "Skybox Textures", 0);

	//add skybox textures
	for(int i=0;i<Asura_Skybox::ASURA_SKYBOX_NUM_TEXTURES;i++)
	{
		if(i==Asura_Skybox::ASURA_SKYBOX_FRACTALSKY && !Asura_Skybox::GetDrawClouds()) 
		{
			continue;//ignore clouds texture if it isn't being used
		}
		if(i==Asura_Skybox::ASURA_SKYBOX_FRACTALSKY_2 && !Asura_Skybox::GetDrawClouds())
		{
			continue;//and similarly for the secondary clouds
		}
		
		Asura_String xTextureName = Asura_Skybox::GetTexture(i);
		if(xTextureName.IsBlank()) continue;
	
		int iIndex = Asura_Texture_Management::GetTextureIndex(xTextureName.GetString());
	   	if(iIndex != ASURA_TEXTURE_NONE)
		{
			//add this texture
			s_iPercent = i*100/Asura_Skybox::ASURA_SKYBOX_NUM_TEXTURES;
			UpdateProgress();
			AddTexture(pSaveHandle,xTextureName.GetString(),iIndex, true);
		}
	}

	UpdateStatus(EXPORT_STAGE_END, 0, 0);
}

void Tools_Level_Export::AddMiscTextures(HANDLE pSaveHandle)
{
	const int iTotalCount = m_xMiscTextureList.size() + m_xGUIMenuTextureList.size();
  
	if(iTotalCount == 0) return;

	UpdateStatus(EXPORT_STAGE_ADD_MISC_TEXTURES, "Adding Misc Textures", 0);

	Asura_FixedArray< List<int>*, 2 > axLists;
	axLists[0] = &m_xMiscTextureList;
	axLists[1] = &m_xGUIMenuTextureList;

	Asura_FixedArray< bool, 2 > abSRGB;
	abSRGB[0] = true;	// Most misc textures should be treated as sRGB
	abSRGB[1] = false;	// But GUIMenu ones shouldn't, because they're rendered as overlay quads

	int iCount = 0;
	for(u_int u = 0; u < axLists.GetSize(); ++u )
	{
		for(LIF<int> xTextureLIF(axLists[u]);!xTextureLIF.done();xTextureLIF.next(),iCount++)
	{
		int iTextureIndex = xTextureLIF();

		const Asura_String* pxTextureName = Asura_Texture_Management::GetTextureName(iTextureIndex);
		if(!pxTextureName)
		{
			Asura_Assert(0,"Failed to get name of texture being exported");
			continue;
		}
		
		
	   	if(iTextureIndex != ASURA_TEXTURE_NONE)
		{
			//add this texture
			s_iPercent = iCount*100/iTotalCount;
			UpdateProgress();
			AddTexture(pSaveHandle,pxTextureName->GetString(),iTextureIndex,abSRGB[u]);
		}
	}
	}
	
	UpdateStatus(EXPORT_STAGE_END, 0, 0);
}

void Tools_Level_Export::AddEntityTextureToExportList(int iTextureID, void* pNotUsed)
{
	if(iTextureID != ASURA_TEXTURE_NONE)
	{
		AddTextureToExportList(iTextureID);
	}
}


void Tools_Level_Export::AddSkinTextures(HANDLE pSaveHandle)
{
	if(!m_pxSkinFileList->GetNumberOfNodes()) return;

	UpdateStatus( EXPORT_STAGE_ADDING_SKIN_TEXTURES, "Adding skin textures...", -1 );

	//get a list of skin file names
	const Asura_String** ppxNameList = new const Asura_String*[m_pxSkinFileList->GetNumberOfNodes()];
	m_pxSkinFileList->MakeNameList(ppxNameList);

	//Make sure all the skin files are loaded first to load all the texture flags
	//RAR 15/01/2008
	for(int i=0;i<m_pxSkinFileList->GetNumberOfNodes() && !IsAbortFlagSet();i++)
	{
		Asura_String skinName;
		const Asura_String& xFilename = *ppxNameList[i];
		xFilename.SplitFilename(0,&skinName,0);
		//load the file if it isn't already loaded
		if(!Asura_Animation_System::GetSkin(skinName.GetString()))
		{
			if(!LoadAsuraFile(xFilename.GetString()))
			{
				AddMessage(MT_ERROR,"Failed to load %s.",xFilename.GetString());
				continue;
			}
			if(!Asura_Animation_System::GetSkin(skinName.GetString()))
			{
				AddMessage(MT_ERROR,"Add Textures Failed to find skin %s.",skinName.GetString());
				continue;
			}
		}
	}

	for(int i=0;i<m_pxSkinFileList->GetNumberOfNodes() && !IsAbortFlagSet();i++)
	{
		s_iPercent = i * 100 / m_pxSkinFileList->GetNumberOfNodes();
		UpdateProgress();

		AddTexturesForSkin(pSaveHandle,*ppxNameList[i]);
	}

	delete [] ppxNameList;
}

void Tools_Level_Export::AddTexturesForSkin(HANDLE saveHandle,const Asura_String& xFilename)
{
	//determine skin name from filename
	Asura_String skinName;
	xFilename.SplitFilename(0,&skinName,0);
	//load the file if it isn't already loaded
	if(!Asura_Animation_System::GetSkin(skinName.GetString()))
	{
		if(!LoadAsuraFile(xFilename.GetString()))
		{
			AddMessage(MT_ERROR,"Failed to load %s.",xFilename.GetString());
			return;
		}
		if(!Asura_Animation_System::GetSkin(skinName.GetString()))
		{
			AddMessage(MT_ERROR,"Add Textures Failed to find skin %s.",skinName.GetString());
			return;
		}
	}
	Asura_Hierarchy_Skin* skin = Asura_Animation_System::GetSkin(skinName.GetString());
	if(skin)
	{
		AddTexturesForSkin(saveHandle,skin);
	}
}

void Tools_Level_Export::AddTexturesForSkin(HANDLE saveHandle,Asura_Hierarchy_Skin* skin)
{
	TOOLS_RESOURCEEXPORTLOG("Skin",skin->Name.GetString(),false);

	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	List<Asura_Hash_ID> xMaterialList;
	#else
	List<int> textureIndeces;
	#endif

	//Load the Asura_Meshes for the skin if they don't already exist
	#if defined ASURA_ASSERT
		bool bLoaded =
	#endif
		Tools_Animation_Files::LoadMeshForSkin(skin);

	Asura_Assertf(bLoaded,("Failed to load the meshes for skin %s",skin->Name.GetString()));

	//check which textures are used by the mesh(es) involved
	
	for(int i=0;i<skin->NumberOfBones;i++)
	{
		Asura_Mesh* mesh;
		if(skin->NumberOfVertices)
		{
			if(i) break;
			//get the mesh
			mesh = Asura_Mesh_System::GetMesh(skin->Name);
			if(!mesh)
			{
				AddMessage(MT_ERROR,"Failed to find mesh '%s'.",skin->Name.GetString());
				return;
			}
		}
		else
		{
			Asura_Assert(skin->SectionData,"");
			mesh = skin->SectionData[i].Mesh;
		}
		if(!mesh) continue;
		if(!mesh->TextureDataValid) return;

		// (SB - 10/04/08) If the skin has any LODs then add the materials for these too
		if ( skin->m_pxNextLowerSkin != NULL )
		{
			AddTexturesForSkin( saveHandle, skin->m_pxNextLowerSkin );
		}

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		{
			//add the textures for this mesh to the list
			for(int i=0;i<mesh->NumberOfPolygons;i++)
			{
				for(int j=0;j<TOOLS_NUM_MATERIALRESPONSE_PER_POLY;j++)
				{
					Asura_Hash_ID uMaterial = mesh->TextureDataPtr[i].m_auMaterialResponseHashID[j];
					if(uMaterial && !xMaterialList.contains(uMaterial))
					{
						xMaterialList.add_entry(uMaterial);
					}
				}
			}
		}

#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

		{
			//add the textures for this mesh to the list
			for(int i=0;i<mesh->NumberOfPolygons;i++)
			{
				// Diffuse texture
				int index = Asura_Material_System::GetTextureID(mesh->TextureDataPtr[i].m_iMaterialID);

				if(index>=0 && !textureIndeces.contains(index))
				{
					textureIndeces.add_entry(index);
				}

				// Normal map
				int iNormalMapindex = Asura_Material_System::GetNormalMapID(mesh->TextureDataPtr[i].m_iMaterialID);

				if(iNormalMapindex>=0 && !textureIndeces.contains(iNormalMapindex))
				{
					textureIndeces.add_entry(iNormalMapindex);
				}

				// Specular map
				int iSpecularMapIndex = Asura_Material_System::GetSpecularMapID(mesh->TextureDataPtr[i].m_iMaterialID);

				if(iSpecularMapIndex>=0 && !textureIndeces.contains(iSpecularMapIndex))
				{
					textureIndeces.add_entry(iSpecularMapIndex);
				}

#ifdef ASURA_USE_DAMAGE_MAPS
				{		
					// When we're using damage maps, we need to support the dual-texture slots on skins
					int iMtl = mesh->TextureDataPtr[i].m_iMaterialID;
					if (Asura_Material_System::IsMaterialDualTexture(iMtl))
					{
						int iTextureIndex = Asura_Material_System::GetTextureID(iMtl,1);
						if (iTextureIndex>=0 && !textureIndeces.contains(iTextureIndex))
						{
							textureIndeces.add_entry(iTextureIndex);
						}
					}

					if (Asura_Material_System::IsMaterialDualNormalMap(iMtl))
					{
						int iTextureIndex = Asura_Material_System::GetNormalMapID(iMtl,1);
						if (iTextureIndex>=0 && !textureIndeces.contains(iTextureIndex))
						{
							textureIndeces.add_entry(iTextureIndex);
						}
					}

					if (Asura_Material_System::IsMaterialDualSpecularMap(iMtl))
					{
						int iTextureIndex = Asura_Material_System::GetSpecularMapID(iMtl,1);
						if (iTextureIndex>=0 && !textureIndeces.contains(iTextureIndex))
						{
							textureIndeces.add_entry(iTextureIndex);
						}
					}
				}
#endif // ASURA_USE_DAMAGE_MAPS
			}
		}
#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	}
	
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	//Allow project code to adjust the material list
	Project_AddMaterialsForSkin(xMaterialList, skin, saveHandle);

	//Add the listed materials
	for(LIF<Asura_Hash_ID> xMaterialLIF(&xMaterialList); !xMaterialLIF.done(); xMaterialLIF.next())
	{
		AddMaterialToExport( saveHandle, xMaterialLIF() );
	}
	
#else // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	Project_AddTexturesForSkin(textureIndeces, skin, saveHandle);

		
	//create an array to get the texture names
	const Asura_String** nameList = new const Asura_String*[ASURA_MAX_NO_OF_TEXTURES];
	//initialise array
	int i;
	for(i=0;i<ASURA_MAX_NO_OF_TEXTURES;i++)
	{
		nameList[i] = NULL;	
	}

	//and get those names
	Asura_Texture_Management::MakeNameList(nameList);

	i=0;
	for(CLIF<int> ilif(&textureIndeces);!ilif.done();ilif.next(),i++)
	{
		int iTextureID = ilif();
		if(nameList[iTextureID])
		{
			//add this texture to the list of skin textures
			int iJunk;
			m_pxSkinTextureList->FindNodeIndexByName(nameList[iTextureID]->GetString(),true,iJunk);


			//Make sure the default texture flags are set in the texture management system. RAR 16/05/2006
			Asura_Texture_Management::AddTextureFlags(iTextureID,GetDefaultCharacterTextureFlags());
			
			//add this texture
			AddTexture(saveHandle,*nameList[iTextureID],iTextureID);
		}
		
	}

	delete [] nameList;

#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

}

u_int Tools_Level_Export::GetDefaultCharacterTextureFlags( void )
{
	//Flags no longer applied here, in favour of applying them by hand where required.
	//RAR 08/02/2008
	return 0;
	//return AsuraTextureFlag_8BitAlpha|AsuraTextureFlag_DontPremultiplyAlphaInPaletteGeneration;
}

void Tools_Level_Export::AddTextureToIgnore(const char* szFilename)
{
	//Add to list of textures that have already been exported ,this will prevent it from being exported later.
	int iJunk;
	m_pxExportedTexturesList->FindNodeIndexByName(szFilename,true,iJunk);
}


bool Tools_Level_Export::AddTexture( HANDLE saveHandle, const Asura_String& filename, int index, bool bSRGB /*= true*/ )
{
	//make sure this texture hasn't already been added
	int iJunk;
	if(m_pxExportedTexturesList->FindNodeIndexByName(filename,true,iJunk))
	{
		return(true);
	}

	//check against environment textures list (which will exist during cutscene exports)
	if(m_pxEnvExportedTexturesList)
	{
		if(m_pxEnvExportedTexturesList->FindNodeIndexByName(filename,true,iJunk))
		{
			return(true);
		}
	}

	TOOLS_RESOURCEEXPORTLOG("Texture",filename.GetString(),true);


    Asura_String OutputFilename;
	TextureConversionData xOutputConversionData;
    if( !GenerateTextureIfNecessary(filename, index,false, bSRGB, &OutputFilename,&xOutputConversionData) )
	{
		return(false);
	}
	
	HANDLE file = CreateFile(OutputFilename.GetString(),GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
	if(file==INVALID_HANDLE_VALUE)
	{
		AddMessage(MT_ERROR,"Failed to open %s",OutputFilename.GetString());
		return(false);
	}

	//allocate a buffer , and read in the file
	int trueFileSize = GetFileSize(file,0);
	int fileSize = ((trueFileSize+3)/4)*4;
	char* buffer = new char[fileSize];
	unsigned long bytesRead;
	ReadFile(file,buffer,trueFileSize,&bytesRead,0);
	CloseHandle(file);

	if(s_eExportType == Export_PS2)
	{
		if(trueFileSize>1024*1024)
		{
			//complain if too large
			AddMessage(MT_ERROR,"Tim2 file is %d K. Must be less than 1 Mb",trueFileSize/1024);
		}
	}
	
	Asura_String xBaseFilenameForResource = "\\graphics";
	xBaseFilenameForResource += filename;

    Asura_String xActualFilenameForResource;

    // For PS2 exports, the actual texture data needs to be on a QW boundary.
    if( Export_PS2 == s_eExportType )
    {
        const u_int uSizeOfHeaderAndBaseResourceName = sizeof(Asura_Chunk_ResourceFile) + (xBaseFilenameForResource.GetSizeInInts() << 2);

        // Get num bytes written so far.
        int iNumBytesWrittenSoFar = SetFilePointer( saveHandle, 0, NULL, FILE_CURRENT );
        if( INVALID_SET_FILE_POINTER == iNumBytesWrittenSoFar )
        {
            AddMessage( MT_ERROR, "Couldn't check how many bytes exported so far. Reason: %s.", Tools_OS::GetLastSystemError() );
        }

        // Calculate num bytes of padding required.
        u_int uPaddingRequired = 0;
        if( (iNumBytesWrittenSoFar + uSizeOfHeaderAndBaseResourceName) & 15 )
        {
            uPaddingRequired = 16 - ((iNumBytesWrittenSoFar + uSizeOfHeaderAndBaseResourceName) & 15);
        }

        // If any is required, insert spaces at the beginning of the resource
        // name.
        if( uPaddingRequired )
        {
            char szSpaces[16 + 1] = "                ";

            szSpaces[uPaddingRequired] = '\0';
            xActualFilenameForResource = szSpaces;
        }
    }

    xActualFilenameForResource += xBaseFilenameForResource;
    
	const int iTotalSize = sizeof(Asura_Chunk_ResourceFile) + (xActualFilenameForResource.GetSizeInInts()*4) + (fileSize);

	DWORD uBytesWritten;
	//create the chunk header
	//RAR 15/05/2006 Texture flags now get stored in the sub type (was previously unused for textures)
	Asura_Chunk_ResourceFile xChunk;
	u_int uFlags=0;
	if( s_eExportType == Export_Xbox360 && Tools_Xbox360_Texture_Writer::ShouldExportAsXPR( index ) )
	{
		uFlags |= Asura_Chunk_ResourceFile::TF_XBOX360_TEXTURE_XPR;
	}

	if( bSRGB )
	{
		uFlags |= Asura_Chunk_ResourceFile::TF_SRGB;
	}

	xChunk.Create(iTotalSize, uFlags, ASURA_RESOURCEFILE_TYPE_TEXTURE, xOutputConversionData.m_iTextureFlags, fileSize);
	
	WriteFile(saveHandle,&xChunk,sizeof(xChunk),&uBytesWritten,0);

	// add archivename (padded to int)
	//WriteFile(saveHandle,xActualFilenameForResource.GetString(),xActualFilenameForResource.GetSizeInInts()*4,&uBytesWritten,NULL);
	WriteFileString(saveHandle,xActualFilenameForResource.GetString(),&uBytesWritten);

	// add contents of file
	WriteFile(saveHandle,buffer,fileSize,&uBytesWritten,0);

	delete [] buffer;
	return(true);
}

void Tools_Level_Export::AddTextureToExportList(int iTextureID)
{
	Asura_Assert(This,"");

	if(iTextureID == ASURA_TEXTURE_NONE) return;
	if(This->m_xMiscTextureList.contains(iTextureID)) return;
	This->m_xMiscTextureList.add_entry(iTextureID);
}

void Tools_Level_Export::AddTextureToGUIMenuList(int iTextureID)
{
	Asura_Assert(This,"");

	if(iTextureID == ASURA_TEXTURE_NONE) return;
	if(This->m_xGUIMenuTextureList.contains(iTextureID)) return;
	This->m_xGUIMenuTextureList.add_entry(iTextureID);
}

void Tools_Level_Export::CheckForInvalidTextureFlags( const Asura_String& xFilename, int iIndex )
{
// (MH - 24.09.2002) 8-bit alpha now allowed for translucent polys. But there are no 
// guarantees about sorting and stuff!
//	int iTextureFlags = Asura_Texture_Management::GetTextureFlags(iIndex);
//
//	if(s_eExportType == Export_PS2)
//	{
//		if(iTextureFlags & AsuraTextureFlag_8BitAlpha)
//		{
//			bool b8BitAllowed = false;
//			//can be used for self-illuminating textures
//			if(iTextureFlags & AsuraTextureFlag_SelfIlluminate)
//			{
//				b8BitAllowed = true;
//			}
//			else
//			{
//				//and for skybox textures
//				for(int i=0;i<7;i++)
//				{
//					if(xFilename == Asura_Skybox::GetTexture(i))
//					{
//						b8BitAllowed = true;
//						break;
//					}
//				}
//			}
//			if(!b8BitAllowed)
//			{
//				AddMessage(MT_ERROR,"%s has the 8 bit alpha flag",xFilename.GetString());
//				AddMessage(MT_ERROR,"This flag should only be used for self-illuminating and skybox textures");
//			}
//		} 
//	}
    int iTextureFlags = Asura_Texture_Management::GetTextureFlags(iIndex);
	unsigned int uMaterialFlags = Asura_Material_System::GetMaterialFlagsUsedWithTexture(iIndex);
    if(s_eExportType == Export_PS2)
    {
        // try to detect illegal combinations


        bool b8BitAlpha = (iTextureFlags & AsuraTextureFlag_8BitAlpha) != 0;

        bool bFMV = (iTextureFlags & AsuraTextureFlag_FMV) != 0;

        bool bSelfIlluminate = (uMaterialFlags & AsuraMaterialFlag_SelfIlluminate) != 0;
        bool bTransparent = (uMaterialFlags & AsuraMaterialFlag_Transparent) != 0;
        bool bReflective = (uMaterialFlags & AsuraMaterialFlag_Reflective) != 0;
        bool bFlowingWater = (uMaterialFlags & AsuraMaterialFlag_FlowingWater) != 0;
        bool bWaterfall = (uMaterialFlags & AsuraMaterialFlag_Waterfall) != 0;
        bool bAdditiveAlpha = (uMaterialFlags & AsuraMaterialFlag_PS2AdditiveAlpha) != 0;
            
        if (bFMV && (bSelfIlluminate || bTransparent || b8BitAlpha || bReflective || bFlowingWater || bWaterfall || bAdditiveAlpha))
        {
            AddMessage(MT_ERROR,"%s has the FMV flag as well as other incompatible ones.",xFilename.GetString());
        }
        
        /* This check still needs to be rewritten to deal with material flags properly. RAR 17/02/03
        if (bTransparent && (bSelfIlluminate || b8BitAlpha || bReflective || bFlowingWater || bWaterfall || bAdditiveAlpha))
        {
            AddMessage(MT_ERROR,"%s has the transparent flag as well as other incompatible ones.",xFilename.GetString());
        }
		*/
        // todo: add others here.
        // (MH - 17.10.2002) maybe should deal with all this in texture flag mode.
        // could also do with sorting out when 8-bit alpha and transparent get used.

    }
}


bool Tools_Level_Export::GenerateDDSFile(const char* pszInputName,const char* pszOutputName,int iIndex)
{
	EXPORT_TYPE eSafeExportType = s_eExportType;
	s_eExportType = Export_PC;

	//initialise conversion data struct for checking to see if the existing dds is valid
	TextureConversionData xConvertData;
	xConvertData.Initialise( s_eExportType );

	xConvertData.m_iTextureFlags = Asura_Texture_Management::GetTextureFlags(iIndex);
	xConvertData.m_uMaterialFlags = Asura_Material_System::GetMaterialFlagsUsedWithTexture(iIndex);
	xConvertData.m_bSwizzled = false;
	xConvertData.m_bUnpalettised = false; //(irrelevant , just leave as false)
	xConvertData.m_iPixelFormat = 0;//ditto
	xConvertData.m_iPaletteFormat = 0;//ditto

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	const u_int uTextureNameHash = Asura_Texture_Management::GetHashIDFromIndex( iIndex );
	xConvertData.m_uTextureUsageFlags = Tools_MaterialResponse_System::GetTextureUsage( uTextureNameHash );
#endif

    if (xConvertData.m_uMaterialFlags & AsuraMaterialFlag_PS2AdditiveAlpha)
    {
        xConvertData.m_iTextureFlags |= AsuraTextureFlag_8BitAlpha;
    }
	//Ignore no mip map flag. RAR 08/12/2004
	xConvertData.m_iTextureFlags &=~ AsuraTextureFlag_NoMipMaps;

	bool bNeedToGenerate = false;

	xConvertData.ClearUnusedEntriesForPlatform(Export_PC);
	const Asura_Hash_ID uConversionHash = xConvertData.GetHash();

	#if 1
	//check to see if local copy of converted file is okay
	if(!Tools_FileSystem::DoModifiedTimesMatch(pszInputName,pszOutputName)  || !ConversionDataFileMatches(pszOutputName,&xConvertData))
	{
		Asura_String xShadowFilename;
		Tools_FileSystem::GetShadowConversionFilename(pszOutputName,&xShadowFilename,uConversionHash);
		//check network version
		if(xShadowFilename.IsBlank() || !Tools_FileSystem::DoModifiedTimesMatch(pszInputName,xShadowFilename.GetString())  || !ConversionDataFileMatches(xShadowFilename.GetString(),&xConvertData))
		{
			//need to regenerate file then
			bNeedToGenerate = true;
		}
		else
		{
			char szMessage[200];
			Asura_PrintfToBfr(szMessage,"Retrieving %s from network",200,true,Tools_FileSystem::StripPath(pszInputName));
			DisplayProgressMessage(szMessage);
			bNeedToGenerate = !Tools_FileSystem::CopyFromConvertedFileShadow(pszOutputName, uConversionHash);
		}
	}
	#else
	//some debugging junk. RAR 06/07/2006
	const char* szLogFile = "Logfiles\\DDSConversion.txt";

	static bool s_bFirstTime = true;
	if(s_bFirstTime)
	{
		Asura_SimpleLogfileClear(szLogFile);
		s_bFirstTime = false;
	}

	Asura_SimpleLogfileOutput((szLogFile,"\nChecking %s\n",pszInputName));

	if(!Tools_FileSystem::DoModifiedTimesMatch(pszInputName,pszOutputName)  || !ConversionDataFileMatches(pszOutputName,&xConvertData))
	{
		if(!Tools_FileSystem::DoModifiedTimesMatch(pszInputName,pszOutputName))
		{
			FILETIME xInputTime,xOutputTime;
			if(!Tools_FileSystem::GetModifiedTime(pszInputName,&xInputTime))
			{
				Asura_SimpleLogfileOutput((szLogFile,"Failed to get source time\n"));
			}
			else if(!Tools_FileSystem::GetModifiedTime(pszOutputName,&xOutputTime))
			{
				Asura_SimpleLogfileOutput((szLogFile,"Failed to get local destination time\n"));
			}
			else
			{
				Asura_SimpleLogfileOutput((szLogFile,"Local cache modified time differs\n"));
				Asura_SimpleLogfileOutput((szLogFile,"    Input :  0x%x 0x%x\n",xInputTime.dwHighDateTime,xInputTime.dwLowDateTime));
				Asura_SimpleLogfileOutput((szLogFile,"    Output : 0x%x 0x%x\n",xOutputTime.dwHighDateTime,xOutputTime.dwLowDateTime));
			}
		}
		if(!ConversionDataFileMatches(pszOutputName,&xConvertData))
		{
			Asura_SimpleLogfileOutput((szLogFile,"Local cache dat file differs\n"));
		}
		
		Asura_String xShadowFilename;
		Tools_FileSystem::GetShadowConversionFilename(pszOutputName,&xShadowFilename, uConversionHash);
		//check network version
		if(xShadowFilename.IsBlank() || !Tools_FileSystem::DoModifiedTimesMatch(pszInputName,xShadowFilename.GetString())  || !ConversionDataFileMatches(xShadowFilename.GetString(),&xConvertData))
		{
			if(!xShadowFilename.IsBlank())
			{
				if(!Tools_FileSystem::DoModifiedTimesMatch(pszInputName,xShadowFilename.GetString()))
				{
					FILETIME xInputTime,xOutputTime;
					if(!Tools_FileSystem::GetModifiedTime(pszInputName,&xInputTime))
					{
						Asura_SimpleLogfileOutput((szLogFile,"Failed to get source time\n"));
					}
					else if(!Tools_FileSystem::GetModifiedTime(xShadowFilename.GetString(),&xOutputTime))
					{
						Asura_SimpleLogfileOutput((szLogFile,"Failed to get shadow destination time\n"));
					}
					else
					{
						Asura_SimpleLogfileOutput((szLogFile,"Shadow cache modified time differs\n"));
						Asura_SimpleLogfileOutput((szLogFile,"    Input :  0x%x 0x%x\n",xInputTime.dwHighDateTime,xInputTime.dwLowDateTime));
						Asura_SimpleLogfileOutput((szLogFile,"    Output : 0x%x 0x%x\n",xOutputTime.dwHighDateTime,xOutputTime.dwLowDateTime));
					}
				}
				if(!ConversionDataFileMatches(xShadowFilename.GetString(),&xConvertData))
				{
					Asura_SimpleLogfileOutput((szLogFile,"Shadow cache dat file differs\n"));
				}
			}
			
			//need to regenerate file then
			bNeedToGenerate = true;
		}
		else
		{
			char szMessage[200];
			Asura_PrintfToBfr(szMessage,"Retrieving %s from network",200,true,Tools_FileSystem::StripPath(pszInputName));
			DisplayProgressMessage(szMessage);
			bNeedToGenerate = !Tools_FileSystem::CopyFromConvertedFileShadow(pszOutputName, uConversionHash);
		}

		if(bNeedToGenerate)
		{
			Asura_SimpleLogfileOutput((szLogFile,"Generating texture\n"));
		}
	}

	#endif //debugging stuff


	if(bNeedToGenerate)
	{
		ASURA_CPU_PROFILE_SCOPED_TIMER( "Tools_Level_Export::GenerateDDSFile Generating");

		char szMessage[200];
		if(!s_bIsExport)
		{
			Asura_PrintfToBfr(szMessage,"Converting %s...      ",200,true,Tools_FileSystem::StripPath(pszInputName));
			DisplayProgressMessage(szMessage);
		}
 		
		Tools_FileSystem::EnsureDirectoryExists(pszOutputName);
		if(CreateDDSFile(pszInputName,pszOutputName,iIndex,xConvertData))
		{
			if(!s_bIsExport)
			{
				Asura_PrintfToBfr(szMessage,"Converting %s...Done  ",200,true,Tools_FileSystem::StripPath(pszInputName));
				DisplayProgressMessage(szMessage);
			}
			
			
			Tools_FileSystem::CopyFileTimes(pszInputName,pszOutputName);
			CreateConversionDataFile(pszOutputName,&xConvertData);
			Tools_FileSystem::CopyToConvertedFileShadow(pszInputName, pszOutputName, uConversionHash);
			s_eExportType = eSafeExportType;
			return true;//succesfully generated dds file
		}
		if(!s_bIsExport)
		{
			Asura_PrintfToBfr(szMessage,"Converting %s...Failed",200,true,Tools_FileSystem::StripPath(pszInputName));
			DisplayProgressMessage(szMessage);
			Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Converting %s...Failed", Tools_FileSystem::StripPath(pszInputName) );
		}

		s_eExportType = eSafeExportType;
		return false;//failed to generate dds file
	}
	s_eExportType = eSafeExportType;
	return true;//existing file was okay
}

bool Tools_Level_Export::IsSkyboxTexture(int iTextureIndex)
{
	return Tools_SkyboxOverlay::IsSkyboxTexture(iTextureIndex);
}

D3DFORMAT CheckDDSFormat(const char* inputName, u_int *puMipMapCount)
{
	Asura_Handle hHandle = Asura_FileAccess::Open(inputName, false, false);
	if (hHandle != ASURA_INVALID_HANDLE)
	{
		struct Asura_PC_DDS_Header
		{
			u_int m_uMagic;
			u_int m_uSize;
			u_int m_uFlags;
			u_int m_uHeight;
			u_int m_uWidth;
			u_int m_uPitchOrLinearSize;
			u_int m_uDepth;
			u_int m_uMipMapCount;
			u_int m_auReserved[11];
			DDPIXELFORMAT m_xPixelFormat;
			DDSCAPS2 m_xCaps;
			u_int m_uReserved2;
		} xHdr;

		bool bOK = Asura_FileAccess::ReadData(hHandle, &xHdr, sizeof(xHdr));
		Asura_FileAccess::Close(hHandle);

		bOK = bOK && xHdr.m_uMagic == ' SDD'                                       // Magic number is 'DDS ', but stored big-endian in the file
			&& xHdr.m_uSize == sizeof( Asura_PC_DDS_Header ) - sizeof( DWORD )		// The magic number isn't included in the size
			&& (xHdr.m_uFlags & DDSD_PIXELFORMAT);


		if (bOK && puMipMapCount)
		{
			*puMipMapCount = xHdr.m_uFlags & DDSD_MIPMAPCOUNT ? xHdr.m_uMipMapCount : 1;
		}

		if (bOK && xHdr.m_xPixelFormat.dwFlags == (DDPF_LUMINANCE|DDPF_ALPHAPIXELS)
			&& xHdr.m_xPixelFormat.dwRGBBitCount == 16
			&& xHdr.m_xPixelFormat.dwAlphaBitDepth == 16)
		{
			return D3DFMT_A8L8;
		}

		if (bOK && xHdr.m_xPixelFormat.dwFlags == DDPF_LUMINANCE
			&& xHdr.m_xPixelFormat.dwRGBBitCount == 8)
		{
			return D3DFMT_L8;
		}

	}
	return D3DFMT_UNKNOWN;
}


bool Tools_Level_Export::CreateDDSFile(const char* inputName,const char* outputName,int index, const TextureConversionData& xConversionData)
{
	Asura_Assertf(!Tools_FileSystem::IsReadOnly(outputName),("%s is readonly. It will be made writeable.",outputName));
	Tools_FileSystem::MakeWriteable(outputName);

	// (AAM - 12.06.2007) If this is already a DDS file, no need to go through this whole process.
	// Just copy the input to the output destination
	if( Asura_StringUtil::EndsWith( inputName, ".dds", true ) )
	{
		// [27/10/2008 AS] Unfortunately we need to convert gamma correct L8's to L16s so need to know what we are dealing with
		u_int uMipMapCount;
		bool bSuccess;
		switch (xConversionData.m_bSRGB && s_eExportType == Export_Xbox360 ? CheckDDSFormat(inputName, &uMipMapCount) : D3DFMT_UNKNOWN)
		{
		// L8 get converted to L16
		case D3DFMT_L8:
		{
			HRESULT hres;
			Direct3D_Texture* dxTexture = 0;
			Direct3D_Surface* dxSurface = 0;
			D3DSURFACE_DESC dxSurfaceDesc;


			//get the filename without path
			Asura_String filename;
			((Asura_String) inputName).SplitFilename(0,&filename,0);

			D3DXIMAGE_INFO imageInfo;

			//attempt to load in the texture to an 8888 format
			hres = D3DXCreateTextureFromFileExA(
				Asura_Direct3D::D3DDevice,
				inputName,
				D3DX_DEFAULT,//width
				D3DX_DEFAULT,//height
				uMipMapCount,//MipLevels
				0,//Usage
				D3DFMT_L8,//format
				D3DPOOL_SYSTEMMEM,//Pool
				D3DX_FILTER_NONE,//Filter
				D3DX_FILTER_NONE,//MipFilter
				0,//ColorKey
				&imageInfo,//pSourceInfo
				0,//pPalette
				&dxTexture
				);	

			if(FAILED(hres))
			{
				Asura_Assertf(0,("Failed to load texture '%s' for convertion", inputName));
				AddMessage(MT_ERROR,"Error %x loading %s",hres,inputName);
				return false;
			}

			//get the surface
			hres = dxTexture->GetSurfaceLevel(0,&dxSurface);
			if(FAILED(hres))
			{
				Asura_Assertf(0, ("Failed to get texture '%s' surface", inputName));
				dxTexture->Release();
				return false;
			}

			//get the surface description
			hres = dxSurface->GetDesc(&dxSurfaceDesc);
			if(FAILED(hres))
			{
				Asura_Assertf(0,("Failed to get texture '%s' surface description", inputName));
				dxTexture->Release();
				dxSurface->Release();
				return false;
			}
			dxSurface->Release();

			//check its in the format requested
			if(dxSurfaceDesc.Format != D3DFMT_L8)
			{
				Asura_Assert(0,"Failed to load texture as D3DFMT_L8");
				dxTexture->Release();
				return false;
			}

			//warn if width/height have changed
			if(imageInfo.Width!=dxSurfaceDesc.Width || imageInfo.Height!=dxSurfaceDesc.Height)
			{
				AddMessage(MT_WARNING,"Image resized from %dx%d to %dx%d by loaders",imageInfo.Width,imageInfo.Height,dxSurfaceDesc.Width,dxSurfaceDesc.Height);
			}

			Direct3D_Texture* dxNewTexture = 0;

			if (!SUCCEEDED(Asura_Direct3D::D3DDevice->CreateTexture(dxSurfaceDesc.Width, dxSurfaceDesc.Height, uMipMapCount, 0, D3DFMT_L16, D3DPOOL_SYSTEMMEM, &dxNewTexture, NULL)))
			{
				Asura_Assert(0,"Unable to convert L8 texture to L16, unable to create L16 texture");
			} else {
				for (u_int i = 0; i < uMipMapCount; ++i)
				{
					D3DLOCKED_RECT xSourceRect;
					D3DLOCKED_RECT xDestRect;
					dxTexture->LockRect(i, &xSourceRect, NULL, D3DLOCK_READONLY);
					dxNewTexture->LockRect(i, &xDestRect, NULL, 0);
					u_int uMipHeight = Asura_Maths::Max(dxSurfaceDesc.Height>>i, 1u);
					u_int uMipWidth = Asura_Maths::Max(dxSurfaceDesc.Width>>i, 1u);

					for (u_int y = 0; y < uMipHeight; ++y)
					{
						for (u_int x = 0; x < uMipWidth; ++x)
						{
							*LookupTexel<u_short>(&xDestRect, 2, x, y) = 
								Xbox360_PiecewiseLinearDegamma(*LookupTexel<u_char>(&xSourceRect, 1, x, y));
						}
					}
					dxNewTexture->UnlockRect(i);
					dxTexture->UnlockRect(i);
				}
				bSuccess = SUCCEEDED(D3DXSaveTextureToFile(outputName, D3DXIFF_DDS, dxNewTexture, NULL));
				dxNewTexture->Release();
				dxTexture->Release();
			}
		}
		break;
		// A8L8 get converted to A8R8G8B8 (No A16L16 format and would be the same size in memory anyway)
		case D3DFMT_A8L8:
		{
			HRESULT hres;
			Direct3D_Texture* dxTexture = 0;
			Direct3D_Surface* dxSurface = 0;
			D3DSURFACE_DESC dxSurfaceDesc;


			//get the filename without path
			Asura_String filename;
			((Asura_String) inputName).SplitFilename(0,&filename,0);

			D3DXIMAGE_INFO imageInfo;

			//attempt to load in the texture to an 8888 format
			hres = D3DXCreateTextureFromFileExA(
				Asura_Direct3D::D3DDevice,
				inputName,
				D3DX_DEFAULT,//width
				D3DX_DEFAULT,//height
				uMipMapCount,//MipLevels
				0,//Usage
				D3DFMT_A8L8,//format
				D3DPOOL_SYSTEMMEM,//Pool
				D3DX_FILTER_NONE,//Filter
				D3DX_FILTER_NONE,//MipFilter
				0,//ColorKey
				&imageInfo,//pSourceInfo
				0,//pPalette
				&dxTexture
				);	

			if(FAILED(hres))
			{
				Asura_Assertf(0,("Failed to load texture '%s' for convertion", inputName));
				AddMessage(MT_ERROR,"Error %x loading %s",hres,inputName);
				return false;
			}

			//get the surface
			hres = dxTexture->GetSurfaceLevel(0,&dxSurface);
			if(FAILED(hres))
			{
				Asura_Assertf(0, ("Failed to get texture '%s' surface", inputName));
				dxTexture->Release();
				return false;
			}

			//get the surface description
			hres = dxSurface->GetDesc(&dxSurfaceDesc);
			if(FAILED(hres))
			{
				Asura_Assertf(0,("Failed to get texture '%s' surface description", inputName));
				dxTexture->Release();
				dxSurface->Release();
				return false;
			}
			dxSurface->Release();

			//check its in the format requested
			if(dxSurfaceDesc.Format != D3DFMT_A8L8)
			{
				Asura_Assert(0,"Failed to load texture as D3DFMT_A8L8");
				dxTexture->Release();
				return false;
			}

			//warn if width/height have changed
			if(imageInfo.Width!=dxSurfaceDesc.Width || imageInfo.Height!=dxSurfaceDesc.Height)
			{
				AddMessage(MT_WARNING,"Image resized from %dx%d to %dx%d by loaders",imageInfo.Width,imageInfo.Height,dxSurfaceDesc.Width,dxSurfaceDesc.Height);
			}

			Direct3D_Texture* dxNewTexture = 0;

			if (!SUCCEEDED(Asura_Direct3D::D3DDevice->CreateTexture(dxSurfaceDesc.Width, dxSurfaceDesc.Height, uMipMapCount, 0, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &dxNewTexture, NULL)))
			{
				Asura_Assert(0,"Unable to convert L8 texture to L16, unable to create L16 texture");
			} else {
				for (u_int i = 0; i < uMipMapCount; ++i)
				{
					Direct3D_Surface *pxSurface, *pxNewSurface;

					dxTexture->GetSurfaceLevel(i, &pxSurface);
					dxNewTexture->GetSurfaceLevel(i, &pxNewSurface);
					D3DXLoadSurfaceFromSurface(pxNewSurface, NULL, NULL, pxSurface, NULL, NULL, D3DX_DEFAULT, 0xFF000000);
					pxSurface->Release();
					pxNewSurface->Release();
				}
				bSuccess = SUCCEEDED(D3DXSaveTextureToFile(outputName, D3DXIFF_DDS, dxNewTexture, NULL));
				dxNewTexture->Release();
				dxTexture->Release();
			}
		}
		break;
		default:
			bSuccess = CopyFile( inputName, outputName, FALSE ) ? true : false;
		}
		if( !bSuccess )
		{
			char szErrorText[100];
			if( FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), 0, szErrorText, 99, NULL ) )
			{
				szErrorText[ strlen( szErrorText ) - 2 ] = '\0'; // FormatMessage() terminates with a CR-LF
				Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Unable to export texture %s:", inputName );
				Tools_Level_Export::AddMessage( Tools_Level_Export::MT_INFORMATION, "%s", szErrorText );
			}

			return false;
		}

		SetFileAttributes( outputName, FILE_ATTRIBUTE_NORMAL );
		return true;
	}

	HRESULT hres;
	Direct3D_Texture* dxTexture = 0;
	Direct3D_Surface* dxSurface = 0;
	int mipLevelsToDrop = 0;
	bool generateMipMaps;
	D3DSURFACE_DESC dxSurfaceDesc;
	bool success = false;

	const int iTextureFlags = xConversionData.m_iTextureFlags;

	//get the filename without path
	Asura_String filename;
	((Asura_String) inputName).SplitFilename(0,&filename,0);
	
	D3DXIMAGE_INFO imageInfo;

	//attempt to load in the texture to an 8888 format
	hres = D3DXCreateTextureFromFileExA(
			Asura_Direct3D::D3DDevice,
			inputName,
			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
			&imageInfo,//pSourceInfo
			0,//pPalette
			&dxTexture
		);	

	if(FAILED(hres))
	{
		Asura_Assertf(0,("Failed to load texture '%s' for convertion", inputName));
		AddMessage(MT_ERROR,"Error %x loading %s",hres,inputName);
		return false;
	}

	//get the surface
	hres = dxTexture->GetSurfaceLevel(0,&dxSurface);
	if(FAILED(hres))
	{
		Asura_Assertf(0, ("Failed to get texture '%s' surface", inputName));
		goto Clearup;
	}

	//get the surface description
	hres = dxSurface->GetDesc(&dxSurfaceDesc);
	if(FAILED(hres))
	{
		Asura_Assertf(0,("Failed to get texture '%s' surface description", inputName));
		goto Clearup;
	}

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

	//warn if width/height have changed
	if(imageInfo.Width!=dxSurfaceDesc.Width || imageInfo.Height!=dxSurfaceDesc.Height)
	{
		AddMessage(MT_WARNING,"Image resized from %dx%d to %dx%d by loaders",imageInfo.Width,imageInfo.Height,dxSurfaceDesc.Width,dxSurfaceDesc.Height);
	}

	// DDS file can be used on consoles, not just PC. Hence need to check the reduce flag
	if( (s_eExportType != Export_PC) && (iTextureFlags & AsuraTextureFlag_ReduceExtraForConsoles) )
		{
			mipLevelsToDrop++;	
		}

	if(xConversionData.m_uMaximumTextureSize > 0)
	{
		//drop mip levels to get width/height less than the maximum set in the conversion data
		//RAR 26/11/2007
		u_int uSize = Asura_Maths::Max(dxSurfaceDesc.Width,dxSurfaceDesc.Height);
		uSize >>= mipLevelsToDrop;

		while(uSize > xConversionData.m_uMaximumTextureSize)
		{
			uSize >>= 1;
			mipLevelsToDrop++;
		}
	}

	//lock the surface
	D3DLOCKED_RECT rect;
	hres = dxSurface->LockRect(&rect,0,D3DLOCK_READONLY);
	if(FAILED(hres))
	{
		Asura_Assert(0,"Failed to lock the texture surface");
		goto Clearup;
	}

	// Luminance textures only get through to the RGB components in the pixel shader. However
	// 8-bit TGAs come through as luminance, when we need them as alpha
	if( imageInfo.Format == D3DFMT_L8 )
	{
		for( u_int u = 0; u < dxSurfaceDesc.Height; ++u )
		{
			u_int* puTexel = static_cast< u_int* >( rect.pBits );

			for( u_int v = 0; v < dxSurfaceDesc.Width; ++v )
			{
				// RGB should all be the same, just copy the red to alpha
				const u_int uA = (puTexel[v] & 0x00FF0000) << 8;
				const u_int uRGB = (puTexel[v] & 0x00FFFFFF);
				puTexel[v] = uA | uRGB;
			}

			// Pitch specified in bytes for non-DXT formats
			puTexel += rect.Pitch / sizeof( u_int );
		}
	}

	//see if this surface has any alpha
	bool bHasAlpha,bBinaryAlpha;
	bHasAlpha = false;
	bBinaryAlpha = true;
	unsigned char* lineStart;
	lineStart = (unsigned char*)rect.pBits;
	for(unsigned int y=0;y<dxSurfaceDesc.Height && bBinaryAlpha;y++,lineStart+=rect.Pitch)
	{
		unsigned int* colour = (unsigned int*) lineStart;
		for(unsigned int x=0;x<dxSurfaceDesc.Width && bBinaryAlpha;x++,colour++)
		{
			if((*colour >> 24)!=0xff)
			{
				bHasAlpha = true;
				if((*colour >> 24)!=0)
				{
					bBinaryAlpha = false;
				}
			}
		}
	}
	
	generateMipMaps = (iTextureFlags & AsuraTextureFlag_NoMipMaps) ? false : true;

	//Create a 32 bit dds file with the appropriate images using nvidia compression library.
	//Once this has been done , the file can be compressed using the more recent command line
	//executable , which will hopefully give a better result.
	//I would use most recent library to do both steps , except we don't have all the files
	//needed to link with it.
	//Alternatively I would use the executable to do both steps , except the program
	//can't load the 32 bit bitmaps that have been used in some places.
	//RAR 26/11/2003

	{
		//#ifdef TOOLS_USE_NEW_NVIDIA_LIBRARIES
		if(s_bUseNewNVidiaExport)
		{
			#ifdef TOOLS_DO_NVIDIA_PERFORMANCE_TEST
			if (!s_bIsTimerCreated)
			{
				s_iPerformanceHandle = Asura_Profiling_System::CreateTimer ();
				s_bIsTimerCreated = true;
			}

			Asura_Profiling_System::ResetTimer (s_iPerformanceHandle);
			Asura_Profiling_System::StartTiming (s_iPerformanceHandle);
			#endif

			nvtt::CompressionOptions xCompressionOptions;

			// Set the input options

			nvtt::InputOptions xInputOptions;

			xInputOptions.setTextureLayout (nvtt::TextureType_2D, imageInfo.Width, imageInfo.Height);

			xInputOptions.setMipmapData (rect.pBits, imageInfo.Width, imageInfo.Height);
			xInputOptions.setMipmapFilter (nvtt::MipmapFilter_Kaiser);
			xInputOptions.setGamma (1.0f, 1.0f);

			const bool bGenerateBitmaps = (generateMipMaps || mipLevelsToDrop) ? true : false;

			xInputOptions.setMipmapGeneration (bGenerateBitmaps);

			// Set the output options

			nvtt::OutputOptions xOutputOptions;

			// Use this to bypass our MipmapOutputHandler class and use the standard handler.  You'll also have to disable the call to setOutputHandler below.
			//xOutputOptions.setFileName (outputName);

			// Create the output dds file
			s_pDDSHandle = CreateFile (outputName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);

			if(s_pDDSHandle == INVALID_HANDLE_VALUE)
			{
				success = false;
			}
			else
			{
				// Set the compression options

				nvtt::Format xFormat;

				bool bIs8BitAlphaOnly = false;

				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

					const u_int uTextureNameHash = Asura_Texture_Management::GetHashIDFromIndex( index );
					u_int uUsage = Tools_MaterialResponse_System::GetTextureUsage( uTextureNameHash );

					// (AAM - 11.07.2007) If a mesh has had a normal map applied in Max/Maya, but never
					// been loaded in Tools before, the normal map won't be referenced by the MR
					// system. However we need to convert it as a normal map for later use, so make
					// sure we check the old texture flag too

					if( (uUsage & Tools_MaterialResponse_System::TU_NORMAL_CHANNELS) || (iTextureFlags & AsuraTextureFlag_NormalMap) )
					{
						if( uUsage & ~Tools_MaterialResponse_System::TU_NORMAL_CHANNELS )
						{
							if( true == Tools_BatchProcessing::IsBatch())
							{
								Asura_Assertf( false, ("Texture '%s' is used as both a normal map and something else. That's not allowed.", inputName));
							}
							Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Texture '%s' is used as both a normal map and something else. That's not allowed.", inputName );
							goto Clearup;
						}

						xFormat = nvtt::Format_DXT5n;
					}
					else // Fall through for other textures

				#endif // defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

				{
					xFormat = (iTextureFlags & AsuraTextureFlag_HighQualityCompression) ? nvtt::Format_RGBA
													: (imageInfo.Format == D3DFMT_L8 || imageInfo.Format == D3DFMT_A8) ? nvtt::Format_RGBA
													: bHasAlpha ? nvtt::Format_DXT5
													: nvtt::Format_DXT1;

					if((imageInfo.Format == D3DFMT_L8 || imageInfo.Format == D3DFMT_A8) && (iTextureFlags != AsuraTextureFlag_HighQualityCompression))
					{
						bIs8BitAlphaOnly = true;

						xCompressionOptions.setPixelFormat (8, 0x00, 0x00, 0x00, 0xff);
						//xCompressionOptions.setQuantization (true, false, bBinaryAlpha);
						//xCompressionOptions.setColorWeights (0.0, 0.0, 0.0);
					}
					else
					{
						xCompressionOptions.setPixelFormat (32, 0x0000ff00, 0x00ff0000, 0xff000000, 0x000000ff);
						xCompressionOptions.setQuantization (true, false, bBinaryAlpha);
						xCompressionOptions.setColorWeights (0.3086f, 0.6094f, 0.0820f);
					}
				}

				xCompressionOptions.setFormat (xFormat);

				MipmapOutputHandler xMipmapOutputHandler (generateMipMaps, mipLevelsToDrop, s_pDDSHandle, bIs8BitAlphaOnly);

				xOutputOptions.setOutputHandler (&xMipmapOutputHandler);

				if(s_pDDSHandle != INVALID_HANDLE_VALUE)
				{
					//The memory management system incorrectly picks up the type of new being used by nvidia library
					Asura_MemoryManagement_System::SetIgnoreNewAndDeleteTypeMismatch (true);

					// Create the compressed texture

					try
					{
						nvtt::Compressor xCompressor;

						if (xCompressor.process (xInputOptions, xCompressionOptions, xOutputOptions))
						{
							success = true;
						}
					}

					catch (...)
					{
						success = false;
					}

					Asura_MemoryManagement_System::SetIgnoreNewAndDeleteTypeMismatch (false);

					CloseHandle(s_pDDSHandle);
					s_pDDSHandle = INVALID_HANDLE_VALUE;
				}
				else
				{
					Tools_Level_Export::AddMessage(MT_ERROR,"Failed to open %s for writing.",outputName);
				}
			}

			#ifdef TOOLS_DO_NVIDIA_PERFORMANCE_TEST
			Asura_Profiling_System::StopTiming (s_iPerformanceHandle);
			float fThisDuration = Asura_Profiling_System::GetTotalDuration (s_iPerformanceHandle);
			s_fTotalDuration += fThisDuration;
			char szBuffer [256];
			sprintf_s (szBuffer, 256, "***,%f,%f,%s\n", s_fTotalDuration, fThisDuration, outputName);
			OutputDebugStringA (szBuffer);
			#endif

		}
		else
		{
			//#else // TOOLS_USE_NEW_NVIDIA_LIBRARIES
			#ifdef TOOLS_DO_NVIDIA_PERFORMANCE_TEST
			if (!s_bIsTimerCreated)
			{
				s_iPerformanceHandle = Asura_Profiling_System::CreateTimer ();
				s_bIsTimerCreated = true;
			}

			Asura_Profiling_System::ResetTimer (s_iPerformanceHandle);
			Asura_Profiling_System::StartTiming (s_iPerformanceHandle);
			#endif

			//fill in the options for compression
			nvCompressionOptions xOptions;

			// new version
			xOptions.SetDefaultOptions();
			xOptions.mipMapGeneration =  (generateMipMaps || mipLevelsToDrop) ? kGenerateMipMaps : kNoMipMaps;
			xOptions.bBinaryAlpha = bBinaryAlpha; // clamp alpha zero or one
			xOptions.bDitherColor = true;        // enable dithering during 16 bit conversion
			xOptions.mipFilterType = kMipFilterKaiser;

			//We used to alter some of the options based on the texture flags for XBox export.
			//XBox is no longer supported (MH - 08.05.2008).
			//Not being done on PC at the moment because of issues with doing the converison
			//upon load. RAR 29/07/04
			{
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
							
					const u_int uTextureNameHash = Asura_Texture_Management::GetHashIDFromIndex( index );
					u_int uUsage = Tools_MaterialResponse_System::GetTextureUsage( uTextureNameHash );
					
					// (AAM - 11.07.2007) If a mesh has had a normal map applied in Max/Maya, but never
					// been loaded in Tools before, the normal map won't be referenced by the MR
					// system. However we need to convert it as a normal map for later use, so make
					// sure we check the old texture flag too
					
					if( (uUsage & Tools_MaterialResponse_System::TU_NORMAL_CHANNELS) || (iTextureFlags & AsuraTextureFlag_NormalMap) )
					{
						if( uUsage & ~Tools_MaterialResponse_System::TU_NORMAL_CHANNELS )
					{
							if( true == Tools_BatchProcessing::IsBatch())
					{
								Asura_Assertf( false, ("Texture '%s' is used as both a normal map and something else. That's not allowed.", inputName));
					}
							AddMessage( MT_ERROR, "Texture '%s' is used as both a normal map and something else. That's not allowed.", inputName );
							goto Clearup;
					}

						xOptions.textureFormat = kDXT5_NM;
					}
					else // Fall through for other textures

				#endif // defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

				{
					xOptions.textureFormat = (iTextureFlags & AsuraTextureFlag_HighQualityCompression) ? k8888
										   : imageInfo.Format == D3DFMT_L8 || imageInfo.Format == D3DFMT_A8 ? kA8
										   : bHasAlpha ? kDXT5
										   : kDXT1;
				}
			}

			if(xOptions.textureFormat == kDXT1)
			{
				//TY 07.05.08 - If we are using DXT1 (i.e. definately no transparency) then force a four colour palette
				//              this will give us optimal colour depth and avoid any potential alpha errors
				xOptions.bForceDXT1FourColors = true;
				}

			// (MH - 20.12.2007) added sharpening of first few mip levels (down to 64x64)
			if (iTextureFlags & AsuraTextureFlag_SharpenMipMaps)
				{
				Tools_NvTextureTools::SetOptionsForSharpenMipMaps(xOptions, imageInfo.Width, imageInfo.Height);
			}

			//initialise some globals for use by the callback function
			InitialiseDDSOutput(mipLevelsToDrop,generateMipMaps);

			//now call the nvidia dds creation function
			DDSHandle = CreateFile(outputName,GENERIC_WRITE,0,0,CREATE_ALWAYS,0,0);
			if(DDSHandle != INVALID_HANDLE_VALUE)
			{
				//The memory management system incorrectly picks up the type of new being used by nvidia library
				Asura_MemoryManagement_System::SetIgnoreNewAndDeleteTypeMismatch(true);

	#if 0
				NV_ERROR_CODE eError = nvDDS::nvDXTcompress(
					reinterpret_cast<const u_char*>(rect.pBits),//raw_data
					dxSurfaceDesc.Width,
					dxSurfaceDesc.Height,
					rect.Pitch,
					nvBGRA,	// pixel order
					&xOptions,//compression options
					DxtMipMapCallback,	// mip callback
					NULL);	// subrect (use defaults).
	#else
				NV_ERROR_CODE eError = Asura_PC_CompressDXT(
					reinterpret_cast<const u_char*>(rect.pBits),//raw_data
					dxSurfaceDesc.Width,
					dxSurfaceDesc.Height,
					rect.Pitch,
					nvBGRA,	// pixel order
					&xOptions,//compression options
					DxtMipMapCallback,	// mip callback
					NULL);	// subrect (use defaults).

	#endif
				Asura_MemoryManagement_System::SetIgnoreNewAndDeleteTypeMismatch(false);
					
				Asura_Assertf(eError == NV_OK, ("Error in Asura_PC_CompressDXT (from file %s): %s", inputName, Asura_PC_CompressDXT_GetErrorString(eError)));

				CloseHandle(DDSHandle);
				DDSHandle = INVALID_HANDLE_VALUE;

				success = (eError == NV_OK);
			}
			else
			{
				AddMessage(MT_ERROR,"Failed to open %s for writing.",outputName);
			}

			#ifdef TOOLS_DO_NVIDIA_PERFORMANCE_TEST
			Asura_Profiling_System::StopTiming (s_iPerformanceHandle);
			float fThisDuration = Asura_Profiling_System::GetTotalDuration (s_iPerformanceHandle);
			s_fTotalDuration += fThisDuration;
			char szBuffer [256];
			sprintf_s (szBuffer, 256, "***,%f,%f,%s\n", s_fTotalDuration, fThisDuration, outputName);
			OutputDebugStringA (szBuffer);
			#endif

			//#endif // TOOLS_USE_NEW_NVIDIA_LIBRARIES
		}

		hres = dxSurface->UnlockRect();
		Asura_Assert(!hres,"Error unlocking surface");
	}


Clearup :

	if(dxSurface) dxSurface->Release();
	if(dxTexture) dxTexture->Release();

	return success;
}

bool Tools_Level_Export::CreateTim2File(const char* inputName,const char* outputName,int index, const TextureConversionData& xConversionData, int iWidthOveride, int iHeightOveride)
{
	Tools_Tim2_Writer timWriter;
	HRESULT hres;
	Direct3D_Texture* dxTexture = 0;
	Direct3D_Surface* dxSurface = 0;
	int mipLevelsToDrop;
	D3DSURFACE_DESC dxSurfaceDesc;
	bool success = false;
	bool generateMipMaps;
	bool isSkybox;
	bool bAllow512 = false;
	bool bEnvironmentTexture = IsTextureUsedOnEnvironment(index);
	bool bSizeOveride = iWidthOveride && iHeightOveride;

 	Asura_Assertf(!Tools_FileSystem::IsReadOnly(outputName),("%s is readonly. It will be made writeable.",outputName));
	Tools_FileSystem::MakeWriteable(outputName);

	//get the filename without path , for checking alpha type naming convention
	Asura_String filename;
	((Asura_String) inputName).SplitFilename(0,&filename,0);

	D3DXIMAGE_INFO imageInfo;

	//attempt to load in the texture to an 8888 format
	hres = D3DXCreateTextureFromFileExA(
			Asura_Direct3D::D3DDevice,
			inputName,
			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
			&imageInfo,//pSourceInfo
			0,//pPalette
			&dxTexture
		);	

	if(FAILED(hres))
	{
		AddMessage(MT_ERROR,"Error %x loading %s",hres,inputName);
		return false;
	}

	//get the surface
	hres = dxTexture->GetSurfaceLevel(0,&dxSurface);
	if(FAILED(hres))
	{
		Asura_Assert(0,"Failed to get texture surface");
		goto Clearup;
	}

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

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

	//warn if width/height have changed
	if(imageInfo.Width!=dxSurfaceDesc.Width || imageInfo.Height!=dxSurfaceDesc.Height)
	{
		AddMessage(MT_WARNING,"Image resized from %dx%d to %dx%d by loaders",imageInfo.Width,imageInfo.Height,dxSurfaceDesc.Width,dxSurfaceDesc.Height);
	}


	//get default number of mip levels to drop
	if(xConversionData.m_iTextureFlags & AsuraTextureFlag_PS2DontHalveSize)
	{
		mipLevelsToDrop = 0;
	}
	else
	{
		mipLevelsToDrop = 1;
	}
	
	isSkybox = IsSkyboxTexture(index);

	generateMipMaps = !isSkybox || bEnvironmentTexture;

	//make sure its square
	// (MH - 29.05.2004) the PS2 renderers support non-square textures now so this check should be unnecessary
//	if(bSizeOveride)
//	{
//		if(iWidthOveride!=iHeightOveride && generateMipMaps)
//		{
//			AddMessage(MT_ERROR,"Dimensions are %dx%d. Texture must be square",iWidthOveride,iHeightOveride);
//			goto Clearup;
//		}
//	}
//	else
//	{
//		if(dxSurfaceDesc.Width!=dxSurfaceDesc.Height && generateMipMaps)
//		{
//			AddMessage(MT_ERROR,"Dimensions are %dx%d. Texture must be square",imageInfo.Width,imageInfo.Height);
//			goto Clearup;
//		}
//	}
	//and width is a power of 2
	if(!bSizeOveride)
	{
		int bits_set;
		bits_set = 0;
		for(int w=dxSurfaceDesc.Width;w;w>>=1)
		{
			if(w&1)bits_set++;
		}
		if(bits_set!=1)
		{
			AddMessage(MT_ERROR,"Width is %d. Must be a power of 2",dxSurfaceDesc.Width);
			goto Clearup;
		}
		bits_set = 0;
		for(int h=dxSurfaceDesc.Height;h;h>>=1)
		{
			if(h&1)bits_set++;
		}
		if(bits_set!=1)
		{
			AddMessage(MT_ERROR,"Height is %d. Must be a power of 2",imageInfo.Height);
			goto Clearup;
		}
	}

	int minDim, maxDim;
	maxDim = max(dxSurfaceDesc.Width,dxSurfaceDesc.Height);
	minDim = min(dxSurfaceDesc.Width,dxSurfaceDesc.Height);
	//make sure it isn't to large
	if(!bSizeOveride)
	{
		if((maxDim>>mipLevelsToDrop)>512)
		{
			AddMessage(MT_WARNING,"Width is %d (%d on PS2). PS2 width must be no more than 512",maxDim,maxDim>>mipLevelsToDrop);
			while((maxDim>>mipLevelsToDrop)>512) mipLevelsToDrop++;
		}
	
		if( xConversionData.m_iTextureFlags & AsuraTextureFlag_PS2Allow512 )
    	{
			bAllow512 = true;
			AddMessage( MT_LOGONLY, "(Texture has \"allow 512\" flag set.)" );
    	}

		if(isSkybox) bAllow512 = true;

			
		if((maxDim>>mipLevelsToDrop)>256 && !bAllow512)
		{
			//textures are limited to 256 after reduction , unless skybox ,or otherwise flagged
			AddMessage(MT_WARNING,"Width is %d (%d after reduction).",maxDim,maxDim>>mipLevelsToDrop);
			AddMessage(MT_WARNING,"Textures should be no larger than 256 after reduction unless otherwise flagged");
    	    AddMessage( MT_WARNING, "Forcing reduction to 256." );
			while((maxDim>>mipLevelsToDrop)>256) mipLevelsToDrop++;
		}
	}

	// Check for textures that are too small
	if( Export_PS2 == s_eExportType && 	minDim<64 )
	{
		AddMessage(MT_ERROR,"Texture is too small ( smaller than 64x64 )");
	}

	if(maxDim<=64)
	{
		//don't drop mip levels for small textures
		mipLevelsToDrop = 0;
	}
    else if( Export_PC != s_eExportType && (xConversionData.m_iTextureFlags & AsuraTextureFlag_ReduceExtraForConsoles) )
    {
        ++mipLevelsToDrop;
    }

    if( IsPS2MultiplayerExport() )
    {
        if(!(xConversionData.m_iTextureFlags & AsuraTextureFlag_PS2DontHalveExtraForMultiplayer))
		{
        	++mipLevelsToDrop;
		}
    }

	//if final reduced size is 512 , and this is an environment texture , don't generate mip maps unless flagged. RAR 01/04/03
	if(maxDim>>mipLevelsToDrop == 512 && bEnvironmentTexture && !(xConversionData.m_iTextureFlags & AsuraTextureFlag_PS2AllowMipMapsOn512))
	{
		generateMipMaps = false;
	}
		
	//lock the surface
	D3DLOCKED_RECT rect;
	hres = dxSurface->LockRect(&rect,0,D3DLOCK_READONLY);
	if(FAILED(hres))
	{
		Asura_Assert(0,"Failed to lock the texture surface");
		goto Clearup;
	}

	//pass the image data to the tim writer
	timWriter.Initialise(dxSurfaceDesc.Width,dxSurfaceDesc.Height,rect.Pitch,(int*)rect.pBits,xConversionData);

	hres = dxSurface->UnlockRect();
	Asura_Assert(!hres,"Error unlocking surface");

	if(iWidthOveride && iHeightOveride)
	{
		//If texture sizes re being overidden , then ignore any other settings for dropping mip map levels,
		//Halving for MP exports is dealt with when calculating the width and height overides.
		mipLevelsToDrop = 0;
		
		//use the overide width and height
		timWriter.ScaleImage(iWidthOveride,iHeightOveride);

	}
    

	//get the writer to generate the tim file
	success = timWriter.WriteOutFile(outputName,generateMipMaps ? 3 : 0,mipLevelsToDrop);


	Clearup :
	if(dxSurface) dxSurface->Release();
	if(dxTexture) dxTexture->Release();

	return success;
}

bool Tools_Level_Export::DoesTextureHaveAlphaChannel(const char* pszFilename)
{
	HRESULT hres;
	Direct3D_Texture* dxTexture = 0;
	Direct3D_Surface* dxSurface = 0;
	D3DSURFACE_DESC dxSurfaceDesc;
	bool bHasAlpha = false;

	
	D3DXIMAGE_INFO imageInfo;

	//attempt to load in the texture to an 8888 format
	hres = D3DXCreateTextureFromFileExA(
			Asura_Direct3D::D3DDevice,
			pszFilename,
			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
			&imageInfo,//pSourceInfo
			0,//pPalette
			&dxTexture
		);	

	if(FAILED(hres))
	{
		AddMessage(MT_ERROR,"Error %x loading %s",hres,pszFilename);
		return false;
	}

	//get the surface
	hres = dxTexture->GetSurfaceLevel(0,&dxSurface);
	if(FAILED(hres))
	{
		Asura_Assert(0,"Failed to get texture surface");
		goto Clearup;
	}

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

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


	//lock the surface
	D3DLOCKED_RECT rect;
	hres = dxSurface->LockRect(&rect,0,D3DLOCK_READONLY);
	if(FAILED(hres))
	{
		Asura_Assert(0,"Failed to lock the texture surface");
		goto Clearup;
	}

	//see if this surface has any alpha
	unsigned char* lineStart;
	lineStart = (unsigned char*)rect.pBits;
	for(unsigned int y=0;y<dxSurfaceDesc.Height && !bHasAlpha;y++,lineStart+=rect.Pitch)
	{
		unsigned int* colour = (unsigned int*) lineStart;
		for(unsigned int x=0;x<dxSurfaceDesc.Width && !bHasAlpha;x++,colour++)
		{
			if((*colour >> 24)!=0xff)
			{
				bHasAlpha = true;
			}
		}
	}

				
	hres = dxSurface->UnlockRect();
	Asura_Assert(!hres,"Error unlocking surface");
	
	Clearup :
	if(dxSurface) dxSurface->Release();
	if(dxTexture) dxTexture->Release();

	return bHasAlpha;
}

bool Tools_Level_Export::IsTextureUsedOnEnvironment(int index)
{
	//check each polygon of each module for use of this texture index
	int numberOfMeshes = Asura_Module_System::GetNumberOfModules();

	for (int i=0; i<numberOfMeshes; i++)
	{
		Asura_Mesh* mesh = Asura_Module_System::GetMeshForModule(i);
	
		for(int p=0; p<mesh->NumberOfPolygons; p++)
		{
			if(Asura_Material_System::GetTextureID(mesh->TextureDataPtr[p].m_iMaterialID)==index) return true;
		}
	}

	return false;
}

bool Tools_Level_Export::GetPS2TextureSizeOverride(int iTextureID,int* piOutputWidth,int* piOutputHeight,int* piPixelFormat,int* piPaletteFormat)
{
	if(!m_pxPS2TextureSizeIni)
	{
		const char* pszIniFilename = "ToolsIniFiles\\PS2TextureSize.ini";
		
		//load the ini file
		m_pxPS2TextureSizeIni = new Tools_IniFile(pszIniFilename);

       	if(m_pxPS2TextureSizeIni->LoadIniFile())
		{
       		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "\nOpened %s.",pszIniFilename);

			//check for dodgy entries in the file
			u_int uNumErrors = 0;
			int iNumSections = m_pxPS2TextureSizeIni->GetNumberOfSections();
			for(int i=0;i<iNumSections;i++)
			{
				m_pxPS2TextureSizeIni->GetIniTextSectionByIndex(i);

				while(m_pxPS2TextureSizeIni->GetNextIniTextLine())
				{
					//all entries should be of the form 'identifier' = 'something'
					Asura_String xLine;
					if(!m_pxPS2TextureSizeIni->GetString(xLine)) continue;

					if(!strchr(xLine.GetString(),'='))
					{
			       		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "Bad entry on line %d : '%s'",m_pxPS2TextureSizeIni->LineIndex,xLine.GetString());
						uNumErrors++;	
					}
				}
			}
			if(uNumErrors)
			{
	       		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "Found %d errors in PS2TextureSize.ini (see log file).",uNumErrors);
			}
       		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "");

		}
		else
		{
       		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "\nFailed to open %s.\n",pszIniFilename);
		}

	}

	//get the texture name without path and extension
	const Asura_String* xTextureFilename = Asura_Texture_Management::GetTextureName(iTextureID);
	Asura_String xTextureName;
	xTextureFilename->SplitFilename(0,&xTextureName,0);

	//is it a skin texture?
	int iJunkIndex;
	bool bSkinTexture = ( m_pxSkinTextureList && m_pxSkinTextureList->FindNodeIndexByName(xTextureFilename->GetString(),false,iJunkIndex) );

	//get the level name without path and extension
	Asura_String xLevelName;
	if(m_bIsCharacterExport)
	{
		//For doing character exports , the level name would be inappropriate.
		//So just look under a special section for them
		xLevelName = "Character Export";
	}
	else
	{
		Asura_String xFileName = Tools_SaveAsura::GetMainFilename();
		xFileName.SplitFilename(0,&xLevelName,0);
	}

	int iTextureFlags = Asura_Texture_Management::GetTextureFlags(iTextureID);

	
	*piOutputWidth = 0;
	*piOutputHeight = 0;
	if(piPixelFormat) *piPixelFormat = 0;
	if(piPaletteFormat) *piPaletteFormat = 0;

	int iPixelFormat = 0;
	int iPaletteFormat = 0;
	
	//check for entries in this order...
	// LevelName / TextureName
	// Default / TextureName
	// LevelName / Default
	// Default / Default

	//for multiplayer exports check the multiplayer sections first


	for(int iMultiplayer = IsPS2MultiplayerExport() ? 1 : 0; iMultiplayer>=0;iMultiplayer--)
	{
		for(int iTexture=1;iTexture>=0;iTexture--)
		{
			if(iTexture==0 && !bSkinTexture) continue;//only have a default entry for skin textures at the moment

			const char* pszIdentifier = iTexture ? xTextureName.GetString() : "Default_Skin";
			for(int iLevel=1;iLevel>=0;iLevel--)
			{
				Asura_String xSection = iMultiplayer ? "MP " : "";
				xSection += iLevel ? xLevelName.GetString() : "Default";
								

				//get to the correct section for the level
				if(!m_pxPS2TextureSizeIni->GetIniTextSection(xSection.GetString())) continue;

				//see if there is an entry for this texture
				Asura_String xText;
				if(!m_pxPS2TextureSizeIni->GetStringInCurrentSection(pszIdentifier,xText)) continue;

				//scan the string for width (and possibly height)
				int iWidth,iHeight;
				int iSizeRead;

				iSizeRead = sscanf(xText.GetString(),"%dx%d",&iWidth,&iHeight);
				if(iSizeRead == 1)
				{
					//only one number , assume square texture
					*piOutputWidth = iWidth;
					*piOutputHeight = iWidth;
				}
				if(iSizeRead == 2)
				{
					*piOutputWidth = iWidth;
					*piOutputHeight = iHeight;
				}

				char* pcU = strchr(xText.GetString(),'U');
				if(!pcU) pcU = strchr(xText.GetString(),'u');
				char* pcP = strchr(xText.GetString(),'P');
				if(!pcP) pcP = strchr(xText.GetString(),'p');

				if(pcU)
				{
					//unpalettised , read in bit depth
					int iBitDepth = 32;
					int iIntsRead = sscanf(pcU,"U%d",&iBitDepth);
					if (iIntsRead < 1)
					{
						iIntsRead = sscanf(pcU,"u%d",&iBitDepth);
					}
					if (iIntsRead < 1)
					{
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Invalid entry found in PS2TextureSize.ini for Level '%s' Texture '%s'. no bit depth specified",xSection.GetString(),pszIdentifier );
					}
					Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "Texture '%s'. Using %d-bit unpalettised", pszIdentifier, iBitDepth );

					if(iBitDepth == 32)
					{
						iPixelFormat = TIM2_RGB32;
					}
					else if(iBitDepth == 24)
					{
						iPixelFormat = TIM2_RGB24;
					}
					else if(iBitDepth == 16)
					{
						iPixelFormat = TIM2_RGB16;
					}
					else
					{
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Invalid entry found in PS2TextureSize.ini for Level '%s' Texture '%s'. %d bit unpalettised not allowed",xSection.GetString(),pszIdentifier,iBitDepth );
					}

				}
				else if(pcP)
				{
					//palettised , read in pixel and palette bit depth
					int iPixelBit = 8;
					int iPaletteBit = 32;
					int iIntsRead = sscanf(pcP,"P%d,%d",&iPixelBit,&iPaletteBit);
					if (iIntsRead < 1)
					{
						iIntsRead = sscanf(pcP,"p%d,%d",&iPixelBit,&iPaletteBit);
					}
					if (iIntsRead < 1)
					{
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Invalid entry found in PS2TextureSize.ini for Level '%s' Texture '%s'. no bit depth specified",xSection.GetString(),pszIdentifier );
					}
					Tools_Level_Export::AddMessage( Tools_Level_Export::MT_LOGONLY, "Texture '%s'. Using %d-bit CLUT with %d-bit palette", pszIdentifier, iPixelBit, iPaletteBit );

					if(iPaletteBit == 32)
					{
						iPaletteFormat = TIM2_RGB32;
					}
					else if(iPaletteBit == 24)
					{
				        //appears 24 bit cluts aren't actually allowable on PS2. RAR 16/02/04
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "24 bit palette entries specified in PS2TextureSize.ini for Level '%s' Texture '%s'.",xSection.GetString(),pszIdentifier);
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Using 32 bit instead.");
						iPaletteFormat = TIM2_RGB32;
					}
					else if(iPaletteBit == 16)
					{
						iPaletteFormat = TIM2_RGB16;
					}
					else
					{
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Invalid entry found in PS2TextureSize.ini for Level '%s' Texture '%s'. Palette cannot use %d bit colours.",xSection.GetString(),pszIdentifier,iPaletteBit );
						iPaletteFormat = TIM2_RGB16;
					}

					if(iPixelBit == 8)
					{
						iPixelFormat = TIM2_IDTEX8;
					}
					else if(iPixelBit == 4)
					{
						iPixelFormat = TIM2_IDTEX4;
					}
					else
					{
				        Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Invalid entry found in PS2TextureSize.ini for Level '%s' Texture '%s'. %d bit palettised not allowed.",xSection.GetString(),pszIdentifier,iPixelBit );
						iPixelFormat = TIM2_IDTEX8;
					}
				}
				if(piPixelFormat) *piPixelFormat = iPixelFormat;
				if(piPaletteFormat) *piPaletteFormat = iPaletteFormat;
				
				
				//check for unpalletised flag , which is indicated by having a 'U' at the end
				//if(pbUnpalettised)
				//{
				//	*pbUnpalettised = bUnpalettised;
				//}

				if(iSizeRead==1 || iSizeRead==2)
				{
					
					
					if(IsPS2MultiplayerExport() && !iMultiplayer)
					{
						//Multiplayer export , but didn't find a multiplayer specific section.
						//Halve the size if needed.
						if(!(iTextureFlags & AsuraTextureFlag_PS2DontHalveExtraForMultiplayer))
						{
							*piOutputWidth /= 2;
							*piOutputHeight /= 2;
							
						}
					}
					return true;
				}
								

				//dodgy entry
		        //Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Invalid entry found in PS2TextureSize.ini for Level '%s' Texture '%s'",xSection.GetString(),pszIdentifier );
				return false;
			}
		}
	}

	//no texture size overide found
	return false;

}


void Tools_Level_Export::GetUVScaleForPS2Skin(const Asura_Hierarchy_Skin* pxSkin,float * pfUScale,float* pfVScale)
{
	
	//first need to find out what texture this skin uses
	int iTextureID = ASURA_TEXTURE_NONE;
	*pfUScale = 1.0f;
	*pfVScale = 1.0f;

	for(int i=0;i<pxSkin->NumberOfBones;i++)
	{
		Asura_Mesh* pxMesh;
		if(pxSkin->NumberOfVertices)
		{
			if(i) break;
			//get the pxMesh
			pxMesh = Asura_Mesh_System::GetMesh(pxSkin->Name);
			if(!pxMesh)
			{
				AddMessage(MT_ERROR,"Failed to find pxMesh '%s'.",pxSkin->Name.GetString());
				return;
			}
		}
		else
		{
			Asura_Assert(pxSkin->SectionData,"");
			pxMesh = pxSkin->SectionData[i].Mesh;
		}
		if(!pxMesh) continue;
		if(!pxMesh->TextureDataValid) return;

		for(int i=0;i<pxMesh->NumberOfPolygons;i++)
		{
			iTextureID = Asura_Material_System::GetTextureID(pxMesh->TextureDataPtr[i].m_iMaterialID);
			if(iTextureID != ASURA_TEXTURE_NONE) break;

		}
		if(iTextureID != ASURA_TEXTURE_NONE) break;
	}
	if(iTextureID == ASURA_TEXTURE_NONE) return;

	//get the textures original width/height
	int iWidth,iHeight;
	if(!Tools_Texture_Management::GetTextureSize(iTextureID,iWidth,iHeight)) return;
	
	//get the overide settings
	int iWidthOveride,iHeightOveride;
	if(!GetPS2TextureSizeOverride(iTextureID,&iWidthOveride,&iHeightOveride,NULL,NULL)) return;

	if(!iWidthOveride || !iHeightOveride) return;
	if(iWidthOveride > iWidth) return;
	if(iHeightOveride > iHeight) return;


	//Okay , looks like the texture was scaled.
	//Now , I think we need to get the scaling factor required to go from the next power of 2 up 
	//down to the overide settings.
	int iPower =1;
	while(iPower<iWidthOveride) iPower<<=1;
	*pfUScale = (float)iWidthOveride/iPower;

	iPower =1;
	while(iPower<iHeightOveride) iPower<<=1;
	*pfVScale = (float)iHeightOveride/iPower;
}

void Tools_Level_Export::AddShapeTextures( HANDLE pSaveHandle)
{
	Asura_NameTree xShapeTextures;

	const Asura_String** ppxNameList = new const Asura_String*[ASURA_MAX_NO_OF_TEXTURES];
	bool* pbTextureUsed = new bool[ASURA_MAX_NO_OF_TEXTURES];
	int* piTextureFlags = new int[ASURA_MAX_NO_OF_TEXTURES];

	for(int i=0;i<ASURA_MAX_NO_OF_TEXTURES;i++)
	{
		ppxNameList[i] = NULL;
		pbTextureUsed[i] = 0;
		piTextureFlags[i] = 0;
	}

	//and get those names
	Asura_Texture_Management::MakeNameList(ppxNameList);
	
	//list meshes used by shapes
	List<Asura_Mesh*> xMeshList;
	for(int iCommon = 0;iCommon<2;iCommon++)
	{
		if((iCommon==0) && !m_bIncludeEntityShapes) continue;
		if((iCommon==1) && !m_bIncludeCommonShapes) continue;

		int iNumMeshes = iCommon ? Tools_ShapeTable::GetNumExportShapes() : Tools_ShapeTable::GetNumElements();

		for(int i=0;i<iNumMeshes;i++)
		{
			//ignore shapes that aren't going to be exported
			if(!Tools_ShapeTable::ShouldExportShapeByIndex(i,iCommon ? true : false)) continue;

			Asura_Mesh* pxMesh = Tools_ShapeTable::GetMeshByIndex(i,iCommon ? true : false);	
			if(pxMesh) xMeshList.add_entry(pxMesh);
		}
	}

	//add meshes for fragments to list to check
	for(LIF<Asura_Mesh*> xShapeMeshLIF(&m_xShapeMeshList);!xShapeMeshLIF.done();xShapeMeshLIF.next() )
	{
		xMeshList.add_entry(xShapeMeshLIF());
	}
	
	//find textures used by all those meshes

	#if !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	int iNumberOfTexturesUsed = 0;
	#endif

	for(LIF<Asura_Mesh*> xMeshLIF(&xMeshList);!xMeshLIF.done();xMeshLIF.next())
	{
		Asura_Mesh* pxMesh = xMeshLIF();	
		//add the texture(s) used by this mesh and any detail models
		while(pxMesh)
		{
			for(int p=0; p<pxMesh->NumberOfPolygons; p++)
			{
	            if( pxMesh->PolygonFlagDataValid && (pxMesh->PolygonFlagDataPtr[p].Flags & (AsuraPolygonFlag_DontExport| AsuraPolygonFlag_DontRender)) )
				{
					//ignore textures for polygons that won't ne rendered/exported
					continue;
				}
						
				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
					for(int j=0;j<TOOLS_NUM_MATERIALRESPONSE_PER_POLY;j++)
					{
						AddMaterialToExport( pSaveHandle, pxMesh->TextureDataPtr[p].m_auMaterialResponseHashID[j] );
					}
				#else

				for (int t = 0; t < ASURA_MATERIAL_MAXTEXTURES; t++)
				{
					// Diffuse texture
					int iIndex = Asura_Material_System::GetTextureID(pxMesh->TextureDataPtr[p].m_iMaterialID, t);
					if (iIndex!=-1 && !pbTextureUsed[iIndex])
					{
						pbTextureUsed[iIndex] = true;
						int iNewIndex;
						xShapeTextures.FindNodeIndexByName(ppxNameList[iIndex]->GetString(),true,iNewIndex);
						piTextureFlags[iNewIndex] = Asura_Texture_Management::GetTextureFlags(iIndex);
						iNumberOfTexturesUsed++;
					}

					// Normal map
					iIndex = Asura_Material_System::GetNormalMapID(pxMesh->TextureDataPtr[p].m_iMaterialID, t);
					if (iIndex!=-1 && !pbTextureUsed[iIndex])
					{
						pbTextureUsed[iIndex] = true;
						int iNewIndex;
						xShapeTextures.FindNodeIndexByName(ppxNameList[iIndex]->GetString(),true,iNewIndex);
						piTextureFlags[iNewIndex] = Asura_Texture_Management::GetTextureFlags(iIndex);
						iNumberOfTexturesUsed++;
					}

					// Specular map
					iIndex = Asura_Material_System::GetSpecularMapID(pxMesh->TextureDataPtr[p].m_iMaterialID, t);
					if (iIndex!=-1 && !pbTextureUsed[iIndex])
					{
						pbTextureUsed[iIndex] = true;
						int iNewIndex;
						xShapeTextures.FindNodeIndexByName(ppxNameList[iIndex]->GetString(),true,iNewIndex);
						piTextureFlags[iNewIndex] = Asura_Texture_Management::GetTextureFlags(iIndex);
						iNumberOfTexturesUsed++;
					}
				}

				#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

			}
			//get the next detail mesh
			pxMesh = pxMesh->m_pxNextLowerMesh;
		}
	
	}

	// AddMaterialToExport() above will take care of any textures needed too
	#if !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	//create a save block containing the texture names and flags
	Asura_SaveBlock xSaveBlock;
	Asura_Chunk_TextureNames::Create(&xShapeTextures,&xSaveBlock);
	Asura_Chunk_TextureFlags::Create(iNumberOfTexturesUsed,piTextureFlags,&xSaveBlock);

	//and output to the file
	unsigned long uBytesWritten;
	WriteFile(pSaveHandle,xSaveBlock.GetStartPtr()+ASURA_FILE_ID_NUM_CHARS,xSaveBlock.GetSize()-ASURA_FILE_ID_NUM_CHARS,&uBytesWritten,0);

		UpdateStatus( EXPORT_STAGE_ADDING_SHAPE_TEXTURES, "Adding shape textures...", -1 );

	//export the textures found
	int iNumberOfTexturesOutput = 0;
	for(int i = 0;i<ASURA_MAX_NO_OF_TEXTURES && !IsAbortFlagSet();i++)
	{
		if(!pbTextureUsed[i]) continue;
		
		s_iPercent = iNumberOfTexturesOutput++*100/iNumberOfTexturesUsed;
		if(ppxNameList[i])
		{
			
			UpdateProgress();
			//add this texture
			AddTexture(pSaveHandle,*ppxNameList[i],i);

		}

		UpdateStatus(EXPORT_STAGE_END, 0, -1);
	}
#endif // !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	delete [] ppxNameList;
	delete [] pbTextureUsed;
	delete [] piTextureFlags;
}


bool Tools_Level_Export::GetTextureFilenameForExport(const char* pszInputName,const char* pszExtension,Asura_String& xOutputName,bool* pbUsingPlatformDir)
{
	extern Asura_String NetworkGraphicsFolder;

	HANDLE file;

	char szFullFilename[MAX_PATH];

	//Get the filename to use.. checking for a platform overide first
	int iPlatformOveride;
	bool bFoundFile = false;
	for(int iPass=0;iPass<3;iPass++)
	{
		//Pass 0 : Platform directory + platform extension
		//Pass 1 : Platform directory + unmodified directory
		//Pass 2 : Non platform specific

		//get the full local filename
		char szLocalFilename[MAX_PATH];

		iPlatformOveride = (iPass==0 || iPass==1);

		const char* pszGraphicsRoot = "graphics";
		if(iPlatformOveride)
		{
			switch(s_eExportType)
			{
				case Export_PC :
				///may as well let PC have an overide directory as well... though I don't suppose it will be used
					pszGraphicsRoot = "graphics_pc";
					break;
				case Export_PS2 :
					pszGraphicsRoot = "graphics_ps2";
					break;
				case Export_PSP :
					pszGraphicsRoot = "graphics_psp";
					break;
				case Export_PS3 :
					pszGraphicsRoot = "graphics_ps3";
					break;
				case Export_Xbox360 :
					pszGraphicsRoot = "graphics_xbox360";
					break;
				case Export_Wii:
					pszGraphicsRoot = "graphics_wii";
					break;
				default :
					Asura_Assert(0,"Unknown export type");
					break;
			}
		}

		Asura_PrintfToBfr(szLocalFilename,"%s%s",MAX_PATH,true,pszGraphicsRoot,pszInputName);

		if(iPass==0 && pszExtension)
		{
			//try with platform filename extension
			char* pcDot = strrchr(szLocalFilename,'.');
			if(pcDot) * pcDot = 0;
			if(*pszExtension != '.') strcat(szLocalFilename,".");
			strcat(szLocalFilename,pszExtension);
		}

		//and from that the full filename of the file to use (possibly over network)
		Asura_FileAccess::PlatformGetNetworkFilename(szLocalFilename,szFullFilename,MAX_PATH);

		file = CreateFile(szFullFilename,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
	
		if(file == INVALID_HANDLE_VALUE)
		{
			continue;
		}
		bFoundFile = true;
		//found file
		CloseHandle(file);
		break;
	}
	if(!bFoundFile) return false;

	xOutputName = szFullFilename;
	if(pbUsingPlatformDir)
	{
		*pbUsingPlatformDir = iPlatformOveride ? true : false;
	}
	return true;
}

bool Tools_Level_Export::GenerateTextureIfNecessary( const Asura_String& xName,
                                                     int iTexID,
                                                     bool bQuiet,
													 bool bSRGB,
                                                    Asura_String* pxOutputFilename,
                                                     TextureConversionData* pxOutputConversionData)
{

	const char* pszSubDirectory = 0;
	const char* pszExtension = 0;
	switch(s_eExportType)
	{
		case Export_PS2 :
            pszSubDirectory = ( IsPS2MultiplayerExport() ? "TIM2_multiplayer" : "TIM2" );
			pszExtension = "tm2";
			break;
		case Export_PC :
			pszSubDirectory = "DDS";
			pszExtension = "dds";
			break;
		case Export_Wii:
			pszSubDirectory = "TPL";
			pszExtension = "tpl";
			break;
		case Export_PSP :
		{
			pszSubDirectory = "PSPTx";
			pszExtension = "PSPTx";
			break;
		}
		case Export_PS3 :
		{
			pszSubDirectory = "PS3Tx";
			pszExtension = "PS3Tx";
			break;
		}
		case Export_Xbox360 :
		{
			pszSubDirectory = "DDS_Xbox360";
			pszExtension = Tools_Xbox360_Texture_Writer::ShouldExportAsXPR( iTexID ) ? "xpr" : "dds";
			break;
		}


		default :
			Asura_Assert(0,"");
	}

	//check for file in platform override directory
	Asura_String xFullFilename;
	bool bUsingPlatformDir;
	if(!GetTextureFilenameForExport(xName.GetString(),pszExtension,xFullFilename,&bUsingPlatformDir))
	{
		if(!bQuiet)
		{
			AddMessage(MT_ERROR,"Failed to find %s",xName.GetString());
		}
		return false;
	}

	//If the file is in the platform dir , check to see if its in the correct format already
	if(bUsingPlatformDir)
	{
		const char* pcDot = strrchr(xFullFilename.GetString(),'.');
		if(pcDot && !stricmp(pcDot+1,pszExtension))
		{
			//can just go with this file then
			if(pxOutputFilename)
			{
				*pxOutputFilename = xFullFilename;
			}
		    if( !bQuiet )
			{
				AddMessage(MT_LOGONLY,"Using %s from platform override directory",xFullFilename.GetString());
			}
			return true;
		}
	}

	
	//create a temporary dds file
    if( !bQuiet )
    {
    	AddMessage( MT_HEADING, "Processing %s...", xName.GetString() );
		if(bUsingPlatformDir)
		{
			AddMessage(MT_LOGONLY,"(Using platform overide directory)");
		}
    }


	//build the filename for the converted file
	Asura_String xOutputFilename;
	Tools_FileSystem::GenerateFilenameForConvertedFile(xFullFilename.GetString(),pszSubDirectory,pszExtension,xOutputFilename);
	Tools_FileSystem::EnsureDirectoryExists(xOutputFilename.GetString());

	//If this texture has size overrides , we'll need to alter the filename accordingly
	//(since the same texture may use different sizes for different levels)
	
	int iWidthOverride = 0;
	int iHeightOverride = 0;
	int iPixelFormat = 0;
	int iPaletteFormat = 0;
	bool bUsePS2TextureOverride = false;
	bool bUseMaximumTextureSize = false;
	bool bSwizzle = (s_eExportType == Export_PS2) ? (s_bSwizzleTims) : (s_eExportType == Export_PSP);
	
	
	if(s_eExportType == Export_PS2)
	{
		int iWidth,iHeight;
		if(Tools_Texture_Management::GetTextureSize(iTexID,iWidth,iHeight))
		{
			if(GetPS2TextureSizeOverride(iTexID,&iWidthOverride,&iHeightOverride,&iPixelFormat,&iPaletteFormat))
			{
				bUsePS2TextureOverride = true;
				
				//ignore the override settings if they would make the image larger than the original
				if(iWidth<iWidthOverride || iHeight<iHeightOverride)
				{
					iWidthOverride = 0;
					iHeightOverride = 0;
				}

				if((iWidthOverride && iHeightOverride))
				{		
					//tack the width and height onto the end of the name
					char szTemp[MAX_PATH];
					strcpy(szTemp,xOutputFilename.GetString());
					char* pcDot = strrchr(szTemp,'.');
					Asura_Assert(pcDot,"");

					//add dimensions to name
					if(iWidthOverride)
					{
						sprintf(pcDot,"%dx%d",iWidthOverride,iHeightOverride);
						pcDot += strlen(pcDot);
					}
					sprintf(pcDot,".%s",pszExtension);


					xOutputFilename = szTemp;

				}
			}
		}
	}

	bool bMayNeedToReduceSize = s_uMaximumTextureSize > 0 && !bUsePS2TextureOverride && s_bUseMaximumTextureSize;
	
	#if defined ( ASURA_USE_TEXTURE_STREAMING )
	bool bStreamingTexture = IsLevelExport() && Asura_Texture_Streaming_System::Tools_IsTextureRegistered( iTexID );
	bMayNeedToReduceSize |= bStreamingTexture;
	#endif
	u_int uMaximumTextureSize = 0;

	if( bMayNeedToReduceSize )
	{
		uMaximumTextureSize = s_uMaximumTextureSize;
		if(bUsingPlatformDir)
		{
			//assume that textures in the platform directories are already at the correct size. RAR 06/05/2009
			uMaximumTextureSize = 0;
		}

		#if defined ( ASURA_USE_TEXTURE_STREAMING )
		if (bStreamingTexture)
		{
			uMaximumTextureSize = 128;
		}
		#endif

		if(uMaximumTextureSize)
		{
			//See if the texture is larger than the maximum texture size
			int iWidth,iHeight;
			if(Tools_Texture_Management::GetTextureSize(iTexID,iWidth,iHeight))
			{
				if((u_int)iWidth > uMaximumTextureSize || (u_int)iHeight > uMaximumTextureSize)
				{
					bUseMaximumTextureSize = true;

					//get the width/height that will be used
					u_int uTargetWidth = iWidth;
					u_int uTargetHeight = iHeight;
					while(uTargetWidth > uMaximumTextureSize || uTargetHeight > uMaximumTextureSize)
					{
						uTargetWidth >>= 1;
						uTargetHeight >>= 1;
					}
				
					//generate a new output name to avoid clashes from different people doing
					//exports with different max texture sizes
					char szTemp[MAX_PATH];
					strcpy(szTemp,xOutputFilename.GetString());
					char* pcDot = strrchr(szTemp,'.');
					Asura_Assert(pcDot,"");

					//add dimensions to name
					sprintf(pcDot,"%dx%d",uTargetWidth, uTargetHeight);
					pcDot += strlen(pcDot);

					sprintf(pcDot,".%s",pszExtension);

					xOutputFilename = szTemp;

				}

			}
		}
	}


	//Force all skybox textures to use 32 bit palette entries (since 16 bit isn't sufficient for them)  , this
	//can be done by adding the 8BitAlpha flag.
	//If not using fractal clouds , also need to ditch the alpha channel.
	//RAR 03/06/04

	
	int iTextureFlags = Asura_Texture_Management::GetTextureFlags(iTexID);
	if(s_eExportType == Export_PS2 && bUsingPlatformDir)
	{
		//Using a texture from the PS2 overide directory... so don't halve automatically
		iTextureFlags |= AsuraTextureFlag_PS2DontHalveSize;
	}

	if(s_eExportType == Export_PS2 && (iTextureFlags & AsuraTextureFlag_16ColourPalette))
	{
		//Force a 16 colour palette
		iPixelFormat = TIM2_IDTEX4;
	}
	
	//Check if this a skybox texture , if so add 8 bit alpha flag
	for(int i=0;i<Asura_Skybox::ASURA_SKYBOX_NUM_TEXTURES;i++)
	{
		if(Asura_Skybox::GetTexture(i) == xName)
		{
			if(Asura_Skybox::GetDrawClouds())
			{
				iTextureFlags |= AsuraTextureFlag_8BitAlpha;
			}
			else
			{
				iTextureFlags |= AsuraTextureFlag_IgnoreAlphaChannel;
				if(s_eExportType == Export_PS2)
				{
					//setting 8bit alpha flag for PS2 to force 8 bits for each of R,G,B as well
					iTextureFlags |= AsuraTextureFlag_8BitAlpha;
				}
			}
			//RAR 09/03/2006 Force no mip map flag for skybox textures
			//(Ignored for PC to avoid problems with not knowing flags when loading the texture in the first place)
			if(s_eExportType != Export_PC)
			{
				iTextureFlags |= AsuraTextureFlag_NoMipMaps;
			}
			break;
		}
	}
	
	if(s_eExportType == Export_PS2)
	{
		Asura_PFX_ParticleTemplateResourceSet_ConstResourceIterator xIterator(Asura_PFX_System::GetParticleTempResourceSet());

		// see if this is a PFX texture...
		while(!xIterator.Done())
		{
			const Asura_PFX_Particle_Template*	pxParticleTemplate	= xIterator.GetCurrent();

			if(pxParticleTemplate->m_xTextureName == xName)
			{
				// don't mipmap or downsample...
				iTextureFlags |= AsuraTextureFlag_NoMipMaps;
				iTextureFlags |= AsuraTextureFlag_PS2DontHalveSize;
				iTextureFlags |= AsuraTextureFlag_PS2Allow512;
			}

			xIterator.Next();
		}
	}
	
	if(s_eExportType == Export_PS2)
	{
		//see if this is the reflectance map
		if(xName == Asura_Render::GetReflectanceMapName())
		{
			//set some flags
			iTextureFlags |= AsuraTextureFlag_8BitAlpha;

// (MP - 07.04.2005) The PC and XBox renderers have changed to do their
// reflective stuff additively as well so all platforms now use the same
// texture.
//			//give a warning if not using texture from overide directory
//			if(!bUsingPlatformDir && !bQuiet)
//			{
//	        	AddMessage( MT_WARNING, "No PS2-specific reflectance map found.");
//	        	AddMessage( MT_WARNING, "Using PC texture instead (which probably won't work).");
//			}

		}
	}
	

	TextureConversionData convertData;
	convertData.Initialise( s_eExportType );

	convertData.m_iTextureFlags = iTextureFlags;
	convertData.m_uMaterialFlags = Asura_Material_System::GetMaterialFlagsUsedWithTexture(iTexID);
	convertData.m_bSwizzled = bSwizzle;
	convertData.m_bUnpalettised = false;
	convertData.m_iPixelFormat = iPixelFormat;
	convertData.m_iPaletteFormat = iPaletteFormat;
	convertData.m_uMaxMipDrops = static_cast<u_int>(s_fMaxMipDrops);
	convertData.m_uMaximumTextureSize = uMaximumTextureSize;
	convertData.m_bSRGB = bSRGB;

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	const u_int uTextureNameHash = Asura_Texture_Management::GetHashIDFromIndex( iTexID );
	convertData.m_uTextureUsageFlags = Tools_MaterialResponse_System::GetTextureUsage( uTextureNameHash );
#endif

	u_int uAndedMaterialFlags = Asura_Material_System::GetAndedMaterialFlagsUsedWithTexture(iTexID);
	u_int uTransparentMaterialFlags = AsuraMaterialFlag_Transparent|AsuraMaterialFlag_PS2AdditiveAlpha;
	if(!(uAndedMaterialFlags & uTransparentMaterialFlags) && (convertData.m_uMaterialFlags & uTransparentMaterialFlags))
	{
		//Material occurs as both transparent and non-transparent , so we need to
		//disable premultiplyng alpha when doing palette generation. RAR 28/11/2003
		convertData.m_iTextureFlags |= AsuraTextureFlag_DontPremultiplyAlphaInPaletteGeneration;

	}

    // (MH - 03.12.2002) TEST: ps2 additive alpha => 8bit alpha
    if (convertData.m_uMaterialFlags & AsuraMaterialFlag_PS2AdditiveAlpha)
    {
        convertData.m_iTextureFlags |= AsuraTextureFlag_8BitAlpha;
    }

	if(s_eExportType == Export_PC)
	{
		//Ignore no mip map flag. RAR 08/12/2004
		convertData.m_iTextureFlags &=~ AsuraTextureFlag_NoMipMaps;
	}

	CheckForInvalidTextureFlags( xName, iTexID );

	//Filled in everything , let the project code get a last say at the settings. RAR 26/10/2006
	Project_ModifyTextureConversionSettings(&convertData,iTexID, xName.GetString());

	convertData.ClearUnusedEntriesForPlatform(s_eExportType);
	const Asura_Hash_ID uConversionHash = convertData.GetHash();

	bool bNeedToGenerate = false;
	//check to see if local copy of converted file is okay
	if(!Tools_FileSystem::DoModifiedTimesMatch(xFullFilename.GetString(),xOutputFilename.GetString())  || !ConversionDataFileMatches(xOutputFilename.GetString(),&convertData))
	{
		Asura_String xShadowFilename;
		Tools_FileSystem::GetShadowConversionFilename(xOutputFilename.GetString(),&xShadowFilename, uConversionHash);
		//check network version
		if(xShadowFilename.IsBlank() || !Tools_FileSystem::DoModifiedTimesMatch(xFullFilename.GetString(),xShadowFilename.GetString())  || !ConversionDataFileMatches(xShadowFilename.GetString(),&convertData))
		{
			//need to regenerate file then
			bNeedToGenerate = true;
		}
		else
		{
			bNeedToGenerate = !Tools_FileSystem::CopyFromConvertedFileShadow(xOutputFilename.GetString(), uConversionHash);
		}
	}

	const bool bAlwaysGenerate = (s_eExportType == Export_Wii) && Tools_Wii_Textures::ShouldMergeAlphaTextures();

	if(bNeedToGenerate || bAlwaysGenerate || s_bForceTextureGeneration)
	{
        if( !bQuiet )
        {
            AddMessage( MT_INFORMATION, "Regenerating export data..." );
        }

		bool bGenerated = false;
		if(s_eExportType == Export_PS2)
		{
 			bGenerated = CreateTim2File(xFullFilename.GetString(),xOutputFilename.GetString(), iTexID, convertData, iWidthOverride, iHeightOverride);
		}
		else if(s_eExportType == Export_Wii)
		{
			bGenerated = CreateTPLFile(xFullFilename.GetString(),xOutputFilename.GetString(), iTexID, convertData);
		}
		else if (s_eExportType == Export_PSP)
		{
			bGenerated = Tools_PSP_Export::ExportTexture(xFullFilename.GetString(), xOutputFilename.GetString(), iTexID, convertData);
		}
		else if (s_eExportType == Export_PS3)
		{
			bGenerated = Tools_PS3_Export::ExportTexture( xFullFilename.GetString(), xOutputFilename.GetString(), iTexID, convertData );
		}
		else if (s_eExportType == Export_Xbox360)
		{
			bGenerated = Tools_Xbox360_Export::ExportTexture( xFullFilename.GetString(), xOutputFilename.GetString(), iTexID, convertData );
		}
		else
		{
			bGenerated = CreateDDSFile(xFullFilename.GetString(),xOutputFilename.GetString(),iTexID, convertData);
		}

		if(bGenerated && !bAlwaysGenerate)
		{
			Tools_FileSystem::CopyFileTimes(xFullFilename.GetString(),xOutputFilename.GetString());
			CreateConversionDataFile(xOutputFilename.GetString(),&convertData);

			//update network shadow
			Tools_FileSystem::CopyToConvertedFileShadow(xFullFilename.GetString(), xOutputFilename.GetString(), uConversionHash);
		}
	}

    if( pxOutputFilename)
    {
        *pxOutputFilename = xOutputFilename.GetString();
    }
	if(pxOutputConversionData)
	{
		*pxOutputConversionData = convertData;
	}

	ClearMessageDisplayHeading();

    return true;
}

u_int Tools_Level_Export::GetTIM2DataSize( int iTexID, bool bQuiet )
{
    if( ASURA_TEXTURE_NONE == iTexID ) { return 0; }

    Asura_Assert( This, "" );

    s_eExportType = Export_PS2;
    
    Asura_String xFilename;
    if( !This->GenerateTextureIfNecessary(*Asura_Texture_Management::GetTextureName(iTexID), iTexID, bQuiet, false, &xFilename) )
    {
        return 0;
    }

    u_int uDataSize;
    if( !Tools_Tim2_Writer::GetDataSizeFromExistingFile(xFilename.GetString(), &uDataSize) )
    {
        return 0;
    }

    return uDataSize;
}

bool Tools_Level_Export::GetTIM2Proportions( int iTexID, u_int* puWidth, u_int* puHeight, bool bQuiet )
{
    if( ASURA_TEXTURE_NONE == iTexID ) { return false; }

    Asura_Assert( This, "" );

    s_eExportType = Export_PS2;

    Asura_String xFilename;
    if( !This->GenerateTextureIfNecessary(*Asura_Texture_Management::GetTextureName(iTexID), iTexID, bQuiet, false, &xFilename) )
    {
        return false;
    }

    if( !Tools_Tim2_Writer::GetImgProportionsFromExistingFile(xFilename.GetString(), puWidth, puHeight) )
    {
        return false;
    }

    return true;
}

bool Tools_Level_Export::DoesTIM2Have8BitAlpha( int iTexID, bool* pbHas8BitAlpha, bool bQuiet )
{
    if( ASURA_TEXTURE_NONE == iTexID ) { return false; }

    Asura_Assert( This, "" );

    s_eExportType = Export_PS2;
    Asura_String xFilename;
    if( !This->GenerateTextureIfNecessary(*Asura_Texture_Management::GetTextureName(iTexID), iTexID, bQuiet, false, &xFilename) )
    {
        return false;
    }

    Tools_Tim2_Writer::ALPHA_TYPES eAlphaType;
    if( !Tools_Tim2_Writer::GetAlphaTypeFromExistingFile(xFilename.GetString(), &eAlphaType) )
    {
        return false;
    }

    *pbHas8BitAlpha = ( Tools_Tim2_Writer::ALPHA_8BIT == eAlphaType );
    return true;
}

bool Tools_Level_Export::CreateTPLFile(const char* inputName,const char* outputName,int index, const TextureConversionData& xConversionData )
{
	return Tools_Wii_Textures::ExportTexture(inputName, outputName, xConversionData, (unsigned int)s_fMinGCTexRes, (unsigned int)s_fMaxMipDrops);
}

#if defined( ASURA_USE_BOTANICALS )
void Tools_Level_Export::AddBotanicalsTextures( HANDLE pSaveHandle )
{
	UpdateStatus( EXPORT_STAGE_ADDING_BOTANICAL_TEXTURES, "Adding botanical textures...", -1 );

	// Loop over the botanical shapes finding the materials used and adding them to the list
	m_xBotanicalsTextures.Reset();

	const Asura_String** ppxNameList = new const Asura_String*[ASURA_MAX_NO_OF_TEXTURES];
	Asura_FixedArray<bool, ASURA_MAX_NO_OF_TEXTURES> xTexturesUsed;

	for(int i=0;i<ASURA_MAX_NO_OF_TEXTURES;i++)
	{
		ppxNameList[i] = NULL;
		xTexturesUsed[i] = 0;
	}

	Asura_FixedArray<bool, (ASURA_MAX_NO_OF_MATERIALS+ASURA_MATERIAL_FIRSTID)> xMaterialsUsed;
	for( u_int u = 0; u < xMaterialsUsed.GetSize(); u++ )
	{
		xMaterialsUsed[u] = false;
	}

	//and get those names
	Asura_Texture_Management::MakeNameList( ppxNameList );

	// list meshes used by shapes
	// Loop over the instances rather than the actual template types - so that we only export
	// template type data which is actually being used i.e. instanced and the instance is in a valid module
	const u_int uMaxNumBotanicalsTypes = Asura_Botanicals_TemplateManager::GetNumTemplates();
	Tools_DynamicArray<bool> xActiveTypes( uMaxNumBotanicalsTypes );
	
	// (MH - 10.04.2006) deal with 0-sized array.
	if (uMaxNumBotanicalsTypes)
	{
		Asura_Assert( xActiveTypes, "" );
		memset( xActiveTypes, false, sizeof( bool ) * uMaxNumBotanicalsTypes );
	}

	int iNumberOfTexturesUsed = 0;
	List<Asura_Mesh*> xMeshList;
	Asura_LinkList<Asura_Botanicals_Instance*>& xInstanceList = Asura_Botanicals_InstanceManager::GetInstanceList();
	for ( Asura_ListElt<Asura_Botanicals_Instance*>* pxElt = xInstanceList.Front(); pxElt; pxElt = pxElt->Next() )
	{
		const Asura_Botanicals_Instance* const pxInstance = pxElt->Data();
		const int iTypeID = pxInstance->iTemplateUID;
		const int iTypeIndex = Asura_Botanicals_TemplateManager::GetTemplateIndexFromUID( iTypeID );
		// Ignore if the type doesn't exist
		if ( iTypeIndex < 0 )
		{
			continue;
		}
		const Asura_Botanicals_Template* const pxTemplate = Asura_Botanicals_TemplateManager::GetTemplate( iTypeIndex );
		// Ignore templates which are marked to be merged with the environment
		if ( pxTemplate->bMergeRenderMeshWithEnv == true )
		{
			continue;
		}
		const Asura_Vector_3& xPos = pxInstance->xPosition;
		// Ignore instance if it is not in a valid module
		int iModuleIndex = Asura_Botanicals_InstanceManager::GetModuleIndexFromPosition( xPos, pxInstance->xOrientation );
		if ( iModuleIndex < 0 )
		{
			continue;
		}
		// Only export the data if this is a new botanical type
		if ( xActiveTypes[iTypeIndex] == false )
		{
			bool bValidType = true;
			// So is it a standard shape or a special botanical shape?
			switch ( pxTemplate->eType )
			{
				case ASURA_BOTANICALS_TEMPLATE_SHAPE:
				{
					if ( pxTemplate->uShapeID == ASURA_HASH_ID_UNSET )
					{
						// This is an invalid type
						AddMessage( MT_ERROR, "Botanical template %s has no valid shape.", pxTemplate->xTemplateName.GetString() );
						bValidType = false;
					}
					break;
				}
				case ASURA_BOTANICALS_TEMPLATE_BOTANICALSHAPE:
				{
					if ( pxTemplate->pxBotanicalShape == NULL )
					{
						// This is an invalid type
						AddMessage( MT_ERROR, "Botanical template %s has no valid botanical shape.", pxTemplate->xTemplateName.GetString() );
						bValidType = false;
					}
					else if ( pxTemplate->pxBotanicalShape->GetToolsShape() == NULL )
					{
						// This is an invalid type
						AddMessage( MT_ERROR, "Botanical template %s has no valid Tools shape.", pxTemplate->xTemplateName.GetString() );
						bValidType = false;
					}
					break;
				}
				default:
				{
					// Unknown template type (should never happen)
					bValidType = false;
					break;
				}
			}

			// Ignore invalid template types
			if ( bValidType == false )
			{
				AddMessage( MT_ERROR, "Unable to process botanical template %s.", pxTemplate->xTemplateName.GetString() );
				continue;
			}

			// A new type so add its meshes to the list of meshes to export
			if ( pxTemplate->eType == ASURA_BOTANICALS_TEMPLATE_SHAPE )
			{
				// Get the mesh for the standard shape (i.e. not a specific botanical shape)
				Asura_String xShapeName( pxTemplate->xShapeName );
				Asura_String xMeshName;
				Asura_String xExtension;
				Asura_String xPath;
				xShapeName.SplitFilename( &xPath, &xMeshName, &xExtension );
				Asura_Mesh* pxMesh = Asura_Mesh_System::GetMesh( xMeshName.GetString(), false );
				if ( pxMesh ) xMeshList.add_entry(pxMesh);
			}	
			else
			{
				// Must be a special botanicals shape
				// Know these are not NULL because it has been tested in previous validation code
				Asura_BotanicalShape* const	pxBotanicalShape = pxTemplate->pxBotanicalShape;
				Tools_BotanicalShape* const pxToolsShape = pxBotanicalShape->GetToolsShape(); 
				const u_int uNumElements = pxToolsShape->GetNumElements();
				// Loop over the elements in the botanical shape
				for ( u_int uI = 0; uI < uNumElements; uI++ )
				{
					Tools_BotanicalElement*	const pxElement = pxToolsShape->GetElementByIndex( uI );
					Asura_Assert( pxElement, "" ); 
					if ( pxElement == NULL )
					{
						bValidType = false;
						break;
					}
					const int iNumLoDs = pxElement->GetNumLODs();
					// Get to each LoD mesh - the lowest level data
					for ( int iLoD = 0; iLoD < iNumLoDs; iLoD++ )
					{
						Tools_BotanicalElementLOD* const pxLoD = pxElement->GetLODByIndex( iLoD );
						Asura_Assert( pxLoD, "" ); 
						if ( pxLoD == NULL )
						{
							bValidType = false;
							break;
						}
						if ( pxElement->m_eType != TOOLS_BOTANICALS_ELEMENTTYPE_IMPOSTER )
						{
							Asura_Mesh* const pxMesh = pxLoD->m_pxMesh;
							if ( pxMesh ) xMeshList.add_entry(pxMesh);
						}
						else
						{
							// For imposters loop over them finding the texture they use
							// Check it is only 1 texture for all the imposters
							int iTexID = ASURA_TEXTURE_NONE;
							const u_int uNumImposters = pxLoD->GetNumImposters();
							for ( u_int uI = 0; uI < uNumImposters; uI++ )
							{
								const Tools_Botanicals_Imposter* const pxImposter = pxLoD->GetImposterByIndex( uI );


								// DMR 22/11/06 - Imposter meshes aren't discarded any more, they're								// kept around for material export....
								if (pxImposter->m_pxMesh)
								{
									xMeshList.add_entry(pxImposter->m_pxMesh);
								}
								
#if defined( ASURA_USE_MATERIAL_RESPONSE_SYSTEM )
								{
									const Asura_Hash_ID xMaterialHashID = pxImposter->m_uMaterialResponseHashID;
									Tools_Level_Export::AddMaterialToExport( pSaveHandle, xMaterialHashID);
								}
#endif
								
								if ( pxImposter->m_iTextureIndexID != iTexID )
								{
									if ( iTexID == ASURA_TEXTURE_NONE )
									{
										// A new texture so add it to the list and its texture flags
										iTexID = pxImposter->m_iTextureIndexID;
										xTexturesUsed[iTexID] = true;
										int iNewIndex;
										m_xBotanicalsTextures.FindNodeIndexByName( ppxNameList[iTexID]->GetString(), true, iNewIndex );
										int iTextureFlags = Asura_Texture_Management::GetTextureFlags( iTexID );
										// For the PS2 export set the dont premultiply alpha in palette generation flag for 8-bit alpha textures
										if ( s_eExportType == Export_PS2 )
										{
//CSB 28/04/06	Force certain texture flags	vvvvv
											iTextureFlags |= s_iForcedBotanicalTextureFlagsPS2;
											if ( iTextureFlags & AsuraTextureFlag_8BitAlpha )
											{
												iTextureFlags |=  AsuraTextureFlag_DontPremultiplyAlphaInPaletteGeneration;
												Asura_Texture_Management::SetTextureFlags( iTexID, iTextureFlags );
											}
										}
										iNumberOfTexturesUsed++;
									}
									else
									{
										const char* const szMeshName = pxImposter->m_pxMesh ? pxImposter->m_pxMesh->Name.GetString() : NULL;
										Asura_Assertf( false, ("Only allowed 1 texture for the botanical imposters (%s)", szMeshName) );
									}
								}
							}
						}
					}
					if ( bValidType == false )
					{
						break;
					}
				}
			}
			xActiveTypes[iTypeIndex] = true;
		}
	}

	// (AAM - 29.08.2006) Create a conversion array to help with platform-specific exports
	Tools_SaveAsura::PrepareMeshMaterialConversionLists( xMeshList, m_aiBotanicalsMtlConvList, NULL );

	// Find textures used by the meshes
	m_xBotanicalsMtlList.Reset();
	m_iNumBotanicalsMtls = 0;
	for( LIF<Asura_Mesh*> xMeshLIF( &xMeshList ); !xMeshLIF.done(); xMeshLIF.next() )
	{
		Asura_Mesh* pxMesh = xMeshLIF();	
		//add the texture(s) used by this mesh and any detail models
		while( pxMesh )
		{
			for( int iP = 0; iP < pxMesh->NumberOfPolygons; iP++ )
			{
				if ( pxMesh->PolygonFlagDataValid && ( pxMesh->PolygonFlagDataPtr[iP].Flags & ( AsuraPolygonFlag_DontExport | AsuraPolygonFlag_DontRender ) ) )
				{
					//ignore textures for polygons that won't be rendered/exported
					continue;
				}

#if defined( ASURA_USE_MATERIAL_RESPONSE_SYSTEM )
				{
					for(int iMatSlot=0; iMatSlot < TOOLS_NUM_MATERIALRESPONSE_PER_POLY; iMatSlot++)
					{
						const Asura_Hash_ID xMaterialHashID = pxMesh->TextureDataPtr[iP].m_auMaterialResponseHashID[iMatSlot];
						Tools_Level_Export::AddMaterialToExport( pSaveHandle, xMaterialHashID);
					}
				}
#endif

				const int iMaterialID = pxMesh->TextureDataPtr[iP].m_iMaterialID;

				Asura_Material& xMaterial = m_xBotanicalsMtlList[m_iNumBotanicalsMtls];
				Asura_Material* const pxMaterialData = Asura_Material_System::GetMaterial( iMaterialID );
				if ( pxMaterialData != NULL )
				{
					xMaterial = *pxMaterialData;
				}

				for ( int iT = 0; iT < ASURA_MATERIAL_MAXTEXTURES; iT++ )
				{
					// Diffuse texture
					int iIndex = Asura_Material_System::GetTextureID( iMaterialID, iT );
					if ( iIndex != -1 )
					{
						int iNewIndex;
						m_xBotanicalsTextures.FindNodeIndexByName( ppxNameList[iIndex]->GetString(), true, iNewIndex );
						if ( xMaterialsUsed[iMaterialID] == false )
						{
							xMaterial.m_iTextureID[iT] = iNewIndex;
						}
						if ( xTexturesUsed[iIndex] == false )
						{
							xTexturesUsed[iIndex] = true;
							int iTextureFlags = Asura_Texture_Management::GetTextureFlags( iIndex );

							// For the PS2 export set the dont premultiply alpha in palette generation flag for 8-bit alpha textures
							if ( s_eExportType == Export_PS2 )
							{
//CSB 28/04/06	Force certain texture flags	vvvvv
								iTextureFlags |= s_iForcedBotanicalTextureFlagsPS2;
								if ( iTextureFlags & AsuraTextureFlag_8BitAlpha )
								{
									iTextureFlags |=  AsuraTextureFlag_DontPremultiplyAlphaInPaletteGeneration;
									Asura_Texture_Management::SetTextureFlags( iIndex, iTextureFlags );
								}
							}
							iNumberOfTexturesUsed++;
						}
					}

					// Normal map
					iIndex = Asura_Material_System::GetNormalMapID( iMaterialID, iT );
					if ( iIndex != -1 )
					{
						int iNewIndex;
						m_xBotanicalsTextures.FindNodeIndexByName( ppxNameList[iIndex]->GetString(),true,iNewIndex );
						if ( xMaterialsUsed[iMaterialID] == false )
						{
							xMaterial.m_iNormalMapID[iT] = iNewIndex;
						}
						if ( xTexturesUsed[iIndex] == false )
						{
							xTexturesUsed[iIndex] = true;
							iNumberOfTexturesUsed++;
						}
					}

					// Specular map
					iIndex = Asura_Material_System::GetSpecularMapID( iMaterialID, iT );
					if ( iIndex != -1 )
					{
						int iNewIndex;
						m_xBotanicalsTextures.FindNodeIndexByName( ppxNameList[iIndex]->GetString(), true, iNewIndex );
						if ( xMaterialsUsed[iMaterialID] == false )
						{
							xMaterial.m_iSpecularMapID[iT] = iNewIndex;
						}
						if ( xTexturesUsed[iIndex] == false )
						{
							xTexturesUsed[iIndex] = true;
							iNumberOfTexturesUsed++;
						}
					}
				}

				// (AAM - 27.10.2006) Only export if no equivalent has already been added, since
				// Tools_SaveAsura::PrepareMeshMaterialConversionLists() does the same

				bool bAlreadyExported = false;

				for( int i = 0; i < m_iNumBotanicalsMtls; ++i )
				{
					// Don't care about their names
					if( Asura_Material::MaterialsAreEquivalent( &m_xBotanicalsMtlList[i], &xMaterial, false ) )
					{
						bAlreadyExported = true;
						break;
					}
				}

				if ( xMaterialsUsed[iMaterialID] == false && !bAlreadyExported )
				{
					xMaterialsUsed[iMaterialID] = true;
					m_iNumBotanicalsMtls++;
				}
			}
			//get the next detail mesh
			pxMesh = pxMesh->m_pxNextLowerMesh;
		}
	}

	// (AAM - 25.09.2006) Used to output the texture names & flags and material chunks here,
	// but they need to be done immediately before mesh processing. Flags chunk is actually
	// redundant now anyway since they get exported in the resource chunks

	//export the textures found
	int iNumberOfTexturesOutput = 0;
	for( int i = 0; i < ASURA_MAX_NO_OF_TEXTURES && !IsAbortFlagSet(); i++ )
	{
		if ( !xTexturesUsed[i] ) continue;

		s_iPercent = iNumberOfTexturesOutput++ * 100 / iNumberOfTexturesUsed;
		if ( ppxNameList[i] )
		{
			UpdateProgress();
			//add this texture
			AddTexture( pSaveHandle, *ppxNameList[i], i, true );
		}
	}

	UpdateStatus(EXPORT_STAGE_END, 0, -1);

	delete [] ppxNameList;
}

#endif // #if defined( ASURA_USE_BOTANICALS )

bool Tools_Level_Export::IsTextureOkayForStreaming(int iTextureID)
{
	if(iTextureID == ASURA_TEXTURE_NONE) return false;
	
	//Make sure this isn't the FMV placeholder texture
	Asura_String xTextureName = *Asura_Texture_Management::GetTextureName(iTextureID);
	xTextureName.MakeLowercase();

	if(xTextureName.Contains("fmv_output"))
	{
		return false;
	}

	//Ignore small textures - no point in streaming in a texture if the 'small' version is larger than the full sized version.
	int iWidth,iHeight;
	if(Tools_Texture_Management::GetTextureSize(iTextureID, iWidth, iHeight))
	{
		if(iWidth*iHeight <= 128*128)
		{
			return false;
		}
	}
	return true;
}

/// End of file. ///
