//
// Asura_Chunk_Hierarchy.cpp
//
// Created by Kevin Lea - 12/06/2001 11:18:19
// 
// Copyright (C) 2001 Rebellion, All Rights Reserved.
//

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "ChunkSystem/Asura_Chunk_Hierarchy.h"

//  Engine independent.
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "ChunkSystem/Asura_Chunk_Loaders.h"
#include "ChunkSystem/Asura_Chunk_Macros.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Memory/Asura_ResourceAllocator.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Mesh/Asura_CollisionMesh.h"

#ifdef ASURA_TOOLS
#include "PC/FileSystem/Tools_FileSystem.h"
#include "Load&Save/Tools_AsuraFileContents.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Mesh/Asura_Mesh.h"
#include "Mesh/Asura_Mesh_System.h"
#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
#include "Misc/Tools_Platform.h"
#endif
#endif

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


#ifdef ASURA_TOOLS
void Asura_Chunk_Hierarchy_Anim::Create( const Asura_Hierarchy_Anim* pxAnim, class Asura_SaveBlock* pxSaveBlock)
{
	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create	(pxAnim,xStream);
}

void Asura_Chunk_Hierarchy_Anim::Create( const Asura_Hierarchy_Anim* pxAnim , char* pcChunkBuffer , int iChunkSize)
{
	Asura_Chunk_ChunkBuffer_Stream xStream(pcChunkBuffer);
	Create(pxAnim, xStream);

	Asura_Assert(xStream.GetPosition() == (u_int)iChunkSize, "Wrong chunk size");
}

int Asura_Chunk_Hierarchy_Anim::GetChunkBufferSize( const Asura_Hierarchy_Anim* pxAnim)
{
	Asura_Chunk_Dummy_Stream xStream;
	Create(pxAnim, xStream);

	return xStream.GetPosition();
}


void Asura_Chunk_Hierarchy_Anim::Create( const Asura_Hierarchy_Anim* pxAnim , Asura_Chunk_Stream& xStream)
{
	u_int uChunkFlags = 0;
	int iVersion = ASURA_CHUNK_HIERARCHY_ANIM_VERSION;
	
	//Determine the chunk flags used ,and which version this will be saved out as
	bool bIncludeHierarchyData = (pxAnim->m_piParentBones && pxAnim->m_pxBoneNames && !Tools_Level_Export::IsExport());
	bool bIncludeSkinExportTransforms = pxAnim->m_pxSkinExportTransform && !Tools_Level_Export::IsExport(); 
	bool bIncludeBoneRestPose = pxAnim->m_pxBoneRestPose && !Tools_Level_Export::IsExport();
	bool bIncludeBoneDeltaPose = pxAnim->m_pxBoneDeltaPose && !Tools_Level_Export::IsExport();
	bool bSaveOldSoundVersion = false;

	if(iVersion == 16)
	{
		//see if we can get away with the old version for the sound data
		//(makes life easier with not having to sync up updates with max plugins and various versions of tools)
		bool bNeedNewVersion = false;
		for(int i=0;i<pxAnim->m_iNumberOfAnimationSounds;i++)
		{
			if(pxAnim->m_pxAnimationSounds[i].m_uFlags != 0)
			{
				bNeedNewVersion = true;
				break;
			}
		}
		if(!bNeedNewVersion)
		{
			bSaveOldSoundVersion = true;
			iVersion = 15;
		}
	}

	if(pxAnim->BoneLengths) uChunkFlags |= ChunkFlag_HasBoneLengths;
	if(pxAnim->IsDeltaAnimation) uChunkFlags |= ChunkFlag_DeltaAnimation;
	if(pxAnim->m_bDontRemoveKeyframes) uChunkFlags |= ChunkFlag_DontRemoveKeyframes;
	if(pxAnim->m_bDontCompressKeyframes) uChunkFlags |= ChunkFlag_DontCompressKeyframes;
	if(pxAnim->GetBB()) uChunkFlags |= ChunkFlag_HasBoundingBox;
	if(pxAnim->m_bSharedAnimDeltaPos) uChunkFlags |= ChunkFlag_SharedAnimDeltaPos;
	if(pxAnim->m_bSharedAnimDeltaOrient) uChunkFlags |= ChunkFlag_SharedAnimDeltaOrient;
	if(pxAnim->m_bHasFacialAnimation) uChunkFlags |= ChunkFlag_HasFacialAnimation;
	//Version 10 - HasMovementData flag
	if(pxAnim->HasMovementData()) uChunkFlags |= ChunkFlag_HasMovementData;
	//Version 11 - HasHierarchyData flag
	if(bIncludeHierarchyData) uChunkFlags |= ChunkFlag_HasHierarchyData;
	//Version 12 - HasSkinExportTransforms flag
	if(bIncludeSkinExportTransforms) uChunkFlags |= ChunkFlag_HasSkinExportTransforms;
	//Version 13 - HasBoneRestPose flag
	if(bIncludeBoneRestPose) uChunkFlags |= ChunkFlag_HasBoneRestPose;
	//Version 14 - HasBoneDeltaPose flag
	if(bIncludeBoneDeltaPose) uChunkFlags |= ChunkFlag_HasBoneDeltaPose;


	//start on writing the chunk
	xStream.WriteChunk_Start(ASURA_CHUNK_HIERARCHY_ANIM, iVersion, uChunkFlags);



	xStream << pxAnim->NumberOfBones;
	xStream << pxAnim->Duration;
	xStream << pxAnim->Name;


	int iBoneArraySize = pxAnim->GetBoneArraySize();
	
	for(int nLp=0;nLp<iBoneArraySize;nLp++)
	{
		xStream << pxAnim->BoneAnims[ nLp ].NumberOfKeyFrames;
		xStream << (unsigned char)pxAnim->BoneAnims[nLp].ConstantOffset;

		//write quat keyframes
		for (int f=0; f<pxAnim->BoneAnims[ nLp ].NumberOfKeyFrames; f++)
		{
			xStream << pxAnim->BoneAnims[nLp].KeyFrames[f].Quat;
			xStream << pxAnim->BoneAnims[nLp].KeyFrames[f].Time;
		}

		//write position keyframes
		int numberOfPositionKeyFrames = pxAnim->BoneAnims[nLp].ConstantOffset ? 1 : pxAnim->BoneAnims[ nLp ].NumberOfKeyFrames;
		for (int f=0;f<numberOfPositionKeyFrames; f++)
		{
			xStream << pxAnim->BoneAnims[nLp].Positions[f];
		}
	}

	xStream << pxAnim->Speed;

	xStream << pxAnim->Direction;

	//version 4 , bone lengths
	if(pxAnim->BoneLengths)
	{
		for(int b=0;b<pxAnim->NumberOfBones;b++)
		{
			xStream << pxAnim->BoneLengths[b];
		}
	}

	//version 5 , Animation flags
	xStream << pxAnim->NumberOfAnimationFlags;

	for(int i=0;i<pxAnim->NumberOfAnimationFlags;i++)
	{
		xStream << pxAnim->AnimationFlags[i].Flag;
		xStream << pxAnim->AnimationFlags[i].StartTime;
		xStream << pxAnim->AnimationFlags[i].EndTime;
	}

	//version 7 , bounding box
	if(pxAnim->GetBB())
	{
		Asura_Bounding_Box xBB = *pxAnim->GetBB();
		xStream << xBB;
		
	}

	//version 8 - loop point
	xStream << pxAnim->GetLoopPoint();

	//version 9 , Animation sounds
	xStream << pxAnim->m_iNumberOfAnimationSounds;

	for(int i=0;i<pxAnim->m_iNumberOfAnimationSounds;i++)
	{
		xStream << pxAnim->m_pxAnimationSounds[i].m_fStartTime;
		xStream << pxAnim->m_pxAnimationSounds[i].m_iAnimSoundID;
		xStream << pxAnim->m_pxAnimationSounds[i].m_fEndTime;
		if(!bSaveOldSoundVersion)
		{
			xStream << pxAnim->m_pxAnimationSounds[i].m_uFlags;
		}
	}

	//Version 11 , Hierarchy Data
	if(bIncludeHierarchyData)
	{
		for(int i=0;i<pxAnim->NumberOfBones;i++)
		{
			xStream << pxAnim->m_piParentBones[i];
		}
		for(int i=0;i<pxAnim->NumberOfBones;i++)
		{
			xStream << pxAnim->m_pxBoneNames[i];
		}
	}

	//Version 12 , skin export transforms
	if(bIncludeSkinExportTransforms)
	{
		for(int i=0;i<pxAnim->NumberOfBones;i++)
		{
			xStream << pxAnim->m_pxSkinExportTransform[i];
		}
	}

	//Version 13 , skin rest pose
	if(bIncludeBoneRestPose)
	{
		for(int i=0;i<pxAnim->NumberOfBones;i++)
		{
			xStream << pxAnim->m_pxBoneRestPose[i].Position;
			xStream << pxAnim->m_pxBoneRestPose[i].Quat;
		}
	}

	//Version 14 , delta pose
	if(bIncludeBoneDeltaPose)
	{
		for(int i=0;i<pxAnim->NumberOfBones;i++)
		{
			xStream << pxAnim->m_pxBoneDeltaPose[i].Position;
			xStream << pxAnim->m_pxBoneDeltaPose[i].Quat;
		}
	}

	xStream.WriteChunk_End();
}


#endif//#ifdef ASURA_TOOLS

struct HierarchyAnimChunk_V5
{
	char szAnimName[128];
	char szSkinName[128];
	int iNumberOfBones;
	float fDuration;
};


#if ASURA_ALLOW_UNCOMPRESSED_KEYFRAMES
bool Asura_Chunk_Hierarchy_Anim::Process(Asura_Handle iFileHandle) const
{
	#ifdef ASURA_TOOLS
	const char* szFilename = Asura_FileAccess::GetFilename( iFileHandle );
	#else
	const char* szFilename = 0;
	#endif

	Asura_Chunk_File_Stream	xStream(iFileHandle);
	return Process(xStream, szFilename, NULL);
}

