////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   cgfconvertor.cpp
//  Version:     v1.00
//  Created:     5/11/2002 by Timur.
//  Compilers:   Visual Studio.NET
//  Description: 
// -------------------------------------------------------------------------
//  History:		 1/14/2003 :- Taken over by Sergiy Migdalskiy
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include <io.h>
#include "Dbghelp.h"
#include "FileUtil.h"
#include "PathUtil.h"
#include "ResourceCompilerPC.h"
#include "IRCLog.h"
#include "CgfUtils.h"								// CMatEntityNameTokenizer
#include <ICryAnimation.h>	        // AnimSinkEventData
#include "CgfConvertor.h"
#include "CryChunkedFile.h"
#include "ConvertContext.h"
#include "CryCompiledFile.h"
#include "CryBoneHierarchyLoader.h"
#include "CryBoneDesc.h"
#include "RenderMeshBuilder.h"
//#include "StencilShadowConnectivity.h"
//#include "StencilShadowConnectivityBuilder.h"
#include "SkinDataSources.h"
#include "CrySkinBuilder.h"
#include "CrySkinFull.h"
#include "CrySkinMorph.h"
#include "CrySkinMorphBuilder.h"
#include "CrySkinRigidBasis.h"
#include "CrySkinBasisBuilder.h"
#include "BoneLightBindInfo.h"
#include "StringUtils.h"
#include "StlUtils.h"
#include "iconfig.h"
#include "Controller.h"
extern unsigned g_nFrameID;
#include "CryAnimationInfo.h"

void ValidateHeap()
{
#ifdef WIN64
	//assert (_CrtCheckMemory());
#else
	int nHeapCheck = _heapchk();
	assert (nHeapCheck == _HEAPEMPTY || nHeapCheck == _HEAPOK);
#endif
}

CGFConvertor::CGFConvertor ():
m_fTarget(NULL)
{
}

CGFConvertor::~CGFConvertor()
{
	clear();
}

//////////////////////////////////////////////////////////////////////////
bool CGFConvertor::Process( ConvertContext &cc )
{
	bool bResult = true;
	m_pContext = &cc;
	// Here loading/conversion/bla bla

	g_pLog = cc.pLog;

	CString strSourceFilePath = cc.getSourcePath();

	
	if (CryStringUtils::MatchWildcard(CryStringUtils::toLower(string(cc.sourceFile.GetString())).c_str(), "*_lod?.*")
		&&isAnimatedFastCheck(strSourceFilePath.GetString()))
	{
		return true; // we ignore lods of existing animated objects
	}

	try
	{
		//if (!LoadLODs())
			//return getStatCGFCompiler()->Process(cc);

//		cc.pLog->Log("Converting as an Animated Character: %s", (const char*)cc.sourceFile);
		cc.pLog->Log(" Converting as an Animated Character ...");

		cc.pLog->Log(" BuildRenderMeshes() ...");
		BuildRenderMeshes();
		cc.pLog->Log(" UpdateDeadBodyPhysics() ...");
		UpdateDeadBodyPhysics();

		if (!m_arrLODs.empty())
		{
			m_fTarget = fopen (cc.getOutputPath().GetString(), "wb");
			if (m_fTarget)
			{

				m_Writer.SetFile(m_fTarget);
				WriteHeader();
				WriteBoneInfo();
				WriteMaterials( cc.getSourcePath().GetString() );
				for (unsigned nLOD = 0; nLOD < m_arrLODs.size(); ++nLOD)
				{
					WriteGeometryInfo (nLOD);
					if (nLOD < 2)
					{ // write the LOD 0 and possibly 1 physical geometry for bones
						WriteBoneGeometry (nLOD);
					}
				}
				WriteMorphTargetSet();
				WriteLights();

				{
					FILE *fCal = fopen(GetCalFilePath().c_str(), "rt");
					if (fCal)
					{
						WriteAnimListWithCAL(fCal);
						fclose(fCal);
					}
					else
						WriteAnimListNoCAL();
				}
				WriteSceneProps();

				m_Writer.SetFile(NULL);
				fclose (m_fTarget);
			}
			else
			{
				Log ("Error: cannot open target file %s", (const char*)cc.outputFile.GetString());
				bResult = false;
			}
		}
		else
		{
			Log ("Error: Cannot load cry chunked file");
			bResult = false;
		}
	}
	catch (Error&e)
	{
//		Log ("Error converting \"%s\": \"%s\"", (const char*)cc.sourceFile, e.c_str());
		LogError("%s",e.c_str());
		bResult = false;
	}
#ifndef _DEBUG
	catch (...)
	{
		Log ("Unspecified Error converting \"%s\"", Path::Make(cc.sourceFile,cc.sourceFile).GetString() );
		bResult = false;
	}
#endif
	clear();
	return bResult;
}

CryChunkedFile* CGFConvertor::LoadCryChunkedFile (const char* szFileName)
{
	CChunkFile_AutoPtr pReader = new CChunkFile();
	if (!pReader->Read(szFileName))
		return NULL;

	try
	{
		return new CryChunkedFile(pReader);
	}
	catch (CryChunkedFile::Error& e)
	{
		LogError("%s", e.strDesc.c_str());
		return NULL;
	}
	catch (...)
	{
		LogError("UNEXPECTED ERROR while trying to load Cry Chunked File \"%s\"", szFileName);
		return NULL;
	}
}

// loads the m_arrLODs
// throws error
// if the CGF doesn't have bones, returns false
bool CGFConvertor::LoadLODs()
{
	CString sourceFile = m_pContext->getSourcePath();

	string strFileNoExt = sourceFile;
	CryStringUtils::StripFileExtension(strFileNoExt);
	m_arrLODs.clear();
	m_arrLODBoneMaps.clear();
	for (;;)
	{
		unsigned nLOD = m_arrLODs.size(); // the LOD currently being loaded
		string strFile = sourceFile;
		if (m_arrLODs.empty())
		{
			//LOD 0 - no modifications
			strFile = sourceFile;
		}
		else
		{
			// LOD > 0
			char szLodSuffix[24];
			sprintf (szLodSuffix, "_lod%d.cgf", nLOD);
			strFile = strFileNoExt + szLodSuffix;
		}

		CryChunkedFile_AutoPtr pSource = LoadCryChunkedFile(strFile.c_str());

		if (!pSource)
		{
			if (GetFileAttributes (strFile.c_str()) != 0xFFFFFFFF)
				throw Error ("Malformed CGF: %s", strFile.c_str());
			else
				break;
		}
		if (!pSource->Bones.numBones())
		{
			if (nLOD)
			{
				LogWarning ("LOD %d doesn't have bones. Converting as static.", nLOD);
				return false;
			}
		}

		if (!pSource->IsBoneInitialPosPresent())
		{
			LogWarning ("The CGF doesn't contain initial pose data and can be converted incorrectly.");
			LogWarning ("Please re-export the CGF with the latest exporter.");
		}

		if (pSource->arrMeshes.empty())
			throw Error("No mesh found");

		if (pSource->arrNames.size() != pSource->Bones.numBones())
			throw Error("There are %d bone names, but %d bones", pSource->arrNames.size(), pSource->Bones.numBones());

		if (pSource->arrMeshes.size() != 1)
			throw Error ("In LOD %d, there are %d meshes. One and Only One mesh per LOD is supported", m_arrLODs.size(), pSource->arrMeshes.size());

		if (!m_arrLODs.empty() && pSource->Bones.numBones() != m_arrLODs[0]->Bones.numBones())
		{
			LogWarning ("In LOD%d, %d bones found. In Master LOD, there are %d bones.", nLOD, pSource->Bones.numBones(), m_arrLODs[0]->Bones.numBones());
			LogWarning ("   Please re-export LOD%d",nLOD);
		}

		m_arrLODs.push_back(pSource);

		RemapBones(nLOD);
	}
	return true;
}


