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/Editor/Util/KDTree.cpp

573 lines
15 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "KDTree.h"
#include <IStatObj.h>
class KDTreeNode
{
public:
KDTreeNode()
{
pChildren[0] = nullptr;
pChildren[1] = nullptr;
pVertexIndices = nullptr;
}
~KDTreeNode()
{
if (!IsLeaf())
{
if (pChildren[0])
{
delete pChildren[0];
}
if (pChildren[1])
{
delete pChildren[1];
}
}
else if (GetVertexBufferSize() > 1)
{
if (pVertexIndices)
{
delete [] pVertexIndices;
}
}
}
uint32 GetVertexBufferSize() const
{
return nVertexIndexBufferSize;
}
float GetSplitPos() const
{
return splitPos;
}
void SetSplitPos(float pos)
{
splitPos = pos;
}
CKDTree::ESplitAxis GetSplitAxis() const
{
if (splitAxis == 0)
{
return CKDTree::eSA_X;
}
if (splitAxis == 1)
{
return CKDTree::eSA_Y;
}
if (splitAxis == 2)
{
return CKDTree::eSA_Z;
}
return CKDTree::eSA_Invalid;
}
void SetSplitAxis(const CKDTree::ESplitAxis& axis)
{
splitAxis = axis;
}
bool IsLeaf() const
{
return pChildren[0] == nullptr && pChildren[1] == nullptr;
}
KDTreeNode* GetChild(uint32 nIndex) const
{
if (nIndex > 1)
{
return nullptr;
}
return pChildren[nIndex];
}
void SetChild(uint32 nIndex, KDTreeNode* pNode)
{
if (nIndex > 1)
{
return;
}
if (pChildren[nIndex])
{
delete pChildren[nIndex];
}
pChildren[nIndex] = pNode;
}
const AABB& GetBoundBox()
{
return boundbox;
}
void SetBoundBox(const AABB& aabb)
{
boundbox = aabb;
}
void SetVertexIndexBuffer(std::vector<uint32>& vertexInfos)
{
nVertexIndexBufferSize = (uint32)vertexInfos.size();
if (nVertexIndexBufferSize == 0)
{
return;
}
if (nVertexIndexBufferSize == 1)
{
oneIndex = vertexInfos[0];
}
else
{
pVertexIndices = new uint32[nVertexIndexBufferSize];
memcpy(pVertexIndices, &vertexInfos[0], sizeof(uint32) * nVertexIndexBufferSize);
}
}
uint32 GetVertexIndex(uint32 nIndex) const
{
if (GetVertexBufferSize() == 1)
{
return oneIndex & 0x00FFFFFF;
}
return pVertexIndices[nIndex] & 0x00FFFFFF;
}
uint32 GetObjIndex(uint32 nIndex) const
{
if (GetVertexBufferSize() == 1)
{
return (oneIndex & 0xFF000000) >> 24;
}
return (pVertexIndices[nIndex] & 0xFF000000) >> 24;
}
private:
union
{
float splitPos; // Interior
uint32 oneIndex; // Leaf
uint32* pVertexIndices; // Leaf : high 8bits - object index, low 24bits - vertex index
};
union
{
uint32 splitAxis; // Interior
uint32 nVertexIndexBufferSize; // Leaf
};
AABB boundbox; // Both
KDTreeNode* pChildren[2]; // Interior
};
CKDTree::ESplitAxis SearchForBestSplitAxis(const AABB& aabb)
{
float xsize = aabb.max.x - aabb.min.x;
float ysize = aabb.max.y - aabb.min.y;
float zsize = aabb.max.z - aabb.min.z;
CKDTree::ESplitAxis axis;
if (xsize > ysize && xsize > zsize)
{
axis = CKDTree::eSA_X;
}
else if (ysize > zsize && ysize > xsize)
{
axis = CKDTree::eSA_Y;
}
else
{
axis = CKDTree::eSA_Z;
}
return axis;
}
bool SearchForBestSplitPos(CKDTree::ESplitAxis axis, const std::vector<CKDTree::SStatObj>& statObjList, std::vector<uint32>& indices, float& outBestSplitPos)
{
if (axis != CKDTree::eSA_X && axis != CKDTree::eSA_Y && axis != CKDTree::eSA_Z)
{
return false;
}
outBestSplitPos = 0;
int nSizeOfIndices = static_cast<int>(indices.size());
for (int i = 0; i < nSizeOfIndices; ++i)
{
int nObjIndex = (indices[i] & 0xFF000000) >> 24;
int nVertexIndex = (indices[i] & 0xFFFFFF);
const CKDTree::SStatObj* pObj = &statObjList[nObjIndex];
const IIndexedMesh* pMesh = pObj->pStatObj->GetIndexedMesh();
if (pMesh == nullptr)
{
continue;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
if (meshDesc.m_pVerts)
{
outBestSplitPos += pObj->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex])[axis];
}
else if (meshDesc.m_pVertsF16)
{
outBestSplitPos += pObj->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3())[axis];
}
}
outBestSplitPos /= nSizeOfIndices;
return true;
}
struct SSplitInfo
{
AABB aboveBoundbox;
std::vector<uint32> aboveIndices;
AABB belowBoundbox;
std::vector<uint32> belowIndices;
};
bool SplitNode(const std::vector<CKDTree::SStatObj>& statObjList, const AABB& boundbox, const std::vector<uint32>& indices, CKDTree::ESplitAxis splitAxis, float splitPos, SSplitInfo& outInfo)
{
if (splitAxis != CKDTree::eSA_X && splitAxis != CKDTree::eSA_Y && splitAxis != CKDTree::eSA_Z)
{
return false;
}
outInfo.aboveBoundbox = boundbox;
outInfo.belowBoundbox = boundbox;
outInfo.aboveBoundbox.max[splitAxis] = splitPos;
outInfo.belowBoundbox.min[splitAxis] = splitPos;
uint32 iIndexSize = (uint32)indices.size();
outInfo.aboveIndices.reserve(iIndexSize);
outInfo.belowIndices.reserve(iIndexSize);
for (uint32 i = 0; i < iIndexSize; ++i)
{
int nObjIndex = (indices[i] & 0xFF000000) >> 24;
int nVertexIndex = indices[i] & 0xFFFFFF;
const CKDTree::SStatObj* pObj = &statObjList[nObjIndex];
const IIndexedMesh* pMesh = pObj->pStatObj->GetIndexedMesh();
if (pMesh == nullptr)
{
return false;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
Vec3 vPos;
if (meshDesc.m_pVerts)
{
vPos = pObj->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex]);
}
else if (meshDesc.m_pVertsF16)
{
vPos = pObj->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3());
}
else
{
continue;
}
if (vPos[splitAxis] < splitPos)
{
outInfo.aboveIndices.push_back(indices[i]);
assert(outInfo.aboveBoundbox.IsContainPoint(vPos));
}
else
{
outInfo.belowIndices.push_back(indices[i]);
assert(outInfo.belowBoundbox.IsContainPoint(vPos));
}
}
return true;
}
CKDTree::CKDTree()
{
m_pRootNode = nullptr;
}
CKDTree::~CKDTree()
{
if (m_pRootNode)
{
delete m_pRootNode;
}
}
bool CKDTree::Build(IStatObj* pStatObj)
{
if (pStatObj == nullptr)
{
return false;
}
m_StatObjectList.clear();
if (pStatObj->GetIndexedMesh(true))
{
SStatObj rootObj;
rootObj.tm.SetIdentity();
rootObj.pStatObj = pStatObj;
m_StatObjectList.push_back(rootObj);
}
ConstructStatObjList(pStatObj, Matrix34::CreateIdentity());
AABB entireBoundBox;
entireBoundBox.Reset();
std::vector<uint32> indices;
for (int i = 0, iStatObjSize = static_cast<uint32>(m_StatObjectList.size()); i < iStatObjSize; ++i)
{
IIndexedMesh* pMesh = m_StatObjectList[i].pStatObj->GetIndexedMesh(true);
if (pMesh == nullptr)
{
continue;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
for (int k = 0; k < meshDesc.m_nVertCount; ++k)
{
entireBoundBox.Add(m_StatObjectList[i].tm.TransformPoint(meshDesc.m_pVerts[k]));
indices.push_back((i << 24) | k);
}
}
if (m_pRootNode)
{
delete m_pRootNode;
}
m_pRootNode = new KDTreeNode;
BuildRecursively(m_pRootNode, entireBoundBox, indices);
return true;
}
void CKDTree::BuildRecursively(KDTreeNode* pNode, const AABB& boundbox, std::vector<uint32>& indices) const
{
pNode->SetBoundBox(boundbox);
if (indices.size() <= s_MinimumVertexSizeInLeafNode)
{
pNode->SetVertexIndexBuffer(indices);
return;
}
ESplitAxis splitAxis = SearchForBestSplitAxis(boundbox);
float splitPos(0);
SearchForBestSplitPos(splitAxis, m_StatObjectList, indices, splitPos);
pNode->SetSplitAxis(splitAxis);
pNode->SetSplitPos(splitPos);
SSplitInfo splitInfo;
if (!SplitNode(m_StatObjectList, boundbox, indices, splitAxis, splitPos, splitInfo))
{
return;
}
if (splitInfo.aboveIndices.empty() || splitInfo.belowIndices.empty())
{
pNode->SetVertexIndexBuffer(indices);
return;
}
KDTreeNode* pChild0 = new KDTreeNode;
KDTreeNode* pChild1 = new KDTreeNode;
pNode->SetChild(0, pChild0);
pNode->SetChild(1, pChild1);
BuildRecursively(pChild0, splitInfo.aboveBoundbox, splitInfo.aboveIndices);
BuildRecursively(pChild1, splitInfo.belowBoundbox, splitInfo.belowIndices);
}
void CKDTree::ConstructStatObjList(IStatObj* pStatObj, const Matrix34& matParent)
{
if (pStatObj == nullptr)
{
return;
}
for (int i = 0, nChildObjSize(pStatObj->GetSubObjectCount()); i < nChildObjSize; ++i)
{
IStatObj::SSubObject* pSubObj = pStatObj->GetSubObject(i);
SStatObj s;
s.tm = matParent * pSubObj->localTM;
if (pSubObj->pStatObj && pSubObj->pStatObj->GetIndexedMesh(true))
{
s.pStatObj = pSubObj->pStatObj;
m_StatObjectList.push_back(s);
}
ConstructStatObjList(pSubObj->pStatObj, s.tm);
}
}
bool CKDTree::FindNearestVertex(const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const
{
return FindNearestVertexRecursively(m_pRootNode, raySrc, rayDir, vVertexBoxSize, localCameraPos, outPos, vOutHitPosOnCube);
}
AABB GetNodeBoundBox(KDTreeNode* pNode, float vVertexBoxSize, const Vec3& localCameraPos)
{
AABB nodeAABB = pNode->GetBoundBox();
float fScreenFactorMin = localCameraPos.GetDistance(nodeAABB.min);
Vec3 vBoundBoxMin(fScreenFactorMin * vVertexBoxSize, fScreenFactorMin * vVertexBoxSize, fScreenFactorMin * vVertexBoxSize);
float fScreenFactorMax = localCameraPos.GetDistance(nodeAABB.max);
Vec3 vBoundBoxMax(fScreenFactorMax * vVertexBoxSize, fScreenFactorMax * vVertexBoxSize, fScreenFactorMax * vVertexBoxSize);
nodeAABB.min -= vBoundBoxMin;
nodeAABB.max += vBoundBoxMax;
return nodeAABB;
}
bool CKDTree::FindNearestVertexRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const
{
if (!pNode)
{
return false;
}
Vec3 vHitPos;
AABB nodeAABB = GetNodeBoundBox(pNode, vVertexBoxSize, localCameraPos);
if (!pNode->GetBoundBox().IsContainPoint(raySrc) && !Intersect::Ray_AABB(raySrc, rayDir, nodeAABB, vHitPos))
{
return false;
}
if (pNode->IsLeaf())
{
if (m_StatObjectList.empty())
{
return false;
}
uint32 nVBuffSize = pNode->GetVertexBufferSize();
if (nVBuffSize == 0)
{
return false;
}
float fNearestDist = 3e10f;
for (uint32 i = 0; i < nVBuffSize; ++i)
{
uint32 nVertexIndex = pNode->GetVertexIndex(i);
uint32 nObjIndex = pNode->GetObjIndex(i);
assert(nObjIndex < m_StatObjectList.size());
const SStatObj* pStatObjInfo = &(m_StatObjectList[nObjIndex]);
IIndexedMesh* pMesh = m_StatObjectList[nObjIndex].pStatObj->GetIndexedMesh();
if (pMesh == nullptr)
{
continue;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
Vec3 vCandidatePos(0, 0, 0);
if (meshDesc.m_pVerts)
{
vCandidatePos = pStatObjInfo->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex]);
}
else if (meshDesc.m_pVertsF16)
{
vCandidatePos = pStatObjInfo->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3());
}
else
{
continue;
}
float fScreenFactor = localCameraPos.GetDistance(vCandidatePos);
Vec3 vBoundBox(fScreenFactor * vVertexBoxSize, fScreenFactor * vVertexBoxSize, fScreenFactor * vVertexBoxSize);
Vec3 vHitPosOnCube;
if (Intersect::Ray_AABB(raySrc, rayDir, AABB(vCandidatePos - vBoundBox, vCandidatePos + vBoundBox), vHitPosOnCube))
{
float fDist = vHitPosOnCube.GetDistance(raySrc);
if (fDist < fNearestDist)
{
fNearestDist = fDist;
outPos = vCandidatePos;
vOutHitPosOnCube = vHitPosOnCube;
}
}
}
if (fNearestDist < 3e10f)
{
return true;
}
return false;
}
Vec3 vNearestPos0, vNearestPos0OnCube;
Vec3 vNearestPos1, vNearestPos1OnCube;
bool bFoundChild0 = FindNearestVertexRecursively(pNode->GetChild(0), raySrc, rayDir, vVertexBoxSize, localCameraPos, vNearestPos0, vNearestPos0OnCube);
bool bFoundChild1 = FindNearestVertexRecursively(pNode->GetChild(1), raySrc, rayDir, vVertexBoxSize, localCameraPos, vNearestPos1, vNearestPos1OnCube);
if (bFoundChild0 && bFoundChild1)
{
float fDist0 = raySrc.GetDistance(vNearestPos0OnCube);
float fDist1 = raySrc.GetDistance(vNearestPos1OnCube);
if (fDist0 < fDist1)
{
outPos = vNearestPos0;
vOutHitPosOnCube = vNearestPos0OnCube;
}
else
{
outPos = vNearestPos1;
vOutHitPosOnCube = vNearestPos1OnCube;
}
}
else if (bFoundChild0 && !bFoundChild1)
{
outPos = vNearestPos0;
vOutHitPosOnCube = vNearestPos0OnCube;
}
else if (!bFoundChild0 && bFoundChild1)
{
outPos = vNearestPos1;
vOutHitPosOnCube = vNearestPos1OnCube;
}
return bFoundChild0 || bFoundChild1;
}
void CKDTree::GetPenetratedBoxes(const Vec3& raySrc, const Vec3& rayDir, std::vector<AABB>& outBoxes)
{
GetPenetratedBoxesRecursively(m_pRootNode, raySrc, rayDir, outBoxes);
}
void CKDTree::GetPenetratedBoxesRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, std::vector<AABB>& outBoxes)
{
Vec3 vHitPos;
if (!pNode || (!pNode->GetBoundBox().IsContainPoint(raySrc) && !Intersect::Ray_AABB(raySrc, rayDir, pNode->GetBoundBox(), vHitPos)))
{
return;
}
outBoxes.push_back(pNode->GetBoundBox());
GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes);
GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes);
}