bool Asura_Chunk_Hierarchy_Anim::Process(Asura_Chunk_Stream& xStream, const char* szFilename, Asura_Hierarchy_Anim* pxPreAllocatedAnim) const
{

	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		Asura_Assert(Version<=ASURA_CHUNK_HIERARCHY_ANIM_VERSION,"Unknown chunk version");

		Asura_Hierarchy_Anim* pxAnim;

		int iNumberOfBones;
		float fDuration;
		if(Version>=6)
		{
			xStream >> iNumberOfBones;
			xStream >> fDuration;

			char acAnimName[256];
			xStream.ReadString(acAnimName, sizeof(acAnimName));

#if ASURA_LOG_FILE_CONTENTS
			Asura_SimpleLogfileOutput((ASURA_FILE_CONTENTS_LOGFILE,"Animation : %s\n",acAnimName));
#endif

			// JAT 19/04/07 - check the filename matches the anim name in tools only and loading an ANIM file
			#ifdef ASURA_TOOLS
			if ( szFilename && Tools_AsuraFileContents::GetAsuraFileType(szFilename) == AsuraFile_Anim)
			{
				// Get anim name from the filename
				Asura_String xAnimName;
				Asura_String xFileName( szFilename );
				xFileName.SplitFilename(0,&xAnimName,0);
				if ( stricmp( xAnimName.GetString(), acAnimName ) != 0 )
				{
					Asura_Assertf( false, ( "Loading Animation File '%s'\nName '%s' doesn't match animation name '%s'", szFilename, xAnimName.GetString(), acAnimName ) );
				}
			}
			#endif // #ifdef ASURA_TOOLS

			if(pxPreAllocatedAnim)
			{
				pxAnim = pxPreAllocatedAnim;
			}
			else if( !Asura_Animation_System::NewAnimation(acAnimName, pxAnim) )
			{
#if !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
				Asura_Assertf( false, ( "Loading File '%s'\nAnim with name '%s' already loaded.\nCheck they are the same anim.", szFilename, acAnimName) );
				Asura_OutputToDebugger_Retail(( "WARNING: Loading File '%s' Anim '%s' already loaded.\n", szFilename ? szFilename : "unknown", acAnimName ));
#endif	// !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)

				Asura_ResourcePopType();
				return false;
			}

			pxAnim->Name = acAnimName;
			pxAnim->m_uHashID = Asura_GetHashID(acAnimName);

			#ifdef ASURA_TOOLS
			if(szFilename)
			{
				Tools_FileSystem::GetModifiedTime(szFilename,&pxAnim->m_xModifiedTime);
			
				#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
				const char * szRelFilename = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(szFilename);
				pxAnim->m_iPlatform = Tools_Platform::GetPlatformFromPath(szRelFilename);
				#endif
			}
			#endif
		}
		else
		{
			//read in what used to be in the main chunk structure
			HierarchyAnimChunk_V5 xData;
			xStream.Read(sizeof(xData), &xData);
			iNumberOfBones = xData.iNumberOfBones;
			fDuration = xData.fDuration;

#if ASURA_LOG_FILE_CONTENTS
			Asura_SimpleLogfileOutput((ASURA_FILE_CONTENTS_LOGFILE,"Animation : %s\n",xData.szAnimName));
#endif

			if(pxPreAllocatedAnim)
			{
				pxAnim = pxPreAllocatedAnim;
			}
			else if( !Asura_Animation_System::NewAnimation(xData.szAnimName, pxAnim) )
			{
#ifdef ASURA_ASSERT
				char acBuffer[256];
				Asura_PrintfToBfr(acBuffer, "Anim with name \"%s\" already loaded. Check they are the same anim.\n", 256, true, xData.szAnimName);
				Asura_Assert(false, acBuffer);
#endif
				Asura_OutputToDebugger_Retail(( "WARNING: Anim \"%s\" already loaded.\n", xData.szAnimName ));

				Asura_ResourcePopType();
				return false;
			}

			pxAnim->Name = xData.szAnimName;
			pxAnim->m_uHashID = Asura_GetHashID(xData.szAnimName);
		}

		pxAnim->Initialise(iNumberOfBones,fDuration,(Flags & ChunkFlag_HasMovementData) ? true : false);


		int iBoneArraySize = pxAnim->GetBoneArraySize();
		for(int i=0; i<iBoneArraySize; i++)
		{
			int iNumberOfFrames;

			xStream >> iNumberOfFrames;

			if(Version<=2)
			{
				//loader for old format , different layout so elements need to be read in one at a time
				pxAnim->BoneAnims[i].Initialise(iNumberOfFrames,false);
				for(int j=0;j<iNumberOfFrames;j++)
				{
					xStream >> pxAnim->BoneAnims[i].Positions[j];
					xStream >> pxAnim->BoneAnims[i].KeyFrames[j].Quat;
					xStream >> pxAnim->BoneAnims[i].KeyFrames[j].Time;
				}
			}
			else
			{
				//new loader
				unsigned char ucConstantOffset;
				xStream >> ucConstantOffset;

				if(iNumberOfFrames)
				{
					pxAnim->BoneAnims[i].Initialise(iNumberOfFrames,ucConstantOffset!=0);

					//read orientation/time keyframes
					xStream.Read(sizeof(Asura_Hierarchy_Bone_QuatKeyFrame)*iNumberOfFrames,pxAnim->BoneAnims[i].KeyFrames);

					//read Positions
					int iNumberOfPositionFrames = ucConstantOffset ? 1 : iNumberOfFrames;
					xStream.Read(sizeof(Asura_Vector_3)*iNumberOfPositionFrames, pxAnim->BoneAnims[i].Positions);
				}

			}
		}


		if(Version>=2)
		{
			//read in speed and direction
			xStream >> pxAnim->Speed;
			xStream >> pxAnim->Direction;
		}

		if(Flags & ChunkFlag_HasBoneLengths)
		{
			Asura_ResourceAllocateArray(pxAnim->BoneLengths, pxAnim->NumberOfBones);
			xStream.Read(sizeof(float)*pxAnim->NumberOfBones, pxAnim->BoneLengths);

			pxAnim->DetermineMovementScaleBone();
		}

		if(Version>=5)
		{
			xStream >> pxAnim->NumberOfAnimationFlags;
			if(pxAnim->NumberOfAnimationFlags)
			{
				Asura_ResourceAllocateArray(pxAnim->AnimationFlags, pxAnim->NumberOfAnimationFlags);
				xStream.Read(sizeof(Asura_Animation_Flag_Interval)*pxAnim->NumberOfAnimationFlags, pxAnim->AnimationFlags);
			}
		}

		if(Flags & ChunkFlag_DeltaAnimation) pxAnim->IsDeltaAnimation = true;
		if(Flags & ChunkFlag_SharedAnimDeltaPos) pxAnim->m_bSharedAnimDeltaPos = true;
		if(Flags & ChunkFlag_SharedAnimDeltaOrient) pxAnim->m_bSharedAnimDeltaOrient = true;
		if(Flags & ChunkFlag_HasFacialAnimation) pxAnim->m_bHasFacialAnimation = true;
#ifdef ASURA_TOOLS
		if(Flags & ChunkFlag_DontRemoveKeyframes) pxAnim->m_bDontRemoveKeyframes = true;
		if(Flags & ChunkFlag_DontCompressKeyframes) pxAnim->m_bDontCompressKeyframes = true;
#endif

		if(Version>=7)
		{
			//load bounding box
			if(Flags & ChunkFlag_HasBoundingBox)
			{
				Asura_Bounding_Box xBB;
				xStream >> xBB;
				pxAnim->SetBB(&xBB);
			}
		}
		if(Version>=8)
		{
			float fLoop;
			xStream >> fLoop;
			pxAnim->SetLoopPoint(fLoop);
		}

		if(Version>=9)
		{
			xStream >> pxAnim->m_iNumberOfAnimationSounds;
			if(pxAnim->m_iNumberOfAnimationSounds)
			{
				Asura_ResourceAllocateArray(pxAnim->m_pxAnimationSounds, pxAnim->m_iNumberOfAnimationSounds);

				Asura_Animation_Sound* pxSound = pxAnim->m_pxAnimationSounds;
				for(int i=0; i<pxAnim->m_iNumberOfAnimationSounds; i++,pxSound++)
				{
					xStream >> pxSound->m_fStartTime;
					xStream >> pxSound->m_iAnimSoundID;
					if(Version>=15)
					{
						xStream >> pxSound->m_fEndTime;
					}
					else
					{
						pxSound->m_fEndTime = pxSound->m_fStartTime;
					}	
					if(Version>=16)
					{
						xStream >> pxSound->m_uFlags;
					}
					else
					{
						pxSound->m_uFlags = 0;
					}	
				}
			}
		}

		if(Version>=11)
		{
			if(Flags & ChunkFlag_HasHierarchyData)
			{
#ifdef ASURA_TOOLS
				Asura_ResourceAllocateArray(pxAnim->m_piParentBones, pxAnim->NumberOfBones);
				xStream.Read(sizeof(int)*pxAnim->NumberOfBones, pxAnim->m_piParentBones);

				Asura_ResourceAllocateArray(pxAnim->m_pxBoneNames, pxAnim->NumberOfBones);
				for(int i=0;i<pxAnim->NumberOfBones;i++)
				{
					xStream >> pxAnim->m_pxBoneNames[i];
				}
#else
				//skip parent bone array
				xStream.Skip(sizeof(int)*pxAnim->NumberOfBones);
				//skip bone names
				for(int i=0;i<pxAnim->NumberOfBones;i++)
				{
					xStream.SkipString();
				}
#endif

			}
		}

		if(Version>=12)
		{
			if(Flags & ChunkFlag_HasSkinExportTransforms)
			{
				#ifdef ASURA_TOOLS
				Asura_ResourceAllocateArray(pxAnim->m_pxSkinExportTransform, pxAnim->NumberOfBones);
				xStream.Read(sizeof(Asura_Quat)*pxAnim->NumberOfBones, pxAnim->m_pxSkinExportTransform);

				#else
				//skip skin export transforms
				xStream.Skip(sizeof(Asura_Quat)*pxAnim->NumberOfBones);
				#endif

			}
		}

		if(Version>=13)
		{
			if(Flags & ChunkFlag_HasBoneRestPose)
			{
				#ifdef ASURA_TOOLS
				Asura_ResourceAllocateArray(pxAnim->m_pxBoneRestPose, pxAnim->NumberOfBones);
				xStream.Read(sizeof(Asura_Hierarchy_Bone_RestFrame)*pxAnim->NumberOfBones, pxAnim->m_pxBoneRestPose);

				#else
				//skip skin rest pose
				xStream.Skip(sizeof(Asura_Hierarchy_Bone_RestFrame)*pxAnim->NumberOfBones);
				#endif

			}
		}

		if(Version>=14)
		{
			if(Flags & ChunkFlag_HasBoneDeltaPose)
			{
				#ifdef ASURA_TOOLS
				Asura_ResourceAllocateArray(pxAnim->m_pxBoneDeltaPose, pxAnim->NumberOfBones);
				xStream.Read(sizeof(Asura_Hierarchy_Bone_RestFrame)*pxAnim->NumberOfBones, pxAnim->m_pxBoneDeltaPose);

				#else
				//skip delta pose
				xStream.Skip(sizeof(Asura_Hierarchy_Bone_RestFrame)*pxAnim->NumberOfBones);
				#endif

			}
		}
	}
	Asura_ResourcePopType();
	return true;
}
#else // #ifdef ASURA_ALLOW_UNCOMPRESSED_KEYFRAMES
bool Asura_Chunk_Hierarchy_Anim::Process(Asura_Handle fileHandle) const
{
	Asura_Assert(false, "loading uncompressed anim - no longer supported.\n");
	return false;
}

bool Asura_Chunk_Hierarchy_Anim::Process(Asura_Chunk_Stream& xStream, const char* szFilename, Asura_Hierarchy_Anim* pxPreAllocatedAnim) const
{
	Asura_Assert(false, "loading uncompressed anim - no longer supported.\n");
	return false;
}

#endif

#ifdef ASURA_TOOLS
Asura_Hierarchy_Anim* Asura_Chunk_Hierarchy_Anim::Process() const
{
	//create a chunk buffer stream
	const char*				pcChunkBuffer	= (const char*)(this+1);
	Asura_Chunk_ChunkBuffer_Stream xStream(pcChunkBuffer);

	//and read in using the main function
	Asura_Hierarchy_Anim*	pxAnim			= new Asura_Hierarchy_Anim;
	if(Process(xStream, NULL, pxAnim))
	{
		return pxAnim;
	}
	else
	{
		delete pxAnim;
		return 0;
	}

}
#endif //ASURA_TOOLS

//////////////////////////////////////////////////////////////////////////////////////////////////
/*----------------------------**
** Asura_Chunk_Hierarchy_Skin **
**----------------------------*/



