//////////////////////////////////////////////////////////////////////////////
//
// Tools_SmoothSkin_Processor_Xbox360.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#include "MeshProcessing/Tools_SmoothSkin_Processor_Xbox360.h"

// Asura Platform-independent
#include "Animation/Asura_Hierarchy.h"
#include "ChunkSystem/Asura_Chunk_ResourceFile.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Memory/Asura_Mem.h"
#include "Core/Asura_HalfFloat.h"
#include "Core/Asura_Hash.h"
#include "Core/Asura_Maths.h"
#include "Debugging/Asura_Assert.h"

// Asura Xbox 360
#include "Xbox360/ChunkSystem/Asura_Chunk_ResourceFile_Xbox360.h"
#include "Xbox360/ChunkSystem/Asura_Chunk_ResourceFile_Xbox360_Character.h"
#include "Xbox360/ChunkSystem/Asura_Chunk_ResourceFile_Xbox360_Skin.h"

// Tools
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "MeshProcessing/Asura_TriStripper.h"
#include "MeshProcessing/Tools_HierarchyProcessor_MultiMaterial_NormalMapped.h"
#include "MeshProcessing/Tools_MeshRestartIndex.h"
#include "MeshProcessing/Tools_SmoothSkin_Processor.h"
#include "Xbox360/Tools_Xbox360_Export.h"

// Misc
#include <windows.h>

/******************************************************************************/
/* Classes                                                                    */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Tools_SmoothSkin_Processor_Xbox360::ProcessedStrip
//
// Holds information on a strip as it's prepared by the skin processor
//
////////////////////////////////////////////////////////////////////////////////

class Tools_SmoothSkin_Processor_Xbox360::ProcessedStrip
{
public:

    // Constructor / destructor
	ProcessedStrip();
    ~ProcessedStrip();

	// Functions
	u_int GetMaxWeights() const;

    // Data

	Tools_SmoothSkin_Vertex* m_pxVertices;
    u_int    m_uNumVertices;
    u_short* m_pusIndices;
    u_int    m_uNumIndices;
	#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
	Asura_Hash_ID m_auMaterialResponseHashID[2];
	#else
	u_int	 m_uMaterialID;
	#endif
	int      m_iElementID;

	u_int	m_uBoneMapID;

};

/******************************************************************************/
/* Tools_SmoothSkin_Processor_Xbox360::ProcessedStrip Class Functions		  */
/******************************************************************************/

Tools_SmoothSkin_Processor_Xbox360::ProcessedStrip::ProcessedStrip()
	: m_pxVertices( NULL )
	, m_uNumVertices(0)
	, m_pusIndices( NULL )
	, m_uNumIndices(0)
	, m_iElementID(0)
	, m_uBoneMapID(0)
{
}


Tools_SmoothSkin_Processor_Xbox360::ProcessedStrip::~ProcessedStrip()
{
	delete[] m_pxVertices;
	delete[] m_pusIndices;
}

u_int Tools_SmoothSkin_Processor_Xbox360::ProcessedStrip::GetMaxWeights() const
{
	// Find the maximum number of weightings used by verts in this strip

	u_int uMax = 0;

	for( u_int u = 0; u < m_uNumVertices; ++u )
	{
		uMax = Asura_Maths::Max( uMax, m_pxVertices[u].GetNumWeightings() );
	}

	return uMax;
}

/******************************************************************************/
/* Tools_SmoothSkin_Processor_Xbox360 Class Functions                         */
/******************************************************************************/

Tools_SmoothSkin_Processor_Xbox360::Tools_SmoothSkin_Processor_Xbox360()
	: m_pxSrcProcessor( NULL )
	, m_szSrcName( NULL )
	, m_pxProcessedStrips( NULL )
{
}

Tools_SmoothSkin_Processor_Xbox360::~Tools_SmoothSkin_Processor_Xbox360()
{
	delete[] m_pxProcessedStrips;
}

