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

1590 lines
51 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 "ResourceCompilerPC_precompiled.h"
#include "StaticObjectCompiler.h"
#include "../../CryEngine/Cry3DEngine/MeshCompiler/MeshCompiler.h"
#include "CGF/CGFNodeMerger.h"
#include "StringHelpers.h"
#include "Util.h"
#include "PropertyHelpers.h"
#include "AzFramework/StringFunc/StringFunc.h"
#include "CGFContent.h"
static const char* const s_consolesLod0MarkerStr = "consoles_lod0";
static const uint8 s_maxSkinningWeight = 0xFF;
static bool NodeHasProperty(const CNodeCGF* pNode, const char* property)
{
return strstr(StringHelpers::MakeLowerCase(pNode->properties).c_str(), property) != 0;
}
static bool HasNodeWithConsolesLod0(const CContentCGF* pCGF)
{
const string lodNamePrefix(CGF_NODE_NAME_LOD_PREFIX);
const int nodeCount = pCGF->GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
if (!pNode)
{
continue;
}
if (!StringHelpers::StartsWithIgnoreCase(string(pNode->name), lodNamePrefix))
{
continue;
}
if (NodeHasProperty(pNode, s_consolesLod0MarkerStr))
{
return true;
}
}
return false;
}
static CNodeCGF* FindNodeByName(CContentCGF* pCGF, const char* name)
{
if (name == 0)
{
assert(name);
return 0;
}
const int nodeCount = pCGF->GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode && strcmp(pNode->name, name) == 0)
{
return pNode;
}
}
return 0;
}
static void ReportDuplicatedMeshNodeNames(const CContentCGF* pCGF)
{
assert(pCGF);
const string ignoreNamePrefix("$");
std::set<const char*, stl::less_stricmp<const char*> > names;
const int nodeCount = pCGF->GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode == 0)
{
assert(pNode);
continue;
}
const char* const pName = &pNode->name[0];
if (pName[0] == 0)
{
RCLogWarning("Node with empty name found in %s", pCGF->GetFilename());
continue;
}
if (StringHelpers::StartsWithIgnoreCase(string(pName), ignoreNamePrefix))
{
continue;
}
if (pNode->pMesh == 0)
{
continue;
}
if (names.find(pName) == names.end())
{
names.insert(pName);
}
else
{
RCLogWarning(
"Duplicated mesh node name %s found in %s. Please make sure that all mesh nodes have unique names.",
pName, pCGF->GetFilename());
}
}
}
//////////////////////////////////////////////////////////////////////////
CStaticObjectCompiler::CStaticObjectCompiler(bool bConsole, int logVerbosityLevel)
: m_bConsole(bConsole)
, m_logVerbosityLevel(logVerbosityLevel)
, m_bOptimizePVRStripify(false)
{
m_bSplitLODs = false;
m_bOwnLod0 = false;
for (int i = 0; i < MAX_LOD_COUNT; ++i)
{
m_pLODs[i] = 0;
}
}
//////////////////////////////////////////////////////////////////////////
CStaticObjectCompiler::~CStaticObjectCompiler()
{
for (int i = 0; i < MAX_LOD_COUNT; ++i)
{
if (i != 0 || m_bOwnLod0)
{
delete m_pLODs[i];
}
}
}
//////////////////////////////////////////////////////////////////////////
void CStaticObjectCompiler::SetSplitLods(bool bSplit)
{
m_bSplitLODs = bSplit;
}
//////////////////////////////////////////////////////////////////////////
// Confetti: Nicholas Baldwin
void CStaticObjectCompiler::SetOptimizeStripify(bool bStripify)
{
m_bOptimizePVRStripify = bStripify;
}
//////////////////////////////////////////////////////////////////////////
CContentCGF* CStaticObjectCompiler::MakeCompiledCGF(CContentCGF* pCGF, bool const forceRecompile)
{
if (pCGF->GetExportInfo()->bCompiledCGF)
{
const bool ok = ProcessCompiledCGF(pCGF);
if (!ok)
{
return 0;
}
if (forceRecompile)
{
// most likely combined with "OptimizedPrimitiveType=1"
// otherwise CompileMeshes() will just bail out since the CGF was already compiled.
if (!CompileMeshes(pCGF))
{
return 0;
}
}
return pCGF;
}
if (m_logVerbosityLevel > 2)
{
RCLog("Compiling CGF");
}
m_bOwnLod0 = true;
MakeLOD(0, pCGF);
CContentCGF* const pCompiledCGF = m_pLODs[0];
// Setup mesh subsets for the original CGF.
for (int i = 0, num = pCGF->GetNodeCount(); i < num; ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode->pMesh)
{
string errorMessage;
if (!CGFNodeMerger::SetupMeshSubsets(pCGF, *pNode->pMesh, pNode->pMaterial, errorMessage))
{
RCLogError("%s: %s", __FUNCTION__, errorMessage.c_str());
return 0;
}
}
}
if (pCGF->GetExportInfo()->bMergeAllNodes)
{
if (m_logVerbosityLevel > 2)
{
RCLog("Merging nodes");
}
if (!MakeMergedCGF(pCompiledCGF, pCGF))
{
return 0;
}
}
else
{
for (int i = 0, num = pCGF->GetNodeCount(); i < num; ++i)
{
CNodeCGF* const pNodeCGF = pCGF->GetNode(i);
pCompiledCGF->AddNode(pNodeCGF);
}
}
// Compile meshes in all nodes.
{
if (m_logVerbosityLevel > 2)
{
RCLog("Compiling meshes");
}
if (!CompileMeshes(pCompiledCGF))
{
return 0;
}
}
// Try to find shared meshes.
{
if (m_logVerbosityLevel > 2)
{
RCLog("Searching for shared meshes");
}
AnalyzeSharedMeshes(pCompiledCGF);
}
{
if (m_logVerbosityLevel > 2)
{
RCLog("Physicalizing");
}
const bool ok = Physicalize(pCompiledCGF, pCGF);
if (!ok)
{
return 0;
}
if (m_logVerbosityLevel > 2)
{
RCLog("Compiling deformable physics data");
}
CompileDeformablePhysData(pCompiledCGF);
}
if (!ValidateBoundingBoxes(pCompiledCGF))
{
return 0;
}
// Try to split LODs
if (m_bSplitLODs)
{
if (m_logVerbosityLevel > 2)
{
RCLog("Splitting to LODs");
}
const bool ok = SplitLODs(pCompiledCGF);
if (!ok)
{
return 0;
}
}
{
if (m_logVerbosityLevel > 2)
{
RCLog("Validating breakable joints");
}
ValidateBreakableJoints(pCGF);
}
return pCompiledCGF;
}
//////////////////////////////////////////////////////////////////////////
void CStaticObjectCompiler::ValidateBreakableJoints(const CContentCGF* pCGF)
{
// Warn in case we have too many sub-meshes.
const int subMeshCount = GetSubMeshCount(m_pLODs[0]);
const int jointCount = GetJointCount(pCGF);
enum
{
kBreakableSubMeshLimit = 64
};
if (jointCount > 0 && subMeshCount > kBreakableSubMeshLimit)
{
RCLogError("Breakable CGF contains %d sub-meshes (%d is the maximum): %s",
subMeshCount, (int)kBreakableSubMeshLimit, pCGF->GetFilename());
}
}
//////////////////////////////////////////////////////////////////////////
void CStaticObjectCompiler::CompileDeformablePhysData(CContentCGF* pCompiledCGF)
{
if (!pCompiledCGF->GetExportInfo()->bSkinnedCGF)
{
for (int i = 0; i < pCompiledCGF->GetNodeCount(); ++i)
{
if (pCompiledCGF->GetNode(i)->type == CNodeCGF::NODE_MESH)
{
break;
}
}
}
const string skeletonPrefix = "skeleton_";
for (int i = 0; i < pCompiledCGF->GetNodeCount(); ++i)
{
CNodeCGF* pSkeletonNode = pCompiledCGF->GetNode(i);
if (StringHelpers::StartsWithIgnoreCase(pSkeletonNode->name, skeletonPrefix))
{
const char* meshName = pSkeletonNode->name + skeletonPrefix.size();
CNodeCGF* pMeshNode = FindNodeByName(pCompiledCGF, meshName);
bool bKeepSkeleton = true; // always keep for PC
if (!pMeshNode)
{
RCLogError("Unable to find mesh node \"%s\" for \"%s\"", meshName, pSkeletonNode->name);
continue;
}
if (pMeshNode->pMesh == 0)
{
RCLogError("Node %s: Has corresponding skeleton, but no mesh. Disabling deformation.", pMeshNode->name);
bKeepSkeleton = false;
}
if (m_bConsole)
{
if (NodeHasProperty(pSkeletonNode, "consoles_deformable")) // enabled for consoles only by UDP
{
if (HasNodeWithConsolesLod0(pCompiledCGF))
{
RCLogWarning("Node %s: %s and consoles_deformable may not be used together. Disabling object deformation.",
pMeshNode->name, s_consolesLod0MarkerStr);
bKeepSkeleton = false;
}
}
else
{
bKeepSkeleton = false;
}
}
if (bKeepSkeleton)
{
float r = 0.0f;
if (const char* ptr = strstr(pSkeletonNode->properties, "skin_dist"))
{
for (; *ptr && (*ptr<'0' || * ptr>'9'); ptr++)
{
;
}
if (*ptr)
{
r = (float)atof(ptr);
}
}
PrepareSkinData(pMeshNode, pMeshNode->localTM.GetInverted() * pSkeletonNode->localTM, pSkeletonNode, r, false);
}
else
{
SAFE_DELETE_ARRAY(pMeshNode->pSkinInfo);
pCompiledCGF->RemoveNode(pSkeletonNode);
--i;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CStaticObjectCompiler::CompileMeshes(CContentCGF* pCGF)
{
// Compile Meshes in all nodes.
for (int i = 0; i < pCGF->GetNodeCount(); i++)
{
CNodeCGF* pNodeCGF = pCGF->GetNode(i);
if (pNodeCGF->pMesh)
{
if (m_logVerbosityLevel > 2)
{
RCLog("Compiling geometry in node '%s'", pNodeCGF->name);
}
mesh_compiler::CMeshCompiler meshCompiler;
const bool DegenerateFacesAreErrors = false; // We need to get this from a CVar properly in order to make this an option, but for now,
// always treat this as a warning
int nMeshCompileFlags = mesh_compiler::MESH_COMPILE_TANGENTS
| ((DegenerateFacesAreErrors) ? mesh_compiler::MESH_COMPILE_VALIDATE_FAIL_ON_DEGENERATE_FACES : 0)
| mesh_compiler::MESH_COMPILE_VALIDATE;
if (pCGF->GetExportInfo()->bUseCustomNormals)
{
nMeshCompileFlags |= mesh_compiler::MESH_COMPILE_USECUSTOMNORMALS;
}
if (!pNodeCGF->bPhysicsProxy)
{
// Confetti: Nicholas Baldwin
nMeshCompileFlags |= (m_bOptimizePVRStripify) ? mesh_compiler::MESH_COMPILE_PVR_STRIPIFY : mesh_compiler::MESH_COMPILE_OPTIMIZE;
}
if (!meshCompiler.Compile(*pNodeCGF->pMesh, nMeshCompileFlags))
{
RCLogError("Failed to compile geometry in node '%s' in file %s - %s", pNodeCGF->name, pCGF->GetFilename(), meshCompiler.GetLastError());
return false;
}
//if we dont pass in MESH_COMPILE_VALIDATE_FAIL_ON_DEGENERATE_FACES during compilation
//it will not check for degenerate faces and fail, but we still want to warn on degenerate faces
if (!(nMeshCompileFlags & mesh_compiler::MESH_COMPILE_VALIDATE_FAIL_ON_DEGENERATE_FACES))
{
if (meshCompiler.CheckForDegenerateFaces(*pNodeCGF->pMesh))
{
RCLogWarning("Geometry in node '%s' in file %s contains degenerate faces. This mesh is sub optimal and should be fixed!", pNodeCGF->name, pCGF->GetFilename());
}
}
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CStaticObjectCompiler::AnalyzeSharedMeshes(CContentCGF* pCGF)
{
// Check if any duplicate meshes exist, and try to share them.
mesh_compiler::CMeshCompiler meshCompiler;
uint32 numNodes = pCGF->GetNodeCount();
for (int i = 0; i < numNodes - 1; i++)
{
CNodeCGF* pNode1 = pCGF->GetNode(i);
if (!pNode1->pMesh || pNode1->pSharedMesh)
{
continue;
}
if (!(pNode1->pMesh->GetVertexCount() && pNode1->pMesh->GetFaceCount()))
{
continue;
}
if (pNode1->bPhysicsProxy)
{
continue;
}
for (int j = i + 1; j < numNodes; j++)
{
CNodeCGF* pNode2 = pCGF->GetNode(j);
if (pNode1 == pNode2 || !pNode2->pMesh)
{
continue;
}
if (pNode2->bPhysicsProxy)
{
continue;
}
if (pNode1->properties == pNode2->properties)
{
if (pNode1->pMesh != pNode2->pMesh && meshCompiler.CompareMeshes(*pNode1->pMesh, *pNode2->pMesh))
{
// Meshes are same, share them.
delete pNode2->pMesh;
pNode2->pMesh = pNode1->pMesh;
pNode2->pSharedMesh = pNode1;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CStaticObjectCompiler::ValidateBoundingBoxes(CContentCGF* pCGF)
{
static const float maxValidObjectRadius = 10000000000.0f;
bool ok = true;
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode->type == CNodeCGF::NODE_MESH && pNode->pMesh && pNode->pMesh->GetVertexCount() == 0 && pNode->pMesh->GetIndexCount() == 0)
{
const float radius = pNode->pMesh->m_bbox.GetRadius();
if (radius <= 0 || radius > maxValidObjectRadius || !_finite(radius))
{
RCLogWarning(
"Node '%s' in file %s has an invalid bounding box, the engine will fail to load this object. Check that the node has valid geometry and is not empty.",
pNode->name, pCGF->GetFilename());
ok = false;
}
}
}
return ok;
}
//////////////////////////////////////////////////////////////////////////
bool CStaticObjectCompiler::Physicalize(CContentCGF* pCompiledCGF, CContentCGF* pSrcCGF)
{
AZ_UNUSED(pCompiledCGF);
AZ_UNUSED(pSrcCGF);
return true;
}
//////////////////////////////////////////////////////////////////////////
inline int check_mask(unsigned int* pMask, int i)
{
return pMask[i >> 5] >> (i & 31) & 1;
}
inline void set_mask(unsigned int* pMask, int i)
{
pMask[i >> 5] |= 1u << (i & 31);
}
inline void clear_mask(unsigned int* pMask, int i)
{
pMask[i >> 5] &= ~(1u << (i & 31));
}
int UpdatePtTriDist(const Vec3* vtx, const Vec3& n, const Vec3& pt, float& minDist, float& minDenom)
{
float rvtx[3] = { (vtx[0] - pt).len2(), (vtx[1] - pt).len2(), (vtx[2] - pt).len2() }, elen2[2], dist, denom;
int i = idxmin3(rvtx), bInside[2];
Vec3 edge[2], dp = pt - vtx[i];
edge[0] = vtx[incm3(i)] - vtx[i];
elen2[0] = edge[0].len2();
edge[1] = vtx[decm3(i)] - vtx[i];
elen2[1] = edge[1].len2();
bInside[0] = isneg((dp ^ edge[0]) * n);
bInside[1] = isneg((edge[1] ^ dp) * n);
rvtx[i] = rvtx[i] * elen2[bInside[0]] - sqr(Util::getMax(0.0f, dp * edge[bInside[0]])) * (bInside[0] | bInside[1]);
denom = elen2[bInside[0]];
if (bInside[0] & bInside[1])
{
if (edge[0] * edge[1] < 0)
{
edge[0] = vtx[decm3(i)] - vtx[incm3(i)];
dp = pt - vtx[incm3(i)];
if ((dp ^ edge[0]) * n > 0)
{
dist = rvtx[incm3(i)] * edge[0].len2() - sqr(dp * edge[0]);
denom = edge[0].len2();
goto found;
}
}
dist = sqr((pt - vtx[0]) * n), denom = n.len2();
}
else
{
dist = rvtx[i];
}
found:
if (dist * minDenom < minDist * denom)
{
minDist = dist;
minDenom = denom;
return 1;
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
void CStaticObjectCompiler::PrepareSkinData([[maybe_unused]] CNodeCGF* pNode, [[maybe_unused]] const Matrix34& mtxSkelToMesh, [[maybe_unused]] CNodeCGF* pNodeSkel, [[maybe_unused]] float r, [[maybe_unused]] bool bSwapEndian)
{
assert(pNode->pMesh->m_pPositionsF16 == 0);
}
//////////////////////////////////////////////////////////////////////////
bool CStaticObjectCompiler::ProcessCompiledCGF(CContentCGF* pCGF)
{
assert(pCGF->GetExportInfo()->bCompiledCGF);
// CGF is already compiled, so we just need to perform some validation and re-compiling steps.
ReportDuplicatedMeshNodeNames(pCGF);
m_pLODs[0] = pCGF;
m_bOwnLod0 = false;
CompileDeformablePhysData(pCGF);
// Try to split LODs
if (m_bSplitLODs)
{
const bool ok = SplitLODs(pCGF);
if (!ok)
{
return false;
}
}
ValidateBreakableJoints(pCGF);
return true;
}
//////////////////////////////////////////////////////////////////////////
namespace LodHelpers
{
static bool NodeHasChildren(
const CContentCGF* const pCGF,
const CNodeCGF* const pNode)
{
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
const CNodeCGF* const p = pCGF->GetNode(i);
if (p->pParent == pNode)
{
return true;
}
}
return false;
}
static int GetLodIndex(const char* const pName, const string& lodNamePrefix)
{
if (!StringHelpers::StartsWithIgnoreCase(string(pName), lodNamePrefix))
{
return -1;
}
int value = 0;
const char* p = pName + lodNamePrefix.length();
while ((p[0] >= '0') && (p[0] <= '9'))
{
value = value * 10 + (p[0] - '0');
++p;
}
return value;
}
static bool ValidateLodNodes(CContentCGF* const pCGF, const string& lodNamePrefix)
{
static const char* const howToFix = " Please modify and re-export source asset file.";
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
const string nodeName = pNode->name;
if (!StringHelpers::StartsWithIgnoreCase(nodeName, lodNamePrefix))
{
continue;
}
if (nodeName.length() == lodNamePrefix.length())
{
RCLogError(
"LOD node '%s' has no index. Valid name format is '%sNxxx', where N is LOD index 1-%i and xxx is any text. File: %s.%s",
nodeName.c_str(), lodNamePrefix.c_str(), (int)CStaticObjectCompiler::MAX_LOD_COUNT - 1, pCGF->GetFilename(), howToFix);
return false;
}
const int lodIndex = GetLodIndex(nodeName.c_str(), lodNamePrefix);
if ((lodIndex <= 0) || (lodIndex >= CStaticObjectCompiler::MAX_LOD_COUNT))
{
RCLogError(
"LOD node '%s' has bad or missing LOD index. Valid LOD name format is '%sNxxx', where N is LOD index 1-%i and xxx is any text. File: %s.%s",
nodeName.c_str(), lodNamePrefix.c_str(), (int)CStaticObjectCompiler::MAX_LOD_COUNT - 1, pCGF->GetFilename(), howToFix);
return false;
}
if (pNode->pParent == NULL)
{
RCLogError(
"LOD node '%s' has no parent node. File: %s.%s",
nodeName.c_str(), pCGF->GetFilename(), howToFix);
return false;
}
if ((pNode->pParent->type != CNodeCGF::NODE_MESH) && (pNode->pParent->type != CNodeCGF::NODE_HELPER))
{
RCLogError(
"LOD0 node '%s' (parent node of LOD node '%s') is neither MESH nor HELPER. File: %s.%s",
pNode->pParent->name, nodeName.c_str(), pCGF->GetFilename(), howToFix);
return false;
}
if (pNode->pParent->pMesh == 0)
{
RCLogError(
"LOD0 node '%s' (parent node of LOD node '%s') has no mesh data. File: %s.%s",
pNode->pParent->name, nodeName.c_str(), pCGF->GetFilename(), howToFix);
return false;
}
if ((pNode->type != CNodeCGF::NODE_MESH) && (pNode->type != CNodeCGF::NODE_HELPER))
{
RCLogError(
"LOD node '%s' is neither MESH nor HELPER. File %s.%s",
nodeName.c_str(), pCGF->GetFilename(), howToFix);
return false;
}
if (pNode->pMesh == 0)
{
RCLogError(
"LOD node '%s' has no mesh data. File: %s.%s",
nodeName.c_str(), pCGF->GetFilename(), howToFix);
return false;
}
if (NodeHasChildren(pCGF, pNode))
{
RCLogError(
"LOD node '%s' has children. File: %s.%s",
nodeName.c_str(), pCGF->GetFilename(), howToFix);
return false;
}
}
return true;
}
static void FindLodNodes(
std::vector<CNodeCGF*>& resultLodNodes,
CContentCGF* const pCGF,
bool bReturnSingleNode,
const CNodeCGF* const pParent,
const string& lodNamePrefix)
{
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
if (pParent && (pNode->pParent != pParent))
{
continue;
}
if (StringHelpers::StartsWithIgnoreCase(string(pNode->name), lodNamePrefix))
{
resultLodNodes.push_back(pNode);
if (bReturnSingleNode)
{
// Caller asked for *single* arbitrary LOD node
break;
}
}
}
}
static bool ValidateMeshSharing(const CContentCGF* pCGF)
{
assert(pCGF);
typedef std::set<CMesh*> MeshSet;
MeshSet meshes;
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
assert(pNode);
if (pNode->pMesh == 0)
{
if (pNode->pSharedMesh)
{
RCLogError(
"Data integrity check failed on %s: node refers a shared node, but pointer to shared mesh is NULL. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
}
else if (pNode->pSharedMesh == 0)
{
const MeshSet::const_iterator it = meshes.find(pNode->pMesh);
if (it == meshes.end())
{
meshes.insert(pNode->pMesh);
}
else
{
RCLogError(
"Data integrity check failed on %s: a mesh referenced from few nodes without using sharing. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
}
else
{
if (pNode == pNode->pSharedMesh)
{
RCLogError(
"Data integrity check failed on %s: a node refers itself. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
if (pNode->pSharedMesh->pSharedMesh)
{
RCLogError(
"Data integrity check failed on %s: a chain of shared nodes found. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
if (!pNode->pSharedMesh->pMesh)
{
RCLogError(
"Data integrity check failed on %s: mesh in shared node is NULL. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
if (pNode->pSharedMesh->pMesh != pNode->pMesh)
{
RCLogError(
"Data integrity check failed on %s: pointer to shared mesh does not point to mesh in shared node. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
}
}
return true;
}
static bool CopyMeshData(CContentCGF* pCGF, CNodeCGF* pDstNode, CNodeCGF* pSrcNode)
{
assert(pDstNode != pSrcNode);
if (!ValidateMeshSharing(pCGF))
{
return false;
}
if (!pSrcNode->pMesh)
{
RCLogError(
"Unexpected empty LOD mesh in %s. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
if (!pDstNode->pMesh)
{
RCLogError(
"Unexpected empty LOD 0 mesh in %s. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
if (pSrcNode->pSharedMesh == pDstNode)
{
return true;
}
// Make destination node "meshless"
if (pDstNode->pSharedMesh == 0)
{
// Find new owner for destination node's mesh.
CNodeCGF* newOwnerOfDstMesh = 0;
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode->pSharedMesh == pDstNode)
{
if (newOwnerOfDstMesh == 0)
{
newOwnerOfDstMesh = pNode;
pNode->pSharedMesh = 0;
}
else
{
pNode->pSharedMesh = newOwnerOfDstMesh;
}
}
}
if (newOwnerOfDstMesh == 0)
{
delete pDstNode->pMesh;
}
}
pDstNode->pMesh = 0;
pDstNode->pSharedMesh = 0;
// Everyone who referred source node, now should refer destination node
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode->pSharedMesh == pSrcNode)
{
pNode->pSharedMesh = pDstNode;
}
}
// Transfer mesh data from source to destination node
pDstNode->pMesh = pSrcNode->pMesh;
pDstNode->pSharedMesh = pSrcNode->pSharedMesh ? pSrcNode->pSharedMesh : 0;
pSrcNode->pSharedMesh = pDstNode;
if (!ValidateMeshSharing(pCGF))
{
return false;
}
return true;
}
static bool DeleteNode(CContentCGF* pCGF, CNodeCGF* pDeleteNode)
{
assert(pDeleteNode);
if (!ValidateMeshSharing(pCGF))
{
return false;
}
pCGF->RemoveNode(pDeleteNode);
if (!ValidateMeshSharing(pCGF))
{
return false;
}
return true;
}
} // namespace LodHelpers
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
bool CStaticObjectCompiler::SplitLODs(CContentCGF* pCGF)
{
const string lodNamePrefix(CGF_NODE_NAME_LOD_PREFIX);
if (!LodHelpers::ValidateMeshSharing(pCGF) || !LodHelpers::ValidateLodNodes(pCGF, lodNamePrefix))
{
return false;
}
// Check that meshes are not damaged
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
if (!pNode)
{
RCLogError(
"Unexpected NULL node in %s. Contact an RC programmer.",
pCGF->GetFilename());
return false;
}
const CMesh* const pMesh = pNode->pMesh;
if (!pMesh)
{
continue;
}
const char* pError = "";
if (!pMesh->Validate(&pError))
{
RCLogError(
"Mesh in node '%s' is damaged: %s. File %s.",
pNode->name, pError, pCGF->GetFilename());
return false;
}
}
std::vector<CNodeCGF*> lodNodes;
LodHelpers::FindLodNodes(lodNodes, pCGF, true, NULL, lodNamePrefix);
if (lodNodes.empty())
{
// We don't have any LOD nodes. Done.
return true;
}
//
// Collect all nodes which can potentially have LODs.
//
struct LodableNodeInfo
{
CNodeCGF* pNode;
size_t maxLodFound;
size_t lod0Index;
LodableNodeInfo(CNodeCGF* a_pNode, size_t a_maxLodFound, size_t a_lod0Index)
: pNode(a_pNode)
, maxLodFound(a_maxLodFound)
, lod0Index(a_lod0Index)
{
assert(a_pNode);
}
};
std::vector<LodableNodeInfo> lodableNodes;
for (int i = 0; i < pCGF->GetNodeCount(); ++i)
{
CNodeCGF* const pNode = pCGF->GetNode(i);
// Skip nodes which cannot have LODs
{
if (pNode->pMesh == 0)
{
continue;
}
if ((pNode->type != CNodeCGF::NODE_MESH) && (pNode->type != CNodeCGF::NODE_HELPER))
{
continue;
}
const string nodeName = pNode->name;
if (StringHelpers::StartsWithIgnoreCase(nodeName, lodNamePrefix))
{
continue;
}
}
//
// Get and analyze children LOD nodes, if any
//
LodableNodeInfo lodsInfo(pNode, 0, 0);
lodNodes.clear();
LodHelpers::FindLodNodes(lodNodes, pCGF, false, pNode, lodNamePrefix);
if (lodNodes.empty())
{
lodableNodes.push_back(lodsInfo);
continue;
}
CNodeCGF* arrLodNodes[MAX_LOD_COUNT] = { 0 };
for (size_t ii = 0; ii < lodNodes.size(); ++ii)
{
CNodeCGF* const pLodNode = lodNodes[ii];
assert(pLodNode);
assert(pNode == pLodNode->pParent);
const int lodIndex = LodHelpers::GetLodIndex(pLodNode->name, lodNamePrefix);
assert((lodIndex > 0) && (lodIndex < CStaticObjectCompiler::MAX_LOD_COUNT));
if (arrLodNodes[lodIndex])
{
RCLogError(
"More than one node of LOD %i ('%s', '%s') attached to same parent node '%s' in file %s. Please modify and re-export source asset file.",
lodIndex, arrLodNodes[lodIndex]->name, pLodNode->name, pNode->name, pCGF->GetFilename());
return false;
}
arrLodNodes[lodIndex] = pLodNode;
if (lodIndex > lodsInfo.maxLodFound)
{
lodsInfo.maxLodFound = lodIndex;
}
}
assert(arrLodNodes[0] == 0);
//
// Check LOD sequence for validity
//
{
arrLodNodes[0] = pNode;
// Check that we don't have gaps in the LOD sequence
{
int gapStart = -1;
for (int lodIndex = 1; lodIndex < MAX_LOD_COUNT; ++lodIndex)
{
if ((gapStart < 0) && !arrLodNodes[lodIndex] && arrLodNodes[lodIndex - 1])
{
gapStart = lodIndex;
}
if ((gapStart >= 0) && arrLodNodes[lodIndex])
{
RCLogError(
"Missing LOD node%s between '%s' and '%s' in file %s. Please modify and re-export source asset file.",
((lodIndex - gapStart > 1) ? "s" : ""), arrLodNodes[gapStart - 1]->name, arrLodNodes[lodIndex]->name, pCGF->GetFilename());
return false;
}
}
}
// Check that geometry simplification of the LODs is good
for (int lodIndex = 1; lodIndex < MAX_LOD_COUNT; ++lodIndex)
{
const CNodeCGF* const pLod0 = arrLodNodes[lodIndex - 1];
const CNodeCGF* const pLod1 = arrLodNodes[lodIndex];
if (pLod1 == 0)
{
break;
}
const int subsetCount0 = pLod0->pMesh->GetSubSetCount();
const int subsetCount1 = pLod1->pMesh->GetSubSetCount();
if (subsetCount1 < subsetCount0)
{
// Number of draw calls decreased. The LOD is good.
continue;
}
if (subsetCount1 > subsetCount0)
{
RCLogWarning(
"LOD node '%s' has more submaterials used than node '%s' (%d vs %d) in file %s. Please modify and re-export source asset file.",
pLod1->name, pLod0->name, subsetCount1, subsetCount0, pCGF->GetFilename());
continue;
}
// Number of draw calls is same. Let's check that number of faces is small enough comparing to previous LOD.
const int faceCount0 = pLod0->pMesh->GetIndexCount() / 3;
const int faceCount1 = pLod1->pMesh->GetIndexCount() / 3;
static const float faceCountRatio = 1.5f;
const int maxFaceCount1 = int(faceCount0 / faceCountRatio);
if (faceCount1 > maxFaceCount1)
{
RCLogWarning(
"LOD node '%s' should have less than %2.0f%% of the faces of it's parent. It has %d faces, it's parent has %d. It should have less than %d.",
pLod1->name, 100.0f / faceCountRatio, faceCount1, faceCount0, maxFaceCount1);
continue;
}
}
arrLodNodes[0] = 0;
}
//
// For consoles user can mark a LOD as being LOD0. Let's handle it.
//
if (m_bConsole)
{
int newLod0 = -1;
for (int lodIndex = 0; lodIndex < MAX_LOD_COUNT; ++lodIndex)
{
CNodeCGF* const pLodNode = arrLodNodes[lodIndex];
if (pLodNode && NodeHasProperty(pLodNode, s_consolesLod0MarkerStr))
{
newLod0 = lodIndex;
break;
}
}
if (newLod0 >= 0)
{
// Breakable objects expect rendering and physics geometry matching each other,
// so we cannot change geometry (LOD0's by consoles_lod0's)
const string filename = StringHelpers::MakeLowerCase(PathHelpers::GetFilename(pCGF->GetFilename()));
if (strstr(filename.c_str(), "break"))
{
RCLogWarning(
"Ignoring property '%s' in node '%s' because the mesh is Breakable. File %s.",
s_consolesLod0MarkerStr, arrLodNodes[newLod0]->name, pCGF->GetFilename());
newLod0 = -1;
}
}
if (newLod0 >= 0)
{
RCLog(
"Found property '%s' in node '%s' of file %s. This node becomes LOD 0.",
s_consolesLod0MarkerStr, arrLodNodes[newLod0]->name, pCGF->GetFilename());
lodsInfo.lod0Index = newLod0;
}
}
lodableNodes.push_back(lodsInfo);
}
assert(!lodableNodes.empty());
//
// Process all nodes which can potentially have LODs.
//
size_t maxFinalLod = 0;
for (size_t lodableIndex = 0; lodableIndex < lodableNodes.size(); ++lodableIndex)
{
const LodableNodeInfo& info = lodableNodes[lodableIndex];
const size_t finalLod = info.maxLodFound - info.lod0Index;
if (finalLod > maxFinalLod)
{
maxFinalLod = finalLod;
}
}
int64 finalLodVertexCount[MAX_LOD_COUNT] = { 0 };
int finalLodMaxAutocopyReceiver = 0;
bool bUsedAutocopyLimit = false;
// Two passes: first one computes resulting mesh sizes; second one
// performs modifications to nodes and forms final lod lists.
// Between passes we will made decision about how many lod levels
// should be auto-copied.
for (int pass = 0; pass < 2; ++pass)
{
// Find maximal LOD which should receive auto-copied LODs
if (pass == 1)
{
for (int finalLodIndex = 1; finalLodIndex <= maxFinalLod; ++finalLodIndex)
{
const double vertexCount0 = (double)finalLodVertexCount[finalLodIndex - 1];
const double vertexCount1 = (double)finalLodVertexCount[finalLodIndex];
if (vertexCount1 > vertexCount0 * 0.6)
{
// size of this LOD is more than 60% of previous LOD,
// so it's better to disable auto-copying to this LOD
break;
}
finalLodMaxAutocopyReceiver = finalLodIndex;
}
}
for (size_t lodableIndex = 0; lodableIndex < lodableNodes.size(); ++lodableIndex)
{
const LodableNodeInfo& info = lodableNodes[lodableIndex];
CNodeCGF* const pNode = info.pNode;
assert(info.lod0Index + maxFinalLod >= info.maxLodFound);
lodNodes.clear();
LodHelpers::FindLodNodes(lodNodes, pCGF, false, pNode, lodNamePrefix);
CNodeCGF* arrLodNodes[MAX_LOD_COUNT] = { 0 };
for (size_t i = 0; i < lodNodes.size(); ++i)
{
CNodeCGF* const pLodNode = lodNodes[i];
const int lodIndex = LodHelpers::GetLodIndex(pLodNode->name, lodNamePrefix);
assert((lodIndex > 0) && (lodIndex < CStaticObjectCompiler::MAX_LOD_COUNT));
arrLodNodes[lodIndex] = pLodNode;
}
arrLodNodes[0] = pNode;
// Handle shifting LODs according to consoles_lod0
if ((pass == 1) && (info.lod0Index > 0))
{
if (!LodHelpers::CopyMeshData(pCGF, pNode, arrLodNodes[info.lod0Index]))
{
return false;
}
// Delete unused LOD nodes
for (int lodIndex = 1; lodIndex <= info.lod0Index; ++lodIndex)
{
if (arrLodNodes[lodIndex])
{
if (!LodHelpers::DeleteNode(pCGF, arrLodNodes[lodIndex]))
{
return false;
}
}
}
arrLodNodes[info.lod0Index] = pNode;
}
// Add LOD nodes to appropriate CGF objects
static const bool bShowAutcopyStatistics = false;
CNodeCGF* pLastLodNode = arrLodNodes[info.lod0Index];
for (int finalLodIndex = 0; finalLodIndex <= maxFinalLod; ++finalLodIndex)
{
const size_t lodIndex = info.lod0Index + finalLodIndex;
const bool bDuplicate = ((lodIndex >= MAX_LOD_COUNT) || (arrLodNodes[lodIndex] == 0));
CNodeCGF* const pLodNode = bDuplicate ? pLastLodNode : arrLodNodes[lodIndex];
if (bDuplicate && (pNode->name[0] == '$'))
{
// No need to autocopy special nodes, engine doesn't handle them
// correctly when they are stored in LODs
continue;
}
if ((pass == 0) || (finalLodIndex == 0))
{
if (pass == 0)
{
finalLodVertexCount[finalLodIndex] += pLodNode->pMesh->GetVertexCount();
}
continue;
}
if (bDuplicate && (pLodNode->pMesh->GetVertexCount() <= 0))
{
// No need to autocopy meshes without geometry, streaming in the
// engine doesn't use such nodes.
continue;
}
if (bDuplicate && (finalLodIndex > finalLodMaxAutocopyReceiver))
{
bUsedAutocopyLimit = true;
continue;
}
if constexpr (bShowAutcopyStatistics && bDuplicate)
{
const int subsetCount = pLodNode->pMesh->GetSubSetCount();
const int faceCount = pLodNode->pMesh->GetIndexCount() / 3;
const int vertexCount = pLodNode->pMesh->GetVertexCount();
RCLogWarning(
"@,%u,%u,%u,%u,%u,%s,%s",
(uint)finalLodIndex - 1, (uint)maxFinalLod, (uint)subsetCount, (uint)faceCount, (uint)vertexCount, pLodNode->name, pCGF->GetFilename());
}
if (pLodNode != pNode)
{
cry_strcpy(pLodNode->name, pNode->name);
pLodNode->pParent = 0;
}
CContentCGF* const pLodCGF = (m_pLODs[finalLodIndex] == 0)
? MakeLOD(finalLodIndex, pCGF)
: m_pLODs[finalLodIndex];
// Note: we should set lod nodes' pParent to 0 right before saving lod nodes to
// a file. Otherwise parent nodes (LOD0 and its parents) will be exported also,
// which is wrong for lod files.
// Note that we cannot set pParent to 0 *here*, because in case of LODs auto-copying
// the lod node can actually be the LOD0 node (pLodNome == pNode), so setting its
// pParent to 0 will destroy LOD0's parenting info, which is bad, because we really
// need to have proper hierarchy for LOD0 CGF.
pLodCGF->AddNode(pLodNode);
pLastLodNode = pLodNode;
}
// Delete LOD nodes from LOD0 CGF
if (pass == 1)
{
for (int finalLodIndex = 1; finalLodIndex < MAX_LOD_COUNT; ++finalLodIndex)
{
const size_t lodIndex = info.lod0Index + finalLodIndex;
if ((lodIndex < MAX_LOD_COUNT) && arrLodNodes[lodIndex])
{
if (!LodHelpers::DeleteNode(pCGF, arrLodNodes[lodIndex]))
{
return false;
}
}
}
}
}
}
if (bUsedAutocopyLimit)
{
RCLogWarning(
"Autocopying LODs was limited to LOD %d because vertex count difference between LODs is less than 40%% (%lli vs %lli). File %s.",
finalLodMaxAutocopyReceiver, finalLodVertexCount[finalLodMaxAutocopyReceiver], finalLodVertexCount[finalLodMaxAutocopyReceiver + 1], pCGF->GetFilename());
}
if (!LodHelpers::ValidateMeshSharing(pCGF) || !LodHelpers::ValidateLodNodes(pCGF, lodNamePrefix))
{
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
CContentCGF* CStaticObjectCompiler::MakeLOD(int lodIndex, const CContentCGF* pCGF)
{
assert((lodIndex >= 0) && (lodIndex < MAX_LOD_COUNT));
if (m_pLODs[lodIndex])
{
return 0;
}
const string filename = pCGF->GetFilename();
m_pLODs[lodIndex] = new CContentCGF(filename.c_str());
CContentCGF* pCompiledCGF = m_pLODs[lodIndex];
*pCompiledCGF->GetExportInfo() = *pCGF->GetExportInfo(); // Copy export info.
*pCompiledCGF->GetPhysicalizeInfo() = *pCGF->GetPhysicalizeInfo();
pCompiledCGF->GetExportInfo()->bCompiledCGF = true;
pCompiledCGF->GetUsedMaterialIDs() = pCGF->GetUsedMaterialIDs();
*pCompiledCGF->GetSkinningInfo() = *pCGF->GetSkinningInfo();
if (lodIndex > 0 && m_pLODs[0])
{
m_pLODs[0]->GetExportInfo()->bHaveAutoLods = true;
}
return m_pLODs[lodIndex];
}
//////////////////////////////////////////////////////////////////////////
bool CStaticObjectCompiler::MakeMergedCGF(CContentCGF* pCompiledCGF, CContentCGF* pCGF)
{
std::vector<CNodeCGF*> merge_nodes;
for (int i = 0; i < pCGF->GetNodeCount(); i++)
{
CNodeCGF* pNode = pCGF->GetNode(i);
if (pNode->pMesh && !pNode->bPhysicsProxy && pNode->type == CNodeCGF::NODE_MESH)
{
merge_nodes.push_back(pNode);
}
}
if (merge_nodes.empty())
{
RCLogError("Error merging nodes, No mergeable geometry in CGF %s", pCGF->GetFilename());
return false;
}
CMesh* pMergedMesh = new CMesh;
string errorMessage;
if (!CGFNodeMerger::MergeNodes(pCGF, merge_nodes, errorMessage, pMergedMesh))
{
RCLogError("Error merging nodes: %s, in CGF %s", errorMessage.c_str(), pCGF->GetFilename());
delete pMergedMesh;
return false;
}
//////////////////////////////////////////////////////////////////////////
// Add single node for merged mesh.
CNodeCGF* pNode = new CNodeCGF;
pNode->type = CNodeCGF::NODE_MESH;
cry_strcpy(pNode->name, "Merged");
pNode->bIdentityMatrix = true;
pNode->pMesh = pMergedMesh;
pNode->pMaterial = pCGF->GetCommonMaterial();
CNodeCGF* pMergedNode = pNode;
// Add node to CGF contents.
pCompiledCGF->AddNode(pNode);
//////////////////////////////////////////////////////////////////////////
const string lodNamePrefix(CGF_NODE_NAME_LOD_PREFIX);
for (int nLod = 1; nLod < MAX_LOD_COUNT; nLod++)
{
// Try to merge LOD nodes.
merge_nodes.clear();
for (int i = 0; i < pCGF->GetNodeCount(); i++)
{
CNodeCGF* pNode2 = pCGF->GetNode(i);
if (pNode2->pMesh && !pNode2->bPhysicsProxy && pNode2->type == CNodeCGF::NODE_HELPER)
{
const string nodeName(pNode2->name);
if (StringHelpers::StartsWithIgnoreCase(nodeName, lodNamePrefix))
{
if (nodeName.length() <= lodNamePrefix.length())
{
RCLogError("Error merging LOD %d nodes: LOD node name '%s' doesn't contain LOD index in CGF %s", nLod, nodeName.c_str(), pCGF->GetFilename());
return false;
}
const int lodIndex = atoi(nodeName.c_str() + lodNamePrefix.length());
if (lodIndex == nLod)
{
// This is a LOD helper.
merge_nodes.push_back(pNode2);
}
}
}
}
if (!merge_nodes.empty())
{
CMesh* pMergedLodMesh = new CMesh;
string errorMessage2;
if (!CGFNodeMerger::MergeNodes(pCGF, merge_nodes, errorMessage2, pMergedLodMesh))
{
RCLogError("Error merging LOD %d nodes: %s, in CGF %s", nLod, errorMessage2.c_str(), pCGF->GetFilename());
delete pMergedLodMesh;
return false;
}
//////////////////////////////////////////////////////////////////////////
// Add LOD node
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Add single node for merged mesh.
CNodeCGF* pNode2 = new CNodeCGF;
pNode2->type = CNodeCGF::NODE_HELPER;
pNode2->helperType = HP_GEOMETRY;
azsnprintf(pNode2->name, sizeof(pNode2->name), "%s%d_%s", lodNamePrefix.c_str(), nLod, pMergedNode->name);
pNode2->bIdentityMatrix = true;
pNode2->pMesh = pMergedLodMesh;
pNode2->pParent = pMergedNode;
pNode2->pMaterial = pCGF->GetCommonMaterial();
// Add node to CGF contents.
pCompiledCGF->AddNode(pNode2);
//////////////////////////////////////////////////////////////////////////
}
}
// Add rest of the helper nodes.
int numNodes = pCGF->GetNodeCount();
for (int i = 0; i < numNodes; i++)
{
CNodeCGF* pNode2 = pCGF->GetNode(i);
if (pNode2->type != CNodeCGF::NODE_MESH)
{
// Do not add LOD nodes.
if (!StringHelpers::StartsWithIgnoreCase(string(pNode2->name), lodNamePrefix))
{
if (pNode2->pParent && pNode2->pParent->type == CNodeCGF::NODE_MESH)
{
pNode2->pParent = pMergedNode;
}
pCompiledCGF->AddNode(pNode2);
}
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
int CStaticObjectCompiler::GetSubMeshCount(const CContentCGF* pCGFLod0)
{
int subMeshCount = 0;
if (pCGFLod0)
{
const int nodeCount = pCGFLod0->GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
const CNodeCGF* const pNode = pCGFLod0->GetNode(i);
if (pNode && pNode->type == CNodeCGF::NODE_MESH && pNode->pMesh)
{
++subMeshCount;
}
}
}
return subMeshCount;
}
//////////////////////////////////////////////////////////////////////////
int CStaticObjectCompiler::GetJointCount(const CContentCGF* pCGF)
{
int jointCount = 0;
if (pCGF)
{
const int nodeCount = pCGF->GetNodeCount();
for (int i = 0; i < nodeCount; ++i)
{
const CNodeCGF* const pNode = pCGF->GetNode(i);
if (pNode && pNode->type == CNodeCGF::NODE_HELPER && StringHelpers::StartsWith(pNode->name, "$joint"))
{
++jointCount;
}
}
}
return jointCount;
}