typedef std::map<unsigned, unsigned> UIntUIntMap;
void BuildBoneCtrlMap (const std::vector<CryBoneDesc>& arrBones, UIntUIntMap& mapCtrl)
{
	for (unsigned i = 0; i < arrBones.size(); ++i)
		mapCtrl.insert (UIntUIntMap::value_type(arrBones[i].getControllerId(), i));
}


// returns the number of parents for this bone: 0 for root, 1 for its children, 2 for grandchildren and so on
unsigned NumParents(const CryBoneDesc* pBone)
{
	unsigned nResult = 0;
	for (nResult = 0;pBone->getParentIndexOffset();++nResult)
		pBone = pBone + pBone->getParentIndexOffset();
	return nResult;
}


// remaps, if necessary, the bones from the LOD source to the Master source
// and changes the links in the LOD source so that the boneid's there point to the indices (not IDs!)
// of the bones in the Master Source, not LOD Source
// This assumes that the bone information from the LOD source won't be used at all
// throws an error if there's some unrecognized bone in the LOD source
void CGFConvertor::RemapBones (unsigned nLOD)
{
	CryChunkedFile* pMaster = m_arrLODs[0];
	CryChunkedFile* pLOD = m_arrLODs[nLOD];

	if (m_arrLODBoneMaps.size() < nLOD+1)
		m_arrLODBoneMaps.resize (nLOD+1);
	std::vector<int>& arrBoneMap = m_arrLODBoneMaps[nLOD];

	// this will be the map from the LOD Source to Master Source
	// for bones that have no match, there will be -1's
	unsigned numMasterBones = pMaster->Bones.numBones();
	unsigned numLODBones = pLOD->Bones.numBones();
	arrBoneMap.resize (numMasterBones, -1);

	if (nLOD == 0)
	{
		// the special case - LOD 0 IS a Master
		for (unsigned nBone = 0; nBone < numLODBones; ++nBone)
			arrBoneMap[nBone] = nBone;
		return;
	}

	std::vector<int> arrLODToMaster;
	arrLODToMaster.resize (numLODBones, -1);

	// build the map for CtrlID->bone index
	UIntUIntMap mapCtrlLOD;
	BuildBoneCtrlMap (pLOD->Bones.m_arrBones, mapCtrlLOD);


	for (unsigned nBone = 0; nBone < numMasterBones; ++nBone)
	{
		// try to find the matching bone in LOD
		const CryBoneDesc& rBoneMaster = pMaster->Bones.m_arrBones[nBone];
		UIntUIntMap::iterator it = mapCtrlLOD.find (rBoneMaster.getControllerId());

		if (it != mapCtrlLOD.end()
			/*&& NumParents (&rBoneMaster) == NumParents(&pLOD->Bones.m_arrBones[it->second])*/)
		{
			// the bones match
			arrBoneMap[nBone] = it->second;
			arrLODToMaster[it->second] = nBone;
		}
		else
			LogError ("Cannot map bone %s; the Physics will remain uninitialized", rBoneMaster.getNameCStr());
	}

	// now remap the bone ids
	RemapBoneIndices (pLOD, arrLODToMaster);
}


// remaps the bone indices, throws error if some indices cannot be remapped for some reason
// (e.g. mapping contains -1, i.e. no mapping)
void CGFConvertor::RemapBoneIndices (CryChunkedFile* pLOD, const std::vector<int>& arrMap)
{
	for (unsigned i = 0; i  < pLOD->arrMeshes.size(); ++i)
	{
		CryChunkedFile::MeshDesc& rMeshDesc = pLOD->arrMeshes[i];
		for (unsigned nVertex = 0; nVertex < rMeshDesc.arrVertBinds.size(); ++nVertex)
		{
			CryVertexBinding& rLinks = rMeshDesc.arrVertBinds[nVertex];
			for (unsigned nLink = 0; nLink < rLinks.size(); ++nLink)
			{
				CryLink& rLink = rLinks[nLink];
				if ((unsigned)rLink.BoneID > arrMap.size())
					throw Error ("Cannot convert vertex %u link %u because bone link (%d) is out of range", nVertex, nLink, rLink.BoneID);
				if (arrMap[rLink.BoneID] < 0)
					throw Error ("Cannot convert vertex %u link %u because the bone (%d) wasn't mapped", nVertex, nLink, rLink.BoneID);
				rLink.BoneID = arrMap[rLink.BoneID];
			}
		}
	}
}


// builds the m_arrRenderMeshes array with the CRenderMeshBuilder
// structures containing all the additional info required for leaf buffer creation
void CGFConvertor::BuildRenderMeshes()
{
	try
	{
		m_arrRenderMeshes.resize (m_arrLODs.size());
		// this is the base to add to the material id in each subsequent LOD
		// it reflects the fact that a common array of materials is used for all LODs
		for (unsigned nLOD = 0; nLOD < m_arrLODs.size(); ++nLOD)
		{
			CryChunkedFile* pSource = m_arrLODs[nLOD];
			if (pSource->arrMeshes.size() != 1)
				throw Error ("Unexpected number of meshes (%d) in LOD %d", pSource->arrMeshes.size(), nLOD);

			m_arrRenderMeshes[nLOD].build(&pSource->arrMeshes[0]);
			if (m_arrRenderMeshes[nLOD].m_arrExtFaces.size() != pSource->arrMeshes[0].numFaces())
				LogWarning ("The prepared mesh number of faces doesn't match that of the original CGF mesh. THere are perhaps %d degenerate faces", 
					pSource->arrMeshes[0].numFaces() - m_arrRenderMeshes[nLOD].m_arrExtFaces.size());
			m_arrRenderMeshes[nLOD].addMaterialOffset(getLODMtlOffset(nLOD));
		}
	}
	catch (CRenderMeshBuilder::Error& e)
	{
		throw Error ("Cannot build render meshes: \"%s\"", e.c_str());
	}
}


//////////////////////////////////////////////////////////////////////////
// Updates physics if needed (if there is LOD1, which contains physical data for LOD1 physics
// This is actually hardcoded hack: the dead body physics is kept in LOD 1
// and copied into LOD 0 1st physics slot from 0th physics slot of LOD 1 here
void CGFConvertor::UpdateDeadBodyPhysics()
{
	if (m_arrLODs.size() <= 1)
		return;
	
	CryBoneHierarchyLoader::CryBoneDescArray
		// bones of LOD 0 (main body)
		&arrMainBones = m_arrLODs[0]->Bones.m_arrBones,
		// bones of LOD 1
		&arrSecBones = m_arrLODs[1]->Bones.m_arrBones;

  if (arrMainBones.size() != arrSecBones.size())
		LogWarning ("LOD 1 has %d bones, LOD 0 has %d bones", arrSecBones.size(), arrMainBones.size());

	unsigned numBones = arrMainBones.size();
	for (unsigned nBone = 0; nBone < numBones; ++nBone)
	{
		CryBoneDesc& rBoneMain = arrMainBones[nBone];
		int nLODBone = m_arrLODBoneMaps[1][nBone];
		if (nLODBone < 0)
		{
			LogWarning("bone \"%s\" won't have dead body physics because there's no such bone in LOD1", rBoneMain.getNameCStr());
			rBoneMain.resetPhysics (1);
			continue;
		}
		assert ((unsigned)nLODBone < arrSecBones.size());
		CryBoneDesc& rBoneSec  = arrSecBones[nLODBone];
		//if (!rBoneMain.isEqual(rBoneSec))
		//	throw Error ("LOD 0 and 1 bone #%d are different", nBone);
		rBoneMain.setPhysics (1, rBoneSec.getPhysics(0));
	}
}