bool Tools_SmoothSkin_Processor_Xbox360::Initialise( const Tools_SmoothSkin_Processor& xSrcProcessor )
{
	m_pxSrcProcessor = &xSrcProcessor;
	m_szSrcName = xSrcProcessor.GetInputSkin()->Name.GetString();

	// (MP - 10.05.2006) I haven't bothered coping with multiple elements
	// since that functionality isn't used and I suspect never will be. It
	// would be fairly straightforward to extend the code below if necessary.
	// Asura_Assert( m_pxSrcProcessor->GetNumElements() == 1, "Multiple elements currently not supported" );

	// (DMR - 12.06.2006) Multiple elements now supported. Note that everything that
	// was called a 'material' before, is called a 'strip' now, which is consistent
	// with the PC version


	// First, count the number of non-empty elements
	u_int uNumStrips = 0; 
	for( u_int u = 0; u < m_pxSrcProcessor->GetNumMaterials(); ++u )
	{
		for( u_int v = 0; v < m_pxSrcProcessor->GetNumElements(); ++v )
		{
			const Tools_SmoothSkin_Material& xMaterial = m_pxSrcProcessor->GetMaterial( u );
			const Tools_SmoothSkin_SubElement& xSubElement = xMaterial.GetSubElement( v );

			if (xSubElement.GetNumTris()>0)
			{
				uNumStrips++;
			}
		}
	}

	m_uNumProcessedStrips = uNumStrips;
	m_pxProcessedStrips   = new ProcessedStrip[uNumStrips];
	m_uTotalVertices	  = 0;
	m_uTotalIndices		  = 0;
	m_uTotalTriangles	  = 0;
	m_uNumProcessedStrips = 0;

	for( u_int u = 0; u < m_pxSrcProcessor->GetNumMaterials(); ++u )
	{
		for( u_int v = 0; v < m_pxSrcProcessor->GetNumElements(); ++v )
		{
			const Tools_SmoothSkin_Material& xMaterial = m_pxSrcProcessor->GetMaterial( u );
			const Tools_SmoothSkin_SubElement& xSubElement = xMaterial.GetSubElement( v );

			if (xSubElement.GetNumTris()>0)
			{			
				ProcessedStrip& xProcessedStrip = m_pxProcessedStrips[m_uNumProcessedStrips];
				m_uNumProcessedStrips++;

				Tools_TriStripper::Initialise();
				Tools_TriStripper::SetOutputType( Tools_TriStripper::OUTPUT_TRISTRIP_WITH_RESET_INDICES );
				// TODO: Set cache size?
				
				xProcessedStrip.m_iElementID  = m_pxSrcProcessor->GetElementID(v);

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
				xProcessedStrip.m_auMaterialResponseHashID[0] = m_pxSrcProcessor->GetMaterial(u).GetMaterialResponseHashID(0);
				xProcessedStrip.m_auMaterialResponseHashID[1] = m_pxSrcProcessor->GetMaterial(u).GetMaterialResponseHashID(1);
#else
				xProcessedStrip.m_uMaterialID = m_pxSrcProcessor->GetMaterial(u).GetMaterialIDWithinSkin();
#endif
				xProcessedStrip.m_uNumIndices = Tools_TriStripper::CreateStrips( xSubElement.GetNumTris(), xSubElement.GetIndices(),
																					&xProcessedStrip.m_pusIndices );

				xProcessedStrip.m_uBoneMapID = m_pxSrcProcessor->GetBoneMapIndex(u, v);

				u_short* pusVtxPermutations;
				Tools_TriStripper::ComputeVertexPermutation( xProcessedStrip.m_uNumIndices, xProcessedStrip.m_pusIndices,
															 m_pxSrcProcessor->GetNumVertices(), &pusVtxPermutations,
															 &xProcessedStrip.m_uNumVertices );

				Tools_TriStripper::Shutdown();

				// Create new list of vertices just containing the ones used by
				// this material's strips, matching the newly modified index list.
				xProcessedStrip.m_pxVertices = new Tools_SmoothSkin_Vertex[xProcessedStrip.m_uNumVertices];
				for( u_int u = 0; u < xProcessedStrip.m_uNumVertices; ++u )
				{
					const Tools_SmoothSkin_Vertex &xSrcVtx = m_pxSrcProcessor->GetVertex( pusVtxPermutations[u] );

					const Tools_SmoothSkin_BoneMap *pxBoneMap = m_pxSrcProcessor->GetBoneMap(m_pxSrcProcessor->GetVertexBoneMapIndex(pusVtxPermutations[u]));
					if (pxBoneMap)
					{
						short axBoneIndicies[4];
						axBoneIndicies[0] = (short)pxBoneMap->GetEntryForBone(xSrcVtx.GetBoneIndices()[0]);
						axBoneIndicies[1] = (short)pxBoneMap->GetEntryForBone(xSrcVtx.GetBoneIndices()[1]);
						axBoneIndicies[2] = (short)pxBoneMap->GetEntryForBone(xSrcVtx.GetBoneIndices()[2]);
						axBoneIndicies[3] = (short)pxBoneMap->GetEntryForBone(xSrcVtx.GetBoneIndices()[3]);

						xProcessedStrip.m_pxVertices[u].Set(xSrcVtx.GetMaterialIdx(), xSrcVtx.GetPos(), xSrcVtx.GetNormal(),
							xSrcVtx.GetU(), xSrcVtx.GetV(), 
#ifdef TOOLS_SMOOTHSKIN_PROCESSOR_USE_VERTEX_FLAGS
							xSrcVtx.GetFlags(), 
#endif
#ifdef TOOLS_SMOOTHSKIN_PROCESSOR_USE_ELEMENT
							xSrcVtx.GetElement(), 
#endif
							axBoneIndicies, xSrcVtx.GetBoneWeights(), xSrcVtx.GetColour());

						xProcessedStrip.m_pxVertices[u].SetTangentAndBinormal( xSrcVtx.GetTangent(), xSrcVtx.GetBinormal() );
						xProcessedStrip.m_pxVertices[u].SetDualPass( Asura_Vector_2(xSrcVtx.GetU2(), xSrcVtx.GetV2()), xSrcVtx.GetAlpha() );

					} else {
						xProcessedStrip.m_pxVertices[u] =  xSrcVtx;
					}
				}

				delete[] pusVtxPermutations;

				m_uTotalVertices  += xProcessedStrip.m_uNumVertices;
				m_uTotalIndices   += xProcessedStrip.m_uNumIndices;
				m_uTotalTriangles += xSubElement.GetNumTris();
			}
		}
	}

	// Check we're going to be able to put all of these vertices into a
	// single buffer using only 16-bit indices.
	{
		u_short usRestartIndex;
		Tools_MeshRestartIndex::IsValidForCurrentPlatform( usRestartIndex );
		Asura_Assert( m_uTotalVertices != usRestartIndex && m_uTotalVertices < (1 << 16), "Too many vertices" );
	}

	// Also, check we can put all of the indices in a single buffer. Max
	// vertex index is 0xFFFFFF on 360.
	Asura_Assert( m_uTotalIndices <= 0xFFFFFF, "" );

	return true;
}