#ifdef ASURA_TOOLS
void Asura_Chunk_Hierarchy_Skin::Create( Asura_Chunk_Stream& xStream, Asura_Hierarchy_Skin* pxSkin, const List<Asura_Mesh*>* pxMeshList, bool bCreateLODChunks)
{
	u_int uChunkFlags = 0;

	//determine the chunk flags

	bool bCollisionBB = false;
	if(pxSkin->HasCustomCollisionBox())
	{
		bCollisionBB = pxSkin->GetBB() != pxSkin->GetCollisionBB();
	}

	if(bCollisionBB)
	{
		uChunkFlags |= ChunkFlag_HasCollisionBB;
	}

	if(pxSkin->SectionData)
	{
		uChunkFlags |= ChunkFlag_HasSectionArray;
	}

	bool bCollisionMesh = pxSkin->HasCollisionMesh() ? true : false;
	if(bCollisionMesh)
	{
		uChunkFlags |= ChunkFlag_HasCollisionMesh;
	}

	if(pxSkin->m_bManualLOD)
	{
		uChunkFlags |= ChunkFlag_ManualLOD;
	}


	//some data is skipped for level exports
	if(Tools_Level_Export::IsExport())
	{
		uChunkFlags |= ChunkFlag_NoIndependentSkinData;
	}
	else
	{
		if(pxSkin->m_pxInitialRestPose)
		{
			uChunkFlags |= ChunkFlag_HasInitialRestPose;
		}
		if(pxSkin->m_piBoneSortKey)
		{
			uChunkFlags |= ChunkFlag_HasBoneSortKey;
		}
	}


	//start on the chunk
	xStream.WriteChunk_Start(ASURA_CHUNK_HIERARCHY_SKIN, ASURA_CHUNK_HIERARCHY_SKIN_VERSION, uChunkFlags);
	

	xStream << pxSkin->NumberOfVertices;
	xStream << pxSkin->NumberOfBones;

	xStream << pxSkin->Name;
	
	if(!(uChunkFlags & ChunkFlag_NoIndependentSkinData))
	{
		const Asura_Hierarchy_Skin_Vertex* pxSrc = pxSkin->VertexData;
		int iNumberOfVertices = pxSkin->NumberOfVertices;
		while(iNumberOfVertices--)
		{
			xStream << pxSrc->Offset[0];
			xStream << pxSrc->Offset[1];
			xStream << pxSrc->Offset[2];
			xStream << pxSrc->Offset[3];

			xStream << pxSrc->BoneID[0];
			xStream << pxSrc->BoneID[1];
			xStream << pxSrc->BoneID[2];
			xStream << pxSrc->BoneID[3];

			xStream << pxSrc->Weight[0];
			xStream << pxSrc->Weight[1];
			xStream << pxSrc->Weight[2];
			xStream << pxSrc->Weight[3];

			pxSrc++;
		}
	}
	

	
	for (int n=0; n<pxSkin->NumberOfBones; n++)
	{
		xStream << pxSkin->ParentBone[n];
	}
	

	
	for (int n=0; n<pxSkin->NumberOfBones; n++)
	{
		xStream << pxSkin->BoneRestPose[n].Position;
		xStream << pxSkin->BoneRestPose[n].Quat;
	}
	
	
    int* piMaterialIDToIndexConversion = 0;
	
	#ifdef ASURA_MAIN
	if(pxMeshList) //mesh list isn't provided by max exports ,since no conversion is needed. RAR 09/06/2006
	{
    	piMaterialIDToIndexConversion = new int[ASURA_MAX_NO_OF_MATERIALS];
    	Tools_SaveAsura::PrepareMeshMaterialConversionLists( *pxMeshList, piMaterialIDToIndexConversion, 0 );
	}
	#endif

	if(pxSkin->SectionData)
	{
		//save section names
		for(int i=0;i<pxSkin->NumberOfBones;i++)
		{
			Asura_Hierarchy_Section* pxSection = &pxSkin->SectionData[i];
			xStream << pxSection->SectionName;

			if(pxSection->m_pxCollisionMesh)
			{
				pxSection->m_pxCollisionMesh->WriteToChunkStream(xStream, true, piMaterialIDToIndexConversion);
			}
			else
			{
				xStream << (u_int) 0;
			}
		}
	}

	if(bCollisionBB)
	{
		Asura_Bounding_Box xBB = pxSkin->GetCollisionBB();
		xStream << xBB;
	}

	if(bCollisionMesh)
	{
		pxSkin->GetCollisionMesh()->WriteToChunkStream(xStream, true, piMaterialIDToIndexConversion);
	}

	if(!pxSkin->m_piMirroredBone) pxSkin->CalculateMirroredBones();
	for (int n=0; n<pxSkin->NumberOfBones; n++)
	{
		xStream << pxSkin->m_piMirroredBone[n];
	}

	if(uChunkFlags & ChunkFlag_HasInitialRestPose)
	{
		
		for (int n=0; n<pxSkin->NumberOfBones; n++)
		{
			xStream << pxSkin->m_pxInitialRestPose[n].Position;
			xStream << pxSkin->m_pxInitialRestPose[n].Quat;
		}
		
	}

	if(uChunkFlags & ChunkFlag_HasBoneSortKey)
	{
		for(int i=0;i<pxSkin->NumberOfBones;i++)
		{
			xStream << pxSkin->m_piBoneSortKey[i];
		}
	}


    delete [] piMaterialIDToIndexConversion;

	xStream.WriteChunk_End();

	//create chunks for LOD skins
	if(bCreateLODChunks)
	{
		Asura_Hierarchy_Skin* pxLODSkin = pxSkin->m_pxNextLowerSkin;
		while(pxLODSkin)
		{
			Asura_Chunk_Hierarchy_SkinLOD::Create(xStream, pxLODSkin, pxSkin->Name);
			pxLODSkin = pxLODSkin->m_pxNextLowerSkin;
		}
	}
}

void Asura_Chunk_Hierarchy_Skin::Create( Asura_SaveBlock* pxSaveBlock, Asura_Hierarchy_Skin* pxSkin, const List<Asura_Mesh*>* pxMeshList, bool bCreateLODChunks)
{
	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create(xStream, pxSkin, pxMeshList, bCreateLODChunks);
}

#endif//#ifdef ASURA_TOOLS

bool Asura_Chunk_Hierarchy_Skin::Process(Asura_Handle iFileHandle) const
{
	Asura_Chunk_File_Stream	xStream(iFileHandle);
	Asura_Hierarchy_Skin* pxSkin = Process(xStream, 0);

	#ifdef ASURA_TOOLS
	if(pxSkin)
	{
		const char* szFilename = Asura_FileAccess::GetFilename(iFileHandle);
		Tools_FileSystem::GetModifiedTime(szFilename,&pxSkin->m_xModifiedTime);

		#ifdef ASURA_SUPPORT_PLATFORM_HASHED_FILENAMES
		const char * szRelFilename = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(szFilename);
		pxSkin->m_iPlatform = Tools_Platform::GetPlatformFromPath(szRelFilename);
		#endif

		//setup the bounding boxes.
		//outside of tools this should be setup by a later chunk instead
		pxSkin->InitSectionBoundingBoxes();
	}
	#endif

	return pxSkin != 0;
}