CGFConvertor::Error::Error (const char* szFormat, ...)
{
	char szBuffer[0x800];
	va_list arg;
	va_start(arg,szFormat);
	_vsnprintf (szBuffer, sizeof(szBuffer), szFormat, arg);
	va_end(arg);
	this->m_strReason = szBuffer;
}

CGFConvertor::Error::Error (int nCode)
{
	char szBuffer[36];
	sprintf (szBuffer, "Generic Error #%d", nCode);
	this->m_strReason = szBuffer;
}


// releases all the resources
void CGFConvertor::clear()
{
	m_arrLODs.clear();

	m_Writer.SetFile(NULL);
	if (m_fTarget)
	{
		fclose (m_fTarget);
		m_fTarget = NULL;
	}
	
	m_pContext = NULL;
}


//////////////////////////////////////////////////////////////////////////
bool CGFConvertor::GetOutputFile( ConvertContext &cc )
{
	m_pContext = &cc;

	//specify output path
	//cc.outputFile = Path::ReplaceExtension( cc.sourceFile,"ccg" );
	//cc.outputFolder = cc.masterFolder +CString("CCGF_CACHE") + "\\" + cc.outputFolder;

	cc.outputFile = cc.sourceFile;
	//cc.outputFolder = cc.masterFolder + CString("CCGF_CACHE") + "\\" + cc.outputFolder;

	return true;
}

//////////////////////////////////////////////////////////////////////////
int CGFConvertor::GetNumPlatforms() const
{
	return 4;
}

//////////////////////////////////////////////////////////////////////////
Platform CGFConvertor::GetPlatform( int index ) const
{
	switch (index)
	{
	case 0:	return PLATFORM_PC;
	case 1:	return PLATFORM_XBOX;
	//case 2:	return PLATFORM_PS2;
	//case 3:	return PLATFORM_GAMECUBE;
	};
	//assert(0);
	return PLATFORM_UNKNOWN;
}


// writes the data directly into the file
void CGFConvertor::writeRaw (const void* pData, unsigned nSize)
{
	fwrite (pData, nSize, 1, m_fTarget);
}


void CGFConvertor::WriteBoneGeometry (unsigned nLOD)
{
	// construct the corresponding map Bone Index -> Bone Mesh
	// for each bone in the Master, get the corresponding bone in LOD
	// and get its corresponding geometry chunk id. Find the MeshDesc and map it
	typedef std::map<unsigned, CryChunkedFile::MeshDesc*> BoneMeshDescMap;
	BoneMeshDescMap mapBoneMesh;

	CryChunkedFile *pMaster = m_arrLODs[0], *pLOD = m_arrLODs[nLOD];

	for (unsigned nBone = 0; nBone < pMaster->Bones.numBones(); ++nBone)
	{
		int nLODBone = m_arrLODBoneMaps[nLOD][nBone];
		if (nLODBone < 0)
			continue; // we don't export it.

		// the chunk id for the bone mesh
		int nChunkIdMesh = pLOD->Bones.m_arrBones[nLODBone].getPhysGeomId (0);
		CryChunkedFile::MeshDesc* pMesh = pLOD->GetBoneMeshDesc(nChunkIdMesh);
		if (!pMesh)
		{
			if (nChunkIdMesh != -1)
				LogError ("Cannot find bone mesh for bone %d (lod bone %d) LOD %d (mesh chunk id expected: 0x%08X). No physics will be initialized for this bone.", nBone, nLODBone, nLOD, nChunkIdMesh);
			continue;
		}
		mapBoneMesh.insert (BoneMeshDescMap::value_type(nBone, pMesh));
	}

	if (mapBoneMesh.empty())
		return;

	// we export at least one bone in this LOD. Write the header
	m_Writer.AddChunk(CCF_BONE_GEOMETRY);
	CCFBoneGeometry bg;
	bg.numBGBones = mapBoneMesh.size();
	write (bg);

	CCFFileWriter SubChunks (m_fTarget);

	for (BoneMeshDescMap::iterator it = mapBoneMesh.begin(); it != mapBoneMesh.end(); ++it)
	{
		SubChunks.AddChunk (CCF_BG_BONE);
		writeBGBone(nLOD, it->first, it->second);
	}
}


void CGFConvertor::WriteMorphTargetSet ()
{
	CCFMorphTargetSet mts;
	CryChunkedFile::MeshDesc* pMesh = &(m_arrLODs[0]->arrMeshes[0]);
	CRenderMeshBuilder* pRMesh = &m_arrRenderMeshes[0];
	mts.numMorphTargets = pMesh->arrMorphTargets.size();

	if (!mts.numMorphTargets)
		return;
	m_Writer.AddChunk (CCF_MORPH_TARGET_SET);
	write (mts);
	writeMorphTargets(0);
}

void CGFConvertor::writeMorphTargets (unsigned nLOD)
{
	CryChunkedFile* pSource = m_arrLODs[nLOD];
	CryChunkedFile::MeshDesc* pMesh = &pSource->arrMeshes[0];
	unsigned numMorphTargets = pMesh->arrMorphTargets.size();
	CRenderMeshBuilder*pRMesh = &m_arrRenderMeshes[nLOD];

	// build the array of inverse-default-global matrices from the bone infos
	// These matrices are required by the morph skin builder to determine the offsets
	// of morphed vertices relatively to the skin
	// the default global matrices are used to recreate the skin
	unsigned numBones = pSource->Bones.numBones();
	std::vector<Matrix34> arrMatInvDef, arrMatDef;
	arrMatInvDef.resize (numBones);
	arrMatDef.resize (numBones);
	for (unsigned nBone = 0; nBone < numBones; ++nBone)
	{
		arrMatInvDef[nBone] = pSource->Bones.getBoneByIndex(nBone).getInvDefGlobal();
		if (pSource->Bones.hasInitPos ())
			arrMatDef[nBone] = pSource->Bones.getInitPosMatrixByIndex(nBone);
		else
		{
			arrMatDef[nBone] = arrMatInvDef[nBone];
			arrMatDef[nBone].Invert(); // since the matrix is orthogonal, this can be made simpler..
		}
	}

	CRCSkinVertexSource VertexSource (*pRMesh, *pMesh, &arrMatDef[0]);


	CrySkinMorphBuilder builder (&VertexSource, &arrMatInvDef[0], numBones);

	CCFFileWriter SubChunks (m_fTarget);
	for (unsigned nMorphTarget = 0; nMorphTarget < numMorphTargets; ++nMorphTarget)
	{
		CryChunkedFile::MorphTargetDesc* pMorphTarget = &pMesh->arrMorphTargets[nMorphTarget];
		CrySkinMorph Skin;

		std::vector <SMeshMorphTargetVertex> arrMorphVertices;
		arrMorphVertices.resize (pMorphTarget->numMorphVertices);
		Matrix34 tmNode;
		if (pMesh->pNode)
			tmNode = pMesh->pNode->getWorldTransform();
		for (unsigned i = 0; i < pMorphTarget->numMorphVertices; ++i)
		{
			arrMorphVertices[i] = pMorphTarget->pMorphVertices[i];
			arrMorphVertices[i].ptVertex = tmNode.TransformPoint(arrMorphVertices[i].ptVertex);
		}

		builder.initSkinMorph (&arrMorphVertices[0]/*pMorphTarget->pMorphVertices*/, pMorphTarget->numMorphVertices, &Skin);
		SubChunks.AddChunk(CCF_MORPH_TARGET);
		CCFMorphTarget mt;
		mt.numLODs = 1;
		write (mt);
		
		// write the skin - make the buffer(4-byte-aligned)
		std::vector<char>arrBuffer;
		arrBuffer.resize ((Skin.Serialize_PC (true, NULL, 0)+3)&~3);
		if (arrBuffer.empty())
			throw Error ("Can't (pre-)serialize morph target %s", pMorphTarget->strName.c_str());
		unsigned nBytesWritten = Skin.Serialize_PC(true, &arrBuffer[0], arrBuffer.size());
		if (!nBytesWritten)
			throw Error ("Can't serialize morph target %s", pMorphTarget->strName.c_str());
		writeArray (arrBuffer);

		// write the name
		unsigned nLen = pMorphTarget->strName.length() + 1; // write the ternimating null also
		write (pMorphTarget->strName.c_str(), nLen); 
		// pad the name with 0s if necessary
		if (nLen & 3)
		{
			char nPad[4] = {0,0,0,0};
			write(nPad, 4-(nLen&3));
		}
	}
}