bool Tools_SmoothSkin_Processor_Xbox360::InitialiseFromHierarchyProcessor()
{
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	// Don't need to do much, just copy the hierarchy processor's data into our 'processed strips'

	m_szSrcName =
		Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetCurrentSkin()->Name.GetString();

	m_uTotalTriangles = 0; // Will be incremented in the loop

	m_uTotalVertices =
		Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfVertices();

	m_uTotalIndices =
		Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfIndices();

	m_uNumProcessedStrips =
		Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetTotalNumberOfStrips();

	m_pxProcessedStrips = new ProcessedStrip[ m_uNumProcessedStrips ];

	Asura_Assert( m_pxProcessedStrips, "Unable to allocate array for skin processor." );
	if( !m_pxProcessedStrips ) return false;

	for( u_int u = 0; u < m_uNumProcessedStrips; ++u )
	{
		const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Strip& xSrcStrip =
			*Tools_HierarchyProcessor_MultiMaterial_NormalMapped::GetProcessedStrip(u);

		ProcessedStrip& xProcessedStrip = m_pxProcessedStrips[u];

		xProcessedStrip.m_auMaterialResponseHashID[0] = xSrcStrip.m_auMaterialResponseHashID[0];
		xProcessedStrip.m_auMaterialResponseHashID[1] = xSrcStrip.m_auMaterialResponseHashID[1];

		// Indices

		const u_int uNumIndices = xSrcStrip.m_uNumberOfIndices;

		xProcessedStrip.m_uNumIndices = uNumIndices;
		xProcessedStrip.m_pusIndices = new u_short[ uNumIndices ];

		if( !xProcessedStrip.m_pusIndices )
		{
			Asura_Assert( false, "Unable to allocate index array for skin processor." );
			return false;
		}

		Asura_Mem::Copy( xProcessedStrip.m_pusIndices, xSrcStrip.m_pusIndices,
			sizeof( u_short ) * uNumIndices );

		xProcessedStrip.m_iElementID = 0; // TODO: can hierarchies be split into chunking elements?
		xProcessedStrip.m_uBoneMapID = 0; // TODO: can remove bone maps after this

		// Vertices

		const u_int uNumVertices = xSrcStrip.m_uNumberOfVertices;

		xProcessedStrip.m_uNumVertices = uNumVertices;
		xProcessedStrip.m_pxVertices = new Tools_SmoothSkin_Vertex[ uNumVertices ];

		if( !xProcessedStrip.m_pxVertices )
		{
			Asura_Assert( false, "Unable to allocate vertex array for skin processor." );
			return false;
		}

		for( u_int v = 0; v < uNumVertices; ++v )
		{
			const Tools_HierarchyProcessor_MultiMaterial_NormalMapped_Vertex& xSrcVtx =
				xSrcStrip.m_pxVertices[v];

			Tools_SmoothSkin_Vertex& xDstVtx = xProcessedStrip.m_pxVertices[v];

			// Hierarchy verts weighted to just one bone each
			const short ausBoneIndices[4] = { xSrcVtx.m_usBoneIndex, 0, 0, 0 };
			const float afBoneWeights[4] = { 1.0f, 0.0f, 0.0f, 0.0f };

			xDstVtx.Set( 0, xSrcVtx.m_xPosition, xSrcVtx.m_xNormal, xSrcVtx.m_fU0, xSrcVtx.m_fV0,
				ausBoneIndices, afBoneWeights );

			xDstVtx.SetTangentAndBinormal( xSrcVtx.m_xTangent, xSrcVtx.m_xBinormal );

			xDstVtx.SetDualPass( Asura_Vector_2( xSrcVtx.m_fU1, xSrcVtx.m_fV1 ), xSrcVtx.m_fAlpha );
		}

		m_uTotalTriangles += xSrcStrip.m_uNumberOfTriangles;
	}

	return true;

#else
	return false;
#endif
}

