/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "CGFLoader.h" // the 3d Engine dll (which loads particle files) // actually needs named type info for these types. // until particle loading from xml can be removed from the 3d engine // we must include type info names in order to be able to load // xml text that contains things like (x=0.2, y=0.5...) // This must be defined in any module that includes typeInfo_impl.h // and parses files with that kind of format inside it. #ifdef ENABLE_TYPE_INFO_NAMES #if !ENABLE_TYPE_INFO_NAMES #error ENABLE_TYPE_INFO_NAMES previously defined to 0 #endif #else #define ENABLE_TYPE_INFO_NAMES 1 #endif #include "TypeInfo_impl.h" #include "Common_TypeInfo2.h" #include #include #include "CryPath.h" #include "ReadOnlyChunkFile.h" #include "IStatObj.h" // #define DEBUG_DUMP_RBATCHES #define VERTEX_SCALE (0.01f) #define PHYSICS_PROXY_NODE "PhysicsProxy" #define PHYSICS_PROXY_NODE2 "$collision" #define PHYSICS_PROXY_NODE3 "$physics_proxy" enum { MAX_NUMBER_OF_BONES = 65534 }; #if !defined(FUNCTION_PROFILER_3DENGINE) #define FUNCTION_PROFILER_3DENGINE #endif #if !defined(LOADING_TIME_PROFILE_SECTION) #define LOADING_TIME_PROFILE_SECTION #endif #ifndef ARRAY_LENGTH # define ARRAY_LENGTH(arr) (sizeof(arr) / sizeof(arr[0])) #endif namespace { // Templated construct helper function using an inplace factory // // Called from the templated New function below. Returns a typed // pointer to the inplace constructed object. template T* Construct( const InPlaceFactory& factory, void* (*pAllocFnc)(size_t)) { return reinterpret_cast(factory.template apply(pAllocFnc(sizeof(T)))); } // Templated construct helper function using an inplace factory // // Called from the templated New function below. Returns a typed // pointer to the inplace constructed object. template T* Construct(void* (*pAllocFnc)(size_t)) { return new (pAllocFnc(sizeof(T)))T; } // Templated destruct helper function // // Calls the object's destructor and returns a void pointer to the storage template void Destruct(T* obj, void(*pDestructFnc)(void*)) { obj->~T(); pDestructFnc(reinterpret_cast(obj)); } } ////////////////////////////////////////////////////////////////////////// CLoaderCGF::CLoaderCGF( AllocFncPtr pAllocFnc, DestructFncPtr pDestructFnc, bool bAllowStreamSharing) : m_pAllocFnc(pAllocFnc) , m_pDestructFnc(pDestructFnc) { m_pChunkFile = NULL; ////////////////////////////////////////////////////////////////////////// // To find really used materials memset(MatIdToSubset, 0, sizeof(MatIdToSubset)); nLastChunkId = 0; ////////////////////////////////////////////////////////////////////////// m_bUseReadOnlyMesh = false; m_bAllowStreamSharing = bAllowStreamSharing; m_pCompiledCGF = 0; m_maxWeightsPerVertex = 4; } ////////////////////////////////////////////////////////////////////////// CLoaderCGF::~CLoaderCGF() { } ////////////////////////////////////////////////////////////////////////// CContentCGF* CLoaderCGF::LoadCGF(const char* filename, IChunkFile& chunkFile, ILoaderCGFListener* pListener, unsigned long nLoadingFlags) { CContentCGF* const pContentCGF = new CContentCGF(filename); if (!pContentCGF) { return NULL; } if (!LoadCGF(pContentCGF, filename, chunkFile, pListener, nLoadingFlags)) { delete pContentCGF; return NULL; } return pContentCGF; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadCGF(CContentCGF* pContentCGF, const char* filename, IChunkFile& chunkFile, ILoaderCGFListener* pListener, unsigned long nLoadingFlags) { LOADING_TIME_PROFILE_SECTION; if (!chunkFile.IsLoaded()) { if (!chunkFile.Read(filename)) { m_LastError = chunkFile.GetLastError(); return false; } } if (gEnv) { //Update loading screen and important tick functions SYNCHRONOUS_LOADING_TICK(); } return LoadCGFWork(pContentCGF, filename, chunkFile, pListener, nLoadingFlags); } bool CLoaderCGF::LoadCGFFromMem(CContentCGF* pContentCGF, const void* pData, size_t nDataLen, IChunkFile& chunkFile, ILoaderCGFListener* pListener, unsigned long nLoadingFlags) { LOADING_TIME_PROFILE_SECTION; if (!chunkFile.IsLoaded()) { if (!chunkFile.ReadFromMemory(pData, (int)nDataLen)) { m_LastError = chunkFile.GetLastError(); return false; } } if (gEnv) { //Update loading screen and important tick functions SYNCHRONOUS_LOADING_TICK(); } return LoadCGFWork(pContentCGF, pContentCGF->GetFilename(), chunkFile, pListener, nLoadingFlags); } bool CLoaderCGF::LoadCGFWork(CContentCGF* pContentCGF, const char* filename, IChunkFile& chunkFile, ILoaderCGFListener* pListener, unsigned long nLoadingFlags) { m_pListener = pListener; m_bUseReadOnlyMesh = chunkFile.IsReadOnly(); cry_strcpy(m_filename, filename); m_pChunkFile = &chunkFile; // Load mesh from chunk file. if (!pContentCGF) { m_LastError.Format("no valid CContentCGF instance for cgf file: %s", m_filename); return false; } m_pCGF = pContentCGF; { const char* const pExt = PathUtil::GetExt(filename); m_IsCHR = !azstricmp(pExt, "chr") || !azstricmp(pExt, "chrp") || !azstricmp(pExt, "chrm") || !azstricmp(pExt, "skin") || !azstricmp(pExt, "skinp") || !azstricmp(pExt, "skinm") || !azstricmp(pExt, "skel"); } const bool bJustGeometry = (nLoadingFlags& IStatObj::ELoadingFlagsJustGeometry) != 0; if (!LoadChunks(bJustGeometry)) { return false; } for (int i = 0; i < m_pChunkFile->NumChunks(); i++) { if (m_pChunkFile->GetChunk(i)->bSwapEndian) { m_pCGF->m_bConsoleFormat = true; break; } } if (!bJustGeometry) { if (m_pCGF->GetMaterialCount() > 0) { m_pCGF->SetCommonMaterial(m_pCGF->GetMaterial(0)); } } ProcessNodes(); if (gEnv) { SYNCHRONOUS_LOADING_TICK(); } if (!bJustGeometry) { if (!ProcessSkinning()) { return false; } } if (pListener && pListener->IsValidationEnabled()) { const char* pErrorDescription = 0; if (!m_pCGF->ValidateMeshes(&pErrorDescription)) { Warning( "!Invalid meshes (%s) found in file: %s\n" "The file is corrupt (possibly generated with old/buggy RC) -- please re-export it with newest RC", pErrorDescription, m_filename); } } m_pListener = 0; return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadChunks(bool bJustGeometry) { LOADING_TIME_PROFILE_SECTION; m_CompiledBones = false; m_CompiledMesh = 0; m_CompiledBonesBoxes = false; m_numBonenameList = 0; m_numBoneInitialPos = 0; m_numMorphTargets = 0; m_numBoneHierarchy = 0; // Load Nodes. const uint32 numChunk = m_pChunkFile->NumChunks(); m_pCGF->GetSkinningInfo()->m_arrPhyBoneMeshes.clear(); m_pCGF->GetSkinningInfo()->m_numChunks = numChunk; for (uint32 i = 0; i < numChunk; i++) { IChunkFile::ChunkDesc* const pChunkDesc = m_pChunkFile->GetChunk(i); if (!bJustGeometry) { if (m_IsCHR) { switch (pChunkDesc->chunkType) { case ChunkType_BoneNameList: m_numBonenameList++; if (!ReadBoneNameList(pChunkDesc)) { return false; } break; case ChunkType_BoneInitialPos: m_numBoneInitialPos++; if (!ReadBoneInitialPos(pChunkDesc)) { return false; } break; case ChunkType_BoneAnim: m_numBoneHierarchy++; if (!ReadBoneHierarchy(pChunkDesc)) { return false; } break; case ChunkType_BoneMesh: if (!ReadBoneMesh(pChunkDesc)) { return false; } break; case ChunkType_MeshMorphTarget: m_numMorphTargets++; if (!ReadMorphTargets(pChunkDesc)) { return false; } break; //--------------------------------------------------------- //--- chunks for compiled characters ---- //--------------------------------------------------------- case ChunkType_CompiledBones: if (!ReadCompiledBones(pChunkDesc)) { return false; } break; case ChunkType_CompiledPhysicalBones: if (!ReadCompiledPhysicalBones(pChunkDesc)) { return false; } break; case ChunkType_CompiledPhysicalProxies: if (!ReadCompiledPhysicalProxies(pChunkDesc)) { return false; } break; case ChunkType_CompiledMorphTargets: if (!ReadCompiledMorphTargets(pChunkDesc)) { return false; } break; case ChunkType_CompiledIntFaces: if (!ReadCompiledIntFaces(pChunkDesc)) { return false; } break; case ChunkType_CompiledIntSkinVertices: if (!ReadCompiledIntSkinVertice(pChunkDesc)) { return false; } break; case ChunkType_CompiledExt2IntMap: if (!ReadCompiledExt2IntMap(pChunkDesc)) { return false; } break; case ChunkType_BonesBoxes: if (!ReadCompiledBonesBoxes(pChunkDesc)) { return false; } break; } } switch (pChunkDesc->chunkType) { //--------------------------------------------------------- //--- chunks for CGA-objects ----------------------- //--------------------------------------------------------- case ChunkType_ExportFlags: if (!LoadExportFlagsChunk(pChunkDesc)) { return false; } break; case ChunkType_Node: if (!LoadNodeChunk(pChunkDesc, false)) { return false; } break; case ChunkType_MtlName: if (!LoadMaterialFromChunk(pChunkDesc->chunkId)) { return false; } break; case ChunkType_BreakablePhysics: if (!ReadCompiledBreakablePhysics(pChunkDesc)) { return false; } break; case ChunkType_FoliageInfo: if (!LoadFoliageInfoChunk(pChunkDesc)) { return false; } break; } } else { switch (pChunkDesc->chunkType) { case ChunkType_Node: if (!LoadNodeChunk(pChunkDesc, true)) { return false; } break; } } } return true; } bool CLoaderCGF::ReadBoneInitialPos(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->chunkVersion != BONEINITIALPOS_CHUNK_DESC_0001::VERSION) { m_LastError.Format("BoneInitialPos chunk is of unknown version %d", pChunkDesc->chunkVersion); return false; } BONEINITIALPOS_CHUNK_DESC_0001* const pBIPChunk = (BONEINITIALPOS_CHUNK_DESC_0001*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pBIPChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; const uint32 numBones = pBIPChunk->numBones; SBoneInitPosMatrix* const pDefMatrix = (SBoneInitPosMatrix*)(pBIPChunk + 1); SwapEndian(pDefMatrix, numBones, bSwapEndianness); m_arrInitPose34.resize(numBones, IDENTITY); for (uint32 nBone = 0; nBone < numBones; ++nBone) { m_arrInitPose34[nBone].m00 = pDefMatrix[nBone].mx[0][0]; m_arrInitPose34[nBone].m01 = pDefMatrix[nBone].mx[1][0]; m_arrInitPose34[nBone].m02 = pDefMatrix[nBone].mx[2][0]; m_arrInitPose34[nBone].m03 = pDefMatrix[nBone].mx[3][0] * VERTEX_SCALE; m_arrInitPose34[nBone].m10 = pDefMatrix[nBone].mx[0][1]; m_arrInitPose34[nBone].m11 = pDefMatrix[nBone].mx[1][1]; m_arrInitPose34[nBone].m12 = pDefMatrix[nBone].mx[2][1]; m_arrInitPose34[nBone].m13 = pDefMatrix[nBone].mx[3][1] * VERTEX_SCALE; m_arrInitPose34[nBone].m20 = pDefMatrix[nBone].mx[0][2]; m_arrInitPose34[nBone].m21 = pDefMatrix[nBone].mx[1][2]; m_arrInitPose34[nBone].m22 = pDefMatrix[nBone].mx[2][2]; m_arrInitPose34[nBone].m23 = pDefMatrix[nBone].mx[3][2] * VERTEX_SCALE; m_arrInitPose34[nBone].OrthonormalizeFast(); // for some reason Max supplies unnormalized matrices. } return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadMorphTargets(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; #if !defined(RESOURCE_COMPILER) m_LastError.Format("%s tried to load a noncompiled MeshMorphTarget chunk %i", __FUNCTION__, (int)pChunkDesc->chunkId); return false; #else if (pChunkDesc->chunkVersion != MESHMORPHTARGET_CHUNK_DESC_0001::VERSION) { m_LastError.Format("MeshMorphTarget chunk %i is of unknown version %i", (int)pChunkDesc->chunkId, (int)pChunkDesc->chunkVersion); return false; } // the chunk must at least contain its header and the name (min 2 bytes) if (pChunkDesc->size <= sizeof(MESHMORPHTARGET_CHUNK_DESC_0001)) { m_LastError.Format("MeshMorphTarget chunk %i: Bad size: %i", (int)pChunkDesc->chunkId, (int)pChunkDesc->size); return false; } if (m_vertexOldToNew.empty()) { m_LastError.Format("MeshMorphTarget chunk %i: main mesh was not loaded yet or its type is not Skin.", (int)pChunkDesc->chunkId); return false; } MESHMORPHTARGET_CHUNK_DESC_0001* const pMeshMorphTarget = (MESHMORPHTARGET_CHUNK_DESC_0001*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pMeshMorphTarget, bSwapEndianness); pChunkDesc->bSwapEndian = false; const uint32 oldVertexCount = pMeshMorphTarget->numMorphVertices; if (oldVertexCount <= 0) { m_LastError.Format("MeshMorphTarget chunk %i: Bad # of vertices: %i", (int)pChunkDesc->chunkId, (int)oldVertexCount); return false; } if (oldVertexCount > m_vertexOldToNew.size()) { m_LastError.Format("MeshMorphTarget chunk %i: bad # of morph target vertices: %i (# of entries in the remapping table is %i).", (int)pChunkDesc->chunkId, (int)oldVertexCount, (int)m_vertexOldToNew.size()); return false; } SMeshMorphTargetVertex* const pSrcVertices = (SMeshMorphTargetVertex*)(pMeshMorphTarget + 1); SwapEndian(pSrcVertices, (size_t)oldVertexCount, bSwapEndianness); // Remap vertices to match main mesh's remapping for (uint32 i = 0; i < oldVertexCount; ++i) { const unsigned oldIdx = pSrcVertices[i].nVertexId; if (oldIdx >= m_vertexOldToNew.size()) { m_LastError.Format("MeshMorphTarget chunk %i: bad vertex index (%u) at element %u", (int)pChunkDesc->chunkId, oldIdx, (unsigned)i); return false; } const int newIdx = m_vertexOldToNew[oldIdx]; if (newIdx < 0) { m_LastError.Format("MeshMorphTarget chunk %i: bad remapping value (%i) at element %u", (int)pChunkDesc->chunkId, (int)newIdx, (unsigned)i); return false; } pSrcVertices[i].nVertexId = (unsigned)newIdx; } // Get rid of duplicated entries and also sort in ascending order of vertex indices struct CompareByVertexIndex { static bool less(const SMeshMorphTargetVertex& left, const SMeshMorphTargetVertex& right) { return left.nVertexId < right.nVertexId; } }; std::sort(&pSrcVertices[0], &pSrcVertices[oldVertexCount], CompareByVertexIndex::less); uint32 newVertexCount = 0; for (uint32 i = 0; i < oldVertexCount; ++i) { if (i == 0 || CompareByVertexIndex::less(pSrcVertices[i - 1], pSrcVertices[i])) { pSrcVertices[newVertexCount++] = pSrcVertices[i]; } } // Form results MorphTargets* const pMT = new MorphTargets; //store the mesh ID pMT->MeshID = pMeshMorphTarget->nChunkIdMesh; //store the name of morph-target const char* pName = (const char*)(pSrcVertices + oldVertexCount); pMT->m_strName = "#" + string(pName); //store the vertices&indices of morph-target pMT->m_arrIntMorph.resize(newVertexCount); memcpy(&pMT->m_arrIntMorph[0], pSrcVertices, sizeof(SMeshMorphTargetVertex) * newVertexCount); CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrMorphTargets.push_back(pMT); return true; #endif } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadBoneNameList(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->chunkVersion != BONENAMELIST_CHUNK_DESC_0745::VERSION) { m_LastError.Format("Unknown version of bone name list chunk"); return false; } // the chunk contains variable-length packed strings following tightly each other BONENAMELIST_CHUNK_DESC_0745* pNameChunk = (BONENAMELIST_CHUNK_DESC_0745*)(pChunkDesc->data); SwapEndian(*pNameChunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; const uint32 nGeomBones = pNameChunk->numEntities; // we know how many strings there are there; note that the whole bunch // of strings may be followed by padding zeros m_arrBoneNameTable.resize(nGeomBones, ""); // scan through all the strings (strings are zero-terminated) const char* const pNameListEnd = ((const char*)pNameChunk) + pChunkDesc->size; const char* pName = (const char*)(pNameChunk + 1); uint32 numNames = 0; while (*pName && pName < pNameListEnd && numNames < nGeomBones) { m_arrBoneNameTable[numNames] = pName; pName += m_arrBoneNameTable[numNames].size() + 1; numNames++; } if (numNames < nGeomBones) { m_LastError.Format("inconsistent bone name list chunk: only %d out of %d bone names have been read.", numNames, nGeomBones); return false; } return true; } ///////////////////////////////////////////////////////////////////////////////////// // loads the root bone (and the hierarchy) and returns true if loaded successfully // this gets called only for LOD 0 ///////////////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadBoneHierarchy(IChunkFile::ChunkDesc* pChunkDesc) { if (pChunkDesc->chunkVersion != BONEANIM_CHUNK_DESC_0290::VERSION) { m_LastError.Format("Unknown version of bone hierarchy chunk"); return false; } CSkinningInfo* pSkinningInfo = m_pCGF->GetSkinningInfo(); BONEANIM_CHUNK_DESC_0290* pChunk = (BONEANIM_CHUNK_DESC_0290*)(pChunkDesc->data); const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; m_pBoneAnimRawData = 0; if (pChunk->nBones <= 0) { m_LastError = "There must be at least one bone."; return false; } if (pChunkDesc->size < sizeof(*pChunk) || pChunk->nBones != (pChunkDesc->size - sizeof(*pChunk)) / sizeof(BONE_ENTITY)) { m_LastError = "Corrupted bone hierarchy chunk data."; } m_pBoneAnimRawData = pChunk + 1; m_pBoneAnimRawDataEnd = ((const char*)pChunk) + pChunkDesc->size; BONE_ENTITY* const pBones = (BONE_ENTITY*)m_pBoneAnimRawData; for (int i = 0; i < pChunk->nBones; ++i) { SwapEndian(pBones[i], bSwapEndianness); } if (pBones[0].ParentID != -1) { m_LastError = "The first bone in the hierarchy has a parent, but the first none expected to be the root bone."; return false; } /* for (int i = 1; i < pChunk->nBones; ++i) { if (pBones[i].ParentID == -1) { m_LastError = "The skeleton has multiple roots. Only single-rooted skeletons are supported in this version."); return false; } } */ pSkinningInfo->m_arrBonesDesc.resize(pChunk->nBones); CryBoneDescData zeroBone; memset(&zeroBone, 0, sizeof(zeroBone)); std::fill(pSkinningInfo->m_arrBonesDesc.begin(), pSkinningInfo->m_arrBonesDesc.end(), zeroBone); m_arrIndexToId.resize(pChunk->nBones, ~0); m_arrIdToIndex.resize(pChunk->nBones, ~0); m_nNextBone = 1; assert(m_nNextBone <= pChunk->nBones); m_numBones = 0; for (int i = 0; i < pChunk->nBones; ++i) { if (pBones[i].ParentID == -1) { const int nRootBoneIndex = i; m_nNextBone = nRootBoneIndex + 1; RecursiveBoneLoader(nRootBoneIndex, nRootBoneIndex); } } assert(pChunk->nBones == m_numBones); //----------------------------------------------------------------------------- //--- read physical information --- //----------------------------------------------------------------------------- pSkinningInfo->m_arrBoneEntities.resize(pChunk->nBones); int32 test = 0; for (int i = 0; i < pChunk->nBones; ++i) { pSkinningInfo->m_arrBoneEntities[i] = pBones[i]; test |= pBones[i].phys.nPhysGeom; } return true; } // loads the whole hierarchy of bones, using the state machine // when this function is called, the bone is already allocated uint32 CLoaderCGF::RecursiveBoneLoader(int nBoneParentIndex, int nBoneIndex) { m_numBones++; CSkinningInfo* pSkinningInfo = m_pCGF->GetSkinningInfo(); BONE_ENTITY* pEntity = (BONE_ENTITY*)m_pBoneAnimRawData; SwapEndian(*pEntity); m_pBoneAnimRawData = ((uint8*)m_pBoneAnimRawData) + sizeof(BONE_ENTITY); // initialize the next bone CryBoneDescData& rBoneDesc = pSkinningInfo->m_arrBonesDesc[nBoneIndex]; //read bone info CopyPhysInfo(rBoneDesc.m_PhysInfo[0], pEntity->phys); int nFlags = 0; if (pEntity->prop[0]) { nFlags = joint_no_gravity | joint_isolated_accelerations; } else { if (!CryStringUtils::strnstr(pEntity->prop, "gravity", sizeof(pEntity->prop))) { nFlags |= joint_no_gravity; } if (!CryStringUtils::strnstr(pEntity->prop, "active_phys", sizeof(pEntity->prop))) { nFlags |= joint_isolated_accelerations; } } (rBoneDesc.m_PhysInfo[0].flags &= ~(joint_no_gravity | joint_isolated_accelerations)) |= nFlags; //get bone info rBoneDesc.m_nControllerID = pEntity->ControllerID; // set the mapping entries m_arrIndexToId[nBoneIndex] = pEntity->BoneID; m_arrIdToIndex[pEntity->BoneID] = nBoneIndex; rBoneDesc.m_nOffsetParent = nBoneParentIndex - nBoneIndex; // load children if (pEntity->nChildren) { int nChildrenIndexBase = m_nNextBone; m_nNextBone += pEntity->nChildren; if (nChildrenIndexBase < 0) { return 0; } // load the children rBoneDesc.m_numChildren = pEntity->nChildren; rBoneDesc.m_nOffsetChildren = nChildrenIndexBase - nBoneIndex; for (int nChild = 0; nChild < pEntity->nChildren; ++nChild) { if (!RecursiveBoneLoader(nBoneIndex, nChildrenIndexBase + nChild)) { return 0; } } } else { rBoneDesc.m_numChildren = 0; rBoneDesc.m_nOffsetChildren = 0; } return m_numBones; } namespace { struct BoneVertex { Vec3 pos; int matId; int faceIndex; int cornerIndex; }; struct BoneVertexComparator { bool operator()(const BoneVertex& v0, const BoneVertex& v1) const { int res = memcmp(&v0.pos, &v1.pos, sizeof(v0.pos)); // The 'if' block below prevents sharing vertices between faces with different materials. // Note: you can comment the block out to enable sharing and decreases # of resulting // vertices in case of multi-material geometry. if (res == 0) { res = v0.matId - v1.matId; } return res < 0; } }; } static bool CompactBoneVertices( DynArray& outArrPositions, DynArray& outArrMaterials, DynArray& outArrIndices, const int inVertexCount, const CryVertex* const inVertices, const int inFaceCount, const CryFace* const inFaces) { outArrPositions.clear(); outArrMaterials.clear(); outArrIndices.clear(); DynArray verts; verts.reserve(inFaceCount * 3); outArrMaterials.reserve(inFaceCount); for (int i = 0; i < inFaceCount; ++i) { outArrMaterials.push_back(inFaces[i].MatID); for (int j = 0; j < 3; ++j) { const int vIdx = inFaces[i][j]; if (vIdx < 0 || vIdx >= inVertexCount) { return false; } BoneVertex v; v.pos = inVertices[vIdx].p; v.matId = inFaces[i].MatID; v.faceIndex = i; v.cornerIndex = j; verts.push_back(v); } } std::sort(verts.begin(), verts.end(), BoneVertexComparator()); outArrPositions.reserve(inVertexCount); // preallocating by using a good enough guess outArrIndices.resize(3 * inFaceCount, -1); int outVertexCount = 0; for (int i = 0; i < verts.size(); ++i) { if (i == 0 || BoneVertexComparator()(verts[i - 1], verts[i])) { outArrPositions.push_back(verts[i].pos); ++outVertexCount; if (outVertexCount > (1 << 16)) { return false; } } outArrIndices[verts[i].faceIndex * 3 + verts[i].cornerIndex] = outVertexCount - 1; } // Making sure that the code above has no bugs for (int i = 0; i < outArrIndices.size(); ++i) { if (outArrIndices[i] < 0) { return false; } } return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadBoneMesh(IChunkFile::ChunkDesc* pChunkDesc) { if (pChunkDesc->chunkVersion != MESH_CHUNK_DESC_0745::VERSION && pChunkDesc->chunkVersion != MESH_CHUNK_DESC_0745::COMPATIBLE_OLD_VERSION) { m_LastError.Format( "Unknown version (0x%x) of BoneMesh chunk. The only supported versions are 0x%x and 0x%x.", (uint)pChunkDesc->chunkVersion, (uint)MESH_CHUNK_DESC_0745::VERSION, (uint)MESH_CHUNK_DESC_0745::COMPATIBLE_OLD_VERSION); return false; } if (pChunkDesc->size < sizeof(MESH_CHUNK_DESC_0745)) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Bad chunk size"); return false; } MESH_CHUNK_DESC_0745* const pMeshChunk = (MESH_CHUNK_DESC_0745*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pMeshChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; if (pMeshChunk->nVerts <= 0) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Bad vertex count (%d)", pMeshChunk->nVerts); return false; } if (pMeshChunk->nFaces <= 0) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Bad face count (%d)", pMeshChunk->nFaces); return false; } if (pMeshChunk->nTVerts != 0) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Texture coordinates found (%d)", pMeshChunk->nTVerts); return false; } if (pMeshChunk->flags1 != 0 || pMeshChunk->flags2 != 0) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Flags are not 0 (0x%x, 0x%x)", (int)pMeshChunk->flags1, (int)pMeshChunk->flags2); return false; } // prepare vertices void* pRawData = pMeshChunk + 1; CryVertex* const pSrcVertices = (CryVertex*)pRawData; SwapEndian(pSrcVertices, pMeshChunk->nVerts, bSwapEndianness); if ((const char*)(pSrcVertices + pMeshChunk->nVerts) > (const char*)pRawData + (pChunkDesc->size - sizeof(*pMeshChunk))) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Vertex data are truncated"); return false; } pRawData = pSrcVertices + pMeshChunk->nVerts; // prepare indices and MatIDs CryFace* const pSrcFaces = (CryFace*)pRawData; SwapEndian(pSrcFaces, pMeshChunk->nFaces, bSwapEndianness); if ((const char*)(pSrcFaces + pMeshChunk->nFaces) > (const char*)pRawData + (pChunkDesc->size - sizeof(*pMeshChunk))) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Vertex data are truncated"); return false; } PhysicalProxy pbm; pbm.ChunkID = pChunkDesc->chunkId; // Bone meshes may contain many vertices sharing positions, so we // call CompactBoneVertices() to get vertices with unique positions only if (!CompactBoneVertices( pbm.m_arrPoints, pbm.m_arrMaterials, pbm.m_arrIndices, pMeshChunk->nVerts, pSrcVertices, pMeshChunk->nFaces, pSrcFaces)) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Bad geometry (indices are out range or too many vertices in mesh)"); return false; } // SS: I have no idea why we used this magic number of vertices (60000). I just keep it as is. if (pbm.m_arrPoints.size() > 60000) { m_LastError.Format("CLoaderCGF::ReadBoneMesh: Bad vertex count (%d)", pbm.m_arrPoints.size()); return false; } for (int i = 0, n = pbm.m_arrPoints.size(); i < n; ++i) { pbm.m_arrPoints[i] *= VERTEX_SCALE; } CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrPhyBoneMeshes.push_back(pbm); return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledBones(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; COMPILED_BONE_CHUNK_DESC_0800* const pBIPChunk = (COMPILED_BONE_CHUNK_DESC_0800*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pBIPChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; if (pChunkDesc->chunkVersion == pBIPChunk->VERSION) { CryBoneDescData_Comp* const pSrcVertices = (CryBoneDescData_Comp*)(pBIPChunk + 1); const uint32 nDataSize = pChunkDesc->size - sizeof(COMPILED_BONE_CHUNK_DESC_0800); const uint32 numBones = nDataSize / sizeof(CryBoneDescData_Comp); CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrBonesDesc.resize(numBones); SwapEndian(pSrcVertices, (size_t)numBones, bSwapEndianness); for (uint32 i = 0; i < numBones; i++) { pSkinningInfo->m_arrBonesDesc[i].m_nControllerID = pSrcVertices[i].m_nControllerID; memcpy( &pSkinningInfo->m_arrBonesDesc[i].m_fMass, &pSrcVertices[i].m_fMass, (INT_PTR)&pSrcVertices[i + 1] - (INT_PTR)&pSrcVertices[i].m_fMass); for (uint32 j = 0; j < 2; j++) { pSkinningInfo->m_arrBonesDesc[i].m_PhysInfo[j].pPhysGeom = (phys_geometry*)(INT_PTR)pSrcVertices[i].m_PhysInfo[j].nPhysGeom; memcpy( &pSkinningInfo->m_arrBonesDesc[i].m_PhysInfo[j].flags, &pSrcVertices[i].m_PhysInfo[j].flags, (INT_PTR)&pSrcVertices[i].m_PhysInfo[j + 1] - (INT_PTR)&pSrcVertices[i].m_PhysInfo[j].flags); } } m_CompiledBones = true; return true; } m_LastError.Format("Unknown version of compiled bone chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledPhysicalBones(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; COMPILED_PHYSICALBONE_CHUNK_DESC_0800* const pChunk = (COMPILED_PHYSICALBONE_CHUNK_DESC_0800*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; if (pChunkDesc->chunkVersion == pChunk->VERSION) { BONE_ENTITY* pSrcVertices = (BONE_ENTITY*)(pChunk + 1); const uint32 nDataSize = pChunkDesc->size - sizeof(COMPILED_PHYSICALBONE_CHUNK_DESC_0800); const uint32 numBones = nDataSize / sizeof(BONE_ENTITY); CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrBoneEntities.resize(numBones); SwapEndian(pSrcVertices, (size_t)numBones, bSwapEndianness); for (uint32 i = 0; i < numBones; i++) { pSkinningInfo->m_arrBoneEntities[i] = pSrcVertices[i]; } m_CompiledBones = true; return true; } m_LastError.Format("Unknown version of compiled physical bone chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledPhysicalProxies(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; CSkinningInfo* pSkinningInfo = m_pCGF->GetSkinningInfo(); COMPILED_PHYSICALPROXY_CHUNK_DESC_0800* const pIMTChunk = (COMPILED_PHYSICALPROXY_CHUNK_DESC_0800*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pIMTChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; if (pIMTChunk->numPhysicalProxies > 0xffff) { // Fixing bug of old format: numPhysicalProxies was stored in little-endian // format when chunk header had 'big-endian' flag set. SwapEndianBase(&pIMTChunk->numPhysicalProxies, 1); } if (pChunkDesc->chunkVersion == pIMTChunk->VERSION) { PhysicalProxy sm; const uint8* rawdata = (const uint8*)(pIMTChunk + 1); const uint32 numPhysicalProxies = pIMTChunk->numPhysicalProxies; for (uint32 i = 0; i < numPhysicalProxies; i++) { SMeshPhysicalProxyHeader* header = (SMeshPhysicalProxyHeader*)rawdata; rawdata += sizeof(SMeshPhysicalProxyHeader); SwapEndian(*header, bSwapEndianness); sm.ChunkID = header->ChunkID; if (sm.ChunkID > 0xFFFF) { SwapEndian(sm.ChunkID, true); } //store the vertices COMPILE_TIME_ASSERT(sizeof(sm.m_arrPoints[0]) == sizeof(Vec3)); SwapEndian((Vec3*)rawdata, (size_t)header->numPoints, bSwapEndianness); sm.m_arrPoints.resize(header->numPoints); memcpy(sm.m_arrPoints.begin(), rawdata, sizeof(sm.m_arrPoints[0]) * header->numPoints); rawdata += sizeof(sm.m_arrPoints[0]) * header->numPoints; //store the indices COMPILE_TIME_ASSERT(sizeof(sm.m_arrIndices[0]) == sizeof(uint16)); SwapEndian((uint16*)rawdata, (size_t)header->numIndices, bSwapEndianness); sm.m_arrIndices.resize(header->numIndices); memcpy(sm.m_arrIndices.begin(), rawdata, sizeof(sm.m_arrIndices[0]) * header->numIndices); rawdata += sizeof(sm.m_arrIndices[0]) * header->numIndices; //store the materials COMPILE_TIME_ASSERT(sizeof(sm.m_arrMaterials[0]) == sizeof(uint8)); SwapEndian((uint8*)rawdata, (size_t)header->numMaterials, bSwapEndianness); sm.m_arrMaterials.resize(header->numMaterials); memcpy(sm.m_arrMaterials.begin(), rawdata, sizeof(sm.m_arrMaterials[0]) * header->numMaterials); rawdata += sizeof(sm.m_arrMaterials[0]) * header->numMaterials; pSkinningInfo->m_arrPhyBoneMeshes.push_back(sm); } return true; } m_LastError.Format("Unknown version of compiled physical proxies chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledMorphTargets(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; // Note that this chunk type often contains non-aligned data. Because of that // we use chunk's data only after memcpy()'ing them. const bool bSwapEndianness = pChunkDesc->bSwapEndian; COMPILED_MORPHTARGETS_CHUNK_DESC_0800 chunk; memcpy(&chunk, pChunkDesc->data, sizeof(chunk)); SwapEndian(chunk, bSwapEndianness); const uint8* rawdata = ((const uint8*)pChunkDesc->data) + sizeof(chunk); if (pChunkDesc->chunkVersion == chunk.VERSION || pChunkDesc->chunkVersion == chunk.VERSION1) { CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); if (pChunkDesc->chunkVersion == chunk.VERSION1) { pSkinningInfo->m_bRotatedMorphTargets = true; } for (uint32 i = 0; i < chunk.numMorphTargets; ++i) { MorphTargetsPtr pSm = new MorphTargets; SMeshMorphTargetHeader header; memcpy(&header, rawdata, sizeof(header)); SwapEndian(header, bSwapEndianness); rawdata += sizeof(header); pSm->MeshID = header.MeshID; pSm->m_strName = string((const char*)rawdata); rawdata += header.NameLength; // store the internal vertices&indices of morph-target COMPILE_TIME_ASSERT(sizeof(pSm->m_arrIntMorph[0]) == sizeof(SMeshMorphTargetVertex)); pSm->m_arrIntMorph.resize(header.numIntVertices); size_t size = sizeof(pSm->m_arrIntMorph[0]) * header.numIntVertices; if (size > 0) { memcpy(pSm->m_arrIntMorph.begin(), rawdata, size); SwapEndian(pSm->m_arrIntMorph.begin(), (size_t)header.numIntVertices, bSwapEndianness); rawdata += size; } // store the external vertices&indices of morph-target COMPILE_TIME_ASSERT(sizeof(pSm->m_arrExtMorph[0]) == sizeof(SMeshMorphTargetVertex)); pSm->m_arrExtMorph.resize(header.numExtVertices); size = sizeof(pSm->m_arrExtMorph[0]) * header.numExtVertices; if (size > 0) { memcpy(pSm->m_arrExtMorph.begin(), rawdata, size); SwapEndian(pSm->m_arrExtMorph.begin(), (size_t)header.numExtVertices, bSwapEndianness); rawdata += size; } pSkinningInfo->m_arrMorphTargets.push_back(pSm); } return true; } m_LastError.Format("Unknown version of compiled morph targets chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledIntFaces(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; const bool bSwapEndianness = pChunkDesc->bSwapEndian; pChunkDesc->bSwapEndian = false; if (pChunkDesc->chunkVersion == COMPILED_INTFACES_CHUNK_DESC_0800::VERSION) { TFace* const pSrc = (TFace*)pChunkDesc->data; const uint32 numIntFaces = pChunkDesc->size / sizeof(pSrc[0]); CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrIntFaces.resize(numIntFaces); SwapEndian(pSrc, (size_t)numIntFaces, bSwapEndianness); for (uint32 i = 0; i < numIntFaces; ++i) { pSkinningInfo->m_arrIntFaces[i] = pSrc[i]; } m_CompiledMesh |= 2; return true; } m_LastError.Format("Unknown version of compiled int faces chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledIntSkinVertice(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; COMPILED_INTSKINVERTICES_CHUNK_DESC_0800* const pBIPChunk = (COMPILED_INTSKINVERTICES_CHUNK_DESC_0800*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pBIPChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; if (pChunkDesc->chunkVersion == pBIPChunk->VERSION) { IntSkinVertex* const pSrcVertices = (IntSkinVertex*)(pBIPChunk + 1); const uint32 nDataSize = pChunkDesc->size - sizeof(COMPILED_INTSKINVERTICES_CHUNK_DESC_0800); const uint32 numIntVertices = nDataSize / sizeof(pSrcVertices[0]); CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrIntVertices.resize(numIntVertices); SwapEndian(pSrcVertices, (size_t)numIntVertices, bSwapEndianness); for (uint32 i = 0; i < numIntVertices; ++i) { pSkinningInfo->m_arrIntVertices[i] = pSrcVertices[i]; } m_CompiledMesh |= 1; return true; } m_LastError.Format("Unknown version of compiled skin vertices chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledBonesBoxes(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; const bool bSwapEndianness = pChunkDesc->bSwapEndian; pChunkDesc->bSwapEndian = false; if (pChunkDesc->chunkVersion == COMPILED_BONEBOXES_CHUNK_DESC_0800::VERSION || pChunkDesc->chunkVersion == COMPILED_BONEBOXES_CHUNK_DESC_0800::VERSION1) { CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); if (pChunkDesc->chunkVersion == COMPILED_BONEBOXES_CHUNK_DESC_0800::VERSION1) { pSkinningInfo->m_bProperBBoxes = false; if (pSkinningInfo->m_arrCollisions.empty()) { pSkinningInfo->m_bProperBBoxes = true; } else { // CHECK FOR AT LEAST 1 EMPTY for (int n = 0; n < pSkinningInfo->m_arrCollisions.size(); ++n) { const bool bValid = !pSkinningInfo->m_arrCollisions[n].m_aABB.IsReset(); if (bValid) { pSkinningInfo->m_bProperBBoxes = true; break; } } } /* Ivo: this warning is not needed. It happens always if a character has no physics-proxy, and many simple characters don't have one if (!pSkinningInfo->m_bProperBBoxes) { CryWarning(VALIDATOR_MODULE_3DENGINE,VALIDATOR_WARNING,"%s: Invalid bones bounds", m_filename); }*/ pSkinningInfo->m_bProperBBoxes = false; } char* pSrc = (char*)pChunkDesc->data; pSkinningInfo->m_arrCollisions.push_back(MeshCollisionInfo()); MeshCollisionInfo& info = pSkinningInfo->m_arrCollisions[pSkinningInfo->m_arrCollisions.size() - 1]; COMPILE_TIME_ASSERT(sizeof(info.m_iBoneId) == sizeof(int32)); SwapEndian((int32*)pSrc, 1, bSwapEndianness); memcpy(&info.m_iBoneId, pSrc, sizeof(info.m_iBoneId)); pSrc += sizeof(info.m_iBoneId); COMPILE_TIME_ASSERT(sizeof(info.m_aABB) == sizeof(AABB)); SwapEndian((AABB*)pSrc, 1, bSwapEndianness); memcpy(&info.m_aABB, pSrc, sizeof(info.m_aABB)); pSrc += sizeof(info.m_aABB); int32 size; COMPILE_TIME_ASSERT(sizeof(size) == sizeof(int32)); SwapEndian((int32*)pSrc, 1, bSwapEndianness); memcpy(&size, pSrc, sizeof(size)); pSrc += sizeof(size); if (size > 0) { COMPILE_TIME_ASSERT(sizeof(info.m_arrIndexes[0]) == sizeof(int16)); SwapEndian((int16*)pSrc, size, bSwapEndianness); info.m_arrIndexes.resize(size); memcpy(info.m_arrIndexes.begin(), pSrc, size * sizeof(info.m_arrIndexes[0])); } m_CompiledBonesBoxes = true; return true; } m_LastError.Format("Unknown version of compiled bone boxes chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledExt2IntMap(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; const bool bSwapEndianness = pChunkDesc->bSwapEndian; pChunkDesc->bSwapEndian = false; if (pChunkDesc->chunkVersion == COMPILED_EXT2INTMAP_CHUNK_DESC_0800::VERSION) { uint16* const pSrc = (uint16*)pChunkDesc->data; const uint32 numExtVertices = pChunkDesc->size / sizeof(pSrc[0]); CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); pSkinningInfo->m_arrExt2IntMap.resize(numExtVertices); SwapEndian(pSrc, (size_t)numExtVertices, bSwapEndianness); for (uint32 i = 0; i < numExtVertices; ++i) { assert(pSrc[i] != 0xffff); pSkinningInfo->m_arrExt2IntMap[i] = pSrc[i]; } m_CompiledMesh |= 4; return true; } m_LastError.Format("Unknown version of compiled Ext2Int map chunk"); return false; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::ReadCompiledBreakablePhysics(IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->chunkVersion != BREAKABLE_PHYSICS_CHUNK_DESC::VERSION) { m_LastError.Format("Unknown version of breakable physics chunk"); return false; } BREAKABLE_PHYSICS_CHUNK_DESC* const pChunk = (BREAKABLE_PHYSICS_CHUNK_DESC*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(*pChunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; CPhysicalizeInfoCGF* const pPi = m_pCGF->GetPhysicalizeInfo(); pPi->nGranularity = pChunk->granularity; pPi->nMode = pChunk->nMode; pPi->nRetVtx = pChunk->nRetVtx; pPi->nRetTets = pChunk->nRetTets; if (pPi->nRetVtx > 0) { char* pData = ((char*)pChunk) + sizeof(BREAKABLE_PHYSICS_CHUNK_DESC); SwapEndian((Vec3*)pData, pPi->nRetVtx, bSwapEndianness); memcpy(pPi->pRetVtx, pData, pPi->nRetVtx * sizeof(Vec3)); } if (pPi->nRetTets > 0) { char* pData = ((char*)pChunk) + sizeof(BREAKABLE_PHYSICS_CHUNK_DESC) + pPi->nRetVtx * sizeof(Vec3); SwapEndian((int*)pData, pPi->nRetTets * 4, bSwapEndianness); memcpy(pPi->pRetTets, pData, pPi->nRetTets * sizeof(int) * 4); } return true; } ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// #if defined(RESOURCE_COMPILER) namespace ProcessSkinningHelpers { struct RBatch { uint32 startFace; uint32 numFaces; uint32 MaterialID; }; bool SplitIntoRBatches( std::vector& arrSubsets, std::vector& arrExtFaces, string& lastError, const CMesh* const pMesh) { arrSubsets.clear(); arrExtFaces.clear(); const int numSubsets = pMesh->m_subsets.size(); //-------------------------------------------------------------------------- //--- sort render-batches for hardware skinning --- //-------------------------------------------------------------------------- std::vector arrBatches; for (uint32 m = 0; m < numSubsets; m++) { const int numFacesTotal = pMesh->GetIndexCount() / 3; const int firstFace = pMesh->m_subsets[m].nFirstIndexId / 3; const int numFaces = pMesh->m_subsets[m].nNumIndices / 3; if (firstFace >= numFacesTotal) { lastError.Format("Wrong first face id index (%i out of %i)", (int)firstFace, (int)numFacesTotal); return false; } if (numFaces <= 0 || firstFace + numFaces > numFacesTotal) { lastError.Format("Bad # of faces (%i)", (int)numFaces); return false; } { RBatch rbatch; rbatch.MaterialID = pMesh->m_subsets[m].nMatID; rbatch.startFace = arrExtFaces.size(); rbatch.numFaces = numFaces; arrBatches.push_back(rbatch); } vtx_idx* const pIndices = &pMesh->m_pIndices[firstFace * 3]; for (int i = 0; i < numFaces * 3; i += 3) { arrExtFaces.push_back(TFace(pIndices[i + 0], pIndices[i + 1], pIndices[i + 2])); } } //------------------------------------------------------------------------- //--- check if material batches overlap --- //------------------------------------------------------------------------- { for (uint32 m = 0; m < numSubsets; m++) { uint32 vmin = 0xffffffff; uint32 vmax = 0x00000000; uint32 nFirstFaceId = pMesh->m_subsets[m].nFirstIndexId / 3; uint32 numFaces = pMesh->m_subsets[m].nNumIndices / 3; for (uint32 f = 0; f < numFaces; f++) { uint32 i0 = arrExtFaces[nFirstFaceId + f].i0; uint32 i1 = arrExtFaces[nFirstFaceId + f].i1; uint32 i2 = arrExtFaces[nFirstFaceId + f].i2; if (vmin > i0) { vmin = i0; } if (vmin > i1) { vmin = i1; } if (vmin > i2) { vmin = i2; } if (vmax < i0) { vmax = i0; } if (vmax < i1) { vmax = i1; } if (vmax < i2) { vmax = i2; } } uint32 a = pMesh->m_subsets[m].nFirstVertId; uint32 b = pMesh->m_subsets[m].nNumVerts; if (pMesh->m_subsets[m].nFirstVertId != vmin || pMesh->m_subsets[m].nNumVerts != vmax - vmin + 1) { lastError = "Overlapping material batches"; return false; } } for (uint32 a = 0; a < numSubsets; a++) { for (uint32 b = 0; b < numSubsets; b++) { if (a == b) { continue; } uint32 amin = pMesh->m_subsets[a].nFirstVertId; uint32 amax = pMesh->m_subsets[a].nNumVerts + amin - 1; uint32 bmin = pMesh->m_subsets[b].nFirstVertId; uint32 bmax = pMesh->m_subsets[b].nNumVerts + bmin - 1; if (amax >= bmin && amin <= bmax) { lastError = "Overlapping material batches"; return false; } } } } arrSubsets.resize(arrBatches.size()); //subsets of the mesh as they appear in video-mem for (uint32 m = 0; m < arrBatches.size(); m++) { uint32 mat = arrBatches[m].MaterialID; // assert(matm_subsets.size(); for (r = 0; r < numSubsets; r++) { if (mat == pMesh->m_subsets[r].nMatID) { found = true; break; } } if (!found) { lastError.Format("Mesh subset for material %i was not found.", (int)mat); return false; } arrSubsets[m] = pMesh->m_subsets[r]; arrSubsets[m].nMatID = arrBatches[m].MaterialID; arrSubsets[m].nFirstIndexId = arrBatches[m].startFace * 3; arrSubsets[m].nNumIndices = arrBatches[m].numFaces * 3; //Make sure the all vertices are in range if indices. This is important for ATI-kards uint32 sml_index = 0xffffffff; uint32 big_index = 0x00000000; uint32 sface = arrBatches[m].startFace; uint32 eface = arrBatches[m].numFaces + sface; for (uint32 i = sface; i < eface; i++) { uint32 i0 = arrExtFaces[i].i0; uint32 i1 = arrExtFaces[i].i1; uint32 i2 = arrExtFaces[i].i2; if (sml_index > i0) { sml_index = i0; } if (sml_index > i1) { sml_index = i1; } if (sml_index > i2) { sml_index = i2; } if (big_index < i0) { big_index = i0; } if (big_index < i1) { big_index = i1; } if (big_index < i2) { big_index = i2; } } arrSubsets[m].nFirstVertId = sml_index; arrSubsets[m].nNumVerts = big_index - sml_index + 1; } return true; } } #endif bool CLoaderCGF::ProcessSkinning() { LOADING_TIME_PROFILE_SECTION; CSkinningInfo* const pSkinningInfo = m_pCGF->GetSkinningInfo(); const uint32 numBones = pSkinningInfo->m_arrBonesDesc.size(); //do we have an initialized skeleton? if (numBones == 0) { return true; } if (numBones > MAX_NUMBER_OF_BONES) { m_LastError.Format("Too many bones: %i. Reached limit of %i bones.", int(numBones), int(MAX_NUMBER_OF_BONES)); return false; } #if !defined(RESOURCE_COMPILER) if (m_CompiledMesh != 7 && m_CompiledBones != 1) { CryFatalError("%s tried to load a noncompiled mesh: %s", __FUNCTION__, m_filename); m_LastError.Format("noncompiled mesh"); return false; } return true; #else using namespace ProcessSkinningHelpers; if (m_CompiledBones == 0) { //process raw bone-data assert(m_numBonenameList < 2); assert(m_numBoneInitialPos < 2); assert(m_numBoneHierarchy < 2); const uint32 numIPos = m_arrInitPose34.size(); if (numBones != numIPos) { m_LastError.Format("Skeleton-Initial-Positions are missing."); return false; } const uint32 numBonenames = m_arrBoneNameTable.size(); if (numBones != numBonenames) { m_LastError.Format("Number of bones does not match in the bone hierarchy chunk (%d) and the bone name chunk (%d)", numBones, numBonenames); return false; } //------------------------------------------------------------------------------------------ //---- use the old chunks to initialize the bone structure ---------------------------- //------------------------------------------------------------------------------------------ static const char* const arrLimbNames[] = { "L UpperArm", "R UpperArm", "L Thigh", "R Thigh" }; static const int limbNamesCount = sizeof(arrLimbNames) / sizeof(arrLimbNames[0]); const uint32 numBonesDesc = pSkinningInfo->m_arrBonesDesc.size(); for (uint32 nBone = 0; nBone < numBonesDesc; ++nBone) { uint32 nBoneID = m_arrIndexToId[nBone]; if (nBoneID == ~0) { continue; } pSkinningInfo->m_arrBonesDesc[nBone].m_DefaultW2B = m_arrInitPose34[nBoneID].GetInvertedFast(); pSkinningInfo->m_arrBonesDesc[nBone].m_DefaultB2W = m_arrInitPose34[nBoneID]; memset(pSkinningInfo->m_arrBonesDesc[nBone].m_arrBoneName, 0, sizeof(pSkinningInfo->m_arrBonesDesc[nBone].m_arrBoneName)); azstrcpy(pSkinningInfo->m_arrBonesDesc[nBone].m_arrBoneName, sizeof(pSkinningInfo->m_arrBonesDesc[nBone].m_arrBoneName), m_arrBoneNameTable[nBoneID].c_str()); //fill in names of the bones and the limb IDs --- uint32 nBoneId = m_arrIndexToId[nBone]; pSkinningInfo->m_arrBonesDesc[nBone].m_nLimbId = -1; for (int j = 0; j < limbNamesCount; ++j) { if (strstr(m_arrBoneNameTable[nBoneId], arrLimbNames[j])) { pSkinningInfo->m_arrBonesDesc[nBone].m_nLimbId = j; break; } } } } if (m_CompiledMesh != 0 && m_CompiledMesh != 7) { m_LastError.Format("Found mix of new and old chunks"); return false; } if (m_CompiledMesh) { return true; } //------------------------------------------------------------------------------------------ //---- get the mesh ----------------------------------------------------------------- //------------------------------------------------------------------------------------------ CNodeCGF* pNode = 0; for (int i = 0; i < m_pCGF->GetNodeCount(); ++i) { if (m_pCGF->GetNode(i)->type == CNodeCGF::NODE_MESH && m_pCGF->GetNode(i)->pMesh) { pNode = m_pCGF->GetNode(i); break; } } if (!pNode) { return true; } CMesh* const pMesh = pNode->pMesh; if (!pMesh) { m_LastError = "No mesh found"; return false; } if (pMesh->m_pPositionsF16) { m_LastError.Format("Unexpected format of vertex positions: f16"); return false; } const uint32 numIntVertices = pMesh->GetVertexCount(); /* ----------------------------------------------------------------------------- // NOTE: Related to commented out code below! // Color0 is copied into two distinct streams in the Engine: // as Color in the Base stream and as indices in the Shape // Deformation stream. The latter needs for the values to be // snapped while the former doesn't. // Snapping used to happen here at compiling/loading time, however // this will destroy the Color information that might be needed in // the Base stream. // We are now snapping the values when copying this data into the // Shape Deformation stream, while leaving the data copied in the // Base stream untouched. A properer solution would be to create an // additional chunk in the file and completely decouple the two // datasets. if (pMesh->m_pColor0) { //----------------------------------------------------------------- //--- do a color-snap on all vcolors and calculate index --- //----------------------------------------------------------------- for (uint32 i=0; im_pColor0[i].r = (pMesh->m_pColor0[i].r>0x80) ? 0xff : 0x00; pMesh->m_pColor0[i].g = (pMesh->m_pColor0[i].g>0x80) ? 0xff : 0x00; pMesh->m_pColor0[i].b = (pMesh->m_pColor0[i].b>0x80) ? 0xff : 0x00; pMesh->m_pColor0[i].a = 0; //calculate the index (this is the only value we need in the vertex-shader) if (pMesh->m_pColor0[i].r) pMesh->m_pColor0[i].a|=1; if (pMesh->m_pColor0[i].g) pMesh->m_pColor0[i].a|=2; if (pMesh->m_pColor0[i].b) pMesh->m_pColor0[i].a|=4; } } ----------------------------------------------------------------------------- */ //-------------------------------------------------------------- //--- copy the links into geometry info --- //-------------------------------------------------------------- { const uint32 numLinks = m_arrLinksTmp.size(); if (numIntVertices != numLinks) { m_LastError.Format("Different number of vertices (%i) and vertex links (%i)", (int)numIntVertices, (int)numLinks); return false; } assert(!m_arrIdToIndex.empty()); for (uint32 i = 0; i < numIntVertices; ++i) { MeshUtils::VertexLinks& links = m_arrLinksTmp[i]; for (int j = 0; j < (int)links.links.size(); ++j) { MeshUtils::VertexLinks::Link& cl = links.links[j]; if (cl.boneId >= 0 && cl.boneId < (int)m_arrIdToIndex.size()) { cl.boneId = m_arrIdToIndex[cl.boneId]; } else { // bone index is out of range // if you get this assert, most probably there is desynchronization between different LODs of the same model // - all of them must be exported with exactly the same skeletons. assert(0); cl.boneId = 0; } } const char* const err = links.Normalize(MeshUtils::VertexLinks::eSort_ByWeight, 0.0f, (int)links.links.size()); if (err) { m_LastError.Format("Internal error in skin compiler: %s", err); return false; } // Paranoid checks { float w = 0; for (int j = 0; j < (int)links.links.size(); ++j) { w += links.links[j].weight; } if (fabsf(w - 1.0f) > 0.005f) { m_LastError.Format("Internal error in skin compiler: %s", "sum of weights is not 1.0"); return false; } for (int j = 1; j < (int)links.links.size(); ++j) { if (links.links[j - 1].weight < links.links[j].weight) { m_LastError.Format("Internal error in skin compiler: %s", "links are not sorted by weight"); return false; } } } } } //--------------------------------------------------------------------- // create internal SkinBuffer //--------------------------------------------------------------------- bool bHasExtraBoneMappings = false; pSkinningInfo->m_arrIntVertices.resize(numIntVertices); for (uint32 nVert = 0; nVert < numIntVertices; ++nVert) { const MeshUtils::VertexLinks& rLinks_base = m_arrLinksTmp[nVert]; const int numVertexLinks = (int)rLinks_base.links.size(); assert(numVertexLinks > 0 && numVertexLinks <= 8); bHasExtraBoneMappings = bHasExtraBoneMappings || numVertexLinks > 4; IntSkinVertex v; if (pMesh->m_pColor0) { v.color = pMesh->m_pColor0[nVert].GetRGBA(); } else { v.color = ColorB(0xff, 0xff, 0xff, 1 | 2 | 4); } v.__obsolete0 = Vec3(ZERO); v.__obsolete2 = Vec3(ZERO); const int n = std::min(numVertexLinks, (int)ARRAY_LENGTH(v.weights)); for (int j = 0; j < n; ++j) { v.boneIDs[j] = rLinks_base.links[j].boneId; v.weights[j] = rLinks_base.links[j].weight; } for (int j = n; j < ARRAY_LENGTH(v.weights); ++j) { v.boneIDs[j] = 0; v.weights[j] = 0; } // transform position from bone-space to world-space v.pos = Vec3(ZERO); for (int j = 0; j < numVertexLinks; ++j) { v.pos += pSkinningInfo->m_arrBonesDesc[rLinks_base.links[j].boneId].m_DefaultB2W * rLinks_base.links[j].offset * rLinks_base.links[j].weight; } pSkinningInfo->m_arrIntVertices[nVert] = v; } //-------------------------------------------------------------------------- // sort faces by subsets //-------------------------------------------------------------------------- // for each subset, construct an array of faces and keep the faces (in the original order) in there typedef std::map > SubsetFacesMap; SubsetFacesMap mapSubsetFaces; if (numIntVertices > (1 << 16)) { m_LastError.Format("Too many vertices in skin geometry: %i (max possible is %i)", numIntVertices, (1 << 16)); return false; } // put each face into its subset group; // the corresponding groups will be created by the map automatically // upon the first request of that group const uint32 numIntFaces = pMesh->GetFaceCount(); for (uint32 i = 0; i < numIntFaces; ++i) { const int subsetIdx = pMesh->m_pFaces[i].nSubset; if (subsetIdx < 0 || subsetIdx > pMesh->m_subsets.size()) { m_LastError.Format("Invalid subset index detected: %i (# of subsets: %i)", subsetIdx, (int)pMesh->m_subsets.size()); return false; } if (pMesh->m_subsets[subsetIdx].nMatID >= MAX_SUB_MATERIALS) { m_LastError.Format("Maximum number of submaterials reached (%i)", (int)MAX_SUB_MATERIALS); return false; } int vIdx[3]; for (int j = 0; j < 3; ++j) { vIdx[j] = pMesh->m_pFaces[i].v[j]; if (vIdx[j] < 0) { m_LastError.Format("Internal vertex index %i is negative (# of vertices is %i)", vIdx[j], numIntVertices); return false; } if (vIdx[j] >= numIntVertices) { m_LastError.Format("Internal vertex index %i is out of range (# of vertices is %i)", vIdx[j], numIntVertices); return false; } } mapSubsetFaces[subsetIdx].push_back(TFace(vIdx[0], vIdx[1], vIdx[2])); } if (pMesh->GetSubSetCount() != mapSubsetFaces.size()) { m_LastError.Format("Number of referenced subsets (%d) is not equal to number of stored subsets (%i)", (int)mapSubsetFaces.size(), pMesh->GetSubSetCount()); return false; } //-------------------------------------------------------------------------- // create array with internal faces (sorted by subsets) //-------------------------------------------------------------------------- { pSkinningInfo->m_arrIntFaces.resize(numIntFaces); int newFaceCount = 0; for (SubsetFacesMap::iterator itMtl = mapSubsetFaces.begin(); itMtl != mapSubsetFaces.end(); ++itMtl) { const size_t faceCount = itMtl->second.size(); for (size_t f = 0; f < faceCount; ++f, ++newFaceCount) { pSkinningInfo->m_arrIntFaces[newFaceCount] = itMtl->second[f]; } } assert(newFaceCount == numIntFaces); } // Compile contents. // These map from internal (original) to external (optimized) indices/vertices std::vector arrVRemapping; std::vector arrIRemapping; m_pCompiledCGF = MakeCompiledSkinCGF(m_pCGF, &arrVRemapping, &arrIRemapping); if (!m_pCompiledCGF) { return false; } const uint32 numVRemapping = arrVRemapping.size(); if (numVRemapping == 0) { m_LastError = "Empty vertex remapping"; return false; } if (arrIRemapping.size() != numIntFaces * 3) { m_LastError.Format("Wrong # of indices for remapping"); return false; } //allocates the external to internal map entries // (m_arrExt2IntMap[]: for each final vertex contains its index in initial vertex buffer) // TODO: in theory arrVRemapping.size() is not necessarily equals to the number of final vertices. // It could be bigger if a face is not referenced from any of the subsets. pSkinningInfo->m_arrExt2IntMap.resize(numVRemapping, ~0); for (uint32 i = 0; i < numIntFaces; ++i) { const uint32 idx0 = arrVRemapping[arrIRemapping[i * 3 + 0]]; const uint32 idx1 = arrVRemapping[arrIRemapping[i * 3 + 1]]; const uint32 idx2 = arrVRemapping[arrIRemapping[i * 3 + 2]]; if (idx0 >= numVRemapping || idx1 >= numVRemapping || idx2 >= numVRemapping) { m_LastError.Format("Indices out of range"); return false; } pSkinningInfo->m_arrExt2IntMap[idx0] = pSkinningInfo->m_arrIntFaces[i].i0; pSkinningInfo->m_arrExt2IntMap[idx1] = pSkinningInfo->m_arrIntFaces[i].i1; pSkinningInfo->m_arrExt2IntMap[idx2] = pSkinningInfo->m_arrIntFaces[i].i2; } { int brokenCount = 0; for (uint32 i = 0; i < numVRemapping; i++) { if (pSkinningInfo->m_arrExt2IntMap[i] >= numIntVertices) { ++brokenCount; // "Fixing" mapping allows us to comment out "return false" below (in case of an urgent need) pSkinningInfo->m_arrExt2IntMap[i] = 0; } } if (brokenCount > 0) { m_LastError.Format("Remapping-table is broken. %i of %i vertices are not remapped", brokenCount, numVRemapping); return false; } } //------------------------------------------------------------------------- std::vector arrSubsets; std::vector arrExtFaces; if (!SplitIntoRBatches(arrSubsets, arrExtFaces, m_LastError, pMesh)) { return false; } //-------------------------------------------------------------------------- //--- copy compiled-data back into CMesh --- //-------------------------------------------------------------------------- for (size_t f = 0, n = arrExtFaces.size(); f < n; ++f) { pMesh->m_pIndices[f * 3 + 0] = arrExtFaces[f].i0; pMesh->m_pIndices[f * 3 + 1] = arrExtFaces[f].i1; pMesh->m_pIndices[f * 3 + 2] = arrExtFaces[f].i2; } pMesh->m_subsets.clear(); pMesh->m_subsets.reserve(arrSubsets.size()); for (size_t i = 0; i < arrSubsets.size(); ++i) { pMesh->m_subsets.push_back(arrSubsets[i]); } ////////////////////////////////////////////////////////////////////////// // Create and fill bone-mapping streams. ////////////////////////////////////////////////////////////////////////// { pMesh->ReallocStream(CMesh::BONEMAPPING, 0, numVRemapping); if (bHasExtraBoneMappings) { pMesh->ReallocStream(CMesh::EXTRABONEMAPPING, 0, numVRemapping); } for (uint32 i = 0; i < numVRemapping; ++i) { const uint32 index = pSkinningInfo->m_arrExt2IntMap[i]; const MeshUtils::VertexLinks& links = m_arrLinksTmp[index]; const int linkCount = (int)links.links.size(); // Convert floating point weights to integer [0;255] weights int w[8]; { assert(linkCount <= 8); int wSum = 0; for (int j = 0; j < linkCount; ++j) { w[j] = (int)(links.links[j].weight * 255.0f + 0.5f); wSum += w[j]; } // Ensure that the sum of weights is exactly 255. // Note that the code below preserves sorting by // weight in descending order. if (wSum < 255) { w[0] += 255 - wSum; } else if (wSum > 255) { for (int j = 0;; ++j) { if (j >= linkCount - 1 || w[j] > w[j + 1]) { --w[j]; if (--wSum == 255) { break; } j = std::max(j - 1, 0) - 1; } } } // TODO: quantization to integer values might produce zero weight links, so // it might be a good idea to delete such links. Warning: m_arrIntVertices[] // also stores (up to) four links, so we should delete matching zero-weight // links in m_arrIntVertices[] as well. } // Fill CMesh::BONEMAPPING stream { const int n = std::min(linkCount, 4); for (int j = 0; j < n; ++j) { pMesh->m_pBoneMapping[i].boneIds[j] = links.links[j].boneId; pMesh->m_pBoneMapping[i].weights[j] = (uint8)w[j]; } for (int j = n; j < 4; ++j) { pMesh->m_pBoneMapping[i].boneIds[j] = 0; pMesh->m_pBoneMapping[i].weights[j] = 0; } } // Fill CMesh::EXTRABONEMAPPING stream if (bHasExtraBoneMappings) { const int n = std::max(linkCount - 4, 0); for (int j = 0; j < n; ++j) { pMesh->m_pExtraBoneMapping[i].boneIds[j] = links.links[4 + j].boneId; pMesh->m_pExtraBoneMapping[i].weights[j] = (uint8)w[4 + j]; } for (int j = n; j < 4; ++j) { pMesh->m_pExtraBoneMapping[i].boneIds[j] = 0; pMesh->m_pExtraBoneMapping[i].weights[j] = 0; } } } } // Keep original transform for morph targets Matrix34 mat34 = pNode->localTM * Diag33(VERTEX_SCALE, VERTEX_SCALE, VERTEX_SCALE); ////////////////////////////////////////////////////////////////////////// // Copy shape-deformation and positions. ////////////////////////////////////////////////////////////////////////// { // Modify orientation, but keep translation. // We need to keep translation to be able to use pivot of the node. // It allows us to control coordinate origin for FP16 meshes. // The translation is applied later before skinning. const Matrix34 oldWorldTM = pNode->worldTM; const Vec3 translation = oldWorldTM.GetTranslation(); pNode->worldTM = Matrix34(Matrix33(IDENTITY), translation); // Reconstruct localTM out of new worldTM if (pNode->pParent) { Matrix34 parentWorldInverted = pNode->pParent->worldTM; parentWorldInverted.Invert(); pNode->localTM = parentWorldInverted * pNode->worldTM; } else { pNode->localTM = pNode->worldTM; } for (uint32 e = 0; e < numVRemapping; ++e) { const uint32 i = pSkinningInfo->m_arrExt2IntMap[e]; const IntSkinVertex& intVertex = pSkinningInfo->m_arrIntVertices[i]; pMesh->m_pPositions[e] = intVertex.pos - translation; } // The exporting pipeline is expected to produce pNode->worldTM // with identity orientation only, but let's be paranoid and handle // non-identity orientations properly as well. const float eps = 0.001f; const bool bIdentity = oldWorldTM.GetColumn0().IsEquivalent(Vec3(1, 0, 0), eps) && oldWorldTM.GetColumn1().IsEquivalent(Vec3(0, 1, 0), eps) && oldWorldTM.GetColumn2().IsEquivalent(Vec3(0, 0, 1), eps); if (!bIdentity) { for (uint32 e = 0; e < numVRemapping; ++e) { const uint32 i = pSkinningInfo->m_arrExt2IntMap[e]; pMesh->m_pNorms[e].RotateSafelyBy(oldWorldTM); pMesh->m_pTangents[i].RotateSafelyBy(oldWorldTM); } } } //-------------------------------------------------------------------------- //--- prepare morph-targets --- //-------------------------------------------------------------------------- uint32 numMorphTargets = pSkinningInfo->m_arrMorphTargets.size(); for (uint32 it = 0; it < numMorphTargets; ++it) { //init internal morph-targets MorphTargets* pMorphtarget = pSkinningInfo->m_arrMorphTargets[it]; uint32 numMorphVerts = pMorphtarget->m_arrIntMorph.size(); uint32 intVertexCount = pSkinningInfo->m_arrIntVertices.size(); for (uint32 i = 0; i < numMorphVerts; i++) { uint32 idx = pMorphtarget->m_arrIntMorph[i].nVertexId; assert(idx < intVertexCount); Vec3 mvertex = (mat34 * pMorphtarget->m_arrIntMorph[i].ptVertex) - pSkinningInfo->m_arrIntVertices[idx].pos; pMorphtarget->m_arrIntMorph[i].ptVertex = mvertex; } //init external morph-targets for (uint32 v = 0; v < numMorphVerts; v++) { uint32 idx = pMorphtarget->m_arrIntMorph[v].nVertexId; Vec3 mvertex = pMorphtarget->m_arrIntMorph[v].ptVertex; const uint16* pExtToIntMap = &pSkinningInfo->m_arrExt2IntMap[0]; uint32 numExtVertices = numVRemapping; assert(numExtVertices); for (uint32 i = 0; i < numExtVertices; ++i) { uint32 index = pExtToIntMap[i]; if (index == idx) { SMeshMorphTargetVertex mp; mp.nVertexId = i; mp.ptVertex = mvertex; pMorphtarget->m_arrExtMorph.push_back(mp); } } } } pMesh->m_bbox.Reset(); for (size_t v = 0; v < numVRemapping; ++v) { pMesh->m_bbox.Add(pMesh->m_pPositions[v]); } return true; #endif } //------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------------------------------ ////////////////////////////////////////////////////////////////////////// #if defined(RESOURCE_COMPILER) CContentCGF* CLoaderCGF::MakeCompiledSkinCGF( CContentCGF* pCGF, std::vector* pVertexRemapping, std::vector* pIndexRemapping) PREFAST_SUPPRESS_WARNING(6262) //function uses > 32k stack space { CContentCGF* const pCompiledCGF = new CContentCGF(pCGF->GetFilename()); *pCompiledCGF->GetExportInfo() = *pCGF->GetExportInfo(); // Copy export info. // Compile mesh. // Note that this function cannot fill/return mapping arrays properly in case of // multiple meshes (because mapping is per-mesh), so we will treat multiple // meshes as error. bool bMeshFound = false; for (int i = 0; i < pCGF->GetNodeCount(); ++i) { CNodeCGF* const pNodeCGF = pCGF->GetNode(i); if (!pNodeCGF->pMesh || pNodeCGF->type != CNodeCGF::NODE_MESH || pNodeCGF->bPhysicsProxy) { continue; } if (bMeshFound) { m_LastError.Format("Failed to compile skinned geometry file %s - %s", pCGF->GetFilename(), "*multiple* mesh nodes aren't supported"); delete pCompiledCGF; return 0; } bMeshFound = true; mesh_compiler::CMeshCompiler meshCompiler; meshCompiler.SetIndexRemapping(pIndexRemapping); meshCompiler.SetVertexRemapping(pVertexRemapping); int flags = mesh_compiler::MESH_COMPILE_TANGENTS | mesh_compiler::MESH_COMPILE_OPTIMIZE; if (pCompiledCGF->GetExportInfo()->bUseCustomNormals) { flags |= mesh_compiler::MESH_COMPILE_USECUSTOMNORMALS; } if (!meshCompiler.Compile(*pNodeCGF->pMesh, flags)) { m_LastError.Format("Failed to compile skinned geometry file %s - %s", pCGF->GetFilename(), meshCompiler.GetLastError()); delete pCompiledCGF; return 0; } pCompiledCGF->AddNode(pNodeCGF); // We continue scanning just to detect if we have multiple mesh nodes (to throw an error) } // Compile physics proxy nodes. if (pCGF->GetExportInfo()->bHavePhysicsProxy) { for (int i = 0; i < pCGF->GetNodeCount(); ++i) { CNodeCGF* pNodeCGF = pCGF->GetNode(i); if (pNodeCGF->pMesh && pNodeCGF->bPhysicsProxy) { // Compile physics proxy mesh. mesh_compiler::CMeshCompiler meshCompiler; if (!meshCompiler.Compile(*pNodeCGF->pMesh, mesh_compiler::MESH_COMPILE_OPTIMIZE)) { m_LastError.Format("Failed to compile skinned geometry in node %s in file %s - %s", pNodeCGF->name, pCGF->GetFilename(), meshCompiler.GetLastError()); delete pCompiledCGF; return 0; } } pCompiledCGF->AddNode(pNodeCGF); } } return pCompiledCGF; } #endif ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadExportFlagsChunk(IChunkFile::ChunkDesc* pChunkDesc) { if (pChunkDesc->chunkVersion != EXPORT_FLAGS_CHUNK_DESC::VERSION) { m_LastError.Format("Unknown version of export flags chunk"); return false; } EXPORT_FLAGS_CHUNK_DESC& chunk = *(EXPORT_FLAGS_CHUNK_DESC*)pChunkDesc->data; SwapEndian(chunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; CExportInfoCGF* pExportInfo = m_pCGF->GetExportInfo(); if (chunk.flags & EXPORT_FLAGS_CHUNK_DESC::MERGE_ALL_NODES) { pExportInfo->bMergeAllNodes = true; } else { pExportInfo->bMergeAllNodes = false; } if (chunk.flags & EXPORT_FLAGS_CHUNK_DESC::HAVE_AUTO_LODS) { pExportInfo->bHaveAutoLods = true; } else { pExportInfo->bHaveAutoLods = false; } if (chunk.flags & EXPORT_FLAGS_CHUNK_DESC::USE_CUSTOM_NORMALS) { pExportInfo->bUseCustomNormals = true; } else { pExportInfo->bUseCustomNormals = false; } if (chunk.flags & EXPORT_FLAGS_CHUNK_DESC::WANT_F32_VERTICES) { pExportInfo->bWantF32Vertices = true; } else { pExportInfo->bWantF32Vertices = false; } if (chunk.flags & EXPORT_FLAGS_CHUNK_DESC::EIGHT_WEIGHTS_PER_VERTEX) { pExportInfo->b8WeightsPerVertex = true; } else { pExportInfo->b8WeightsPerVertex = false; } if (chunk.flags & EXPORT_FLAGS_CHUNK_DESC::SKINNED_CGF) { pExportInfo->bSkinnedCGF = true; } else { pExportInfo->bSkinnedCGF = false; } return true; } inline const char* stristr2(const char* szString, const char* szSubstring) { int nSuperstringLength = (int)strlen(szString); int nSubstringLength = (int)strlen(szSubstring); for (int nSubstringPos = 0; nSubstringPos <= nSuperstringLength - nSubstringLength; ++nSubstringPos) { if (_strnicmp(szString + nSubstringPos, szSubstring, nSubstringLength) == 0) { return szString + nSubstringPos; } } return NULL; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadNodeChunk(IChunkFile::ChunkDesc* pChunkDesc, bool bJustGeometry) { if (pChunkDesc->chunkVersion != NODE_CHUNK_DESC_0824::VERSION && pChunkDesc->chunkVersion != NODE_CHUNK_DESC_0824::COMPATIBLE_OLD_VERSION) { m_LastError.Format( "Unknown version (0x%x) of Node chunk. The only supported versions are 0x%x and 0x%x.", (uint)pChunkDesc->chunkVersion, (uint)NODE_CHUNK_DESC_0824::VERSION, (uint)NODE_CHUNK_DESC_0824::COMPATIBLE_OLD_VERSION); return false; } NODE_CHUNK_DESC_0824* nodeChunk = (NODE_CHUNK_DESC_0824*)pChunkDesc->data; assert(nodeChunk); SwapEndian(*nodeChunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; CNodeCGF* pNodeCGF = Construct(InplaceFactory(m_pDestructFnc), m_pAllocFnc); m_pCGF->AddNode(pNodeCGF); cry_strcpy(pNodeCGF->name, nodeChunk->name); // Fill node object. pNodeCGF->nChunkId = pChunkDesc->chunkId; pNodeCGF->nParentChunkId = nodeChunk->ParentID; pNodeCGF->nObjectChunkId = nodeChunk->ObjectID; pNodeCGF->pParent = 0; pNodeCGF->pMesh = 0; pNodeCGF->pos_cont_id = nodeChunk->pos_cont_id; pNodeCGF->rot_cont_id = nodeChunk->rot_cont_id; pNodeCGF->scl_cont_id = nodeChunk->scl_cont_id; pNodeCGF->pMaterial = 0; if (nodeChunk->MatID > 0) { pNodeCGF->pMaterial = LoadMaterialFromChunk(nodeChunk->MatID); if (!pNodeCGF->pMaterial) { return false; } } { const float* const pMat = &nodeChunk->tm[0][0]; pNodeCGF->localTM.SetFromVectors( Vec3(pMat[0], pMat[1], pMat[2]), Vec3(pMat[4], pMat[5], pMat[6]), Vec3(pMat[8], pMat[9], pMat[10]), Vec3(pMat[12] * VERTEX_SCALE, pMat[13] * VERTEX_SCALE, pMat[14] * VERTEX_SCALE)); } if (pNodeCGF->nParentChunkId > 1) { pNodeCGF->bIdentityMatrix = false; } else { // FIXME: 1) Other code in CryEngine sets bIdentityMatrix by analyzing worldTM instead of localTM. What is the right choice? // FIXME: 2) bIdentityMatrix is re-computed in CLoaderCGF::ProcessNodes(). What is the point of computing it here as well? pNodeCGF->bIdentityMatrix = pNodeCGF->localTM.IsIdentity(); } if (nodeChunk->PropStrLen > 0) { pNodeCGF->properties.Format("%.*s", nodeChunk->PropStrLen, ((const char*)nodeChunk) + sizeof(*nodeChunk)); } // By default node type is mesh. pNodeCGF->type = CNodeCGF::NODE_MESH; pNodeCGF->bPhysicsProxy = false; if (stristr2(nodeChunk->name, PHYSICS_PROXY_NODE) || stristr2(nodeChunk->name, PHYSICS_PROXY_NODE2) || stristr2(nodeChunk->name, PHYSICS_PROXY_NODE3)) { pNodeCGF->type = CNodeCGF::NODE_HELPER; pNodeCGF->bPhysicsProxy = true; m_pCGF->GetExportInfo()->bHavePhysicsProxy = true; } else if (nodeChunk->name[0] == '$') { pNodeCGF->type = CNodeCGF::NODE_HELPER; } // Check if valid object node. if (nodeChunk->ObjectID > 0) { IChunkFile::ChunkDesc* const pObjChunkDesc = m_pChunkFile->FindChunkById(nodeChunk->ObjectID); if (!pObjChunkDesc) { assert(pObjChunkDesc); m_LastError.Format("Failed to find chunk with id %d", nodeChunk->ObjectID); return false; } if (pObjChunkDesc->chunkType == ChunkType_Mesh) { if (pNodeCGF->type == CNodeCGF::NODE_HELPER) { pNodeCGF->helperType = HP_GEOMETRY; } if (!LoadGeomChunk(pNodeCGF, pObjChunkDesc)) { return false; } } else if (!bJustGeometry) { if (pObjChunkDesc->chunkType == ChunkType_Helper) { pNodeCGF->type = CNodeCGF::NODE_HELPER; if (!LoadHelperChunk(pNodeCGF, pObjChunkDesc)) { return false; } } } } else { // pNodeCGF->type = CNodeCGF::NODE_HELPER; // pNodeCGF->helperType = HP_POINT; // pNodeCGF->helperSize = Vec3(0.01f,0.01f,0.01f); } return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadHelperChunk(CNodeCGF* pNode, IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->chunkVersion != HELPER_CHUNK_DESC::VERSION) { m_LastError.Format("Unknown version of Helper chunk"); return false; } HELPER_CHUNK_DESC& chunk = *(HELPER_CHUNK_DESC*)pChunkDesc->data; assert(&chunk); SwapEndian(chunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; // Fill node object. pNode->helperType = chunk.type; pNode->helperSize = chunk.size; return true; } ////////////////////////////////////////////////////////////////////////// void CLoaderCGF::ProcessNodes() { LOADING_TIME_PROFILE_SECTION; ////////////////////////////////////////////////////////////////////////// // Bind Nodes parents. ////////////////////////////////////////////////////////////////////////// for (int i = 0; i < m_pCGF->GetNodeCount(); i++) { if (m_pCGF->GetNode(i)->nParentChunkId > 0) { for (int j = 0; j < m_pCGF->GetNodeCount(); j++) { if (m_pCGF->GetNode(i)->nParentChunkId == m_pCGF->GetNode(j)->nChunkId) { m_pCGF->GetNode(i)->pParent = m_pCGF->GetNode(j); break; } } } } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // Calculate Node world matrices. ////////////////////////////////////////////////////////////////////////// for (int i = 0; i < m_pCGF->GetNodeCount(); i++) { CNodeCGF* pNode = m_pCGF->GetNode(i); Matrix34 tm = pNode->localTM; for (CNodeCGF* pCurNode = pNode->pParent; pCurNode; pCurNode = pCurNode->pParent) { tm = pCurNode->localTM * tm; } pNode->worldTM = tm; pNode->bIdentityMatrix = pNode->worldTM.IsIdentity(); if (pNode->pMesh) { SetupMeshSubsets(*pNode->pMesh, pNode->pMaterial); } } } ////////////////////////////////////////////////////////////////////////// void CLoaderCGF::SetupMeshSubsets(CMesh& mesh, CMaterialCGF* pMaterialCGF) { if (!m_pCGF->GetExportInfo()->bCompiledCGF) { const DynArray& usedMaterialIds = m_pCGF->GetUsedMaterialIDs(); if (mesh.m_subsets.empty()) { ////////////////////////////////////////////////////////////////////////// // Setup mesh subsets. ////////////////////////////////////////////////////////////////////////// for (int i = 0; i < usedMaterialIds.size(); i++) { SMeshSubset meshSubset; int nMatID = usedMaterialIds[i]; meshSubset.nMatID = nMatID; meshSubset.nPhysicalizeType = PHYS_GEOM_TYPE_NONE; mesh.m_subsets.push_back(meshSubset); } } } if (pMaterialCGF) { for (int i = 0; i < mesh.m_subsets.size(); i++) { SMeshSubset& meshSubset = mesh.m_subsets[i]; if (pMaterialCGF->subMaterials.size() > 0) { int id = meshSubset.nMatID; if (id >= (int)pMaterialCGF->subMaterials.size()) { // Let's use 3dsMax's approach of handling material ids out of range id %= (int)pMaterialCGF->subMaterials.size(); } if (id >= 0 && pMaterialCGF->subMaterials[id] != NULL) { meshSubset.nPhysicalizeType = pMaterialCGF->subMaterials[id]->nPhysicalizeType; } else { Warning("Submaterial %d is not available for subset %d in %s", meshSubset.nMatID, i, m_filename); } } else { meshSubset.nPhysicalizeType = pMaterialCGF->nPhysicalizeType; } } } } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadGeomChunk(CNodeCGF* pNode, IChunkFile::ChunkDesc* pChunkDesc) { LOADING_TIME_PROFILE_SECTION; // First check if this geometry chunk was already loaded by some node. int nNumNodes = m_pCGF->GetNodeCount(); for (int i = 0; i < nNumNodes; i++) { CNodeCGF* pOldNode = m_pCGF->GetNode(i); if (pOldNode != pNode && pOldNode->nObjectChunkId == pChunkDesc->chunkId) { pNode->pMesh = pOldNode->pMesh; pNode->pSharedMesh = pOldNode; return true; } } assert(pChunkDesc && pChunkDesc->chunkType == ChunkType_Mesh); if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0801::VERSION || pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0801::COMPATIBLE_OLD_VERSION || pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0802::VERSION) { m_pCGF->GetExportInfo()->bCompiledCGF = true; return LoadCompiledMeshChunk(pNode, pChunkDesc); } // Uncompiled format if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0745::VERSION || pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0745::COMPATIBLE_OLD_VERSION) { #if !defined(RESOURCE_COMPILER) && !defined(ENABLE_NON_COMPILED_CGF) m_LastError.Format("%s: non-compiled geometry chunk in %s", __FUNCTION__, m_filename); return false; #else m_pCGF->GetExportInfo()->bCompiledCGF = false; const int maxLinkCount = m_pCGF->GetExportInfo()->b8WeightsPerVertex ? 8 : ((m_maxWeightsPerVertex <= 8) ? m_maxWeightsPerVertex : 8); // CMesh doesn't support more than 8 weights const bool bSwapEndianness = pChunkDesc->bSwapEndian; pChunkDesc->bSwapEndian = false; uint8* pMeshChunkData = (uint8*)pChunkDesc->data; MESH_CHUNK_DESC_0745* chunk; StepData(chunk, pMeshChunkData, 1, bSwapEndianness); if (!(chunk->flags2 & MESH_CHUNK_DESC_0745::FLAG2_HAS_TOPOLOGY_IDS)) { m_LastError.Format("%s: obsolete non-compiled geometry chunk format in %s", __FUNCTION__, m_filename); return false; } ////////////////////////////////////////////////////////////////////////// // Preparing source mesh data (may contain duplicate vertices) ////////////////////////////////////////////////////////////////////////// MeshUtils::Mesh mesh; const char* err = 0; if (chunk->nVerts <= 0) { m_LastError.Format("%s: missing vertices in %s", __FUNCTION__, m_filename); return false; } if (chunk->nFaces <= 0) { m_LastError.Format("%s: missing faces in %s", __FUNCTION__, m_filename); return false; } if (chunk->nTVerts != 0 && chunk->nTVerts != chunk->nVerts) { m_LastError.Format("%s: Number of texture coordinates doesn't match number of vertices", __FUNCTION__); return false; } // Preparing positions and normals { CryVertex* p; StepData(p, pMeshChunkData, chunk->nVerts, bSwapEndianness); err = mesh.SetPositions(&p->p.x, chunk->nVerts, sizeof(*p), VERTEX_SCALE); // VERTEX_SCALE - to convert from centimeters to meters if (!err) { err = mesh.SetNormals(&p->n.x, chunk->nVerts, sizeof(*p)); } if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } // Preparing faces and face material IDs { CryFace* p; StepData(p, pMeshChunkData, chunk->nFaces, bSwapEndianness); err = mesh.SetFaces(&p->v0, chunk->nFaces, sizeof(*p)); if (!err) { err = mesh.SetFaceMatIds(&p->MatID, chunk->nFaces, sizeof(*p), MAX_SUB_MATERIALS - 1); } if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } mesh.RemoveDegradedFaces(); } // Preparing topology IDs { int* p; StepData(p, pMeshChunkData, chunk->nVerts, bSwapEndianness); err = mesh.SetTopologyIds(p, chunk->nVerts, sizeof(*p)); if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } // Preparing texture coordinates if (chunk->nTVerts > 0) { CryUV* p; StepData(p, pMeshChunkData, chunk->nVerts, bSwapEndianness); err = mesh.SetTexCoords(&p->u, chunk->nVerts, sizeof(*p), true, 0); if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } // Preparing vertex-bone links if (chunk->flags1 & MESH_CHUNK_DESC_0745::FLAG1_BONE_INFO) { mesh.m_links.resize(chunk->nVerts); for (int i = 0; i < chunk->nVerts; ++i) { MeshUtils::VertexLinks& linksDst = mesh.m_links[i]; int32* pNumLinks; StepData(pNumLinks, pMeshChunkData, 1, bSwapEndianness); if (pNumLinks == nullptr) { return false; } if (*pNumLinks <= 0) { m_LastError.Format("%s: Number of links for vertex is invalid: %i", __FUNCTION__, *pNumLinks); return false; } linksDst.links.resize(*pNumLinks); CryLink* pLinksSrc; StepData(pLinksSrc, pMeshChunkData, *pNumLinks, bSwapEndianness); for (int j = 0; j < *pNumLinks; ++j) { linksDst.links[j].boneId = pLinksSrc[j].BoneID; linksDst.links[j].weight = pLinksSrc[j].Blending; linksDst.links[j].offset = pLinksSrc[j].offset * VERTEX_SCALE; } err = linksDst.Normalize(MeshUtils::VertexLinks::eSort_ByWeight, 0.0f, maxLinkCount); if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } } // Preparing colors if (chunk->flags2 & MESH_CHUNK_DESC_0745::FLAG2_HAS_VERTEX_COLOR) { CryIRGB* p; StepData(p, pMeshChunkData, chunk->nVerts, bSwapEndianness); assert(&p->r < &p->b); err = mesh.SetColors(&p->r, chunk->nVerts, sizeof(*p)); if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } // Preparing alphas if (chunk->flags2 & MESH_CHUNK_DESC_0745::FLAG2_HAS_VERTEX_ALPHA) { uint8* p; StepData(p, pMeshChunkData, chunk->nVerts, bSwapEndianness); err = mesh.SetAlphas(p, chunk->nVerts, sizeof(*p)); if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } // Prevent sharing materials by vertices (this call might create new vertices) mesh.SetVertexMaterialIdsFromFaceMaterialIds(); // Validation { err = mesh.Validate(); if (err) { m_LastError.Format("%s: Failed: %s", __FUNCTION__, err); return false; } } ////////////////////////////////////////////////////////////////////////// // Compute vertex remapping ////////////////////////////////////////////////////////////////////////// mesh.ComputeVertexRemapping(); ////////////////////////////////////////////////////////////////////////// // Creating resulting mesh ////////////////////////////////////////////////////////////////////////// CMesh* const pMesh = new CMesh(); const int nVerts = mesh.m_vertexOldToNew.size(); const int nVertsNew = mesh.m_vertexNewToOld.size(); // Filling positions, normals, topology IDs, texture coordinates, colors { pMesh->SetVertexCount(nVertsNew); pMesh->ReallocStream(CMesh::TOPOLOGY_IDS, 0, nVertsNew); pMesh->ReallocStream(CMesh::TEXCOORDS, 0, nVertsNew); if (!mesh.m_colors.empty() || !mesh.m_alphas.empty()) { pMesh->ReallocStream(CMesh::COLORS, 0, nVertsNew); } for (uint32 uvSet = 0; uvSet < mesh.m_texCoords.size(); ++uvSet) { if (!mesh.m_texCoords[uvSet].empty()) { SMeshTexCoord* texCoords = pMesh->GetStreamPtr(CMesh::TEXCOORDS, uvSet); for (int i = 0; i < nVertsNew; ++i) { const int origVertex = mesh.m_vertexNewToOld[i]; texCoords[i] = SMeshTexCoord(mesh.m_texCoords[uvSet][origVertex].x, mesh.m_texCoords[uvSet][origVertex].y); } } } for (int i = 0; i < nVertsNew; ++i) { const int origVertex = mesh.m_vertexNewToOld[i]; pMesh->m_pPositions[i] = mesh.m_positions[origVertex]; pMesh->m_pNorms[i] = SMeshNormal(mesh.m_normals[origVertex]); pMesh->m_pTopologyIds[i] = mesh.m_topologyIds[origVertex]; if (pMesh->m_pColor0) { uint8 r = 0xFF; uint8 g = 0xFF; uint8 b = 0xFF; uint8 a = 0xFF; if (!mesh.m_colors.empty()) { r = mesh.m_colors[origVertex].r; g = mesh.m_colors[origVertex].g; b = mesh.m_colors[origVertex].b; } if (!mesh.m_alphas.empty()) { a = mesh.m_alphas[origVertex]; } pMesh->m_pColor0[i] = SMeshColor(r, g, b, a); } } } // Filling vertex-bone links { m_arrLinksTmp.clear(); if (!mesh.m_links.empty()) { m_arrLinksTmp.reserve(nVertsNew); for (int i = 0; i < nVertsNew; ++i) { const int origVertex = mesh.m_vertexNewToOld[i]; m_arrLinksTmp.push_back(mesh.m_links[origVertex]); } // Remember the mapping table for future re-mapping of uncompiled Morph Target vertices (if any) m_vertexOldToNew = mesh.m_vertexOldToNew; } } // Filling faces { const int nFaces = mesh.GetFaceCount(); pMesh->SetFaceCount(nFaces); DynArray& usedMaterialIds = m_pCGF->GetUsedMaterialIDs(); for (int i = 0; i < nFaces; ++i) { const MeshUtils::Face& cf = mesh.m_faces[i]; const int matId = mesh.m_faceMatIds[i]; SMeshFace& face = pMesh->m_pFaces[i]; face.v[0] = mesh.m_vertexOldToNew[cf.vertexIndex[0]]; face.v[1] = mesh.m_vertexOldToNew[cf.vertexIndex[1]]; face.v[2] = mesh.m_vertexOldToNew[cf.vertexIndex[2]]; // Map material ID to index of subset if (!MatIdToSubset[matId]) { MatIdToSubset[matId] = 1 + nLastChunkId++; // Order of material ids in usedMaterialIds correspond to the indices of chunks. usedMaterialIds.push_back(matId); } face.nSubset = MatIdToSubset[matId] - 1; } } // Computing AABB pMesh->m_bbox.Reset(); for (int i = 0; i < nVertsNew; ++i) { pMesh->m_bbox.Add(pMesh->m_pPositions[i]); } // Saving results pNode->pMesh = pMesh; return true; #endif } m_LastError.Format("%s: unknown geometry chunk version in %s", __FUNCTION__, m_filename); return false; } template bool CLoaderCGF::LoadStreamChunk(CMesh& mesh, const MESH_CHUNK_DESC& chunk, ECgfStreamType Type, int streamIndex, CMesh::EStream MStream) { if (chunk.GetStreamChunkID(Type, streamIndex) <= 0) { return true; } void* pStreamData; int nStreamType; int nStreamIndex; int nElemCount; int nElemSize; bool bSwapEndianness; if (!LoadStreamDataChunk(chunk.GetStreamChunkID(Type, streamIndex), pStreamData, nStreamType, nStreamIndex, nElemCount, nElemSize, bSwapEndianness)) { return false; } if (nStreamType != Type) { m_LastError.Format("Mesh stream type %d stream number %d has unknown type (%d instead of %d)", (int)MStream, streamIndex, (int)nStreamType, (int)Type); return false; } if (nStreamIndex != streamIndex) { m_LastError.Format("Mesh stream index for type %d did not match what was expected (%d instead of %d)", (int)Type, nStreamIndex, streamIndex); return false; } if (nElemSize != sizeof(T)) { m_LastError.Format("Mesh stream type %d stream number %d has damaged data (elemSize:%u)", (int)MStream, streamIndex, (uint)nElemSize); return false; } SwapEndian((T*)pStreamData, nElemCount, bSwapEndianness); void* pMeshElements = 0; { const bool bSourceAligned = (((UINT_PTR)pStreamData & 0x3) == 0); const bool bShare = (m_bUseReadOnlyMesh && bSourceAligned && m_bAllowStreamSharing); if (bShare) { mesh.SetSharedStream(MStream, streamIndex, pStreamData, nElemCount); } else { mesh.ReallocStream(MStream, streamIndex, nElemCount); } int nMeshElemSize = 0; mesh.GetStreamInfo(MStream, streamIndex, pMeshElements, nMeshElemSize); if (nMeshElemSize != nElemSize || nMeshElemSize != sizeof(T)) { m_LastError.Format("Mesh stream type %d stream number %d has damaged data (elemCount:%u, elemSize:%u)", (int)MStream, streamIndex, (uint)nElemCount, (uint)nMeshElemSize); return false; } if (!bShare) { memcpy(pMeshElements, pStreamData, nElemCount * nElemSize); } } return true; } template bool CLoaderCGF::LoadStreamChunk(CMesh& mesh, const MESH_CHUNK_DESC& chunk, ECgfStreamType Type, int streamIndex, CMesh::EStream MStreamA, CMesh::EStream MStreamB) { if (chunk.GetStreamChunkID(Type, streamIndex) <= 0) { return true; } void* pStreamData; int nStreamType; int nStreamIndex; int nElemCount; int nElemSize; bool bSwapEndianness; if (!LoadStreamDataChunk(chunk.GetStreamChunkID(Type, streamIndex), pStreamData, nStreamType, nStreamIndex, nElemCount, nElemSize, bSwapEndianness)) { return false; } if (nStreamType != Type) { m_LastError.Format("Mesh stream type %d/%d stream number %d/%d has unknown type (%d instead of %d)", (int)MStreamA, (int)MStreamB, streamIndex, streamIndex, (int)nStreamType, (int)Type); return false; } if (nStreamIndex != streamIndex) { m_LastError.Format("Mesh stream index for type %d did not match what was expected (%d instead of %d)", (int)Type, nStreamIndex, streamIndex); return false; } if ((nElemSize != sizeof(TA) && nElemSize != sizeof(TB))) { m_LastError.Format("Mesh stream type %d/%d stream number %d/%d has unsupported element size (%u instead of %u or %u)", (int)MStreamA, (int)MStreamB, streamIndex, streamIndex, (uint)nElemSize, (uint)sizeof(TA), (uint)sizeof(TB)); return false; } const bool bUseA = (nElemSize == sizeof(TA)); if (bUseA) { SwapEndian((TA*)pStreamData, nElemCount, bSwapEndianness); } else { SwapEndian((TB*)pStreamData, nElemCount, bSwapEndianness); } void* pMeshElements = 0; { const bool bSourceAligned = (((UINT_PTR)pStreamData & 0x3) == 0); const bool bShare = (m_bUseReadOnlyMesh && bSourceAligned && m_bAllowStreamSharing); if (bShare) { mesh.SetSharedStream((bUseA ? MStreamA : MStreamB), streamIndex, pStreamData, nElemCount); } else { mesh.ReallocStream((bUseA ? MStreamA : MStreamB), streamIndex, nElemCount); } int nMeshElemSize = 0; mesh.GetStreamInfo((bUseA ? MStreamA : MStreamB), streamIndex, pMeshElements, nMeshElemSize); if (nMeshElemSize != nElemSize || nMeshElemSize != (bUseA ? sizeof(TA) : sizeof(TB))) { m_LastError.Format("Mesh stream type %d stream number %d has damaged data (elemCount:%u, elemSize:%u)", (int)(bUseA ? MStreamA : MStreamB), streamIndex, (uint)nElemCount, (uint)nMeshElemSize); return false; } if (!bShare) { memcpy(pMeshElements, pStreamData, nElemCount * nElemSize); } } return true; } template bool CLoaderCGF::LoadBoneMappingStreamChunk(CMesh& mesh, const MESH_CHUNK_DESC& chunk, const std::vector >& globalBonesPerSubset) { const ECgfStreamType Type = CGF_STREAM_BONEMAPPING; CMesh::EStream MStream = CMesh::BONEMAPPING; // There is only one index stream per mesh, so get index stream 0 int streamIndex = 0; if (chunk.GetStreamChunkID(Type, streamIndex) <= 0) { return true; } void* pStreamData; int nStreamType; int nStreamIndex; int nElemCount; int nStreamElemSize; bool bSwapEndianness; if (!LoadStreamDataChunk(chunk.GetStreamChunkID(Type, streamIndex), pStreamData, nStreamType, nStreamIndex, nElemCount, nStreamElemSize, bSwapEndianness)) { return false; } if (nStreamType != Type) { m_LastError.Format("Bone mapping stream %d has unknown type (%d instead of %d)", (int)MStream, (int)nStreamType, (int)Type); return false; } if (nElemCount != mesh.GetVertexCount() && nElemCount != 2 * mesh.GetVertexCount()) { m_LastError.Format("Bone mapping stream %d has wrong # vertices %d (expected %d or %d)", (int)MStream, nElemCount, mesh.GetVertexCount(), 2 * mesh.GetVertexCount()); return false; } COMPILE_TIME_ASSERT(sizeof(mesh.m_pBoneMapping[0]) == sizeof(SMeshBoneMapping_uint16)); if (nStreamElemSize == sizeof(SMeshBoneMapping_uint8)) { // Obsolete format. We support it just because many existing asset files use it. if (globalBonesPerSubset.size() != mesh.m_subsets.size()) { m_LastError.Format("Bad or missing bone remapping tables. Contact an RC programmer."); return false; } SwapEndian((SMeshBoneMapping_uint8*)pStreamData, nElemCount, bSwapEndianness); // Converting local (per-preset) uint8 bone indices to global bone uint16 indices mesh.ReallocStream(MStream, nStreamIndex, nElemCount); SMeshBoneMapping_uint16* const pMeshElements = mesh.GetStreamPtr(MStream, nStreamIndex); if (!pMeshElements) { // Should never happen because we did check it by COMPILE_TIME_ASSERT() above. m_LastError.Format("Bone mapping has invalid size. Contact an RC programmer."); return false; } // Filling bone indices with 0xFFFF allows us to perform input data // validation (see code with '== 0xFFFF' below) memset(pMeshElements, 0xFF, nElemCount * sizeof(pMeshElements[0])); const SMeshBoneMapping_uint8* const pSrcBoneMapping = (const SMeshBoneMapping_uint8*)pStreamData; const int vertexCount = mesh.GetVertexCount(); const int indexCount = mesh.GetIndexCount(); for (int subset = 0; subset < mesh.m_subsets.size(); ++subset) { SMeshSubset& meshSubset = mesh.m_subsets[subset]; const std::vector& globalBones = globalBonesPerSubset[subset]; if (meshSubset.nNumIndices == 0) { continue; } for (int extra = 0; extra < nElemCount; extra += vertexCount) { for (int j = meshSubset.nFirstIndexId; j < meshSubset.nFirstIndexId + meshSubset.nNumIndices; ++j) { const int vIdx = mesh.m_pIndices[j]; if (vIdx < meshSubset.nFirstVertId || vIdx >= meshSubset.nFirstVertId + meshSubset.nNumVerts) { m_LastError.Format("Index stream contains invalid vertex index."); return false; } for (int k = 0; k < 4; ++k) { const uint8 weight = pSrcBoneMapping[vIdx + extra].weights[k]; if (weight <= 0) { if (pMeshElements[vIdx + extra].boneIds[k] == 0xFFFF) { pMeshElements[vIdx + extra].weights[k] = 0; pMeshElements[vIdx + extra].boneIds[k] = 0; } else if (pMeshElements[vIdx + extra].weights[k] != 0 || pMeshElements[vIdx + extra].boneIds[k] != 0) { m_LastError.Format("Conflicting vertex-bone references."); return false; } continue; } const uint8 boneIdx = pSrcBoneMapping[vIdx + extra].boneIds[k]; if (boneIdx < 0 || (size_t)boneIdx >= globalBones.size()) { m_LastError.Format( "Bad bone mapping found in subset %d, vertex %d: boneIdx %d, # bones in subset %d.", subset, vIdx, boneIdx, (int)globalBones.size()); return false; } const uint16 globalBoneIdx = globalBones[boneIdx]; if (pMeshElements[vIdx + extra].boneIds[k] == 0xFFFF) { pMeshElements[vIdx + extra].weights[k] = weight; pMeshElements[vIdx + extra].boneIds[k] = globalBoneIdx; } else if (pMeshElements[vIdx + extra].weights[k] != weight || pMeshElements[vIdx + extra].boneIds[k] != globalBoneIdx) { m_LastError.Format("Conflicting vertex-bone references."); return false; } } } } } int orphanVertexCount = 0; for (int i = 0; i < nElemCount; ++i) { for (int k = 0; k < 4; ++k) { const uint boneIdx = pMeshElements[i].boneIds[k]; if (boneIdx >= 0xFFFF) { ++orphanVertexCount; pMeshElements[i].weights[k] = 0; pMeshElements[i].boneIds[k] = 0; } } } if (orphanVertexCount) { orphanVertexCount /= 4 * (nElemCount / vertexCount); CryWarning( VALIDATOR_MODULE_ASSETS, VALIDATOR_WARNING, "Found %d orphan vertices", orphanVertexCount); } } else if (nStreamElemSize == sizeof(SMeshBoneMapping_uint16)) { SwapEndian((SMeshBoneMapping_uint16*)pStreamData, nElemCount, bSwapEndianness); const bool bSourceAligned = (((UINT_PTR)pStreamData & 0x3) == 0); const bool bShare = (m_bUseReadOnlyMesh && bSourceAligned && m_bAllowStreamSharing); if (bShare) { mesh.SetSharedStream(MStream, nStreamIndex, pStreamData, nElemCount); } else { mesh.ReallocStream(MStream, nStreamIndex, nElemCount); } SMeshBoneMapping_uint16* const pMeshElements = mesh.GetStreamPtr(MStream, nStreamIndex); if (!pMeshElements) { // Should never happen because we did check it by COMPILE_TIME_ASSERT() above. m_LastError.Format("Bone mapping has invalid size. Contact an RC programmer."); return false; } if (!bShare) { memcpy(pMeshElements, pStreamData, nElemCount * sizeof(pMeshElements[0])); } } else { m_LastError.Format("Bone mapping stream %d has damaged data (elemSize:%u)", (int)MStream, (uint)nStreamElemSize); return false; } // Validation { SMeshBoneMapping_uint16* const pMeshElements = mesh.GetStreamPtr(MStream, nStreamIndex); for (int i = 0; i < nElemCount; ++i) { for (int k = 0; k < 4; ++k) { const uint boneIdx = pMeshElements[i].boneIds[k]; if (boneIdx >= MAX_NUMBER_OF_BONES) { m_LastError.Format("Bad bone index detected: %u.", (uint)boneIdx); return false; } } } } return true; } template bool CLoaderCGF::LoadIndexStreamChunk(CMesh& mesh, const MESH_CHUNK_DESC& chunk) { const ECgfStreamType Type = CGF_STREAM_INDICES; CMesh::EStream MStream = CMesh::INDICES; // There is only one index stream per mesh int streamIndex = 0; if (chunk.GetStreamChunkID(Type, streamIndex) <= 0) { return true; } void* pStreamData; int nStreamType; int nStreamIndex; int nElemCount; int nStreamElemSize; bool bSwapEndianness; if (!LoadStreamDataChunk(chunk.GetStreamChunkID(Type, streamIndex), pStreamData, nStreamType, nStreamIndex, nElemCount, nStreamElemSize, bSwapEndianness)) { return false; } if (nStreamType != Type) { m_LastError.Format("Index stream %d has unknown type (%d instead of %d)", (int)MStream, (int)nStreamType, (int)Type); return false; } if (nStreamElemSize == sizeof(uint16)) { SwapEndian((uint16*)pStreamData, nElemCount, bSwapEndianness); } else if (nStreamElemSize == sizeof(uint32)) { SwapEndian((uint32*)pStreamData, nElemCount, bSwapEndianness); } else { m_LastError.Format("Index stream %d has damaged data (elemSize:%u)", (int)MStream, (uint)nStreamElemSize); return false; } const bool bSourceAligned = (((UINT_PTR)pStreamData & 0x3) == 0); const bool bShare = (m_bUseReadOnlyMesh && bSourceAligned && m_bAllowStreamSharing); COMPILE_TIME_ASSERT(sizeof(mesh.m_pIndices[0]) == sizeof(vtx_idx)); COMPILE_TIME_ASSERT(sizeof(vtx_idx) == 2 || sizeof(vtx_idx) == 4); if (nStreamElemSize == sizeof(vtx_idx)) { if (bShare) { mesh.SetSharedStream(MStream, nStreamIndex, pStreamData, nElemCount); } else { mesh.ReallocStream(MStream, nStreamIndex, nElemCount); } void* pMeshIndices = 0; int nMeshIndexSize = 0; mesh.GetStreamInfo(MStream, nStreamIndex, pMeshIndices, nMeshIndexSize); if (nMeshIndexSize != sizeof(vtx_idx)) { // Should never happen - we already did COMPILE_TIME_ASSERT(sizeof(mesh.m_pIndices[0]) == sizeof(vtx_idx)) m_LastError.Format("Vertex index has invalid size. Contact an RC programmer."); return false; } if (!bShare) { memcpy(pMeshIndices, pStreamData, nElemCount * nStreamElemSize); } } else { // Converting index format uint16 <--> uint32 #if !defined(RESOURCE_COMPILER) #if 0 // Sokov: commented out the warning because it was confusing users. We will uncomment it after implementing converting files to proper index format during asset exporting. CryWarning( VALIDATOR_MODULE_ASSETS, VALIDATOR_WARNING, "%s: This asset contains vertex indices in incorrect format (%u-bit instead of %u-bit). " "Probably it didn't go through build process yet. " "Disregard this message if this asset was just exported.", m_filename, (uint)nStreamElemSize * 8, (uint)sizeof(vtx_idx) * 8); #endif #endif mesh.ReallocStream(MStream, nStreamIndex, nElemCount); void* pMeshIndices = 0; int nMeshIndexSizeCheck = 0; mesh.GetStreamInfo(MStream, nStreamIndex, pMeshIndices, nMeshIndexSizeCheck); if (nMeshIndexSizeCheck != sizeof(vtx_idx)) { // Should never happen - we already did COMPILE_TIME_ASSERT(sizeof(mesh.m_pIndices[0]) == sizeof(vtx_idx)) m_LastError.Format("Vertex index has invalid size. Contact an RC programmer."); return false; } if (nStreamElemSize == sizeof(uint16)) { const uint16* const pSrc = (const uint16*)pStreamData; for (int i = 0; i < nElemCount; ++i) { ((uint32*)pMeshIndices)[i] = (uint32)pSrc[i]; } } else { const uint32* const pSrc = (const uint32*)pStreamData; for (int i = 0; i < nElemCount; ++i) { const uint32 idx = pSrc[i]; if (idx >= 0xffff) // ">=": index 0xffff is reserved (the engine uses it to mark invalid indices etc) { m_LastError.Format("Cannot convert index stream %d from %u-bit to %u-bit format because it contains index %u", (int)MStream, (uint)nStreamElemSize * 8, (uint)sizeof(vtx_idx) * 8, (uint)pSrc[i]); return false; } ((uint16*)pMeshIndices)[i] = (uint16)idx; } } } return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadCompiledMeshChunk(CNodeCGF* pNode, IChunkFile::ChunkDesc* pChunkDesc) { if (!pChunkDesc || pChunkDesc->chunkType != ChunkType_Mesh) { m_LastError.Format("Corrupted compiled mesh chunk"); return false; } if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0802::VERSION) { MESH_CHUNK_DESC_0802& chunk = *(MESH_CHUNK_DESC_0802*)pChunkDesc->data; return LoadCompiledMeshChunk(pNode, pChunkDesc, chunk); } if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0801::VERSION || pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0801::COMPATIBLE_OLD_VERSION) { MESH_CHUNK_DESC_0801& chunk = *(MESH_CHUNK_DESC_0801*)pChunkDesc->data; return LoadCompiledMeshChunk(pNode, pChunkDesc, chunk); } m_LastError.Format("Unknown version of compiled mesh chunk"); return false; } template bool CLoaderCGF::LoadCompiledMeshChunk(CNodeCGF* pNode, IChunkFile::ChunkDesc* pChunkDesc, MESH_CHUNK_DESC chunk) { LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->bSwapEndian) { SwapEndian(chunk, true); pChunkDesc->bSwapEndian = false; } Vec3 bboxMin, bboxMax; memcpy(&bboxMin, &chunk.bboxMin, sizeof(bboxMin)); memcpy(&bboxMax, &chunk.bboxMax, sizeof(bboxMax)); pNode->meshInfo.nVerts = chunk.nVerts; pNode->meshInfo.nIndices = chunk.nIndices; pNode->meshInfo.nSubsets = chunk.nSubsets; pNode->meshInfo.bboxMin = bboxMin; pNode->meshInfo.bboxMax = bboxMax; pNode->meshInfo.fGeometricMean = chunk.geometricMeanFaceArea; pNode->nPhysicalizeFlags = chunk.nFlags2; for (int nPhysGeomType = 0; nPhysGeomType < 4; nPhysGeomType++) { if (chunk.nPhysicsDataChunkId[nPhysGeomType] > 0) { LoadPhysicsDataChunk(pNode, nPhysGeomType, chunk.nPhysicsDataChunkId[nPhysGeomType]); } } if (chunk.nFlags & MESH_CHUNK_DESC::MESH_IS_EMPTY) { // This is an empty mesh. if (pNode->type == CNodeCGF::NODE_MESH) { // Do not create CMesh for it. m_pCGF->GetExportInfo()->bNoMesh = true; } return true; } std::unique_ptr pMesh(new CMesh()); CMesh& mesh = *(pMesh.get()); if (!m_bUseReadOnlyMesh) { mesh.SetVertexCount(chunk.nVerts); mesh.SetIndexCount(chunk.nIndices); if (chunk.GetStreamChunkID(CGF_STREAM_TEXCOORDS, 0) > 0) { mesh.ReallocStream(CMesh::TEXCOORDS, 0, chunk.nVerts); } } mesh.m_bbox = AABB(bboxMin, bboxMax); std::vector > globalBonesPerSubset; if (chunk.nSubsets > 0 && chunk.nSubsetsChunkId > 0) { IChunkFile::ChunkDesc* pSubsetChunkDesc = m_pChunkFile->FindChunkById(chunk.nSubsetsChunkId); if (!pSubsetChunkDesc || pSubsetChunkDesc->chunkType != ChunkType_MeshSubsets) { m_LastError.Format("MeshSubsets Chunk not found in CGF file %s", m_filename); return false; } if (!LoadMeshSubsetsChunk(mesh, pSubsetChunkDesc, globalBonesPerSubset)) { return false; } } ////////////////////////////////////////////////////////////////////////// // Read streams ////////////////////////////////////////////////////////////////////////// COMPILE_TIME_ASSERT(sizeof(Vec3f16) == 8); bool ok = true; // Read position stream. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_POSITIONS, 0, CMesh::POSITIONS, CMesh::POSITIONSF16); if (mesh.m_streamSize[CMesh::POSITIONSF16][0] > 0 && !m_bUseReadOnlyMesh) { const int count = mesh.m_streamSize[CMesh::POSITIONSF16][0]; mesh.ReallocStream(CMesh::POSITIONS, 0, count); void* pSrc = 0; int nSrcElementSize = 0; mesh.GetStreamInfo(CMesh::POSITIONSF16, 0, pSrc, nSrcElementSize); assert(pSrc); void* pDst = 0; int nDstElementSize = 0; mesh.GetStreamInfo(CMesh::POSITIONS, 0, pDst, nDstElementSize); if (pDst) { const Vec3f16* const pS = (const Vec3f16*)pSrc; Vec3* const pD = (Vec3*)pDst; for (int i = 0; i < count; ++i) { pD[i] = pS[i].ToVec3(); } mesh.ReallocStream(CMesh::POSITIONSF16, 0, 0); } } // Read normals stream. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_NORMALS, 0, CMesh::NORMALS); // Read Texture coordinates stream. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_TEXCOORDS, 0, CMesh::TEXCOORDS); if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0802::VERSION) { ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_TEXCOORDS, 1, CMesh::TEXCOORDS); } // Read indices stream. ok = ok && LoadIndexStreamChunk(mesh, chunk); // Read colors stream. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_COLORS, 0, CMesh::COLORS); // Read 2nd colors stream // For 0801 and prior compatable versions, a second color stream would have a stream type of CGF_STREAM_COLORS2, since only one stream per type was allowed if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0801::VERSION || pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0801::COMPATIBLE_OLD_VERSION) { ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_COLORS2, 0, CMesh::COLORS); } // For 0802 (which coincides with multiple-uv sets implementation) and beyond, a 2nd color stream would have a type of CGF_STREAM_COLORS with an index of 1 else { ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_COLORS, 1, CMesh::COLORS); } // Read Vertex Mapping. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_VERT_MATS, 0, CMesh::VERT_MATS); // Read Tangent Streams. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_TANGENTS, 0, CMesh::TANGENTS); ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_QTANGENTS, 0, CMesh::QTANGENTS); // Read interleaved stream. ok = ok && LoadStreamChunk(mesh, chunk, CGF_STREAM_P3S_C4B_T2S, 0, CMesh::P3S_C4B_T2S); ok = ok && LoadBoneMappingStreamChunk(mesh, chunk, globalBonesPerSubset); if (!ok) { return false; } if (chunk.nFlags & MESH_CHUNK_DESC::HAS_EXTRA_WEIGHTS) { // The memory being used by the extraWeight array has been allocated in the LoadBoneMappingStreamChunk. mesh.m_pExtraBoneMapping = &mesh.m_pBoneMapping[mesh.GetVertexCount()]; } if (chunk.nFlags & MESH_CHUNK_DESC::HAS_TEX_MAPPING_DENSITY) { mesh.m_texMappingDensity = chunk.texMappingDensity; } else { mesh.RecomputeTexMappingDensity(); } if (chunk.nFlags & MESH_CHUNK_DESC::HAS_FACE_AREA) { mesh.m_geometricMeanFaceArea = chunk.geometricMeanFaceArea; } else { //if the chunk does not have face area than try computing it mesh.RecomputeGeometricMeanFaceArea(); } if (mesh.m_geometricMeanFaceArea <= 0.0f) { Warning("Invalid geometric mean face area for node %s on file %s", pNode->name, this->m_filename); } pNode->pMesh = pMesh.release(); if (chunk.GetStreamChunkID(CGF_STREAM_SKINDATA, 0) > 0) { int nStreamType, nStreamIndex, nStreamCount, nElemSize; void* pStreamData; bool bSwapEndianness; if (!LoadStreamDataChunk(chunk.GetStreamChunkID(CGF_STREAM_SKINDATA, 0), pStreamData, nStreamType, nStreamIndex, nStreamCount, nElemSize, bSwapEndianness)) { return false; } SwapEndian((CrySkinVtx*)pStreamData, nStreamCount, bSwapEndianness); memcpy(pNode->pSkinInfo = new CrySkinVtx[nStreamCount], pStreamData, nStreamCount * nElemSize); } return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadMeshSubsetsChunk(CMesh& mesh, IChunkFile::ChunkDesc* pChunkDesc, std::vector >& globalBonesPerSubset) { LOADING_TIME_PROFILE_SECTION; globalBonesPerSubset.clear(); if (pChunkDesc->chunkType != ChunkType_MeshSubsets) { m_LastError.Format("Unknown type in mesh subset chunk"); return false; } if (pChunkDesc->chunkVersion != MESH_SUBSETS_CHUNK_DESC_0800::VERSION) { m_LastError.Format("Unknown version of mesh subset chunk"); return false; } const bool bSwapEndianness = pChunkDesc->bSwapEndian; pChunkDesc->bSwapEndian = false; uint8* pCurDataLoc = (uint8*)pChunkDesc->data; MESH_SUBSETS_CHUNK_DESC_0800& chunk = *StepData(pCurDataLoc, bSwapEndianness); const bool cbBoneIDs = (chunk.nFlags & MESH_SUBSETS_CHUNK_DESC_0800::BONEINDICES) != 0; const bool cbSubsetTexelDensities = (chunk.nFlags & MESH_SUBSETS_CHUNK_DESC_0800::HAS_SUBSET_TEXEL_DENSITY) != 0; for (int i = 0; i < chunk.nCount; i++) { MESH_SUBSETS_CHUNK_DESC_0800::MeshSubset& meshSubset = *StepData(pCurDataLoc, bSwapEndianness); SMeshSubset subset; subset.nFirstIndexId = meshSubset.nFirstIndexId; subset.nNumIndices = meshSubset.nNumIndices; subset.nFirstVertId = meshSubset.nFirstVertId; subset.nNumVerts = meshSubset.nNumVerts; subset.nMatID = meshSubset.nMatID; subset.fRadius = meshSubset.fRadius; subset.vCenter = meshSubset.vCenter; mesh.m_subsets.push_back(subset); } //------------------------------------------------------------------ if (cbBoneIDs) { globalBonesPerSubset.resize(chunk.nCount); for (int i = 0; i < chunk.nCount; i++) { MESH_SUBSETS_CHUNK_DESC_0800::MeshBoneIDs& meshSubset = *StepData(pCurDataLoc, bSwapEndianness); globalBonesPerSubset[i].resize(meshSubset.numBoneIDs); for (uint32 b = 0; b < meshSubset.numBoneIDs; ++b) { globalBonesPerSubset[i][b] = meshSubset.arrBoneIDs[b]; } } } if (cbSubsetTexelDensities) { for (int i = 0; i < chunk.nCount; i++) { MESH_SUBSETS_CHUNK_DESC_0800::MeshSubsetTexelDensity& meshSubset = *StepData(pCurDataLoc, bSwapEndianness); mesh.m_subsets[i].fTexelDensity = meshSubset.texelDensity; } } return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadStreamDataChunk(int nChunkId, void*& pStreamData, int& nStreamType, int& nStreamIndex, int& nCount, int& nElemSize, bool& bSwapEndianness) { IChunkFile::ChunkDesc* pChunkDesc = m_pChunkFile->FindChunkById(nChunkId); if (!pChunkDesc) { m_LastError.Format("Failed to find chunk with id %d", nChunkId); return false; } if (pChunkDesc->chunkType != ChunkType_DataStream) { m_LastError.Format("Unknown type of stream data chunk"); return false; } if (pChunkDesc->chunkVersion != STREAM_DATA_CHUNK_DESC_0800::VERSION && pChunkDesc->chunkVersion != STREAM_DATA_CHUNK_DESC_0801::VERSION) { m_LastError.Format("Unknown version of stream data chunk"); return false; } LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->chunkVersion == STREAM_DATA_CHUNK_DESC_0800::VERSION) { // Convert legacy .cgf file to the new version STREAM_DATA_CHUNK_DESC_0800& chunk = *(STREAM_DATA_CHUNK_DESC_0800*)pChunkDesc->data; bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(chunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; nStreamType = chunk.nStreamType; nStreamIndex = 0; // Since version 0800 didn't have a streamIndex, set the streamIndex to 0 nCount = chunk.nCount; nElemSize = chunk.nElementSize; pStreamData = (char*)pChunkDesc->data + sizeof(chunk); return true; } else { STREAM_DATA_CHUNK_DESC_0801& chunk = *(STREAM_DATA_CHUNK_DESC_0801*)pChunkDesc->data; bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(chunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; nStreamType = chunk.nStreamType; nStreamIndex = chunk.nStreamIndex; nCount = chunk.nCount; nElemSize = chunk.nElementSize; pStreamData = (char*)pChunkDesc->data + sizeof(chunk); return true; } } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadPhysicsDataChunk(CNodeCGF* pNode, int nPhysGeomType, int nChunkId) { IChunkFile::ChunkDesc* const pChunkDesc = m_pChunkFile->FindChunkById(nChunkId); if (!pChunkDesc) { return false; } if (pChunkDesc->chunkType != ChunkType_MeshPhysicsData) { return false; } if (pChunkDesc->chunkVersion != MESH_PHYSICS_DATA_CHUNK_DESC_0800::VERSION) { return false; } LOADING_TIME_PROFILE_SECTION; MESH_PHYSICS_DATA_CHUNK_DESC_0800& chunk = *(MESH_PHYSICS_DATA_CHUNK_DESC_0800*)pChunkDesc->data; SwapEndian(chunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; assert(nPhysGeomType >= 0 && nPhysGeomType < 4); pNode->physicalGeomData[nPhysGeomType].resize(chunk.nDataSize); void* const pDst = &(pNode->physicalGeomData[nPhysGeomType][0]); const void* const pSrc = (char*)pChunkDesc->data + sizeof(chunk); memcpy(pDst, pSrc, chunk.nDataSize); return true; } ////////////////////////////////////////////////////////////////////////// bool CLoaderCGF::LoadFoliageInfoChunk(IChunkFile::ChunkDesc* pChunkDesc) { if (pChunkDesc->chunkVersion != FOLIAGE_INFO_CHUNK_DESC::VERSION && pChunkDesc->chunkVersion != FOLIAGE_INFO_CHUNK_DESC::VERSION2) { m_LastError.Format("Unknown version of FoliageInfo chunk"); return false; } FOLIAGE_INFO_CHUNK_DESC& chunk = *(FOLIAGE_INFO_CHUNK_DESC*)pChunkDesc->data; const bool bSwapEndianness = pChunkDesc->bSwapEndian; SwapEndian(chunk, bSwapEndianness); pChunkDesc->bSwapEndian = false; SFoliageInfoCGF& fi = *m_pCGF->GetFoliageInfo(); bool isSkinned = m_pCGF->GetExportInfo()->bSkinnedCGF; if (fi.nSpines = chunk.nSpines) { fi.nSkinnedVtx = chunk.nSkinnedVtx; FOLIAGE_SPINE_SUB_CHUNK* const pSpineSrc = (FOLIAGE_SPINE_SUB_CHUNK*)(&chunk + 1); Vec3* const pSpineVtxSrc = (Vec3*)(&pSpineSrc[chunk.nSpines]); Vec4* const pSpineSegDimSrc = (Vec4*)(&pSpineVtxSrc[chunk.nSpineVtx]); //START: Per bone UDP for stiffness, damping and thickness for touch bending vegetation float* pStiffness = new float[chunk.nSpineVtx]; float* pDamping = new float[chunk.nSpineVtx]; float* pThickness = new float[chunk.nSpineVtx]; SMeshBoneMapping_uint8* pBoneMappingSrc = nullptr; if (pChunkDesc->chunkVersion == FOLIAGE_INFO_CHUNK_DESC::VERSION) { for (int i = 0; i < chunk.nSpineVtx; i++) { pStiffness[i] = SSpineRC::GetDefaultStiffness(); pDamping[i] = SSpineRC::GetDefaultDamping(); pThickness[i] = SSpineRC::GetDefaultThickness(); } pBoneMappingSrc = (SMeshBoneMapping_uint8*)&pSpineSegDimSrc[chunk.nSpineVtx]; } else { float* pStiffnessSrc = (float*)(&pSpineSegDimSrc[chunk.nSpineVtx]); float* pDampingSrc = (float*)(&pStiffnessSrc[chunk.nSpineVtx]); float* pThicknessSrc = (float*)(&pDampingSrc[chunk.nSpineVtx]); if (bSwapEndianness) { SwapEndian(pStiffnessSrc, chunk.nSpineVtx, true); SwapEndian(pDampingSrc, chunk.nSpineVtx, true); SwapEndian(pThicknessSrc, chunk.nSpineVtx, true); } memcpy(pStiffness, pStiffnessSrc, sizeof(pStiffness[0]) * chunk.nSpineVtx); memcpy(pDamping, pDampingSrc, sizeof(pStiffness[0]) * chunk.nSpineVtx); memcpy(pThickness, pThicknessSrc, sizeof(pStiffness[0]) * chunk.nSpineVtx); pBoneMappingSrc = (SMeshBoneMapping_uint8*)&pThicknessSrc[chunk.nSpineVtx]; } //Add LOD support for touch bending vegetation //Load bone mapping. Skinned CGF doesn't have chunkBoneIds because it doesn't need bone index remapping to mesh bone id. if (isSkinned && chunk.nBoneIds == 0) { const char* pStart = (const char*)pBoneMappingSrc; { int numBoneMapping = *pStart; pStart += sizeof(int); int vertexCount = 0; for (int i = 0; i < numBoneMapping; i++) { const char* pCGFNodeName = pStart; pStart += CGF_NODE_NAME_LENGTH; memcpy(&vertexCount, pStart, sizeof(int)); SMeshBoneMappingInfo_uint8* pBoneMappingEntry = new SMeshBoneMappingInfo_uint8(vertexCount); pStart += sizeof(int); memcpy(pBoneMappingEntry->pBoneMapping, pStart, sizeof(SMeshBoneMapping_uint8)*vertexCount); pStart += sizeof(SMeshBoneMapping_uint8)*vertexCount; if (bSwapEndianness) { SwapEndian(&pBoneMappingEntry->nVertexCount, 1, true); SwapEndian(pBoneMappingEntry->pBoneMapping, pBoneMappingEntry->nVertexCount, true); } fi.boneMappings[pCGFNodeName] = pBoneMappingEntry; } } } else { uint16* const pBoneIdsSrc = (uint16*)(&pBoneMappingSrc[chunk.nSkinnedVtx]); if (bSwapEndianness) { SwapEndian(pBoneMappingSrc, chunk.nSkinnedVtx, true); SwapEndian(pBoneIdsSrc, chunk.nBoneIds, true); } fi.pBoneMapping = new SMeshBoneMapping_uint8[chunk.nSkinnedVtx]; memcpy(fi.pBoneMapping, pBoneMappingSrc, sizeof(pBoneMappingSrc[0]) * chunk.nSkinnedVtx); fi.chunkBoneIds.resize(chunk.nBoneIds); memcpy(&fi.chunkBoneIds[0], pBoneIdsSrc, sizeof(fi.chunkBoneIds[0]) * chunk.nBoneIds); COMPILE_TIME_ASSERT(sizeof(fi.chunkBoneIds[0]) == sizeof(pBoneIdsSrc[0])); } if (bSwapEndianness) { SwapEndian(pSpineSrc, chunk.nSpines, true); SwapEndian(pSpineVtxSrc, chunk.nSpineVtx, true); SwapEndian(pSpineSegDimSrc, chunk.nSpineVtx, true); } Vec3* const pSpineVtx = new Vec3[chunk.nSpineVtx]; Vec4* const pSpineSegDim = new Vec4[chunk.nSpineVtx]; memcpy(pSpineVtx, pSpineVtxSrc, sizeof(pSpineVtx[0]) * chunk.nSpineVtx); memcpy(pSpineSegDim, pSpineSegDimSrc, sizeof(pSpineSegDim[0]) * chunk.nSpineVtx); fi.pSpines = new SSpineRC[chunk.nSpines]; int i, j; for (i = j = 0; i < chunk.nSpines; j += fi.pSpines[i++].nVtx) { fi.pSpines[i].nVtx = pSpineSrc[i].nVtx; fi.pSpines[i].len = pSpineSrc[i].len; fi.pSpines[i].navg = pSpineSrc[i].navg; fi.pSpines[i].iAttachSpine = pSpineSrc[i].iAttachSpine - 1; fi.pSpines[i].iAttachSeg = pSpineSrc[i].iAttachSeg - 1; fi.pSpines[i].pVtx = pSpineVtx + j; fi.pSpines[i].pSegDim = pSpineSegDim + j; // Per bone data for stiffness, damping and thickness for touch bending vegetation fi.pSpines[i].pStiffness = pStiffness + j; fi.pSpines[i].pDamping = pDamping + j; fi.pSpines[i].pThickness = pThickness + j; } } return true; } ////////////////////////////////////////////////////////////////////////// CMaterialCGF* CLoaderCGF::LoadMaterialFromChunk(int nChunkId) { for (int i = 0; i < m_pCGF->GetMaterialCount(); i++) { if (m_pCGF->GetMaterial(i)->nChunkId == nChunkId) { return m_pCGF->GetMaterial(i); } } IChunkFile::ChunkDesc* const pChunkDesc = m_pChunkFile->FindChunkById(nChunkId); if (!pChunkDesc) { m_LastError.Format("Can't find material chunk with id %d in file %s", nChunkId, m_filename); return 0; } LOADING_TIME_PROFILE_SECTION; if (pChunkDesc->chunkType != ChunkType_MtlName) { m_LastError.Format( "Invalid chunk type (0x%08x instead of expected material chunk type 0x%08x) in chunk %d in file %s", (int)pChunkDesc->chunkType, (int)ChunkType_MtlName, nChunkId, m_filename); return 0; } return LoadMaterialNameChunk(pChunkDesc); } ////////////////////////////////////////////////////////////////////////// static const char* GetNextAsciizString(const char*& pCurrent, const char* const pEnd) { const char* const result = pCurrent; while (pCurrent < pEnd && *pCurrent >= ' ') { ++pCurrent; } if (pCurrent >= pEnd || *pCurrent != 0) { // If we found that the data are damaged (*pCurrent != 0) then it's // better to stop using the data. We move pCurrent to the end // for that - it guarantees that all future calls return "". pCurrent = pEnd; return ""; } ++pCurrent; return result; } ////////////////////////////////////////////////////////////////////////// CMaterialCGF* CLoaderCGF::LoadMaterialNameChunk(IChunkFile::ChunkDesc* pChunkDesc) { FUNCTION_PROFILER_3DENGINE; if (pChunkDesc->chunkVersion == MTL_NAME_CHUNK_DESC_0802::VERSION) { MTL_NAME_CHUNK_DESC_0802 chunk; const bool bSwapEndianness = pChunkDesc->bSwapEndian; memcpy(&chunk, pChunkDesc->data, sizeof(chunk)); SwapEndian(chunk, bSwapEndianness); CMaterialCGF* pMtlCGF = Construct(InplaceFactory(m_pDestructFnc), m_pAllocFnc); pMtlCGF->nChunkId = pChunkDesc->chunkId; m_pCGF->AddMaterial(pMtlCGF); for (size_t i = 0; i < sizeof(chunk.name); ++i) { if (chunk.name[i] == '\\') { chunk.name[i] = '/'; } } cry_strcpy(pMtlCGF->name, chunk.name); const int32 slotCount = (chunk.nSubMaterials <= 0) ? 1 : chunk.nSubMaterials; const int nPhysicalizeTypeMaxCount = (pChunkDesc->size - sizeof(MTL_NAME_CHUNK_DESC_0802)) / sizeof(int32); if (slotCount > nPhysicalizeTypeMaxCount) { m_LastError.Format("Corrupted MTL_NAME_CHUNK_DESC_0802 chunk"); return NULL; } const int32* const pPhysicalizeTypes = (const int32*)((const char*)pChunkDesc->data + sizeof(MTL_NAME_CHUNK_DESC_0802)); if (chunk.nSubMaterials <= 0) { int nPhysicalizeType = pPhysicalizeTypes[0]; SwapEndian(nPhysicalizeType, bSwapEndianness); pMtlCGF->nPhysicalizeType = nPhysicalizeType; } else if (chunk.nSubMaterials <= MAX_SUB_MATERIALS) { const char* pNames = (const char*)&pPhysicalizeTypes[slotCount]; const char* const pNamesEnd = (const char*)pChunkDesc->data + pChunkDesc->size; for (int i = 0; i < chunk.nSubMaterials; ++i) { CMaterialCGF* const pSubMaterial = new CMaterialCGF; cry_strcpy(pSubMaterial->name, GetNextAsciizString(pNames, pNamesEnd)); int nPhysicalizeType = pPhysicalizeTypes[i]; SwapEndian(nPhysicalizeType, bSwapEndianness); if (nPhysicalizeType != PHYS_GEOM_TYPE_NONE && (nPhysicalizeType < PHYS_GEOM_TYPE_DEFAULT || nPhysicalizeType > PHYS_GEOM_TYPE_DEFAULT_PROXY)) { m_LastError.Format("Invalid physicalize type in material name chunk (0x%08x) in %s, %s", nPhysicalizeType, pMtlCGF->name, m_filename); return NULL; } pSubMaterial->nPhysicalizeType = nPhysicalizeType; pMtlCGF->subMaterials.push_back(pSubMaterial); m_pCGF->AddMaterial(pSubMaterial); } } else { m_LastError.Format("Material name chunk: too many submaterials (0x%08x) in %s, %s", chunk.nSubMaterials, pMtlCGF->name, m_filename); return NULL; } return pMtlCGF; } if (pChunkDesc->chunkVersion == MTL_NAME_CHUNK_DESC_0800::VERSION) { if (pChunkDesc->size > sizeof(MTL_NAME_CHUNK_DESC_0800)) { m_LastError.Format("Illegal material name chunk size %s (%d should be %d)", m_filename, (int)pChunkDesc->size, (int)sizeof(MTL_NAME_CHUNK_DESC_0800)); return NULL; } MTL_NAME_CHUNK_DESC_0800& chunk = *(MTL_NAME_CHUNK_DESC_0800*)pChunkDesc->data; SwapEndian(chunk, pChunkDesc->bSwapEndian); pChunkDesc->bSwapEndian = false; for (size_t i = 0; i < sizeof(chunk.name); ++i) { if (chunk.name[i] == '\\') { chunk.name[i] = '/'; } } CMaterialCGF* pMtlCGF = Construct(InplaceFactory(m_pDestructFnc), m_pAllocFnc); pMtlCGF->nChunkId = pChunkDesc->chunkId; m_pCGF->AddMaterial(pMtlCGF); cry_strcpy(pMtlCGF->name, chunk.name); // hack for old broken assets if (size_t(chunk.nSubMaterials) > 0xffff || chunk.nPhysicalizeType > 0xffff) { Warning("Fixing material name chunk with wrong endianness: %s, %s", pMtlCGF->name, m_filename); Warning(" nSubMaterials=0x%08x, nPhysicalizeType=0x%08x, nFlags=0x%08x", int(chunk.nSubMaterials), int(chunk.nPhysicalizeType), int(chunk.nFlags)); SwapEndian(chunk, true); } pMtlCGF->nPhysicalizeType = chunk.nPhysicalizeType; if ((unsigned int)pMtlCGF->nPhysicalizeType <= (PHYS_GEOM_TYPE_DEFAULT_PROXY - PHYS_GEOM_TYPE_DEFAULT)) { pMtlCGF->nPhysicalizeType += PHYS_GEOM_TYPE_DEFAULT; // fixup if was exported with PHYS_GEOM_TYPE_DEFAULT==0 } if (pMtlCGF->nPhysicalizeType != PHYS_GEOM_TYPE_NONE && (pMtlCGF->nPhysicalizeType < PHYS_GEOM_TYPE_DEFAULT || pMtlCGF->nPhysicalizeType > PHYS_GEOM_TYPE_DEFAULT_PROXY)) { m_LastError.Format("Invalid physicalize type in material name chunk (0x%08x) in %s, %s", pMtlCGF->nPhysicalizeType, pMtlCGF->name, m_filename); return NULL; } if (size_t(chunk.nSubMaterials) <= MTL_NAME_CHUNK_DESC_0800_MAX_SUB_MATERIALS) { pMtlCGF->subMaterials.resize(chunk.nSubMaterials, NULL); for (int i = 0; i < chunk.nSubMaterials; i++) { if (chunk.nSubMatChunkId[i] > 0) { CMaterialCGF* const pMtl = LoadMaterialFromChunk(chunk.nSubMatChunkId[i]); if (!pMtl) { return NULL; } if (!pMtl->subMaterials.empty()) { m_LastError.Format("Multi-material used as sub-material from file %s", m_filename); return NULL; } pMtlCGF->subMaterials[i] = pMtl; } } } else { m_LastError.Format("Material name chunk: too many submaterials (0x%08x) in %s, %s", chunk.nSubMaterials, pMtlCGF->name, m_filename); return NULL; } return pMtlCGF; } m_LastError.Format("Illegal material name chunk %s", m_filename); return NULL; } ////////////////////////////////////////////////////////////////////////// void CLoaderCGF::Warning(const char* szFormat, ...) { if (m_pListener) { char szBuffer[1024]; va_list args; va_start(args, szFormat); azvsprintf(szBuffer, szFormat, args); m_pListener->Warning(szBuffer); va_end(args); } }