void CGFConvertor::writeBGBone(unsigned nLOD, unsigned nBone, CryChunkedFile::MeshDesc* pMesh)
{
	CCFBGBone bgb;
	bgb.nBone = nBone;
	assert (pMesh);
	bgb.numVertices = pMesh->numVertices();
	bgb.numFaces    = pMesh->numFaces();
	write (bgb);

	//write the vertices
	std::vector<Vec3> arrVertices;
	arrVertices.resize (pMesh->numVertices());
	for (unsigned v = 0; v < pMesh->numVertices(); ++v)
		arrVertices[v] = pMesh->pVertices[v].p;
	writeArray (arrVertices);

	// immediately, write the faces followed by the materials
	std::vector<unsigned char> arrMtls;
	std::vector<CCFIntFace> arrFaces;
	arrFaces.resize (bgb.numFaces);
	arrMtls.resize (bgb.numFaces);
	unsigned nMtlOffset = getLODMtlOffset(nLOD);

	CMatEntityNameTokenizer tok;
	if (nLOD > 0)
	{
		// Remap face material ID by physics name.
		std::vector<MAT_ENTITY>& arrLodMtls = m_arrLODs[nLOD]->arrMtls;
		for (unsigned f = 0; f < bgb.numFaces; ++f)
		{
			arrFaces[f] = pMesh->pFaces[f];
			arrMtls[f] = pMesh->pFaces[f].MatID+nMtlOffset;

			// This can use some optimization.
			MAT_ENTITY& me = arrLodMtls[pMesh->pFaces[f].MatID];
			tok.tokenize(me.name);
			PhysMatIDMap::iterator it = m_physMtlToFaceIdMap.find(tok.szPhysMtl);
			if (it != m_physMtlToFaceIdMap.end())
			{
				int remappedMatId = it->second;
				arrMtls[f] = remappedMatId;
			}
			else
			{
				Log ("Warning: Lod%d Reference physics material %s which is not in the main LOD",(int)nLOD,tok.szPhysMtl );
			}

		}
	}
	else
	{
		for (unsigned f = 0; f < bgb.numFaces; ++f)
		{
			arrFaces[f] = pMesh->pFaces[f];
			arrMtls[f] = pMesh->pFaces[f].MatID+nMtlOffset;
		}
	}
	writeArray (arrFaces);
	writeArray (arrMtls);
}


void CGFConvertor::WriteGeometryInfo (unsigned nLOD)
{
	m_Writer.AddChunk (CCF_GEOMETRY_INFO);
	CCFAnimGeomInfo gi;

	CryChunkedFile* pSource = m_arrLODs[nLOD];
	const CryChunkedFile::MeshDesc& rMeshDesc = pSource->arrMeshes[0];
	const CRenderMeshBuilder& rRendMesh = m_arrRenderMeshes[nLOD];

	gi.numVertices = rMeshDesc.numVertices();
	gi.numExtTangents = rRendMesh.m_arrExtTangents.size();
	gi.numFaces = rMeshDesc.numFaces();
	gi.numIndices = rRendMesh.m_arrIndices.size();
	gi.numPrimGroups = rRendMesh.m_arrPrimGroups.size();
	write (gi);

	CCFFileWriter SubChunks (m_fTarget);
	SubChunks.AddChunk (CCF_GI_PRIMITIVE_GROUPS);
	writeArray (rRendMesh.m_arrPrimGroups);

	SubChunks.AddChunk(CCF_GI_INDEX_BUFFER);
	writeArray (rRendMesh.m_arrIndices);

	SubChunks.AddChunk(CCF_GI_EXT_TO_INT_MAP);
	writeArray (rRendMesh.m_arrExtTangMap);

	std::vector<CryUV> arrExtUVs;
	arrExtUVs.resize (rRendMesh.m_arrExtTangents.size());
	for (unsigned i = 0; i < rRendMesh.m_arrExtTangents.size(); ++i)
	{
		arrExtUVs[i] = rMeshDesc.pUVs[rRendMesh.m_arrExtUVMap[i]];
		// for some reason, the renderer flips v coordinate
		arrExtUVs[i].v = 1- arrExtUVs[i].v;
	}
	SubChunks.AddChunk(CCF_GI_EXT_UVS);
	writeArray (arrExtUVs);

	/*
	SubChunks.AddChunk(CCF_GI_EXT_TANGENTS);
	writeArray (rRendMesh.m_arrExtTangents);
	*/

//	SubChunks.AddChunk(CCF_STENCIL_SHADOW_CONNECTIVITY);
//	writeShadowConnectivity (nLOD);
	
	SubChunks.AddChunk (CCF_SKIN_VERTICES);
	writeVertexSkin (nLOD);

	SubChunks.AddChunk (CCF_SKIN_NORMALS);
	writeNormalSkin (nLOD);

	SubChunks.AddChunk(CCF_SKIN_TANGENTS);
	writeTangSkin(nLOD);

	SubChunks.AddChunk(CCF_GI_INT_FACES);
	writeIntFaces (nLOD);
}


void CGFConvertor::WriteHeader()
{
	m_Writer.AddChunk(CCF_HEADER_CCG);
	CCFCCGHeader Header;
	Header.numLODs = m_arrLODs.size();
	write (Header);
}


void CGFConvertor::WriteBoneInfo()
{
	CryChunkedFile* pMaster = m_arrLODs[0];
	m_Writer.AddChunk(CCF_BONE_DESC_ARRAY);
	CryBoneHierarchyLoader::CryBoneDescArray
		// bones of LOD 0 (main body)
		&arrBones = pMaster->Bones.m_arrBones;


	CCFBoneDescArrayHeader Header;
	Header.numBones = arrBones.size();
	write (Header);
	std::vector<byte> arrBuf;
	for (unsigned nBone = 0; nBone < arrBones.size(); ++nBone)
	{
		CryBoneDesc& rBone = arrBones[nBone];
		unsigned nSizeBuf = rBone.Serialize(true, NULL, 0);
		arrBuf.resize(nSizeBuf);
	
		if (nSizeBuf != rBone.Serialize(true, &arrBuf[0], nSizeBuf))
			throw Error ("Unexpected error while serializing bone \"%s\"", rBone.getNameCStr());

		writeArray (arrBuf);
		arrBuf.clear();
	}
}