bool Tools_SmoothSkin_Processor_Xbox360::OutputToChunk( HANDLE pvFile ) const
{
	const Asura_Hierarchy_Skin* pxInputSkin = m_pxSrcProcessor->GetInputSkin();

	u_int uResourceFileSize;

	// Calculate total resource file size.
	{
		uResourceFileSize = 0;

		// General skin header.
		uResourceFileSize += sizeof(Asura_Hash_ID) // Name hash ID.
						  +  sizeof(u_int)         // Material count.
						  +  sizeof(u_int)         // Total vertex count.
						  +  sizeof(u_int)         // Total index count.
						  +  sizeof(u_int)			// Total triangle count.
						  +  sizeof(u_int);         // Total bonemap count.

		// Material headers.
		uResourceFileSize += m_uNumProcessedStrips * ( 
		#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
													sizeof(Asura_Hash_ID)*2   // Material response IDs.
		#else
													sizeof(int)   // Material ID.
		#endif
													 + sizeof(u_int) // Index count.
													 + sizeof(int)   // Element ID.
													 + sizeof(int)   // Bonemap ID.
													 );

		// Vertex data.
		uResourceFileSize += m_uTotalVertices * ( sizeof(float) * 3            // Position. 12
												+ sizeof(Asura_HalfFloat) * 2  // Texture coordinates. 4
												+ sizeof(Asura_HalfFloat) * 2  // Texture coordinates. 4
												+ sizeof(float)				   // Alpha 4
												+ sizeof(u_char) * 4           // Bone indices. 4
												+ sizeof(Asura_HalfFloat) * 4  // Bone weights. 8
												+ sizeof(u_int) * 3            // Normal, tangent, binormal. 12
												);
		for(u_int uBoneMap = 0; uBoneMap < m_pxSrcProcessor->GetNumBoneMaps(); ++uBoneMap)
		{
			const Tools_SmoothSkin_BoneMap *pxBoneMap = m_pxSrcProcessor->GetBoneMap(uBoneMap);

			uResourceFileSize += sizeof(u_int) + sizeof(u_int) * pxBoneMap->GetNumEntries();
		}

		// Index data.
		uResourceFileSize += m_uTotalIndices * sizeof(u_short);
	}

	// Total chunk size is therefore...
	u_int uChunkSize = sizeof(Asura_Chunk_ResourceFile)
					 + pxInputSkin->Name.GetSizeInInts() * sizeof(int)
					 + uResourceFileSize;

	u_char* pucData = new u_char[uChunkSize];
	Asura_Chunk_Memory_Stream xStream( uChunkSize, pucData );
	xStream.SetSwapEndian( Tools_Level_Export::ShouldExportBigEndian() );

	// Chunk header.
	{
		Asura_Chunk_ResourceFile xChunkHdr;

		xChunkHdr.Create( uChunkSize,
						  0,
						  ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
						  ASURA_RESOURCEFILE_TYPE_XBOX360_CHARACTER,
						  uResourceFileSize,
						  iASURA_CHUNK_XBOX360_CHARACTER_VERSION);


		xStream.Write( sizeof(xChunkHdr), &xChunkHdr );		
	}

	// Resource file name.
	xStream << pxInputSkin->Name;

	// Skin header.
	xStream << pxInputSkin->m_uNameHashID;
	xStream << m_uNumProcessedStrips;
	xStream << m_uTotalVertices;
	xStream << m_uTotalIndices;
	xStream << m_uTotalTriangles;
	xStream << m_pxSrcProcessor->GetNumBoneMaps();

	// Material headers.
	for( u_int uMaterial = 0; uMaterial < m_uNumProcessedStrips; ++uMaterial)
	{
		#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
		xStream << m_pxProcessedStrips[uMaterial].m_auMaterialResponseHashID[0];
		xStream << m_pxProcessedStrips[uMaterial].m_auMaterialResponseHashID[1];
		#else
		xStream << m_pxProcessedStrips[uMaterial].m_uMaterialID;
		#endif
		xStream << m_pxProcessedStrips[uMaterial].m_uNumIndices;
		xStream << m_pxProcessedStrips[uMaterial].m_iElementID;
		xStream << m_pxProcessedStrips[uMaterial].m_uBoneMapID;

	}

	// Vertex data.
	for( u_int uMaterial = 0; uMaterial < m_uNumProcessedStrips; ++uMaterial)
	{
		const ProcessedStrip& xProcessedStrip = m_pxProcessedStrips[uMaterial];
		for( u_int uVtx = 0; uVtx < xProcessedStrip.m_uNumVertices; ++uVtx )
		{
			WriteVtxToStream( xProcessedStrip.m_pxVertices[uVtx], xStream );
		}
	}

	// Index data.
	{
		u_short usRestartIndex;
		Tools_MeshRestartIndex::IsValidForCurrentPlatform( usRestartIndex );

		u_int uIndexOffset = 0;
		for( u_int uMaterial = 0; uMaterial < m_uNumProcessedStrips; ++uMaterial)
		{
			const ProcessedStrip& xProcessedStrip = m_pxProcessedStrips[uMaterial];
			for( u_int u = 0; u < xProcessedStrip.m_uNumIndices; ++u )
			{
				u_short usNextIdx = xProcessedStrip.m_pusIndices[u];

				// Offset into cumulative vertex buffer as we go.
				if( usNextIdx != usRestartIndex ) { usNextIdx = static_cast< u_short >( usNextIdx + uIndexOffset ); }

				xStream << usNextIdx;
			}

			uIndexOffset += xProcessedStrip.m_uNumVertices;
		}
	}

	for(u_int uBoneMap = 0; uBoneMap < m_pxSrcProcessor->GetNumBoneMaps(); ++uBoneMap)
	{
		const Tools_SmoothSkin_BoneMap *pxBoneMap = m_pxSrcProcessor->GetBoneMap(uBoneMap);

		xStream << pxBoneMap->GetNumEntries();
		for( u_int u = 0; u < pxBoneMap->GetNumEntries(); ++u )
		{
			xStream << pxBoneMap->GetBone(u);
		}
	}


	// Check we wrote the expected amount.
	Asura_Assert( xStream.GetPosition() == uChunkSize, "" );

	// Write it all out.
	DWORD uBytesWritten;
	bool bOK = ( WriteFile(pvFile, pucData, uChunkSize, &uBytesWritten, NULL)
				 &&
				 uBytesWritten == uChunkSize );

	delete[] pucData;

	return bOK;
}

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

