//////////////////////////////////////////////////////////////////////////// // // 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 "AutoFile.h" #include #include "Dbghelp.h" #include "FileUtil.h" #include "PathUtil.h" #include "ResourceCompilerPC.h" #include "IRCLog.h" #include "CgfUtils.h" // CMatEntityNameTokenizer #include // 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), m_pStatCGFCompiler(NULL) { extern IConvertor* NewStatCGFCompiler(); m_pStatCGFCompiler = NewStatCGFCompiler(); } CGFConvertor::~CGFConvertor() { clear(); if (m_pStatCGFCompiler) m_pStatCGFCompiler->Release(); } IConvertor* CGFConvertor::getStatCGFCompiler() { return m_pStatCGFCompiler; } bool CGFConvertor::ProcessStatic( ConvertContext &cc ) { IConvertor* pConv = getStatCGFCompiler(); ConvertContext cc2 = cc; return pConv->Process(cc2); } ////////////////////////////////////////////////////////////////////////// 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?.cgf") &&isAnimatedFastCheck(strSourceFilePath.GetString())) { return true; // we ignore lods of existing animated objects } if (!isAnimatedFastCheck( strSourceFilePath.GetString() )) { if (!cc.config->GetAs("StaticCGF", true)) return true; return ProcessStatic(cc); } if (!cc.config->GetAs("AnimatedCGF", true)) return true; 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(); { CAutoFile fCal (GetCalFilePath().c_str(), "rt"); if (fCal) WriteAnimListWithCAL(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; } // 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 = m_pContext->pRC->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 UIntUIntMap; void BuildBoneCtrlMap (const std::vector& 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& 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 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& 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; if (isAnimatedFastCheck(cc.getSourcePath().GetString() )) { //specify output path cc.outputFile = Path::ReplaceExtension( cc.sourceFile,"ccg" ); cc.outputFolder = cc.masterFolder +CString("CCGF_CACHE") + "\\" + cc.outputFolder; } else m_pStatCGFCompiler->GetOutputFile(cc); 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 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 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].Invert44(); // 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 arrMorphVertices; arrMorphVertices.resize (pMorphTarget->numMorphVertices); Matrix44 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.TransformPointOLD(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::vectorarrBuffer; 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 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 arrMtls; std::vector 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& 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 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 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 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& 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::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& 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 Vec3d Quantize (const Vec3d& v) { return v; //return Vec3d(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 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 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 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 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 arrBases; arrBases.resize (VSource.numExtTangents()); std::vector arrMatDef; arrMatDef.resize(numBones); for (i = 0; i < numBones; ++i) { arrMatDef[i] = Matrix44::GetInverted44 (arrMatInvDef[i]); } 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 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 arrFaces; std::vector 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::vectorarrLights; 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 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), "cgf")) return false; // only cgf files can be animated cgfs string strFileNoExt = filename; CryStringUtils::StripFileExtension(strFileNoExt); /* CString tempfile = Path::GetFileName(filename); tempfile = tempfile.MakeLower(); if (tempfile.GetLength() > 5) {} */ if (GetFileAttributes((strFileNoExt+".cal").c_str())!= 0xFFFFFFFF) return true; __finddata64_t fi; int nFind = _findfirst64 ((strFileNoExt+"_*.caf").c_str(), &fi); if (nFind != -1) { _findclose (nFind); return true; } return false; } // 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"; Vec3d 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 != Vec3d(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::mapPropMap; 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) { CChunkFileReader Reader; Anim.init(); if (!Reader.open (strFilePath)) { 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 (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; }