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.
4336 lines
150 KiB
C++
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);
|
|
}
|
|
}
|