2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-05-25 02:51:43 +00:00

Merge pull request #790 from Laupetin/feature/xmodel-collision-tree

feat: xmodel collision tree
This commit is contained in:
Jan
2026-05-24 12:10:44 +02:00
committed by GitHub
8 changed files with 769 additions and 12 deletions
+2 -2
View File
@@ -327,8 +327,8 @@ namespace IW3
struct XSurfaceCollisionTree struct XSurfaceCollisionTree
{ {
float trans[3]; vec3_t trans;
float scale[3]; vec3_t scale;
unsigned int nodeCount; unsigned int nodeCount;
XSurfaceCollisionNode* nodes; XSurfaceCollisionNode* nodes;
unsigned int leafCount; unsigned int leafCount;
+2 -2
View File
@@ -468,8 +468,8 @@ namespace IW4
struct XSurfaceCollisionTree struct XSurfaceCollisionTree
{ {
float trans[3]; vec3_t trans;
float scale[3]; vec3_t scale;
unsigned int nodeCount; unsigned int nodeCount;
XSurfaceCollisionNode* nodes; XSurfaceCollisionNode* nodes;
unsigned int leafCount; unsigned int leafCount;
+7 -2
View File
@@ -492,8 +492,8 @@ namespace IW5
struct XSurfaceCollisionTree struct XSurfaceCollisionTree
{ {
float trans[3]; vec3_t trans;
float scale[3]; vec3_t scale;
unsigned int nodeCount; unsigned int nodeCount;
XSurfaceCollisionNode* nodes; XSurfaceCollisionNode* nodes;
unsigned int leafCount; unsigned int leafCount;
@@ -516,6 +516,11 @@ namespace IW5
typedef tdef_align32(16) XSurfaceTri XSurfaceTri16; typedef tdef_align32(16) XSurfaceTri XSurfaceTri16;
enum XSurfaceFlag
{
XSURFACE_FLAG_DEFORMED = 0x40
};
struct XSurface struct XSurface
{ {
unsigned char tileMode; unsigned char tileMode;
+2 -2
View File
@@ -488,8 +488,8 @@ namespace T5
struct XSurfaceCollisionTree struct XSurfaceCollisionTree
{ {
float trans[3]; vec3_t trans;
float scale[3]; vec3_t scale;
unsigned int nodeCount; unsigned int nodeCount;
XSurfaceCollisionNode* nodes; XSurfaceCollisionNode* nodes;
unsigned int leafCount; unsigned int leafCount;
@@ -0,0 +1,566 @@
#include "CollisionTreeCreator.h"
#include "Utils/Logging/Log.h"
#include <algorithm>
#include <cassert>
namespace
{
class Bounds
{
public:
Bounds()
: m_mins(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max()),
m_maxs(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::max(), -std::numeric_limits<float>::max())
{
}
void ExpandFromTri(const std::array<Eigen::Vector3f, 3>& 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::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]);
}
[[nodiscard]] Eigen::Vector3f GetDelta() const
{
return m_maxs - m_mins;
}
[[nodiscard]] float GetVolume() const
{
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;
};
constexpr auto MIN_ITEMS_PER_LEAF = 1u;
constexpr auto MAX_ITEMS_PER_LEAF = 16u;
struct GenericAabbTree
{
size_t firstItem;
size_t itemCount;
size_t firstChild;
size_t childCount;
};
class AabbTreeBuilder
{
public:
AabbTreeBuilder(std::vector<GenericAabbTree>& nodes, std::vector<xmodel::CommonCollisionLeaf>& items, std::vector<Bounds>& itemBounds)
: m_nodes(nodes),
m_items(items),
m_item_bounds(itemBounds)
{
}
size_t BuildAabbTree()
{
if (m_items.empty())
return 0;
const auto itemCount = m_items.size();
std::vector<size_t> remap(m_items.size());
m_sorted_mins = std::vector<float>(m_items.size());
m_sorted_maxs = std::vector<float>(m_items.size());
m_sorted_coplanar = std::vector<float>(m_items.size());
std::ranges::iota(remap, 0);
// Insert root node
m_nodes.emplace_back(GenericAabbTree{
.firstItem = 0,
.itemCount = itemCount,
.firstChild = 0,
.childCount = 0,
});
BuildAabbTree_r(0, remap.data());
// Reorder items
std::vector<xmodel::CommonCollisionLeaf> 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]];
// Always trying to maintain valid bounds
std::vector<Bounds> 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 m_nodes.size();
}
private:
void BuildAabbTree_r(size_t treeIndex, size_t* remap)
{
assert(m_nodes[treeIndex].itemCount);
m_nodes[treeIndex].firstChild = m_nodes.size();
m_nodes[treeIndex].childCount = 0;
size_t midStart, lastStart;
if (m_nodes[treeIndex].itemCount > MAX_ITEMS_PER_LEAF && SplitAabbTree(m_nodes[treeIndex].itemCount, remap, &midStart, &lastStart))
{
const auto subTreeStartIndex = m_nodes.size();
assert(m_nodes[treeIndex].firstChild == subTreeStartIndex);
CreateAabbSubTrees(treeIndex, remap, 0, midStart);
if (midStart < lastStart)
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(const size_t count, size_t* remap, size_t* midStart, size_t* lastStart)
{
size_t swapCache;
int splitAxis;
float splitDist;
if (!PickAabbSplitPlane(m_item_bounds.data(), remap, count, splitAxis, splitDist))
return 0;
int bot = 0;
int top = static_cast<int>(count - 1);
Bounds bounds[2];
while (bot <= top)
{
while (bot <= top && splitDist >= m_item_bounds[remap[bot]].m_maxs[splitAxis] && splitDist > m_item_bounds[remap[bot]].m_mins[splitAxis])
{
bounds[0].ExpandFromBounds(m_item_bounds[remap[bot]]);
++bot;
}
while (bot <= top && m_item_bounds[remap[top]].m_mins[splitAxis] >= splitDist && m_item_bounds[remap[top]].m_maxs[splitAxis] > splitDist)
{
bounds[1].ExpandFromBounds(m_item_bounds[remap[top]]);
--top;
}
if (bot > top)
break;
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 (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 >= m_item_bounds[remap[mid]].m_maxs[splitAxis] && splitDist > m_item_bounds[remap[mid]].m_mins[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 < MIN_ITEMS_PER_LEAF || top - bot + 1 < MIN_ITEMS_PER_LEAF || count - top - 1 < MIN_ITEMS_PER_LEAF))
{
while (bot <= top)
{
while (bot <= top)
{
if (bounds[1].AddedVolume(m_item_bounds[remap[bot]]) < bounds[0].AddedVolume(m_item_bounds[remap[bot]]))
break;
bounds[0].ExpandFromBounds(m_item_bounds[remap[bot]]);
++bot;
}
while (bot <= top)
{
if (bounds[0].AddedVolume(m_item_bounds[remap[top]]) < bounds[1].AddedVolume(m_item_bounds[remap[top]]))
break;
bounds[1].ExpandFromBounds(m_item_bounds[remap[top]]);
--top;
}
if (bot >= top)
{
if (bot == top)
{
if (static_cast<size_t>(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(const Bounds* bounds, const size_t* remap, const size_t count, int& chosenAxis, float& chosenDist)
{
Bounds globalBounds;
for (size_t i = 0; i < count; ++i)
globalBounds.ExpandFromBounds(bounds[remap[i]]);
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;
int axisBias[3];
for (size_t i = 0; i < 3u; ++i)
{
axisBias[i] = static_cast<int>((globalBounds.m_maxs[i] - globalBounds.m_mins[i] + 1.0f) * 10.0f
/ (globalBounds.m_maxs[smallestAxis] - globalBounds.m_mins[smallestAxis] + 1.0f)
+ 0.4999999990686774);
}
auto bestHeuristic = -1;
for (int axisIndex = 0; axisIndex < 3; ++axisIndex)
{
size_t minMaxCount = 0;
size_t coplanarCount = 0;
for (size_t i = 0; i < count; ++i)
{
if (bounds[remap[i]].m_mins[axisIndex] == bounds[remap[i]].m_maxs[axisIndex])
{
m_sorted_coplanar[coplanarCount++] = bounds[remap[i]].m_mins[axisIndex];
}
else
{
m_sorted_mins[minMaxCount] = bounds[remap[i]].m_mins[axisIndex];
m_sorted_maxs[minMaxCount++] = bounds[remap[i]].m_maxs[axisIndex];
}
}
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());
int sideFrontCount = 0;
int sideBackCount = static_cast<int>(count);
int sideSplitCount = 0;
int sideOnCount = 0;
int prevMinCount = 0;
int prevOnCount = 0;
float nextDist;
if (coplanarCount && m_sorted_coplanar[0] - m_sorted_mins[0] < 0.0f)
nextDist = m_sorted_coplanar[0];
else
nextDist = m_sorted_mins[0];
size_t minIndex = 0;
size_t maxIndex = 0;
size_t onIndex = 0;
while (nextDist < std::numeric_limits<float>::max())
{
const auto dist = nextDist;
nextDist = std::numeric_limits<float>::max();
sideSplitCount += prevMinCount;
sideBackCount -= prevMinCount;
prevMinCount = 0;
while (minIndex < minMaxCount && m_sorted_mins[minIndex] == dist)
{
++prevMinCount;
++minIndex;
}
if (minIndex < minMaxCount && m_sorted_mins[minIndex] < nextDist)
nextDist = m_sorted_mins[minIndex];
while (maxIndex < minMaxCount && m_sorted_maxs[maxIndex] == dist)
{
++sideFrontCount;
--sideSplitCount;
++maxIndex;
}
if (maxIndex < minMaxCount && nextDist > m_sorted_maxs[maxIndex])
nextDist = m_sorted_maxs[maxIndex];
sideFrontCount += prevOnCount;
sideOnCount -= prevOnCount;
prevOnCount = 0;
while (onIndex < coplanarCount && m_sorted_coplanar[onIndex] == dist)
{
++prevOnCount;
++onIndex;
}
sideOnCount += prevOnCount;
sideBackCount -= prevOnCount;
if (onIndex < coplanarCount && nextDist > m_sorted_coplanar[onIndex])
nextDist = m_sorted_coplanar[onIndex];
assert(sideFrontCount + sideBackCount + sideSplitCount + sideOnCount == count);
assert(sideFrontCount >= 0);
assert(sideBackCount >= 0);
assert(sideSplitCount >= 0);
assert(sideOnCount >= 0);
if (sideFrontCount > 1 && sideBackCount > 1)
{
int heuristic =
axisBias[axisIndex] + static_cast<int>(count) - std::abs(sideFrontCount - sideBackCount) - sideOnCount - 4 * sideSplitCount;
if (!sideOnCount && !sideSplitCount && !prevMinCount)
heuristic += static_cast<int>(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(const size_t treeIndex, size_t* remap, const size_t firstIndex, const size_t count)
{
size_t midStart, lastStart;
if (count > MAX_ITEMS_PER_LEAF && SplitAabbTree(count, &remap[firstIndex], &midStart, &lastStart))
{
m_nodes.emplace_back(GenericAabbTree{
.firstItem = firstIndex + m_nodes[treeIndex].firstItem,
.itemCount = midStart,
.firstChild = 0,
.childCount = 0,
});
if (midStart < lastStart)
{
m_nodes.emplace_back(GenericAabbTree{
.firstItem = midStart + firstIndex + m_nodes[treeIndex].firstItem,
.itemCount = lastStart - midStart,
.firstChild = 0,
.childCount = 0,
});
}
m_nodes.emplace_back(GenericAabbTree{
.firstItem = lastStart + firstIndex + m_nodes[treeIndex].firstItem,
.itemCount = count - lastStart,
.firstChild = 0,
.childCount = 0,
});
}
else
{
m_nodes.emplace_back(GenericAabbTree{
.firstItem = firstIndex + m_nodes[treeIndex].firstItem,
.itemCount = count,
.firstChild = 0,
.childCount = 0,
});
}
}
// Options
std::vector<GenericAabbTree>& m_nodes;
std::vector<xmodel::CommonCollisionLeaf>& m_items;
std::vector<Bounds>& m_item_bounds;
// State
std::vector<float> m_sorted_mins;
std::vector<float> m_sorted_maxs;
std::vector<float> m_sorted_coplanar;
};
} // namespace
namespace xmodel
{
std::unique_ptr<CommonCollisionTree> CreateCollisionTree(const IRigidVertListAccessor& vertList)
{
auto tree = std::make_unique<CommonCollisionTree>();
Bounds globalBounds;
const auto triOffset = vertList.GetTriOffset();
const auto triCount = vertList.GetTriCount();
std::vector<Bounds> 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<decltype(CommonCollisionLeaf::triangleBeginIndex)>(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<uint16_t>::max(), std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max())
.cwiseQuotient(globalDelta);
std::vector<GenericAabbTree> nodes;
AabbTreeBuilder aabbTreeBuilder(nodes, tree->leafs, leafBounds);
aabbTreeBuilder.BuildAabbTree();
const auto nodeCount = nodes.size();
tree->nodes.resize(nodeCount);
for (size_t nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex)
{
auto& outNode = tree->nodes[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(leafBounds[leafIndex]);
outNode.aabb.mins[0] = static_cast<uint16_t>(std::clamp<float>(
(nodeBounds.m_mins.x() + tree->trans[0]) * tree->scale[0] - 0.5f, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()));
outNode.aabb.mins[1] = static_cast<uint16_t>(std::clamp<float>(
(nodeBounds.m_mins.y() + tree->trans[1]) * tree->scale[1] - 0.5f, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()));
outNode.aabb.mins[2] = static_cast<uint16_t>(std::clamp<float>(
(nodeBounds.m_mins.z() + tree->trans[2]) * tree->scale[2] - 0.5f, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()));
outNode.aabb.maxs[0] = static_cast<uint16_t>(std::clamp<float>(
(nodeBounds.m_maxs.x() + tree->trans[0]) * tree->scale[0] + 0.5f, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()));
outNode.aabb.maxs[1] = static_cast<uint16_t>(std::clamp<float>(
(nodeBounds.m_maxs.y() + tree->trans[1]) * tree->scale[1] + 0.5f, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()));
outNode.aabb.maxs[2] = static_cast<uint16_t>(std::clamp<float>(
(nodeBounds.m_maxs.z() + tree->trans[2]) * tree->scale[2] + 0.5f, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()));
if (builtNode.childCount)
{
assert(builtNode.firstChild <= std::numeric_limits<decltype(outNode.childBeginIndex)>::max());
outNode.childBeginIndex = static_cast<decltype(outNode.childBeginIndex)>(builtNode.firstChild);
assert(builtNode.childCount <= std::numeric_limits<decltype(outNode.childCount)>::max());
outNode.childCount = static_cast<decltype(outNode.childCount)>(builtNode.childCount);
}
else
{
assert(builtNode.itemCount);
assert(builtNode.firstItem <= std::numeric_limits<decltype(outNode.childBeginIndex)>::max());
outNode.childBeginIndex = static_cast<decltype(outNode.childBeginIndex)>(builtNode.firstItem);
assert(builtNode.itemCount <= std::numeric_limits<decltype(outNode.childCount)>::max());
outNode.childCount = static_cast<decltype(outNode.childCount)>(builtNode.itemCount);
outNode.childrenAreLeafs = true;
}
}
return std::move(tree);
}
} // namespace xmodel
@@ -0,0 +1,55 @@
#pragma once
#include <Eigen>
#include <array>
#include <cstdint>
#include <memory>
#include <vector>
namespace xmodel
{
struct CommonCollisionAabb
{
std::array<uint16_t, 3> mins;
std::array<uint16_t, 3> 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<CommonCollisionNode> nodes;
std::vector<CommonCollisionLeaf> 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<Eigen::Vector3f, 3> GetCoordinatesForTri(size_t triIndex) const = 0;
};
std::unique_ptr<CommonCollisionTree> CreateCollisionTree(const IRigidVertListAccessor& vertList);
} // namespace xmodel
+123 -4
View File
@@ -9,12 +9,16 @@
#if GAME == "IW3" #if GAME == "IW3"
#define FEATURE_IW3 #define FEATURE_IW3
#define EVEN_TRI_COUNT
#elif GAME == "IW4" #elif GAME == "IW4"
#define FEATURE_IW4 #define FEATURE_IW4
#define EVEN_TRI_COUNT
#elif GAME == "IW5" #elif GAME == "IW5"
#define FEATURE_IW5 #define FEATURE_IW5
#define EVEN_TRI_COUNT
#elif GAME == "T5" #elif GAME == "T5"
#define FEATURE_T5 #define FEATURE_T5
#define EVEN_TRI_COUNT
#elif GAME == "T6" #elif GAME == "T6"
#define FEATURE_T6 #define FEATURE_T6
#endif #endif
@@ -26,6 +30,7 @@
#include JSON_HEADER #include JSON_HEADER
#include "Asset/AssetRegistration.h" #include "Asset/AssetRegistration.h"
#include "Utils/Alignment.h"
#include "Utils/Logging/Log.h" #include "Utils/Logging/Log.h"
#include "Utils/QuatInt16.h" #include "Utils/QuatInt16.h"
#include "Utils/StringUtils.h" #include "Utils/StringUtils.h"
@@ -39,6 +44,7 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#pragma warning(pop) #pragma warning(pop)
#include "XModel/CollisionTreeCreator.h"
#include "XModel/PartClassificationState.h" #include "XModel/PartClassificationState.h"
#include "XModel/TangentData.h" #include "XModel/TangentData.h"
#include "XModel/Tangentspace.h" #include "XModel/Tangentspace.h"
@@ -54,6 +60,47 @@ using namespace GAME;
namespace 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<Eigen::Vector3f, 3> GetCoordinatesForTri(const size_t triIndex) const override
{
return std::array<Eigen::Vector3f, 3>({
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<AssetXModel> class XModelLoader final : public AssetCreator<AssetXModel>
{ {
public: public:
@@ -501,6 +548,49 @@ namespace
surface.partBits[partBitsIndex] |= 1 << shiftValue; surface.partBits[partBitsIndex] |= 1 << shiftValue;
} }
[[nodiscard]] XSurfaceCollisionTree* ConvertCollisionTree(const xmodel::CommonCollisionTree& commonTree) const
{
auto* tree = m_memory.Alloc<XSurfaceCollisionTree>();
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<decltype(XSurfaceCollisionTree::nodeCount)>(commonTree.nodes.size());
tree->nodes = m_memory.Alloc<XSurfaceCollisionNode>(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 = static_cast<decltype(tree->leafCount)>(commonTree.leafs.size());
tree->leafs = m_memory.Alloc<XSurfaceCollisionLeaf>(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<size_t>& xmodelToCommonVertexIndexLookup, const XModelCommon& common) const void CreateVertListData(XSurface& surface, const std::vector<size_t>& xmodelToCommonVertexIndexLookup, const XModelCommon& common) const
{ {
ReorderRigidTrisByBoneIndex(xmodelToCommonVertexIndexLookup, surface, common); ReorderRigidTrisByBoneIndex(xmodelToCommonVertexIndexLookup, surface, common);
@@ -533,7 +623,10 @@ namespace
if (boneVertList.triCount > 0 || boneVertList.vertCount > 0) 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); vertLists.emplace_back(boneVertList);
currentVertexTail = currentVertexHead; currentVertexTail = currentVertexHead;
@@ -723,7 +816,14 @@ namespace
} }
surface.triCount = static_cast<uint16_t>(commonObject.m_faces.size()); surface.triCount = static_cast<uint16_t>(commonObject.m_faces.size());
#ifdef EVEN_TRI_COUNT
// Some games round up to an even tri count
const auto allocatedTriCount = utils::Align<unsigned>(surface.triCount, 2);
surface.triIndices = m_memory.Alloc<XSurfaceTri>(allocatedTriCount);
#else
surface.triIndices = m_memory.Alloc<XSurfaceTri>(surface.triCount); surface.triIndices = m_memory.Alloc<XSurfaceTri>(surface.triCount);
#endif
xmodelToCommonVertexIndexLookup.reserve(static_cast<size_t>(surface.triCount) * std::extent_v<decltype(XModelFace::vertexIndex)>); xmodelToCommonVertexIndexLookup.reserve(static_cast<size_t>(surface.triCount) * std::extent_v<decltype(XModelFace::vertexIndex)>);
for (auto faceIndex = 0u; faceIndex < surface.triCount; faceIndex++) for (auto faceIndex = 0u; faceIndex < surface.triCount; faceIndex++)
@@ -752,9 +852,16 @@ namespace
// Since bone weights are sorted by weight count, the last must have the highest weight count // 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 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<size_t>(std::numeric_limits<decltype(XSurface::vertCount)>::max()); constexpr auto maxVerticesForRigid = static_cast<size_t>(std::numeric_limits<decltype(XSurface::vertCount)>::max());
if (vertexOffset + xmodelToCommonVertexIndexLookup.size() > maxVerticesForRigid) if (vertexOffset + xmodelToCommonVertexIndexLookup.size() > maxVerticesForRigid)
@@ -790,7 +897,7 @@ namespace
if (!common.m_bone_weight_data.weights.empty()) if (!common.m_bone_weight_data.weights.empty())
{ {
if (modelIsRigid) if (surfaceIsRigid)
{ {
CreateVertListData(surface, xmodelToCommonVertexIndexLookup, common); CreateVertListData(surface, xmodelToCommonVertexIndexLookup, common);
} }
@@ -809,6 +916,18 @@ namespace
} }
} }
#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; return true;
} }
@@ -423,6 +423,12 @@ namespace
if (surface.vertList) 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++) for (auto vertListIndex = 0u; vertListIndex < surface.vertListCount; vertListIndex++)
{ {
const auto& vertList = surface.vertList[vertListIndex]; const auto& vertList = surface.vertList[vertListIndex];
@@ -442,6 +448,12 @@ namespace
auto vertsBlendOffset = 0u; auto vertsBlendOffset = 0u;
if (surface.vertInfo.vertsBlend) 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 // 1 bone weight
for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++) for (auto vertIndex = 0; vertIndex < surface.vertInfo.vertCount[0]; vertIndex++)
{ {