//////////////////////////////////////////////////////////////////////////
// Writes out the material chunk into the compiled cgf
void CGFConvertor::WriteMaterials( const char *inszSrcFileName )
{
	m_Writer.AddChunk(CCF_MATERIALS);
	
	// the set of textures used in all the LODs
//	std::set<string> setTextures;

	// keep the track of how many materials are already written. If some lod uses
	// more materials, the last materials are taken from that lod
	unsigned numMtlsWritten = 0; 
	for (unsigned nLOD = 0; nLOD < m_arrLODs.size(); ++nLOD)
	{
		std::vector<MAT_ENTITY>& arrMtls = m_arrLODs[nLOD]->arrMtls;
		for (; numMtlsWritten < arrMtls.size(); ++numMtlsWritten)
		{
			MAT_ENTITY& me = arrMtls[numMtlsWritten];
			write(me);
			// dump the dependencies

			CMatEntityNameTokenizer tok;
			tok.tokenize(me.name);

			m_physMtlToFaceIdMap[tok.szPhysMtl] = numMtlsWritten;

			m_pContext->pRC->AddDependencyMaterial(inszSrcFileName,tok.szName,tok.szPhysMtl);

//Log ("  %s", me.name);
#define ADD_MAP(MAP) if (me.map_##MAP.name[0]) m_pContext->pRC->AddDependencyFile(inszSrcFileName,me.map_##MAP.name);
			ADD_MAP(a);
			ADD_MAP(d);
			ADD_MAP(o);
			ADD_MAP(b);
			ADD_MAP(s);
			ADD_MAP(g);
			ADD_MAP(detail);
			ADD_MAP(e);
			ADD_MAP(subsurf);
			ADD_MAP(displ);
#undef ADD_MAP
		}
	}

	/*
	Log ("Maps used:");
	for (std::set<string>::iterator it = setTextures.begin(); it != setTextures.end(); ++it)
		Log ("  %s", it->c_str());
	*/
}

void CGFConvertor::WriteVertices (CryChunkedFile* pSource)
{
	m_Writer.AddChunk(CCF_VERTICES);
	unsigned numVertices = pSource->arrMeshes[0].numVertices();
	const CryVertex* pVertices = pSource->arrMeshes[0].pVertices;
	for (unsigned nVertex = 0; nVertex < numVertices; ++nVertex)
		write (pVertices[nVertex].p);

}


void CGFConvertor::WriteNormals (CryChunkedFile* pSource)
{
	m_Writer.AddChunk(CCF_NORMALS);
	unsigned numVertices = pSource->arrMeshes[0].numVertices();
	std::vector<Vec3>& arrNormals = pSource->arrMeshes[0].arrNormals;
	writeArray (arrNormals);
}


const float fQuantizeTolerance = 4;

float Quantize (float x)
{
	return float(floor (x/fQuantizeTolerance+0.5)*fQuantizeTolerance);
}

// rounds up the x,y,z values of the vector to allow more coarse connectivity
Vec3 Quantize (const Vec3& v)
{
	return v;
	//return Vec3(Quantize(v.x), Quantize(v.y),Quantize(v.z));
}
/*
// Creates and serialize the connectivity info for the character
void CGFConvertor::writeShadowConnectivity (unsigned nLOD)
{
	CryChunkedFile* pSource = m_arrLODs[nLOD];

	CStencilShadowConnectivityBuilder Builder;
	if (pSource->arrMeshes.size() != 1)
		throw Error ("Unexpected number of meshes in CGF (%d)", pSource->arrMeshes.size());

	CryChunkedFile::MeshDesc& rMesh = pSource->arrMeshes[0];
	unsigned numFaces = rMesh.numFaces(), numVertices = rMesh.numVertices();
	Builder.ReserveForTriangles(numFaces,numVertices);
	//Builder.SetWeldTolerance(0.1f);
	for (unsigned i = 0; i < numFaces; ++i)
	{
		const CryFace& rFace = rMesh.pFaces[i];
		if (!pSource->DoesMtlCastShadow(rFace.MatID))
			continue;

		// with welding
		unsigned a = rFace.v0, b = rFace.v1, c = rFace.v2;
		Builder.AddTriangleWelded (a, b, c, Quantize(rMesh.pVertices[a].p), Quantize(rMesh.pVertices[b].p), Quantize(rMesh.pVertices[c].p));
	}

	if (Builder.numOrphanedEdges() > 0)
		Log ("Warning: Shadow Connectivity Builder: %u orphaned edges discovered in the model %s", Builder.numOrphanedEdges(), (const char*)m_pContext->sourceFile.GetString());

	IStencilShadowConnectivity* pConnectivity = Builder.ConstructConnectivity();

	if (!pConnectivity)
		throw Error ("Could not construct connectivity");

	unsigned nRequiredSize = pConnectivity->Serialize(true, NULL, 0);

	DWORD dwVertCount,dwTriCount;
	pConnectivity->GetStats(dwVertCount,dwTriCount);
	//Log("StencilEdgeConnectivity Stats: %d/%d Vertices %d/%d Faces. %u bytes.",dwVertCount,numVertices,dwTriCount,numFaces, nRequiredSize);

	std::vector<byte> arrConnectivityPacked;
	arrConnectivityPacked.resize (nRequiredSize);
	unsigned nWrittenBytes = pConnectivity->Serialize(true, &arrConnectivityPacked[0], arrConnectivityPacked.size());
	pConnectivity->Release();
	if (nRequiredSize != nWrittenBytes)
		throw Error ("Could not serialize connectivity: %d of %d bytes were written", nWrittenBytes, nRequiredSize);

	writeArray (arrConnectivityPacked);
}
*/

void CGFConvertor::writeVertexSkin (unsigned nLOD)
{
	CRCSkinVertexSource VSource (m_arrRenderMeshes[nLOD], m_arrLODs[nLOD]->arrMeshes[0]);
	CrySkinBuilder builder (&VSource);
	CrySkinFull VertexSkin;
	builder.initSkinFull (&VertexSkin);

	unsigned nSizeRequired = VertexSkin.Serialize_PC (true, NULL, 0);
	std::vector<byte> arrBuffer;
	arrBuffer.resize (nSizeRequired);
	unsigned nSizeWritten = VertexSkin.Serialize_PC (true, &arrBuffer[0], arrBuffer.size());
	if (nSizeRequired != nSizeWritten)
		throw Error ("Cannot serialize the vertex skin: %d of %d bytes serialized", nSizeWritten, nSizeRequired);
	writeArray (arrBuffer);
}


