Path: blob/master/thirdparty/jolt_physics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h
9912 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Core/ByteBuffer.h>7#include <Jolt/Math/HalfFloat.h>8#include <Jolt/AABBTree/AABBTreeBuilder.h>910JPH_NAMESPACE_BEGIN1112class NodeCodecQuadTreeHalfFloat13{14public:15/// Number of child nodes of this node16static constexpr int NumChildrenPerNode = 4;1718/// Header for the tree19struct Header20{21Float3 mRootBoundsMin;22Float3 mRootBoundsMax;23uint32 mRootProperties;24uint8 mBlockIDBits; ///< Number of bits to address a triangle block25uint8 mPadding[3] = { 0 };26};2728/// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable)29static constexpr int HeaderSize = sizeof(Header);3031/// Stack size to use during DecodingContext::sWalkTree32static constexpr int StackSize = 128;3334/// Node properties35enum : uint3236{37TRIANGLE_COUNT_BITS = 4,38TRIANGLE_COUNT_SHIFT = 28,39TRIANGLE_COUNT_MASK = (1 << TRIANGLE_COUNT_BITS) - 1,40OFFSET_BITS = 28,41OFFSET_MASK = (1 << OFFSET_BITS) - 1,42OFFSET_NON_SIGNIFICANT_BITS = 2,43OFFSET_NON_SIGNIFICANT_MASK = (1 << OFFSET_NON_SIGNIFICANT_BITS) - 1,44};4546/// Node structure47struct Node48{49HalfFloat mBoundsMinX[4]; ///< 4 child bounding boxes50HalfFloat mBoundsMinY[4];51HalfFloat mBoundsMinZ[4];52HalfFloat mBoundsMaxX[4];53HalfFloat mBoundsMaxY[4];54HalfFloat mBoundsMaxZ[4];55uint32 mNodeProperties[4]; ///< 4 child node properties56};5758static_assert(sizeof(Node) == 64, "Node should be 64 bytes");5960/// This class encodes and compresses quad tree nodes61class EncodingContext62{63public:64/// Mimics the size a call to NodeAllocate() would add to the buffer65void PrepareNodeAllocate(const AABBTreeBuilder::Node *inNode, uint64 &ioBufferSize) const66{67// We don't emit nodes for leafs68if (!inNode->HasChildren())69return;7071// Add size of node72ioBufferSize += sizeof(Node);73}7475/// Allocate a new node for inNode.76/// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed77/// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax78/// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node79/// Returns size_t(-1) on error and reports the error in outError80size_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) const81{82// We don't emit nodes for leafs83if (!inNode->HasChildren())84return ioBuffer.size();8586// Remember the start of the node87size_t node_start = ioBuffer.size();8889// Fill in bounds90Node *node = ioBuffer.Allocate<Node>();9192for (size_t i = 0; i < 4; ++i)93{94if (i < ioChildren.size())95{96const AABBTreeBuilder::Node *this_node = ioChildren[i];9798// Copy bounding box99node->mBoundsMinX[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetX());100node->mBoundsMinY[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetY());101node->mBoundsMinZ[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetZ());102node->mBoundsMaxX[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetX());103node->mBoundsMaxY[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetY());104node->mBoundsMaxZ[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetZ());105106// Store triangle count107node->mNodeProperties[i] = this_node->GetTriangleCount() << TRIANGLE_COUNT_SHIFT;108if (this_node->GetTriangleCount() >= TRIANGLE_COUNT_MASK)109{110outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";111return size_t(-1);112}113}114else115{116// Make this an invalid triangle node117node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT;118119// Make bounding box invalid120node->mBoundsMinX[i] = HALF_FLT_MAX;121node->mBoundsMinY[i] = HALF_FLT_MAX;122node->mBoundsMinZ[i] = HALF_FLT_MAX;123node->mBoundsMaxX[i] = HALF_FLT_MAX;124node->mBoundsMaxY[i] = HALF_FLT_MAX;125node->mBoundsMaxZ[i] = HALF_FLT_MAX;126}127}128129// Since we don't keep track of the bounding box while descending the tree, we keep the root bounds at all levels for triangle compression130for (int i = 0; i < NumChildrenPerNode; ++i)131{132outChildBoundsMin[i] = inNodeBoundsMin;133outChildBoundsMax[i] = inNodeBoundsMax;134}135136return node_start;137}138139/// 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)140bool NodeFinalize(const AABBTreeBuilder::Node *inNode, size_t inNodeStart, uint inNumChildren, const size_t *inChildrenNodeStart, const size_t *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError)141{142if (!inNode->HasChildren())143return true;144145Node *node = ioBuffer.Get<Node>(inNodeStart);146for (uint i = 0; i < inNumChildren; ++i)147{148size_t offset;149if (node->mNodeProperties[i] != 0)150{151// This is a triangle block152offset = inChildrenTrianglesStart[i];153154// Store highest block with triangles so we can count the number of bits we need155mHighestTriangleBlock = max(mHighestTriangleBlock, offset);156}157else158{159// This is a node block160offset = inChildrenNodeStart[i];161}162163// Store offset of next node / triangles164if (offset & OFFSET_NON_SIGNIFICANT_MASK)165{166outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";167return false;168}169offset >>= OFFSET_NON_SIGNIFICANT_BITS;170if (offset > OFFSET_MASK)171{172outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";173return false;174}175node->mNodeProperties[i] |= uint32(offset);176}177178return true;179}180181/// Once all nodes have been finalized, this will finalize the header of the nodes182bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, size_t inRootNodeStart, size_t inRootTrianglesStart, const char *&outError) const183{184// Check if we can address the root node185size_t offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart;186if (offset & OFFSET_NON_SIGNIFICANT_MASK)187{188outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";189return false;190}191offset >>= OFFSET_NON_SIGNIFICANT_BITS;192if (offset > OFFSET_MASK)193{194outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";195return false;196}197198// If the root has triangles, we need to take that offset instead since the mHighestTriangleBlock will be zero199size_t highest_triangle_block = inRootTrianglesStart != size_t(-1)? inRootTrianglesStart : mHighestTriangleBlock;200highest_triangle_block >>= OFFSET_NON_SIGNIFICANT_BITS;201202inRoot->mBounds.mMin.StoreFloat3(&outHeader->mRootBoundsMin);203inRoot->mBounds.mMax.StoreFloat3(&outHeader->mRootBoundsMax);204outHeader->mRootProperties = uint32(offset) + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT);205outHeader->mBlockIDBits = uint8(32 - CountLeadingZeros(uint32(highest_triangle_block)));206if (inRoot->GetTriangleCount() >= TRIANGLE_COUNT_MASK)207{208outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";209return false;210}211212return true;213}214215private:216size_t mHighestTriangleBlock = 0;217};218219/// This class decodes and decompresses quad tree nodes220class DecodingContext221{222public:223/// Get the amount of bits needed to store an ID to a triangle block224inline static uint sTriangleBlockIDBits(const Header *inHeader)225{226return inHeader->mBlockIDBits;227}228229/// Convert a triangle block ID to the start of the triangle buffer230inline static const void * sGetTriangleBlockStart(const uint8 *inBufferStart, uint inTriangleBlockID)231{232return inBufferStart + (inTriangleBlockID << OFFSET_NON_SIGNIFICANT_BITS);233}234235/// Constructor236JPH_INLINE explicit DecodingContext(const Header *inHeader)237{238// Start with the root node on the stack239mNodeStack[0] = inHeader->mRootProperties;240}241242/// Walk the node tree calling the Visitor::VisitNodes for each node encountered and Visitor::VisitTriangles for each triangle encountered243template <class TriangleContext, class Visitor>244JPH_INLINE void WalkTree(const uint8 *inBufferStart, const TriangleContext &inTriangleContext, Visitor &ioVisitor)245{246do247{248// Test if node contains triangles249uint32 node_properties = mNodeStack[mTop];250uint32 tri_count = node_properties >> TRIANGLE_COUNT_SHIFT;251if (tri_count == 0)252{253const Node *node = reinterpret_cast<const Node *>(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS));254255// Unpack bounds256#ifdef JPH_CPU_BIG_ENDIAN257Vec4 bounds_minx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinX[0] + (node->mBoundsMinX[1] << 16), node->mBoundsMinX[2] + (node->mBoundsMinX[3] << 16), 0, 0));258Vec4 bounds_miny = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinY[0] + (node->mBoundsMinY[1] << 16), node->mBoundsMinY[2] + (node->mBoundsMinY[3] << 16), 0, 0));259Vec4 bounds_minz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinZ[0] + (node->mBoundsMinZ[1] << 16), node->mBoundsMinZ[2] + (node->mBoundsMinZ[3] << 16), 0, 0));260261Vec4 bounds_maxx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxX[0] + (node->mBoundsMaxX[1] << 16), node->mBoundsMaxX[2] + (node->mBoundsMaxX[3] << 16), 0, 0));262Vec4 bounds_maxy = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxY[0] + (node->mBoundsMaxY[1] << 16), node->mBoundsMaxY[2] + (node->mBoundsMaxY[3] << 16), 0, 0));263Vec4 bounds_maxz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxZ[0] + (node->mBoundsMaxZ[1] << 16), node->mBoundsMaxZ[2] + (node->mBoundsMaxZ[3] << 16), 0, 0));264#else265UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinX[0]));266Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy);267Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());268269UVec4 bounds_minzmaxx = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinZ[0]));270Vec4 bounds_minz = HalfFloatConversion::ToFloat(bounds_minzmaxx);271Vec4 bounds_maxx = HalfFloatConversion::ToFloat(bounds_minzmaxx.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());272273UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMaxY[0]));274Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz);275Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());276#endif277278// Load properties for 4 children279UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]);280281// Check which sub nodes to visit282int num_results = ioVisitor.VisitNodes(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, properties, mTop);283284// Push them onto the stack285JPH_ASSERT(mTop + 4 < StackSize);286properties.StoreInt4(&mNodeStack[mTop]);287mTop += num_results;288}289else 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)290{291// Node contains triangles, do individual tests292uint32 triangle_block_id = node_properties & OFFSET_MASK;293const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id);294295ioVisitor.VisitTriangles(inTriangleContext, triangles, tri_count, triangle_block_id);296}297298// Check if we're done299if (ioVisitor.ShouldAbort())300break;301302// Fetch next node until we find one that the visitor wants to see303do304--mTop;305while (mTop >= 0 && !ioVisitor.ShouldVisitNode(mTop));306}307while (mTop >= 0);308}309310/// This can be used to have the visitor early out (ioVisitor.ShouldAbort() returns true) and later continue again (call WalkTree() again)311bool IsDoneWalking() const312{313return mTop < 0;314}315316private:317uint32 mNodeStack[StackSize];318int mTop = 0;319};320};321322JPH_NAMESPACE_END323324325