Asura_Hierarchy_Skin* Asura_Chunk_Hierarchy_Skin::Process(Asura_Chunk_Stream& xStream, Asura_Hierarchy_Skin* pxPreAllocatedSkin, bool bConvertMaterialIDs /*=true*/) const
{
	Asura_Hierarchy_Skin* pxSkin;

	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		Asura_Assert(Version<=ASURA_CHUNK_HIERARCHY_SKIN_VERSION,"Unknown chunk version");

#ifndef ASURA_TOOLS
		// (MP - 07.06.2004) No longer supporting versions with 8 weights outside
		// of tools.
		Asura_Assert( Version >= 4, "" );
#endif
		
		char szSkinName[100];
		xStream.ReadString(szSkinName,sizeof(szSkinName));


#if ASURA_LOG_FILE_CONTENTS
		if(!pxPreAllocatedSkin)
		{
			Asura_SimpleLogfileOutput((ASURA_FILE_CONTENTS_LOGFILE,"Skin : %s\n",szSkinName));
		}
#endif

		if(pxPreAllocatedSkin)
		{
			//Empty skin already allocated ,so fill in the name
			pxSkin = pxPreAllocatedSkin;
			pxSkin->Name = szSkinName;
			pxSkin->m_uNameHashID = Asura_GetHashID(pxSkin->Name.GetString());

		}
		else
		{

			bool bSuccess = Asura_Animation_System::NewSkin(szSkinName,pxSkin);

			if (!bSuccess)
			{
				Asura_ResourcePopType();
				return 0;
			}
		}



		Asura_Assert( NumberOfBones <= ASURA_MAX_NO_OF_BONES_PER_MODEL, "Fix this model or increase ASURA_MAX_NO_OF_BONES_PER_MODEL for your project. MEMORY WILL NOW BE GETTING TRASHED." );
		pxSkin->Initialise(NumberOfVertices,NumberOfBones);

		// Kevin Lea - 10/08/2001 17:31:58 - extract vertex data
		if( NumberOfVertices )
		{
#ifdef ASURA_TOOLS
			struct Asura_Hierarchy_Skin_Vertex_v3
			{
				Asura_Vector_3 Offset[8];
				int            BoneID[8];
				float          Weight[8];
			};
#endif

			if(Flags & ChunkFlag_NoIndependentSkinData)
			{
				//No independent skin data, which means that this is an exported skin.
				//This is probably bad if being loaded on a build that requires this data (namely tools) , unless
				//loading into a pre-allocated skin ,that won't be in the animation system
				//RAR 03/10/2007
				Asura_Assertf(!Asura_PlatformHierarchy_NeedIndependentSkinData() || pxPreAllocatedSkin,("Skin '%s' doesn't contain the tools vertex data",szSkinName));
			}
			else
			{
				if( Asura_PlatformHierarchy_NeedIndependentSkinData() )
				{

#ifdef ASURA_TOOLS
					if( Version < 4 )
					{

						Asura_Hierarchy_Skin_Vertex_v3 xVtxTmp;         
						for( int i = 0; i < NumberOfVertices; ++i )
						{
							xStream.Read( sizeof(Asura_Hierarchy_Skin_Vertex_v3), &xVtxTmp  );
							for( u_int u = 0; u < 4; ++u )
							{
								pxSkin->VertexData[i].Weight[u] = xVtxTmp.Weight[u];
								if( -1.0f == xVtxTmp.Weight[u] )
								{
									break;
								}

								pxSkin->VertexData[i].Offset[u] = xVtxTmp.Offset[u];
								pxSkin->VertexData[i].BoneID[u] = static_cast<short>( xVtxTmp.BoneID[u] );
							}
						}
					}
					else
#endif
					{
						xStream.Read( NumberOfVertices * sizeof(Asura_Hierarchy_Skin_Vertex), pxSkin->VertexData );
					}

					Asura_Hierarchy_Skin_Vertex* destPtr = pxSkin->VertexData;
					for(int i=0; i<NumberOfVertices; i++)
					{
						// KJL - 01-May-01 17:20:31 - put the most important bone first!
						int mostImportant=0;
						for (int b=1; b<4; b++)
						{
							if (destPtr->Weight[b]==-1.0f) break;
							if (destPtr->Weight[b]>destPtr->Weight[mostImportant])
							{
								mostImportant=b;
							}
						}
						if(mostImportant)
						{
							int tempBoneID = destPtr->BoneID[0];
							float tempWeight = destPtr->Weight[0];
							Asura_Vector_3 tempOffset = destPtr->Offset[0];

							destPtr->BoneID[0] = destPtr->BoneID[mostImportant];
							destPtr->Weight[0] = destPtr->Weight[mostImportant];
							destPtr->Offset[0] = destPtr->Offset[mostImportant];

							destPtr->BoneID[mostImportant] = static_cast<short>( tempBoneID );
							destPtr->Weight[mostImportant] = tempWeight;
							destPtr->Offset[mostImportant] = tempOffset;
						}

						destPtr++;
					}
				}
				else
				{
#ifdef ASURA_TOOLS
					if( Version < 4 )
					{
						// (MP - 12.02.2003) TODO: Remove this once artists have had a
						// reasonable amount of time to update skins.
						xStream.Skip( NumberOfVertices * sizeof(Asura_Hierarchy_Skin_Vertex_v3) );
					}
					else
#endif
					{
						xStream.Skip( NumberOfVertices * sizeof(Asura_Hierarchy_Skin_Vertex) );
					}
				}
			}
		}
		// End if( NumberOfVertices ).

		// Kevin Lea - 13/08/2001 10:32:05 - get bone parent data
		xStream.Read(NumberOfBones*sizeof(int),pxSkin->ParentBone);

		// Kevin Lea - 10/08/2001 17:31:58 - extract bone positional data
		xStream.Read(sizeof(Asura_Hierarchy_Bone_RestFrame)*NumberOfBones, pxSkin->BoneRestPose);

		if(Flags & ChunkFlag_HasSectionArray)
		{
			//allocate the sections
			Asura_ResourceAllocateArray(pxSkin->SectionData, pxSkin->NumberOfBones);
			//and read in the section names
			for(int i=0;i<pxSkin->NumberOfBones;i++)
			{
				char szSectionName[200];
				xStream.ReadString(szSectionName, sizeof(szSectionName));		

#if defined(ASURA_TOOLS) || defined(ASURA_HIERARCHY_SKIN_LOAD_SECTION_NAMES)
				// (MH - 05.06.2003) these section names are used to figure out 
				// body parts for the versatile death anim system in GF2.
				pxSkin->SectionData[i].SectionName = szSectionName;
#endif

#ifdef ASURA_TOOLS
				pxSkin->SectionData[i].Mesh = NULL;
#endif

#ifdef ASURA_TOOLS
				if(Version>=3)
#endif
				{
					//check for collision mesh
					int iSize;
					xStream >> iSize;
					
					if(iSize)
					{
						int iCollisionMeshVersion;
						pxSkin->SectionData[i].m_pxCollisionMesh = new Asura_CollisionMesh;
						pxSkin->SectionData[i].m_pxCollisionMesh->ReadFromChunkStream(xStream, bConvertMaterialIDs, &iCollisionMeshVersion);
						#ifdef ASURA_TOOLS
						//Delete any version 8 collision meshes , to force any that were saved out from Max without
						//the material response IDs to be recalculated.
						//Can only do this for non-skinned models , since the skinned versions aren't automatically
						//generated.
						//RAR 02/09/2008
						if(!pxSkin->IsSkinnedModel() && iCollisionMeshVersion <= 8)
						{
							delete pxSkin->SectionData[i].m_pxCollisionMesh;
							pxSkin->SectionData[i].m_pxCollisionMesh = 0;
						}
						#endif
					}
				}

#ifdef ASURA_TOOLS
				if(!pxSkin->IsSkinnedModel() && !pxSkin->SectionData[i].SectionName.IsBlank())
				{
					//build the mesh name
					Asura_String xMeshName = pxSkin->Name;
					xMeshName += "_";
					xMeshName += pxSkin->SectionData[i].SectionName;
					//and get the mesh
					pxSkin->SectionData[i].Mesh = Asura_Mesh_System::GetMesh(xMeshName);

					Asura_Mesh* pxMesh = pxSkin->SectionData[i].Mesh;
					if(pxMesh && !pxSkin->SectionData[i].m_pxCollisionMesh)
					{
						//generate the collision mesh now
						if(!pxMesh->AreNormalsValid()) pxMesh->CreateNormals();
						if(!pxMesh->BoundingBoxDataValid) pxMesh->CalculateBoundingBox();
						pxSkin->SectionData[i].m_pxCollisionMesh = new Asura_CollisionMesh(pxMesh);

						if(!bConvertMaterialIDs)
						{
							//If bConvertMaterialIDs is false then the caller is expecting the materials to be left
							//as indeces within the collision mesh.
							//This will no longer be the case since the collision mesh has been recalculated from the meshes.
							//This should just happen for exports , and will be entirely irrelevant for material response projects.
							//Resetting the materials is probably the safest thing to do here.
							//RAR 24/03/2009
							#if !defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
							Asura_Assert(0,"Resetting the materials in a skins collision mesh.\nThis may cause problems for the exported skin if anything cared about the materials used by the collision mesh.");
							#endif
							pxSkin->SectionData[i].m_pxCollisionMesh->ResetMaterialIDs();
						}
					}
				}
#endif				
			}
		}

		pxSkin->CalculateRestPose();

		if(Version>=5)
		{
			if(Flags & ChunkFlag_HasCollisionBB)
			{
				Asura_Bounding_Box xBB;
				xStream >> xBB;
				pxSkin->SetCollisionBB(xBB);
			}
		}

		if(Version>=6)
		{
			if(Flags & ChunkFlag_HasCollisionMesh)
			{
				//read in the size of the collision mesh data
				int iCollisionChunkSize = 0;
				xStream >> iCollisionChunkSize;

				//create the collision mesh 
				Asura_CollisionMesh* pxCollMesh = new Asura_CollisionMesh;
				pxCollMesh->ReadFromChunkStream(xStream, bConvertMaterialIDs);

				pxSkin->SetCollisionMesh(pxCollMesh);
			}
		}

		if(Version>=7)
		{
			Asura_ResourceAllocateArray(pxSkin->m_piMirroredBone, pxSkin->NumberOfBones);
			xStream.Read(NumberOfBones*sizeof(int), pxSkin->m_piMirroredBone);
		}

#ifdef ASURA_TOOLS
		if(Flags & ChunkFlag_ManualLOD)
		{
			pxSkin->m_bManualLOD = true;
		}
#endif
		if((Flags & ChunkFlag_HasInitialRestPose) && Version >= 8)
		{
			#ifdef ASURA_TOOLS
			Asura_ResourceAllocateArray(pxSkin->m_pxInitialRestPose, pxSkin->NumberOfBones);
			xStream.Read(sizeof(Asura_Hierarchy_Bone_RestFrame)*NumberOfBones, pxSkin->m_pxInitialRestPose);
			#else
			xStream.Skip(sizeof(Asura_Hierarchy_Bone_RestFrame)*NumberOfBones);
			#endif
		}

		if((Flags & ChunkFlag_HasBoneSortKey) && Version >= 9)
		{
			#ifdef ASURA_TOOLS
			Asura_ResourceAllocateArray(pxSkin->m_piBoneSortKey, pxSkin->NumberOfBones);
			xStream.Read(sizeof(int)*NumberOfBones, pxSkin->m_piBoneSortKey);
			#else
			xStream.Skip(sizeof(int)*NumberOfBones);
			#endif
		}

	}
	Asura_ResourcePopType();
	return pxSkin;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
/*--------------------------------**
** Asura_Chunk_Hierarchy_SkinLOD  **
**--------------------------------*/


#ifdef ASURA_TOOLS
void Asura_Chunk_Hierarchy_SkinLOD::Create( Asura_Chunk_Stream& xStream, const Asura_Hierarchy_Skin* pxSkin, const Asura_String& xRootSkinName)
{
	u_int uChunkFlags = 0;
	if(Tools_Level_Export::IsExport() || !pxSkin->VertexData)
	{
		uChunkFlags |= ChunkFlag_NoIndependentSkinData;
	}


	xStream.WriteChunk_Start(ASURA_CHUNK_HIERARCHY_SKINLOD, 0, uChunkFlags);
	


	xStream << xRootSkinName;
	xStream << pxSkin->Name;
	xStream << pxSkin->NumberOfVertices;

	if(!(uChunkFlags & ChunkFlag_NoIndependentSkinData))
	{
		for(int i=0;i<pxSkin->NumberOfVertices;i++)
		{
			xStream << pxSkin->VertexData[i].Offset[0];
			xStream << pxSkin->VertexData[i].Offset[1];
			xStream << pxSkin->VertexData[i].Offset[2];
			xStream << pxSkin->VertexData[i].Offset[3];
			xStream << pxSkin->VertexData[i].BoneID[0];
			xStream << pxSkin->VertexData[i].BoneID[1];
			xStream << pxSkin->VertexData[i].BoneID[2];
			xStream << pxSkin->VertexData[i].BoneID[3];
			xStream << pxSkin->VertexData[i].Weight[0];
			xStream << pxSkin->VertexData[i].Weight[1];
			xStream << pxSkin->VertexData[i].Weight[2];
			xStream << pxSkin->VertexData[i].Weight[3];
		}
	}
	xStream << pxSkin->m_fMinDistance;

	xStream.WriteChunk_End();
}
#endif//#ifdef ASURA_TOOLS

bool Asura_Chunk_Hierarchy_SkinLOD::Process(Asura_Handle iFileHandle) const
{
	Asura_Chunk_File_Stream	xStream(iFileHandle);
	return Process(xStream);
}


bool Asura_Chunk_Hierarchy_SkinLOD::Process(Asura_Chunk_Stream& xStream, Asura_Hierarchy_Skin* pxPreAllocatedSkin /*=0*/,Asura_String* pxOutputRootSkinName /*= 0*/) const
{
	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		Asura_Assert(Version<=0,"Unknown chunk version");

		Asura_Hierarchy_Skin* pxSkin = 0;
		Asura_Hierarchy_Skin* pxRootSkin = 0;
		
		char szRootSkinName[100];
		xStream.ReadString(szRootSkinName, sizeof(szRootSkinName));

		if(pxOutputRootSkinName) *pxOutputRootSkinName = szRootSkinName;

		if(pxPreAllocatedSkin)
		{
			pxSkin = pxPreAllocatedSkin;
		}
		else
		{
			//find the main skin that this is an LOD skin for
			pxRootSkin = Asura_Animation_System::GetSkin(szRootSkinName);
			Asura_Assert(pxRootSkin,"");

			//allocate the LOD skin
			pxSkin = Asura_Hierarchy_System::Project_AllocateSkin();
			pxSkin->CopyFromRootSkin(pxRootSkin);
		}

		xStream >> pxSkin->Name;
		pxSkin->m_uNameHashID = Asura_GetHashID(pxSkin->Name.GetString());
		xStream >> pxSkin->NumberOfVertices;

		if(pxSkin->NumberOfVertices && !(Flags & ChunkFlag_NoIndependentSkinData))
		{
			if( Asura_PlatformHierarchy_NeedIndependentSkinData() )
			{
				Asura_ResourceAllocateArray(pxSkin->VertexData, pxSkin->NumberOfVertices);
				
				xStream.Read(sizeof(Asura_Hierarchy_Skin_Vertex)*pxSkin->NumberOfVertices, pxSkin->VertexData);
			}
			else
			{
				xStream.Skip(pxSkin->NumberOfVertices * sizeof(Asura_Hierarchy_Skin_Vertex) );
			}
		}					    
		xStream >> pxSkin->m_fMinDistance;

		if(!pxPreAllocatedSkin)
		{
			//add the skin to the end of the LOD list
			Asura_Hierarchy_Skin* pxParentSkin = pxRootSkin;
			while(pxParentSkin->m_pxNextLowerSkin) pxParentSkin = pxParentSkin->m_pxNextLowerSkin;

			pxParentSkin->m_pxNextLowerSkin = pxSkin;
			pxSkin->m_pxNextHigherSkin = pxParentSkin;
		}

#ifdef ASURA_TOOLS
		if(pxSkin && !pxSkin->IsSkinnedModel() && !pxPreAllocatedSkin)
		{
			Asura_Assert(pxSkin->SectionData,"");

			//need to get the meshes for this model

			for(int i=0;i<pxSkin->NumberOfBones;i++)
			{
				if(!pxSkin->SectionData[i].SectionName.IsBlank())
				{
					//build the mesh name
					Asura_String xMeshName = pxSkin->Name;
					xMeshName += "_";
					xMeshName += pxSkin->SectionData[i].SectionName;
					//and get the mesh
					pxSkin->SectionData[i].Mesh = Asura_Mesh_System::GetMesh(xMeshName);
				}
			}		
		}
#endif //ASURA_TOOLS
	}
	Asura_ResourcePopType();
	return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