void CGFConvertor::writeNormalSkin (unsigned nLOD)
{
	CRCSkinNormalSource VSource (m_arrRenderMeshes[nLOD], m_arrLODs[nLOD]->arrMeshes[0], m_arrLODs[0]->Bones.m_arrBones);
	CrySkinBuilder builder (&VSource);
	CrySkinFull NormalSkin;
	builder.initSkinFull (&NormalSkin);

	unsigned nSizeRequired = NormalSkin.Serialize_PC (true, NULL, 0);
	std::vector<byte> arrBuffer;
	arrBuffer.resize (nSizeRequired);
	unsigned nSizeWritten = NormalSkin.Serialize_PC (true, &arrBuffer[0], arrBuffer.size());
	if (nSizeRequired != nSizeWritten)
		throw Error ("Cannot serialize the vertex skin: %d of %d bytes serialized", nSizeWritten, nSizeRequired);
	writeArray (arrBuffer);
}


void CGFConvertor::writeTangSkin (unsigned nLOD)
{
	CryChunkedFile* pSource = m_arrLODs[nLOD];
	CRCSkinVertexSource VSource (m_arrRenderMeshes[nLOD], pSource->arrMeshes[0]);

	// form the array of inverse-default-global matrices for the bones
	std::vector<Matrix34> arrMatInvDef;
	unsigned numBones = m_arrLODs[0]->Bones.numBones();
	arrMatInvDef.resize (numBones);
	unsigned i;
	for (i = 0; i < numBones; ++i)
		arrMatInvDef[i] = m_arrLODs[0]->Bones.getBoneByIndex(i).getInvDefGlobal();

	validateTangents(&VSource.getExtTangent(0), VSource.numExtTangents());

	CrySkinBasisBuilder builder (&VSource, &arrMatInvDef[0], numBones);
	builder.setDestinationInterval(0,0xFFFFFFFF);
	CrySkinRigidBasis TangSkin;
	builder.initRigidBasisSkin (&TangSkin);

	//////////////////////////////////////////////////////////////////////////
	// Validation code - calculates the tangents in the default pose and compares them to the 
	// source tangents supplied by the render mesh
	std::vector<SPipTangentsA> arrBases;
	arrBases.resize (VSource.numExtTangents());
	std::vector<Matrix34> arrMatDef;
	arrMatDef.resize(numBones);
	for (i = 0; i < numBones; ++i)
	{
		arrMatDef[i] = arrMatInvDef[i].GetInverted();
	}
	TangSkin.skin (&arrMatDef[0], &arrBases[0]);

	// the minimal cosine between the calculated and the original tangents
	float fMinCos = 1;
	// compare the calculated tangents with those supplied by the RenderMesh
	for (i = 0; i < VSource.numExtTangents(); ++i)
	{
		float fCos;
		fCos = VSource.getExtTangent(i).tangent * arrBases[i].m_Tangent;
		if (fCos < fMinCos && fabs(fCos)>1e-2 )
			fMinCos = fCos;
		fCos = VSource.getExtTangent(i).tnormal * arrBases[i].m_TNormal;
		if (fCos < fMinCos && fabs(fCos)>1e-2)
			fMinCos = fCos;
		fCos = VSource.getExtTangent(i).binormal * arrBases[i].m_Binormal;
		if (fCos < fMinCos && fabs(fCos)>1e-2)
			fMinCos = fCos;
	}
	Log ("Validated tangents difference: the error is no more than %g degrees", cry_acosf (fMinCos)*180/M_PI);

	//
	//////////////////////////////////////////////////////////////////////////

	unsigned nSizeRequired = TangSkin.Serialize (true, NULL, 0);
	std::vector<byte> arrBuffer;
	arrBuffer.resize (nSizeRequired);
	unsigned nSizeWritten = TangSkin.Serialize (true, &arrBuffer[0], arrBuffer.size());
	if (nSizeRequired != nSizeWritten)
		throw Error ("Cannot serialize the tangent skin: %d of %d bytes serialized", nSizeWritten, nSizeRequired);
	writeArray (arrBuffer);
}

// validate the calculated tangents from which the tangent skin will be formed 
void CGFConvertor::validateTangents (const TangData* pTangents, unsigned numTangents)
{
	unsigned numDegraded = 0, numLeftHanded = 0, numNonOrthogonal = 0, numNonUnit = 0;
	float fMaxEDTB = 0, fMaxEDTN = 0, fMaxEDBN = 0;
	for (unsigned i = 0; i < numTangents; ++i)
	{
		const TangData& td = pTangents[i];
		SBasisProperties Prop (td);
		if (Prop.bMatrixDegraded)
			++numDegraded;
		else
		{
			if (Prop.bLeftHanded)
				++numLeftHanded;
			if (!Prop.isOrthogonal())
				++numNonOrthogonal;
			if (!isUnit(td,1e-3f))
				++numNonUnit;
		}

		fMaxEDTB = max(fMaxEDTB, Prop.fErrorDegTB);
		fMaxEDTN = max(fMaxEDTN, Prop.fErrorDegTN);
		fMaxEDBN = max(fMaxEDBN, Prop.fErrorDegBN);
	}
	if (numNonOrthogonal|numNonUnit)
	{
		LogWarning ("Substandard tangents found:");
		LogWarning ("  %d non-orthogonal, %d non-unit", numNonOrthogonal, numNonUnit);
		LogWarning ("  %d lefthanded, %d degraded", numLeftHanded, numDegraded);
	}
	else
	if (numDegraded)
		Log ("%d degraded tangents. %d of %d are left-handed", numDegraded, numLeftHanded, numTangents);
	Log ("Max deviation from ortho angles: max[Tangent^Binormal]=%.2f, max[Tangent^Normal]=%.2f, max[Binormal^Normal]=%.2f", fMaxEDTB, fMaxEDTN, fMaxEDBN);
}

void CGFConvertor::writeIntFaces (unsigned nLOD)
{
	CryChunkedFile* pSource = m_arrLODs[nLOD];
	std::vector<CCFIntFace> arrFaces;
	std::vector<CCFIntFaceMtlID> arrFaceMtls;
	if (pSource->arrMeshes.size()!=1)
		throw ("Multiple mesh not supported");
	const CryChunkedFile::MeshDesc& rMesh = pSource->arrMeshes[0];
	arrFaces.resize (rMesh.numFaces());
	arrFaceMtls.resize (rMesh.numFaces());
	
	unsigned nOffset = getLODMtlOffset (nLOD);

	for (unsigned nFace = 0; nFace < rMesh.numFaces(); ++nFace)
	{
		const CryFace& rFace = rMesh.pFaces[nFace];
		arrFaces[nFace] = rFace;
		arrFaceMtls[nFace] = rFace.MatID + nOffset;
	}

	writeArray (arrFaces);
	writeArray (arrFaceMtls);
}

// !!!OBSOLETE!!!
// calculates the number of materials used by the previous LODs (< than given)
// this is to offset the mtl numbers of the nLOD to use the range of materials
// belonging to that LOD
// !!!OBSOLETE!!!
unsigned CGFConvertor::getLODMtlOffset (unsigned nLOD)
{
	// WARNING
	// According to the new paradigm, each LOD uses the same materials as LOD0
	return 0;
	/*
	unsigned nOffset = 0, i;
	for (i = 0; i < nLOD && i < m_arrLODs.size(); ++i)
		nOffset += m_arrLODs[i]->arrMtls.size();
	return nOffset;
	*/
}


