Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h
9912 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#pragma once
6
7
#include <Jolt/Core/ByteBuffer.h>
8
#include <Jolt/Math/HalfFloat.h>
9
#include <Jolt/AABBTree/AABBTreeBuilder.h>
10
11
JPH_NAMESPACE_BEGIN
12
13
class NodeCodecQuadTreeHalfFloat
14
{
15
public:
16
/// Number of child nodes of this node
17
static constexpr int NumChildrenPerNode = 4;
18
19
/// Header for the tree
20
struct Header
21
{
22
Float3 mRootBoundsMin;
23
Float3 mRootBoundsMax;
24
uint32 mRootProperties;
25
uint8 mBlockIDBits; ///< Number of bits to address a triangle block
26
uint8 mPadding[3] = { 0 };
27
};
28
29
/// Size of the header (an empty struct is always > 0 bytes so this needs a separate variable)
30
static constexpr int HeaderSize = sizeof(Header);
31
32
/// Stack size to use during DecodingContext::sWalkTree
33
static constexpr int StackSize = 128;
34
35
/// Node properties
36
enum : uint32
37
{
38
TRIANGLE_COUNT_BITS = 4,
39
TRIANGLE_COUNT_SHIFT = 28,
40
TRIANGLE_COUNT_MASK = (1 << TRIANGLE_COUNT_BITS) - 1,
41
OFFSET_BITS = 28,
42
OFFSET_MASK = (1 << OFFSET_BITS) - 1,
43
OFFSET_NON_SIGNIFICANT_BITS = 2,
44
OFFSET_NON_SIGNIFICANT_MASK = (1 << OFFSET_NON_SIGNIFICANT_BITS) - 1,
45
};
46
47
/// Node structure
48
struct Node
49
{
50
HalfFloat mBoundsMinX[4]; ///< 4 child bounding boxes
51
HalfFloat mBoundsMinY[4];
52
HalfFloat mBoundsMinZ[4];
53
HalfFloat mBoundsMaxX[4];
54
HalfFloat mBoundsMaxY[4];
55
HalfFloat mBoundsMaxZ[4];
56
uint32 mNodeProperties[4]; ///< 4 child node properties
57
};
58
59
static_assert(sizeof(Node) == 64, "Node should be 64 bytes");
60
61
/// This class encodes and compresses quad tree nodes
62
class EncodingContext
63
{
64
public:
65
/// Mimics the size a call to NodeAllocate() would add to the buffer
66
void PrepareNodeAllocate(const AABBTreeBuilder::Node *inNode, uint64 &ioBufferSize) const
67
{
68
// We don't emit nodes for leafs
69
if (!inNode->HasChildren())
70
return;
71
72
// Add size of node
73
ioBufferSize += sizeof(Node);
74
}
75
76
/// Allocate a new node for inNode.
77
/// Algorithm can modify the order of ioChildren to indicate in which order children should be compressed
78
/// Algorithm can enlarge the bounding boxes of the children during compression and returns these in outChildBoundsMin, outChildBoundsMax
79
/// inNodeBoundsMin, inNodeBoundsMax is the bounding box if inNode possibly widened by compressing the parent node
80
/// Returns size_t(-1) on error and reports the error in outError
81
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
82
{
83
// We don't emit nodes for leafs
84
if (!inNode->HasChildren())
85
return ioBuffer.size();
86
87
// Remember the start of the node
88
size_t node_start = ioBuffer.size();
89
90
// Fill in bounds
91
Node *node = ioBuffer.Allocate<Node>();
92
93
for (size_t i = 0; i < 4; ++i)
94
{
95
if (i < ioChildren.size())
96
{
97
const AABBTreeBuilder::Node *this_node = ioChildren[i];
98
99
// Copy bounding box
100
node->mBoundsMinX[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetX());
101
node->mBoundsMinY[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetY());
102
node->mBoundsMinZ[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_NEG_INF>(this_node->mBounds.mMin.GetZ());
103
node->mBoundsMaxX[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetX());
104
node->mBoundsMaxY[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetY());
105
node->mBoundsMaxZ[i] = HalfFloatConversion::FromFloat<HalfFloatConversion::ROUND_TO_POS_INF>(this_node->mBounds.mMax.GetZ());
106
107
// Store triangle count
108
node->mNodeProperties[i] = this_node->GetTriangleCount() << TRIANGLE_COUNT_SHIFT;
109
if (this_node->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
110
{
111
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
112
return size_t(-1);
113
}
114
}
115
else
116
{
117
// Make this an invalid triangle node
118
node->mNodeProperties[i] = uint32(TRIANGLE_COUNT_MASK) << TRIANGLE_COUNT_SHIFT;
119
120
// Make bounding box invalid
121
node->mBoundsMinX[i] = HALF_FLT_MAX;
122
node->mBoundsMinY[i] = HALF_FLT_MAX;
123
node->mBoundsMinZ[i] = HALF_FLT_MAX;
124
node->mBoundsMaxX[i] = HALF_FLT_MAX;
125
node->mBoundsMaxY[i] = HALF_FLT_MAX;
126
node->mBoundsMaxZ[i] = HALF_FLT_MAX;
127
}
128
}
129
130
// 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
131
for (int i = 0; i < NumChildrenPerNode; ++i)
132
{
133
outChildBoundsMin[i] = inNodeBoundsMin;
134
outChildBoundsMax[i] = inNodeBoundsMax;
135
}
136
137
return node_start;
138
}
139
140
/// 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)
141
bool NodeFinalize(const AABBTreeBuilder::Node *inNode, size_t inNodeStart, uint inNumChildren, const size_t *inChildrenNodeStart, const size_t *inChildrenTrianglesStart, ByteBuffer &ioBuffer, const char *&outError)
142
{
143
if (!inNode->HasChildren())
144
return true;
145
146
Node *node = ioBuffer.Get<Node>(inNodeStart);
147
for (uint i = 0; i < inNumChildren; ++i)
148
{
149
size_t offset;
150
if (node->mNodeProperties[i] != 0)
151
{
152
// This is a triangle block
153
offset = inChildrenTrianglesStart[i];
154
155
// Store highest block with triangles so we can count the number of bits we need
156
mHighestTriangleBlock = max(mHighestTriangleBlock, offset);
157
}
158
else
159
{
160
// This is a node block
161
offset = inChildrenNodeStart[i];
162
}
163
164
// Store offset of next node / triangles
165
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
166
{
167
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";
168
return false;
169
}
170
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
171
if (offset > OFFSET_MASK)
172
{
173
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
174
return false;
175
}
176
node->mNodeProperties[i] |= uint32(offset);
177
}
178
179
return true;
180
}
181
182
/// Once all nodes have been finalized, this will finalize the header of the nodes
183
bool Finalize(Header *outHeader, const AABBTreeBuilder::Node *inRoot, size_t inRootNodeStart, size_t inRootTrianglesStart, const char *&outError) const
184
{
185
// Check if we can address the root node
186
size_t offset = inRoot->HasChildren()? inRootNodeStart : inRootTrianglesStart;
187
if (offset & OFFSET_NON_SIGNIFICANT_MASK)
188
{
189
outError = "NodeCodecQuadTreeHalfFloat: Internal Error: Offset has non-significant bits set";
190
return false;
191
}
192
offset >>= OFFSET_NON_SIGNIFICANT_BITS;
193
if (offset > OFFSET_MASK)
194
{
195
outError = "NodeCodecQuadTreeHalfFloat: Offset too large. Too much data.";
196
return false;
197
}
198
199
// If the root has triangles, we need to take that offset instead since the mHighestTriangleBlock will be zero
200
size_t highest_triangle_block = inRootTrianglesStart != size_t(-1)? inRootTrianglesStart : mHighestTriangleBlock;
201
highest_triangle_block >>= OFFSET_NON_SIGNIFICANT_BITS;
202
203
inRoot->mBounds.mMin.StoreFloat3(&outHeader->mRootBoundsMin);
204
inRoot->mBounds.mMax.StoreFloat3(&outHeader->mRootBoundsMax);
205
outHeader->mRootProperties = uint32(offset) + (inRoot->GetTriangleCount() << TRIANGLE_COUNT_SHIFT);
206
outHeader->mBlockIDBits = uint8(32 - CountLeadingZeros(uint32(highest_triangle_block)));
207
if (inRoot->GetTriangleCount() >= TRIANGLE_COUNT_MASK)
208
{
209
outError = "NodeCodecQuadTreeHalfFloat: Too many triangles";
210
return false;
211
}
212
213
return true;
214
}
215
216
private:
217
size_t mHighestTriangleBlock = 0;
218
};
219
220
/// This class decodes and decompresses quad tree nodes
221
class DecodingContext
222
{
223
public:
224
/// Get the amount of bits needed to store an ID to a triangle block
225
inline static uint sTriangleBlockIDBits(const Header *inHeader)
226
{
227
return inHeader->mBlockIDBits;
228
}
229
230
/// Convert a triangle block ID to the start of the triangle buffer
231
inline static const void * sGetTriangleBlockStart(const uint8 *inBufferStart, uint inTriangleBlockID)
232
{
233
return inBufferStart + (inTriangleBlockID << OFFSET_NON_SIGNIFICANT_BITS);
234
}
235
236
/// Constructor
237
JPH_INLINE explicit DecodingContext(const Header *inHeader)
238
{
239
// Start with the root node on the stack
240
mNodeStack[0] = inHeader->mRootProperties;
241
}
242
243
/// Walk the node tree calling the Visitor::VisitNodes for each node encountered and Visitor::VisitTriangles for each triangle encountered
244
template <class TriangleContext, class Visitor>
245
JPH_INLINE void WalkTree(const uint8 *inBufferStart, const TriangleContext &inTriangleContext, Visitor &ioVisitor)
246
{
247
do
248
{
249
// Test if node contains triangles
250
uint32 node_properties = mNodeStack[mTop];
251
uint32 tri_count = node_properties >> TRIANGLE_COUNT_SHIFT;
252
if (tri_count == 0)
253
{
254
const Node *node = reinterpret_cast<const Node *>(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS));
255
256
// Unpack bounds
257
#ifdef JPH_CPU_BIG_ENDIAN
258
Vec4 bounds_minx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinX[0] + (node->mBoundsMinX[1] << 16), node->mBoundsMinX[2] + (node->mBoundsMinX[3] << 16), 0, 0));
259
Vec4 bounds_miny = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinY[0] + (node->mBoundsMinY[1] << 16), node->mBoundsMinY[2] + (node->mBoundsMinY[3] << 16), 0, 0));
260
Vec4 bounds_minz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinZ[0] + (node->mBoundsMinZ[1] << 16), node->mBoundsMinZ[2] + (node->mBoundsMinZ[3] << 16), 0, 0));
261
262
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxX[0] + (node->mBoundsMaxX[1] << 16), node->mBoundsMaxX[2] + (node->mBoundsMaxX[3] << 16), 0, 0));
263
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxY[0] + (node->mBoundsMaxY[1] << 16), node->mBoundsMaxY[2] + (node->mBoundsMaxY[3] << 16), 0, 0));
264
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxZ[0] + (node->mBoundsMaxZ[1] << 16), node->mBoundsMaxZ[2] + (node->mBoundsMaxZ[3] << 16), 0, 0));
265
#else
266
UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinX[0]));
267
Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy);
268
Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
269
270
UVec4 bounds_minzmaxx = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinZ[0]));
271
Vec4 bounds_minz = HalfFloatConversion::ToFloat(bounds_minzmaxx);
272
Vec4 bounds_maxx = HalfFloatConversion::ToFloat(bounds_minzmaxx.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
273
274
UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMaxY[0]));
275
Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz);
276
Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
277
#endif
278
279
// Load properties for 4 children
280
UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]);
281
282
// Check which sub nodes to visit
283
int num_results = ioVisitor.VisitNodes(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, properties, mTop);
284
285
// Push them onto the stack
286
JPH_ASSERT(mTop + 4 < StackSize);
287
properties.StoreInt4(&mNodeStack[mTop]);
288
mTop += num_results;
289
}
290
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)
291
{
292
// Node contains triangles, do individual tests
293
uint32 triangle_block_id = node_properties & OFFSET_MASK;
294
const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id);
295
296
ioVisitor.VisitTriangles(inTriangleContext, triangles, tri_count, triangle_block_id);
297
}
298
299
// Check if we're done
300
if (ioVisitor.ShouldAbort())
301
break;
302
303
// Fetch next node until we find one that the visitor wants to see
304
do
305
--mTop;
306
while (mTop >= 0 && !ioVisitor.ShouldVisitNode(mTop));
307
}
308
while (mTop >= 0);
309
}
310
311
/// This can be used to have the visitor early out (ioVisitor.ShouldAbort() returns true) and later continue again (call WalkTree() again)
312
bool IsDoneWalking() const
313
{
314
return mTop < 0;
315
}
316
317
private:
318
uint32 mNodeStack[StackSize];
319
int mTop = 0;
320
};
321
};
322
323
JPH_NAMESPACE_END
324
325