/*------------------------------------**
** Asura_Chunk_Hierarchy_MarkerPoints **
**------------------------------------*/

#ifdef ASURA_TOOLS
void Asura_Chunk_Hierarchy_MarkerPoints::Create( const Asura_Hierarchy_Skin* pxSkin, const class Asura_String& xSkinName, Asura_SaveBlock* pxSaveBlock)
{
	if(!pxSkin->NumberOfMarkerPoints) return;//no need for the chunk if no marker points
	
	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create	(pxSkin, xSkinName, xStream);
}

void Asura_Chunk_Hierarchy_MarkerPoints::Create( const Asura_Hierarchy_Skin* pxSkin, const class Asura_String& xSkinName, Asura_Chunk_Stream& xStream)
{
	if(!pxSkin->NumberOfMarkerPoints) return;//no need for the chunk if no marker points
	

	u_int uChunkVersion = 1;
	u_int uChunkFlags = 0;
	xStream.WriteChunk_Start(ASURA_CHUNK_HIERARCHY_MARKERPOINTS, uChunkVersion, uChunkFlags);

	xStream << pxSkin->NumberOfMarkerPoints;

	
	//output skin name
	xStream << xSkinName;
	
	//output each marker point
	const Asura_Hierarchy_Marker_Point* pxMarker = pxSkin->MarkerPoint;
	for(int i=0;i<pxSkin->NumberOfMarkerPoints;i++,pxMarker++)
	{
		xStream << pxMarker->Position;
		xStream << pxMarker->Matrix;
		xStream << pxMarker->BoneIndex;
		xStream << pxMarker->GetName();
	}

	xStream.WriteChunk_End();
}

#endif//#ifdef ASURA_TOOLS


bool Asura_Chunk_Hierarchy_MarkerPoints::Process(Asura_Handle fileHandle) const
{
	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		//get the skin name
		const Asura_Char* skinName = Asura_FileAccess::ReadString(fileHandle);

		//find the skin with that name
		Asura_Hierarchy_Skin* skin = Asura_Animation_System::GetSkin(skinName);
		if(!skin)
		{
			Asura_Assert(skin,"Failed to find skin for marker points.");
			Asura_ResourcePopType();
			return false;
		}
		if(skin->NumberOfMarkerPoints)
		{
#if !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
			Asura_Assertf(false,("Marker points have already been allocated for %s.Skipping this chunk.",skinName));
#endif	// !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)

			Asura_ResourcePopType();
			return false;
		}

		//allocate space for marker points
		skin->NumberOfMarkerPoints = NumberOfMarkerPoints;
		Asura_ResourceAllocateArray(skin->MarkerPoint, NumberOfMarkerPoints);

		//read the points in
		Asura_Hierarchy_Marker_Point* marker = skin->MarkerPoint;
		for(int i=0;i<skin->NumberOfMarkerPoints;i++,marker++)
		{
			Asura_FileAccess::ReadData(fileHandle,&marker->Position,sizeof(Asura_Vector_3));
			Asura_FileAccess::ReadData(fileHandle,&marker->Matrix,sizeof(Asura_Matrix_3x3));
			Asura_FileAccess::ReadData(fileHandle,&marker->BoneIndex,sizeof(int));

			marker->SetName(Asura_FileAccess::ReadString(fileHandle));
		}
	}
	Asura_ResourcePopType();
	return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
/*----------------------------------**
** Asura_Chunk_Hierarchy_SectionBBs **
**----------------------------------*/

static const int ASURA_CHUNK_HIERARCHY_SECTIONBBS_VERSION = 1;

#ifdef ASURA_TOOLS
bool Asura_Chunk_Hierarchy_SectionBBs::Create( const Asura_Hierarchy_Skin* pxSkin, const Asura_String& xSkinName, Asura_SaveBlock* pxSaveBlock )
{
    if( !pxSkin->m_pxSectionBoundingBoxData )
    {
        Asura_Assert( false, "Skin's section BB data hasn't been generated." );
        return false;
    }

	int iSizeOfName = (xSkinName.GetSizeInInts() << 2);
    
    int iChunkSize = sizeof(Asura_Chunk_Hierarchy_SectionBBs)
                   + iSizeOfName
                   + sizeof(int)
                   + pxSkin->NumberOfBones * sizeof(Asura_Hierarchy_SectionBoundingBox);

    char* const pcChunk = reinterpret_cast<char*>( pxSaveBlock->GetAccessPointer(iChunkSize) );

    char* pcOut = pcChunk;

    // Output hdr.
    {
	    Asura_Chunk_Hierarchy_SectionBBs xChunkHdr;
	    xChunkHdr.ID      = ASURA_CHUNK_HIERARCHY_SECTIONBBS;
	    xChunkHdr.Size    = iChunkSize;
	    xChunkHdr.Version = ASURA_CHUNK_HIERARCHY_SECTIONBBS_VERSION;
	    xChunkHdr.Flags   = 0;

		ASURA_CHUNK_ARRAY_VERIFY(xChunkHdr, int, 4);
        ASURA_CHUNK_FILL( pcOut, xChunkHdr, Asura_Chunk_Hierarchy_SectionBBs );
    }

    // Skin name.
    ASURA_CHUNK_FILL_STRING( pcOut, xSkinName );

    // Bone count.
    ASURA_CHUNK_FILLANDVERIFY( pcOut, pxSkin->NumberOfBones, int );

    // Section BB data.
    for( int i = 0; i < pxSkin->NumberOfBones; ++i )
    {
        if( pxSkin->m_pxSectionBoundingBoxData[i].m_uValid )
        {
            Asura_Assert( pxSkin->m_pxSectionBoundingBoxData[i].m_xBB.MinX < Asura_Maths::LargeFloat, "Section BB data is invalid." );
        }

        ASURA_CHUNK_ARRAY_FILLANDVERIFY( pcOut, pxSkin->m_pxSectionBoundingBoxData[i].m_xBB, Asura_Bounding_Box, float, 6);
        ASURA_CHUNK_BITFIELD_FILLANDVERIFY( pcOut, pxSkin->m_pxSectionBoundingBoxData[i].m_uFlags, int);
    }

    Asura_Assert( pcOut - pcChunk == iChunkSize, "Didn't write expected number of bytes for chunk." );

    return true;
}
#endif

bool Asura_Chunk_Hierarchy_SectionBBs::Process( Asura_Handle iFileHandle ) const
{
	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		Asura_Assert( Version <= ASURA_CHUNK_HIERARCHY_SECTIONBBS_VERSION, "Chunk version is too new. You need new code." );

		const Asura_Char* szSkinName = Asura_FileAccess::ReadString( iFileHandle );

		Asura_Hierarchy_Skin* pxSkin = Asura_Animation_System::GetSkin( szSkinName );
		if( !pxSkin )
		{
			Asura_Assertf( false, ("Couldn't find skin \"%s\" in order to process section BB chunk.", szSkinName) );
			Asura_ResourcePopType();
			return false;
		}
		else if( pxSkin->m_pxSectionBoundingBoxData )
		{
#if !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
			Asura_Assertf( false, ("Section BB data already loaded or generated for skin \"%s\".", szSkinName) );
#endif	// !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)

			Asura_ResourcePopType();
			return false;
		}

		int iBoneCount;
		Asura_FileAccess::ReadData( iFileHandle, &iBoneCount, sizeof(int) );

		if( iBoneCount != pxSkin->NumberOfBones )
		{
			Asura_Assertf( false, ("Bone count in section BB chunk doesn't match bone count in skin \"%s\".", szSkinName) );
			Asura_ResourcePopType();
			return false;
		}

		Asura_ResourceAllocateArray(pxSkin->m_pxSectionBoundingBoxData, iBoneCount);
		Asura_FileAccess::ReadData(iFileHandle, pxSkin->m_pxSectionBoundingBoxData, iBoneCount * sizeof(Asura_Hierarchy_SectionBoundingBox));

#if _DEBUG
		for( int i = 0; i < iBoneCount; ++i )
		{
			if( pxSkin->m_pxSectionBoundingBoxData[i].m_uValid )
			{
				Asura_Assert( pxSkin->m_pxSectionBoundingBoxData[i].m_xBB.MinX < Asura_Maths::LargeFloat, "Section BB data is invalid." );
			}
		}
#endif

		pxSkin->SetupBB();
	}
	Asura_ResourcePopType();
    return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
/*----------------------------------------**
** Asura_Chunk_Hierarchy_ManualSectionBBs **
**----------------------------------------*/
#ifdef ASURA_TOOLS 

#define ASURA_CHUNK_HIERARCHY_MANUALSECTIONBBS_VERSION 0

void Asura_Chunk_Hierarchy_ManualSectionBBs::Create(const Asura_Hierarchy_Skin* pxSkin,Asura_SaveBlock* pxSaveBlock)
{
	if(!pxSkin->m_pxManualSectionBoundingBoxData) return;//nothing to save
	if(!pxSkin->SectionData) return;


	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create	(pxSkin,xStream);
}

void Asura_Chunk_Hierarchy_ManualSectionBBs::Create(const Asura_Hierarchy_Skin* pxSkin,Asura_Chunk_Stream& xStream)
{
	if(!pxSkin->m_pxManualSectionBoundingBoxData) return;//nothing to save
	if(!pxSkin->SectionData) return;


	int iNumBBs = 0;

	//check number of BBs to save
	const Asura_Hierarchy_SectionBoundingBox* pxSBB = pxSkin->m_pxManualSectionBoundingBoxData;
	for(int i=0;i<pxSkin->NumberOfBones;i++,pxSBB++)
	{
		if(pxSBB->m_uValid)
		{
			iNumBBs++;
		}
	}
	if(!iNumBBs) return;//no BBs needed to be saved

	u_int uChunkFlags = 0;
	xStream.WriteChunk_Start(ASURA_CHUNK_HIERARCHY_MANUALSECTIONBBS, ASURA_CHUNK_HIERARCHY_MANUALSECTIONBBS_VERSION, uChunkFlags);


	//and the rest
	xStream << pxSkin->Name;
	xStream <<iNumBBs;


	pxSBB = pxSkin->m_pxManualSectionBoundingBoxData;
	for(int i=0;i<pxSkin->NumberOfBones;i++,pxSBB++)
	{
		if(pxSBB->m_uValid)
		{
			xStream << pxSBB->m_xBB;
			xStream << pxSkin->SectionData[i].SectionName;
		}
	}

	xStream.WriteChunk_End();
}


