You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/Cry3DEngine/CGF/CGFLoader.cpp

4336 lines
150 KiB
C++

/*
* 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 <platform.h>
#include "CGFLoader.h"
#include <StringUtils.h>
#include <InplaceFactory.h>
#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<T, Expr> function below. Returns a typed
// pointer to the inplace constructed object.
template<typename T, typename InPlaceFactory>
T* Construct(
const InPlaceFactory& factory,
void* (*pAllocFnc)(size_t))
{
return reinterpret_cast<T*>(factory.template apply<T>(pAllocFnc(sizeof(T))));
}
// Templated construct helper function using an inplace factory
//
// Called from the templated New<T, Expr> function below. Returns a typed
// pointer to the inplace constructed object.
template<typename T>
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<typename T>
void Destruct(T* obj, void(*pDestructFnc)(void*))
{
obj->~T();
pDestructFnc(reinterpret_cast<void*>(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<Vec3>& outArrPositions,
DynArray<char>& outArrMaterials,
DynArray<uint16>& outArrIndices,
const int inVertexCount,
const CryVertex* const inVertices,
const int inFaceCount,
const CryFace* const inFaces)
{
outArrPositions.clear();
outArrMaterials.clear();
outArrIndices.clear();
DynArray<BoneVertex> 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<SMeshSubset>& arrSubsets,
std::vector<TFace>& 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<RBatch> 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(mat<testcheck);
uint32 r = 0;
bool found = false;
const uint32 numSubsets2 = pMesh->m_subsets.size();
for (r = 0; r < numSubsets2; 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; i<numIntVertices; ++i)
{
pMesh->m_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<uint8, std::vector<TFace> > 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<int> arrVRemapping;
std::vector<int> 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<SMeshSubset> arrSubsets;
std::vector<TFace> 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<int>* pVertexRemapping, std::vector<int>* 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<CNodeCGF>(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<int>& 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<SMeshTexCoord>(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<int>& 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<class T, class MESH_CHUNK_DESC>
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<class TA, class TB, class MESH_CHUNK_DESC>
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 <class MESH_CHUNK_DESC>
bool CLoaderCGF::LoadBoneMappingStreamChunk(CMesh& mesh, const MESH_CHUNK_DESC& chunk, const std::vector<std::vector<uint16> >& 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<SMeshBoneMapping_uint16>(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<uint16>& 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<SMeshBoneMapping_uint16>(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<SMeshBoneMapping_uint16>(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 <class MESH_CHUNK_DESC>
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<class MESH_CHUNK_DESC>
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<CMesh> 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<std::vector<uint16> > 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<Vec3, Vec3f16>(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<Vec3>(mesh, chunk, CGF_STREAM_NORMALS, 0, CMesh::NORMALS);
// Read Texture coordinates stream.
ok = ok && LoadStreamChunk<SMeshTexCoord>(mesh, chunk, CGF_STREAM_TEXCOORDS, 0, CMesh::TEXCOORDS);
if (pChunkDesc->chunkVersion == MESH_CHUNK_DESC_0802::VERSION)
{
ok = ok && LoadStreamChunk<SMeshTexCoord>(mesh, chunk, CGF_STREAM_TEXCOORDS, 1, CMesh::TEXCOORDS);
}
// Read indices stream.
ok = ok && LoadIndexStreamChunk(mesh, chunk);
// Read colors stream.
ok = ok && LoadStreamChunk<SMeshColor>(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<SMeshColor>(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<SMeshColor>(mesh, chunk, CGF_STREAM_COLORS, 1, CMesh::COLORS);
}
// Read Vertex Mapping.
ok = ok && LoadStreamChunk<int>(mesh, chunk, CGF_STREAM_VERT_MATS, 0, CMesh::VERT_MATS);
// Read Tangent Streams.
ok = ok && LoadStreamChunk<SMeshTangents>(mesh, chunk, CGF_STREAM_TANGENTS, 0, CMesh::TANGENTS);
ok = ok && LoadStreamChunk<SMeshQTangents>(mesh, chunk, CGF_STREAM_QTANGENTS, 0, CMesh::QTANGENTS);
// Read interleaved stream.
ok = ok && LoadStreamChunk<SVF_P3S_C4B_T2S>(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<std::vector<uint16> >& 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<MESH_SUBSETS_CHUNK_DESC_0800>(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<MESH_SUBSETS_CHUNK_DESC_0800::MeshSubset>(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<MESH_SUBSETS_CHUNK_DESC_0800::MeshBoneIDs>(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<MESH_SUBSETS_CHUNK_DESC_0800::MeshSubsetTexelDensity>(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<CMaterialCGF>(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<CMaterialCGF>(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);
}
}