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

2146 lines
64 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 "Cry3DEngine_precompiled.h"
#include "StatObj.h"
#include "IndexedMesh.h"
#include "../RenderDll/Common/Shadow_Renderer.h"
#include <IRenderer.h>
#include <CrySizer.h>
#include "ObjMan.h"
#include "MatMan.h"
#include "RenderMeshMerger.h"
#include <CryPhysicsDeprecation.h>
#define MAX_VERTICES_MERGABLE 15000
#define MAX_TRIS_IN_LOD_0 512
#define TRIS_IN_LOD_WARNING_RAIO (1.5f)
// Minimal ratio of Lod(n-1)/Lod(n) polygons to consider LOD for sub-object merging.
#define MIN_TRIS_IN_MERGED_LOD_RAIO (1.5f)
DEFINE_INTRUSIVE_LINKED_LIST(CStatObj)
//////////////////////////////////////////////////////////////////////////
CStatObj::CStatObj()
{
m_pAsyncUpdateContext = 0;
m_nNodeCount = 0;
m_nMergedMemoryUsage = 0;
m_nUsers = 0; // reference counter
m_nLastDrawMainFrameId = 0;
#ifdef SERVER_CHECKS
m_pMesh = NULL;
#endif
m_nFlags = 0;
#ifdef SUPPORT_TERRAIN_AO_PRE_COMPUTATIONS
m_fOcclusionAmount = -1;
m_pHeightmap = NULL;
m_nHeightmapSize = 0;
#endif
m_pLODs = 0;
m_lastBooleanOpScale = 1.f;
m_fGeometricMeanFaceArea = 0.f;
m_fLodDistance = 0.0f;
Init();
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::Init()
{
m_pAsyncUpdateContext = 0;
m_pIndexedMesh = 0;
m_lockIdxMesh = 0;
m_nRenderTrisCount = m_nLoadedTrisCount = m_nLoadedVertexCount = 0;
m_nRenderMatIds = 0;
m_fObjectRadius = 0;
m_fRadiusHors = 0;
m_fRadiusVert = 0;
m_pParentObject = 0;
m_pClonedSourceObject = 0;
m_bVehicleOnlyPhysics = 0;
m_bBreakableByGame = 0;
m_idmatBreakable = -1;
m_nLoadedLodsNum = 1;
m_nMinUsableLod0 = 0;
m_nMaxUsableLod0 = 0;
m_nMaxUsableLod = 0;
m_pLod0 = 0;
m_aiVegetationRadius = -1.0f;
m_phys_mass = -1.0f;
m_phys_density = -1.0f;
m_vBoxMin.Set(0, 0, 0);
m_vBoxMax.Set(0, 0, 0);
m_vVegCenter.Set(0, 0, 0);
m_fGeometricMeanFaceArea = 0.f;
m_fLodDistance = 0.0f;
m_pRenderMesh = 0;
m_bDefaultObject = false;
if (m_pLODs)
{
for (int i = 0; i < MAX_STATOBJ_LODS_NUM; i++)
{
if (m_pLODs[i])
{
m_pLODs[i]->Init();
}
}
}
m_pReadStream = 0;
m_nSubObjectMeshCount = 0;
m_nRenderMeshMemoryUsage = 0;
m_arrRenderMeshesPotentialMemoryUsage[0] = m_arrRenderMeshesPotentialMemoryUsage[1] = -1;
m_bCanUnload = false;
m_bLodsLoaded = false;
m_bDefaultObject = false;
m_bOpenEdgesTested = false;
m_bSubObject = false;
m_bSharesChildren = false;
m_bHasDeformationMorphs = false;
m_bTmpIndexedMesh = false;
m_bMerged = false;
m_bMergedLODs = false;
m_bUnmergable = false;
m_bLowSpecLod0Set = false;
m_bHaveOcclusionProxy = false;
m_bCheckGarbage = false;
m_bLodsAreLoadedFromSeparateFile = false;
m_bNoHitRefinement = false;
m_bDontOccludeExplosions = false;
m_isDeformable = false;
m_isProxyTooBig = false;
m_bHasStreamOnlyCGF = true;
// Assign default material originally.
m_pMaterial = GetMatMan()->GetDefaultMaterial();
m_pLattice = 0;
m_pSpines = 0;
m_nSpines = 0;
m_pBoneMapping = 0;
m_pLastBooleanOp = 0;
m_pMapFaceToFace0 = 0;
m_pClothTangentsData = 0;
m_pSkinInfo = 0;
m_hasClothTangentsData = m_hasSkinInfo = 0;
m_pDelayedSkinParams = 0;
m_arrPhysGeomInfo.m_array.clear();
m_nInitialSubObjHideMask = 0;
#if !defined (_RELEASE)
m_fStreamingStart = 0.0f;
#endif
}
//////////////////////////////////////////////////////////////////////////
CStatObj::~CStatObj()
{
ShutDown();
}
void CStatObj::ShutDown()
{
if (m_pReadStream)
{
// We don't need this stream anymore.
m_pReadStream->Abort();
m_pReadStream = NULL;
}
SAFE_DELETE(m_pAsyncUpdateContext);
// assert (IsHeapValid());
SAFE_DELETE(m_pIndexedMesh);
// assert (IsHeapValid());
for (int n = 0; n < m_arrPhysGeomInfo.GetGeomCount(); n++)
{
if (m_arrPhysGeomInfo[n])
{
if (m_arrPhysGeomInfo[n]->pGeom->GetForeignData() == (void*)this)
{
m_arrPhysGeomInfo[n]->pGeom->SetForeignData(0, 0);
}
// Unregister geometry
CRY_PHYSICS_REPLACEMENT_ASSERT();
}
}
m_arrPhysGeomInfo.m_array.clear();
m_pStreamedRenderMesh = 0;
m_pMergedRenderMesh = 0;
SetRenderMesh(0);
#ifdef SERVER_CHECKS
SAFE_DELETE(m_pMesh);
#endif
// assert (IsHeapValid());
/* // SDynTexture is not accessible for 3dengine
for(int i=0; i<FAR_TEX_COUNT; i++)
if(m_arrSpriteTexPtr[i])
m_arrSpriteTexPtr[i]->ReleaseDynamicRT(true);
for(int i=0; i<FAR_TEX_COUNT_60; i++)
if(m_arrSpriteTexPtr_60[i])
m_arrSpriteTexPtr_60[i]->ReleaseDynamicRT(true);
*/
SAFE_RELEASE(m_pLattice);
if (m_pLODs)
{
for (int i = 0; i < MAX_STATOBJ_LODS_NUM; i++)
{
if (m_pLODs[i])
{
if (m_pLODs[i]->m_pParentObject)
{
GetObjManager()->UnregisterForStreaming(m_pLODs[i]->m_pParentObject);
}
else
{
GetObjManager()->UnregisterForStreaming(m_pLODs[i]);
}
// Sub objects do not own the LODs, so they should not delete them.
m_pLODs[i] = 0;
}
}
}
//////////////////////////////////////////////////////////////////////////
// Handle sub-objects and parents.
//////////////////////////////////////////////////////////////////////////
for (size_t i = 0; i < m_subObjects.size(); i++)
{
CStatObj* pChildObj = (CStatObj*)m_subObjects[i].pStatObj;
if (pChildObj)
{
if (!m_bSharesChildren)
{
pChildObj->m_pParentObject = NULL;
}
GetObjManager()->UnregisterForStreaming(pChildObj);
pChildObj->Release();
}
}
m_subObjects.clear();
if (m_pParentObject && !m_pParentObject->m_subObjects.empty())
{
// Remove this StatObject from sub-objects of the parent.
SSubObject* pSubObjects = &m_pParentObject->m_subObjects[0];
for (int i = 0, num = m_pParentObject->m_subObjects.size(); i < num; i++)
{
if (pSubObjects[i].pStatObj == this)
{
m_pParentObject->m_subObjects.erase(m_pParentObject->m_subObjects.begin() + i);
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
if (m_pMapFaceToFace0)
{
delete[] m_pMapFaceToFace0;
}
m_pMapFaceToFace0 = 0;
if (m_hasClothTangentsData && !m_pClonedSourceObject)
{
delete[] m_pClothTangentsData;
}
if (m_hasSkinInfo && !m_pClonedSourceObject)
{
delete[] m_pSkinInfo;
}
m_pClothTangentsData = 0;
m_hasClothTangentsData = 0;
m_pSkinInfo = 0;
m_hasSkinInfo = 0;
SAFE_DELETE(m_pDelayedSkinParams);
SAFE_RELEASE(m_pClonedSourceObject);
#ifdef SUPPORT_TERRAIN_AO_PRE_COMPUTATIONS
m_fOcclusionAmount = -1;
SAFE_DELETE_ARRAY(m_pHeightmap);
m_pHeightmap = 0;
#endif
GetObjManager()->UnregisterForStreaming(this);
SAFE_DELETE_ARRAY(m_pLODs);
}
//////////////////////////////////////////////////////////////////////////
int CStatObj::Release()
{
// Has to be thread safe, as it can be called by a worker thread for deferred plane breaks
int newRef = CryInterlockedDecrement(&m_nUsers);
if (newRef <= 1)
{
if (m_pParentObject && m_pParentObject->m_nUsers <= 0)
{
GetObjManager()->CheckForGarbage(m_pParentObject);
}
if (newRef <= 0)
{
GetObjManager()->CheckForGarbage(this);
}
}
return newRef;
}
//////////////////////////////////////////////////////////////////////////
void* CStatObj::operator new ([[maybe_unused]] size_t size)
{
IObjManager* pObjManager = GetObjManager();
assert(size == sizeof(CStatObj));
return pObjManager->AllocateStatObj();
}
void CStatObj::operator delete (void* pToFree)
{
IObjManager* pObjManager = GetObjManager();
pObjManager->FreeStatObj((CStatObj*)pToFree);
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::FreeIndexedMesh()
{
if (!m_lockIdxMesh)
{
WriteLock lock(m_lockIdxMesh);
delete m_pIndexedMesh;
m_pIndexedMesh = 0;
}
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::CalcRadiuses()
{
if (!m_vBoxMin.IsValid() || !m_vBoxMax.IsValid())
{
Error("CStatObj::CalcRadiuses: Invalid bbox, File name: %s", m_szFileName.c_str());
m_vBoxMin.zero();
m_vBoxMax.zero();
}
m_fObjectRadius = m_vBoxMin.GetDistance(m_vBoxMax) * 0.5f;
float dxh = (float)max(fabs(GetBoxMax().x), fabs(GetBoxMin().x));
float dyh = (float)max(fabs(GetBoxMax().y), fabs(GetBoxMin().y));
m_fRadiusHors = (float)sqrt_tpl(dxh * dxh + dyh * dyh);
m_fRadiusVert = (GetBoxMax().z - 0) * 0.5f;// never change this
m_vVegCenter = (m_vBoxMax + m_vBoxMin) * 0.5f;
m_vVegCenter.z = m_fRadiusVert;
}
void CStatObj::MakeRenderMesh()
{
if (gEnv->IsDedicated())
{
return;
}
FUNCTION_PROFILER_3DENGINE;
SetRenderMesh(0);
if (!m_pIndexedMesh || m_pIndexedMesh->GetSubSetCount() == 0)
{
return;
}
CMesh* pMesh = m_pIndexedMesh->GetMesh();
m_nRenderTrisCount = 0;
//////////////////////////////////////////////////////////////////////////
// Initialize Mesh subset material flags.
//////////////////////////////////////////////////////////////////////////
for (int i = 0; i < pMesh->GetSubSetCount(); i++)
{
SMeshSubset& subset = pMesh->m_subsets[i];
if (!(subset.nMatFlags & MTL_FLAG_NODRAW))
{
m_nRenderTrisCount += subset.nNumIndices / 3;
}
}
//////////////////////////////////////////////////////////////////////////
if (!m_nRenderTrisCount)
{
return;
}
if (!(GetFlags() & STATIC_OBJECT_DYNAMIC))
{
m_pRenderMesh = GetRenderer()->CreateRenderMesh("StatObj_Static", GetFilePath(), NULL, eRMT_Static);
}
else
{
m_pRenderMesh = GetRenderer()->CreateRenderMesh("StatObj_Dynamic", GetFilePath(), NULL, eRMT_Dynamic);
m_pRenderMesh->KeepSysMesh(true);
}
SMeshBoneMapping_uint16* const pBoneMap = pMesh->m_pBoneMapping;
pMesh->m_pBoneMapping = 0;
uint32 nFlags = 0;
nFlags |= (!GetCVars()->e_StreamCgf && Get3DEngine()->m_bInLoad) ? FSM_SETMESH_ASYNC : 0;
m_pRenderMesh->SetMesh(*pMesh, 0, nFlags, false);
pMesh->m_pBoneMapping = pBoneMap;
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::SetMaterial(_smart_ptr<IMaterial> pMaterial)
{
m_pMaterial = pMaterial;
}
///////////////////////////////////////////////////////////////////////////////////////
Vec3 CStatObj::GetHelperPos(const char* szHelperName)
{
SSubObject* pSubObj = FindSubObject(szHelperName);
if (!pSubObj)
{
return Vec3(0, 0, 0);
}
return Vec3(pSubObj->tm.m03, pSubObj->tm.m13, pSubObj->tm.m23);
}
///////////////////////////////////////////////////////////////////////////////////////
const Matrix34& CStatObj::GetHelperTM(const char* szHelperName)
{
SSubObject* pSubObj = FindSubObject(szHelperName);
if (!pSubObj)
{
static Matrix34 identity(IDENTITY);
return identity;
}
return pSubObj->tm;
}
bool CStatObj::IsSameObject(const char* szFileName, const char* szGeomName)
{
// cmp object names
if (szGeomName)
{
if (azstricmp(szGeomName, m_szGeomName) != 0)
{
return false;
}
}
// Normalize file name
char szFileNameNorm[MAX_PATH_LENGTH] = "";
char* pszDest = szFileNameNorm;
const char* pszSource = szFileName;
while (*pszSource)
{
if (*pszSource == '\\')
{
*pszDest++ = '/';
}
else
{
*pszDest++ = *pszSource;
}
pszSource++;
}
*pszDest = 0;
// cmp file names
if (azstricmp(szFileNameNorm, m_szFileName) != 0)
{
return false;
}
return true;
}
void CStatObj::GetMemoryUsage(ICrySizer* pSizer) const
{
{
SIZER_COMPONENT_NAME(pSizer, "Self");
pSizer->AddObject(this, sizeof(*this));
}
{
SIZER_COMPONENT_NAME(pSizer, "subObjects");
pSizer->AddObject(m_subObjects);
}
{
SIZER_COMPONENT_NAME(pSizer, "Strings");
pSizer->AddObject(m_szFileName);
pSizer->AddObject(m_szGeomName);
pSizer->AddObject(m_szProperties);
}
{
SIZER_COMPONENT_NAME(pSizer, "Material");
pSizer->AddObject(m_pMaterial);
}
{
SIZER_COMPONENT_NAME(pSizer, "PhysGeomInfo");
pSizer->AddObject(m_arrPhysGeomInfo);
}
if (m_pLODs)
{
for (int i = 1; i < MAX_STATOBJ_LODS_NUM; i++)
{
SIZER_COMPONENT_NAME(pSizer, "StatObjLods");
pSizer->AddObject(m_pLODs[i]);
}
}
if (m_pIndexedMesh)
{
SIZER_COMPONENT_NAME(pSizer, "Mesh");
pSizer->AddObject(m_pIndexedMesh);
}
int nVtx = 0;
if (m_pIndexedMesh)
{
nVtx = m_pIndexedMesh->GetVertexCount();
}
else if (m_pRenderMesh)
{
nVtx = m_pRenderMesh->GetVerticesCount();
}
if (m_pSpines)
{
SIZER_COMPONENT_NAME(pSizer, "StatObj Foliage Data");
pSizer->AddObject(m_pSpines, sizeof(m_pSpines[0]), m_nSpines);
if (m_pBoneMapping)
{
pSizer->AddObject(m_pBoneMapping, sizeof(m_pBoneMapping[0]), nVtx);
}
for (int i = 0; i < m_nSpines; i++)
{
pSizer->AddObject(m_pSpines[i].pVtx, sizeof(Vec3) * 2 + sizeof(Vec4), m_pSpines[i].nVtx);
}
}
if (m_hasClothTangentsData && (!m_pClonedSourceObject || m_pClonedSourceObject == this))
{
SIZER_COMPONENT_NAME(pSizer, "Deformable StatObj ClothTangents");
pSizer->AddObject(m_pClothTangentsData, nVtx * sizeof(m_pClothTangentsData[0]));
}
if (m_hasSkinInfo && (!m_pClonedSourceObject || m_pClonedSourceObject == this))
{
SIZER_COMPONENT_NAME(pSizer, "Deformable StatObj SkinData");
pSizer->AddObject(m_pSkinInfo, (nVtx + 1) * sizeof(m_pSkinInfo[0]));
}
if (m_pMapFaceToFace0)
{
SIZER_COMPONENT_NAME(pSizer, "Deformable StatObj Mesh");
pSizer->AddObject(m_pMapFaceToFace0, sizeof(m_pMapFaceToFace0[0]) * max(m_nLoadedTrisCount, m_nRenderTrisCount));
}
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::SetLodObject(int nLod, IStatObj* pLod)
{
assert(nLod > 0 && nLod < MAX_STATOBJ_LODS_NUM);
if (nLod <= 0 || nLod >= MAX_STATOBJ_LODS_NUM)
{
return;
}
if (strstr(m_szProperties, "lowspeclod0"))
{
m_bLowSpecLod0Set = true;
}
bool bLodCompound = pLod && (pLod->GetFlags() & STATIC_OBJECT_COMPOUND) != 0;
if (pLod && !bLodCompound)
{
// Check if low lod decrease amount of used materials.
int nPrevLodMatIds = m_nRenderMatIds;
int nPrevLodTris = m_nLoadedTrisCount;
if (nLod > 1 && m_pLODs && m_pLODs[nLod - 1])
{
nPrevLodMatIds = m_pLODs[nLod - 1]->m_nRenderMatIds;
nPrevLodTris = m_pLODs[nLod - 1]->m_nLoadedTrisCount;
}
if (GetCVars()->e_LodsForceUse)
{
if ((int)m_nMaxUsableLod < nLod)
{
m_nMaxUsableLod = nLod;
}
}
else
{
int min_tris = GetCVars()->e_LodMinTtris;
if (((pLod->GetLoadedTrisCount() >= min_tris || nPrevLodTris >= (3 * min_tris) / 2)
|| pLod->GetRenderMatIds() < nPrevLodMatIds) && nLod > (int)m_nMaxUsableLod)
{
m_nMaxUsableLod = nLod;
}
}
if (m_pParentObject && (m_pParentObject->m_nMaxUsableLod < m_nMaxUsableLod))
{
m_pParentObject->m_nMaxUsableLod = m_nMaxUsableLod;
}
pLod->SetLodLevel0(this);
pLod->SetMaterial(m_pMaterial); // Lod must use same material as parent.
if (pLod->GetLoadedTrisCount() > MAX_TRIS_IN_LOD_0)
{
if ((strstr(pLod->GetProperties(), "lowspeclod0") != 0) && !m_bLowSpecLod0Set)
{
m_bLowSpecLod0Set = true;
m_nMaxUsableLod0 = nLod;
}
if (!m_bLowSpecLod0Set)
{
m_nMaxUsableLod0 = nLod;
}
}
if (nLod + 1 > (int)m_nLoadedLodsNum)
{
m_nLoadedLodsNum = nLod + 1;
}
// When assigning lod to child object.
if (m_pParentObject)
{
if (nLod + 1 > (int)m_pParentObject->m_nLoadedLodsNum)
{
m_pParentObject->m_nLoadedLodsNum = nLod + 1;
}
}
}
if (pLod && bLodCompound)
{
m_nMaxUsableLod = nLod;
pLod->SetUnmergable(m_bUnmergable);
}
if (!m_pLODs && pLod)
{
m_pLODs = new _smart_ptr<CStatObj>[MAX_STATOBJ_LODS_NUM];
}
if (m_pLODs)
{
m_pLODs[nLod] = (CStatObj*)pLod;
}
}
//////////////////////////////////////////////////////////////////////////
IStatObj* CStatObj::GetLodObject(int nLodLevel, bool bReturnNearest)
{
if (nLodLevel < 1)
{
return this;
}
if (!m_pLODs)
{
if (bReturnNearest || nLodLevel == 0)
{
return this;
}
else
{
return NULL;
}
}
if (bReturnNearest)
{
nLodLevel = CLAMP(nLodLevel, 0, MAX_STATOBJ_LODS_NUM - 1);
}
CStatObj* pLod = 0;
if (nLodLevel < MAX_STATOBJ_LODS_NUM)
{
pLod = m_pLODs[nLodLevel];
// Look up
if (bReturnNearest && !pLod)
{
// Find highest existing lod looking up.
int lod = nLodLevel;
while (lod > 0 && m_pLODs[lod] == 0)
{
lod--;
}
if (lod > 0)
{
pLod = m_pLODs[lod];
}
else
{
pLod = this;
}
}
// Look down
if (bReturnNearest && !pLod)
{
// Find highest existing lod looking down.
for (int lod = nLodLevel + 1; lod < MAX_STATOBJ_LODS_NUM; lod++)
{
if (m_pLODs[lod])
{
pLod = m_pLODs[lod];
break;
}
}
}
}
return pLod;
}
bool CStatObj::IsPhysicsExist()
{
return m_arrPhysGeomInfo.GetGeomCount() > 0;
}
bool CStatObj::IsSphereOverlap(const Sphere& sSphere)
{
if (m_pRenderMesh && Overlap::Sphere_AABB(sSphere, AABB(m_vBoxMin, m_vBoxMax)))
{ // if inside bbox
int nPosStride = 0;
int nInds = m_pRenderMesh->GetIndicesCount();
const byte* pPos = m_pRenderMesh->GetPosPtr(nPosStride, FSL_READ);
vtx_idx* pInds = m_pRenderMesh->GetIndexPtr(FSL_READ);
if (pInds && pPos)
{
for (int i = 0; (i + 2) < nInds; i += 3)
{ // test all triangles of water surface strip
Vec3 v0 = (*(Vec3*)&pPos[nPosStride * pInds[i + 0]]);
Vec3 v1 = (*(Vec3*)&pPos[nPosStride * pInds[i + 1]]);
Vec3 v2 = (*(Vec3*)&pPos[nPosStride * pInds[i + 2]]);
Vec3 vBoxMin = v0;
vBoxMin.CheckMin(v1);
vBoxMin.CheckMin(v2);
Vec3 vBoxMax = v0;
vBoxMax.CheckMax(v1);
vBoxMax.CheckMax(v2);
if (Overlap::Sphere_AABB(sSphere, AABB(vBoxMin, vBoxMax)))
{
return true;
}
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::Invalidate(bool bPhysics, float tolerance)
{
AZ_UNUSED(bPhysics);
AZ_UNUSED(tolerance);
if (m_pIndexedMesh)
{
if (m_pIndexedMesh->GetIndexCount() > 0)
{
m_pIndexedMesh->CalcBBox();
m_vBoxMin = m_pIndexedMesh->m_bbox.min;
m_vBoxMax = m_pIndexedMesh->m_bbox.max;
MakeRenderMesh();
m_nLoadedVertexCount = m_pIndexedMesh->GetVertexCount();
m_nLoadedTrisCount = m_pIndexedMesh->GetFaceCount();
if (!m_nLoadedTrisCount)
{
m_nLoadedTrisCount = m_pIndexedMesh->GetIndexCount() / 3;
}
CalcRadiuses();
}
ReleaseIndexedMesh(true);
}
//////////////////////////////////////////////////////////////////////////
// Iterate through sub objects and update hide mask and sub obj mesh count.
//////////////////////////////////////////////////////////////////////////
m_nSubObjectMeshCount = 0;
for (size_t i = 0, numsub = m_subObjects.size(); i < numsub; i++)
{
SSubObject& subObj = m_subObjects[i];
if (subObj.pStatObj && subObj.nType == STATIC_SUB_OBJECT_MESH)
{
m_nSubObjectMeshCount++;
}
}
UnMergeSubObjectsRenderMeshes();
}
//////////////////////////////////////////////////////////////////////////
IStatObj* CStatObj::Clone(bool bCloneGeometry, bool bCloneChildren, bool bMeshesOnly)
{
if (m_bDefaultObject)
{
return this;
}
CStatObj* pNewObj = new CStatObj;
pNewObj->m_pClonedSourceObject = m_pClonedSourceObject ? m_pClonedSourceObject : this;
pNewObj->m_pClonedSourceObject->AddRef(); // Cloned object will keep a reference to this.
pNewObj->m_nLoadedTrisCount = m_nLoadedTrisCount;
pNewObj->m_nLoadedVertexCount = m_nLoadedVertexCount;
pNewObj->m_nRenderTrisCount = m_nRenderTrisCount;
if (bCloneGeometry)
{
if (m_bMerged)
{
UnMergeSubObjectsRenderMeshes();
}
if (m_pIndexedMesh && !m_bMerged)
{
pNewObj->m_pIndexedMesh = new CIndexedMesh;
pNewObj->m_pIndexedMesh->Copy(*m_pIndexedMesh->GetMesh());
}
if (m_pRenderMesh && !m_bMerged)
{
_smart_ptr<IRenderMesh> pRenderMesh = GetRenderer()->CreateRenderMesh(
"StatObj_Cloned",
pNewObj->GetFilePath(),
NULL,
((GetFlags() & STATIC_OBJECT_DYNAMIC) ? eRMT_Dynamic : eRMT_Static));
m_pRenderMesh->CopyTo(pRenderMesh);
pNewObj->SetRenderMesh(pRenderMesh);
}
}
else
{
if (m_pRenderMesh)
{
if (m_pMergedRenderMesh != m_pRenderMesh)
{
pNewObj->SetRenderMesh(m_pRenderMesh);
}
else
{
pNewObj->m_pRenderMesh = m_pRenderMesh;
}
}
pNewObj->m_pMergedRenderMesh = m_pMergedRenderMesh;
pNewObj->m_bMerged = m_pMergedRenderMesh != NULL;
}
pNewObj->m_szFileName = m_szFileName;
pNewObj->m_szGeomName = m_szGeomName;
pNewObj->m_cgfNodeName = m_cgfNodeName;
// Default material.
pNewObj->m_pMaterial = m_pMaterial;
//*pNewObj->m_arrSpriteTexID = *m_arrSpriteTexID;
pNewObj->m_fObjectRadius = m_fObjectRadius;
for (int i = 0; i < m_arrPhysGeomInfo.GetGeomCount(); i++)
{
pNewObj->m_arrPhysGeomInfo.SetPhysGeom(m_arrPhysGeomInfo[i], i, m_arrPhysGeomInfo.GetGeomType(i));
if (pNewObj->m_arrPhysGeomInfo[i])
{
// Remove geometry
CRY_PHYSICS_REPLACEMENT_ASSERT();
}
}
pNewObj->m_vBoxMin = m_vBoxMin;
pNewObj->m_vBoxMax = m_vBoxMax;
pNewObj->m_vVegCenter = m_vVegCenter;
pNewObj->m_fGeometricMeanFaceArea = m_fGeometricMeanFaceArea;
pNewObj->m_fRadiusHors = m_fRadiusHors;
pNewObj->m_fRadiusVert = m_fRadiusVert;
pNewObj->m_nFlags = m_nFlags | STATIC_OBJECT_CLONE;
pNewObj->m_fLodDistance = m_fLodDistance;
//////////////////////////////////////////////////////////////////////////
// Internal Flags.
//////////////////////////////////////////////////////////////////////////
pNewObj->m_bCanUnload = false;
pNewObj->m_bDefaultObject = m_bDefaultObject;
pNewObj->m_bOpenEdgesTested = m_bOpenEdgesTested;
pNewObj->m_bSubObject = false; // [anton] since parent is not copied anyway
pNewObj->m_bVehicleOnlyPhysics = m_bVehicleOnlyPhysics;
pNewObj->m_idmatBreakable = m_idmatBreakable;
pNewObj->m_bBreakableByGame = m_bBreakableByGame;
pNewObj->m_bHasDeformationMorphs = m_bHasDeformationMorphs;
pNewObj->m_bTmpIndexedMesh = m_bTmpIndexedMesh;
pNewObj->m_bHaveOcclusionProxy = m_bHaveOcclusionProxy;
pNewObj->m_bHasStreamOnlyCGF = m_bHasStreamOnlyCGF;
pNewObj->m_eStreamingStatus = m_eStreamingStatus;
//////////////////////////////////////////////////////////////////////////
int numSubObj = (int)m_subObjects.size();
if (bMeshesOnly)
{
numSubObj = 0;
for (size_t i = 0; i < m_subObjects.size(); i++)
{
if (m_subObjects[i].nType == STATIC_SUB_OBJECT_MESH)
{
numSubObj++;
}
else
{
break;
}
}
}
pNewObj->m_subObjects.reserve(numSubObj);
for (int i = 0; i < numSubObj; i++)
{
pNewObj->m_subObjects.push_back(m_subObjects[i]);
if (m_subObjects[i].pStatObj)
{
if (bCloneChildren)
{
pNewObj->m_subObjects[i].pStatObj = m_subObjects[i].pStatObj->Clone(bCloneGeometry, bCloneChildren, bMeshesOnly);
pNewObj->m_subObjects[i].pStatObj->AddRef();
((CStatObj*)(pNewObj->m_subObjects[i].pStatObj))->m_pParentObject = pNewObj;
}
else
{
m_subObjects[i].pStatObj->AddRef();
((CStatObj*)(m_subObjects[i].pStatObj))->m_nFlags |= STATIC_OBJECT_MULTIPLE_PARENTS;
}
}
}
pNewObj->m_nSubObjectMeshCount = m_nSubObjectMeshCount;
if (!bCloneChildren)
{
pNewObj->m_bSharesChildren = true;
}
if (pNewObj->m_hasClothTangentsData = m_hasClothTangentsData)
{
pNewObj->m_pClothTangentsData = m_pClothTangentsData;
}
if (pNewObj->m_hasSkinInfo = m_hasSkinInfo)
{
pNewObj->m_pSkinInfo = m_pSkinInfo;
}
return pNewObj;
}
//////////////////////////////////////////////////////////////////////////
IIndexedMesh* CStatObj::GetIndexedMesh(bool bCreateIfNone)
{
WriteLock lock(m_lockIdxMesh);
if (m_pIndexedMesh)
{
return m_pIndexedMesh;
}
else if (m_pRenderMesh && bCreateIfNone)
{
if (m_pRenderMesh->GetIndexedMesh(m_pIndexedMesh = new CIndexedMesh) == NULL)
{
// GetIndexMesh will free the IndexedMesh object if an allocation failed
m_pIndexedMesh = NULL;
return NULL;
}
CMesh* pMesh = m_pIndexedMesh->GetMesh();
if (!pMesh || pMesh->m_subsets.size() <= 0)
{
m_pIndexedMesh->Release();
m_pIndexedMesh = 0;
return 0;
}
m_bTmpIndexedMesh = true;
int i, j, i0 = pMesh->m_subsets[0].nFirstVertId + pMesh->m_subsets[0].nNumVerts;
for (i = j = 1; i < pMesh->m_subsets.size(); i++)
{
if (pMesh->m_subsets[i].nFirstVertId - i0 < pMesh->m_subsets[j].nFirstVertId - i0)
{
j = i;
}
}
if (j < pMesh->m_subsets.size() && pMesh->m_subsets[0].nPhysicalizeType == PHYS_GEOM_TYPE_DEFAULT &&
pMesh->m_subsets[j].nPhysicalizeType != PHYS_GEOM_TYPE_DEFAULT && pMesh->m_subsets[j].nFirstVertId > i0)
{
pMesh->m_subsets[j].nNumVerts += pMesh->m_subsets[j].nFirstVertId - i0;
pMesh->m_subsets[j].nFirstVertId = i0;
}
return m_pIndexedMesh;
}
return 0;
}
IIndexedMesh* CStatObj::CreateIndexedMesh()
{
if (!m_pIndexedMesh)
{
m_pIndexedMesh = new CIndexedMesh();
}
return m_pIndexedMesh;
}
void CStatObj::ReleaseIndexedMesh(bool bRenderMeshUpdated)
{
WriteLock lock(m_lockIdxMesh);
if (m_bTmpIndexedMesh && m_pIndexedMesh)
{
int istart, iend;
CMesh* pMesh = m_pIndexedMesh->GetMesh();
if (m_pRenderMesh && !bRenderMeshUpdated)
{
TRenderChunkArray& Chunks = m_pRenderMesh->GetChunks();
for (int i = 0; i < pMesh->m_subsets.size(); i++)
{
Chunks[i].m_nMatFlags |= pMesh->m_subsets[i].nMatFlags & 1 << 30;
}
}
if (bRenderMeshUpdated && m_pBoneMapping)
{
for (int i = iend = 0; i < (int)pMesh->m_subsets.size(); i++)
{
if ((pMesh->m_subsets[i].nMatFlags & (MTL_FLAG_NOPHYSICALIZE | MTL_FLAG_NODRAW)) == MTL_FLAG_NOPHYSICALIZE)
{
for (istart = iend++; iend < (int)m_chunkBoneIds.size() && m_chunkBoneIds[iend]; iend++)
{
;
}
if (!pMesh->m_subsets[i].nNumIndices)
{
m_chunkBoneIds.erase(m_chunkBoneIds.begin() + istart, m_chunkBoneIds.begin() + iend);
iend = istart;
}
}
}
}
m_pIndexedMesh->Release();
m_pIndexedMesh = 0;
m_bTmpIndexedMesh = false;
}
}
//////////////////////////////////////////////////////////////////////////
static bool SubObjectsOfCompoundHaveLOD(const CStatObj* pStatObj, int nLod)
{
int numSubObjects = pStatObj->GetSubObjectCount();
for (int i = 0; i < numSubObjects; ++i)
{
const IStatObj::SSubObject* pSubObject = const_cast<CStatObj*>(pStatObj)->GetSubObject(i);
if (!pSubObject)
{
continue;
}
const CStatObj* pChildObj = (const CStatObj*)pSubObject->pStatObj;
if (!pChildObj)
{
continue;
}
if (!pChildObj->m_pLODs)
{
continue;
}
const CStatObj* pLod = pChildObj->m_pLODs[nLod];
if (pLod)
{
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::TryMergeSubObjects(bool bFromStreaming)
{
// sub meshes merging
if (GetCVars()->e_StatObjMerge)
{
if (!m_bUnmergable && !IsDeformable())
{
MergeSubObjectsRenderMeshes(bFromStreaming, this, 0);
if (!bFromStreaming && !m_pLODs && m_nFlags & STATIC_OBJECT_COMPOUND)
{
// Check if LODs were not split (production mode)
for (int i = 1; i < MAX_STATOBJ_LODS_NUM; ++i)
{
if (!SubObjectsOfCompoundHaveLOD(this, i))
{
continue;
}
CStatObj* pStatObj = new CStatObj();
pStatObj->m_szFileName = m_szFileName;
char lodName[32];
azstrcpy(lodName, 32, "-mlod");
azltoa(i, lodName + 5, AZ_ARRAY_SIZE(lodName) - 5, 10);
pStatObj->m_szFileName.append(lodName);
pStatObj->m_szGeomName = m_szGeomName;
pStatObj->m_bSubObject = true;
SetLodObject(i, pStatObj);
m_bMergedLODs = true;
}
}
if (m_pLODs)
{
for (int i = 1; i < MAX_STATOBJ_LODS_NUM; i++)
{
if (m_pLODs[i])
{
m_pLODs[i]->MergeSubObjectsRenderMeshes(bFromStreaming, this, i);
}
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::MergeSubObjectsRenderMeshes(bool bFromStreaming, CStatObj* pLod0, int nLod)
{
if (m_bUnmergable)
{
return;
}
FUNCTION_PROFILER_3DENGINE;
LOADING_TIME_PROFILE_SECTION;
m_bMerged = false;
m_pMergedRenderMesh = 0;
int nSubObjCount = (int)pLod0->m_subObjects.size();
std::vector<SRenderMeshInfoInput> lstRMI;
SRenderMeshInfoInput rmi;
rmi.pMat = m_pMaterial;
rmi.mat.SetIdentity();
rmi.pMesh = 0;
rmi.pSrcRndNode = 0;
for (int s = 0; s < nSubObjCount; s++)
{
CStatObj::SSubObject* pSubObj = &pLod0->m_subObjects[s];
if (pSubObj->pStatObj && pSubObj->nType == STATIC_SUB_OBJECT_MESH)
{
CStatObj* pStatObj = (CStatObj*)pSubObj->pStatObj->GetLodObject(nLod, true); // Get lod, if not exist get lowest existing.
if (pStatObj)
{
CryAutoCriticalSection lock(pStatObj->m_streamingMeshLock);
if (pStatObj->m_pRenderMesh || pStatObj->m_pStreamedRenderMesh)
{
rmi.pMesh = pStatObj->m_pRenderMesh ? pStatObj->m_pRenderMesh : pStatObj->m_pStreamedRenderMesh;
rmi.mat = pSubObj->tm;
rmi.bIdentityMatrix = pSubObj->bIdentityMatrix;
rmi.nSubObjectIndex = s;
lstRMI.push_back(rmi);
}
}
}
}
_smart_ptr<IRenderMesh> pMergedMesh = 0;
if (lstRMI.size() == 1 && lstRMI[0].bIdentityMatrix)
{
// If identity matrix and only mesh-subobject use it as a merged render mesh.
pMergedMesh = rmi.pMesh;
}
else if (!lstRMI.empty())
{
SMergeInfo info;
info.sMeshName = GetFilePath();
info.sMeshType = "StatObj_Merged";
info.bMergeToOneRenderMesh = true;
info.pUseMaterial = m_pMaterial;
pMergedMesh = GetSharedRenderMeshMerger()->MergeRenderMeshes(&lstRMI[0], lstRMI.size(), info);
}
if (pMergedMesh)
{
if (bFromStreaming)
{
CryAutoCriticalSection lock(m_streamingMeshLock);
m_pMergedRenderMesh = pMergedMesh;
m_pStreamedRenderMesh = pMergedMesh;
}
else
{
m_pMergedRenderMesh = pMergedMesh;
SetRenderMesh(pMergedMesh);
}
m_bMerged = true;
if (m_pLod0)
{
// Make sure upmost lod is also marked to be merged.
m_pLod0->SetMerged(true);
}
}
}
bool CStatObj::IsMatIDReferencedByObj(uint16 matID)
{
//Check root obj
if (IRenderMesh* pRenderMesh = GetRenderMesh())
{
TRenderChunkArray& Chunks = pRenderMesh->GetChunks();
for (int nChunkId = 0; nChunkId < Chunks.size(); nChunkId++)
{
if (Chunks[nChunkId].m_nMatID == matID)
{
return true;
}
}
}
//check children
int nSubObjCount = (int)m_subObjects.size();
for (int s = 0; s < nSubObjCount; s++)
{
CStatObj::SSubObject* pSubObj = &m_subObjects[s];
if (pSubObj->pStatObj)
{
CStatObj* pSubStatObj = (CStatObj*)pSubObj->pStatObj;
if (IRenderMesh* pSubRenderMesh = pSubStatObj->GetRenderMesh())
{
TRenderChunkArray& Chunks = pSubRenderMesh->GetChunks();
for (int nChunkId = 0; nChunkId < Chunks.size(); nChunkId++)
{
if (Chunks[nChunkId].m_nMatID == matID)
{
return true;
}
}
}
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CStatObj::CanMergeSubObjects()
{
if (gEnv->IsDedicated())
{
return false;
}
if (!m_clothData.empty())
{
return false;
}
int nSubMeshes = 0;
int nTotalVertexCount = 0;
int nTotalTriCount = 0;
int nTotalRenderChunksCount = 0;
int nSubObjCount = (int)m_subObjects.size();
for (int s = 0; s < nSubObjCount; s++)
{
CStatObj::SSubObject* pSubObj = &m_subObjects[s];
if (pSubObj->pStatObj && pSubObj->nType == STATIC_SUB_OBJECT_MESH && !pSubObj->bHidden)
{
CStatObj* pStatObj = (CStatObj*)pSubObj->pStatObj;
// Conditions to not merge subobjects
if (pStatObj->m_pMaterial != m_pMaterial || // Different materials
pStatObj->m_nSpines || // It's bendable foliage
!pStatObj->m_clothData.empty()) // It's cloth
{
return false;
}
nSubMeshes++;
nTotalVertexCount += pStatObj->m_nLoadedVertexCount;
nTotalTriCount += pStatObj->m_nLoadedTrisCount;
nTotalRenderChunksCount += pStatObj->m_nRenderMatIds;
}
}
// Check for mat_breakable surface type in material
if (m_pMaterial)
{
int nSubMtls = m_pMaterial->GetSubMtlCount();
if (nSubMtls > 0)
{
for (int i = 0; i < nSubMtls; ++i)
{
_smart_ptr<IMaterial> pSubMtl = m_pMaterial->GetSafeSubMtl(i);
if (pSubMtl)
{
ISurfaceType* pSFType = pSubMtl->GetSurfaceType();
// This is breakable glass.
// Do not merge meshes that have procedural physics breakability in them.
if (pSFType && pSFType->GetBreakability() != 0)
{
//if mesh is streamed, we must assume the material is referenced
if (m_bMeshStrippedCGF)
{
return false;
}
else if (IsMatIDReferencedByObj((uint16)i))
{
return false;
}
}
}
}
}
else // nSubMtls==0
{
ISurfaceType* pSFType = m_pMaterial->GetSurfaceType();
// This is breakable glass.
// Do not merge meshes that have procedural physics breakability in them.
if (pSFType && pSFType->GetBreakability() != 0)
{
return false;
}
}
}
if (nTotalVertexCount > MAX_VERTICES_MERGABLE || nSubMeshes <= 1 || nTotalRenderChunksCount <= 1)
{
return false;
}
if ((nTotalTriCount / nTotalRenderChunksCount) > GetCVars()->e_StatObjMergeMaxTrisPerDrawCall)
{
return false; // tris to draw calls ratio is already not so bad
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::UnMergeSubObjectsRenderMeshes()
{
if (m_bMerged)
{
m_bMerged = false;
m_pMergedRenderMesh = 0;
SetRenderMesh(0);
}
if (m_bMergedLODs)
{
delete[] m_pLODs;
m_pLODs = 0;
}
}
//------------------------------------------------------------------------------
// This function is recovered from previous FindSubObject function which was then
// changed and creating many CGA model issues (some joints never move).
CStatObj::SSubObject* CStatObj::FindSubObject_CGA(const char* sNodeName)
{
uint32 numSubObjects = m_subObjects.size();
for (uint32 i = 0; i < numSubObjects; i++)
{
if (azstricmp(m_subObjects[i].name.c_str(), sNodeName) == 0)
{
return &m_subObjects[i];
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
CStatObj::SSubObject* CStatObj::FindSubObject(const char* sNodeName)
{
uint32 numSubObjects = m_subObjects.size();
int len = 1; // some objects has ' ' in the beginning
for (; sNodeName[len] > ' ' && sNodeName[len] != ',' && sNodeName[len] != ';'; len++)
{
;
}
for (uint32 i = 0; i < numSubObjects; i++)
{
if (_strnicmp(m_subObjects[i].name.c_str(), sNodeName, len) == 0 && m_subObjects[i].name[len] == 0)
{
return &m_subObjects[i];
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
CStatObj::SSubObject* CStatObj::FindSubObject_StrStr(const char* sNodeName)
{
uint32 numSubObjects = m_subObjects.size();
for (uint32 i = 0; i < numSubObjects; i++)
{
if (stristr(m_subObjects[i].name.c_str(), sNodeName))
{
return &m_subObjects[i];
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
IStatObj::SSubObject& CStatObj::AddSubObject(IStatObj* pSubObj)
{
assert(pSubObj);
SetSubObjectCount(m_subObjects.size() + 1);
InitializeSubObject(m_subObjects[m_subObjects.size() - 1]);
m_subObjects[m_subObjects.size() - 1].pStatObj = pSubObj;
pSubObj->AddRef();
return m_subObjects[m_subObjects.size() - 1];
}
//////////////////////////////////////////////////////////////////////////
bool CStatObj::RemoveSubObject(int nIndex)
{
if (nIndex >= 0 && nIndex < (int)m_subObjects.size())
{
SSubObject* pSubObj = &m_subObjects[nIndex];
CStatObj* pChildObj = (CStatObj*)pSubObj->pStatObj;
if (pChildObj)
{
if (!m_bSharesChildren)
{
pChildObj->m_pParentObject = NULL;
}
pChildObj->Release();
}
m_subObjects.erase(m_subObjects.begin() + nIndex);
Invalidate(true);
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::SetSubObjectCount(int nCount)
{
// remove sub objects.
while ((int)m_subObjects.size() > nCount)
{
RemoveSubObject(m_subObjects.size() - 1);
}
SSubObject subobj;
InitializeSubObject(subobj);
m_subObjects.resize(nCount, subobj);
if (nCount > 0)
{
m_nFlags |= STATIC_OBJECT_COMPOUND;
}
else
{
m_nFlags &= ~STATIC_OBJECT_COMPOUND;
}
Invalidate(true);
UnMergeSubObjectsRenderMeshes();
}
//////////////////////////////////////////////////////////////////////////
bool CStatObj::CopySubObject(int nToIndex, IStatObj* pFromObj, int nFromIndex)
{
SSubObject* pSrcSubObj = pFromObj->GetSubObject(nFromIndex);
if (!pSrcSubObj)
{
return false;
}
if (nToIndex >= (int)m_subObjects.size())
{
SetSubObjectCount(nToIndex + 1);
if (pFromObj == this)
{
pSrcSubObj = pFromObj->GetSubObject(nFromIndex);
}
}
m_subObjects[nToIndex] = *pSrcSubObj;
if (pSrcSubObj->pStatObj)
{
pSrcSubObj->pStatObj->AddRef();
}
Invalidate(true);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CStatObj::GetPhysicalProperties(float& mass, float& density)
{
mass = m_phys_mass;
density = m_phys_density;
if (mass < 0 && density < 0)
{
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CStatObj::RayIntersection(SRayHitInfo& hitInfo, _smart_ptr<IMaterial> pCustomMtl)
{
Vec3 vOut;
bool bNonDirectionalTest = hitInfo.inRay.direction.IsZero();
// First check if ray intersect objects bounding box.
if (!bNonDirectionalTest && !Intersect::Ray_AABB(hitInfo.inRay, AABB(m_vBoxMin, m_vBoxMax), vOut))
{
return false;
}
if (bNonDirectionalTest && !Overlap::AABB_AABB(
AABB(hitInfo.inRay.origin - Vec3(hitInfo.fMaxHitDistance), hitInfo.inRay.origin + Vec3(hitInfo.fMaxHitDistance)),
AABB(m_vBoxMin, m_vBoxMax)))
{
return false;
}
if ((m_nFlags & STATIC_OBJECT_COMPOUND) && !m_bMerged)
{
SRayHitInfo hitOut;
bool bAnyHit = false;
float fMinDistance = FLT_MAX;
for (int i = 0, num = m_subObjects.size(); i < num; i++)
{
if (m_subObjects[i].pStatObj && m_subObjects[i].nType == STATIC_SUB_OBJECT_MESH && !m_subObjects[i].bHidden)
{
if (((CStatObj*)(m_subObjects[i].pStatObj))->m_nFlags & STATIC_OBJECT_HIDDEN)
{
continue;
}
Matrix34 invertedTM = m_subObjects[i].tm.GetInverted();
SRayHitInfo hit = hitInfo;
// Transform ray into sub-object local space.
hit.inReferencePoint = invertedTM.TransformPoint(hit.inReferencePoint);
hit.inRay.origin = invertedTM.TransformPoint(hit.inRay.origin);
hit.inRay.direction = invertedTM.TransformVector(hit.inRay.direction);
int nFirstTriangleId = hit.pHitTris ? hit.pHitTris->Count() : 0;
if (((CStatObj*)m_subObjects[i].pStatObj)->RayIntersection(hit, pCustomMtl))
{
if (hit.fDistance < fMinDistance)
{
hitInfo.pStatObj = m_subObjects[i].pStatObj;
bAnyHit = true;
hitOut = hit;
}
}
// transform collected triangles from sub-object space into object space
if (hit.pHitTris)
{
for (int t = nFirstTriangleId; t < hit.pHitTris->Count(); t++)
{
SRayHitTriangle& ht = hit.pHitTris->GetAt(t);
for (int c = 0; c < 3; c++)
{
ht.v[c] = m_subObjects[i].tm.TransformPoint(ht.v[c]);
}
}
}
}
}
if (bAnyHit)
{
// Restore input ray/reference point.
hitOut.inReferencePoint = hitInfo.inReferencePoint;
hitOut.inRay = hitInfo.inRay;
hitInfo = hitOut;
return true;
}
}
else
{
bool result = false;
IRenderMesh* pRenderMesh = m_pRenderMesh;
// Sometimes, object has no base lod mesh. So need to hit test with low level mesh.
// If the distance from camera is larger then a base lod distance, then base lod mesh is not loaded yet.
if (!pRenderMesh && m_nMaxUsableLod > 0 && m_pLODs && m_pLODs[m_nMaxUsableLod])
{
pRenderMesh = m_pLODs[m_nMaxUsableLod]->GetRenderMesh();
}
AZ_Warning("StatObj Ray Intersection",
pRenderMesh,
"No render mesh available for hit testing for statobj %s", m_szFileName.c_str());
if (pRenderMesh)
{
result = CRenderMeshUtils::RayIntersection(pRenderMesh, hitInfo, pCustomMtl);
if (result)
{
hitInfo.pStatObj = this;
hitInfo.pRenderMesh = pRenderMesh;
}
}
return result;
}
return false;
}
bool CStatObj::LineSegIntersection(const Lineseg& lineSeg, Vec3& hitPos, int& surfaceTypeId)
{
bool intersects = false;
#ifdef SERVER_CHECKS
if (m_pMesh)
{
int numVertices, numVerticesf16, numIndices;
Vec3* pPositions = m_pMesh->GetStreamPtrAndElementCount<Vec3>(CMesh::POSITIONS, 0, &numVertices);
Vec3f16* pPositionsf16 = m_pMesh->GetStreamPtrAndElementCount<Vec3f16>(CMesh::POSITIONSF16, 0, &numVerticesf16);
uint16* pIndices = m_pMesh->GetStreamPtrAndElementCount<uint16>(CMesh::INDICES, 0, &numIndices);
if (numIndices && numVerticesf16)
{
DynArray<SMeshSubset>::iterator itSubset;
for (itSubset = m_pMesh->m_subsets.begin(); itSubset != m_pMesh->m_subsets.end(); ++itSubset)
{
if (!(itSubset->nMatFlags & MTL_FLAG_NODRAW))
{
int lastIndex = itSubset->nFirstIndexId + itSubset->nNumIndices;
for (int i = itSubset->nFirstIndexId; i < lastIndex; )
{
const Vec3 v0 = pPositionsf16[pIndices[i++]].ToVec3();
const Vec3 v1 = pPositionsf16[pIndices[i++]].ToVec3();
const Vec3 v2 = pPositionsf16[pIndices[i++]].ToVec3();
if (Intersect::Lineseg_Triangle(lineSeg, v0, v2, v1, hitPos) || // Front face
Intersect::Lineseg_Triangle(lineSeg, v0, v1, v2, hitPos)) // Back face
{
_smart_ptr<IMaterial> pMtl = m_pMaterial->GetSafeSubMtl(itSubset->nMatID);
surfaceTypeId = pMtl->GetSurfaceTypeId();
intersects = true;
break;
}
}
}
}
}
else if (numIndices && numVertices)
{
DynArray<SMeshSubset>::iterator itSubset;
for (itSubset = m_pMesh->m_subsets.begin(); itSubset != m_pMesh->m_subsets.end(); ++itSubset)
{
if (!(itSubset->nMatFlags & MTL_FLAG_NODRAW))
{
int lastIndex = itSubset->nFirstIndexId + itSubset->nNumIndices;
for (int i = itSubset->nFirstIndexId; i < lastIndex; )
{
const Vec3& v0 = pPositions[pIndices[i++]];
const Vec3& v1 = pPositions[pIndices[i++]];
const Vec3& v2 = pPositions[pIndices[i++]];
if (Intersect::Lineseg_Triangle(lineSeg, v0, v2, v1, hitPos) || // Front face
Intersect::Lineseg_Triangle(lineSeg, v0, v1, v2, hitPos)) // Back face
{
_smart_ptr<IMaterial> pMtl = m_pMaterial->GetSafeSubMtl(itSubset->nMatID);
surfaceTypeId = pMtl->GetSurfaceTypeId();
intersects = true;
break;
}
}
}
}
}
}
else
#endif
if (m_pRenderMesh)
{
m_pRenderMesh->LockForThreadAccess();
int numIndices = m_pRenderMesh->GetIndicesCount();
int numVertices = m_pRenderMesh->GetVerticesCount();
if (numIndices && numVertices)
{
int posStride;
uint8* pPositions = (uint8*)m_pRenderMesh->GetPosPtr(posStride, FSL_READ);
vtx_idx* pIndices = m_pRenderMesh->GetIndexPtr(FSL_READ);
TRenderChunkArray& Chunks = m_pRenderMesh->GetChunks();
int nChunkCount = Chunks.size();
for (int nChunkId = 0; nChunkId < nChunkCount; nChunkId++)
{
CRenderChunk* pChunk = &Chunks[nChunkId];
if (!(pChunk->m_nMatFlags & MTL_FLAG_NODRAW))
{
int lastIndex = pChunk->nFirstIndexId + pChunk->nNumIndices;
for (int i = pChunk->nFirstIndexId; i < lastIndex; )
{
const Vec3& v0 = *(Vec3*)(pPositions + pIndices[i++] * posStride);
const Vec3& v1 = *(Vec3*)(pPositions + pIndices[i++] * posStride);
const Vec3& v2 = *(Vec3*)(pPositions + pIndices[i++] * posStride);
if (Intersect::Lineseg_Triangle(lineSeg, v0, v2, v1, hitPos) || // Front face
Intersect::Lineseg_Triangle(lineSeg, v0, v1, v2, hitPos)) // Back face
{
_smart_ptr<IMaterial> pMtl = m_pMaterial->GetSafeSubMtl(pChunk->m_nMatID);
surfaceTypeId = pMtl->GetSurfaceTypeId();
intersects = true;
break;
}
}
}
}
}
m_pRenderMesh->UnlockStream(VSF_GENERAL);
m_pRenderMesh->UnlockIndexStream();
m_pRenderMesh->UnLockForThreadAccess();
}
return intersects;
}
#ifdef SUPPORT_TERRAIN_AO_PRE_COMPUTATIONS
float CStatObj::GetOcclusionAmount()
{
if (m_fOcclusionAmount < 0)
{
PrintMessage("Computing occlusion value for: %s ... ", GetFilePath());
float fHits = 0;
float fTests = 0;
Matrix34 objMatrix;
objMatrix.SetIdentity();
Vec3 vStep = Vec3(0.5f, 0.5f, 0.5f);
Vec3 vClosestHitPoint;
float fClosestHitDistance = 1000;
// y-rays
for (float x = m_vBoxMin.x; x <= m_vBoxMax.x; x += vStep.x)
{
for (float z = m_vBoxMin.z; z <= m_vBoxMax.z; z += vStep.z)
{
Vec3 vStart (x, m_vBoxMin.y, z);
Vec3 vEnd (x, m_vBoxMax.y, z);
if (GetObjManager()->RayStatObjIntersection(this, objMatrix, GetMaterial(), vStart, vEnd, vClosestHitPoint, fClosestHitDistance, true))
{
fHits++;
}
fTests++;
}
}
// x-rays
for (float y = m_vBoxMin.y; y <= m_vBoxMax.y; y += vStep.y)
{
for (float z = m_vBoxMin.z; z <= m_vBoxMax.z; z += vStep.z)
{
Vec3 vStart (m_vBoxMin.x, y, z);
Vec3 vEnd (m_vBoxMax.x, y, z);
if (GetObjManager()->RayStatObjIntersection(this, objMatrix, GetMaterial(), vStart, vEnd, vClosestHitPoint, fClosestHitDistance, true))
{
fHits++;
}
fTests++;
}
}
// z-rays
for (float y = m_vBoxMin.y; y <= m_vBoxMax.y; y += vStep.y)
{
for (float x = m_vBoxMin.x; x <= m_vBoxMax.x; x += vStep.x)
{
Vec3 vStart (x, y, m_vBoxMax.z);
Vec3 vEnd (x, y, m_vBoxMin.z);
if (GetObjManager()->RayStatObjIntersection(this, objMatrix, GetMaterial(), vStart, vEnd, vClosestHitPoint, fClosestHitDistance, true))
{
fHits++;
}
fTests++;
}
}
m_fOcclusionAmount = fTests ? (fHits / fTests) : 0;
PrintMessagePlus("[%.2f]", m_fOcclusionAmount);
}
return m_fOcclusionAmount;
}
#endif
void CStatObj::SetRenderMesh(IRenderMesh* pRM)
{
LOADING_TIME_PROFILE_SECTION;
if (pRM == m_pRenderMesh)
{
return;
}
{
CryAutoCriticalSection lock(m_streamingMeshLock);
m_pRenderMesh = pRM;
}
if (m_pRenderMesh)
{
m_nRenderTrisCount = 0;
m_nRenderMatIds = 0;
TRenderChunkArray& Chunks = m_pRenderMesh->GetChunks();
for (int nChunkId = 0; nChunkId < Chunks.size(); nChunkId++)
{
CRenderChunk* pChunk = &Chunks[nChunkId];
if (pChunk->m_nMatFlags & MTL_FLAG_NODRAW || !pChunk->pRE)
{
continue;
}
if (pChunk->nNumIndices > 0)
{
m_nRenderMatIds++;
m_nRenderTrisCount += pChunk->nNumIndices / 3;
}
}
m_nLoadedTrisCount = pRM->GetIndicesCount() / 3;
m_nLoadedVertexCount = pRM->GetVerticesCount();
}
// this will prepare all deformable object during loading instead when needed.
// Thus is will prevent runtime stalls (300ms when shooting a taxi with 6000 vertices)
// but will incrase memory since every deformable information needs to be loaded(500kb for the taxi)
// So this is more a workaround, the real solution would precompute the data in the RC and load
// the data only when needed into memory
if (GetCVars()->e_PrepareDeformableObjectsAtLoadTime && m_pRenderMesh && m_pDelayedSkinParams)
{
PrepareSkinData(m_pDelayedSkinParams->mtxSkelToMesh, m_pDelayedSkinParams->pPhysSkel, m_pDelayedSkinParams->r);
}
}
#ifdef SUPPORT_TERRAIN_AO_PRE_COMPUTATIONS
void CStatObj::CheckUpdateObjectHeightmap()
{
if (m_pHeightmap)
{
return;
}
PrintMessage("Computing object heightmap for: %s ... ", GetFilePath());
m_nHeightmapSize = (int)(CLAMP(max(m_vBoxMax.x - m_vBoxMin.x, m_vBoxMax.y - m_vBoxMin.y) * 16, 8, 256));
m_pHeightmap = new float[m_nHeightmapSize * m_nHeightmapSize];
memset(m_pHeightmap, 0, sizeof(float) * m_nHeightmapSize * m_nHeightmapSize);
float dx = max(0.001f, (m_vBoxMax.x - m_vBoxMin.x) / m_nHeightmapSize);
float dy = max(0.001f, (m_vBoxMax.y - m_vBoxMin.y) / m_nHeightmapSize);
Matrix34 objMatrix;
objMatrix.SetIdentity();
for (float x = m_vBoxMin.x + dx; x < m_vBoxMax.x - dx; x += dx)
{
for (float y = m_vBoxMin.y + dy; y < m_vBoxMax.y - dy; y += dy)
{
Vec3 vStart (x, y, m_vBoxMax.z);
Vec3 vEnd (x, y, m_vBoxMin.z);
Vec3 vClosestHitPoint(0, 0, 0);
float fClosestHitDistance = 1000000;
if (GetObjManager()->RayStatObjIntersection(this, objMatrix, GetMaterial(),
vStart, vEnd, vClosestHitPoint, fClosestHitDistance, false))
{
int nX = CLAMP(int((x - m_vBoxMin.x) / dx), 0, m_nHeightmapSize - 1);
int nY = CLAMP(int((y - m_vBoxMin.y) / dy), 0, m_nHeightmapSize - 1);
m_pHeightmap[nX * m_nHeightmapSize + nY] = vClosestHitPoint.z;
}
}
}
PrintMessagePlus("[%dx%d] done", m_nHeightmapSize, m_nHeightmapSize);
}
float CStatObj::GetObjectHeight(float x, float y)
{
CheckUpdateObjectHeightmap();
float dx = max(0.001f, (m_vBoxMax.x - m_vBoxMin.x) / m_nHeightmapSize);
float dy = max(0.001f, (m_vBoxMax.y - m_vBoxMin.y) / m_nHeightmapSize);
int nX = CLAMP(int((x - m_vBoxMin.x) / dx), 0, m_nHeightmapSize - 1);
int nY = CLAMP(int((y - m_vBoxMin.y) / dy), 0, m_nHeightmapSize - 1);
return m_pHeightmap[nX * m_nHeightmapSize + nY];
}
#endif
//////////////////////////////////////////////////////////////////////////
int CStatObj::CountChildReferences() const
{
// Check if it must be released.
int nChildRefs = 0;
int numChilds = (int)m_subObjects.size();
for (int i = 0; i < numChilds; i++)
{
const IStatObj::SSubObject& so = m_subObjects[i];
CStatObj* pChildStatObj = (CStatObj*)so.pStatObj;
if (!pChildStatObj) // All stat objects must be at the begining of the array//
{
break;
}
// if I'm the real parent of this child, check that no-one else is referencing it, otherwise it doesn't matter if I get deleted
if (pChildStatObj->m_pParentObject == this)
{
bool bIgnoreSharedStatObj = false;
for (int k = 0; k < i; k++)
{
if (pChildStatObj == m_subObjects[k].pStatObj)
{
// If we share pointer to this stat obj then do not count it again.
bIgnoreSharedStatObj = true;
nChildRefs -= 1; // 1 reference from current object should be ignored.
break;
}
}
if (!bIgnoreSharedStatObj)
{
nChildRefs += pChildStatObj->m_nUsers - 1; // 1 reference from parent should be ignored.
}
}
}
assert(nChildRefs >= 0);
return nChildRefs;
}
IStatObj* CStatObj::GetLowestLod()
{
if (int nLowestLod = CStatObj::GetMinUsableLod())
{
return m_pLODs ? (CStatObj*)m_pLODs[CStatObj::GetMinUsableLod()] : (CStatObj*)NULL;
}
return this;
}
//////////////////////////////////////////////////////////////////////////
int CStatObj::FindHighestLOD(int nBias)
{
int nLowestLod = CStatObj::GetMinUsableLod();
// if requested lod is not ready - find nearest ready one
int nLod = CLAMP((int)m_nMaxUsableLod + nBias, nLowestLod, (int)m_nMaxUsableLod);
while (nLod && nLod >= nLowestLod && (!m_pLODs || !m_pLODs[nLod] || !m_pLODs[nLod]->GetRenderMesh()))
{
nLod--;
}
if (nLod == 0 && !GetRenderMesh())
{
nLod = -1;
}
return nLod;
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::GetStatisticsNonRecursive(SStatistics& si)
{
CStatObj* pStatObj = this;
for (int lod = 0; lod < MAX_STATOBJ_LODS_NUM; lod++)
{
CStatObj* pLod = (CStatObj*)pStatObj->GetLodObject(lod);
if (pLod)
{
//if (!pLod0 && pLod->GetRenderMesh())
//pLod0 = pLod;
if (lod > 0 && lod + 1 > si.nLods) // Assign last existing lod.
{
si.nLods = lod + 1;
}
si.nVerticesPerLod[lod] += pLod->m_nLoadedVertexCount;
si.nIndicesPerLod[lod] += pLod->m_nLoadedTrisCount * 3;
si.nMeshSize += pLod->m_nRenderMeshMemoryUsage;
IRenderMesh* pRenderMesh = pLod->GetRenderMesh();
if (pRenderMesh)
{
si.nMeshSizeLoaded += pRenderMesh->GetMemoryUsage(0, IRenderMesh::MEM_USAGE_ONLY_STREAMS);
//if (si.pTextureSizer)
//pRenderMesh->GetTextureMemoryUsage(0,si.pTextureSizer);
//if (si.pTextureSizer2)
//pRenderMesh->GetTextureMemoryUsage(0,si.pTextureSizer2);
}
}
}
si.nIndices += m_nLoadedTrisCount * 3;
si.nVertices += m_nLoadedVertexCount;
for (int j = 0; j < pStatObj->m_arrPhysGeomInfo.GetGeomCount(); j++)
{
if (pStatObj->GetPhysGeom(j))
{
ICrySizer* pPhysSizer = GetISystem()->CreateSizer();
pStatObj->GetPhysGeom(j)->pGeom->GetMemoryStatistics(pPhysSizer);
int sz = pPhysSizer->GetTotalSize();
si.nPhysProxySize += sz;
si.nPhysProxySizeMax = max(si.nPhysProxySizeMax, sz);
si.nPhysPrimitives += pStatObj->GetPhysGeom(j)->pGeom->GetPrimitiveCount();
pPhysSizer->Release();
}
}
}
//////////////////////////////////////////////////////////////////////////
void CStatObj::GetStatistics(SStatistics& si)
{
si.bSplitLods = m_bLodsAreLoadedFromSeparateFile;
bool bMultiSubObj = (GetFlags() & STATIC_OBJECT_COMPOUND) != 0;
if (!bMultiSubObj)
{
GetStatisticsNonRecursive(si);
si.nSubMeshCount = 0;
si.nNumRefs = GetNumRefs();
si.nDrawCalls = m_nRenderMatIds;
}
else
{
si.nNumRefs = GetNumRefs();
std::vector<void*> addedObjects;
for (int k = 0; k < GetSubObjectCount(); k++)
{
if (!GetSubObject(k))
{
continue;
}
CStatObj* pSubObj = (CStatObj*)GetSubObject(k)->pStatObj;
int nSubObjectType = GetSubObject(k)->nType;
if (nSubObjectType != STATIC_SUB_OBJECT_MESH && nSubObjectType != STATIC_SUB_OBJECT_HELPER_MESH)
{
continue;
}
if (stl::find(addedObjects, pSubObj))
{
continue;
}
addedObjects.push_back(pSubObj);
if (pSubObj)
{
pSubObj->GetStatisticsNonRecursive(si);
si.nSubMeshCount++;
if (nSubObjectType == STATIC_SUB_OBJECT_MESH)
{
si.nDrawCalls += pSubObj->m_nRenderMatIds;
}
if (pSubObj->GetNumRefs() > si.nNumRefs)
{
si.nNumRefs = pSubObj->GetNumRefs();
}
}
}
}
// Only do rough estimation based on the material
// This is more consistent when streaming is enabled and render mesh may no exist
if (m_pMaterial)
{
if (si.pTextureSizer)
{
m_pMaterial->GetTextureMemoryUsage(si.pTextureSizer);
}
if (si.pTextureSizer2)
{
m_pMaterial->GetTextureMemoryUsage(si.pTextureSizer2);
}
}
}