/* * 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. * */ #include #include namespace AZ { namespace RPI { ModelKdTree::ESplitAxis ModelKdTree::SearchForBestSplitAxis(const AZ::Aabb& aabb, float& splitPosition) { const float xsize = aabb.GetXExtent(); const float ysize = aabb.GetYExtent(); const float zsize = aabb.GetZExtent(); ModelKdTree::ESplitAxis axis; if (xsize >= ysize && xsize >= zsize) { axis = ModelKdTree::eSA_X; splitPosition = aabb.GetMin().GetX() + xsize * 0.5f; } else if (ysize >= zsize && ysize >= xsize) { axis = ModelKdTree::eSA_Y; splitPosition = aabb.GetMin().GetY() + ysize * 0.5f; } else { axis = ModelKdTree::eSA_Z; splitPosition = aabb.GetMin().GetZ() + zsize * 0.5f; } return axis; } bool ModelKdTree::SplitNode(const AZ::Aabb& boundbox, const AZStd::vector& indices, ModelKdTree::ESplitAxis splitAxis, float splitPos, SSplitInfo& outInfo) { if (splitAxis != ModelKdTree::eSA_X && splitAxis != ModelKdTree::eSA_Y && splitAxis != ModelKdTree::eSA_Z) { return false; } outInfo.m_aboveBoundbox = boundbox; outInfo.m_belowBoundbox = boundbox; { Vector3 maxBound = outInfo.m_aboveBoundbox.GetMax(); maxBound.SetElement(splitAxis, splitPos); outInfo.m_aboveBoundbox.SetMax(maxBound); } { Vector3 minBound = outInfo.m_belowBoundbox.GetMin(); minBound.SetElement(splitAxis, splitPos); outInfo.m_belowBoundbox.SetMin(minBound); } const AZ::u32 iIndexSize = aznumeric_cast(indices.size()); outInfo.m_aboveIndices.reserve(iIndexSize); outInfo.m_belowIndices.reserve(iIndexSize); AZStd::array triangleVertex; for (AZ::u32 i = 0; i <= iIndexSize - 3; i += 3) { const AZ::u32 nObjIndex = (indices[i] & 0xFF000000) >> 24; // asuming that all 3 verices belong to the same triangle from the same object const AZ::u32 nVertexIndices[3] = { indices[i] & 0xFFFFFF, indices[i + 1] & 0xFFFFFF, indices[i + 2] & 0xFFFFFF }; const AZStd::array_view& positionBuffer = m_meshes[nObjIndex].m_vertexData; if (positionBuffer.empty() == false) { for (AZStd::size_t triangleVertexIndex = 0; triangleVertexIndex < triangleVertex.size(); ++triangleVertexIndex) { triangleVertex[triangleVertexIndex].Set(const_cast(positionBuffer.data() + 3 * nVertexIndices[triangleVertexIndex])); } } else { continue; } if (triangleVertex[0].GetElement(splitAxis) < splitPos || triangleVertex[1].GetElement(splitAxis) < splitPos || triangleVertex[2].GetElement(splitAxis) < splitPos) { outInfo.m_aboveIndices.push_back(indices[i + 0]); outInfo.m_aboveIndices.push_back(indices[i + 1]); outInfo.m_aboveIndices.push_back(indices[i + 2]); } if (triangleVertex[0].GetElement(splitAxis) >= splitPos || triangleVertex[1].GetElement(splitAxis) >= splitPos || triangleVertex[2].GetElement(splitAxis) >= splitPos) { outInfo.m_belowIndices.push_back(indices[i + 0]); outInfo.m_belowIndices.push_back(indices[i + 1]); outInfo.m_belowIndices.push_back(indices[i + 2]); } } if (indices.size() == outInfo.m_aboveIndices.size() || indices.size() == outInfo.m_belowIndices.size()) { // triangles are too close to cut any further return false; } return true; } bool ModelKdTree::Build(const ModelAsset* model) { if (model == nullptr) { return false; } ConstructMeshList(model, AZ::Transform::CreateIdentity()); AZ::Aabb entireBoundBox; entireBoundBox.SetNull(); // indices with object ids AZStd::vector indices; int totalSizeNeed = 0; for (const MeshData& data : m_meshes) { totalSizeNeed += data.m_mesh->GetVertexCount(); } indices.reserve(totalSizeNeed); AZ::Vector3 vertex; for (AZ::u32 meshIndex = 0, meshCount = aznumeric_cast(m_meshes.size()); meshIndex < meshCount; ++meshIndex) { AZStd::array_view positionBuffer = m_meshes[meshIndex].m_vertexData; if (positionBuffer.empty() == false) { const int nVertexCount = m_meshes[meshIndex].m_mesh->GetVertexCount(); for (int k = 0; k < nVertexCount; ++k) { vertex.Set(const_cast((positionBuffer.data() + 3 * k))); entireBoundBox.AddPoint(vertex); indices.push_back((meshIndex << 24) | k); } } } m_pRootNode = AZStd::make_unique(); BuildRecursively(m_pRootNode.get(), entireBoundBox, indices); return true; } AZStd::array_view ModelKdTree::GetPositionsBuffer(const ModelLodAsset::Mesh& mesh) { const AZStd::array_view positionRawBuffer = mesh.GetSemanticBuffer(m_positionName); if (positionRawBuffer.empty() == false) { AZStd::array_view floatBuffer(reinterpret_cast(positionRawBuffer.data()), positionRawBuffer.size() / 12); return floatBuffer; } AZ_Warning("ModelKdTree", false, "Could not find position buffers in a mesh"); return {}; } void ModelKdTree::BuildRecursively(ModelKdTreeNode* pNode, const AZ::Aabb& boundbox, AZStd::vector& indices) { pNode->SetBoundBox(boundbox); if (indices.size() <= s_MinimumVertexSizeInLeafNode) { pNode->SetVertexIndexBuffer(AZStd::move(indices)); return; } float splitPos(0); const ESplitAxis splitAxis = SearchForBestSplitAxis(boundbox, splitPos); pNode->SetSplitAxis(splitAxis); pNode->SetSplitPos(splitPos); SSplitInfo splitInfo; if (!SplitNode(boundbox, indices, splitAxis, splitPos, splitInfo)) { pNode->SetVertexIndexBuffer(AZStd::move(indices)); return; } if (splitInfo.m_aboveIndices.empty() || splitInfo.m_belowIndices.empty()) { pNode->SetVertexIndexBuffer(AZStd::move(indices)); return; } pNode->SetChild(0, AZStd::make_unique()); pNode->SetChild(1, AZStd::make_unique()); BuildRecursively(pNode->GetChild(0), splitInfo.m_aboveBoundbox, splitInfo.m_aboveIndices); BuildRecursively(pNode->GetChild(1), splitInfo.m_belowBoundbox, splitInfo.m_belowIndices); } void ModelKdTree::ConstructMeshList(const ModelAsset* model, [[maybe_unused]] const AZ::Transform& matParent) { if (model == nullptr) { return; } if (model->GetLodAssets().empty() == false) { if (ModelLodAsset* loadAssetPtr = model->GetLodAssets()[0].Get()) { for (const ModelLodAsset::Mesh& data : loadAssetPtr->GetMeshes()) { m_meshes.push_back({ &data, GetPositionsBuffer(data) }); } } } } bool ModelKdTree::RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance) const { return RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, distance); } bool ModelKdTree::RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance) const { if (!pNode) { return false; } float start, end; if (AZ::Intersect::IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == Intersect::ISECT_RAY_AABB_NONE) { return false; } if (pNode->IsLeaf()) { if (m_meshes.empty()) { return false; } const AZ::u32 nVBuffSize = pNode->GetVertexBufferSize(); if (nVBuffSize == 0) { return false; } AZ::Vector3 ignoreNormal; float hitDistanceNormalized; const float maxDist(FLT_MAX); float nearestDist = maxDist; for (AZ::u32 i = 0; i <= nVBuffSize - 3; i += 3) { const AZ::u32 nVertexIndex = pNode->GetVertexIndex(i); const AZ::u32 nObjIndex = pNode->GetObjIndex(i); AZStd::array_view positionBuffer = m_meshes[nObjIndex].m_vertexData; AZStd::array trianglePoints; if (positionBuffer.empty() == false) { trianglePoints[0].Set(const_cast(positionBuffer.data() + 3 * nVertexIndex)); trianglePoints[1].Set(const_cast(positionBuffer.data() + 3 * pNode->GetVertexIndex(i + 1))); trianglePoints[2].Set(const_cast(positionBuffer.data() + 3 * pNode->GetVertexIndex(i + 2))); } else { continue; } const AZ::Vector3 rayEnd = raySrc + rayDir * distance; if (AZ::Intersect::IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2], ignoreNormal, hitDistanceNormalized) != Intersect::ISECT_RAY_AABB_NONE) { float hitDistance = hitDistanceNormalized * distance; nearestDist = AZStd::GetMin(nearestDist, hitDistance); } } if (nearestDist < maxDist) { distance = AZStd::GetMin(distance, nearestDist); return true; } return false; } // running both sides to find the closest intersection const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distance); const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distance); return bFoundChild0 || bFoundChild1; } void ModelKdTree::GetPenetratedBoxes(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes) { GetPenetratedBoxesRecursively(m_pRootNode.get(), raySrc, rayDir, outBoxes); } void ModelKdTree::GetPenetratedBoxesRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes) { AZ::Vector3 ignoreNormal; float ignore; if (!pNode || (!pNode->GetBoundBox().Contains(raySrc) && (AZ::Intersect::IntersectRayAABB(raySrc, rayDir, rayDir.GetReciprocal(), pNode->GetBoundBox(), ignore, ignore, ignoreNormal)) == Intersect::ISECT_RAY_AABB_NONE)) { return; } outBoxes.push_back(pNode->GetBoundBox()); GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes); GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes); } } }