static u_int PackVector( const Asura_Vector_3& xVec )
{
	return (Asura_Maths::FloatToInt( xVec.x * 511.0f ) <<  0)
		 + (Asura_Maths::FloatToInt( xVec.y * 511.0f ) << 10)
		 + (Asura_Maths::FloatToInt( xVec.z * 511.0f ) << 20);
}

#endif

bool Tools_SmoothSkin_Processor_Xbox360::OutputToChunk( Asura_Chunk_Stream& xStream ) const
{
#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

	xStream.WriteResourceChunk_Start( ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC,
		ASURA_RESOURCEFILE_TYPE_XBOX360_SKIN,
		Asura_Chunk_ResourceFile_Xbox360_Skin::iCURRENT_VERSION, 0, m_szSrcName );

	xStream << m_uNumProcessedStrips;
	xStream << m_uTotalVertices;
	xStream << m_uTotalIndices;
	xStream << m_uTotalTriangles;

	// Strip headers
	for( u_int u = 0; u < m_uNumProcessedStrips; ++u )
	{
		const ProcessedStrip& xStrip = m_pxProcessedStrips[u];

		const Asura_Hash_ID auMaterials[2] =
			{ xStrip.m_auMaterialResponseHashID[0], xStrip.m_auMaterialResponseHashID[1] };

		Tools_Level_Export::AddMRPShaders( auMaterials[0], auMaterials[1], true );

		xStream << auMaterials[0] << auMaterials[1];

		xStream << xStrip.m_uNumIndices;
		xStream << xStrip.m_iElementID;

		const u_int uMaxWeights = xStrip.GetMaxWeights();
		xStream << uMaxWeights;
	}

	u_int uTotalVert = 0;

	// Vertex data
	for( u_int u = 0; u < m_uNumProcessedStrips; ++u )
	{
		const ProcessedStrip& xStrip = m_pxProcessedStrips[u];

		for( u_int v = 0; v < xStrip.m_uNumVertices; ++v )
		{
			//WriteVtxToStream( xStrip.m_pxVertices[v], xStream );

			const Tools_SmoothSkin_Vertex& xVtx = xStrip.m_pxVertices[v];

			// Position
			xStream << Asura_HalfFloat( xVtx.GetPos().x ) << Asura_HalfFloat( xVtx.GetPos().y );
			xStream << Asura_HalfFloat( xVtx.GetPos().z ) << Asura_HalfFloat( 1.0f );

			// Blend indices

			const u_char* const pucIndices = xVtx.GetBoneIndices();

			const u_int uIndices = pucIndices[0] + (pucIndices[1] << 8) + (pucIndices[2] << 16)
				+ (pucIndices[3] << 24);

			xStream << uIndices;

			// Blend weights

			const float* const pfWeights = xVtx.GetBoneWeights();

			const u_int uWeights = (Asura_Maths::FloatToInt( pfWeights[0] * 255.0f ) <<  0)
								 + (Asura_Maths::FloatToInt( pfWeights[1] * 255.0f ) <<  8)
								 + (Asura_Maths::FloatToInt( pfWeights[2] * 255.0f ) << 16)
								 + (Asura_Maths::FloatToInt( pfWeights[3] * 255.0f ) << 24);

			xStream << uWeights;

			// Tangent, binormal, normal
			xStream << PackVector( xVtx.GetTangent() );
			xStream << PackVector( xVtx.GetBinormal() );
			xStream << PackVector( xVtx.GetNormal() );

			// UV pair
			xStream << Asura_HalfFloat( xVtx.GetU() ) << Asura_HalfFloat( xVtx.GetV() );
			xStream << Asura_HalfFloat( xVtx.GetU2() ) << Asura_HalfFloat( xVtx.GetV2() );

			// Dual texture lerp factor
			xStream << xVtx.GetAlpha();

			++uTotalVert;
		}
	}

	// Index data
	{
		u_short usRestartIndex;
		Tools_MeshRestartIndex::IsValidForCurrentPlatform( usRestartIndex );

		// Within each processed strip, indices start at 0. We want one single VB/IB in export
		u_int uIndexOffset = 0;

		for( u_int u = 0; u < m_uNumProcessedStrips; ++u )
		{
			const ProcessedStrip& xStrip = m_pxProcessedStrips[u];

			for( u_int v = 0; v < xStrip.m_uNumIndices; ++v )
			{
				u_short usNextIdx = xStrip.m_pusIndices[v];

				// Offset into cumulative vertex buffer as we go
				if( usNextIdx != usRestartIndex )
				{
					usNextIdx = static_cast< u_short >( usNextIdx + uIndexOffset );
				}

				xStream << usNextIdx;
			}

			uIndexOffset += xStrip.m_uNumVertices;
		}
	}

	xStream.WriteResourceChunk_End();

	return true;

#else
	return false;
#endif
}