bool Asura_Chunk_Hierarchy_ManualSectionBBs::Process(Asura_Handle iFileHandle) const
{
	if(Version>ASURA_CHUNK_HIERARCHY_MANUALSECTIONBBS_VERSION)
	{
		Asura_Assertf(0,("Unrecognised chunk version (%d) for manual section bounding box chunk",Version));
		return false;
	}

	
	Asura_String xSkinName;
	ASURA_CHUNK_FILEEXTRACT_STRING(iFileHandle,xSkinName);

	//get the skin
	Asura_Hierarchy_Skin* pxSkin = Asura_Animation_System::GetSkin(xSkinName.GetString());
	if(!pxSkin)
	{
		Asura_Assertf(0,("Failed to find skin '%s' while processing manual section bounding box chunk",xSkinName.GetString()));
		return false;
	}

	if(pxSkin->m_pxManualSectionBoundingBoxData)
	{
		//already loaded
		Asura_Assertf(0,("Skin '%s' already has manual section bounding boxes",xSkinName.GetString()));
		return false;
	}
	
	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		//allocate bounding boxes
		Asura_ResourceAllocateArray(pxSkin->m_pxManualSectionBoundingBoxData, pxSkin->NumberOfBones);

		int iNumBBs;
		ASURA_CHUNK_FILEEXTRACT(iFileHandle,iNumBBs,int);

		for(int i=0;i<iNumBBs;i++)
		{
			Asura_Bounding_Box xBB;
			Asura_String xSectionName;
			ASURA_CHUNK_FILEEXTRACT(iFileHandle,xBB,Asura_Bounding_Box);
			ASURA_CHUNK_FILEEXTRACT_STRING(iFileHandle,xSectionName);

			//get index for this section
			for(int iBone=0;iBone<pxSkin->NumberOfBones;iBone++)
			{
				if(!stricmp(xSectionName.GetString(),pxSkin->SectionData[iBone].SectionName.GetString()))
				{
					pxSkin->m_pxManualSectionBoundingBoxData[iBone].m_xBB = xBB;
					pxSkin->m_pxManualSectionBoundingBoxData[iBone].m_uValid = 1;
					break;
				}
			}
		}
	}
	Asura_ResourcePopType();
	return true;
}

bool Asura_Chunk_Hierarchy_ManualSectionBBs::Process(Asura_Hierarchy_Skin* pxSkin) const
{
	if(Version>ASURA_CHUNK_HIERARCHY_MANUALSECTIONBBS_VERSION)
	{
		Asura_Assertf(0,("Unrecognised chunk version (%d) for manual section bounding box chunk",Version));
		return false;
	}

	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		const char* pcChunkBuffer = (const char*)(this+1);

		
		//skip past the skin name
		Asura_String xSkinName;
		ASURA_CHUNK_EXTRACT_STRING(pcChunkBuffer,xSkinName);


		if(!pxSkin->m_pxManualSectionBoundingBoxData)
		{
			//allocate bounding boxes
			Asura_ResourceAllocateArray(pxSkin->m_pxManualSectionBoundingBoxData, pxSkin->NumberOfBones);
		}
		

		int iNumBBs;
		ASURA_CHUNK_EXTRACT(pcChunkBuffer,iNumBBs,int);

		for(int i=0;i<iNumBBs;i++)
		{
			Asura_Bounding_Box xBB;
			Asura_String xSectionName;
			ASURA_CHUNK_EXTRACT(pcChunkBuffer,xBB,Asura_Bounding_Box);
			ASURA_CHUNK_EXTRACT_STRING(pcChunkBuffer,xSectionName);

			//get index for this section
			for(int iBone=0;iBone<pxSkin->NumberOfBones;iBone++)
			{
				if(!stricmp(xSectionName.GetString(),pxSkin->SectionData[iBone].SectionName.GetString()))
				{
					pxSkin->m_pxManualSectionBoundingBoxData[iBone].m_xBB = xBB;
					pxSkin->m_pxManualSectionBoundingBoxData[iBone].m_uValid = 1;
					break;
				}
			}
		}
	}
	Asura_ResourcePopType();
	return true;
}



#endif // ASURA_TOOLS

//////////////////////////////////////////////////////////////////////////////////////////////////
/*------------------------------**
** Asura_Chunk_Hierarchy_Sounds **
**------------------------------*/

#define ASURA_CHUNK_HIERARCHY_SOUNDS_VERSION 5
// Version 2 - Added Inner/Outer radius
//		RAR 12/07/04
// Version 3 - Added Pitch
//		RAR 19/10/04
// Version 4 - Added sound event
//		RAR 19/07/06
// Version 5 - Added dialogue event
//		RAR 18/12/2007

#ifdef ASURA_TOOLS

void Asura_Chunk_Hierarchy_Sounds::Create( const Asura_Hierarchy_Skin* pxSkin, Asura_SaveBlock* pxSaveBlock)
{
	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create(pxSkin->m_iNumberOfSoundDefns,pxSkin->m_pxSoundDefns,&pxSkin->Name,false,xStream);
}

void Asura_Chunk_Hierarchy_Sounds::Create( const Asura_Hierarchy_Skin* pxSkin, Asura_Chunk_Stream& xStream)
{
	Create(pxSkin->m_iNumberOfSoundDefns,pxSkin->m_pxSoundDefns,&pxSkin->Name,false,xStream);
}


void Asura_Chunk_Hierarchy_Sounds::Create( const Asura_Hierarchy_Anim* pxAnim, Asura_SaveBlock* pxSaveBlock)
{
	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create(pxAnim->m_iNumberOfSoundDefns,pxAnim->m_pxSoundDefns,&pxAnim->Name,true,xStream);
}

void Asura_Chunk_Hierarchy_Sounds::Create( const Asura_Hierarchy_Anim* pxAnim, Asura_Chunk_Stream& xStream)
{
	Create(pxAnim->m_iNumberOfSoundDefns,pxAnim->m_pxSoundDefns,&pxAnim->Name,true,xStream);
}


void Asura_Chunk_Hierarchy_Sounds::Create(int iNumberOfSoundDefns,const Asura_Hierarchy_Sound* pxSoundDefns,const Asura_String* pxName,bool bAnim, Asura_SaveBlock* pxSaveBlock)
{
	Asura_Chunk_SaveBlock_Stream xStream(pxSaveBlock);
	Create( iNumberOfSoundDefns, pxSoundDefns, pxName, bAnim, xStream);
}

void Asura_Chunk_Hierarchy_Sounds::Create(int iNumberOfSoundDefns,const Asura_Hierarchy_Sound* pxSoundDefns,const Asura_String* pxName,bool bAnim, Asura_Chunk_Stream& xStream)
{
	if(!iNumberOfSoundDefns) return;//no need for the chunk if no sounds

	int iVersionToWrite = ASURA_CHUNK_HIERARCHY_SOUNDS_VERSION;
	bool bWriteDialogueEvents = true;
	if(iVersionToWrite == 5)
	{
		//Attempt to write out earlier version if no dialogue events are being used , to avoid
		//problems with max plugins being updated out of sync with the main tools exe.
		//RAR 18/12/2007

		bool bHasDialogueEvents = false;
		#ifdef ASURA_USE_DIALOGUE_SYSTEM
		const Asura_Hierarchy_Sound* pxSound = pxSoundDefns;
		for(int i=0;i<iNumberOfSoundDefns;i++,pxSound++)
		{
			if(pxSound->m_uDialogueEvent)
			{
				bHasDialogueEvents = true;
				break;
			}
		}
		#endif
		if(!bHasDialogueEvents)
		{
			iVersionToWrite = 4;
			bWriteDialogueEvents = false;
		}
	}
	
	//start on writing the chunk
	u_int uChunkFlags = bAnim ? ChunkFlag_AnimSounds : 0;
	xStream.WriteChunk_Start(ASURA_CHUNK_HIERARCHY_SOUNDS, iVersionToWrite, uChunkFlags);

	xStream << iNumberOfSoundDefns;
	
	//output skin name
	xStream << *pxName;
	
	//output each sound
	const Asura_Hierarchy_Sound* pxSound = pxSoundDefns;
	for(int i=0;i<iNumberOfSoundDefns;i++,pxSound++)
	{
		xStream << pxSound->m_iAnimSoundID;
		xStream << pxSound->m_fVolume;
		xStream << pxSound->m_fInnerRadius;
		xStream << pxSound->m_fOuterRadius;
		xStream << pxSound->m_fPitch;

		
		xStream << pxSound->m_uSoundEvent;

		if(bWriteDialogueEvents)
		{
			#ifdef ASURA_USE_DIALOGUE_SYSTEM
			xStream << pxSound->m_uDialogueEvent;
			#else
			Asura_Hash_ID uPad = 0;
			xStream << uPad;
			#endif
		}

		
		Asura_String xTemp = pxSound->m_xSoundName;
		if(xTemp.IsNull()) xTemp = "";

		xStream << xTemp;
	}

	xStream.WriteChunk_End();
}
#endif//#ifdef ASURA_TOOLS


bool Asura_Chunk_Hierarchy_Sounds::Process(Asura_Handle iFileHandle) const
{
	Asura_Chunk_File_Stream xStream(iFileHandle);

	return Process(xStream, true, 0, 0, 0);
}

#ifdef ASURA_TOOLS
bool Asura_Chunk_Hierarchy_Sounds::Process(int* piNumberOfSoundDefns,class Asura_Hierarchy_Sound** ppxSoundDefns, Asura_String* pxName) const
{
	const char* pcChunkBuffer = (const char*)(this+1);
	Asura_Chunk_ChunkBuffer_Stream xStream(pcChunkBuffer);

	return Process(xStream, false, piNumberOfSoundDefns, ppxSoundDefns, pxName);
}
#endif //ASURA_TOOLS