void CGFConvertor::WriteLights()
{
	std::vector<CBoneLightBindInfo>arrLights;
	CryChunkedFile* pSource = m_arrLODs[0];
	arrLights.resize (pSource->m_numBoneLightBinds);
	unsigned i;
	for (i = 0; i < pSource->m_numBoneLightBinds; ++i)
	{
		SBoneLightBind Bind = pSource->m_pBoneLightBind[i];

		if (Bind.nBoneId >= pSource->Bones.numBones())
			throw Error ("Bone %d out of range in bone-light binding %d", Bind.nBoneId, i);

		Bind.nBoneId = pSource->Bones.mapIdToIndex(Bind.nBoneId);
		// find the light chunk and the chunk of the node of that light
		const LIGHT_CHUNK_DESC* pLightChunk = pSource->GetLightDesc(Bind.nLightChunkId);
		// the node chunk is required to determine the light's transformation
		// (it's stored in the light's node)
		const NODE_CHUNK_DESC* pNodeChunk = pSource->GetObjectNodeDesc(Bind.nLightChunkId)->pDesc;

		if (!pLightChunk)
			throw Error ("Invalid light chunk id %d in light-bone binding %d", Bind.nLightChunkId, i);

		if (!pNodeChunk)
			LogWarning ("No node chunk for light %d", Bind.nLightChunkId);
		
		arrLights[i].load (Bind, *pLightChunk, pNodeChunk?pNodeChunk->name:"_hs_Unknown", 1);
	}

	if (arrLights.empty())
		return;

	std::sort (arrLights.begin(), arrLights.end());

	m_Writer.AddChunk(CCF_CHAR_LIGHT_DESC);
	CCFCharLightDesc Header;
	Header.numLights = arrLights.size();
	Header.numLocalLights = 0;
	for (i = 0; i < arrLights.size(); ++i)
		if (!arrLights[i].isLocal())
			break;
	write (Header);
	std::vector<char> arrData;
	for (i = 0; i < arrLights.size(); ++i)
	{
		CBoneLightBindInfo& rLight = arrLights[i];
		unsigned nRequiredBytes = (rLight.Serialize(true, NULL, 0) + 3)&~3;
		if (arrData.size() < nRequiredBytes)
		{
			arrData.clear();
			arrData.resize (nRequiredBytes);
		}
		unsigned numBytesWritten = rLight.Serialize(true, &arrData[0], nRequiredBytes);
		if (!numBytesWritten || ((numBytesWritten+3)&~3) != nRequiredBytes)
			throw Error ("Cannot write bone light", rLight.getBone());
		write (&arrData[0], nRequiredBytes);
	}
}


// this should retrieve the timestamp of the convertor executable:
// when it was created by the linker, normally. This date/time is used to
// compare with the compiled file date/time and even if the compiled file
// is not older than the source file, it will be recompiled if it's older than the
// convertor
DWORD CGFConvertor::GetTimestamp() const
{
	return GetTimestampForLoadedLibrary(g_hInst);
}

string CGFConvertor::GetCalFilePath()
{
	string strFileNoExt = m_pContext->getSourcePath();
	CryStringUtils::StripFileExtension(strFileNoExt);
	return strFileNoExt+".cal";
}

string CGFConvertor::GetSourceDir()
{
	string strFileNoExt = m_pContext->getSourcePath();
	return CryStringUtils::GetParentDirectory(strFileNoExt);
}

bool CGFConvertor::isAnimatedFastCheck( const char *filename )
{
	bool bStaticCGF = false;
	m_pContext->config->Get("StaticCGF", bStaticCGF);
	if(bStaticCGF)
		return false;

	if (stricmp(CryStringUtils::FindExtension(filename), "chr"))
		return false; // only chr files can be characters

	return true;
}

// Reads the CAL file or the list of caf files in the same directory with the corresponding name,
// and writes the animation list chunk with the animation names, file paths and compiled directives

void CGFConvertor::WriteAnimListWithCAL(FILE* fCal)
{
	assert (fCal);
	// Load cal file and load animations from animations folder
	// make anim folder name
	// make search path
	string strFileNameNoExt = m_pContext->getSourcePath();
	CryStringUtils::StripFileExtension(strFileNameNoExt);
	string strDirName = CryStringUtils::GetParentDirectory(strFileNameNoExt);
	string strAnimDirName = CryStringUtils::GetParentDirectory(strDirName, 2) + "\\animations";
	Vec3 vOffset(0,0,0); // model offset

	// the flags applicable to the currently being loaded animation
	unsigned nAnimFlags = 0;

	m_Writer.AddChunk(CCF_ANIM_SCRIPT);

	CCFFileWriter SubChunks (m_fTarget);

	for (int i = 0; fCal && !feof(fCal); ++i)
	{
		char sBuffer[0x200]="";
		fgets(sBuffer,sizeof(sBuffer),(FILE*)fCal);
		char*szAnimName;
		char*szFileName;

		if(sBuffer[0] == '/' || sBuffer[0]=='\r' || sBuffer[0]=='\n' || sBuffer[0]==0)
			continue;

		szAnimName = strtok (sBuffer, " \t\n\r=");
		if (!szAnimName)
			continue;
		szFileName = strtok(NULL, " \t\n\r=");
		if (!szFileName || szFileName[0] == '?')
		{
			//RegisterDummyAnimation(szAnimName);
			SubChunks.AddChunk(CCF_ANIM_SCRIPT_DUMMYANIM);
			writeString (szAnimName);
			continue;
		}

		if (szAnimName[0] == '/' && szAnimName[1] == '/')
			continue; // comment

		{ // remove firsrt '\' and replace '/' with '\'
			while(szFileName[0]=='/' || szFileName[0]=='\\')
				memmove(szFileName,szFileName+1,sizeof(szFileName)-1);

			for(char * p = szFileName+strlen(szFileName); p>=szFileName; p--)
				if(*p == '/')
					*p = '\\';
		}

		// process the possible directives
		if (szAnimName[0] == '$')
		{
			const char* szDirective = szAnimName + 1;
			if (!stricmp(szDirective, "AnimationDir")
				||!stricmp(szDirective, "AnimDir")
				||!stricmp(szDirective, "AnimationDirectory")
				||!stricmp(szDirective, "AnimDirectory"))
			{
				// delete the trailing slashes
				for (char* p = szFileName+strlen(szFileName)-1; p >= szFileName && *p == '\\' || *p == '/'; --p)
					*p = '\0';
				if (*szFileName)
				{
					SubChunks.AddChunk(CCF_ANIM_SCRIPT_ANIMDIR);
					writeString(szFileName);

					strAnimDirName = strDirName + "\\" + szFileName;
				}
			}
			else
			if (!stricmp (szDirective, "ModelOffsetX"))
			{
				float fValue;
				if (sscanf (szFileName, "%f", &fValue) != 1)
					LogWarning("CAL Directive ModelOffsetX %s couldn't be interpreted", szFileName);
				else
					vOffset.x = fValue;
			}
			else
			if (!stricmp (szDirective, "ModelOffsetY"))
			{
				float fValue;
				if (sscanf (szFileName, "%f", &fValue) != 1)
					LogWarning("CAL Directive ModelOffsetY %s couldn't be interpreted", szFileName);
				else
					vOffset.y = fValue;
			}
			else
			if (!stricmp (szDirective, "ModelOffsetZ"))
			{
				float fValue;
				if (sscanf (szFileName, "%f", &fValue) != 1)
					LogWarning("CAL Directive ModelOffsetZ %s couldn't be interpreted", szFileName);
				else
					vOffset.z = fValue;
			}
			else
			if (!stricmp(szDirective, "AutoUnload"))
			{
				switch (CryStringUtils::toYesNoType(szFileName))
				{
				case CryStringUtils::nYNT_Yes:
					nAnimFlags &= ~GlobalAnimation::FLAGS_DISABLE_AUTO_UNLOAD;
					break;
				case CryStringUtils::nYNT_No:
					nAnimFlags |= GlobalAnimation::FLAGS_DISABLE_AUTO_UNLOAD;
					break;
				default:
					LogWarning("Invalid option for AutoUnload directive (must be yes or no) in file %s.cal", strFileNameNoExt.c_str());
					break;
				}
			}
			else
			if (!stricmp(szDirective, "DelayLoad"))
			{
				switch (CryStringUtils::toYesNoType(szFileName))
				{
				case CryStringUtils::nYNT_Yes:
					nAnimFlags &= ~GlobalAnimation::FLAGS_DISABLE_DELAY_LOAD;
					break;
				case CryStringUtils::nYNT_No:
					nAnimFlags |= GlobalAnimation::FLAGS_DISABLE_DELAY_LOAD;
					break;
				default:
					LogWarning("Invalid option for DelayLoad directive (must be yes or no) in file %s.cal", strFileNameNoExt.c_str());
					break;
				}
			}
			else
				LogWarning("Unknown directive %s", szDirective);
			continue;
		}

		WriteAnimInfo (SubChunks, szAnimName, strAnimDirName + "\\" + szFileName, nAnimFlags);
	}

	if (vOffset != Vec3(0,0,0))
	{
		SubChunks.AddChunk(CCF_ANIM_SCRIPT_MODELOFFSET);
		write (vOffset);
	}
}