void Tools_SmoothSkin_Processor_Xbox360::WriteVtxToStream( const Tools_SmoothSkin_Vertex& xVtx, Asura_Chunk_Stream& xStream ) const
{
    #define WRITE_FLOAT_AS_HALF( fValue ) \
    { \
        Asura_HalfFloat xHalfVersion( fValue ); \
        xStream << xHalfVersion; \
    }

	#define WRITE_PACKED_BYTES( pucValues ) \
	{ \
		const u_int uIntVersion = pucValues[0] + (pucValues[1] << 8) + (pucValues[2] << 16) + (pucValues[3] << 24); \
		xStream << uIntVersion; \
	}

	#define WRITE_NORMALISED_VECTOR( fX, fY, fZ )\
	{ \
		const u_int uIntVersion = (static_cast< u_int >( fX * 511.0f ) <<  0) \
								+ (static_cast< u_int >( fY * 511.0f ) << 10) \
								+ (static_cast< u_int >( fZ * 511.0f ) << 20); \
		xStream << uIntVersion; \
	}

    const Asura_Vector_3& xPos = xVtx.GetPos();
    xStream << xPos.x;
    xStream << xPos.y;
    xStream << xPos.z;

    WRITE_FLOAT_AS_HALF( xVtx.GetU() );
    WRITE_FLOAT_AS_HALF( xVtx.GetV() );
	WRITE_FLOAT_AS_HALF( xVtx.GetU2() );
	WRITE_FLOAT_AS_HALF( xVtx.GetV2() );
	xStream << xVtx.GetAlpha();

    WRITE_PACKED_BYTES( xVtx.GetBoneIndices() );

    const float* pfBoneWeights = xVtx.GetBoneWeights();
    WRITE_FLOAT_AS_HALF( pfBoneWeights[0] );
    WRITE_FLOAT_AS_HALF( pfBoneWeights[1] );
    WRITE_FLOAT_AS_HALF( pfBoneWeights[2] );
    WRITE_FLOAT_AS_HALF( pfBoneWeights[3] );

    const Asura_Vector_3& xNormal = xVtx.GetNormal();
    WRITE_NORMALISED_VECTOR( xNormal.x, xNormal.y, xNormal.z );

    const Asura_Vector_3& xTangent = xVtx.GetTangent();
    WRITE_NORMALISED_VECTOR( xTangent.x, xTangent.y, xTangent.z );

    const Asura_Vector_3& xBinormal = xVtx.GetBinormal();
	WRITE_NORMALISED_VECTOR( xBinormal.x, xBinormal.y, xBinormal.z );

    #undef WRITE_FLOAT_AS_HALF
	#undef WRITE_PACKED_BYTES
	#undef WRITE_NORMALISED_VECTOR
}
