From 8a791816ed3215b0b919fe07ece7e376a2b053c0 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Mon, 4 May 2026 18:31:12 +0200 Subject: [PATCH 1/6] chore: set XSurface deformed property when applicable --- src/Common/Game/IW5/IW5_Assets.h | 5 +++++ src/ObjLoading/XModel/LoaderXModel.cpp.template | 13 ++++++++++--- src/ObjWriting/XModel/XModelDumper.cpp.template | 12 ++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index ae694a85..e61fb58b 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -516,6 +516,11 @@ namespace IW5 typedef tdef_align32(16) XSurfaceTri XSurfaceTri16; + enum XSurfaceFlag + { + XSURFACE_FLAG_DEFORMED = 0x40 + }; + struct XSurface { unsigned char tileMode; diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index 018ada36..b3332821 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -752,9 +752,16 @@ namespace // Since bone weights are sorted by weight count, the last must have the highest weight count const auto maxWeightCount = common.m_vertex_bone_weights[xmodelToCommonVertexIndexLookup[xmodelToCommonVertexIndexLookup.size() - 1]].weightCount; - const auto modelIsRigid = maxWeightCount <= 1; + const auto surfaceIsRigid = maxWeightCount <= 1; - if (modelIsRigid) +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) + surface.deformed = !surfaceIsRigid; +#else + if (!surfaceIsRigid) + surface.flags |= XSURFACE_FLAG_DEFORMED; +#endif + + if (surfaceIsRigid) { constexpr auto maxVerticesForRigid = static_cast(std::numeric_limits::max()); if (vertexOffset + xmodelToCommonVertexIndexLookup.size() > maxVerticesForRigid) @@ -790,7 +797,7 @@ namespace if (!common.m_bone_weight_data.weights.empty()) { - if (modelIsRigid) + if (surfaceIsRigid) { CreateVertListData(surface, xmodelToCommonVertexIndexLookup, common); } diff --git a/src/ObjWriting/XModel/XModelDumper.cpp.template b/src/ObjWriting/XModel/XModelDumper.cpp.template index 434750af..06a7ff08 100644 --- a/src/ObjWriting/XModel/XModelDumper.cpp.template +++ b/src/ObjWriting/XModel/XModelDumper.cpp.template @@ -423,6 +423,12 @@ namespace if (surface.vertList) { +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) + assert(!surface.deformed); +#else + assert((surface.flags & XSURFACE_FLAG_DEFORMED) == 0); +#endif + for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++) { const auto& vertList = surface.vertList[vertListIndex]; @@ -442,6 +448,12 @@ namespace auto vertsBlendOffset = 0u; if (surface.vertInfo.vertsBlend) { +#if defined(FEATURE_IW3) || defined(FEATURE_IW4) + assert(surface.deformed); +#else + assert((surface.flags & XSURFACE_FLAG_DEFORMED) > 0); +#endif + // 1 bone weight for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) { From a128fbeb5e668beaef48ad11417468b9550e7d21 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Mon, 4 May 2026 19:15:08 +0200 Subject: [PATCH 2/6] chore: imitate game xmodel behaviour in always having even tri count --- .../XModel/LoaderXModel.cpp.template | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index b3332821..4522ca18 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -9,12 +9,16 @@ #if GAME == "IW3" #define FEATURE_IW3 +#define EVEN_TRI_COUNT #elif GAME == "IW4" #define FEATURE_IW4 +#define EVEN_TRI_COUNT #elif GAME == "IW5" #define FEATURE_IW5 +#define EVEN_TRI_COUNT #elif GAME == "T5" #define FEATURE_T5 +#define EVEN_TRI_COUNT #elif GAME == "T6" #define FEATURE_T6 #endif @@ -26,6 +30,7 @@ #include JSON_HEADER #include "Asset/AssetRegistration.h" +#include "Utils/Alignment.h" #include "Utils/Logging/Log.h" #include "Utils/QuatInt16.h" #include "Utils/StringUtils.h" @@ -723,7 +728,14 @@ namespace } surface.triCount = static_cast(commonObject.m_faces.size()); + +#ifdef EVEN_TRI_COUNT + // Some games round up to an even tri count + const auto allocatedTriCount = utils::Align(surface.triCount, 2); + surface.triIndices = m_memory.Alloc(allocatedTriCount); +#else surface.triIndices = m_memory.Alloc(surface.triCount); +#endif xmodelToCommonVertexIndexLookup.reserve(static_cast(surface.triCount) * std::extent_v); for (auto faceIndex = 0u; faceIndex < surface.triCount; faceIndex++) @@ -815,6 +827,18 @@ namespace return false; } } + +#ifdef EVEN_TRI_COUNT + // The game makes sure to allows have an even triCount + // If the triCount is uneven, an additional tri is added that uses the last vertex three times + if (allocatedTriCount > surface.triCount) + { + assert(allocatedTriCount == surface.triCount + 1); + surface.triIndices[allocatedTriCount - 1].i[0] = surface.triIndices[surface.triCount - 1].i[2]; + surface.triIndices[allocatedTriCount - 1].i[1] = surface.triIndices[surface.triCount - 1].i[2]; + surface.triIndices[allocatedTriCount - 1].i[2] = surface.triIndices[surface.triCount - 1].i[2]; + } +#endif return true; } From 5cf0ce37c1d2b8d0fff720c265f7fe606ab2e367 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Thu, 21 May 2026 18:24:51 +0200 Subject: [PATCH 3/6] feat: calculate model collision tree with leafs --- src/Common/Game/IW3/IW3_Assets.h | 4 +- src/Common/Game/IW4/IW4_Assets.h | 4 +- src/Common/Game/IW5/IW5_Assets.h | 4 +- src/Common/Game/T5/T5_Assets.h | 4 +- .../XModel/CollisionTreeCreator.cpp | 116 ++++++++++++++++++ src/ObjLoading/XModel/CollisionTreeCreator.h | 55 +++++++++ .../XModel/LoaderXModel.cpp.template | 90 +++++++++++++- 7 files changed, 268 insertions(+), 9 deletions(-) create mode 100644 src/ObjLoading/XModel/CollisionTreeCreator.cpp create mode 100644 src/ObjLoading/XModel/CollisionTreeCreator.h diff --git a/src/Common/Game/IW3/IW3_Assets.h b/src/Common/Game/IW3/IW3_Assets.h index 0d4062e9..84380acd 100644 --- a/src/Common/Game/IW3/IW3_Assets.h +++ b/src/Common/Game/IW3/IW3_Assets.h @@ -327,8 +327,8 @@ namespace IW3 struct XSurfaceCollisionTree { - float trans[3]; - float scale[3]; + vec3_t trans; + vec3_t scale; unsigned int nodeCount; XSurfaceCollisionNode* nodes; unsigned int leafCount; diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index 40aa6a87..58e6925f 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -468,8 +468,8 @@ namespace IW4 struct XSurfaceCollisionTree { - float trans[3]; - float scale[3]; + vec3_t trans; + vec3_t scale; unsigned int nodeCount; XSurfaceCollisionNode* nodes; unsigned int leafCount; diff --git a/src/Common/Game/IW5/IW5_Assets.h b/src/Common/Game/IW5/IW5_Assets.h index e61fb58b..3dcc1ecc 100644 --- a/src/Common/Game/IW5/IW5_Assets.h +++ b/src/Common/Game/IW5/IW5_Assets.h @@ -492,8 +492,8 @@ namespace IW5 struct XSurfaceCollisionTree { - float trans[3]; - float scale[3]; + vec3_t trans; + vec3_t scale; unsigned int nodeCount; XSurfaceCollisionNode* nodes; unsigned int leafCount; diff --git a/src/Common/Game/T5/T5_Assets.h b/src/Common/Game/T5/T5_Assets.h index 13f25115..7ffd2c64 100644 --- a/src/Common/Game/T5/T5_Assets.h +++ b/src/Common/Game/T5/T5_Assets.h @@ -488,8 +488,8 @@ namespace T5 struct XSurfaceCollisionTree { - float trans[3]; - float scale[3]; + vec3_t trans; + vec3_t scale; unsigned int nodeCount; XSurfaceCollisionNode* nodes; unsigned int leafCount; diff --git a/src/ObjLoading/XModel/CollisionTreeCreator.cpp b/src/ObjLoading/XModel/CollisionTreeCreator.cpp new file mode 100644 index 00000000..f423ed8d --- /dev/null +++ b/src/ObjLoading/XModel/CollisionTreeCreator.cpp @@ -0,0 +1,116 @@ +#include "CollisionTreeCreator.h" + +#include + +namespace +{ + class Bounds + { + public: + Bounds() + : m_mins(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()), + m_maxs(std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min()) + { + } + + void ExpandFromTri(const std::array& triCoordinates) + { + for (const auto& vert : triCoordinates) + { + m_mins[0] = std::min(m_mins[0], vert.x()); + m_mins[1] = std::min(m_mins[1], vert.y()); + m_mins[2] = std::min(m_mins[2], vert.z()); + m_maxs[0] = std::max(m_maxs[0], vert.x()); + m_maxs[1] = std::max(m_maxs[1], vert.y()); + m_maxs[2] = std::max(m_maxs[2], vert.z()); + } + } + + void ExpandFromBounds(const Bounds& otherBounds) + { + m_mins[0] = std::min(m_mins[0], otherBounds.m_mins[0]); + m_mins[1] = std::min(m_mins[1], otherBounds.m_mins[1]); + m_mins[2] = std::min(m_mins[2], otherBounds.m_mins[2]); + m_maxs[0] = std::min(m_maxs[0], otherBounds.m_maxs[0]); + m_maxs[1] = std::min(m_maxs[1], otherBounds.m_maxs[1]); + m_maxs[2] = std::min(m_maxs[2], otherBounds.m_maxs[2]); + } + + [[nodiscard]] Eigen::Vector3f GetDelta() const + { + return m_maxs - m_mins; + } + + [[nodiscard]] float GetVolume() const + { + return GetDelta().prod(); + } + + Eigen::Vector3f m_mins; + Eigen::Vector3f m_maxs; + }; +} // namespace + +namespace xmodel +{ + std::unique_ptr CreateCollisionTree(const IRigidVertListAccessor& vertList) + { + auto tree = std::make_unique(); + + Bounds globalBounds; + + const auto triOffset = vertList.GetTriOffset(); + const auto triCount = vertList.GetTriCount(); + std::vector leafBounds; + auto lastMergeable = false; + + Bounds prevBounds; + for (size_t triNumber = 0; triNumber < triCount; triNumber++) + { + const auto triIndex = triOffset + triNumber; + Bounds triBounds; + + triBounds.ExpandFromTri(vertList.GetCoordinatesForTri(triIndex)); + globalBounds.ExpandFromBounds(triBounds); + + auto shouldMerge = false; + if (lastMergeable) + { + const auto prevVolume = prevBounds.GetVolume(); + const auto thisVolume = triBounds.GetVolume(); + + prevBounds.ExpandFromBounds(triBounds); + const auto combinedVolume = prevBounds.GetVolume(); + if (combinedVolume <= prevVolume + thisVolume) + shouldMerge = true; + } + + if (shouldMerge) + { + leafBounds.back() = prevBounds; + assert(!tree->leafs.back().twoTriangles); + tree->leafs.back().twoTriangles = 1; + lastMergeable = false; + } + else + { + tree->leafs.emplace_back(CommonCollisionLeaf{ + .triangleBeginIndex = static_cast(triIndex), + .twoTriangles = 0, + }); + leafBounds.emplace_back(triBounds); + lastMergeable = true; + prevBounds = triBounds; + } + } + + tree->trans = -globalBounds.m_mins; + const auto globalDelta = globalBounds.GetDelta(); + tree->scale = Eigen::Vector3f(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()) + .cwiseQuotient(globalDelta); + + // TODO: Calculate nodes + + return std::move(tree); + } +} // namespace xmodel diff --git a/src/ObjLoading/XModel/CollisionTreeCreator.h b/src/ObjLoading/XModel/CollisionTreeCreator.h new file mode 100644 index 00000000..169a7caf --- /dev/null +++ b/src/ObjLoading/XModel/CollisionTreeCreator.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace xmodel +{ + struct CommonCollisionAabb + { + std::array mins; + std::array maxs; + }; + + struct CommonCollisionNode + { + CommonCollisionAabb aabb; + uint16_t childBeginIndex; + uint16_t childCount : 15; + uint16_t childrenAreLeafs : 1; + }; + + struct CommonCollisionLeaf + { + uint16_t triangleBeginIndex : 15; + uint16_t twoTriangles : 1; + }; + + struct CommonCollisionTree + { + Eigen::Vector3f trans; + Eigen::Vector3f scale; + std::vector nodes; + std::vector leafs; + }; + + class IRigidVertListAccessor + { + public: + IRigidVertListAccessor() = default; + virtual ~IRigidVertListAccessor() = default; + IRigidVertListAccessor(const IRigidVertListAccessor& other) = default; + IRigidVertListAccessor(IRigidVertListAccessor&& other) noexcept = default; + IRigidVertListAccessor& operator=(const IRigidVertListAccessor& other) = default; + IRigidVertListAccessor& operator=(IRigidVertListAccessor&& other) noexcept = default; + + [[nodiscard]] virtual size_t GetTriOffset() const = 0; + [[nodiscard]] virtual size_t GetTriCount() const = 0; + [[nodiscard]] virtual std::array GetCoordinatesForTri(size_t triIndex) const = 0; + }; + + std::unique_ptr CreateCollisionTree(const IRigidVertListAccessor& vertList); +} // namespace xmodel diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index 4522ca18..923a1a13 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -44,6 +44,7 @@ #include #pragma warning(pop) +#include "XModel/CollisionTreeCreator.h" #include "XModel/PartClassificationState.h" #include "XModel/TangentData.h" #include "XModel/Tangentspace.h" @@ -59,6 +60,47 @@ using namespace GAME; namespace { + constexpr uint16_t XSURFACE_COLLISION_NODE_HAS_LEAFS = 0x8000; + constexpr uint16_t XSURFACE_COLLISION_LEAF_TWO_TRIANGLES = 0x8000; + + Eigen::Vector3f ToEigen(const vec3_t& vec) + { + return Eigen::Vector3f(vec.x, vec.y, vec.z); + } + + class RigidVertListAccessor : public xmodel::IRigidVertListAccessor + { + public: + RigidVertListAccessor(const XSurface& surface, const XRigidVertList& rigidVertList) + : m_surface(surface), + m_rigid_vert_list(rigidVertList) + { + } + + [[nodiscard]] size_t GetTriOffset() const override + { + return m_rigid_vert_list.triOffset; + } + + [[nodiscard]] size_t GetTriCount() const override + { + return m_rigid_vert_list.triCount; + } + + [[nodiscard]] std::array GetCoordinatesForTri(const size_t triIndex) const override + { + return std::array({ + ToEigen(m_surface.verts0[m_surface.triIndices[triIndex].i[0]].xyz), + ToEigen(m_surface.verts0[m_surface.triIndices[triIndex].i[1]].xyz), + ToEigen(m_surface.verts0[m_surface.triIndices[triIndex].i[2]].xyz), + }); + } + + private: + const XSurface& m_surface; + const XRigidVertList& m_rigid_vert_list; + }; + class XModelLoader final : public AssetCreator { public: @@ -505,6 +547,49 @@ namespace const auto shiftValue = 31u - (boneIndex % 32u); surface.partBits[partBitsIndex] |= 1 << shiftValue; } + + [[nodiscard]] XSurfaceCollisionTree* ConvertCollisionTree(const xmodel::CommonCollisionTree& commonTree) const + { + auto* tree = m_memory.Alloc(); + tree->trans.x = commonTree.trans.x(); + tree->trans.y = commonTree.trans.y(); + tree->trans.z = commonTree.trans.z(); + tree->scale.x = commonTree.scale.x(); + tree->scale.y = commonTree.scale.y(); + tree->scale.z = commonTree.scale.z(); + tree->nodeCount = static_cast(commonTree.nodes.size()); + tree->nodes = m_memory.Alloc(tree->nodeCount); + for (auto nodeIndex = 0u; nodeIndex < tree->nodeCount; nodeIndex++) + { + auto& node = tree->nodes[nodeIndex]; + const auto& commonNode = commonTree.nodes[nodeIndex]; + + node.aabb.mins[0] = commonNode.aabb.mins[0]; + node.aabb.mins[1] = commonNode.aabb.mins[1]; + node.aabb.mins[2] = commonNode.aabb.mins[2]; + node.aabb.maxs[0] = commonNode.aabb.maxs[0]; + node.aabb.maxs[1] = commonNode.aabb.maxs[1]; + node.aabb.maxs[2] = commonNode.aabb.maxs[2]; + node.childBeginIndex = commonNode.childBeginIndex; + node.childCount = commonNode.childCount; + if (commonNode.childrenAreLeafs) + node.childCount |= XSURFACE_COLLISION_NODE_HAS_LEAFS; + } + + tree->leafCount = commonTree.leafs.size(); + tree->leafs = m_memory.Alloc(tree->leafCount); + for (auto leafIndex = 0u; leafIndex < tree->leafCount; leafIndex++) + { + auto& leaf = tree->leafs[leafIndex]; + const auto& commonLeaf = commonTree.leafs[leafIndex]; + + leaf.triangleBeginIndex = commonLeaf.triangleBeginIndex; + if (commonLeaf.twoTriangles) + leaf.triangleBeginIndex |= XSURFACE_COLLISION_LEAF_TWO_TRIANGLES; + } + + return tree; + } void CreateVertListData(XSurface& surface, const std::vector& xmodelToCommonVertexIndexLookup, const XModelCommon& common) const { @@ -538,7 +623,10 @@ namespace if (boneVertList.triCount > 0 || boneVertList.vertCount > 0) { - boneVertList.collisionTree = nullptr; // TODO + RigidVertListAccessor vertListAccessor(surface, boneVertList); + const auto commonCollisionTree = xmodel::CreateCollisionTree(vertListAccessor); + boneVertList.collisionTree = ConvertCollisionTree(*commonCollisionTree); + vertLists.emplace_back(boneVertList); currentVertexTail = currentVertexHead; From 64d518405dbc3b67bf227070e409deb452f5eb1c Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 24 May 2026 00:28:34 +0200 Subject: [PATCH 4/6] feat: calculate full collision tree for xmodels --- .../XModel/CollisionTreeCreator.cpp | 614 +++++++++++++++++- 1 file changed, 609 insertions(+), 5 deletions(-) diff --git a/src/ObjLoading/XModel/CollisionTreeCreator.cpp b/src/ObjLoading/XModel/CollisionTreeCreator.cpp index f423ed8d..bb4b6a88 100644 --- a/src/ObjLoading/XModel/CollisionTreeCreator.cpp +++ b/src/ObjLoading/XModel/CollisionTreeCreator.cpp @@ -1,15 +1,41 @@ #include "CollisionTreeCreator.h" +#include "Utils/Logging/Log.h" + +#include #include +#include namespace { + struct GenericAabbTree + { + int firstItem; + int itemCount; + int firstChild; + int childCount; + }; + + struct GenericAabbTreeOptions + { + void* items; + int itemCount; + int itemSize; + int maintainValidBounds; + float (*mins)[3]; + float (*maxs)[3]; + GenericAabbTree* treeNodePool; + int treeNodeLimit; + int minItemsPerLeaf; + int maxItemsPerLeaf; + }; + class Bounds { public: Bounds() : m_mins(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()), - m_maxs(std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min()) + m_maxs(-std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max()) { } @@ -31,9 +57,19 @@ namespace m_mins[0] = std::min(m_mins[0], otherBounds.m_mins[0]); m_mins[1] = std::min(m_mins[1], otherBounds.m_mins[1]); m_mins[2] = std::min(m_mins[2], otherBounds.m_mins[2]); - m_maxs[0] = std::min(m_maxs[0], otherBounds.m_maxs[0]); - m_maxs[1] = std::min(m_maxs[1], otherBounds.m_maxs[1]); - m_maxs[2] = std::min(m_maxs[2], otherBounds.m_maxs[2]); + m_maxs[0] = std::max(m_maxs[0], otherBounds.m_maxs[0]); + m_maxs[1] = std::max(m_maxs[1], otherBounds.m_maxs[1]); + m_maxs[2] = std::max(m_maxs[2], otherBounds.m_maxs[2]); + } + + void ExpandFromBounds(const float (&otherMins)[3], const float (&otherMaxs)[3]) + { + m_mins[0] = std::min(m_mins[0], otherMins[0]); + m_mins[1] = std::min(m_mins[1], otherMins[1]); + m_mins[2] = std::min(m_mins[2], otherMins[2]); + m_maxs[0] = std::max(m_maxs[0], otherMaxs[0]); + m_maxs[1] = std::max(m_maxs[1], otherMaxs[1]); + m_maxs[2] = std::max(m_maxs[2], otherMaxs[2]); } [[nodiscard]] Eigen::Vector3f GetDelta() const @@ -49,6 +85,499 @@ namespace Eigen::Vector3f m_mins; Eigen::Vector3f m_maxs; }; + + void ClearBounds(float* mins, float* maxs) + { + mins[0] = FLT_MAX; + mins[1] = FLT_MAX; + mins[2] = FLT_MAX; + maxs[0] = -FLT_MAX; + maxs[1] = -FLT_MAX; + maxs[2] = -FLT_MAX; + } + + void ExpandBounds(const float* addedmins, const float* addedmaxs, float* mins, float* maxs) + { + *mins = std::min(*mins, *addedmins); + *maxs = std::max(*addedmaxs, *maxs); + mins[1] = std::min(mins[1], addedmins[1]); + maxs[1] = std::max(addedmaxs[1], maxs[1]); + mins[2] = std::min(mins[2], addedmins[2]); + maxs[2] = std::max(addedmaxs[2], maxs[2]); + } + + float AddedVolume(const float* addedmins, const float* addedmaxs, const float* mins, const float* maxs) + { + float expandedMins; // [esp+0h] [ebp-20h] BYREF + float v6; // [esp+4h] [ebp-1Ch] + float v7; // [esp+8h] [ebp-18h] + float expandedVolume; // [esp+Ch] [ebp-14h] + float expandedMaxs; // [esp+14h] [ebp-Ch] BYREF + float v10; // [esp+18h] [ebp-8h] + float v11; // [esp+1Ch] [ebp-4h] + + expandedMins = *mins; + v6 = mins[1]; + v7 = mins[2]; + expandedMaxs = *maxs; + v10 = maxs[1]; + v11 = maxs[2]; + ExpandBounds(addedmins, addedmaxs, &expandedMins, &expandedMaxs); + expandedVolume = (expandedMaxs - expandedMins) * (v10 - v6) * (v11 - v7); + + return expandedVolume - (*maxs - *mins) * (maxs[1] - mins[1]) * (maxs[2] - mins[2]); + } + + int compare_floats(const void* e0, const void* e1) + { + float diff; // [esp+4h] [ebp-4h] + + diff = *static_cast(e0) - *static_cast(e1); + if (diff >= 0.0) + return diff > 0.0; + else + return -1; + } + + class AabbTreeBuilder + { + public: + AabbTreeBuilder() + : sortedMins(nullptr), + sortedMaxs(nullptr), + sortedCoplanar(nullptr), + aabbTreeCount(0) + { + } + + int BuildAabbTree(GenericAabbTreeOptions* options) + { + static constexpr size_t STACK_BUFFER_SIZE = 64; + + float* v2; // [esp+4h] [ebp-454h] + float* v3; // [esp+8h] [ebp-450h] + float* v4; // [esp+Ch] [ebp-44Ch] + float* v5; // [esp+10h] [ebp-448h] + float (*boundCopies)[3]; // [esp+44h] [ebp-414h] + int* remap; // [esp+48h] [ebp-410h] + int itemIndex; // [esp+4Ch] [ebp-40Ch] + int itemIndexa; // [esp+4Ch] [ebp-40Ch] + int itemIndexb; // [esp+4Ch] [ebp-40Ch] + int itemIndexc; // [esp+4Ch] [ebp-40Ch] + int remapBuffer[STACK_BUFFER_SIZE]; // [esp+50h] [ebp-408h] BYREF + float sortedBounds[3][STACK_BUFFER_SIZE]; // [esp+150h] [ebp-308h] BYREF + char* itemCopies; // [esp+454h] [ebp-4h] + + if (options->itemCount > STACK_BUFFER_SIZE) + { + remap = (int*)operator new(4 * options->itemCount); + sortedMins = (float*)operator new(4 * options->itemCount); + sortedMaxs = (float*)operator new(4 * options->itemCount); + sortedCoplanar = (float*)operator new(4 * options->itemCount); + } + else + { + remap = remapBuffer; + sortedMins = sortedBounds[0]; + sortedMaxs = sortedBounds[1]; + sortedCoplanar = sortedBounds[2]; + } + + for (itemIndex = 0; itemIndex < options->itemCount; ++itemIndex) + remap[itemIndex] = itemIndex; + + options->treeNodePool->firstItem = 0; + options->treeNodePool->itemCount = options->itemCount; + + aabbTreeCount = 1; + BuildAabbTree_r(options->treeNodePool, options, remap); + + itemCopies = (char*)operator new(options->itemSize * options->itemCount); + memcpy(itemCopies, options->items, options->itemSize * options->itemCount); + for (itemIndexa = 0; itemIndexa < options->itemCount; ++itemIndexa) + memcpy((char*)options->items + options->itemSize * itemIndexa, &itemCopies[options->itemSize * remap[itemIndexa]], options->itemSize); + operator delete(itemCopies); + + if (options->maintainValidBounds) + { + boundCopies = + (float (*)[3]) operator new(4 * ((3 * (unsigned __int64)(unsigned int)options->itemCount) >> 32 != 0 ? -1 : 3 * options->itemCount)); + memcpy(boundCopies, options->mins, 12 * options->itemCount); + for (itemIndexb = 0; itemIndexb < options->itemCount; ++itemIndexb) + { + v4 = options->mins[itemIndexb]; + v5 = boundCopies[remap[itemIndexb]]; + *v4 = *v5; + v4[1] = v5[1]; + v4[2] = v5[2]; + } + memcpy(boundCopies, options->maxs, 12 * options->itemCount); + for (itemIndexc = 0; itemIndexc < options->itemCount; ++itemIndexc) + { + v2 = options->maxs[itemIndexc]; + v3 = boundCopies[remap[itemIndexc]]; + *v2 = *v3; + v2[1] = v3[1]; + v2[2] = v3[2]; + } + operator delete(boundCopies); + } + if (remap != remapBuffer) + { + operator delete(remap); + operator delete(sortedMins); + operator delete(sortedMaxs); + operator delete(sortedCoplanar); + } + + return aabbTreeCount; + } + + private: + void BuildAabbTree_r(GenericAabbTree* tree, GenericAabbTreeOptions* options, int* remap) + { + int midStart; // [esp+0h] [ebp-10h] BYREF + int childIndex; // [esp+4h] [ebp-Ch] + int lastStart; // [esp+8h] [ebp-8h] BYREF + GenericAabbTree* subtree; // [esp+Ch] [ebp-4h] + + assert(tree->itemCount); + + tree->firstChild = aabbTreeCount; + tree->childCount = 0; + if (tree->itemCount > options->maxItemsPerLeaf && SplitAabbTree(tree->itemCount, options, remap, &midStart, &lastStart)) + { + subtree = &options->treeNodePool[aabbTreeCount]; + + assert(tree->firstChild == aabbTreeCount); + + CreateAabbSubTrees(tree, options, remap, 0, midStart); + if (midStart < lastStart) + CreateAabbSubTrees(tree, options, remap, midStart, lastStart - midStart); + CreateAabbSubTrees(tree, options, remap, lastStart, tree->itemCount - lastStart); + tree->childCount = aabbTreeCount - tree->firstChild; + for (childIndex = 0; childIndex < tree->childCount; ++childIndex) + BuildAabbTree_r(&subtree[childIndex], options, &remap[subtree[childIndex].firstItem - tree->firstItem]); + } + } + + int SplitAabbTree(int count, GenericAabbTreeOptions* options, int* remap, int* midStart, int* lastStart) + { + float v6; // [esp+4h] [ebp-58h] + float v7; // [esp+8h] [ebp-54h] + int top; // [esp+Ch] [ebp-50h] + float (*mins)[3]; // [esp+10h] [ebp-4Ch] + int splitAxis; // [esp+14h] [ebp-48h] BYREF + int bot; // [esp+18h] [ebp-44h] + float bounds[3]; // [esp+1Ch] [ebp-40h] BYREF + float v13[3]; // [esp+28h] [ebp-34h] BYREF + float v14[3]; // [esp+34h] [ebp-28h] BYREF + float v15[3]; // [esp+40h] [ebp-1Ch] BYREF + float (*maxs)[3]; // [esp+4Ch] [ebp-10h] + float splitDist; // [esp+50h] [ebp-Ch] BYREF + int swapCache; // [esp+54h] [ebp-8h] + int mid; // [esp+58h] [ebp-4h] + + mins = options->mins; + maxs = options->maxs; + if (!PickAabbSplitPlane(mins, maxs, remap, count, &splitAxis, &splitDist)) + return 0; + ClearBounds(bounds, v13); + ClearBounds(v14, v15); + bot = 0; + top = count - 1; + while (bot <= top) + { + while (bot <= top && splitDist >= maxs[remap[bot]][splitAxis] && splitDist > mins[remap[bot]][splitAxis]) + { + ExpandBounds(mins[remap[bot]], maxs[remap[bot]], bounds, v13); + ++bot; + } + while (bot <= top && mins[remap[top]][splitAxis] >= splitDist && maxs[remap[top]][splitAxis] > splitDist) + { + ExpandBounds(mins[remap[top]], maxs[remap[top]], v14, v15); + --top; + } + if (bot > top) + break; + if ((mins[remap[bot]][splitAxis] < splitDist || maxs[remap[bot]][splitAxis] <= splitDist) + && (splitDist < maxs[remap[top]][splitAxis] || splitDist <= mins[remap[top]][splitAxis])) + { + for (mid = bot; mid < top; ++mid) + { + if (mins[remap[mid]][splitAxis] >= splitDist && maxs[remap[mid]][splitAxis] > splitDist) + { + swapCache = remap[mid]; + remap[mid] = remap[top]; + remap[top] = swapCache; + break; + } + if (splitDist >= maxs[remap[mid]][splitAxis] && splitDist > mins[remap[mid]][splitAxis]) + { + swapCache = remap[mid]; + remap[mid] = remap[bot]; + remap[bot] = swapCache; + break; + } + } + if (mid == top) + break; + } + else + { + swapCache = remap[bot]; + remap[bot] = remap[top]; + remap[top] = swapCache; + } + } + if (bot <= top && (bot < options->minItemsPerLeaf || top - bot + 1 < options->minItemsPerLeaf || count - top - 1 < options->minItemsPerLeaf)) + { + while (bot <= top) + { + while (bot <= top) + { + v7 = AddedVolume(mins[remap[bot]], maxs[remap[bot]], bounds, v13); + if (AddedVolume(mins[remap[bot]], maxs[remap[bot]], v14, v15) < (double)v7) + break; + ExpandBounds(mins[remap[bot]], maxs[remap[bot]], bounds, v13); + ++bot; + } + while (bot <= top) + { + v6 = AddedVolume(mins[remap[top]], maxs[remap[top]], v14, v15); + if (AddedVolume(mins[remap[top]], maxs[remap[top]], bounds, v13) < (double)v6) + break; + ExpandBounds(mins[remap[top]], maxs[remap[top]], v14, v15); + --top; + } + if (bot >= top) + { + if (bot == top) + { + if (2 * bot >= count) + --top; + else + ++bot; + } + } + else + { + swapCache = remap[bot]; + remap[bot] = remap[top]; + remap[top] = swapCache; + ++bot; + --top; + } + } + } + if (!bot || bot == count) + return 0; + *midStart = bot; + *lastStart = top + 1; + return 1; + } + + bool PickAabbSplitPlane(float (*mins)[3], float (*maxs)[3], int* remap, int count, int* chosenAxis, float* chosenDist) + { + float v7; // [esp+4h] [ebp-A0h] + int sideSplitCount; // [esp+38h] [ebp-6Ch] + float nextDist; // [esp+3Ch] [ebp-68h] + int prevMinCount; // [esp+40h] [ebp-64h] + int prevOnCount; // [esp+44h] [ebp-60h] + float dist; // [esp+48h] [ebp-5Ch] + signed int minMaxCount; // [esp+4Ch] [ebp-58h] + signed int coplanarCount; // [esp+50h] [ebp-54h] + int axisIndex; // [esp+54h] [ebp-50h] + signed int bestHeuristic; // [esp+58h] [ebp-4Ch] + int smallestAxis; // [esp+5Ch] [ebp-48h] + int maxIndex; // [esp+60h] [ebp-44h] + float globalMaxs[3]; // [esp+64h] [ebp-40h] BYREF + int sideFrontCount; // [esp+70h] [ebp-34h] + int i; // [esp+74h] [ebp-30h] + int sideOnCount; // [esp+78h] [ebp-2Ch] + float globalMins[3]; // [esp+7Ch] [ebp-28h] BYREF + int axisBias[3]; // [esp+88h] [ebp-1Ch] + int minIndex; // [esp+94h] [ebp-10h] + int onIndex; // [esp+98h] [ebp-Ch] + int heuristic; // [esp+9Ch] [ebp-8h] + int sideBackCount; // [esp+A0h] [ebp-4h] + + ClearBounds(globalMins, globalMaxs); + + for (i = 0; i < count; ++i) + ExpandBounds(mins[remap[i]], maxs[remap[i]], globalMins, globalMaxs); + + smallestAxis = globalMaxs[0] - globalMins[0] > globalMaxs[1] - globalMins[1]; + if (globalMaxs[smallestAxis] - globalMins[smallestAxis] > globalMaxs[2] - globalMins[2]) + smallestAxis = 2; + + for (i = 0; i < 3; ++i) + { + axisBias[i] = static_cast((globalMaxs[i] - globalMins[i] + 1.0f) * 10.0f / (globalMaxs[smallestAxis] - globalMins[smallestAxis] + 1.0f) + + 0.4999999990686774); + } + + bestHeuristic = -1; + + for (axisIndex = 0; axisIndex < 3; ++axisIndex) + { + minMaxCount = 0; + coplanarCount = 0; + + for (i = 0; i < count; ++i) + { + if (mins[remap[i]][axisIndex] == maxs[remap[i]][axisIndex]) + { + sortedCoplanar[coplanarCount++] = mins[remap[i]][axisIndex]; + } + else + { + sortedMins[minMaxCount] = mins[remap[i]][axisIndex]; + sortedMaxs[minMaxCount++] = maxs[remap[i]][axisIndex]; + } + } + + qsort(sortedMins, minMaxCount, 4u, compare_floats); + qsort(sortedMaxs, minMaxCount, 4u, compare_floats); + qsort(sortedCoplanar, coplanarCount, 4u, compare_floats); + + sideFrontCount = 0; + sideBackCount = count; + sideSplitCount = 0; + sideOnCount = 0; + minIndex = 0; + maxIndex = 0; + onIndex = 0; + prevMinCount = 0; + prevOnCount = 0; + + // if (*sortedCoplanar - *sortedMins < 0.0f) + if (coplanarCount && *sortedCoplanar - *sortedMins < 0.0f) + v7 = *sortedCoplanar; + else + v7 = *sortedMins; + + nextDist = v7; + while (nextDist < FLT_MAX) + { + dist = nextDist; + nextDist = FLT_MAX; + sideSplitCount += prevMinCount; + sideBackCount -= prevMinCount; + prevMinCount = 0; + + while (minIndex < minMaxCount && sortedMins[minIndex] == dist) + { + ++prevMinCount; + ++minIndex; + } + + if (minIndex < minMaxCount && sortedMins[minIndex] < nextDist) + nextDist = sortedMins[minIndex]; + + while (maxIndex < minMaxCount && sortedMaxs[maxIndex] == dist) + { + ++sideFrontCount; + --sideSplitCount; + ++maxIndex; + } + + if (maxIndex < minMaxCount && nextDist > sortedMaxs[maxIndex]) + nextDist = sortedMaxs[maxIndex]; + + sideFrontCount += prevOnCount; + sideOnCount -= prevOnCount; + prevOnCount = 0; + + while (onIndex < coplanarCount && sortedCoplanar[onIndex] == dist) + { + ++prevOnCount; + ++onIndex; + } + + sideOnCount += prevOnCount; + sideBackCount -= prevOnCount; + + if (onIndex < coplanarCount && nextDist > sortedCoplanar[onIndex]) + nextDist = sortedCoplanar[onIndex]; + + assert(sideFrontCount + sideBackCount + sideSplitCount + sideOnCount == count); + assert(sideFrontCount >= 0); + assert(sideBackCount >= 0); + assert(sideSplitCount >= 0); + assert(sideOnCount >= 0); + + if (sideFrontCount > 1 && sideBackCount > 1) + { + heuristic = axisBias[axisIndex] + count - std::abs(sideFrontCount - sideBackCount) - sideOnCount - 4 * sideSplitCount; + if (!sideOnCount && !sideSplitCount && !prevMinCount) + { + // heuristic += (int)((float)(nextDist - dist) + 9.313225746154785e-10); + heuristic += static_cast(nextDist - dist); + } + + if (heuristic > bestHeuristic) + { + bestHeuristic = heuristic; + *chosenAxis = axisIndex; + if (sideOnCount || sideSplitCount || prevMinCount) + *chosenDist = dist; + else + *chosenDist = (dist + nextDist) * 0.5f; + } + } + } + } + + return bestHeuristic != -1; + } + + void CreateAabbSubTrees(GenericAabbTree* tree, GenericAabbTreeOptions* options, int* remap, int firstIndex, int count) + { + int midStart; // [esp+0h] [ebp-Ch] BYREF + int lastStart; // [esp+4h] [ebp-8h] BYREF + GenericAabbTree* subtree; // [esp+8h] [ebp-4h] + + if (count > options->maxItemsPerLeaf && SplitAabbTree(count, options, &remap[firstIndex], &midStart, &lastStart)) + { + subtree = AllocAabbTreeNode(options); + subtree->firstItem = firstIndex + tree->firstItem; + subtree->itemCount = midStart; + if (midStart < lastStart) + { + subtree = AllocAabbTreeNode(options); + subtree->firstItem = midStart + firstIndex + tree->firstItem; + subtree->itemCount = lastStart - midStart; + } + subtree = AllocAabbTreeNode(options); + subtree->firstItem = lastStart + firstIndex + tree->firstItem; + subtree->itemCount = count - lastStart; + } + else + { + subtree = AllocAabbTreeNode(options); + subtree->firstItem = firstIndex + tree->firstItem; + subtree->itemCount = count; + } + } + + GenericAabbTree* AllocAabbTreeNode(GenericAabbTreeOptions* options) + { + if (aabbTreeCount == options->treeNodeLimit) + { + con::error("More than {} AABB nodes needed", options->treeNodeLimit); + throw std::runtime_error("More than {} AABB nodes needed"); + } + + return &options->treeNodePool[aabbTreeCount++]; + } + + // State + float* sortedMins; + float* sortedMaxs; + float* sortedCoplanar; + int aabbTreeCount; + }; } // namespace namespace xmodel @@ -109,7 +638,82 @@ namespace xmodel tree->scale = Eigen::Vector3f(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()) .cwiseQuotient(globalDelta); - // TODO: Calculate nodes + AabbTreeBuilder aabbTreeBuilder; + GenericAabbTreeOptions options{}; + options.maintainValidBounds = 1; + options.treeNodePool = (GenericAabbTree*)malloc(sizeof(GenericAabbTree) * 0x2000); + options.treeNodeLimit = 0x2000; + options.minItemsPerLeaf = 1; + options.maxItemsPerLeaf = 16; + options.mins = (float (*)[3])malloc(12 * leafBounds.size()); + options.maxs = (float (*)[3])malloc(12 * leafBounds.size()); + options.items = tree->leafs.data(); + options.itemCount = static_cast(leafBounds.size()); + options.itemSize = 2; + + for (size_t leafIndex = 0; leafIndex < leafBounds.size(); ++leafIndex) + { + const auto& bounds = leafBounds[leafIndex]; + options.mins[leafIndex][0] = bounds.m_mins.x(); + options.mins[leafIndex][1] = bounds.m_mins.y(); + options.mins[leafIndex][2] = bounds.m_mins.z(); + options.maxs[leafIndex][0] = bounds.m_maxs.x(); + options.maxs[leafIndex][1] = bounds.m_maxs.y(); + options.maxs[leafIndex][2] = bounds.m_maxs.z(); + } + + const auto nodeCount = aabbTreeBuilder.BuildAabbTree(&options); + + tree->nodes.resize(nodeCount); + for (auto nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) + { + auto& outNode = tree->nodes[nodeIndex]; + const auto& builtNode = options.treeNodePool[nodeIndex]; + + const auto leafEnd = builtNode.itemCount + builtNode.firstItem; + Bounds nodeBounds; + + for (auto leafIndex = builtNode.firstItem; leafIndex < leafEnd; ++leafIndex) + nodeBounds.ExpandFromBounds(options.mins[leafIndex], options.maxs[leafIndex]); + + outNode.aabb.mins[0] = static_cast(std::clamp( + (nodeBounds.m_mins.x() + tree->trans[0]) * tree->scale[0] - 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); + outNode.aabb.mins[1] = static_cast(std::clamp( + (nodeBounds.m_mins.y() + tree->trans[1]) * tree->scale[1] - 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); + outNode.aabb.mins[2] = static_cast(std::clamp( + (nodeBounds.m_mins.z() + tree->trans[2]) * tree->scale[2] - 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); + + outNode.aabb.maxs[0] = static_cast(std::clamp( + (nodeBounds.m_maxs.x() + tree->trans[0]) * tree->scale[0] + 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); + outNode.aabb.maxs[1] = static_cast(std::clamp( + (nodeBounds.m_maxs.y() + tree->trans[1]) * tree->scale[1] + 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); + outNode.aabb.maxs[2] = static_cast(std::clamp( + (nodeBounds.m_maxs.z() + tree->trans[2]) * tree->scale[2] + 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); + + if (builtNode.childCount) + { + outNode.childBeginIndex = builtNode.firstChild; + assert(outNode.childBeginIndex == builtNode.firstChild); + + outNode.childCount = builtNode.childCount; + assert(outNode.childCount == builtNode.childCount); + } + else + { + assert(builtNode.itemCount); + + outNode.childBeginIndex = builtNode.firstItem; + assert(outNode.childBeginIndex == builtNode.firstItem); + + outNode.childCount = builtNode.itemCount; + assert(outNode.childCount == builtNode.itemCount); + + outNode.childrenAreLeafs = true; + } + } + free(options.mins); + free(options.maxs); + free(options.treeNodePool); return std::move(tree); } From a81548e300100b7152c5a5086bce947ccac3d91f Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 24 May 2026 01:03:09 +0200 Subject: [PATCH 5/6] fix: account for possible zero items when building aabb trees --- src/ObjLoading/XModel/CollisionTreeCreator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ObjLoading/XModel/CollisionTreeCreator.cpp b/src/ObjLoading/XModel/CollisionTreeCreator.cpp index bb4b6a88..55065b55 100644 --- a/src/ObjLoading/XModel/CollisionTreeCreator.cpp +++ b/src/ObjLoading/XModel/CollisionTreeCreator.cpp @@ -662,7 +662,7 @@ namespace xmodel options.maxs[leafIndex][2] = bounds.m_maxs.z(); } - const auto nodeCount = aabbTreeBuilder.BuildAabbTree(&options); + const auto nodeCount = options.itemCount > 0 ? aabbTreeBuilder.BuildAabbTree(&options) : 0; tree->nodes.resize(nodeCount); for (auto nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) From e1ec018f096d85c6a63f00d34871e1001d5bfa52 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 24 May 2026 02:43:21 +0200 Subject: [PATCH 6/6] chore: modernize collision tree code --- .../XModel/CollisionTreeCreator.cpp | 574 +++++++----------- .../XModel/LoaderXModel.cpp.template | 2 +- 2 files changed, 211 insertions(+), 365 deletions(-) diff --git a/src/ObjLoading/XModel/CollisionTreeCreator.cpp b/src/ObjLoading/XModel/CollisionTreeCreator.cpp index 55065b55..4e9ec646 100644 --- a/src/ObjLoading/XModel/CollisionTreeCreator.cpp +++ b/src/ObjLoading/XModel/CollisionTreeCreator.cpp @@ -4,32 +4,9 @@ #include #include -#include namespace { - struct GenericAabbTree - { - int firstItem; - int itemCount; - int firstChild; - int childCount; - }; - - struct GenericAabbTreeOptions - { - void* items; - int itemCount; - int itemSize; - int maintainValidBounds; - float (*mins)[3]; - float (*maxs)[3]; - GenericAabbTree* treeNodePool; - int treeNodeLimit; - int minItemsPerLeaf; - int maxItemsPerLeaf; - }; - class Bounds { public: @@ -62,16 +39,6 @@ namespace m_maxs[2] = std::max(m_maxs[2], otherBounds.m_maxs[2]); } - void ExpandFromBounds(const float (&otherMins)[3], const float (&otherMaxs)[3]) - { - m_mins[0] = std::min(m_mins[0], otherMins[0]); - m_mins[1] = std::min(m_mins[1], otherMins[1]); - m_mins[2] = std::min(m_mins[2], otherMins[2]); - m_maxs[0] = std::max(m_maxs[0], otherMaxs[0]); - m_maxs[1] = std::max(m_maxs[1], otherMaxs[1]); - m_maxs[2] = std::max(m_maxs[2], otherMaxs[2]); - } - [[nodiscard]] Eigen::Vector3f GetDelta() const { return m_maxs - m_mins; @@ -82,237 +49,152 @@ namespace return GetDelta().prod(); } + [[nodiscard]] float AddedVolume(const Bounds& addedBounds) const + { + Bounds expandedBounds(*this); + expandedBounds.ExpandFromBounds(addedBounds); + + return expandedBounds.GetVolume() - GetVolume(); + } + Eigen::Vector3f m_mins; Eigen::Vector3f m_maxs; }; - void ClearBounds(float* mins, float* maxs) + constexpr auto MIN_ITEMS_PER_LEAF = 1u; + constexpr auto MAX_ITEMS_PER_LEAF = 16u; + + struct GenericAabbTree { - mins[0] = FLT_MAX; - mins[1] = FLT_MAX; - mins[2] = FLT_MAX; - maxs[0] = -FLT_MAX; - maxs[1] = -FLT_MAX; - maxs[2] = -FLT_MAX; - } - - void ExpandBounds(const float* addedmins, const float* addedmaxs, float* mins, float* maxs) - { - *mins = std::min(*mins, *addedmins); - *maxs = std::max(*addedmaxs, *maxs); - mins[1] = std::min(mins[1], addedmins[1]); - maxs[1] = std::max(addedmaxs[1], maxs[1]); - mins[2] = std::min(mins[2], addedmins[2]); - maxs[2] = std::max(addedmaxs[2], maxs[2]); - } - - float AddedVolume(const float* addedmins, const float* addedmaxs, const float* mins, const float* maxs) - { - float expandedMins; // [esp+0h] [ebp-20h] BYREF - float v6; // [esp+4h] [ebp-1Ch] - float v7; // [esp+8h] [ebp-18h] - float expandedVolume; // [esp+Ch] [ebp-14h] - float expandedMaxs; // [esp+14h] [ebp-Ch] BYREF - float v10; // [esp+18h] [ebp-8h] - float v11; // [esp+1Ch] [ebp-4h] - - expandedMins = *mins; - v6 = mins[1]; - v7 = mins[2]; - expandedMaxs = *maxs; - v10 = maxs[1]; - v11 = maxs[2]; - ExpandBounds(addedmins, addedmaxs, &expandedMins, &expandedMaxs); - expandedVolume = (expandedMaxs - expandedMins) * (v10 - v6) * (v11 - v7); - - return expandedVolume - (*maxs - *mins) * (maxs[1] - mins[1]) * (maxs[2] - mins[2]); - } - - int compare_floats(const void* e0, const void* e1) - { - float diff; // [esp+4h] [ebp-4h] - - diff = *static_cast(e0) - *static_cast(e1); - if (diff >= 0.0) - return diff > 0.0; - else - return -1; - } + size_t firstItem; + size_t itemCount; + size_t firstChild; + size_t childCount; + }; class AabbTreeBuilder { public: - AabbTreeBuilder() - : sortedMins(nullptr), - sortedMaxs(nullptr), - sortedCoplanar(nullptr), - aabbTreeCount(0) + AabbTreeBuilder(std::vector& nodes, std::vector& items, std::vector& itemBounds) + : m_nodes(nodes), + m_items(items), + m_item_bounds(itemBounds) { } - int BuildAabbTree(GenericAabbTreeOptions* options) + size_t BuildAabbTree() { - static constexpr size_t STACK_BUFFER_SIZE = 64; + if (m_items.empty()) + return 0; - float* v2; // [esp+4h] [ebp-454h] - float* v3; // [esp+8h] [ebp-450h] - float* v4; // [esp+Ch] [ebp-44Ch] - float* v5; // [esp+10h] [ebp-448h] - float (*boundCopies)[3]; // [esp+44h] [ebp-414h] - int* remap; // [esp+48h] [ebp-410h] - int itemIndex; // [esp+4Ch] [ebp-40Ch] - int itemIndexa; // [esp+4Ch] [ebp-40Ch] - int itemIndexb; // [esp+4Ch] [ebp-40Ch] - int itemIndexc; // [esp+4Ch] [ebp-40Ch] - int remapBuffer[STACK_BUFFER_SIZE]; // [esp+50h] [ebp-408h] BYREF - float sortedBounds[3][STACK_BUFFER_SIZE]; // [esp+150h] [ebp-308h] BYREF - char* itemCopies; // [esp+454h] [ebp-4h] + const auto itemCount = m_items.size(); - if (options->itemCount > STACK_BUFFER_SIZE) - { - remap = (int*)operator new(4 * options->itemCount); - sortedMins = (float*)operator new(4 * options->itemCount); - sortedMaxs = (float*)operator new(4 * options->itemCount); - sortedCoplanar = (float*)operator new(4 * options->itemCount); - } - else - { - remap = remapBuffer; - sortedMins = sortedBounds[0]; - sortedMaxs = sortedBounds[1]; - sortedCoplanar = sortedBounds[2]; - } + std::vector remap(m_items.size()); + m_sorted_mins = std::vector(m_items.size()); + m_sorted_maxs = std::vector(m_items.size()); + m_sorted_coplanar = std::vector(m_items.size()); - for (itemIndex = 0; itemIndex < options->itemCount; ++itemIndex) - remap[itemIndex] = itemIndex; + std::ranges::iota(remap, 0); - options->treeNodePool->firstItem = 0; - options->treeNodePool->itemCount = options->itemCount; + // Insert root node + m_nodes.emplace_back(GenericAabbTree{ + .firstItem = 0, + .itemCount = itemCount, + .firstChild = 0, + .childCount = 0, + }); - aabbTreeCount = 1; - BuildAabbTree_r(options->treeNodePool, options, remap); + BuildAabbTree_r(0, remap.data()); - itemCopies = (char*)operator new(options->itemSize * options->itemCount); - memcpy(itemCopies, options->items, options->itemSize * options->itemCount); - for (itemIndexa = 0; itemIndexa < options->itemCount; ++itemIndexa) - memcpy((char*)options->items + options->itemSize * itemIndexa, &itemCopies[options->itemSize * remap[itemIndexa]], options->itemSize); - operator delete(itemCopies); + // Reorder items + std::vector itemCopies(itemCount); + memcpy(itemCopies.data(), m_items.data(), sizeof(xmodel::CommonCollisionLeaf) * itemCount); + for (size_t itemIndex = 0; itemIndex < itemCount; ++itemIndex) + m_items[itemIndex] = itemCopies[remap[itemIndex]]; - if (options->maintainValidBounds) - { - boundCopies = - (float (*)[3]) operator new(4 * ((3 * (unsigned __int64)(unsigned int)options->itemCount) >> 32 != 0 ? -1 : 3 * options->itemCount)); - memcpy(boundCopies, options->mins, 12 * options->itemCount); - for (itemIndexb = 0; itemIndexb < options->itemCount; ++itemIndexb) - { - v4 = options->mins[itemIndexb]; - v5 = boundCopies[remap[itemIndexb]]; - *v4 = *v5; - v4[1] = v5[1]; - v4[2] = v5[2]; - } - memcpy(boundCopies, options->maxs, 12 * options->itemCount); - for (itemIndexc = 0; itemIndexc < options->itemCount; ++itemIndexc) - { - v2 = options->maxs[itemIndexc]; - v3 = boundCopies[remap[itemIndexc]]; - *v2 = *v3; - v2[1] = v3[1]; - v2[2] = v3[2]; - } - operator delete(boundCopies); - } - if (remap != remapBuffer) - { - operator delete(remap); - operator delete(sortedMins); - operator delete(sortedMaxs); - operator delete(sortedCoplanar); - } + // Always trying to maintain valid bounds + std::vector boundCopies(itemCount); + std::ranges::copy(m_item_bounds, boundCopies.begin()); + for (size_t itemIndex = 0; itemIndex < itemCount; ++itemIndex) + m_item_bounds[itemIndex] = boundCopies[remap[itemIndex]]; - return aabbTreeCount; + return m_nodes.size(); } private: - void BuildAabbTree_r(GenericAabbTree* tree, GenericAabbTreeOptions* options, int* remap) + void BuildAabbTree_r(size_t treeIndex, size_t* remap) { - int midStart; // [esp+0h] [ebp-10h] BYREF - int childIndex; // [esp+4h] [ebp-Ch] - int lastStart; // [esp+8h] [ebp-8h] BYREF - GenericAabbTree* subtree; // [esp+Ch] [ebp-4h] + assert(m_nodes[treeIndex].itemCount); - assert(tree->itemCount); + m_nodes[treeIndex].firstChild = m_nodes.size(); + m_nodes[treeIndex].childCount = 0; - tree->firstChild = aabbTreeCount; - tree->childCount = 0; - if (tree->itemCount > options->maxItemsPerLeaf && SplitAabbTree(tree->itemCount, options, remap, &midStart, &lastStart)) + size_t midStart, lastStart; + if (m_nodes[treeIndex].itemCount > MAX_ITEMS_PER_LEAF && SplitAabbTree(m_nodes[treeIndex].itemCount, remap, &midStart, &lastStart)) { - subtree = &options->treeNodePool[aabbTreeCount]; + const auto subTreeStartIndex = m_nodes.size(); + assert(m_nodes[treeIndex].firstChild == subTreeStartIndex); - assert(tree->firstChild == aabbTreeCount); + CreateAabbSubTrees(treeIndex, remap, 0, midStart); - CreateAabbSubTrees(tree, options, remap, 0, midStart); if (midStart < lastStart) - CreateAabbSubTrees(tree, options, remap, midStart, lastStart - midStart); - CreateAabbSubTrees(tree, options, remap, lastStart, tree->itemCount - lastStart); - tree->childCount = aabbTreeCount - tree->firstChild; - for (childIndex = 0; childIndex < tree->childCount; ++childIndex) - BuildAabbTree_r(&subtree[childIndex], options, &remap[subtree[childIndex].firstItem - tree->firstItem]); + CreateAabbSubTrees(treeIndex, remap, midStart, lastStart - midStart); + + CreateAabbSubTrees(treeIndex, remap, lastStart, m_nodes[treeIndex].itemCount - lastStart); + + m_nodes[treeIndex].childCount = m_nodes.size() - m_nodes[treeIndex].firstChild; + for (size_t childIndex = 0; childIndex < m_nodes[treeIndex].childCount; ++childIndex) + { + const auto subTreeIndex = subTreeStartIndex + childIndex; + BuildAabbTree_r(subTreeIndex, &remap[m_nodes[subTreeIndex].firstItem - m_nodes[treeIndex].firstItem]); + } } } - int SplitAabbTree(int count, GenericAabbTreeOptions* options, int* remap, int* midStart, int* lastStart) + int SplitAabbTree(const size_t count, size_t* remap, size_t* midStart, size_t* lastStart) { - float v6; // [esp+4h] [ebp-58h] - float v7; // [esp+8h] [ebp-54h] - int top; // [esp+Ch] [ebp-50h] - float (*mins)[3]; // [esp+10h] [ebp-4Ch] - int splitAxis; // [esp+14h] [ebp-48h] BYREF - int bot; // [esp+18h] [ebp-44h] - float bounds[3]; // [esp+1Ch] [ebp-40h] BYREF - float v13[3]; // [esp+28h] [ebp-34h] BYREF - float v14[3]; // [esp+34h] [ebp-28h] BYREF - float v15[3]; // [esp+40h] [ebp-1Ch] BYREF - float (*maxs)[3]; // [esp+4Ch] [ebp-10h] - float splitDist; // [esp+50h] [ebp-Ch] BYREF - int swapCache; // [esp+54h] [ebp-8h] - int mid; // [esp+58h] [ebp-4h] + size_t swapCache; - mins = options->mins; - maxs = options->maxs; - if (!PickAabbSplitPlane(mins, maxs, remap, count, &splitAxis, &splitDist)) + int splitAxis; + float splitDist; + if (!PickAabbSplitPlane(m_item_bounds.data(), remap, count, splitAxis, splitDist)) return 0; - ClearBounds(bounds, v13); - ClearBounds(v14, v15); - bot = 0; - top = count - 1; + + int bot = 0; + int top = static_cast(count - 1); + Bounds bounds[2]; while (bot <= top) { - while (bot <= top && splitDist >= maxs[remap[bot]][splitAxis] && splitDist > mins[remap[bot]][splitAxis]) + while (bot <= top && splitDist >= m_item_bounds[remap[bot]].m_maxs[splitAxis] && splitDist > m_item_bounds[remap[bot]].m_mins[splitAxis]) { - ExpandBounds(mins[remap[bot]], maxs[remap[bot]], bounds, v13); + bounds[0].ExpandFromBounds(m_item_bounds[remap[bot]]); ++bot; } - while (bot <= top && mins[remap[top]][splitAxis] >= splitDist && maxs[remap[top]][splitAxis] > splitDist) + + while (bot <= top && m_item_bounds[remap[top]].m_mins[splitAxis] >= splitDist && m_item_bounds[remap[top]].m_maxs[splitAxis] > splitDist) { - ExpandBounds(mins[remap[top]], maxs[remap[top]], v14, v15); + bounds[1].ExpandFromBounds(m_item_bounds[remap[top]]); --top; } + if (bot > top) break; - if ((mins[remap[bot]][splitAxis] < splitDist || maxs[remap[bot]][splitAxis] <= splitDist) - && (splitDist < maxs[remap[top]][splitAxis] || splitDist <= mins[remap[top]][splitAxis])) + + if ((m_item_bounds[remap[bot]].m_mins[splitAxis] < splitDist || m_item_bounds[remap[bot]].m_maxs[splitAxis] <= splitDist) + && (splitDist < m_item_bounds[remap[top]].m_maxs[splitAxis] || splitDist <= m_item_bounds[remap[top]].m_mins[splitAxis])) { + int mid; for (mid = bot; mid < top; ++mid) { - if (mins[remap[mid]][splitAxis] >= splitDist && maxs[remap[mid]][splitAxis] > splitDist) + if (m_item_bounds[remap[mid]].m_mins[splitAxis] >= splitDist && m_item_bounds[remap[mid]].m_maxs[splitAxis] > splitDist) { swapCache = remap[mid]; remap[mid] = remap[top]; remap[top] = swapCache; break; } - if (splitDist >= maxs[remap[mid]][splitAxis] && splitDist > mins[remap[mid]][splitAxis]) + + if (splitDist >= m_item_bounds[remap[mid]].m_maxs[splitAxis] && splitDist > m_item_bounds[remap[mid]].m_mins[splitAxis]) { swapCache = remap[mid]; remap[mid] = remap[bot]; @@ -320,6 +202,7 @@ namespace break; } } + if (mid == top) break; } @@ -330,31 +213,34 @@ namespace remap[top] = swapCache; } } - if (bot <= top && (bot < options->minItemsPerLeaf || top - bot + 1 < options->minItemsPerLeaf || count - top - 1 < options->minItemsPerLeaf)) + + if (bot <= top && (bot < MIN_ITEMS_PER_LEAF || top - bot + 1 < MIN_ITEMS_PER_LEAF || count - top - 1 < MIN_ITEMS_PER_LEAF)) { while (bot <= top) { while (bot <= top) { - v7 = AddedVolume(mins[remap[bot]], maxs[remap[bot]], bounds, v13); - if (AddedVolume(mins[remap[bot]], maxs[remap[bot]], v14, v15) < (double)v7) + if (bounds[1].AddedVolume(m_item_bounds[remap[bot]]) < bounds[0].AddedVolume(m_item_bounds[remap[bot]])) break; - ExpandBounds(mins[remap[bot]], maxs[remap[bot]], bounds, v13); + + bounds[0].ExpandFromBounds(m_item_bounds[remap[bot]]); ++bot; } + while (bot <= top) { - v6 = AddedVolume(mins[remap[top]], maxs[remap[top]], v14, v15); - if (AddedVolume(mins[remap[top]], maxs[remap[top]], bounds, v13) < (double)v6) + if (bounds[0].AddedVolume(m_item_bounds[remap[top]]) < bounds[1].AddedVolume(m_item_bounds[remap[top]])) break; - ExpandBounds(mins[remap[top]], maxs[remap[top]], v14, v15); + + bounds[1].ExpandFromBounds(m_item_bounds[remap[top]]); --top; } + if (bot >= top) { if (bot == top) { - if (2 * bot >= count) + if (static_cast(2 * bot) >= count) --top; else ++bot; @@ -370,126 +256,107 @@ namespace } } } + if (!bot || bot == count) return 0; + *midStart = bot; *lastStart = top + 1; + return 1; } - bool PickAabbSplitPlane(float (*mins)[3], float (*maxs)[3], int* remap, int count, int* chosenAxis, float* chosenDist) + bool PickAabbSplitPlane(const Bounds* bounds, const size_t* remap, const size_t count, int& chosenAxis, float& chosenDist) { - float v7; // [esp+4h] [ebp-A0h] - int sideSplitCount; // [esp+38h] [ebp-6Ch] - float nextDist; // [esp+3Ch] [ebp-68h] - int prevMinCount; // [esp+40h] [ebp-64h] - int prevOnCount; // [esp+44h] [ebp-60h] - float dist; // [esp+48h] [ebp-5Ch] - signed int minMaxCount; // [esp+4Ch] [ebp-58h] - signed int coplanarCount; // [esp+50h] [ebp-54h] - int axisIndex; // [esp+54h] [ebp-50h] - signed int bestHeuristic; // [esp+58h] [ebp-4Ch] - int smallestAxis; // [esp+5Ch] [ebp-48h] - int maxIndex; // [esp+60h] [ebp-44h] - float globalMaxs[3]; // [esp+64h] [ebp-40h] BYREF - int sideFrontCount; // [esp+70h] [ebp-34h] - int i; // [esp+74h] [ebp-30h] - int sideOnCount; // [esp+78h] [ebp-2Ch] - float globalMins[3]; // [esp+7Ch] [ebp-28h] BYREF - int axisBias[3]; // [esp+88h] [ebp-1Ch] - int minIndex; // [esp+94h] [ebp-10h] - int onIndex; // [esp+98h] [ebp-Ch] - int heuristic; // [esp+9Ch] [ebp-8h] - int sideBackCount; // [esp+A0h] [ebp-4h] + Bounds globalBounds; - ClearBounds(globalMins, globalMaxs); + for (size_t i = 0; i < count; ++i) + globalBounds.ExpandFromBounds(bounds[remap[i]]); - for (i = 0; i < count; ++i) - ExpandBounds(mins[remap[i]], maxs[remap[i]], globalMins, globalMaxs); - - smallestAxis = globalMaxs[0] - globalMins[0] > globalMaxs[1] - globalMins[1]; - if (globalMaxs[smallestAxis] - globalMins[smallestAxis] > globalMaxs[2] - globalMins[2]) + size_t smallestAxis = globalBounds.m_maxs[0] - globalBounds.m_mins[0] > globalBounds.m_maxs[1] - globalBounds.m_mins[1]; + if (globalBounds.m_maxs[smallestAxis] - globalBounds.m_mins[smallestAxis] > globalBounds.m_maxs[2] - globalBounds.m_mins[2]) smallestAxis = 2; - for (i = 0; i < 3; ++i) + int axisBias[3]; + for (size_t i = 0; i < 3u; ++i) { - axisBias[i] = static_cast((globalMaxs[i] - globalMins[i] + 1.0f) * 10.0f / (globalMaxs[smallestAxis] - globalMins[smallestAxis] + 1.0f) + axisBias[i] = static_cast((globalBounds.m_maxs[i] - globalBounds.m_mins[i] + 1.0f) * 10.0f + / (globalBounds.m_maxs[smallestAxis] - globalBounds.m_mins[smallestAxis] + 1.0f) + 0.4999999990686774); } - bestHeuristic = -1; - - for (axisIndex = 0; axisIndex < 3; ++axisIndex) + auto bestHeuristic = -1; + for (int axisIndex = 0; axisIndex < 3; ++axisIndex) { - minMaxCount = 0; - coplanarCount = 0; + size_t minMaxCount = 0; + size_t coplanarCount = 0; - for (i = 0; i < count; ++i) + for (size_t i = 0; i < count; ++i) { - if (mins[remap[i]][axisIndex] == maxs[remap[i]][axisIndex]) + if (bounds[remap[i]].m_mins[axisIndex] == bounds[remap[i]].m_maxs[axisIndex]) { - sortedCoplanar[coplanarCount++] = mins[remap[i]][axisIndex]; + m_sorted_coplanar[coplanarCount++] = bounds[remap[i]].m_mins[axisIndex]; } else { - sortedMins[minMaxCount] = mins[remap[i]][axisIndex]; - sortedMaxs[minMaxCount++] = maxs[remap[i]][axisIndex]; + m_sorted_mins[minMaxCount] = bounds[remap[i]].m_mins[axisIndex]; + m_sorted_maxs[minMaxCount++] = bounds[remap[i]].m_maxs[axisIndex]; } } - qsort(sortedMins, minMaxCount, 4u, compare_floats); - qsort(sortedMaxs, minMaxCount, 4u, compare_floats); - qsort(sortedCoplanar, coplanarCount, 4u, compare_floats); + std::sort(&m_sorted_mins[0], &m_sorted_mins[minMaxCount], std::greater()); + std::sort(&m_sorted_maxs[0], &m_sorted_maxs[minMaxCount], std::greater()); + std::sort(&m_sorted_coplanar[0], &m_sorted_coplanar[coplanarCount], std::greater()); - sideFrontCount = 0; - sideBackCount = count; - sideSplitCount = 0; - sideOnCount = 0; - minIndex = 0; - maxIndex = 0; - onIndex = 0; - prevMinCount = 0; - prevOnCount = 0; + int sideFrontCount = 0; + int sideBackCount = static_cast(count); + int sideSplitCount = 0; + int sideOnCount = 0; + int prevMinCount = 0; + int prevOnCount = 0; - // if (*sortedCoplanar - *sortedMins < 0.0f) - if (coplanarCount && *sortedCoplanar - *sortedMins < 0.0f) - v7 = *sortedCoplanar; + float nextDist; + if (coplanarCount && m_sorted_coplanar[0] - m_sorted_mins[0] < 0.0f) + nextDist = m_sorted_coplanar[0]; else - v7 = *sortedMins; + nextDist = m_sorted_mins[0]; - nextDist = v7; - while (nextDist < FLT_MAX) + size_t minIndex = 0; + size_t maxIndex = 0; + size_t onIndex = 0; + while (nextDist < std::numeric_limits::max()) { - dist = nextDist; - nextDist = FLT_MAX; + const auto dist = nextDist; + nextDist = std::numeric_limits::max(); + sideSplitCount += prevMinCount; sideBackCount -= prevMinCount; prevMinCount = 0; - while (minIndex < minMaxCount && sortedMins[minIndex] == dist) + while (minIndex < minMaxCount && m_sorted_mins[minIndex] == dist) { ++prevMinCount; ++minIndex; } - if (minIndex < minMaxCount && sortedMins[minIndex] < nextDist) - nextDist = sortedMins[minIndex]; + if (minIndex < minMaxCount && m_sorted_mins[minIndex] < nextDist) + nextDist = m_sorted_mins[minIndex]; - while (maxIndex < minMaxCount && sortedMaxs[maxIndex] == dist) + while (maxIndex < minMaxCount && m_sorted_maxs[maxIndex] == dist) { ++sideFrontCount; --sideSplitCount; ++maxIndex; } - if (maxIndex < minMaxCount && nextDist > sortedMaxs[maxIndex]) - nextDist = sortedMaxs[maxIndex]; + if (maxIndex < minMaxCount && nextDist > m_sorted_maxs[maxIndex]) + nextDist = m_sorted_maxs[maxIndex]; sideFrontCount += prevOnCount; sideOnCount -= prevOnCount; prevOnCount = 0; - while (onIndex < coplanarCount && sortedCoplanar[onIndex] == dist) + while (onIndex < coplanarCount && m_sorted_coplanar[onIndex] == dist) { ++prevOnCount; ++onIndex; @@ -498,8 +365,8 @@ namespace sideOnCount += prevOnCount; sideBackCount -= prevOnCount; - if (onIndex < coplanarCount && nextDist > sortedCoplanar[onIndex]) - nextDist = sortedCoplanar[onIndex]; + if (onIndex < coplanarCount && nextDist > m_sorted_coplanar[onIndex]) + nextDist = m_sorted_coplanar[onIndex]; assert(sideFrontCount + sideBackCount + sideSplitCount + sideOnCount == count); assert(sideFrontCount >= 0); @@ -509,21 +376,20 @@ namespace if (sideFrontCount > 1 && sideBackCount > 1) { - heuristic = axisBias[axisIndex] + count - std::abs(sideFrontCount - sideBackCount) - sideOnCount - 4 * sideSplitCount; + int heuristic = + axisBias[axisIndex] + static_cast(count) - std::abs(sideFrontCount - sideBackCount) - sideOnCount - 4 * sideSplitCount; + if (!sideOnCount && !sideSplitCount && !prevMinCount) - { - // heuristic += (int)((float)(nextDist - dist) + 9.313225746154785e-10); heuristic += static_cast(nextDist - dist); - } if (heuristic > bestHeuristic) { bestHeuristic = heuristic; - *chosenAxis = axisIndex; + chosenAxis = axisIndex; if (sideOnCount || sideSplitCount || prevMinCount) - *chosenDist = dist; + chosenDist = dist; else - *chosenDist = (dist + nextDist) * 0.5f; + chosenDist = (dist + nextDist) * 0.5f; } } } @@ -532,51 +398,55 @@ namespace return bestHeuristic != -1; } - void CreateAabbSubTrees(GenericAabbTree* tree, GenericAabbTreeOptions* options, int* remap, int firstIndex, int count) + void CreateAabbSubTrees(const size_t treeIndex, size_t* remap, const size_t firstIndex, const size_t count) { - int midStart; // [esp+0h] [ebp-Ch] BYREF - int lastStart; // [esp+4h] [ebp-8h] BYREF - GenericAabbTree* subtree; // [esp+8h] [ebp-4h] - - if (count > options->maxItemsPerLeaf && SplitAabbTree(count, options, &remap[firstIndex], &midStart, &lastStart)) + size_t midStart, lastStart; + if (count > MAX_ITEMS_PER_LEAF && SplitAabbTree(count, &remap[firstIndex], &midStart, &lastStart)) { - subtree = AllocAabbTreeNode(options); - subtree->firstItem = firstIndex + tree->firstItem; - subtree->itemCount = midStart; + m_nodes.emplace_back(GenericAabbTree{ + .firstItem = firstIndex + m_nodes[treeIndex].firstItem, + .itemCount = midStart, + .firstChild = 0, + .childCount = 0, + }); + if (midStart < lastStart) { - subtree = AllocAabbTreeNode(options); - subtree->firstItem = midStart + firstIndex + tree->firstItem; - subtree->itemCount = lastStart - midStart; + m_nodes.emplace_back(GenericAabbTree{ + .firstItem = midStart + firstIndex + m_nodes[treeIndex].firstItem, + .itemCount = lastStart - midStart, + .firstChild = 0, + .childCount = 0, + }); } - subtree = AllocAabbTreeNode(options); - subtree->firstItem = lastStart + firstIndex + tree->firstItem; - subtree->itemCount = count - lastStart; + + m_nodes.emplace_back(GenericAabbTree{ + .firstItem = lastStart + firstIndex + m_nodes[treeIndex].firstItem, + .itemCount = count - lastStart, + .firstChild = 0, + .childCount = 0, + }); } else { - subtree = AllocAabbTreeNode(options); - subtree->firstItem = firstIndex + tree->firstItem; - subtree->itemCount = count; + m_nodes.emplace_back(GenericAabbTree{ + .firstItem = firstIndex + m_nodes[treeIndex].firstItem, + .itemCount = count, + .firstChild = 0, + .childCount = 0, + }); } } - GenericAabbTree* AllocAabbTreeNode(GenericAabbTreeOptions* options) - { - if (aabbTreeCount == options->treeNodeLimit) - { - con::error("More than {} AABB nodes needed", options->treeNodeLimit); - throw std::runtime_error("More than {} AABB nodes needed"); - } - - return &options->treeNodePool[aabbTreeCount++]; - } + // Options + std::vector& m_nodes; + std::vector& m_items; + std::vector& m_item_bounds; // State - float* sortedMins; - float* sortedMaxs; - float* sortedCoplanar; - int aabbTreeCount; + std::vector m_sorted_mins; + std::vector m_sorted_maxs; + std::vector m_sorted_coplanar; }; } // namespace @@ -638,43 +508,22 @@ namespace xmodel tree->scale = Eigen::Vector3f(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()) .cwiseQuotient(globalDelta); - AabbTreeBuilder aabbTreeBuilder; - GenericAabbTreeOptions options{}; - options.maintainValidBounds = 1; - options.treeNodePool = (GenericAabbTree*)malloc(sizeof(GenericAabbTree) * 0x2000); - options.treeNodeLimit = 0x2000; - options.minItemsPerLeaf = 1; - options.maxItemsPerLeaf = 16; - options.mins = (float (*)[3])malloc(12 * leafBounds.size()); - options.maxs = (float (*)[3])malloc(12 * leafBounds.size()); - options.items = tree->leafs.data(); - options.itemCount = static_cast(leafBounds.size()); - options.itemSize = 2; - - for (size_t leafIndex = 0; leafIndex < leafBounds.size(); ++leafIndex) - { - const auto& bounds = leafBounds[leafIndex]; - options.mins[leafIndex][0] = bounds.m_mins.x(); - options.mins[leafIndex][1] = bounds.m_mins.y(); - options.mins[leafIndex][2] = bounds.m_mins.z(); - options.maxs[leafIndex][0] = bounds.m_maxs.x(); - options.maxs[leafIndex][1] = bounds.m_maxs.y(); - options.maxs[leafIndex][2] = bounds.m_maxs.z(); - } - - const auto nodeCount = options.itemCount > 0 ? aabbTreeBuilder.BuildAabbTree(&options) : 0; + std::vector nodes; + AabbTreeBuilder aabbTreeBuilder(nodes, tree->leafs, leafBounds); + aabbTreeBuilder.BuildAabbTree(); + const auto nodeCount = nodes.size(); tree->nodes.resize(nodeCount); - for (auto nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) + for (size_t nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) { auto& outNode = tree->nodes[nodeIndex]; - const auto& builtNode = options.treeNodePool[nodeIndex]; + const auto& builtNode = nodes[nodeIndex]; const auto leafEnd = builtNode.itemCount + builtNode.firstItem; Bounds nodeBounds; for (auto leafIndex = builtNode.firstItem; leafIndex < leafEnd; ++leafIndex) - nodeBounds.ExpandFromBounds(options.mins[leafIndex], options.maxs[leafIndex]); + nodeBounds.ExpandFromBounds(leafBounds[leafIndex]); outNode.aabb.mins[0] = static_cast(std::clamp( (nodeBounds.m_mins.x() + tree->trans[0]) * tree->scale[0] - 0.5f, std::numeric_limits::min(), std::numeric_limits::max())); @@ -692,28 +541,25 @@ namespace xmodel if (builtNode.childCount) { - outNode.childBeginIndex = builtNode.firstChild; - assert(outNode.childBeginIndex == builtNode.firstChild); + assert(builtNode.firstChild <= std::numeric_limits::max()); + outNode.childBeginIndex = static_cast(builtNode.firstChild); - outNode.childCount = builtNode.childCount; - assert(outNode.childCount == builtNode.childCount); + assert(builtNode.childCount <= std::numeric_limits::max()); + outNode.childCount = static_cast(builtNode.childCount); } else { assert(builtNode.itemCount); - outNode.childBeginIndex = builtNode.firstItem; - assert(outNode.childBeginIndex == builtNode.firstItem); + assert(builtNode.firstItem <= std::numeric_limits::max()); + outNode.childBeginIndex = static_cast(builtNode.firstItem); - outNode.childCount = builtNode.itemCount; - assert(outNode.childCount == builtNode.itemCount); + assert(builtNode.itemCount <= std::numeric_limits::max()); + outNode.childCount = static_cast(builtNode.itemCount); outNode.childrenAreLeafs = true; } } - free(options.mins); - free(options.maxs); - free(options.treeNodePool); return std::move(tree); } diff --git a/src/ObjLoading/XModel/LoaderXModel.cpp.template b/src/ObjLoading/XModel/LoaderXModel.cpp.template index 923a1a13..c5efdd94 100644 --- a/src/ObjLoading/XModel/LoaderXModel.cpp.template +++ b/src/ObjLoading/XModel/LoaderXModel.cpp.template @@ -576,7 +576,7 @@ namespace node.childCount |= XSURFACE_COLLISION_NODE_HAS_LEAFS; } - tree->leafCount = commonTree.leafs.size(); + tree->leafCount = static_castleafCount)>(commonTree.leafs.size()); tree->leafs = m_memory.Alloc(tree->leafCount); for (auto leafIndex = 0u; leafIndex < tree->leafCount; leafIndex++) {