bool Asura_Chunk_Hierarchy_Sounds::Process(Asura_Chunk_Stream& xStream,bool bApplyToSkinOrAnim,int* piNumberOfSoundDefns,class Asura_Hierarchy_Sound** ppxSoundDefns, Asura_String* pxName) const
{
	Asura_Assert(Version <= ASURA_CHUNK_HIERARCHY_SOUNDS_VERSION,"Unknown chunk version for hierarchy sounds");
	if(Version>ASURA_CHUNK_HIERARCHY_SOUNDS_VERSION) return false;

	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		
		char acName[256];
		xStream.ReadString(acName, sizeof(acName));

		if(pxName)
		{
			*pxName = acName;
		}

		Asura_Hierarchy_Anim* pxAnim = 0;
		Asura_Hierarchy_Skin* pxSkin = 0;
		if(bApplyToSkinOrAnim)
		{
			//Find the skin/anim that the sounds are to be applied to
			if(Flags & ChunkFlag_AnimSounds)
			{
				//find the anim with that name
				pxAnim = Asura_Animation_System::GetAnim(acName);
				if(!pxAnim)
				{
					Asura_Assert(pxAnim,"Failed to find anim for sounds.");
					Asura_ResourcePopType();
					return false;
				}
				if(pxAnim->m_iNumberOfSoundDefns)
				{
#if !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
					Asura_Assertf(false,("Sounds have already been allocated for %s.Skipping this chunk.",acName));
#endif	// !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)

					Asura_ResourcePopType();
					return false;
				}
				piNumberOfSoundDefns = &pxAnim->m_iNumberOfSoundDefns;
				ppxSoundDefns = &pxAnim->m_pxSoundDefns;
			}
			else
			{
				//find the skin with that name
				pxSkin = Asura_Animation_System::GetSkin(acName);
				if(!pxSkin)
				{
					Asura_Assert(pxSkin,"Failed to find skin for sounds.");
					Asura_ResourcePopType();
					return false;
				}
				if(pxSkin->m_iNumberOfSoundDefns)
				{
#if !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
					Asura_Assertf(!pxSkin->m_iNumberOfSoundDefns,("Sounds have already been allocated for %s.Skipping this chunk.",acName));
#endif	//!defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
					Asura_ResourcePopType();
					return false;
				}
				piNumberOfSoundDefns = &pxSkin->m_iNumberOfSoundDefns;
				ppxSoundDefns = &pxSkin->m_pxSoundDefns;
			}

		}
		else
		{
			Asura_Assert(piNumberOfSoundDefns && ppxSoundDefns, "");
		}

		//allocate space for sounds
		*piNumberOfSoundDefns = m_iNumberOfSoundDefns;
		Asura_ResourceAllocateArray(*ppxSoundDefns, m_iNumberOfSoundDefns);

		//read the sounds in
		Asura_Hierarchy_Sound* pxSound = *ppxSoundDefns;
		for(int i=0;i<m_iNumberOfSoundDefns;i++,pxSound++)
		{
			xStream >> pxSound->m_iAnimSoundID;
			if(Version<1)
			{
				pxSound->m_fVolume = 1.0f;
			}
			else
			{
				xStream >> pxSound->m_fVolume;
			}
			if(Version<2)
			{
				pxSound->m_fInnerRadius = 1.0f;
				pxSound->m_fOuterRadius = 10.0f;
			}
			else
			{
				xStream >> pxSound->m_fInnerRadius;
				xStream >> pxSound->m_fOuterRadius;
			}
			if(Version<3)
			{
				pxSound->m_fPitch = 1.0f;
			}
			else
			{
				xStream >> pxSound->m_fPitch;
			}
			
			if(Version<4)
			{
				pxSound->m_uSoundEvent = 0;
			}
			else
			{
				xStream >> pxSound->m_uSoundEvent;
			}

			if(Version<5)
			{
				#ifdef ASURA_USE_DIALOGUE_SYSTEM
				pxSound->m_uDialogueEvent = 0;
				#endif
			}
			else
			{
				#ifdef ASURA_USE_DIALOGUE_SYSTEM
				xStream >> pxSound->m_uDialogueEvent;
				#else
				xStream.Skip(sizeof(Asura_Hash_ID));
				#endif
			}

			char acSoundName[256];
			xStream.ReadString(acSoundName, sizeof(acSoundName));

			pxSound->m_uSoundHash = Asura_GetHashID(acSoundName);

			#ifdef ASURA_TOOLS
			pxSound->m_xSoundName = acSoundName;
			#endif
			if(pxAnim || pxSkin)
			{
				Asura_Assertf(pxSound->m_uSoundHash==0,("%s '%s' is still using individual wavs for sounds instead of sound events"
														,pxAnim ? "Anim" : "Skin"
														,pxAnim ? pxAnim->Name.GetString() : pxSkin->Name.GetString()));
			}
		}
	}
	Asura_ResourcePopType();
	return true;
}


//////////////////////////////////////////////////////////////////////////////////////////////////
/*--------------------------------------**
** Asura_Chunk_Hierarchy_CompressedAnim **
**--------------------------------------*/


static const int ASURA_CHUNK_HIERARCHY_COMPRESSEDANIM_VERSION = 7;

#ifdef ASURA_TOOLS
int Asura_Chunk_Hierarchy_CompressedAnim::GetChunkBufferSize(const Asura_Hierarchy_Anim* pxAnim) 
{
	int iChunkSize = sizeof(Asura_Chunk_Hierarchy_CompressedAnim);
	iChunkSize += pxAnim->Name.GetSizeInInts()*4;

	iChunkSize += sizeof(Asura_Hierarchy_Bone) * pxAnim->GetBoneArraySize();

	iChunkSize += sizeof(Asura_Comp_Quat_Fast) * pxAnim->m_uTotalNumberOfCompressedKeyFrames;

	iChunkSize += sizeof(u_short) * pxAnim->m_uTotalNumberOfCompressedKeyFrames;

	iChunkSize += sizeof(Asura_Vector_3) * pxAnim->m_uTotalNumberOfCompressedBoneOffsets;

	if(pxAnim->BoneLengths) iChunkSize += sizeof(float) * pxAnim->NumberOfBones;

	iChunkSize += pxAnim->NumberOfAnimationFlags*sizeof(Asura_Animation_Flag_Interval);

	if(pxAnim->GetBB()) iChunkSize += sizeof(Asura_Bounding_Box);

	iChunkSize += sizeof(float);//loop point

	iChunkSize += sizeof(int) + pxAnim->m_iNumberOfAnimationSounds*sizeof(Asura_Animation_Sound);


	return iChunkSize;
}
void Asura_Chunk_Hierarchy_CompressedAnim::Create(const Asura_Hierarchy_Anim* pxAnim, char* pcChunkBuffer)
{
	Asura_Chunk_Hierarchy_CompressedAnim* pxChunk = (Asura_Chunk_Hierarchy_CompressedAnim*)pcChunkBuffer;
	pcChunkBuffer += sizeof(Asura_Chunk_Hierarchy_CompressedAnim);

	int iChunkSize = GetChunkBufferSize(pxAnim);
	
	


	// KJL - 24/06/2003 14:09:01 - fill header and check endianess

	pxChunk->ID = ASURA_CHUNK_HIERARCHY_COMPRESSEDANIM;
	ASURA_CHUNK_VERIFY(pxChunk->ID, int);
	pxChunk->Size = iChunkSize;
	ASURA_CHUNK_VERIFY(pxChunk->Size, int);
	pxChunk->Version = ASURA_CHUNK_HIERARCHY_COMPRESSEDANIM_VERSION;
	#if (ASURA_CHUNK_HIERARCHY_COMPRESSEDANIM_VERSION == 4)
	if(!pxAnim->HasMovementData())
	{
		//Export animations without movement data as version 3 for now , for
		//greater compatability. RAR 04/02/04
		pxChunk->Version = 3;
	}
	#endif

	ASURA_CHUNK_VERIFY(pxChunk->Version, int);
	pxChunk->Flags = 0;

	if(pxAnim->BoneLengths) pxChunk->Flags |= ChunkFlag_HasBoneLengths;
	if(pxAnim->IsDeltaAnimation) pxChunk->Flags |= ChunkFlag_DeltaAnimation;
	if(pxAnim->GetBB()) pxChunk->Flags |= ChunkFlag_HasBoundingBox;
	if(pxAnim->HasMovementData()) pxChunk->Flags |= ChunkFlag_HasMovementData;
	if(pxAnim->m_bSharedAnimDeltaPos) pxChunk->Flags |= ChunkFlag_SharedAnimDeltaPos;
	if(pxAnim->m_bSharedAnimDeltaOrient) pxChunk->Flags |= ChunkFlag_SharedAnimDeltaOrient;
	if(pxAnim->m_bHasFacialAnimation) pxChunk->Flags |= ChunkFlag_HasFacialAnimation;
	pxChunk->Flags |= ChunkFlag_CompressedBoneDataStructure; // (MH - as of 27.04.2004 new saved data is more compressed) 
	ASURA_CHUNK_VERIFY(pxChunk->Flags, int);

	// KJL - 24/06/2003 14:09:15 - fill rest of fixed size data in chunk and check endianess
	pxChunk->m_fDuration = pxAnim->Duration;
	ASURA_CHUNK_VERIFY(pxChunk->m_fDuration, float);

	pxChunk->m_fDirection = pxAnim->Direction;
	ASURA_CHUNK_VERIFY(pxChunk->m_fDirection, float);
	
	pxChunk->m_fSpeed = pxAnim->Speed;
	ASURA_CHUNK_VERIFY(pxChunk->m_fSpeed, float);
	
	pxChunk->m_uNumberOfBones = pxAnim->NumberOfBones;
	ASURA_CHUNK_VERIFY(pxChunk->m_uNumberOfBones, u_int);
	
	pxChunk->m_uTotalNumberOfKeyFrames = pxAnim->m_uTotalNumberOfCompressedKeyFrames;
	ASURA_CHUNK_VERIFY(pxChunk->m_uTotalNumberOfKeyFrames, u_int);
	
	pxChunk->m_uTotalNumberOfBoneOffsets = pxAnim->m_uTotalNumberOfCompressedBoneOffsets;
	ASURA_CHUNK_VERIFY(pxChunk->m_uTotalNumberOfBoneOffsets, u_int);
	
	pxChunk->m_uNumberOfAnimationFlags = pxAnim->NumberOfAnimationFlags;
	ASURA_CHUNK_VERIFY(pxChunk->m_uNumberOfAnimationFlags, u_int);

	// KJL - 24/06/2003 14:09:35 - start of dynamically sized data
	ASURA_CHUNK_FILL_STRING(pcChunkBuffer,pxAnim->Name)

	// KJL - 24/06/2003 14:53:09 - written out in full for clarity
	int iBoneArraySize = pxAnim->GetBoneArraySize();
	for (int u=0; u<iBoneArraySize; u++)
	{
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, pxAnim->m_pxCompressedBoneData[u].m_usNumberOfKeyFrames, u_short);
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, pxAnim->m_pxCompressedBoneData[u].m_usFirstKeyFrameIndex, u_short);
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, pxAnim->m_pxCompressedBoneData[u].m_usNumberOfBoneOffsets, u_short);
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, pxAnim->m_pxCompressedBoneData[u].m_usFirstBoneOffsetIndex, u_short);
	}

	for (u_int u=0; u<pxAnim->m_uTotalNumberOfCompressedKeyFrames; u++)
	{
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, *(u_int*)(&pxAnim->m_pxCompressedQuatKeyFrames[u]), u_int);
	}

	for (u_int u=0; u<pxAnim->m_uTotalNumberOfCompressedKeyFrames; u++)
	{
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, pxAnim->m_pxCompressedTimeKeyFrames[u], u_short);
	}

	for (u_int u=0; u<pxAnim->m_uTotalNumberOfCompressedBoneOffsets; u++)
	{
		ASURA_CHUNK_ARRAY_FILLANDVERIFY(pcChunkBuffer, pxAnim->m_pxCompressedBoneOffsets[u], Asura_Vector_3, float, 3);
	}

	if(pxAnim->BoneLengths)
	{
		for(int b=0;b<pxAnim->NumberOfBones;b++)
		{
			ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer, pxAnim->BoneLengths[b], float);
		}
	}

	for(int i=0;i<pxAnim->NumberOfAnimationFlags;i++)
	{
		ASURA_CHUNK_ARRAY_FILLANDVERIFY(pcChunkBuffer, pxAnim->AnimationFlags[i], Asura_Animation_Flag_Interval, float, 3);
	}

	//version 1 , bounding box
	if(pxAnim->GetBB())
	{
		Asura_Bounding_Box xBB = *pxAnim->GetBB();
		ASURA_CHUNK_ARRAY_FILLANDVERIFY(pcChunkBuffer,xBB,Asura_Bounding_Box,float,6);
		
	}

	//version 2 , loop point
	ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer,pxAnim->GetLoopPoint(),float);

	//version 3 , animation sounds
	ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer,pxAnim->m_iNumberOfAnimationSounds,int);
	for(int i=0;i<pxAnim->m_iNumberOfAnimationSounds;i++)
	{
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer,pxAnim->m_pxAnimationSounds[i].m_fStartTime,float);
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer,pxAnim->m_pxAnimationSounds[i].m_iAnimSoundID,int);
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer,pxAnim->m_pxAnimationSounds[i].m_fEndTime,float);
		ASURA_CHUNK_FILLANDVERIFY(pcChunkBuffer,pxAnim->m_pxAnimationSounds[i].m_uFlags,u_int);
	}


	
	Asura_Assert((int)pcChunkBuffer-(int)pxChunk == iChunkSize,"Incorrect chunk size");


}
#endif


