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.
1452 lines
52 KiB
C++
1452 lines
52 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.
|
|
|
|
// Description : Octree parts used on Jobs to prevent scanning too many files for micro functions
|
|
|
|
#include "Cry3DEngine_precompiled.h"
|
|
|
|
#include "StatObj.h"
|
|
#include "ObjMan.h"
|
|
#include "VisAreas.h"
|
|
#include "CullBuffer.h"
|
|
#include "3dEngine.h"
|
|
#include "IndexedMesh.h"
|
|
#include "ObjectsTree.h"
|
|
|
|
#include "DecalRenderNode.h"
|
|
#include "FogVolumeRenderNode.h"
|
|
#include "Ocean.h"
|
|
#include "LightEntity.h"
|
|
#include "WaterVolumeRenderNode.h"
|
|
#include "DistanceCloudRenderNode.h"
|
|
#include "Environment/OceanEnvironmentBus.h"
|
|
|
|
#define CHECK_OBJECTS_BOX_WARNING_SIZE (1.0e+10f)
|
|
#define fNodeMinSize (8.f)
|
|
#define fObjectToNodeSizeRatio (1.f / 8.f)
|
|
#define fMinShadowCasterViewDist (8.f)
|
|
|
|
namespace LegacyInternal
|
|
{
|
|
// File scoped LegacyJobExecutor instance used to run all RenderContent jobs
|
|
static AZ::LegacyJobExecutor* s_renderContentJobExecutor;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
COctreeNode::COctreeNode(int nSID, const AABB& box, CVisArea* pVisArea, COctreeNode* pParent)
|
|
: m_nOccludedFrameId(0), m_renderFlags(0), m_errTypesBitField(0), m_fObjectsMaxViewDist(0.0f), m_nLastVisFrameId(0)
|
|
, nFillShadowCastersSkipFrameId(0), m_fNodeDistance(0.0f), m_nManageVegetationsFrameId(0)
|
|
, m_bHasLights(0), m_bNodeCompletelyInFrustum(0)
|
|
{
|
|
memset(m_arrChilds, 0, sizeof(m_arrChilds));
|
|
memset(m_arrObjects, 0, sizeof(m_arrObjects));
|
|
memset(&m_lstCasters, 0, sizeof(m_lstCasters));
|
|
|
|
m_pRNTmpData = NULL;
|
|
m_nSID = nSID;
|
|
m_vNodeCenter = box.GetCenter();
|
|
m_vNodeAxisRadius = box.GetSize() * 0.5f;
|
|
m_objectsBox.min = box.max;
|
|
m_objectsBox.max = box.min;
|
|
|
|
#if !defined(_RELEASE)
|
|
// Check if bounding box is crazy
|
|
#define CHECK_OBJECTS_BOX_WARNING_SIZE (1.0e+10f)
|
|
if (GetCVars()->e_CheckOctreeObjectsBoxSize != 0) // pParent is checked as silly sized things are added to the root (e.g. the sun)
|
|
{
|
|
if (pParent && !m_objectsBox.IsReset() && (m_objectsBox.min.len() > CHECK_OBJECTS_BOX_WARNING_SIZE || m_objectsBox.max.len() > CHECK_OBJECTS_BOX_WARNING_SIZE))
|
|
{
|
|
CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR_DBGBRK, "COctreeNode being created with a huge m_objectsBox: [%f %f %f] -> [%f %f %f]\n", m_objectsBox.min.x, m_objectsBox.min.y, m_objectsBox.min.z, m_objectsBox.max.x, m_objectsBox.max.y, m_objectsBox.max.z);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
m_pVisArea = pVisArea;
|
|
m_pParent = pParent;
|
|
|
|
m_fpSunDirX = 63;
|
|
m_fpSunDirZ = 0;
|
|
m_fpSunDirYs = 0;
|
|
|
|
m_pStaticInstancingInfo = nullptr;
|
|
m_bStaticInstancingIsDirty = false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
COctreeNode* COctreeNode::Create(int nSID, const AABB& box, struct CVisArea* pVisArea, COctreeNode* pParent)
|
|
{
|
|
return new COctreeNode(nSID, box, pVisArea, pParent);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool COctreeNode::HasObjects()
|
|
{
|
|
for (int l = 0; l < eRNListType_ListsNum; l++)
|
|
{
|
|
if (m_arrObjects[l].m_pFirstNode)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::RenderContent(int nRenderMask, const SRenderingPassInfo& passInfo, const SRendItemSorter& rendItemSorter, const CCamera* pCam)
|
|
{
|
|
if (GetCVars()->e_StatObjBufferRenderTasks == 1 && passInfo.IsGeneralPass())
|
|
{
|
|
GetObjManager()->AddCullJobProducer();
|
|
}
|
|
|
|
if (!LegacyInternal::s_renderContentJobExecutor)
|
|
{
|
|
LegacyInternal::s_renderContentJobExecutor = new AZ::LegacyJobExecutor;
|
|
}
|
|
|
|
LegacyInternal::s_renderContentJobExecutor->StartJob(
|
|
[this, nRenderMask, passInfo, rendItemSorter, pCam]
|
|
{
|
|
this->RenderContentJobEntry(nRenderMask, passInfo, rendItemSorter, pCam);
|
|
}
|
|
);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::Shutdown()
|
|
{
|
|
WaitForContentJobCompletion();
|
|
}
|
|
|
|
void COctreeNode::WaitForContentJobCompletion()
|
|
{
|
|
//Deleting it calls WaitForCompletion(), and the next call to RenderContent() will create a new instance
|
|
delete LegacyInternal::s_renderContentJobExecutor;
|
|
LegacyInternal::s_renderContentJobExecutor = nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::RenderContentJobEntry(int nRenderMask, const SRenderingPassInfo& passInfo, SRendItemSorter rendItemSorter, const CCamera* pCam)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ThreeDEngine);
|
|
|
|
SSectorTextureSet* pTerrainTexInfo = NULL;
|
|
|
|
if (m_arrObjects[eRNListType_DecalsAndRoads].m_pFirstNode && (passInfo.RenderDecals()))
|
|
{
|
|
RenderDecalsAndRoads(&m_arrObjects[eRNListType_DecalsAndRoads], *pCam, nRenderMask, m_bNodeCompletelyInFrustum != 0, pTerrainTexInfo, passInfo, rendItemSorter);
|
|
}
|
|
|
|
if (m_arrObjects[eRNListType_Unknown].m_pFirstNode)
|
|
{
|
|
RenderCommonObjects(&m_arrObjects[eRNListType_Unknown], *pCam, nRenderMask, m_bNodeCompletelyInFrustum != 0, pTerrainTexInfo, passInfo, rendItemSorter);
|
|
}
|
|
|
|
if (GetCVars()->e_StatObjBufferRenderTasks == 1 && passInfo.IsGeneralPass())
|
|
{
|
|
GetObjManager()->RemoveCullJobProducer();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::RenderDecalsAndRoads(TDoublyLinkedList<IRenderNode>* lstObjects, const CCamera& rCam, [[maybe_unused]] int nRenderMask, bool bNodeCompletelyInFrustum, [[maybe_unused]] SSectorTextureSet* pTerrainTexInfo, const SRenderingPassInfo& passInfo, SRendItemSorter& rendItemSorter)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
CVars* pCVars = GetCVars();
|
|
AABB objBox;
|
|
const Vec3 vCamPos = rCam.GetPosition();
|
|
|
|
bool bCheckPerObjectOcclusion = true;
|
|
|
|
for (IRenderNode* pObj = lstObjects->m_pFirstNode, * pNext; pObj; pObj = pNext)
|
|
{
|
|
rendItemSorter.IncreaseObjectCounter();
|
|
pNext = pObj->m_pNext;
|
|
|
|
if (pObj->m_pNext)
|
|
{
|
|
cryPrefetchT0SSE(pObj->m_pNext);
|
|
}
|
|
|
|
IF (pObj->m_dwRndFlags & ERF_HIDDEN, 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pObj->FillBBox(objBox);
|
|
|
|
if (bNodeCompletelyInFrustum || rCam.IsAABBVisible_FM(objBox))
|
|
{
|
|
float fEntDistance = sqrt_tpl(Distance::Point_AABBSq(vCamPos, objBox)) * passInfo.GetZoomFactor();
|
|
assert(fEntDistance >= 0 && _finite(fEntDistance));
|
|
if (fEntDistance < pObj->m_fWSMaxViewDist)
|
|
{
|
|
#if !defined(_RELEASE)
|
|
EERType rnType = pObj->GetRenderNodeType();
|
|
if (!passInfo.RenderDecals() && rnType == eERType_Decal)
|
|
{
|
|
continue;
|
|
}
|
|
#endif // _RELEASE
|
|
|
|
if (pCVars->e_StatObjBufferRenderTasks == 1 && passInfo.IsGeneralPass())
|
|
{
|
|
// if object is visible, write to output queue for main thread processing
|
|
if (GetObjManager()->CheckOcclusion_TestAABB(objBox, fEntDistance))
|
|
{
|
|
GetObjManager()->PushIntoCullOutputQueue(SCheckOcclusionOutput::CreateDecalsAndRoadsOutput(pObj, objBox, fEntDistance, bCheckPerObjectOcclusion, rendItemSorter));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetObjManager()->RenderDecalAndRoad(pObj, objBox, fEntDistance, bCheckPerObjectOcclusion, passInfo, rendItemSorter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::RenderCommonObjects(TDoublyLinkedList<IRenderNode>* lstObjects, const CCamera& rCam, int nRenderMask, bool bNodeCompletelyInFrustum, SSectorTextureSet* pTerrainTexInfo, const SRenderingPassInfo& passInfo, SRendItemSorter& rendItemSorter)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
CVars* pCVars = GetCVars();
|
|
AABB objBox;
|
|
const Vec3 vCamPos = rCam.GetPosition();
|
|
|
|
for (IRenderNode* pObj = lstObjects->m_pFirstNode, * pNext; pObj; pObj = pNext)
|
|
{
|
|
rendItemSorter.IncreaseObjectCounter();
|
|
pNext = pObj->m_pNext;
|
|
|
|
if (pObj->m_pNext)
|
|
{
|
|
cryPrefetchT0SSE(pObj->m_pNext);
|
|
}
|
|
|
|
IF (pObj->m_dwRndFlags & ERF_HIDDEN, 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pObj->FillBBox(objBox);
|
|
EERType rnType = pObj->GetRenderNodeType();
|
|
|
|
if (bNodeCompletelyInFrustum || rCam.IsAABBVisible_FM(objBox))
|
|
{
|
|
float fEntDistance = sqrt_tpl(Distance::Point_AABBSq(vCamPos, objBox)) * passInfo.GetZoomFactor();
|
|
assert(fEntDistance >= 0 && _finite(fEntDistance));
|
|
if (fEntDistance < pObj->m_fWSMaxViewDist)
|
|
{
|
|
if (nRenderMask & OCTREENODE_RENDER_FLAG_OBJECTS_ONLY_ENTITIES)
|
|
{
|
|
if (rnType != eERType_RenderComponent && rnType != eERType_DynamicMeshRenderComponent && rnType != eERType_SkinnedMeshRenderComponent)
|
|
{
|
|
if (rnType == eERType_Light)
|
|
{
|
|
CLightEntity* pEnt = (CLightEntity*)pObj;
|
|
if (!pEnt->GetEntityVisArea() && !(pEnt->m_light.m_Flags & DLF_THIS_AREA_ONLY))
|
|
{ // not "this area only" outdoor light affects everything
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rnType == eERType_Light)
|
|
{
|
|
bool bLightVisible = true;
|
|
CLightEntity* pLightEnt = (CLightEntity*)pObj;
|
|
|
|
// first check against camera view frustum
|
|
CDLight* pLight = &pLightEnt->m_light;
|
|
if (pLight->m_Flags & DLF_DEFERRED_CUBEMAPS)
|
|
{
|
|
OBB obb(OBB::CreateOBBfromAABB(Matrix33(pLight->m_ObjMatrix), AABB(-pLight->m_ProbeExtents, pLight->m_ProbeExtents)));
|
|
bLightVisible = passInfo.GetCamera().IsOBBVisible_F(pLight->m_Origin, obb);
|
|
}
|
|
else if (pLightEnt->m_light.m_Flags & DLF_AREA_LIGHT)
|
|
{
|
|
// OBB test for area lights.
|
|
Vec3 vBoxMax(pLight->m_fBaseRadius, pLight->m_fBaseRadius + pLight->m_fAreaWidth, pLight->m_fBaseRadius + pLight->m_fAreaHeight);
|
|
Vec3 vBoxMin(-0.1f, -(pLight->m_fBaseRadius + pLight->m_fAreaWidth), -(pLight->m_fBaseRadius + pLight->m_fAreaHeight));
|
|
|
|
OBB obb(OBB::CreateOBBfromAABB(Matrix33(pLight->m_ObjMatrix), AABB(vBoxMin, vBoxMax)));
|
|
bLightVisible = rCam.IsOBBVisible_F(pLight->m_Origin, obb);
|
|
}
|
|
else
|
|
{
|
|
bLightVisible = rCam.IsSphereVisible_F(Sphere(pLight->m_BaseOrigin, pLight->m_fBaseRadius));
|
|
}
|
|
|
|
if (!bLightVisible)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (pCVars->e_StatObjBufferRenderTasks == 1 && passInfo.IsGeneralPass())
|
|
{
|
|
// If object is visible
|
|
if (rnType == eERType_DistanceCloud || GetObjManager()->CheckOcclusion_TestAABB(objBox, fEntDistance))
|
|
{
|
|
if (pObj->CanExecuteRenderAsJob())
|
|
{
|
|
// If object can be executed as a job, call RenderObject directly from this job
|
|
GetObjManager()->RenderObject(pObj, objBox, fEntDistance, eERType_RenderComponent, passInfo, rendItemSorter);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise, write to output queue for main thread processing
|
|
GetObjManager()->PushIntoCullOutputQueue(SCheckOcclusionOutput::CreateCommonObjectOutput(pObj, objBox, fEntDistance, pTerrainTexInfo, rendItemSorter));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GetObjManager()->RenderObject(pObj, objBox, fEntDistance, rnType, passInfo, rendItemSorter);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::UnlinkObject(IRenderNode* pObj)
|
|
{
|
|
ERNListType eListType = pObj->GetRenderNodeListId(pObj->GetRenderNodeType());
|
|
|
|
assert(eListType >= 0 && eListType < eRNListType_ListsNum);
|
|
TDoublyLinkedList<IRenderNode>& rList = m_arrObjects[eListType];
|
|
|
|
assert(pObj != pObj->m_pPrev && pObj != pObj->m_pNext);
|
|
assert(!rList.m_pFirstNode || !rList.m_pFirstNode->m_pPrev);
|
|
assert(!rList.m_pLastNode || !rList.m_pLastNode->m_pNext);
|
|
|
|
if (pObj->m_pNext || pObj->m_pPrev || pObj == rList.m_pLastNode || pObj == rList.m_pFirstNode)
|
|
{
|
|
rList.remove(pObj);
|
|
}
|
|
|
|
assert(!rList.m_pFirstNode || !rList.m_pFirstNode->m_pPrev);
|
|
assert(!rList.m_pLastNode || !rList.m_pLastNode->m_pNext);
|
|
assert(pObj != pObj->m_pPrev && pObj != pObj->m_pNext);
|
|
assert(!pObj->m_pNext && !pObj->m_pPrev);
|
|
}
|
|
|
|
bool COctreeNode::DeleteObject(IRenderNode* pObj)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
if (pObj->m_pOcNode && pObj->m_pOcNode != this)
|
|
{
|
|
return ((COctreeNode*)(pObj->m_pOcNode))->DeleteObject(pObj);
|
|
}
|
|
|
|
UnlinkObject(pObj);
|
|
|
|
if (m_removeVegetationCastersOneByOne)
|
|
{
|
|
for (int i = 0; i < m_lstCasters.Count(); i++)
|
|
{
|
|
if (m_lstCasters[i].pNode == pObj)
|
|
{
|
|
m_lstCasters.Delete(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
C3DEngine* p3DEngine = Get3DEngine();
|
|
bool bSafeToUse = p3DEngine ? p3DEngine->IsObjectTreeReady() : false;
|
|
|
|
pObj->m_pOcNode = NULL;
|
|
pObj->m_nSID = -1;
|
|
|
|
if (bSafeToUse && IsEmpty() && m_arrEmptyNodes.Find(this) < 0)
|
|
{
|
|
m_arrEmptyNodes.Add(this);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::InsertObject(IRenderNode* pObj, const AABB& objBox, const float fObjRadiusSqr, const Vec3& vObjCenter)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ThreeDEngineDetailed);
|
|
|
|
COctreeNode* pCurrentNode = this;
|
|
|
|
const EERType eType = pObj->GetRenderNodeType();
|
|
const uint32 renderFlags = (pObj->GetRndFlags() & (ERF_GOOD_OCCLUDER | ERF_CASTSHADOWMAPS | ERF_HAS_CASTSHADOWMAPS));
|
|
|
|
const bool bTypeLight = (eType == eERType_Light);
|
|
const float fWSMaxViewDist = pObj->m_fWSMaxViewDist;
|
|
|
|
Vec3 vObjectCentre = vObjCenter;
|
|
|
|
while (true)
|
|
{
|
|
PrefetchLine(&pCurrentNode->m_arrChilds[0], 0);
|
|
|
|
#if !defined(_RELEASE)
|
|
if (GetCVars()->e_CheckOctreeObjectsBoxSize != 0) // pCurrentNode->m_pParent is checked as silly sized things are added to the root (e.g. the sun)
|
|
{
|
|
if (pCurrentNode->m_pParent && !objBox.IsReset() && (objBox.min.len() > CHECK_OBJECTS_BOX_WARNING_SIZE || objBox.max.len() > CHECK_OBJECTS_BOX_WARNING_SIZE))
|
|
{
|
|
CryWarning(VALIDATOR_MODULE_3DENGINE, VALIDATOR_ERROR_DBGBRK, "Huge object being added to a COctreeNode, name: '%s', objBox: [%f %f %f] -> [%f %f %f]\n", pObj->GetName(), objBox.min.x, objBox.min.y, objBox.min.z, objBox.max.x, objBox.max.y, objBox.max.z);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// parent bbox includes all children
|
|
pCurrentNode->m_objectsBox.Add(objBox);
|
|
|
|
pCurrentNode->m_fObjectsMaxViewDist = max(pCurrentNode->m_fObjectsMaxViewDist, fWSMaxViewDist);
|
|
|
|
pCurrentNode->m_renderFlags |= renderFlags;
|
|
|
|
pCurrentNode->m_bHasLights |= (bTypeLight);
|
|
|
|
if (pCurrentNode->m_vNodeAxisRadius.x * 2.0f > fNodeMinSize) // store voxels and roads in root
|
|
{
|
|
float nodeRadius = sqrt(pCurrentNode->GetNodeRadius2());
|
|
|
|
if (fObjRadiusSqr < sqr(nodeRadius * fObjectToNodeSizeRatio))
|
|
{
|
|
int nChildId =
|
|
((vObjCenter.x > pCurrentNode->m_vNodeCenter.x) ? 4 : 0) |
|
|
((vObjCenter.y > pCurrentNode->m_vNodeCenter.y) ? 2 : 0) |
|
|
((vObjCenter.z > pCurrentNode->m_vNodeCenter.z) ? 1 : 0);
|
|
|
|
if (!pCurrentNode->m_arrChilds[nChildId])
|
|
{
|
|
pCurrentNode->m_arrChilds[nChildId] = COctreeNode::Create(pCurrentNode->m_nSID, pCurrentNode->GetChildBBox(nChildId), pCurrentNode->m_pVisArea, pCurrentNode);
|
|
}
|
|
|
|
pCurrentNode = pCurrentNode->m_arrChilds[nChildId];
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
//disable as it leads to some corruption on 360
|
|
// PrefetchLine(&pObj->m_pOcNode, 0); //Writing to m_pOcNode was a common L2 cache miss
|
|
|
|
pCurrentNode->LinkObject(pObj, eType);
|
|
|
|
pObj->m_pOcNode = pCurrentNode;
|
|
pObj->m_nSID = pCurrentNode->m_nSID;
|
|
|
|
// only mark octree nodes as not compiled during loading and in the editor
|
|
// otherwise update node (and parent node) flags on per added object basis
|
|
if (m_bLevelLoadingInProgress || gEnv->IsEditor())
|
|
{
|
|
// Do nothing
|
|
}
|
|
else
|
|
{
|
|
pCurrentNode->UpdateObjects(pObj);
|
|
}
|
|
|
|
pCurrentNode->m_nManageVegetationsFrameId = 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
AABB COctreeNode::GetChildBBox(int nChildId)
|
|
{
|
|
int x = (nChildId / 4);
|
|
int y = (nChildId - x * 4) / 2;
|
|
int z = (nChildId - x * 4 - y * 2);
|
|
const Vec3& vSize = m_vNodeAxisRadius;
|
|
Vec3 vOffset = vSize;
|
|
vOffset.x *= x;
|
|
vOffset.y *= y;
|
|
vOffset.z *= z;
|
|
AABB childBox;
|
|
childBox.min = m_vNodeCenter - vSize + vOffset;
|
|
childBox.max = childBox.min + vSize;
|
|
return childBox;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool COctreeNode::IsEmpty()
|
|
{
|
|
if (m_pParent)
|
|
{
|
|
if (!m_arrChilds[0] && !m_arrChilds[1] && !m_arrChilds[2] && !m_arrChilds[3])
|
|
{
|
|
if (!m_arrChilds[4] && !m_arrChilds[5] && !m_arrChilds[6] && !m_arrChilds[7])
|
|
{
|
|
if (!HasObjects())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool COctreeNode::IsRightNode(const AABB& objBox, const float fObjRadiusSqr, [[maybe_unused]] float fObjMaxViewDist)
|
|
{
|
|
const AABB& nodeBox = GetNodeBox();
|
|
if (!Overlap::Point_AABB(objBox.GetCenter(), nodeBox))
|
|
{
|
|
if (m_pParent)
|
|
{
|
|
return false; // fail if center is not inside or node bbox
|
|
}
|
|
}
|
|
if (2 != Overlap::AABB_AABB_Inside(objBox, m_objectsBox))
|
|
{
|
|
return false; // fail if not completely inside of objects bbox
|
|
}
|
|
float fNodeRadiusRated = GetNodeRadius2() * sqr(fObjectToNodeSizeRatio);
|
|
|
|
if (fObjRadiusSqr > fNodeRadiusRated * 4.f)
|
|
{
|
|
if (m_pParent)
|
|
{
|
|
return false; // fail if object is too big and we need to register it some of parents
|
|
}
|
|
}
|
|
if (m_vNodeAxisRadius.x * 2.0f > fNodeMinSize)
|
|
{
|
|
if (fObjRadiusSqr < fNodeRadiusRated)
|
|
{
|
|
// if(fObjMaxViewDist < m_fNodeRadius*GetCVars()->e_ViewDistRatioVegetation*fObjectToNodeSizeRatio)
|
|
return false; // fail if object is too small and we need to register it some of childs
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::LinkObject(IRenderNode* pObj, EERType eERType, bool bPushFront)
|
|
{
|
|
ERNListType eListType = pObj->GetRenderNodeListId(eERType);
|
|
|
|
TDoublyLinkedList<IRenderNode>& rList = m_arrObjects[eListType];
|
|
|
|
assert(pObj != pObj->m_pPrev && pObj != pObj->m_pNext);
|
|
assert(!pObj->m_pNext && !pObj->m_pPrev);
|
|
assert(!rList.m_pFirstNode || !rList.m_pFirstNode->m_pPrev);
|
|
assert(!rList.m_pLastNode || !rList.m_pLastNode->m_pNext);
|
|
|
|
if (bPushFront)
|
|
{
|
|
rList.insertBeginning(pObj);
|
|
}
|
|
else
|
|
{
|
|
rList.insertEnd(pObj);
|
|
}
|
|
|
|
assert(!rList.m_pFirstNode || !rList.m_pFirstNode->m_pPrev);
|
|
assert(!rList.m_pLastNode || !rList.m_pLastNode->m_pNext);
|
|
assert(pObj != pObj->m_pPrev && pObj != pObj->m_pNext);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::UpdateObjects(IRenderNode* pObj)
|
|
{
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::ThreeDEngineDetailed);
|
|
|
|
float fObjMaxViewDistance = 0;
|
|
size_t numCasters = 0;
|
|
IObjManager* pObjManager = GetObjManager();
|
|
|
|
bool bVegetHasAlphaTrans = false;
|
|
int nFlags = pObj->GetRndFlags();
|
|
EERType eRType = pObj->GetRenderNodeType();
|
|
|
|
IF (nFlags & ERF_HIDDEN, 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
I3DEngine* p3DEngine = GetISystem()->GetI3DEngine();
|
|
const Vec3& sunDir = p3DEngine->GetSunDirNormalized();
|
|
uint32 sunDirX = (uint32)(sunDir.x * 63.5f + 63.5f);
|
|
uint32 sunDirZ = (uint32)(sunDir.z * 63.5f + 63.5f);
|
|
uint32 sunDirYs = (uint32)(sunDir.y < 0.0f ? 1 : 0);
|
|
|
|
pObj->m_nInternalFlags &= ~(IRenderNode::REQUIRES_FORWARD_RENDERING | IRenderNode::REQUIRES_NEAREST_CUBEMAP);
|
|
|
|
// update max view distances
|
|
const float fNewMaxViewDist = pObj->GetMaxViewDist();
|
|
pObj->m_fWSMaxViewDist = fNewMaxViewDist;
|
|
|
|
if (eRType != eERType_Light && eRType != eERType_Cloud && eRType != eERType_FogVolume && eRType != eERType_Decal && eRType != eERType_DistanceCloud)
|
|
{
|
|
{
|
|
CMatInfo* pMatInfo = (CMatInfo*)pObj->GetMaterial().get();
|
|
if (pMatInfo)
|
|
{
|
|
if (bVegetHasAlphaTrans || pMatInfo->IsForwardRenderingRequired())
|
|
{
|
|
pObj->m_nInternalFlags |= IRenderNode::REQUIRES_FORWARD_RENDERING;
|
|
}
|
|
|
|
if (pMatInfo->IsNearestCubemapRequired())
|
|
{
|
|
pObj->m_nInternalFlags |= IRenderNode::REQUIRES_NEAREST_CUBEMAP;
|
|
}
|
|
}
|
|
|
|
if (eRType == eERType_RenderComponent || eRType == eERType_DynamicMeshRenderComponent || eRType == eERType_SkinnedMeshRenderComponent)
|
|
{
|
|
int nSlotCount = pObj->GetSlotCount();
|
|
|
|
for (int s = 0; s < nSlotCount; s++)
|
|
{
|
|
if (CMatInfo* pMat = (CMatInfo*)pObj->GetEntitySlotMaterial(s).get())
|
|
{
|
|
if (pMat->IsForwardRenderingRequired())
|
|
{
|
|
pObj->m_nInternalFlags |= IRenderNode::REQUIRES_FORWARD_RENDERING;
|
|
}
|
|
if (pMat->IsNearestCubemapRequired())
|
|
{
|
|
pObj->m_nInternalFlags |= IRenderNode::REQUIRES_NEAREST_CUBEMAP;
|
|
}
|
|
}
|
|
|
|
if (IStatObj* pStatObj = pObj->GetEntityStatObj(s))
|
|
{
|
|
if (CMatInfo* pMat = (CMatInfo*)pStatObj->GetMaterial().get())
|
|
{
|
|
if (pMat->IsForwardRenderingRequired())
|
|
{
|
|
pObj->m_nInternalFlags |= IRenderNode::REQUIRES_FORWARD_RENDERING;
|
|
}
|
|
if (pMat->IsNearestCubemapRequired())
|
|
{
|
|
pObj->m_nInternalFlags |= IRenderNode::REQUIRES_NEAREST_CUBEMAP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bUpdateParentShadowFlags = false;
|
|
bool bUpdateParentOcclusionFlags = false;
|
|
|
|
// fill shadow casters list
|
|
const bool bHasPerObjectShadow = GetCVars()->e_ShadowsPerObject && p3DEngine->GetPerObjectShadow(pObj);
|
|
if (nFlags & ERF_CASTSHADOWMAPS && fNewMaxViewDist > fMinShadowCasterViewDist && eRType != eERType_Light && !bHasPerObjectShadow)
|
|
{
|
|
bUpdateParentShadowFlags = true;
|
|
|
|
float fMaxCastDist = fNewMaxViewDist * GetCVars()->e_ShadowsCastViewDistRatio;
|
|
m_lstCasters.Add(SCasterInfo(pObj, fMaxCastDist, eRType));
|
|
}
|
|
|
|
fObjMaxViewDistance = max(fObjMaxViewDistance, fNewMaxViewDist);
|
|
|
|
// traverse the octree upwards and fill in new flags
|
|
COctreeNode* pNode = this;
|
|
bool bContinue = false;
|
|
do
|
|
{
|
|
// update max view dist
|
|
if (pNode->m_fObjectsMaxViewDist < fObjMaxViewDistance)
|
|
{
|
|
pNode->m_fObjectsMaxViewDist = fObjMaxViewDistance;
|
|
bContinue = true;
|
|
}
|
|
|
|
// update shadow flags
|
|
if (bUpdateParentShadowFlags && (pNode->m_renderFlags & ERF_CASTSHADOWMAPS) == 0)
|
|
{
|
|
pNode->m_renderFlags |= ERF_CASTSHADOWMAPS | ERF_HAS_CASTSHADOWMAPS;
|
|
bContinue = true;
|
|
}
|
|
|
|
pNode = pNode->m_pParent;
|
|
} while (pNode != NULL && bContinue);
|
|
|
|
m_fpSunDirX = sunDirX;
|
|
m_fpSunDirZ = sunDirZ;
|
|
m_fpSunDirYs = sunDirYs;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int16 CObjManager::GetNearestCubeProbe(IVisArea* pVisArea, const AABB& objBox, bool bSpecular)
|
|
{
|
|
// Only used for alpha blended geometry - but still should be optimized further
|
|
float fMinDistance = FLT_MAX;
|
|
int nMaxPriority = -1;
|
|
CLightEntity* pNearestLight = NULL;
|
|
int16 nDefaultId = Get3DEngine()->GetBlackCMTexID();
|
|
|
|
if (!pVisArea)
|
|
{
|
|
if (Get3DEngine()->IsObjectTreeReady())
|
|
{
|
|
Get3DEngine()->GetObjectTree()->GetNearestCubeProbe(fMinDistance, nMaxPriority, pNearestLight, &objBox);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Get3DEngine()->GetVisAreaManager()->GetNearestCubeProbe(fMinDistance, nMaxPriority, pNearestLight, &objBox);
|
|
}
|
|
|
|
if (pNearestLight)
|
|
{
|
|
ITexture* pTexCM = bSpecular ? pNearestLight->m_light.GetSpecularCubemap() : pNearestLight->m_light.GetDiffuseCubemap();
|
|
// Return cubemap ID or -1 if invalid
|
|
return (pTexCM && pTexCM->GetTextureType() >= eTT_Cube) ? pTexCM->GetTextureID() : nDefaultId;
|
|
}
|
|
|
|
// No cubemap found
|
|
return nDefaultId;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CObjManager::IsAfterWater(const Vec3& vPos, const SRenderingPassInfo& passInfo)
|
|
{
|
|
// considered "after water" if any of the following is true:
|
|
// - Position & camera are on the same side of the water surface on recursive level 0
|
|
// - Position & camera are on opposite sides of the water surface on recursive level 1
|
|
|
|
float fWaterLevel;
|
|
if (OceanToggle::IsActive())
|
|
{
|
|
if (!OceanRequest::OceanIsEnabled())
|
|
{
|
|
return true;
|
|
}
|
|
fWaterLevel = OceanRequest::GetOceanLevel();
|
|
}
|
|
else
|
|
{
|
|
fWaterLevel = GetOcean() ? GetOcean()->GetWaterLevel() : WATER_LEVEL_UNKNOWN;
|
|
}
|
|
|
|
return (0.5f - passInfo.GetRecursiveLevel()) * (0.5f - passInfo.IsCameraUnderWater()) * (vPos.z - fWaterLevel) > 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::RenderObjectDebugInfo(IRenderNode* pEnt, float fEntDistance, const SRenderingPassInfo& passInfo)
|
|
{
|
|
if (!passInfo.IsGeneralPass())
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_arrRenderDebugInfo.push_back(SObjManRenderDebugInfo(pEnt, fEntDistance));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::RemoveCullJobProducer()
|
|
{
|
|
m_CheckOcclusionOutputQueue.RemoveProducer();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::AddCullJobProducer()
|
|
{
|
|
m_CheckOcclusionOutputQueue.AddProducer();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CObjManager::CheckOcclusion_TestAABB(const AABB& rAABB, float fEntDistance)
|
|
{
|
|
return m_CullThread.TestAABB(rAABB, fEntDistance);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CObjManager::CheckOcclusion_TestQuad(const Vec3& vCenter, const Vec3& vAxisX, const Vec3& vAxisY)
|
|
{
|
|
return m_CullThread.TestQuad(vCenter, vAxisX, vAxisY);
|
|
}
|
|
|
|
#ifndef _RELEASE
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::CoverageBufferDebugDraw()
|
|
{
|
|
m_CullThread.CoverageBufferDebugDraw();
|
|
}
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CObjManager::LoadOcclusionMesh(const char* pFileName)
|
|
{
|
|
return m_CullThread.LoadLevel(pFileName);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::PushIntoCullQueue(const SCheckOcclusionJobData& rCheckOcclusionData)
|
|
{
|
|
#if !defined(_RELEASE)
|
|
IF (!m_CullThread.IsActive(), 0)
|
|
{
|
|
__debugbreak();
|
|
}
|
|
IF (rCheckOcclusionData.type == SCheckOcclusionJobData::QUIT, 0)
|
|
{
|
|
m_CullThread.SetActive(false);
|
|
}
|
|
#endif
|
|
// Prevent our queue from filling up, and make sure to always leave room for the "QUIT" message.
|
|
// If we try to add nodes to a full queue, it's possible for deadlocks to occur. The CheckOcclusionQueue
|
|
// is filled from the main thread, and will block if the queue is full. The queue is emptied from a culling
|
|
// thread, but the CheckOcclusionOutputQueue is filled from the culling thread and will block if *its* queue is
|
|
// full. The main thread is the one that empties the output queue thread, so queues that are full on both sides,
|
|
// mixed with bad timing, can cause a deadlock. Such are the perils of lockless fixed size queues. :(
|
|
// Rather than locking up, we will instead emit a warning and over-cull by not even submitting our geometry for
|
|
// potential rendering.
|
|
if ((m_CheckOcclusionQueue.FreeCount() > 1) ||
|
|
(rCheckOcclusionData.type == SCheckOcclusionJobData::QUIT))
|
|
{
|
|
m_CheckOcclusionQueue.Push(rCheckOcclusionData);
|
|
}
|
|
else
|
|
{
|
|
// If this warning is hit in the editor, it's likely because of editing terrain. Edited terrain draws at the highest
|
|
// LOD, which means you'll need to set this to (heightmap height * width) / (32 * 32) at a bare minimum to have a
|
|
// large enough buffer. It will need to be even larger if you have significant amounts of static geometry in the level too.
|
|
// If this warning is hit in-game, you'll just need to use trial and error to determine a large enough size.
|
|
AZ_Warning("Cull", false,
|
|
"Occlusion Queue is full - need to set the e_CheckOcclusionQueueSize CVar value larger (current value = %u).",
|
|
m_CheckOcclusionQueue.BufferSize());
|
|
}
|
|
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::PopFromCullQueue(SCheckOcclusionJobData* pCheckOcclusionData)
|
|
{
|
|
m_CheckOcclusionQueue.Pop(pCheckOcclusionData);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjManager::PushIntoCullOutputQueue(const SCheckOcclusionOutput& rCheckOcclusionOutput)
|
|
{
|
|
// Prevent our output queue from filling up. If we try to add nodes to a full queue, it's possible for deadlocks
|
|
// to occur. (See explanation in PushIntoCullQueue() above)
|
|
// Rather than locking up, we will instead emit a warning and over-cull by not even submitting our geometry for
|
|
// potential rendering.
|
|
if (m_CheckOcclusionOutputQueue.FreeCount() > 0)
|
|
{
|
|
m_CheckOcclusionOutputQueue.Push(rCheckOcclusionOutput);
|
|
}
|
|
else
|
|
{
|
|
// If this warning is hit in the editor, it's likely because of editing terrain. This should likely be set to 2x to 4x the
|
|
// size of e_CheckOcclusionQueueSize.
|
|
// If this warning is hit in-game, you'll just need to use trial and error to determine a large enough size.
|
|
AZ_Warning("Cull", false,
|
|
"Occlusion Output Queue is full - need to set the e_CheckOcclusionOutputQueueSize CVar value larger (current value = %u).",
|
|
m_CheckOcclusionOutputQueue.BufferSize());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CObjManager::PopFromCullOutputQueue(SCheckOcclusionOutput* pCheckOcclusionOutput)
|
|
{
|
|
return m_CheckOcclusionOutputQueue.Pop(pCheckOcclusionOutput);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
uint8 CObjManager::GetDissolveRef(float fDist, float fMVD)
|
|
{
|
|
float fDissolveDist = 1.0f / CLAMP(0.1f * fMVD, GetFloatCVar(e_DissolveDistMin), GetFloatCVar(e_DissolveDistMax));
|
|
|
|
return (uint8)SATURATEB((1.0f + (fDist - fMVD) * fDissolveDist) * 255.f);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CObjManager::GetLodDistDissolveRef(SLodDistDissolveTransitionState* pState, float curDist, int nNewLod, [[maybe_unused]] const SRenderingPassInfo& passInfo)
|
|
{
|
|
float fDissolveDistbandClamped = min(GetFloatCVar(e_DissolveDistband), curDist * .4f) + 0.001f;
|
|
|
|
if (!pState->fStartDist)
|
|
{
|
|
pState->fStartDist = curDist;
|
|
pState->nOldLod = nNewLod;
|
|
pState->nNewLod = nNewLod;
|
|
|
|
pState->bFarside = (pState->nNewLod < pState->nOldLod && pState->nNewLod != -1) || pState->nOldLod == -1;
|
|
}
|
|
else if (pState->nNewLod != nNewLod)
|
|
{
|
|
pState->nNewLod = nNewLod;
|
|
pState->fStartDist = curDist;
|
|
|
|
pState->bFarside = (pState->nNewLod < pState->nOldLod && pState->nNewLod != -1) || pState->nOldLod == -1;
|
|
}
|
|
else if ((pState->nOldLod != pState->nNewLod))
|
|
{
|
|
// transition complete
|
|
if (
|
|
(!pState->bFarside && curDist - pState->fStartDist > fDissolveDistbandClamped) ||
|
|
(pState->bFarside && pState->fStartDist - curDist > fDissolveDistbandClamped)
|
|
)
|
|
{
|
|
pState->nOldLod = pState->nNewLod;
|
|
}
|
|
// with distance based transitions we can always 'fail' back to the previous LOD.
|
|
else if (
|
|
(!pState->bFarside && curDist < pState->fStartDist) ||
|
|
(pState->bFarside && curDist > pState->fStartDist)
|
|
)
|
|
{
|
|
pState->nNewLod = pState->nOldLod;
|
|
}
|
|
}
|
|
|
|
if (pState->nOldLod == pState->nNewLod)
|
|
{
|
|
return 0.f;
|
|
}
|
|
else
|
|
{
|
|
if (pState->bFarside)
|
|
{
|
|
return SATURATE(((pState->fStartDist - curDist) * (1.f / fDissolveDistbandClamped)));
|
|
}
|
|
else
|
|
{
|
|
return SATURATE(((curDist - pState->fStartDist) * (1.f / fDissolveDistbandClamped)));
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
int CObjManager::GetObjectLOD(const IRenderNode* pObj, float fDistance)
|
|
{
|
|
SFrameLodInfo frameLodInfo = Get3DEngine()->GetFrameLodInfo();
|
|
int resultLod = MAX_STATOBJ_LODS_NUM - 1;
|
|
bool boundingBBoxBased = ((pObj->GetRndFlags() & ERF_LOD_BBOX_BASED) != 0);
|
|
// If it is bounding box based, it does not use face area data.
|
|
bool useLodFaceArea = (GetCVars()->e_LodFaceArea != 0) && !boundingBBoxBased;
|
|
|
|
if (useLodFaceArea)
|
|
{
|
|
float distances[SMeshLodInfo::s_nMaxLodCount];
|
|
useLodFaceArea = pObj->GetLodDistances(frameLodInfo, distances);
|
|
if (useLodFaceArea)
|
|
{
|
|
for (uint i = 0; i < MAX_STATOBJ_LODS_NUM - 1; ++i)
|
|
{
|
|
if (fDistance < distances[i])
|
|
{
|
|
resultLod = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!useLodFaceArea)
|
|
{
|
|
const float fLodRatioNorm = pObj->GetLodRatioNormalized();
|
|
const float fRadius = pObj->GetBBox().GetRadius();
|
|
resultLod = (int)(fDistance * (fLodRatioNorm * fLodRatioNorm) / (max(frameLodInfo.fLodRatio * min(fRadius, GetFloatCVar(e_LodCompMaxSize)), 0.001f)));
|
|
}
|
|
|
|
return resultLod;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::GetObjectsByFlags(uint dwFlags, PodArray<IRenderNode*>& lstObjects)
|
|
{
|
|
unsigned int nCurrentObject(eRNListType_First);
|
|
for (nCurrentObject = eRNListType_First; nCurrentObject < eRNListType_ListsNum; ++nCurrentObject)
|
|
{
|
|
for (IRenderNode* pObj = m_arrObjects[nCurrentObject].m_pFirstNode; pObj; pObj = pObj->m_pNext)
|
|
{
|
|
if ((pObj->GetRndFlags() & dwFlags) == dwFlags)
|
|
{
|
|
lstObjects.Add(pObj);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (m_arrChilds[i])
|
|
{
|
|
m_arrChilds[i]->GetObjectsByFlags(dwFlags, lstObjects);
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::GetObjectsByType(PodArray<IRenderNode*>& lstObjects, EERType objType, const AABB* pBBox, ObjectTreeQueryFilterCallback filterCallback)
|
|
{
|
|
if (objType == eERType_Light && !m_bHasLights)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pBBox && !Overlap::AABB_AABB(*pBBox, GetObjectsBBox()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ERNListType eListType = IRenderNode::GetRenderNodeListId(objType);
|
|
|
|
for (IRenderNode* pObj = m_arrObjects[eListType].m_pFirstNode; pObj; pObj = pObj->m_pNext)
|
|
{
|
|
if (pObj->GetRenderNodeType() == objType)
|
|
{
|
|
AABB box;
|
|
pObj->FillBBox(box);
|
|
if (!pBBox || Overlap::AABB_AABB(*pBBox, box))
|
|
{
|
|
// Check the filterCallback to perform a final validation that we want this object
|
|
// to appear in our results list. If there's no filterCallback, then always add
|
|
// the object.
|
|
if (!filterCallback || filterCallback(pObj, objType))
|
|
{
|
|
lstObjects.Add(pObj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (m_arrChilds[i])
|
|
{
|
|
m_arrChilds[i]->GetObjectsByType(lstObjects, objType, pBBox, filterCallback);
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void COctreeNode::GetNearestCubeProbe(float& fMinDistance, int& nMaxPriority, CLightEntity*& pNearestLight, const AABB* pBBox)
|
|
{
|
|
if (!m_bHasLights)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pBBox || pBBox && !Overlap::AABB_AABB(*pBBox, GetObjectsBBox()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vec3 vCenter = pBBox->GetCenter();
|
|
|
|
ERNListType eListType = IRenderNode::GetRenderNodeListId(eERType_Light);
|
|
|
|
for (IRenderNode* pObj = m_arrObjects[eListType].m_pFirstNode; pObj; pObj = pObj->m_pNext)
|
|
{
|
|
if (pObj->GetRenderNodeType() == eERType_Light)
|
|
{
|
|
AABB box;
|
|
pObj->FillBBox(box);
|
|
if (Overlap::AABB_AABB(*pBBox, box))
|
|
{
|
|
CLightEntity* pLightEnt = (CLightEntity*)pObj;
|
|
CDLight* pLight = &pLightEnt->m_light;
|
|
|
|
if (pLight->m_Flags & DLF_DEFERRED_CUBEMAPS)
|
|
{
|
|
Vec3 vCenterRel = vCenter - pLight->GetPosition();
|
|
Vec3 vCenterOBBSpace;
|
|
vCenterOBBSpace.x = pLightEnt->m_Matrix.GetColumn0().GetNormalized().dot(vCenterRel);
|
|
vCenterOBBSpace.y = pLightEnt->m_Matrix.GetColumn1().GetNormalized().dot(vCenterRel);
|
|
vCenterOBBSpace.z = pLightEnt->m_Matrix.GetColumn2().GetNormalized().dot(vCenterRel);
|
|
|
|
// Check if object center is within probe OBB
|
|
Vec3 vProbeExtents = pLight->m_ProbeExtents;
|
|
if (fabs(vCenterOBBSpace.x) < vProbeExtents.x && fabs(vCenterOBBSpace.y) < vProbeExtents.y && fabs(vCenterOBBSpace.z) < vProbeExtents.z)
|
|
{
|
|
if (pLight->m_nSortPriority > nMaxPriority
|
|
&& pLight->m_fProbeAttenuation > 0) // Don't return a probe that is disabled/invisible. In particular this provides better results when lighting particles.
|
|
{
|
|
pNearestLight = (CLightEntity*)pObj;
|
|
nMaxPriority = pLight->m_nSortPriority;
|
|
fMinDistance = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (m_arrChilds[i])
|
|
{
|
|
m_arrChilds[i]->GetNearestCubeProbe(fMinDistance, nMaxPriority, pNearestLight, pBBox);
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
bool CObjManager::IsBoxOccluded(const AABB& objBox,
|
|
[[maybe_unused]] float fDistance,
|
|
OcclusionTestClient* const __restrict pOcclTestVars,
|
|
bool/* bIndoorOccludersOnly*/,
|
|
[[maybe_unused]] EOcclusionObjectType eOcclusionObjectType,
|
|
const SRenderingPassInfo& passInfo)
|
|
{
|
|
// if object was visible during last frames
|
|
const uint32 mainFrameID = passInfo.GetMainFrameID();
|
|
|
|
if (GetCVars()->e_OcclusionLazyHideFrames)
|
|
{
|
|
//This causes massive spikes in draw calls when rotating
|
|
if (pOcclTestVars->nLastVisibleMainFrameID > mainFrameID - GetCVars()->e_OcclusionLazyHideFrames)
|
|
{
|
|
// prevent checking all objects in same frame
|
|
int nId = (int)(UINT_PTR(pOcclTestVars) / 256);
|
|
if ((nId & 7) != (mainFrameID & 7))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// use fast and reliable test right here
|
|
CVisAreaManager* pVisAreaManager = GetVisAreaManager();
|
|
if (GetCVars()->e_OcclusionVolumes && pVisAreaManager && pVisAreaManager->IsOccludedByOcclVolumes(objBox, passInfo))
|
|
{
|
|
#if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
|
|
// do not set the lastOccludedMainFrameID because it is camera agnostic so the main pass might occlude
|
|
// objects that should only be occluded in the render scene to texture pass
|
|
if (!passInfo.IsRenderSceneToTexturePass())
|
|
#endif // if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
|
|
{
|
|
pOcclTestVars->nLastOccludedMainFrameID = mainFrameID;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
|
|
// don't use coverage buffer results if we are checking occlusion for a render to texture camera
|
|
// because the render to texture pass does not currently write to the coverage buffer and
|
|
// the frame IDs will not work correctly.
|
|
if (GetCVars()->e_CoverageBuffer && !passInfo.IsRenderSceneToTexturePass())
|
|
#else
|
|
if (GetCVars()->e_CoverageBuffer)
|
|
#endif // if AZ_RENDER_TO_TEXTURE_GEM_ENABLED
|
|
|
|
{
|
|
return pOcclTestVars->nLastOccludedMainFrameID == mainFrameID - 1;
|
|
}
|
|
|
|
pOcclTestVars->nLastVisibleMainFrameID = mainFrameID;
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CLightEntity::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CLightEntity::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CLightEntity::GetRenderNodeType()
|
|
{
|
|
return eERType_Light;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CLightEntity::GetMaxViewDist()
|
|
{
|
|
if (m_light.m_Flags & DLF_SUN)
|
|
{
|
|
return 10.f * DISTANCE_TO_THE_SUN;
|
|
}
|
|
return max(GetCVars()->e_ViewDistMin, CLightEntity::GetBBox().GetRadius() * GetCVars()->e_ViewDistRatioLights * GetViewDistanceMultiplier());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CLightEntity::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
assert(bWorldOnly);
|
|
return m_light.m_Origin;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void COcean::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = COcean::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType COcean::GetRenderNodeType()
|
|
{
|
|
return eERType_WaterVolume;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 COcean::GetPos(bool) const
|
|
{
|
|
return Vec3(0, 0, 0);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> COcean::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CFogVolumeRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CFogVolumeRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CFogVolumeRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_FogVolume;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CFogVolumeRenderNode::GetMaxViewDist()
|
|
{
|
|
return max(GetCVars()->e_ViewDistMin, CFogVolumeRenderNode::GetBBox().GetRadius() * GetCVars()->e_ViewDistRatio * GetViewDistanceMultiplier());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CFogVolumeRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CFogVolumeRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return 0;
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CDecalRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CDecalRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CDecalRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_Decal;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CDecalRenderNode::GetMaxViewDist()
|
|
{
|
|
return m_decalProperties.m_maxViewDist * GetViewDistanceMultiplier();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CDecalRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CDecalRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CWaterVolumeRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CWaterVolumeRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CWaterVolumeRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_WaterVolume;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CWaterVolumeRenderNode::GetMaxViewDist()
|
|
{
|
|
return max(GetCVars()->e_ViewDistMin, CWaterVolumeRenderNode::GetBBox().GetRadius() * GetCVars()->e_ViewDistRatio * GetViewDistanceMultiplier());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CWaterVolumeRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_center;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CWaterVolumeRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CDistanceCloudRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CDistanceCloudRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CDistanceCloudRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_DistanceCloud;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CDistanceCloudRenderNode::GetMaxViewDist()
|
|
{
|
|
return max(GetCVars()->e_ViewDistMin, CDistanceCloudRenderNode::GetBBox().GetRadius() * GetCVars()->e_ViewDistRatio * GetViewDistanceMultiplier());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CDistanceCloudRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CDistanceCloudRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#if !defined(EXCLUDE_DOCUMENTATION_PURPOSE)
|
|
#include "PrismRenderNode.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CPrismRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CPrismRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CPrismRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_PrismObject;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CPrismRenderNode::GetMaxViewDist()
|
|
{
|
|
return 1000.0f;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CPrismRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_mat.GetTranslation();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CPrismRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#include "VolumeObjectRenderNode.h"
|
|
#include "CloudRenderNode.h"
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CVolumeObjectRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CVolumeObjectRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CVolumeObjectRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_VolumeObject;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
float CVolumeObjectRenderNode::GetMaxViewDist()
|
|
{
|
|
return max(GetCVars()->e_ViewDistMin, CVolumeObjectRenderNode::GetBBox().GetRadius() * GetCVars()->e_ViewDistRatio * GetViewDistanceMultiplier());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
Vec3 CVolumeObjectRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CVolumeObjectRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
void CCloudRenderNode::FillBBox(AABB& aabb)
|
|
{
|
|
aabb = CCloudRenderNode::GetBBox();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
EERType CCloudRenderNode::GetRenderNodeType()
|
|
{
|
|
return eERType_Cloud;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
float CCloudRenderNode::GetMaxViewDist()
|
|
{
|
|
return max(GetCVars()->e_ViewDistMin, CCloudRenderNode::GetBBox().GetRadius() * GetCVars()->e_ViewDistRatio * GetViewDistanceMultiplier());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Vec3 CCloudRenderNode::GetPos([[maybe_unused]] bool bWorldOnly) const
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<IMaterial> CCloudRenderNode::GetMaterial([[maybe_unused]] Vec3* pHitPos)
|
|
{
|
|
return m_pMaterial;
|
|
}
|