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;