bool Asura_Chunk_Hierarchy_CompressedAnim::Process(Asura_Handle fileHandle) const
{
	Asura_ResourcePushType(ASURA_RESOURCE_ANIMATION);
	{
		if(Version>ASURA_CHUNK_HIERARCHY_COMPRESSEDANIM_VERSION)
		{
			Asura_Assert(Version<=ASURA_CHUNK_HIERARCHY_COMPRESSEDANIM_VERSION,"Unknown chunk version");
			Asura_ResourcePopType();
			return 0;
		}

		Asura_Hierarchy_Anim* pxAnim;

		u_int uAnimNameSizeInInts;
		const char* szAnimName = Asura_FileAccess::ReadString( fileHandle, NULL, &uAnimNameSizeInInts );

#if ASURA_LOG_FILE_CONTENTS
		Asura_SimpleLogfileOutput((ASURA_FILE_CONTENTS_LOGFILE,"Animation : %s\n",szAnimName));
#endif



		if( !Asura_Animation_System::NewAnimation(szAnimName, pxAnim) )
		{
#if !defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)
			Asura_Assertf(false, ("Anim with name \"%s\" already loaded. Check they are the same anim.\n", szAnimName) );
			//   Asura_OutputToDebugger_Retail(( "WARNING: Anim \"%s\" already loaded.\n", szAnimName ));
#endif	//!defined(ASURA_DONT_ASSERT_ON_DUPLICATE_RESOURCE_DATA)

			const int iNumBytesReadAlready = sizeof(Asura_Chunk_Hierarchy_CompressedAnim) + (uAnimNameSizeInInts << 2);
			Asura_FileAccess::SkipData( fileHandle, this->Size - iNumBytesReadAlready );
			Asura_ResourcePopType();
			return true;
		}

		pxAnim->Name									= szAnimName;
		pxAnim->m_uHashID								= Asura_GetHashID(szAnimName);
		pxAnim->Duration								= m_fDuration;
		pxAnim->Direction								= m_fDirection;
		pxAnim->Speed									= m_fSpeed;
		pxAnim->NumberOfBones							= m_uNumberOfBones;
		pxAnim->m_uTotalNumberOfCompressedKeyFrames		= m_uTotalNumberOfKeyFrames;
		pxAnim->m_uTotalNumberOfCompressedBoneOffsets	= m_uTotalNumberOfBoneOffsets;
		pxAnim->NumberOfAnimationFlags					= m_uNumberOfAnimationFlags;
		pxAnim->m_bHasMovementData						= (Flags & ChunkFlag_HasMovementData) ? true : false;
		pxAnim->m_bSharedAnimDeltaPos					= (Flags & ChunkFlag_SharedAnimDeltaPos) ? true : false;
		pxAnim->m_bSharedAnimDeltaOrient 				= (Flags & ChunkFlag_SharedAnimDeltaOrient) ? true : false;
		pxAnim->m_bHasFacialAnimation					= (Flags & ChunkFlag_HasFacialAnimation) ? true : false;

		pxAnim->BoneAnims = 0;

		if(Flags & ChunkFlag_DeltaAnimation) pxAnim->IsDeltaAnimation = true;

		int iBoneArraySize = pxAnim->GetBoneArraySize();	

		Asura_ResourceAllocateArray(pxAnim->m_pxCompressedBoneData, iBoneArraySize);
		if (Flags & ChunkFlag_CompressedBoneDataStructure)
		{
			Asura_FileAccess::ReadData(fileHandle, pxAnim->m_pxCompressedBoneData, sizeof(Asura_Hierarchy_Bone)*iBoneArraySize);
		}
		else
		{
			// support for old data - can be removed or made tools-only eventually
			Asura_Hierarchy_Bone_OldVersion* pxTempBuffer;

			Asura_ResourcePushType(ASURA_RESOURCE_TEMPORARY);
			{
				Asura_ResourceAllocateArray(pxTempBuffer, iBoneArraySize);
				Asura_FileAccess::ReadData(fileHandle, pxTempBuffer, sizeof(Asura_Hierarchy_Bone_OldVersion)*iBoneArraySize);

				for (int i = 0; i < iBoneArraySize; ++i)
				{
					const Asura_Hierarchy_Bone_OldVersion* pxOldBone = &pxTempBuffer[i];
					Asura_Hierarchy_Bone* pxBone = &pxAnim->m_pxCompressedBoneData[i];
					if (pxOldBone->m_uNumberOfKeyFrames)
					{
						Asura_Assert(pxOldBone->m_uNumberOfKeyFrames < 65536, "too many keyframes");
						Asura_Assert(pxOldBone->m_uFirstKeyFrameIndex < 65536, "animation value out of range");
						Asura_Assert(pxOldBone->m_uNumberOfBoneOffsets < 65536, "animation value out of range");
						Asura_Assert(pxOldBone->m_uFirstBoneOffsetIndex < 65536, "animation value out of range");

						pxBone->m_usNumberOfKeyFrames = static_cast<u_short>(pxOldBone->m_uNumberOfKeyFrames);
						pxBone->m_usFirstKeyFrameIndex = static_cast<u_short>(pxOldBone->m_uFirstKeyFrameIndex);
						pxBone->m_usNumberOfBoneOffsets = static_cast<u_short>(pxOldBone->m_uNumberOfBoneOffsets);
						pxBone->m_usFirstBoneOffsetIndex  = static_cast<u_short>(pxOldBone->m_uFirstBoneOffsetIndex);
					}
					else
					{
						pxBone->m_usNumberOfKeyFrames = 0;
						pxBone->m_usFirstKeyFrameIndex = 0;
						pxBone->m_usNumberOfBoneOffsets = 0;
						pxBone->m_usFirstBoneOffsetIndex  = 0;
					}
				}

				Asura_ResourceDeallocateArray(pxTempBuffer);
			}
			Asura_ResourcePopType();
		}


		//#if _DEBUG
		//	static bool bInitLogfile = false;
		//	if (!bInitLogfile)
		//	{
		//		bInitLogfile = true;
		//		Asura_SimpleLogfileClear(("logfiles\\Asura_Hierarchy_Bone.txt"));
		//	}
		//	for (int i = 0; i < iBoneArraySize; ++i)
		//	{
		//		const Asura_Hierarchy_Bone* pxBone = &pxAnim->m_pxCompressedBoneData[i];
		//		if (pxBone->m_uNumberOfKeyFrames)
		//		{
		//			Asura_SimpleLogfileOutput(("logfiles\\Asura_Hierarchy_Bone.txt", "%u\t%u\t%u\t%u\n", 
		//				pxBone->m_uNumberOfKeyFrames,
		//				pxBone->m_uFirstKeyFrameIndex,
		//				pxBone->m_uNumberOfBoneOffsets,
		//				pxBone->m_uFirstBoneOffsetIndex ));
		//		}
		//	}
		//#endif

		Asura_ResourceAllocateArray(pxAnim->m_pxCompressedQuatKeyFrames, m_uTotalNumberOfKeyFrames);
		Asura_FileAccess::ReadData(fileHandle, pxAnim->m_pxCompressedQuatKeyFrames, sizeof(Asura_Comp_Quat_Fast)*m_uTotalNumberOfKeyFrames);

		if(Version<5)
		{
			//Old versions used Asura_Comp_Quat instead of Asura_Comp_Quat_Fast.
			//Convert them all here..
			Asura_Quat xTempQuat;
			Asura_Comp_Quat_Fast* pxCompQuat = pxAnim->m_pxCompressedQuatKeyFrames;
			for(u_int i=0;i<m_uTotalNumberOfKeyFrames;i++,pxCompQuat++)
			{
				((Asura_Comp_Quat*)pxCompQuat)->ToQuat(xTempQuat);
				pxCompQuat->FromQuat(xTempQuat);
			}
		}

		Asura_ResourceAllocateArray(pxAnim->m_pxCompressedTimeKeyFrames, m_uTotalNumberOfKeyFrames);
		Asura_FileAccess::ReadData(fileHandle, pxAnim->m_pxCompressedTimeKeyFrames, sizeof(u_short)*m_uTotalNumberOfKeyFrames);

		Asura_ResourceAllocateArray(pxAnim->m_pxCompressedBoneOffsets, m_uTotalNumberOfBoneOffsets);
		Asura_FileAccess::ReadData(fileHandle, pxAnim->m_pxCompressedBoneOffsets, sizeof(Asura_Vector_3)*m_uTotalNumberOfBoneOffsets);

		if(Flags & ChunkFlag_HasBoneLengths)
		{
			Asura_ResourceAllocateArray(pxAnim->BoneLengths, pxAnim->NumberOfBones);
			Asura_FileAccess::ReadData(fileHandle, pxAnim->BoneLengths, sizeof(float)*pxAnim->NumberOfBones);
			pxAnim->DetermineMovementScaleBone();

		}

		if(pxAnim->NumberOfAnimationFlags)
		{
			Asura_ResourceAllocateArray(pxAnim->AnimationFlags, pxAnim->NumberOfAnimationFlags);
			Asura_FileAccess::ReadData(fileHandle, pxAnim->AnimationFlags, sizeof(Asura_Animation_Flag_Interval)*pxAnim->NumberOfAnimationFlags);
		}

		if(Version>=1)
		{
			//load bounding box
			if(Flags & ChunkFlag_HasBoundingBox)
			{
				Asura_Bounding_Box xBB;
				const bool bOk = Asura_FileAccess::ReadData(fileHandle,&xBB,sizeof(Asura_Bounding_Box));
				if (!bOk)
				{
					// MGH 7/8/2006: May fix problems with failed reads for Gun PSP.
					xBB.Reset();
				}
				pxAnim->SetBB(&xBB);
			}
		}

		if(Version>=2)
		{
			float fLoop = 0.0f; // MGH 7/8/2006: Default value should fix FPU exception caused by failed read.
			Asura_FileAccess::ReadData(fileHandle,&fLoop,sizeof(float));
			pxAnim->SetLoopPoint(fLoop);
		}

		if(Version>=3)
		{
			Asura_FileAccess::ReadData(fileHandle,&pxAnim->m_iNumberOfAnimationSounds,sizeof(int));
			if(pxAnim->m_iNumberOfAnimationSounds)
			{
				Asura_ResourceAllocateArray(pxAnim->m_pxAnimationSounds, pxAnim->m_iNumberOfAnimationSounds);
				Asura_Animation_Sound* pxSound = pxAnim->m_pxAnimationSounds;

				for(int i=0;i<pxAnim->m_iNumberOfAnimationSounds;i++,pxSound++)
				{
					Asura_FileAccess::ReadData(fileHandle, &pxSound->m_fStartTime, sizeof(float));
					Asura_FileAccess::ReadData(fileHandle, &pxSound->m_iAnimSoundID, sizeof(int));
					if(Version >= 6)
					{
						Asura_FileAccess::ReadData(fileHandle, &pxSound->m_fEndTime, sizeof(float));
					}
					else
					{
						pxSound->m_fEndTime = pxSound->m_fStartTime;
					}
					if(Version >= 7)
					{
						Asura_FileAccess::ReadData(fileHandle, &pxSound->m_uFlags, sizeof(u_int));
					}
					else
					{
						pxSound->m_uFlags = 0;
					}

				}
			}
		}
	}
	Asura_ResourcePopType();
	return true;
}
