initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

View File

@@ -0,0 +1,242 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#include <Jolt/AABBTree/AABBTreeBuilder.h>
JPH_NAMESPACE_BEGIN
uint AABBTreeBuilder::Node::GetMinDepth(const Array<Node> &inNodes) const
{
if (HasChildren())
{
uint left = inNodes[mChild[0]].GetMinDepth(inNodes);
uint right = inNodes[mChild[1]].GetMinDepth(inNodes);
return min(left, right) + 1;
}
else
return 1;
}
uint AABBTreeBuilder::Node::GetMaxDepth(const Array<Node> &inNodes) const
{
if (HasChildren())
{
uint left = inNodes[mChild[0]].GetMaxDepth(inNodes);
uint right = inNodes[mChild[1]].GetMaxDepth(inNodes);
return max(left, right) + 1;
}
else
return 1;
}
uint AABBTreeBuilder::Node::GetNodeCount(const Array<Node> &inNodes) const
{
if (HasChildren())
return inNodes[mChild[0]].GetNodeCount(inNodes) + inNodes[mChild[1]].GetNodeCount(inNodes) + 1;
else
return 1;
}
uint AABBTreeBuilder::Node::GetLeafNodeCount(const Array<Node> &inNodes) const
{
if (HasChildren())
return inNodes[mChild[0]].GetLeafNodeCount(inNodes) + inNodes[mChild[1]].GetLeafNodeCount(inNodes);
else
return 1;
}
uint AABBTreeBuilder::Node::GetTriangleCountInTree(const Array<Node> &inNodes) const
{
if (HasChildren())
return inNodes[mChild[0]].GetTriangleCountInTree(inNodes) + inNodes[mChild[1]].GetTriangleCountInTree(inNodes);
else
return GetTriangleCount();
}
void AABBTreeBuilder::Node::GetTriangleCountPerNode(const Array<Node> &inNodes, float &outAverage, uint &outMin, uint &outMax) const
{
outMin = INT_MAX;
outMax = 0;
outAverage = 0;
uint avg_divisor = 0;
GetTriangleCountPerNodeInternal(inNodes, outAverage, avg_divisor, outMin, outMax);
if (avg_divisor > 0)
outAverage /= avg_divisor;
}
float AABBTreeBuilder::Node::CalculateSAHCost(const Array<Node> &inNodes, float inCostTraversal, float inCostLeaf) const
{
float surface_area = mBounds.GetSurfaceArea();
return surface_area > 0.0f? CalculateSAHCostInternal(inNodes, inCostTraversal / surface_area, inCostLeaf / surface_area) : 0.0f;
}
void AABBTreeBuilder::Node::GetNChildren(const Array<Node> &inNodes, uint inN, Array<const Node*> &outChildren) const
{
JPH_ASSERT(outChildren.empty());
// Check if there is anything to expand
if (!HasChildren())
return;
// Start with the children of this node
outChildren.push_back(&inNodes[mChild[0]]);
outChildren.push_back(&inNodes[mChild[1]]);
size_t next = 0;
bool all_triangles = true;
while (outChildren.size() < inN)
{
// If we have looped over all nodes, start over with the first node again
if (next >= outChildren.size())
{
// If there only triangle nodes left, we have to terminate
if (all_triangles)
return;
next = 0;
all_triangles = true;
}
// Try to expand this node into its two children
const Node *to_expand = outChildren[next];
if (to_expand->HasChildren())
{
outChildren.erase(outChildren.begin() + next);
outChildren.push_back(&inNodes[to_expand->mChild[0]]);
outChildren.push_back(&inNodes[to_expand->mChild[1]]);
all_triangles = false;
}
else
{
++next;
}
}
}
float AABBTreeBuilder::Node::CalculateSAHCostInternal(const Array<Node> &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const
{
if (HasChildren())
return inCostTraversalDivSurfaceArea * mBounds.GetSurfaceArea()
+ inNodes[mChild[0]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea)
+ inNodes[mChild[1]].CalculateSAHCostInternal(inNodes, inCostTraversalDivSurfaceArea, inCostLeafDivSurfaceArea);
else
return inCostLeafDivSurfaceArea * mBounds.GetSurfaceArea() * GetTriangleCount();
}
void AABBTreeBuilder::Node::GetTriangleCountPerNodeInternal(const Array<Node> &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const
{
if (HasChildren())
{
inNodes[mChild[0]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax);
inNodes[mChild[1]].GetTriangleCountPerNodeInternal(inNodes, outAverage, outAverageDivisor, outMin, outMax);
}
else
{
outAverage += GetTriangleCount();
outAverageDivisor++;
outMin = min(outMin, GetTriangleCount());
outMax = max(outMax, GetTriangleCount());
}
}
AABBTreeBuilder::AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTrianglesPerLeaf) :
mTriangleSplitter(inSplitter),
mMaxTrianglesPerLeaf(inMaxTrianglesPerLeaf)
{
}
AABBTreeBuilder::Node *AABBTreeBuilder::Build(AABBTreeBuilderStats &outStats)
{
TriangleSplitter::Range initial = mTriangleSplitter.GetInitialRange();
// Worst case for number of nodes: 1 leaf node per triangle. At each level above, the number of nodes is half that of the level below.
// This means that at most we'll be allocating 2x the number of triangles in nodes.
mNodes.reserve(2 * initial.Count());
mTriangles.reserve(initial.Count());
// Build the tree
Node &root = mNodes[BuildInternal(initial)];
// Collect stats
float avg_triangles_per_leaf;
uint min_triangles_per_leaf, max_triangles_per_leaf;
root.GetTriangleCountPerNode(mNodes, avg_triangles_per_leaf, min_triangles_per_leaf, max_triangles_per_leaf);
mTriangleSplitter.GetStats(outStats.mSplitterStats);
outStats.mSAHCost = root.CalculateSAHCost(mNodes, 1.0f, 1.0f);
outStats.mMinDepth = root.GetMinDepth(mNodes);
outStats.mMaxDepth = root.GetMaxDepth(mNodes);
outStats.mNodeCount = root.GetNodeCount(mNodes);
outStats.mLeafNodeCount = root.GetLeafNodeCount(mNodes);
outStats.mMaxTrianglesPerLeaf = mMaxTrianglesPerLeaf;
outStats.mTreeMinTrianglesPerLeaf = min_triangles_per_leaf;
outStats.mTreeMaxTrianglesPerLeaf = max_triangles_per_leaf;
outStats.mTreeAvgTrianglesPerLeaf = avg_triangles_per_leaf;
return &root;
}
uint AABBTreeBuilder::BuildInternal(const TriangleSplitter::Range &inTriangles)
{
// Check if there are too many triangles left
if (inTriangles.Count() > mMaxTrianglesPerLeaf)
{
// Split triangles in two batches
TriangleSplitter::Range left, right;
if (!mTriangleSplitter.Split(inTriangles, left, right))
{
// When the trace below triggers:
//
// This code builds a tree structure to accelerate collision detection.
// At top level it will start with all triangles in a mesh and then divides the triangles into two batches.
// This process repeats until until the batch size is smaller than mMaxTrianglePerLeaf.
//
// It uses a TriangleSplitter to find a good split. When this warning triggers, the splitter was not able
// to create a reasonable split for the triangles. This usually happens when the triangles in a batch are
// intersecting. They could also be overlapping when projected on the 3 coordinate axis.
//
// To solve this issue, you could try to pass your mesh through a mesh cleaning / optimization algorithm.
// You could also inspect the triangles that cause this issue and see if that part of the mesh can be fixed manually.
//
// When you do not fix this warning, the tree will be less efficient for collision detection, but it will still work.
JPH_IF_DEBUG(Trace("AABBTreeBuilder: Doing random split for %d triangles (max per node: %u)!", (int)inTriangles.Count(), mMaxTrianglesPerLeaf);)
int half = inTriangles.Count() / 2;
JPH_ASSERT(half > 0);
left = TriangleSplitter::Range(inTriangles.mBegin, inTriangles.mBegin + half);
right = TriangleSplitter::Range(inTriangles.mBegin + half, inTriangles.mEnd);
}
// Recursively build
const uint node_index = (uint)mNodes.size();
mNodes.push_back(Node());
uint left_index = BuildInternal(left);
uint right_index = BuildInternal(right);
Node &node = mNodes[node_index];
node.mChild[0] = left_index;
node.mChild[1] = right_index;
node.mBounds = mNodes[node.mChild[0]].mBounds;
node.mBounds.Encapsulate(mNodes[node.mChild[1]].mBounds);
return node_index;
}
// Create leaf node
const uint node_index = (uint)mNodes.size();
mNodes.push_back(Node());
Node &node = mNodes.back();
node.mTrianglesBegin = (uint)mTriangles.size();
node.mNumTriangles = inTriangles.mEnd - inTriangles.mBegin;
const VertexList &v = mTriangleSplitter.GetVertices();
for (uint i = inTriangles.mBegin; i < inTriangles.mEnd; ++i)
{
const IndexedTriangle &t = mTriangleSplitter.GetTriangle(i);
mTriangles.push_back(t);
node.mBounds.Encapsulate(v, t);
}
return node_index;
}
JPH_NAMESPACE_END

View File

@@ -0,0 +1,118 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/TriangleSplitter/TriangleSplitter.h>
#include <Jolt/Geometry/AABox.h>
#include <Jolt/Core/NonCopyable.h>
JPH_NAMESPACE_BEGIN
struct AABBTreeBuilderStats
{
///@name Splitter stats
TriangleSplitter::Stats mSplitterStats; ///< Stats returned by the triangle splitter algorithm
///@name Tree structure
float mSAHCost = 0.0f; ///< Surface Area Heuristic cost of this tree
int mMinDepth = 0; ///< Minimal depth of tree (number of nodes)
int mMaxDepth = 0; ///< Maximum depth of tree (number of nodes)
int mNodeCount = 0; ///< Number of nodes in the tree
int mLeafNodeCount = 0; ///< Number of leaf nodes (that contain triangles)
///@name Configured stats
int mMaxTrianglesPerLeaf = 0; ///< Configured max triangles per leaf
///@name Actual stats
int mTreeMinTrianglesPerLeaf = 0; ///< Minimal amount of triangles in a leaf
int mTreeMaxTrianglesPerLeaf = 0; ///< Maximal amount of triangles in a leaf
float mTreeAvgTrianglesPerLeaf = 0.0f; ///< Average amount of triangles in leaf nodes
};
/// Helper class to build an AABB tree
class JPH_EXPORT AABBTreeBuilder
{
public:
/// A node in the tree, contains the AABox for the tree and any child nodes or triangles
class Node
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Indicates that there is no child
static constexpr uint cInvalidNodeIndex = ~uint(0);
/// Get number of triangles in this node
inline uint GetTriangleCount() const { return mNumTriangles; }
/// Check if this node has any children
inline bool HasChildren() const { return mChild[0] != cInvalidNodeIndex || mChild[1] != cInvalidNodeIndex; }
/// Min depth of tree
uint GetMinDepth(const Array<Node> &inNodes) const;
/// Max depth of tree
uint GetMaxDepth(const Array<Node> &inNodes) const;
/// Number of nodes in tree
uint GetNodeCount(const Array<Node> &inNodes) const;
/// Number of leaf nodes in tree
uint GetLeafNodeCount(const Array<Node> &inNodes) const;
/// Get triangle count in tree
uint GetTriangleCountInTree(const Array<Node> &inNodes) const;
/// Calculate min and max triangles per node
void GetTriangleCountPerNode(const Array<Node> &inNodes, float &outAverage, uint &outMin, uint &outMax) const;
/// Calculate the total cost of the tree using the surface area heuristic
float CalculateSAHCost(const Array<Node> &inNodes, float inCostTraversal, float inCostLeaf) const;
/// Recursively get children (breadth first) to get in total inN children (or less if there are no more)
void GetNChildren(const Array<Node> &inNodes, uint inN, Array<const Node *> &outChildren) const;
/// Bounding box
AABox mBounds;
/// Triangles (if no child nodes)
uint mTrianglesBegin; // Index into mTriangles
uint mNumTriangles = 0;
/// Child node indices (if no triangles)
uint mChild[2] = { cInvalidNodeIndex, cInvalidNodeIndex };
private:
friend class AABBTreeBuilder;
/// Recursive helper function to calculate cost of the tree
float CalculateSAHCostInternal(const Array<Node> &inNodes, float inCostTraversalDivSurfaceArea, float inCostLeafDivSurfaceArea) const;
/// Recursive helper function to calculate min and max triangles per node
void GetTriangleCountPerNodeInternal(const Array<Node> &inNodes, float &outAverage, uint &outAverageDivisor, uint &outMin, uint &outMax) const;
};
/// Constructor
AABBTreeBuilder(TriangleSplitter &inSplitter, uint inMaxTrianglesPerLeaf = 16);
/// Recursively build tree, returns the root node of the tree
Node * Build(AABBTreeBuilderStats &outStats);
/// Get all nodes
const Array<Node> & GetNodes() const { return mNodes; }
/// Get all triangles
const Array<IndexedTriangle> &GetTriangles() const { return mTriangles; }
private:
uint BuildInternal(const TriangleSplitter::Range &inTriangles);
TriangleSplitter & mTriangleSplitter;
const uint mMaxTrianglesPerLeaf;
Array<Node> mNodes;
Array<IndexedTriangle> mTriangles;
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,296 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/AABBTree/AABBTreeBuilder.h>
#include <Jolt/Core/ByteBuffer.h>
#include <Jolt/Geometry/IndexedTriangle.h>
JPH_NAMESPACE_BEGIN
/// Conversion algorithm that converts an AABB tree to an optimized binary buffer
template <class TriangleCodec, class NodeCodec>
class AABBTreeToBuffer
{
public:
/// Header for the tree
using NodeHeader = typename NodeCodec::Header;
/// Size in bytes of the header of the tree
static const int HeaderSize = NodeCodec::HeaderSize;
/// Maximum number of children per node in the tree
static const int NumChildrenPerNode = NodeCodec::NumChildrenPerNode;
/// Header for the triangles
using TriangleHeader = typename TriangleCodec::TriangleHeader;
/// Size in bytes of the header for the triangles
static const int TriangleHeaderSize = TriangleCodec::TriangleHeaderSize;
/// Convert AABB tree. Returns false if failed.
bool Convert(const Array<IndexedTriangle> &inTriangles, const Array<AABBTreeBuilder::Node> &inNodes, const VertexList &inVertices, const AABBTreeBuilder::Node *inRoot, bool inStoreUserData, const char *&outError)
{
typename NodeCodec::EncodingContext node_ctx;
typename TriangleCodec::EncodingContext tri_ctx(inVertices);
// Child nodes out of loop so we don't constantly realloc it
Array<const AABBTreeBuilder::Node *> child_nodes;
child_nodes.reserve(NumChildrenPerNode);
// First calculate how big the tree is going to be.
// Since the tree can be huge for very large meshes, we don't want
// to reallocate the buffer as it may cause out of memory situations.
// This loop mimics the construction loop below.
uint64 total_size = HeaderSize + TriangleHeaderSize;
size_t node_count = 1; // Start with root node
size_t to_process_max_size = 1; // Track size of queues so we can do a single reserve below
size_t to_process_triangles_max_size = 0;
{ // A scope to free the memory associated with to_estimate and to_estimate_triangles
Array<const AABBTreeBuilder::Node *> to_estimate;
Array<const AABBTreeBuilder::Node *> to_estimate_triangles;
to_estimate.push_back(inRoot);
for (;;)
{
while (!to_estimate.empty())
{
// Get the next node to process
const AABBTreeBuilder::Node *node = to_estimate.back();
to_estimate.pop_back();
// Update total size
node_ctx.PrepareNodeAllocate(node, total_size);
if (node->HasChildren())
{
// Collect the first NumChildrenPerNode sub-nodes in the tree
child_nodes.clear(); // Won't free the memory
node->GetNChildren(inNodes, NumChildrenPerNode, child_nodes);
// Increment the number of nodes we're going to store
node_count += child_nodes.size();
// Insert in reverse order so we estimate left child first when taking nodes from the back
for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx)
{
// Store triangles in separate list so we process them last
const AABBTreeBuilder::Node *child = child_nodes[idx];
if (child->HasChildren())
{
to_estimate.push_back(child);
to_process_max_size = max(to_estimate.size(), to_process_max_size);
}
else
{
to_estimate_triangles.push_back(child);
to_process_triangles_max_size = max(to_estimate_triangles.size(), to_process_triangles_max_size);
}
}
}
else
{
// Update total size
tri_ctx.PreparePack(&inTriangles[node->mTrianglesBegin], node->mNumTriangles, inStoreUserData, total_size);
}
}
// If we've got triangles to estimate, loop again with just the triangles
if (to_estimate_triangles.empty())
break;
else
to_estimate.swap(to_estimate_triangles);
}
}
// Finalize the prepare stage for the triangle context
tri_ctx.FinalizePreparePack(total_size);
// Reserve the buffer
if (size_t(total_size) != total_size)
{
outError = "AABBTreeToBuffer: Out of memory!";
return false;
}
mTree.reserve(size_t(total_size));
// Add headers
NodeHeader *header = HeaderSize > 0? mTree.Allocate<NodeHeader>() : nullptr;
TriangleHeader *triangle_header = TriangleHeaderSize > 0? mTree.Allocate<TriangleHeader>() : nullptr;
struct NodeData
{
const AABBTreeBuilder::Node * mNode = nullptr; // Node that this entry belongs to
Vec3 mNodeBoundsMin; // Quantized node bounds
Vec3 mNodeBoundsMax;
size_t mNodeStart = size_t(-1); // Start of node in mTree
size_t mTriangleStart = size_t(-1); // Start of the triangle data in mTree
size_t mChildNodeStart[NumChildrenPerNode]; // Start of the children of the node in mTree
size_t mChildTrianglesStart[NumChildrenPerNode]; // Start of the triangle data in mTree
size_t * mParentChildNodeStart = nullptr; // Where to store mNodeStart (to patch mChildNodeStart of my parent)
size_t * mParentTrianglesStart = nullptr; // Where to store mTriangleStart (to patch mChildTrianglesStart of my parent)
uint mNumChildren = 0; // Number of children
};
Array<NodeData *> to_process;
to_process.reserve(to_process_max_size);
Array<NodeData *> to_process_triangles;
to_process_triangles.reserve(to_process_triangles_max_size);
Array<NodeData> node_list;
node_list.reserve(node_count); // Needed to ensure that array is not reallocated, so we can keep pointers in the array
NodeData root;
root.mNode = inRoot;
root.mNodeBoundsMin = inRoot->mBounds.mMin;
root.mNodeBoundsMax = inRoot->mBounds.mMax;
node_list.push_back(root);
to_process.push_back(&node_list.back());
for (;;)
{
while (!to_process.empty())
{
// Get the next node to process
NodeData *node_data = to_process.back();
to_process.pop_back();
// Due to quantization box could have become bigger, not smaller
JPH_ASSERT(AABox(node_data->mNodeBoundsMin, node_data->mNodeBoundsMax).Contains(node_data->mNode->mBounds), "AABBTreeToBuffer: Bounding box became smaller!");
// Collect the first NumChildrenPerNode sub-nodes in the tree
child_nodes.clear(); // Won't free the memory
node_data->mNode->GetNChildren(inNodes, NumChildrenPerNode, child_nodes);
node_data->mNumChildren = (uint)child_nodes.size();
// Fill in default child bounds
Vec3 child_bounds_min[NumChildrenPerNode], child_bounds_max[NumChildrenPerNode];
for (size_t i = 0; i < NumChildrenPerNode; ++i)
if (i < child_nodes.size())
{
child_bounds_min[i] = child_nodes[i]->mBounds.mMin;
child_bounds_max[i] = child_nodes[i]->mBounds.mMax;
}
else
{
child_bounds_min[i] = Vec3::sZero();
child_bounds_max[i] = Vec3::sZero();
}
// Start a new node
node_data->mNodeStart = node_ctx.NodeAllocate(node_data->mNode, node_data->mNodeBoundsMin, node_data->mNodeBoundsMax, child_nodes, child_bounds_min, child_bounds_max, mTree, outError);
if (node_data->mNodeStart == size_t(-1))
return false;
if (node_data->mNode->HasChildren())
{
// Insert in reverse order so we process left child first when taking nodes from the back
for (int idx = int(child_nodes.size()) - 1; idx >= 0; --idx)
{
const AABBTreeBuilder::Node *child_node = child_nodes[idx];
// Due to quantization box could have become bigger, not smaller
JPH_ASSERT(AABox(child_bounds_min[idx], child_bounds_max[idx]).Contains(child_node->mBounds), "AABBTreeToBuffer: Bounding box became smaller!");
// Add child to list of nodes to be processed
NodeData child;
child.mNode = child_node;
child.mNodeBoundsMin = child_bounds_min[idx];
child.mNodeBoundsMax = child_bounds_max[idx];
child.mParentChildNodeStart = &node_data->mChildNodeStart[idx];
child.mParentTrianglesStart = &node_data->mChildTrianglesStart[idx];
node_list.push_back(child);
// Store triangles in separate list so we process them last
if (child_node->HasChildren())
to_process.push_back(&node_list.back());
else
to_process_triangles.push_back(&node_list.back());
}
}
else
{
// Add triangles
node_data->mTriangleStart = tri_ctx.Pack(&inTriangles[node_data->mNode->mTrianglesBegin], node_data->mNode->mNumTriangles, inStoreUserData, mTree, outError);
if (node_data->mTriangleStart == size_t(-1))
return false;
}
// Patch offset into parent
if (node_data->mParentChildNodeStart != nullptr)
{
*node_data->mParentChildNodeStart = node_data->mNodeStart;
*node_data->mParentTrianglesStart = node_data->mTriangleStart;
}
}
// If we've got triangles to process, loop again with just the triangles
if (to_process_triangles.empty())
break;
else
to_process.swap(to_process_triangles);
}
// Assert that our reservation was correct (we don't know if we swapped the arrays or not)
JPH_ASSERT(to_process_max_size == to_process.capacity() || to_process_triangles_max_size == to_process.capacity());
JPH_ASSERT(to_process_max_size == to_process_triangles.capacity() || to_process_triangles_max_size == to_process_triangles.capacity());
// Finalize all nodes
for (NodeData &n : node_list)
if (!node_ctx.NodeFinalize(n.mNode, n.mNodeStart, n.mNumChildren, n.mChildNodeStart, n.mChildTrianglesStart, mTree, outError))
return false;
// Finalize the triangles
tri_ctx.Finalize(inVertices, triangle_header, mTree);
// Validate that our reservations were correct
if (node_count != node_list.size())
{
outError = "Internal Error: Node memory estimate was incorrect, memory corruption!";
return false;
}
if (total_size != mTree.size())
{
outError = "Internal Error: Tree memory estimate was incorrect, memory corruption!";
return false;
}
// Finalize the nodes
return node_ctx.Finalize(header, inRoot, node_list[0].mNodeStart, node_list[0].mTriangleStart, outError);
}
/// Get resulting data
inline const ByteBuffer & GetBuffer() const
{
return mTree;
}
/// Get resulting data
inline ByteBuffer & GetBuffer()
{
return mTree;
}
/// Get header for tree
inline const NodeHeader * GetNodeHeader() const
{
return mTree.Get<NodeHeader>(0);
}
/// Get header for triangles
inline const TriangleHeader * GetTriangleHeader() const
{
return mTree.Get<TriangleHeader>(HeaderSize);
}
/// Get root of resulting tree
inline const void * GetRoot() const
{
return mTree.Get<void>(HeaderSize + TriangleHeaderSize);
}
private:
ByteBuffer mTree; ///< Resulting tree structure
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,323 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Core/ByteBuffer.h>
#include <Jolt/Math/HalfFloat.h>
#include <Jolt/AABBTree/AABBTreeBuilder.h>
JPH_NAMESPACE_BEGIN
class NodeCodecQuadTreeHalfFloat
{
public:
/// Number of child nodes of this node
static constexpr int NumChildrenPerNode = 4;
/// Header for the tree
struct Header
{
Float3 mRootBoundsMin;
Float3 mRootBoundsMax;
uint32 mRootProperties;
uint8 mBlockIDBits; ///< Number of bits to address a triangle block
uint8 mPadding[3] = { 0 };
};
/// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable)
static constexpr int HeaderSize = sizeof(Header);
/// Stack size to use during DecodingContext::sWalkTree
static constexpr int StackSize = 128;
/// Node properties
enum : uint32
{
TRIANGLE_COUNT_BITS = 4,
TRIANGLE_COUNT_SHIFT = 28,
TRIANGLE_COUNT_MASK = (1 << TRIANGLE_COUNT_BITS) - 1,
OFFSET_BITS = 28,
OFFSET_MASK = (1 << OFFSET_BITS) - 1,
OFFSET_NON_SIGNIFICANT_BITS = 2,
OFFSET_NON_SIGNIFICANT_MASK = (1 << OFFSET_NON_SIGNIFICANT_BITS) - 1,
};
/// Node structure
struct Node
{
HalfFloat mBoundsMinX[4]; ///< 4 child bounding boxes
HalfFloat mBoundsMinY[4];
HalfFloat mBoundsMinZ[4];
HalfFloat mBoundsMaxX[4];
HalfFloat mBoundsMaxY[4];
HalfFloat mBoundsMaxZ[4];
uint32 mNodeProperties[4]; ///< 4 child node properties
};
static_assert(sizeof(Node) == 64, "Node should be 64 bytes");
/// This class encodes and compresses quad tree nodes
class EncodingContext
{
public:
/// Mimics the size a call to NodeAllocate() would add to the buffer
void PrepareNodeAllocate(const AABBTreeBuilder::Node *inNode, uint64 &ioBufferSize) const
{
// We don't emit nodes for leafs
if (!inNode->HasChildren())
return;
// Add size of node
ioBufferSize += sizeof(Node);
}
/// Allocate a new node for inNode.
/// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed
/// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax
/// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node
/// Returns size_t(-1) on error and reports the error in outError
size_t NodeAllocate(const AABBTreeBuilder::Node *inNode, Vec3Arg inNodeBoundsMin, Vec3Arg inNodeBoundsMax, Array<const AABBTreeBuilder::Node *> &ioChildren, Vec3 outChildBoundsMin[NumChildrenPerNode], Vec3 outChildBoundsMax[NumChildrenPerNode], ByteBuffer &ioBuffer, const char *&outError) const
{
// We don't emit nodes for leafs
if (!inNode->HasChildren())
return ioBuffer.size();
// Remember the start of the node
size_t node_start = ioBuffer.size();
// Fill in bounds
Node *node = ioBuffer.Allocate<Node>();
for (size_t i = 0; i < 4; ++i)
{
if (i < ioChildren.size())
{
const AABBTreeBuilder::Node *this_node = ioChildren[i];
// Copy bounding box
node->mBoundsMinX[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetX());
node->mBoundsMinY[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetY());
node->mBoundsMinZ[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetZ());
node->mBoundsMaxX[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetX());
node->mBoundsMaxY[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetY());
node->mBoundsMaxZ[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetZ());
// Store triangle count
node->mNodeProperties[i] = this_node->GetTriangleCount() << TRIANGLE_COUNT_SHIFT;
if (this_node->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
{
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
return size_t(-1);
}
}
else
{
// Make this an invalid triangle node
node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT;
// Make bounding box invalid
node->mBoundsMinX[i] = HALF_FLT_MAX;
node->mBoundsMinY[i] = HALF_FLT_MAX;
node->mBoundsMinZ[i] = HALF_FLT_MAX;
node->mBoundsMaxX[i] = HALF_FLT_MAX;
node->mBoundsMaxY[i] = HALF_FLT_MAX;
node->mBoundsMaxZ[i] = HALF_FLT_MAX;
}
}
// Since we don't keep track of the bounding box while descending the tree, we keep the root bounds at all levels for triangle compression
for (int i = 0; i < NumChildrenPerNode; ++i)
{
outChildBoundsMin[i] = inNodeBoundsMin;
outChildBoundsMax[i] = inNodeBoundsMax;
}
return node_start;
}
/// Once all nodes have been added, this call finalizes all nodes by patching in the offsets of the child nodes (that were added after the node itself was added)
bool NodeFinalize(const AABBTreeBuilder::Node *inNode, size_t inNodeStart, uint inNumChildren, const size_t *inChildrenNodeStart, const size_t *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError)
{
if (!inNode->HasChildren())
return true;
Node *node = ioBuffer.Get<Node>(inNodeStart);
for (uint i = 0; i < inNumChildren; ++i)
{
size_t offset;
if (node->mNodeProperties[i] != 0)
{
// This is a triangle block
offset = inChildrenTrianglesStart[i];
// Store highest block with triangles so we can count the number of bits we need
mHighestTriangleBlock = max(mHighestTriangleBlock, offset);
}
else
{
// This is a node block
offset = inChildrenNodeStart[i];
}
// Store offset of next node / triangles
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
{
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";
return false;
}
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
if (offset > OFFSET_MASK)
{
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
return false;
}
node->mNodeProperties[i] |= uint32(offset);
}
return true;
}
/// Once all nodes have been finalized, this will finalize the header of the nodes
bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, size_t inRootNodeStart, size_t inRootTrianglesStart, const char *&outError) const
{
// Check if we can address the root node
size_t offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart;
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
{
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";
return false;
}
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
if (offset > OFFSET_MASK)
{
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
return false;
}
// If the root has triangles, we need to take that offset instead since the mHighestTriangleBlock will be zero
size_t highest_triangle_block = inRootTrianglesStart != size_t(-1)? inRootTrianglesStart : mHighestTriangleBlock;
highest_triangle_block >>= OFFSET_NON_SIGNIFICANT_BITS;
inRoot->mBounds.mMin.StoreFloat3(&outHeader->mRootBoundsMin);
inRoot->mBounds.mMax.StoreFloat3(&outHeader->mRootBoundsMax);
outHeader->mRootProperties = uint32(offset) + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT);
outHeader->mBlockIDBits = uint8(32 - CountLeadingZeros(uint32(highest_triangle_block)));
if (inRoot->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
{
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
return false;
}
return true;
}
private:
size_t mHighestTriangleBlock = 0;
};
/// This class decodes and decompresses quad tree nodes
class DecodingContext
{
public:
/// Get the amount of bits needed to store an ID to a triangle block
inline static uint sTriangleBlockIDBits(const Header *inHeader)
{
return inHeader->mBlockIDBits;
}
/// Convert a triangle block ID to the start of the triangle buffer
inline static const void * sGetTriangleBlockStart(const uint8 *inBufferStart, uint inTriangleBlockID)
{
return inBufferStart + (inTriangleBlockID << OFFSET_NON_SIGNIFICANT_BITS);
}
/// Constructor
JPH_INLINE explicit DecodingContext(const Header *inHeader)
{
// Start with the root node on the stack
mNodeStack[0] = inHeader->mRootProperties;
}
/// Walk the node tree calling the Visitor::VisitNodes for each node encountered and Visitor::VisitTriangles for each triangle encountered
template <class TriangleContext, class Visitor>
JPH_INLINE void WalkTree(const uint8 *inBufferStart, const TriangleContext &inTriangleContext, Visitor &ioVisitor)
{
do
{
// Test if node contains triangles
uint32 node_properties = mNodeStack[mTop];
uint32 tri_count = node_properties >> TRIANGLE_COUNT_SHIFT;
if (tri_count == 0)
{
const Node *node = reinterpret_cast<const Node *>(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS));
// Unpack bounds
#ifdef JPH_CPU_BIG_ENDIAN
Vec4 bounds_minx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinX[0] + (node->mBoundsMinX[1] << 16), node->mBoundsMinX[2] + (node->mBoundsMinX[3] << 16), 0, 0));
Vec4 bounds_miny = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinY[0] + (node->mBoundsMinY[1] << 16), node->mBoundsMinY[2] + (node->mBoundsMinY[3] << 16), 0, 0));
Vec4 bounds_minz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinZ[0] + (node->mBoundsMinZ[1] << 16), node->mBoundsMinZ[2] + (node->mBoundsMinZ[3] << 16), 0, 0));
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxX[0] + (node->mBoundsMaxX[1] << 16), node->mBoundsMaxX[2] + (node->mBoundsMaxX[3] << 16), 0, 0));
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxY[0] + (node->mBoundsMaxY[1] << 16), node->mBoundsMaxY[2] + (node->mBoundsMaxY[3] << 16), 0, 0));
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxZ[0] + (node->mBoundsMaxZ[1] << 16), node->mBoundsMaxZ[2] + (node->mBoundsMaxZ[3] << 16), 0, 0));
#else
UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinX[0]));
Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy);
Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
UVec4 bounds_minzmaxx = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinZ[0]));
Vec4 bounds_minz = HalfFloatConversion::ToFloat(bounds_minzmaxx);
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(bounds_minzmaxx.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMaxY[0]));
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz);
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
#endif
// Load properties for 4 children
UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]);
// Check which sub nodes to visit
int num_results = ioVisitor.VisitNodes(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, properties, mTop);
// Push them onto the stack
JPH_ASSERT(mTop + 4 < StackSize);
properties.StoreInt4(&mNodeStack[mTop]);
mTop += num_results;
}
else if (tri_count != TRIANGLE_COUNT_MASK) // TRIANGLE_COUNT_MASK indicates a padding node, normally we shouldn't visit these nodes but when querying with a big enough box you could touch HALF_FLT_MAX (about 65K)
{
// Node contains triangles, do individual tests
uint32 triangle_block_id = node_properties & OFFSET_MASK;
const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id);
ioVisitor.VisitTriangles(inTriangleContext, triangles, tri_count, triangle_block_id);
}
// Check if we're done
if (ioVisitor.ShouldAbort())
break;
// Fetch next node until we find one that the visitor wants to see
do
--mTop;
while (mTop >= 0 && !ioVisitor.ShouldVisitNode(mTop));
}
while (mTop >= 0);
}
/// This can be used to have the visitor early out (ioVisitor.ShouldAbort() returns true) and later continue again (call WalkTree() again)
bool IsDoneWalking() const
{
return mTop < 0;
}
private:
uint32 mNodeStack[StackSize];
int mTop = 0;
};
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,555 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Geometry/RayTriangle.h>
JPH_NAMESPACE_BEGIN
/// Store vertices in 64 bits and indices in 8 bits + 8 bit of flags per triangle like this:
///
/// TriangleBlockHeader,
/// TriangleBlock (4 triangles and their flags in 16 bytes),
/// TriangleBlock...
/// [Optional] UserData (4 bytes per triangle)
///
/// Vertices are stored:
///
/// VertexData (1 vertex in 64 bits),
/// VertexData...
///
/// They're compressed relative to the bounding box as provided by the node codec.
class TriangleCodecIndexed8BitPackSOA4Flags
{
public:
class TriangleHeader
{
public:
Float3 mOffset; ///< Offset of all vertices
Float3 mScale; ///< Scale of all vertices, vertex_position = mOffset + mScale * compressed_vertex_position
};
/// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable)
static constexpr int TriangleHeaderSize = sizeof(TriangleHeader);
/// If this codec could return a different offset than the current buffer size when calling Pack()
static constexpr bool ChangesOffsetOnPack = false;
/// Amount of bits per component
enum EComponentData : uint32
{
COMPONENT_BITS = 21,
COMPONENT_MASK = (1 << COMPONENT_BITS) - 1,
};
/// Packed X and Y coordinate
enum EVertexXY : uint32
{
COMPONENT_X = 0,
COMPONENT_Y1 = COMPONENT_BITS,
COMPONENT_Y1_BITS = 32 - COMPONENT_BITS,
};
/// Packed Z and Y coordinate
enum EVertexZY : uint32
{
COMPONENT_Z = 0,
COMPONENT_Y2 = COMPONENT_BITS,
COMPONENT_Y2_BITS = 31 - COMPONENT_BITS,
};
/// A single packed vertex
struct VertexData
{
uint32 mVertexXY;
uint32 mVertexZY;
};
static_assert(sizeof(VertexData) == 8, "Compiler added padding");
/// A block of 4 triangles
struct TriangleBlock
{
uint8 mIndices[3][4]; ///< 8 bit indices to triangle vertices for 4 triangles in the form mIndices[vertex][triangle] where vertex in [0, 2] and triangle in [0, 3]
uint8 mFlags[4]; ///< Triangle flags (could contain material and active edges)
};
static_assert(sizeof(TriangleBlock) == 16, "Compiler added padding");
enum ETriangleBlockHeaderFlags : uint32
{
OFFSET_TO_VERTICES_BITS = 29, ///< Offset from current block to start of vertices in bytes
OFFSET_TO_VERTICES_MASK = (1 << OFFSET_TO_VERTICES_BITS) - 1,
OFFSET_NON_SIGNIFICANT_BITS = 2, ///< The offset from the current block to the start of the vertices must be a multiple of 4 bytes
OFFSET_NON_SIGNIFICANT_MASK = (1 << OFFSET_NON_SIGNIFICANT_BITS) - 1,
OFFSET_TO_USERDATA_BITS = 3, ///< When user data is stored, this is the number of blocks to skip to get to the user data (0 = no user data)
OFFSET_TO_USERDATA_MASK = (1 << OFFSET_TO_USERDATA_BITS) - 1,
};
/// A triangle header, will be followed by one or more TriangleBlocks
struct TriangleBlockHeader
{
const VertexData * GetVertexData() const { return reinterpret_cast<const VertexData *>(reinterpret_cast<const uint8 *>(this) + ((mFlags & OFFSET_TO_VERTICES_MASK) << OFFSET_NON_SIGNIFICANT_BITS)); }
const TriangleBlock * GetTriangleBlock() const { return reinterpret_cast<const TriangleBlock *>(reinterpret_cast<const uint8 *>(this) + sizeof(TriangleBlockHeader)); }
const uint32 * GetUserData() const { uint32 offset = mFlags >> OFFSET_TO_VERTICES_BITS; return offset == 0? nullptr : reinterpret_cast<const uint32 *>(GetTriangleBlock() + offset); }
uint32 mFlags;
};
static_assert(sizeof(TriangleBlockHeader) == 4, "Compiler added padding");
/// This class is used to validate that the triangle data will not be degenerate after compression
class ValidationContext
{
public:
/// Constructor
ValidationContext(const IndexedTriangleList &inTriangles, const VertexList &inVertices) :
mVertices(inVertices)
{
// Only used the referenced triangles, just like EncodingContext::Finalize does
for (const IndexedTriangle &i : inTriangles)
for (uint32 idx : i.mIdx)
mBounds.Encapsulate(Vec3(inVertices[idx]));
}
/// Test if a triangle will be degenerate after quantization
bool IsDegenerate(const IndexedTriangle &inTriangle) const
{
// Quantize the triangle in the same way as EncodingContext::Finalize does
UVec4 quantized_vertex[3];
Vec3 compress_scale = Vec3::sReplicate(COMPONENT_MASK) / Vec3::sMax(mBounds.GetSize(), Vec3::sReplicate(1.0e-20f));
for (int i = 0; i < 3; ++i)
quantized_vertex[i] = ((Vec3(mVertices[inTriangle.mIdx[i]]) - mBounds.mMin) * compress_scale + Vec3::sReplicate(0.5f)).ToInt();
return quantized_vertex[0] == quantized_vertex[1] || quantized_vertex[1] == quantized_vertex[2] || quantized_vertex[0] == quantized_vertex[2];
}
private:
const VertexList & mVertices;
AABox mBounds;
};
/// This class is used to encode and compress triangle data into a byte buffer
class EncodingContext
{
public:
/// Indicates a vertex hasn't been seen yet in the triangle list
static constexpr uint32 cNotFound = 0xffffffff;
/// Construct the encoding context
explicit EncodingContext(const VertexList &inVertices) :
mVertexMap(inVertices.size(), cNotFound)
{
}
/// Mimics the size a call to Pack() would add to the buffer
void PreparePack(const IndexedTriangle *inTriangles, uint inNumTriangles, bool inStoreUserData, uint64 &ioBufferSize)
{
// Add triangle block header
ioBufferSize += sizeof(TriangleBlockHeader);
// Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared)
uint start_vertex = Clamp((int)mVertexCount - 256 + (int)inNumTriangles * 3, 0, (int)mVertexCount);
// Pack vertices
uint padded_triangle_count = AlignUp(inNumTriangles, 4);
for (uint t = 0; t < padded_triangle_count; t += 4)
{
// Add triangle block header
ioBufferSize += sizeof(TriangleBlock);
for (uint vertex_nr = 0; vertex_nr < 3; ++vertex_nr)
for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx)
{
// Fetch vertex index. Create degenerate triangles for padding triangles.
bool triangle_available = t + block_tri_idx < inNumTriangles;
uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[inNumTriangles - 1].mIdx[0];
// Check if we've seen this vertex before and if it is in the range that we can encode
uint32 &vertex_index = mVertexMap[src_vertex_index];
if (vertex_index == cNotFound || vertex_index < start_vertex)
{
// Add vertex
vertex_index = mVertexCount;
mVertexCount++;
}
}
}
// Add user data
if (inStoreUserData)
ioBufferSize += inNumTriangles * sizeof(uint32);
}
/// Mimics the size the Finalize() call would add to ioBufferSize
void FinalizePreparePack(uint64 &ioBufferSize)
{
// Remember where the vertices are going to start in the output buffer
JPH_ASSERT(IsAligned(ioBufferSize, 4));
mVerticesStartIdx = size_t(ioBufferSize);
// Add vertices to buffer
ioBufferSize += uint64(mVertexCount) * sizeof(VertexData);
// Reserve the amount of memory we need for the vertices
mVertices.reserve(mVertexCount);
// Set vertex map back to 'not found'
for (uint32 &v : mVertexMap)
v = cNotFound;
}
/// Pack the triangles in inContainer to ioBuffer. This stores the mMaterialIndex of a triangle in the 8 bit flags.
/// Returns size_t(-1) on error.
size_t Pack(const IndexedTriangle *inTriangles, uint inNumTriangles, bool inStoreUserData, ByteBuffer &ioBuffer, const char *&outError)
{
JPH_ASSERT(inNumTriangles > 0);
// Determine position of triangles start
size_t triangle_block_start = ioBuffer.size();
// Allocate triangle block header
TriangleBlockHeader *header = ioBuffer.Allocate<TriangleBlockHeader>();
// Compute first vertex that this batch will use (ensuring there's enough room if none of the vertices are shared)
uint start_vertex = Clamp((int)mVertices.size() - 256 + (int)inNumTriangles * 3, 0, (int)mVertices.size());
// Store the start vertex offset relative to TriangleBlockHeader
size_t offset_to_vertices = mVerticesStartIdx - triangle_block_start + size_t(start_vertex) * sizeof(VertexData);
if (offset_to_vertices & OFFSET_NON_SIGNIFICANT_MASK)
{
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Internal Error: Offset has non-significant bits set";
return size_t(-1);
}
offset_to_vertices >>= OFFSET_NON_SIGNIFICANT_BITS;
if (offset_to_vertices > OFFSET_TO_VERTICES_MASK)
{
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset to vertices doesn't fit. Too much data.";
return size_t(-1);
}
header->mFlags = uint32(offset_to_vertices);
// When we store user data we need to store the offset to the user data in TriangleBlocks
uint padded_triangle_count = AlignUp(inNumTriangles, 4);
if (inStoreUserData)
{
uint32 num_blocks = padded_triangle_count >> 2;
JPH_ASSERT(num_blocks <= OFFSET_TO_USERDATA_MASK);
header->mFlags |= num_blocks << OFFSET_TO_VERTICES_BITS;
}
// Pack vertices
for (uint t = 0; t < padded_triangle_count; t += 4)
{
TriangleBlock *block = ioBuffer.Allocate<TriangleBlock>();
for (uint vertex_nr = 0; vertex_nr < 3; ++vertex_nr)
for (uint block_tri_idx = 0; block_tri_idx < 4; ++block_tri_idx)
{
// Fetch vertex index. Create degenerate triangles for padding triangles.
bool triangle_available = t + block_tri_idx < inNumTriangles;
uint32 src_vertex_index = triangle_available? inTriangles[t + block_tri_idx].mIdx[vertex_nr] : inTriangles[inNumTriangles - 1].mIdx[0];
// Check if we've seen this vertex before and if it is in the range that we can encode
uint32 &vertex_index = mVertexMap[src_vertex_index];
if (vertex_index == cNotFound || vertex_index < start_vertex)
{
// Add vertex
vertex_index = (uint32)mVertices.size();
mVertices.push_back(src_vertex_index);
}
// Store vertex index
uint32 vertex_offset = vertex_index - start_vertex;
if (vertex_offset > 0xff)
{
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Offset doesn't fit in 8 bit";
return size_t(-1);
}
block->mIndices[vertex_nr][block_tri_idx] = (uint8)vertex_offset;
// Store flags
uint32 flags = triangle_available? inTriangles[t + block_tri_idx].mMaterialIndex : 0;
if (flags > 0xff)
{
outError = "TriangleCodecIndexed8BitPackSOA4Flags: Material index doesn't fit in 8 bit";
return size_t(-1);
}
block->mFlags[block_tri_idx] = (uint8)flags;
}
}
// Store user data
if (inStoreUserData)
{
uint32 *user_data = ioBuffer.Allocate<uint32>(inNumTriangles);
for (uint t = 0; t < inNumTriangles; ++t)
user_data[t] = inTriangles[t].mUserData;
}
return triangle_block_start;
}
/// After all triangles have been packed, this finalizes the header and triangle buffer
void Finalize(const VertexList &inVertices, TriangleHeader *ioHeader, ByteBuffer &ioBuffer) const
{
// Assert that our reservations were correct
JPH_ASSERT(mVertices.size() == mVertexCount);
JPH_ASSERT(ioBuffer.size() == mVerticesStartIdx);
// Check if anything to do
if (mVertices.empty())
return;
// Calculate bounding box
AABox bounds;
for (uint32 v : mVertices)
bounds.Encapsulate(Vec3(inVertices[v]));
// Compress vertices
VertexData *vertices = ioBuffer.Allocate<VertexData>(mVertices.size());
Vec3 compress_scale = Vec3::sReplicate(COMPONENT_MASK) / Vec3::sMax(bounds.GetSize(), Vec3::sReplicate(1.0e-20f));
for (uint32 v : mVertices)
{
UVec4 c = ((Vec3(inVertices[v]) - bounds.mMin) * compress_scale + Vec3::sReplicate(0.5f)).ToInt();
JPH_ASSERT(c.GetX() <= COMPONENT_MASK);
JPH_ASSERT(c.GetY() <= COMPONENT_MASK);
JPH_ASSERT(c.GetZ() <= COMPONENT_MASK);
vertices->mVertexXY = c.GetX() + (c.GetY() << COMPONENT_Y1);
vertices->mVertexZY = c.GetZ() + ((c.GetY() >> COMPONENT_Y1_BITS) << COMPONENT_Y2);
++vertices;
}
// Store decompression information
bounds.mMin.StoreFloat3(&ioHeader->mOffset);
(bounds.GetSize() / Vec3::sReplicate(COMPONENT_MASK)).StoreFloat3(&ioHeader->mScale);
}
private:
using VertexMap = Array<uint32>;
uint32 mVertexCount = 0; ///< Number of vertices calculated during PreparePack
size_t mVerticesStartIdx = 0; ///< Start of the vertices in the output buffer, calculated during PreparePack
Array<uint32> mVertices; ///< Output vertices as an index into the original vertex list (inVertices), sorted according to occurrence
VertexMap mVertexMap; ///< Maps from the original mesh vertex index (inVertices) to the index in our output vertices (mVertices)
};
/// This class is used to decode and decompress triangle data packed by the EncodingContext
class DecodingContext
{
private:
/// Private helper function to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.)
JPH_INLINE void Unpack(const VertexData *inVertices, UVec4Arg inIndex, Vec4 &outX, Vec4 &outY, Vec4 &outZ) const
{
// Get compressed data
UVec4 c1 = UVec4::sGatherInt4<8>(&inVertices->mVertexXY, inIndex);
UVec4 c2 = UVec4::sGatherInt4<8>(&inVertices->mVertexZY, inIndex);
// Unpack the x y and z component
UVec4 xc = UVec4::sAnd(c1, UVec4::sReplicate(COMPONENT_MASK));
UVec4 yc = UVec4::sOr(c1.LogicalShiftRight<COMPONENT_Y1>(), c2.LogicalShiftRight<COMPONENT_Y2>().LogicalShiftLeft<COMPONENT_Y1_BITS>());
UVec4 zc = UVec4::sAnd(c2, UVec4::sReplicate(COMPONENT_MASK));
// Convert to float
outX = Vec4::sFusedMultiplyAdd(xc.ToFloat(), mScaleX, mOffsetX);
outY = Vec4::sFusedMultiplyAdd(yc.ToFloat(), mScaleY, mOffsetY);
outZ = Vec4::sFusedMultiplyAdd(zc.ToFloat(), mScaleZ, mOffsetZ);
}
/// Private helper function to unpack 4 triangles from a triangle block
JPH_INLINE void Unpack(const TriangleBlock *inBlock, const VertexData *inVertices, Vec4 &outX1, Vec4 &outY1, Vec4 &outZ1, Vec4 &outX2, Vec4 &outY2, Vec4 &outZ2, Vec4 &outX3, Vec4 &outY3, Vec4 &outZ3) const
{
// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&inBlock->mIndices[0]));
UVec4 iv1 = indices.Expand4Byte0();
UVec4 iv2 = indices.Expand4Byte4();
UVec4 iv3 = indices.Expand4Byte8();
#ifdef JPH_CPU_BIG_ENDIAN
// On big endian systems we need to reverse the bytes
iv1 = iv1.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
iv2 = iv2.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
iv3 = iv3.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
#endif
// Decompress the triangle data
Unpack(inVertices, iv1, outX1, outY1, outZ1);
Unpack(inVertices, iv2, outX2, outY2, outZ2);
Unpack(inVertices, iv3, outX3, outY3, outZ3);
}
public:
JPH_INLINE explicit DecodingContext(const TriangleHeader *inHeader) :
mOffsetX(Vec4::sReplicate(inHeader->mOffset.x)),
mOffsetY(Vec4::sReplicate(inHeader->mOffset.y)),
mOffsetZ(Vec4::sReplicate(inHeader->mOffset.z)),
mScaleX(Vec4::sReplicate(inHeader->mScale.x)),
mScaleY(Vec4::sReplicate(inHeader->mScale.y)),
mScaleZ(Vec4::sReplicate(inHeader->mScale.z))
{
}
/// Unpacks triangles in the format t1v1,t1v2,t1v3, t2v1,t2v2,t2v3, ...
JPH_INLINE void Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles) const
{
JPH_ASSERT(inNumTriangles > 0);
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
const VertexData *vertices = header->GetVertexData();
const TriangleBlock *t = header->GetTriangleBlock();
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
int triangles_left = inNumTriangles;
do
{
// Unpack the vertices for 4 triangles
Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
// Transpose it so we get normal vectors
Mat44 v1 = Mat44(v1x, v1y, v1z, Vec4::sZero()).Transposed();
Mat44 v2 = Mat44(v2x, v2y, v2z, Vec4::sZero()).Transposed();
Mat44 v3 = Mat44(v3x, v3y, v3z, Vec4::sZero()).Transposed();
// Store triangle data
for (int i = 0; i < 4 && triangles_left > 0; ++i, --triangles_left)
{
*outTriangles++ = v1.GetColumn3(i);
*outTriangles++ = v2.GetColumn3(i);
*outTriangles++ = v3.GetColumn3(i);
}
++t;
}
while (t < end);
}
/// Tests a ray against the packed triangles
JPH_INLINE float TestRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, const void *inTriangleStart, uint32 inNumTriangles, float inClosest, uint32 &outClosestTriangleIndex) const
{
JPH_ASSERT(inNumTriangles > 0);
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
const VertexData *vertices = header->GetVertexData();
const TriangleBlock *t = header->GetTriangleBlock();
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
Vec4 closest = Vec4::sReplicate(inClosest);
UVec4 closest_triangle_idx = UVec4::sZero();
UVec4 start_triangle_idx = UVec4::sZero();
do
{
// Unpack the vertices for 4 triangles
Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
// Perform ray vs triangle test
Vec4 distance = RayTriangle4(inRayOrigin, inRayDirection, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
// Update closest with the smaller values
UVec4 smaller = Vec4::sLess(distance, closest);
closest = Vec4::sSelect(closest, distance, smaller);
// Update triangle index with the smallest values
UVec4 triangle_idx = start_triangle_idx + UVec4(0, 1, 2, 3);
closest_triangle_idx = UVec4::sSelect(closest_triangle_idx, triangle_idx, smaller);
// Next block
++t;
start_triangle_idx += UVec4::sReplicate(4);
}
while (t < end);
// Get the smallest component
Vec4::sSort4(closest, closest_triangle_idx);
outClosestTriangleIndex = closest_triangle_idx.GetX();
return closest.GetX();
}
/// Decode a single triangle
inline void GetTriangle(const void *inTriangleStart, uint32 inTriangleIdx, Vec3 &outV1, Vec3 &outV2, Vec3 &outV3) const
{
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
const VertexData *vertices = header->GetVertexData();
const TriangleBlock *block = header->GetTriangleBlock() + (inTriangleIdx >> 2);
uint32 block_triangle_idx = inTriangleIdx & 0b11;
// Get the 3 vertices
const VertexData &v1 = vertices[block->mIndices[0][block_triangle_idx]];
const VertexData &v2 = vertices[block->mIndices[1][block_triangle_idx]];
const VertexData &v3 = vertices[block->mIndices[2][block_triangle_idx]];
// Pack the vertices
UVec4 c1(v1.mVertexXY, v2.mVertexXY, v3.mVertexXY, 0);
UVec4 c2(v1.mVertexZY, v2.mVertexZY, v3.mVertexZY, 0);
// Unpack the x y and z component
UVec4 xc = UVec4::sAnd(c1, UVec4::sReplicate(COMPONENT_MASK));
UVec4 yc = UVec4::sOr(c1.LogicalShiftRight<COMPONENT_Y1>(), c2.LogicalShiftRight<COMPONENT_Y2>().LogicalShiftLeft<COMPONENT_Y1_BITS>());
UVec4 zc = UVec4::sAnd(c2, UVec4::sReplicate(COMPONENT_MASK));
// Convert to float
Vec4 vx = Vec4::sFusedMultiplyAdd(xc.ToFloat(), mScaleX, mOffsetX);
Vec4 vy = Vec4::sFusedMultiplyAdd(yc.ToFloat(), mScaleY, mOffsetY);
Vec4 vz = Vec4::sFusedMultiplyAdd(zc.ToFloat(), mScaleZ, mOffsetZ);
// Transpose it so we get normal vectors
Mat44 trans = Mat44(vx, vy, vz, Vec4::sZero()).Transposed();
outV1 = trans.GetAxisX();
outV2 = trans.GetAxisY();
outV3 = trans.GetAxisZ();
}
/// Get user data for a triangle
JPH_INLINE uint32 GetUserData(const void *inTriangleStart, uint32 inTriangleIdx) const
{
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
const uint32 *user_data = header->GetUserData();
return user_data != nullptr? user_data[inTriangleIdx] : 0;
}
/// Get flags for entire triangle block
JPH_INLINE static void sGetFlags(const void *inTriangleStart, uint32 inNumTriangles, uint8 *outTriangleFlags)
{
JPH_ASSERT(inNumTriangles > 0);
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
const TriangleBlock *t = header->GetTriangleBlock();
const TriangleBlock *end = t + ((inNumTriangles + 3) >> 2);
int triangles_left = inNumTriangles;
do
{
for (int i = 0; i < 4 && triangles_left > 0; ++i, --triangles_left)
*outTriangleFlags++ = t->mFlags[i];
++t;
}
while (t < end);
}
/// Get flags for a particular triangle
JPH_INLINE static uint8 sGetFlags(const void *inTriangleStart, int inTriangleIndex)
{
const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
const TriangleBlock *first_block = header->GetTriangleBlock();
return first_block[inTriangleIndex >> 2].mFlags[inTriangleIndex & 0b11];
}
/// Unpacks triangles and flags, convenience function
JPH_INLINE void Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const
{
Unpack(inTriangleStart, inNumTriangles, outTriangles);
sGetFlags(inTriangleStart, inNumTriangles, outTriangleFlags);
}
private:
Vec4 mOffsetX;
Vec4 mOffsetY;
Vec4 mOffsetZ;
Vec4 mScaleX;
Vec4 mScaleY;
Vec4 mScaleZ;
};
};
JPH_NAMESPACE_END