void CGFConvertor::WriteAnimInfo (CCFFileWriter &rSubChunks, const char* szAnimName, const string& strFilePath, unsigned nFlags)
{
	CCFAnimInfo animInfo;
	LoadAnimInfo (strFilePath, animInfo);
	animInfo.nAnimFlags = nFlags;

	rSubChunks.AddChunk(CCF_ANIM_SCRIPT_ANIMINFO);
	write (animInfo);
	writeString (szAnimName);
	writeString (strFilePath.c_str());
}

void CGFConvertor::WriteAnimListNoCAL()
{
	m_Writer.AddChunk(CCF_ANIM_SCRIPT);

	CCFFileWriter SubChunks (m_fTarget);

	// load the default pose first
	string strFileNoExt = m_pContext->getSourcePath();
	CryStringUtils::StripFileExtension(strFileNoExt);
	string strDirName = CryStringUtils::GetParentDirectory (strFileNoExt);
	string strDefaultPose = (strFileNoExt + "_default.caf").c_str();

	if (GetFileAttributes(strDefaultPose.c_str()) != -1)
		// we need default animation immediately, but unlikely we'll need it in the future (so we can unload it)
		WriteAnimInfo(SubChunks, "default", strDefaultPose, GlobalAnimation::FLAGS_DEFAULT_ANIMATION);

	// the name of the base cgf (before the underscore) + 1
	unsigned nBaseNameLength = strFileNoExt.length() - strDirName.length();
	__finddata64_t fd;
	long hFF = _findfirst64 ((strFileNoExt + "_*.caf").c_str(), &fd);
	if (hFF != -1)
	{
		do
		{
			string strFileName = strDirName + "\\" + fd.name;

			if(!stricmp(strDefaultPose.c_str(), strFileName.c_str()))
				// skip the default pose as it has already been loaded
				continue;

			//if (!stricmp(FindExtension(fileinfo.name), "caf")) // actually ,according to the search mask, this should be met automatically
			char* szExtension = CryStringUtils::StripFileExtension(fd.name);
			assert (!stricmp(szExtension, "caf"));
			assert (strlen(fd.name) > nBaseNameLength);

			const char* szAnimName = fd.name + nBaseNameLength;
			WriteAnimInfo (SubChunks, szAnimName, strFileName, 0);
		}
		while (_findnext64( hFF, &fd) != -1);
		_findclose (hFF);
	}
}

inline string MakeString (const char* szBuf, size_t nSizeBuf)
{
	return string (szBuf, CryStringUtils::strnlen(szBuf, szBuf+nSizeBuf));
}


void CGFConvertor::WriteSceneProps()
{
	m_Writer.AddChunk(CCF_USER_PROPERTIES);
	typedef std::map<string,string>PropMap;
	PropMap mapProps;
	for (CryChunkedFile_AutoArray::reverse_iterator itLod = m_arrLODs.rbegin(); itLod != m_arrLODs.rend(); ++itLod)
	{
		for (unsigned n = 0; n < (*itLod)->numSceneProps; ++n)
		{
			const SCENEPROP_ENTITY& Prop = (*itLod)->pSceneProps[n];
			mapProps.insert (PropMap::value_type(MakeString(Prop.name,sizeof(Prop.name)), MakeString(Prop.value, sizeof(Prop.value))));
		}
	}

	unsigned nWrittenBytes = 0;
	for (PropMap::iterator it = mapProps.begin(); it != mapProps.end(); ++it)
	{
		nWrittenBytes += writeString (it->first);
		nWrittenBytes += writeString (it->second);
	}

	// pad with up to 3 bytes to align on 4-byte boundary
	if (nWrittenBytes & 3)
	{
		char szPad[4] = {'\0','\0','\0','\0'};
		write (szPad, 4-(nWrittenBytes & 3));
	}
}

// loads the given animation info into the given structure
// returns false upon failure
bool CGFConvertor::LoadAnimInfo (const string& strFilePath, CCFAnimInfo &Anim)
{
	CChunkFile Reader;
	Anim.init();
	if (!Reader.Read(strFilePath.c_str()))
	{
		if (!(Anim.nAnimFlags & GlobalAnimation::FLAGS_DISABLE_LOAD_ERROR_LOG))
			LogError ("LoadAnimInfo: File loading error: %s, last error is: %s", strFilePath.c_str(), Reader.GetLastError());
		return false;
	}

	// check the file header for validity
	const FILE_HEADER& fh = Reader.GetFileHeader();

	if(fh.Version != AnimFileVersion || fh.FileType != FileType_Anim) 
	{
		LogError("LoadAnimInfo: file version error or not an animation file: %s", strFilePath.c_str());
		return false;
	}

	bool bTimingChunkFound = false;
	for (int nChunk = 0; nChunk < Reader.NumChunks (); ++nChunk)
	{
		// this is the chunk header in the chunk table at the end of the file
		const CHUNK_HEADER& chunkHeader = Reader.GetChunkHeader(nChunk);
		// this is the chunk raw data, starts with the chunk header/descriptor structure
		const void* pChunk = Reader.GetChunkData (nChunk);
		unsigned nChunkSize = Reader.GetChunkSize (nChunk);

		switch (chunkHeader.ChunkType)
		{
		case ChunkType_Controller:
			++Anim.numControllers;
			break;

		case ChunkType_Timing:
			{
				// memorize the timing info
				const TIMING_CHUNK_DESC* pTimingChunk = static_cast<const TIMING_CHUNK_DESC*> (pChunk);
				Anim.nTicksPerFrame  = pTimingChunk->TicksPerFrame;
				Anim.fSecsPerTick    = pTimingChunk->SecsPerTick;
				Anim.nRangeStart     = pTimingChunk->global_range.start;
				Anim.nRangeEnd       = pTimingChunk->global_range.end;
				bTimingChunkFound = true;
			}
			break;
		}
	}

	if (!bTimingChunkFound)
	{
		LogError("LoadAnimInfo: file doesn't contain Timing chunk: %s", strFilePath.c_str());
		return false;
	}
	return true;
}