Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp
9918 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Collision/BroadPhase/QuadTree.h>
8
#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h>
9
#include <Jolt/Physics/Collision/RayCast.h>
10
#include <Jolt/Physics/Collision/AABoxCast.h>
11
#include <Jolt/Physics/Collision/CastResult.h>
12
#include <Jolt/Physics/Collision/SortReverseAndStore.h>
13
#include <Jolt/Physics/Body/BodyPair.h>
14
#include <Jolt/Physics/PhysicsLock.h>
15
#include <Jolt/Geometry/AABox4.h>
16
#include <Jolt/Geometry/RayAABox.h>
17
#include <Jolt/Geometry/OrientedBox.h>
18
#include <Jolt/Core/STLLocalAllocator.h>
19
20
#ifdef JPH_DUMP_BROADPHASE_TREE
21
JPH_SUPPRESS_WARNINGS_STD_BEGIN
22
#include <fstream>
23
JPH_SUPPRESS_WARNINGS_STD_END
24
#endif // JPH_DUMP_BROADPHASE_TREE
25
26
JPH_NAMESPACE_BEGIN
27
28
////////////////////////////////////////////////////////////////////////////////////////////////////////
29
// QuadTree::Node
30
////////////////////////////////////////////////////////////////////////////////////////////////////////
31
32
QuadTree::Node::Node(bool inIsChanged) :
33
mIsChanged(inIsChanged)
34
{
35
// First reset bounds
36
Vec4 val = Vec4::sReplicate(cLargeFloat);
37
val.StoreFloat4((Float4 *)&mBoundsMinX);
38
val.StoreFloat4((Float4 *)&mBoundsMinY);
39
val.StoreFloat4((Float4 *)&mBoundsMinZ);
40
val = Vec4::sReplicate(-cLargeFloat);
41
val.StoreFloat4((Float4 *)&mBoundsMaxX);
42
val.StoreFloat4((Float4 *)&mBoundsMaxY);
43
val.StoreFloat4((Float4 *)&mBoundsMaxZ);
44
45
// Reset child node ids
46
mChildNodeID[0] = NodeID::sInvalid();
47
mChildNodeID[1] = NodeID::sInvalid();
48
mChildNodeID[2] = NodeID::sInvalid();
49
mChildNodeID[3] = NodeID::sInvalid();
50
}
51
52
void QuadTree::Node::GetChildBounds(int inChildIndex, AABox &outBounds) const
53
{
54
// Read bounding box in order min -> max
55
outBounds.mMin = Vec3(mBoundsMinX[inChildIndex], mBoundsMinY[inChildIndex], mBoundsMinZ[inChildIndex]);
56
outBounds.mMax = Vec3(mBoundsMaxX[inChildIndex], mBoundsMaxY[inChildIndex], mBoundsMaxZ[inChildIndex]);
57
}
58
59
void QuadTree::Node::SetChildBounds(int inChildIndex, const AABox &inBounds)
60
{
61
// Bounding boxes provided to the quad tree should never be larger than cLargeFloat because this may trigger overflow exceptions
62
// e.g. when squaring the value while testing sphere overlaps
63
JPH_ASSERT(inBounds.mMin.GetX() >= -cLargeFloat && inBounds.mMin.GetX() <= cLargeFloat
64
&& inBounds.mMin.GetY() >= -cLargeFloat && inBounds.mMin.GetY() <= cLargeFloat
65
&& inBounds.mMin.GetZ() >= -cLargeFloat && inBounds.mMin.GetZ() <= cLargeFloat
66
&& inBounds.mMax.GetX() >= -cLargeFloat && inBounds.mMax.GetX() <= cLargeFloat
67
&& inBounds.mMax.GetY() >= -cLargeFloat && inBounds.mMax.GetY() <= cLargeFloat
68
&& inBounds.mMax.GetZ() >= -cLargeFloat && inBounds.mMax.GetZ() <= cLargeFloat);
69
70
// Set max first (this keeps the bounding box invalid for reading threads)
71
mBoundsMaxZ[inChildIndex] = inBounds.mMax.GetZ();
72
mBoundsMaxY[inChildIndex] = inBounds.mMax.GetY();
73
mBoundsMaxX[inChildIndex] = inBounds.mMax.GetX();
74
75
// Then set min (and make box valid)
76
mBoundsMinZ[inChildIndex] = inBounds.mMin.GetZ();
77
mBoundsMinY[inChildIndex] = inBounds.mMin.GetY();
78
mBoundsMinX[inChildIndex] = inBounds.mMin.GetX(); // Min X becomes valid last
79
}
80
81
void QuadTree::Node::InvalidateChildBounds(int inChildIndex)
82
{
83
// First we make the box invalid by setting the min to cLargeFloat
84
mBoundsMinX[inChildIndex] = cLargeFloat; // Min X becomes invalid first
85
mBoundsMinY[inChildIndex] = cLargeFloat;
86
mBoundsMinZ[inChildIndex] = cLargeFloat;
87
88
// Then we reset the max values too
89
mBoundsMaxX[inChildIndex] = -cLargeFloat;
90
mBoundsMaxY[inChildIndex] = -cLargeFloat;
91
mBoundsMaxZ[inChildIndex] = -cLargeFloat;
92
}
93
94
void QuadTree::Node::GetNodeBounds(AABox &outBounds) const
95
{
96
// Get first child bounds
97
GetChildBounds(0, outBounds);
98
99
// Encapsulate other child bounds
100
for (int child_idx = 1; child_idx < 4; ++child_idx)
101
{
102
AABox tmp;
103
GetChildBounds(child_idx, tmp);
104
outBounds.Encapsulate(tmp);
105
}
106
}
107
108
bool QuadTree::Node::EncapsulateChildBounds(int inChildIndex, const AABox &inBounds)
109
{
110
bool changed = AtomicMin(mBoundsMinX[inChildIndex], inBounds.mMin.GetX());
111
changed |= AtomicMin(mBoundsMinY[inChildIndex], inBounds.mMin.GetY());
112
changed |= AtomicMin(mBoundsMinZ[inChildIndex], inBounds.mMin.GetZ());
113
changed |= AtomicMax(mBoundsMaxX[inChildIndex], inBounds.mMax.GetX());
114
changed |= AtomicMax(mBoundsMaxY[inChildIndex], inBounds.mMax.GetY());
115
changed |= AtomicMax(mBoundsMaxZ[inChildIndex], inBounds.mMax.GetZ());
116
return changed;
117
}
118
119
////////////////////////////////////////////////////////////////////////////////////////////////////////
120
// QuadTree
121
////////////////////////////////////////////////////////////////////////////////////////////////////////
122
123
const AABox QuadTree::cInvalidBounds(Vec3::sReplicate(cLargeFloat), Vec3::sReplicate(-cLargeFloat));
124
125
static inline void sQuadTreePerformanceWarning()
126
{
127
#ifdef JPH_ENABLE_ASSERTS
128
static atomic<bool> triggered_report { false };
129
bool expected = false;
130
if (triggered_report.compare_exchange_strong(expected, true))
131
Trace("QuadTree: Performance warning: Stack full!\n"
132
"This must be a very deep tree. Are you batch adding bodies through BodyInterface::AddBodiesPrepare/AddBodiesFinalize?\n"
133
"If you add lots of bodies through BodyInterface::AddBody you may need to call PhysicsSystem::OptimizeBroadPhase to rebuild the tree.");
134
#endif
135
}
136
137
void QuadTree::GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const
138
{
139
uint32 body_location = inTracking[inBodyID.GetIndex()].mBodyLocation;
140
JPH_ASSERT(body_location != Tracking::cInvalidBodyLocation);
141
outNodeIdx = body_location & 0x3fffffff;
142
outChildIdx = body_location >> 30;
143
JPH_ASSERT(mAllocator->Get(outNodeIdx).mChildNodeID[outChildIdx] == inBodyID, "Make sure that the body is in the node where it should be");
144
}
145
146
void QuadTree::SetBodyLocation(TrackingVector &ioTracking, BodyID inBodyID, uint32 inNodeIdx, uint32 inChildIdx) const
147
{
148
JPH_ASSERT(inNodeIdx <= 0x3fffffff);
149
JPH_ASSERT(inChildIdx < 4);
150
JPH_ASSERT(mAllocator->Get(inNodeIdx).mChildNodeID[inChildIdx] == inBodyID, "Make sure that the body is in the node where it should be");
151
ioTracking[inBodyID.GetIndex()].mBodyLocation = inNodeIdx + (inChildIdx << 30);
152
153
#ifdef JPH_ENABLE_ASSERTS
154
uint32 v1, v2;
155
GetBodyLocation(ioTracking, inBodyID, v1, v2);
156
JPH_ASSERT(v1 == inNodeIdx);
157
JPH_ASSERT(v2 == inChildIdx);
158
#endif
159
}
160
161
void QuadTree::sInvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID)
162
{
163
ioTracking[inBodyID.GetIndex()].mBodyLocation = Tracking::cInvalidBodyLocation;
164
}
165
166
QuadTree::~QuadTree()
167
{
168
// Get rid of any nodes that are still to be freed
169
DiscardOldTree();
170
171
// Get the current root node
172
const RootNode &root_node = GetCurrentRoot();
173
174
// Collect all bodies
175
Allocator::Batch free_batch;
176
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;
177
node_stack.reserve(cStackSize);
178
node_stack.push_back(root_node.GetNodeID());
179
JPH_ASSERT(node_stack.front().IsValid());
180
if (node_stack.front().IsNode())
181
{
182
do
183
{
184
// Process node
185
NodeID node_id = node_stack.back();
186
node_stack.pop_back();
187
JPH_ASSERT(!node_id.IsBody());
188
uint32 node_idx = node_id.GetNodeIndex();
189
const Node &node = mAllocator->Get(node_idx);
190
191
// Recurse and get all child nodes
192
for (NodeID child_node_id : node.mChildNodeID)
193
if (child_node_id.IsValid() && child_node_id.IsNode())
194
node_stack.push_back(child_node_id);
195
196
// Mark node to be freed
197
mAllocator->AddObjectToBatch(free_batch, node_idx);
198
}
199
while (!node_stack.empty());
200
}
201
202
// Now free all nodes
203
mAllocator->DestructObjectBatch(free_batch);
204
}
205
206
uint32 QuadTree::AllocateNode(bool inIsChanged)
207
{
208
uint32 index = mAllocator->ConstructObject(inIsChanged);
209
if (index == Allocator::cInvalidObjectIndex)
210
{
211
// If you're running out of nodes, you're most likely adding too many individual bodies to the tree.
212
// Because of the lock free nature of this tree, any individual body is added to the root of the tree.
213
// This means that if you add a lot of bodies individually, you will end up with a very deep tree and you'll be
214
// using a lot more nodes than you would if you added them in batches.
215
// Please look at BodyInterface::AddBodiesPrepare/AddBodiesFinalize.
216
//
217
// If you have created a wrapper around Jolt then a possible solution is to activate a mode during loading
218
// that queues up any bodies that need to be added. When loading is done, insert all of them as a single batch.
219
// This could be implemented as a 'start batching' / 'end batching' call to switch in and out of that mode.
220
// The rest of the code can then just use the regular 'add single body' call on your wrapper and doesn't need to know
221
// if this mode is active or not.
222
//
223
// Calling PhysicsSystem::Update or PhysicsSystem::OptimizeBroadPhase will perform maintenance
224
// on the tree and will make it efficient again. If you're not calling these functions and are adding a lot of bodies
225
// you could still be running out of nodes because the tree is not being maintained. If your application is paused,
226
// consider still calling PhysicsSystem::Update with a delta time of 0 to keep the tree in good shape.
227
//
228
// The system keeps track of a previous and a current tree, this allows for queries to continue using the old tree
229
// while the new tree is being built. If you completely clean the PhysicsSystem and rebuild it from scratch, you may
230
// want to call PhysicsSystem::OptimizeBroadPhase two times after clearing to completely get rid of any lingering nodes.
231
//
232
// The number of nodes that is allocated is related to the max number of bodies that is passed in PhysicsSystem::Init.
233
// For normal situations there are plenty of nodes available. If all else fails, you can increase the number of nodes
234
// by increasing the maximum number of bodies.
235
Trace("QuadTree: Out of nodes!");
236
std::abort();
237
}
238
return index;
239
}
240
241
void QuadTree::Init(Allocator &inAllocator)
242
{
243
// Store allocator
244
mAllocator = &inAllocator;
245
246
// Allocate root node
247
mRootNode[mRootNodeIndex].mIndex = AllocateNode(false);
248
}
249
250
void QuadTree::DiscardOldTree()
251
{
252
// Check if there is an old tree
253
RootNode &old_root_node = mRootNode[mRootNodeIndex ^ 1];
254
if (old_root_node.mIndex != cInvalidNodeIndex)
255
{
256
// Clear the root
257
old_root_node.mIndex = cInvalidNodeIndex;
258
259
// Now free all old nodes
260
mAllocator->DestructObjectBatch(mFreeNodeBatch);
261
262
// Clear the batch
263
mFreeNodeBatch = Allocator::Batch();
264
}
265
}
266
267
AABox QuadTree::GetBounds() const
268
{
269
uint32 node_idx = GetCurrentRoot().mIndex;
270
JPH_ASSERT(node_idx != cInvalidNodeIndex);
271
const Node &node = mAllocator->Get(node_idx);
272
273
AABox bounds;
274
node.GetNodeBounds(bounds);
275
return bounds;
276
}
277
278
void QuadTree::UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild)
279
{
280
#ifdef JPH_ENABLE_ASSERTS
281
// We only read positions
282
BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
283
#endif
284
285
// Assert we have no nodes pending deletion, this means DiscardOldTree wasn't called yet
286
JPH_ASSERT(mFreeNodeBatch.mNumObjects == 0);
287
288
// Mark tree non-dirty
289
mIsDirty = false;
290
291
// Get the current root node
292
const RootNode &root_node = GetCurrentRoot();
293
294
#ifdef JPH_DUMP_BROADPHASE_TREE
295
DumpTree(root_node.GetNodeID(), StringFormat("%s_PRE", mName).c_str());
296
#endif
297
298
// Assert sane data
299
#ifdef JPH_DEBUG
300
ValidateTree(inBodies, ioTracking, root_node.mIndex, mNumBodies);
301
#endif
302
303
// Create space for all body ID's
304
NodeID *node_ids = mNumBodies > 0? new NodeID [mNumBodies] : nullptr;
305
NodeID *cur_node_id = node_ids;
306
307
// Collect all bodies
308
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;
309
node_stack.reserve(cStackSize);
310
node_stack.push_back(root_node.GetNodeID());
311
JPH_ASSERT(node_stack.front().IsValid());
312
do
313
{
314
// Pop node from stack
315
NodeID node_id = node_stack.back();
316
node_stack.pop_back();
317
318
// Check if node is a body
319
if (node_id.IsBody())
320
{
321
// Validate that we're still in the right layer
322
#ifdef JPH_ENABLE_ASSERTS
323
uint32 body_index = node_id.GetBodyID().GetIndex();
324
JPH_ASSERT(ioTracking[body_index].mObjectLayer == inBodies[body_index]->GetObjectLayer());
325
#endif
326
327
// Store body
328
*cur_node_id = node_id;
329
++cur_node_id;
330
}
331
else
332
{
333
// Process normal node
334
uint32 node_idx = node_id.GetNodeIndex();
335
const Node &node = mAllocator->Get(node_idx);
336
337
if (!node.mIsChanged && !inFullRebuild)
338
{
339
// Node is unchanged, treat it as a whole
340
*cur_node_id = node_id;
341
++cur_node_id;
342
}
343
else
344
{
345
// Node is changed, recurse and get all children
346
for (NodeID child_node_id : node.mChildNodeID)
347
if (child_node_id.IsValid())
348
node_stack.push_back(child_node_id);
349
350
// Mark node to be freed
351
mAllocator->AddObjectToBatch(mFreeNodeBatch, node_idx);
352
}
353
}
354
}
355
while (!node_stack.empty());
356
357
// Check that our book keeping matches
358
uint32 num_node_ids = uint32(cur_node_id - node_ids);
359
JPH_ASSERT(inFullRebuild? num_node_ids == mNumBodies : num_node_ids <= mNumBodies);
360
361
// This will be the new root node id
362
NodeID root_node_id;
363
364
if (num_node_ids > 0)
365
{
366
// We mark the first 5 levels (max 1024 nodes) of the newly built tree as 'changed' so that
367
// those nodes get recreated every time when we rebuild the tree. This balances the amount of
368
// time we spend on rebuilding the tree ('unchanged' nodes will be put in the new tree as a whole)
369
// vs the quality of the built tree.
370
constexpr uint cMaxDepthMarkChanged = 5;
371
372
// Build new tree
373
AABox root_bounds;
374
root_node_id = BuildTree(inBodies, ioTracking, node_ids, num_node_ids, cMaxDepthMarkChanged, root_bounds);
375
376
if (root_node_id.IsBody())
377
{
378
// For a single body we need to allocate a new root node
379
uint32 root_idx = AllocateNode(false);
380
Node &root = mAllocator->Get(root_idx);
381
root.SetChildBounds(0, root_bounds);
382
root.mChildNodeID[0] = root_node_id;
383
SetBodyLocation(ioTracking, root_node_id.GetBodyID(), root_idx, 0);
384
root_node_id = NodeID::sFromNodeIndex(root_idx);
385
}
386
}
387
else
388
{
389
// Empty tree, create root node
390
uint32 root_idx = AllocateNode(false);
391
root_node_id = NodeID::sFromNodeIndex(root_idx);
392
}
393
394
// Delete temporary data
395
delete [] node_ids;
396
397
outUpdateState.mRootNodeID = root_node_id;
398
}
399
400
void QuadTree::UpdateFinalize([[maybe_unused]] const BodyVector &inBodies, [[maybe_unused]] const TrackingVector &inTracking, const UpdateState &inUpdateState)
401
{
402
// Tree building is complete, now we switch the old with the new tree
403
uint32 new_root_idx = mRootNodeIndex ^ 1;
404
RootNode &new_root_node = mRootNode[new_root_idx];
405
{
406
// Note: We don't need to lock here as the old tree stays available so any queries
407
// that use it can continue using it until DiscardOldTree is called. This slot
408
// should be empty and unused at this moment.
409
JPH_ASSERT(new_root_node.mIndex == cInvalidNodeIndex);
410
new_root_node.mIndex = inUpdateState.mRootNodeID.GetNodeIndex();
411
}
412
413
// All queries that start from now on will use this new tree
414
mRootNodeIndex = new_root_idx;
415
416
#ifdef JPH_DUMP_BROADPHASE_TREE
417
DumpTree(new_root_node.GetNodeID(), StringFormat("%s_POST", mName).c_str());
418
#endif
419
420
#ifdef JPH_DEBUG
421
ValidateTree(inBodies, inTracking, new_root_node.mIndex, mNumBodies);
422
#endif
423
}
424
425
void QuadTree::sPartition(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inNumber, int &outMidPoint)
426
{
427
// Handle trivial case
428
if (inNumber <= 4)
429
{
430
outMidPoint = inNumber / 2;
431
return;
432
}
433
434
// Calculate bounding box of box centers
435
Vec3 center_min = Vec3::sReplicate(cLargeFloat);
436
Vec3 center_max = Vec3::sReplicate(-cLargeFloat);
437
for (const Vec3 *c = ioNodeCenters, *c_end = ioNodeCenters + inNumber; c < c_end; ++c)
438
{
439
Vec3 center = *c;
440
center_min = Vec3::sMin(center_min, center);
441
center_max = Vec3::sMax(center_max, center);
442
}
443
444
// Calculate split plane
445
int dimension = (center_max - center_min).GetHighestComponentIndex();
446
float split = 0.5f * (center_min + center_max)[dimension];
447
448
// Divide bodies
449
int start = 0, end = inNumber;
450
while (start < end)
451
{
452
// Search for first element that is on the right hand side of the split plane
453
while (start < end && ioNodeCenters[start][dimension] < split)
454
++start;
455
456
// Search for the first element that is on the left hand side of the split plane
457
while (start < end && ioNodeCenters[end - 1][dimension] >= split)
458
--end;
459
460
if (start < end)
461
{
462
// Swap the two elements
463
std::swap(ioNodeIDs[start], ioNodeIDs[end - 1]);
464
std::swap(ioNodeCenters[start], ioNodeCenters[end - 1]);
465
++start;
466
--end;
467
}
468
}
469
JPH_ASSERT(start == end);
470
471
if (start > 0 && start < inNumber)
472
{
473
// Success!
474
outMidPoint = start;
475
}
476
else
477
{
478
// Failed to divide bodies
479
outMidPoint = inNumber / 2;
480
}
481
}
482
483
void QuadTree::sPartition4(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inBegin, int inEnd, int *outSplit)
484
{
485
NodeID *node_ids = ioNodeIDs + inBegin;
486
Vec3 *node_centers = ioNodeCenters + inBegin;
487
int number = inEnd - inBegin;
488
489
// Partition entire range
490
sPartition(node_ids, node_centers, number, outSplit[2]);
491
492
// Partition lower half
493
sPartition(node_ids, node_centers, outSplit[2], outSplit[1]);
494
495
// Partition upper half
496
sPartition(node_ids + outSplit[2], node_centers + outSplit[2], number - outSplit[2], outSplit[3]);
497
498
// Convert to proper range
499
outSplit[0] = inBegin;
500
outSplit[1] += inBegin;
501
outSplit[2] += inBegin;
502
outSplit[3] += outSplit[2];
503
outSplit[4] = inEnd;
504
}
505
506
AABox QuadTree::GetNodeOrBodyBounds(const BodyVector &inBodies, NodeID inNodeID) const
507
{
508
if (inNodeID.IsNode())
509
{
510
// It is a node
511
uint32 node_idx = inNodeID.GetNodeIndex();
512
const Node &node = mAllocator->Get(node_idx);
513
514
AABox bounds;
515
node.GetNodeBounds(bounds);
516
return bounds;
517
}
518
else
519
{
520
// It is a body
521
return inBodies[inNodeID.GetBodyID().GetIndex()]->GetWorldSpaceBounds();
522
}
523
}
524
525
QuadTree::NodeID QuadTree::BuildTree(const BodyVector &inBodies, TrackingVector &ioTracking, NodeID *ioNodeIDs, int inNumber, uint inMaxDepthMarkChanged, AABox &outBounds)
526
{
527
// Trivial case: No bodies in tree
528
if (inNumber == 0)
529
{
530
outBounds = cInvalidBounds;
531
return NodeID::sInvalid();
532
}
533
534
// Trivial case: When we have 1 body or node, return it
535
if (inNumber == 1)
536
{
537
if (ioNodeIDs->IsNode())
538
{
539
// When returning an existing node as root, ensure that no parent has been set
540
Node &node = mAllocator->Get(ioNodeIDs->GetNodeIndex());
541
node.mParentNodeIndex = cInvalidNodeIndex;
542
}
543
outBounds = GetNodeOrBodyBounds(inBodies, *ioNodeIDs);
544
return *ioNodeIDs;
545
}
546
547
// Calculate centers of all bodies that are to be inserted
548
Vec3 *centers = new Vec3 [inNumber];
549
JPH_ASSERT(IsAligned(centers, JPH_VECTOR_ALIGNMENT));
550
Vec3 *c = centers;
551
for (const NodeID *n = ioNodeIDs, *n_end = ioNodeIDs + inNumber; n < n_end; ++n, ++c)
552
*c = GetNodeOrBodyBounds(inBodies, *n).GetCenter();
553
554
// The algorithm is a recursive tree build, but to avoid the call overhead we keep track of a stack here
555
struct StackEntry
556
{
557
uint32 mNodeIdx; // Node index of node that is generated
558
int mChildIdx; // Index of child that we're currently processing
559
int mSplit[5]; // Indices where the node ID's have been split to form 4 partitions
560
uint32 mDepth; // Depth of this node in the tree
561
Vec3 mNodeBoundsMin; // Bounding box of this node, accumulated while iterating over children
562
Vec3 mNodeBoundsMax;
563
};
564
static_assert(sizeof(StackEntry) == 64);
565
StackEntry stack[cStackSize / 4]; // We don't process 4 at a time in this loop but 1, so the stack can be 4x as small
566
int top = 0;
567
568
// Create root node
569
stack[0].mNodeIdx = AllocateNode(inMaxDepthMarkChanged > 0);
570
stack[0].mChildIdx = -1;
571
stack[0].mDepth = 0;
572
stack[0].mNodeBoundsMin = Vec3::sReplicate(cLargeFloat);
573
stack[0].mNodeBoundsMax = Vec3::sReplicate(-cLargeFloat);
574
sPartition4(ioNodeIDs, centers, 0, inNumber, stack[0].mSplit);
575
576
for (;;)
577
{
578
StackEntry &cur_stack = stack[top];
579
580
// Next child
581
cur_stack.mChildIdx++;
582
583
// Check if all children processed
584
if (cur_stack.mChildIdx >= 4)
585
{
586
// Terminate if there's nothing left to pop
587
if (top <= 0)
588
break;
589
590
// Add our bounds to our parents bounds
591
StackEntry &prev_stack = stack[top - 1];
592
prev_stack.mNodeBoundsMin = Vec3::sMin(prev_stack.mNodeBoundsMin, cur_stack.mNodeBoundsMin);
593
prev_stack.mNodeBoundsMax = Vec3::sMax(prev_stack.mNodeBoundsMax, cur_stack.mNodeBoundsMax);
594
595
// Store parent node
596
Node &node = mAllocator->Get(cur_stack.mNodeIdx);
597
node.mParentNodeIndex = prev_stack.mNodeIdx;
598
599
// Store this node's properties in the parent node
600
Node &parent_node = mAllocator->Get(prev_stack.mNodeIdx);
601
parent_node.mChildNodeID[prev_stack.mChildIdx] = NodeID::sFromNodeIndex(cur_stack.mNodeIdx);
602
parent_node.SetChildBounds(prev_stack.mChildIdx, AABox(cur_stack.mNodeBoundsMin, cur_stack.mNodeBoundsMax));
603
604
// Pop entry from stack
605
--top;
606
}
607
else
608
{
609
// Get low and high index to bodies to process
610
int low = cur_stack.mSplit[cur_stack.mChildIdx];
611
int high = cur_stack.mSplit[cur_stack.mChildIdx + 1];
612
int num_bodies = high - low;
613
614
if (num_bodies == 1)
615
{
616
// Get body info
617
NodeID child_node_id = ioNodeIDs[low];
618
AABox bounds = GetNodeOrBodyBounds(inBodies, child_node_id);
619
620
// Update node
621
Node &node = mAllocator->Get(cur_stack.mNodeIdx);
622
node.mChildNodeID[cur_stack.mChildIdx] = child_node_id;
623
node.SetChildBounds(cur_stack.mChildIdx, bounds);
624
625
if (child_node_id.IsNode())
626
{
627
// Update parent for this node
628
Node &child_node = mAllocator->Get(child_node_id.GetNodeIndex());
629
child_node.mParentNodeIndex = cur_stack.mNodeIdx;
630
}
631
else
632
{
633
// Set location in tracking
634
SetBodyLocation(ioTracking, child_node_id.GetBodyID(), cur_stack.mNodeIdx, cur_stack.mChildIdx);
635
}
636
637
// Encapsulate bounding box in parent
638
cur_stack.mNodeBoundsMin = Vec3::sMin(cur_stack.mNodeBoundsMin, bounds.mMin);
639
cur_stack.mNodeBoundsMax = Vec3::sMax(cur_stack.mNodeBoundsMax, bounds.mMax);
640
}
641
else if (num_bodies > 1)
642
{
643
// Allocate new node
644
StackEntry &new_stack = stack[++top];
645
JPH_ASSERT(top < cStackSize / 4);
646
uint32 next_depth = cur_stack.mDepth + 1;
647
new_stack.mNodeIdx = AllocateNode(inMaxDepthMarkChanged > next_depth);
648
new_stack.mChildIdx = -1;
649
new_stack.mDepth = next_depth;
650
new_stack.mNodeBoundsMin = Vec3::sReplicate(cLargeFloat);
651
new_stack.mNodeBoundsMax = Vec3::sReplicate(-cLargeFloat);
652
sPartition4(ioNodeIDs, centers, low, high, new_stack.mSplit);
653
}
654
}
655
}
656
657
// Delete temporary data
658
delete [] centers;
659
660
// Store bounding box of root
661
outBounds.mMin = stack[0].mNodeBoundsMin;
662
outBounds.mMax = stack[0].mNodeBoundsMax;
663
664
// Return root
665
return NodeID::sFromNodeIndex(stack[0].mNodeIdx);
666
}
667
668
void QuadTree::MarkNodeAndParentsChanged(uint32 inNodeIndex)
669
{
670
uint32 node_idx = inNodeIndex;
671
672
do
673
{
674
// If node has changed, parent will be too
675
Node &node = mAllocator->Get(node_idx);
676
if (node.mIsChanged)
677
break;
678
679
// Mark node as changed
680
node.mIsChanged = true;
681
682
// Get our parent
683
node_idx = node.mParentNodeIndex;
684
}
685
while (node_idx != cInvalidNodeIndex);
686
}
687
688
void QuadTree::WidenAndMarkNodeAndParentsChanged(uint32 inNodeIndex, const AABox &inNewBounds)
689
{
690
uint32 node_idx = inNodeIndex;
691
692
for (;;)
693
{
694
// Mark node as changed
695
Node &node = mAllocator->Get(node_idx);
696
node.mIsChanged = true;
697
698
// Get our parent
699
uint32 parent_idx = node.mParentNodeIndex;
700
if (parent_idx == cInvalidNodeIndex)
701
break;
702
703
// Find which child of the parent we're in
704
Node &parent_node = mAllocator->Get(parent_idx);
705
NodeID node_id = NodeID::sFromNodeIndex(node_idx);
706
int child_idx = -1;
707
for (int i = 0; i < 4; ++i)
708
if (parent_node.mChildNodeID[i] == node_id)
709
{
710
// Found one, set the node index and child index and update the bounding box too
711
child_idx = i;
712
break;
713
}
714
JPH_ASSERT(child_idx != -1, "Nodes don't get removed from the tree, we must have found it");
715
716
// To avoid any race conditions with other threads we only enlarge bounding boxes
717
if (!parent_node.EncapsulateChildBounds(child_idx, inNewBounds))
718
{
719
// No changes to bounding box, only marking as changed remains to be done
720
if (!parent_node.mIsChanged)
721
MarkNodeAndParentsChanged(parent_idx);
722
break;
723
}
724
725
// Update node index
726
node_idx = parent_idx;
727
}
728
}
729
730
bool QuadTree::TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)
731
{
732
// Tentatively assign the node as parent
733
bool leaf_is_node = inLeafID.IsNode();
734
if (leaf_is_node)
735
{
736
uint32 leaf_idx = inLeafID.GetNodeIndex();
737
mAllocator->Get(leaf_idx).mParentNodeIndex = inNodeIndex;
738
}
739
740
// Fetch node that we're adding to
741
Node &node = mAllocator->Get(inNodeIndex);
742
743
// Find an empty child
744
for (uint32 child_idx = 0; child_idx < 4; ++child_idx)
745
if (node.mChildNodeID[child_idx].CompareExchange(NodeID::sInvalid(), inLeafID)) // Check if we can claim it
746
{
747
// We managed to add it to the node
748
749
// If leaf was a body, we need to update its bookkeeping
750
if (!leaf_is_node)
751
SetBodyLocation(ioTracking, inLeafID.GetBodyID(), inNodeIndex, child_idx);
752
753
// Now set the bounding box making the child valid for queries
754
node.SetChildBounds(child_idx, inLeafBounds);
755
756
// Widen the bounds for our parents too
757
WidenAndMarkNodeAndParentsChanged(inNodeIndex, inLeafBounds);
758
759
// Update body counter
760
mNumBodies += inLeafNumBodies;
761
762
// And we're done
763
return true;
764
}
765
766
return false;
767
}
768
769
bool QuadTree::TryCreateNewRoot(TrackingVector &ioTracking, atomic<uint32> &ioRootNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)
770
{
771
// Fetch old root
772
uint32 root_idx = ioRootNodeIndex;
773
Node &root = mAllocator->Get(root_idx);
774
775
// Create new root, mark this new root as changed as we're not creating a very efficient tree at this point
776
uint32 new_root_idx = AllocateNode(true);
777
Node &new_root = mAllocator->Get(new_root_idx);
778
779
// First child is current root, note that since the tree may be modified concurrently we cannot assume that the bounds of our child will be correct so we set a very large bounding box
780
new_root.mChildNodeID[0] = NodeID::sFromNodeIndex(root_idx);
781
new_root.SetChildBounds(0, AABox(Vec3::sReplicate(-cLargeFloat), Vec3::sReplicate(cLargeFloat)));
782
783
// Second child is new leaf
784
new_root.mChildNodeID[1] = inLeafID;
785
new_root.SetChildBounds(1, inLeafBounds);
786
787
// Tentatively assign new root as parent
788
bool leaf_is_node = inLeafID.IsNode();
789
if (leaf_is_node)
790
{
791
uint32 leaf_idx = inLeafID.GetNodeIndex();
792
mAllocator->Get(leaf_idx).mParentNodeIndex = new_root_idx;
793
}
794
795
// Try to swap it
796
if (ioRootNodeIndex.compare_exchange_strong(root_idx, new_root_idx))
797
{
798
// We managed to set the new root
799
800
// If leaf was a body, we need to update its bookkeeping
801
if (!leaf_is_node)
802
SetBodyLocation(ioTracking, inLeafID.GetBodyID(), new_root_idx, 1);
803
804
// Store parent node for old root
805
root.mParentNodeIndex = new_root_idx;
806
807
// Update body counter
808
mNumBodies += inLeafNumBodies;
809
810
// And we're done
811
return true;
812
}
813
814
// Failed to swap, someone else must have created a new root, try again
815
mAllocator->DestructObject(new_root_idx);
816
return false;
817
}
818
819
void QuadTree::AddBodiesPrepare(const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber, AddState &outState)
820
{
821
// Assert sane input
822
JPH_ASSERT(ioBodyIDs != nullptr);
823
JPH_ASSERT(inNumber > 0);
824
825
#ifdef JPH_ENABLE_ASSERTS
826
// Below we just cast the body ID's to node ID's, check here that that is valid
827
for (const BodyID *b = ioBodyIDs, *b_end = ioBodyIDs + inNumber; b < b_end; ++b)
828
NodeID::sFromBodyID(*b);
829
#endif
830
831
// Build subtree for the new bodies, note that we mark all nodes as 'not changed'
832
// so they will stay together as a batch and will make the tree rebuild cheaper
833
outState.mLeafID = BuildTree(inBodies, ioTracking, (NodeID *)ioBodyIDs, inNumber, 0, outState.mLeafBounds);
834
835
#ifdef JPH_DEBUG
836
if (outState.mLeafID.IsNode())
837
ValidateTree(inBodies, ioTracking, outState.mLeafID.GetNodeIndex(), inNumber);
838
#endif
839
}
840
841
void QuadTree::AddBodiesFinalize(TrackingVector &ioTracking, int inNumberBodies, const AddState &inState)
842
{
843
// Assert sane input
844
JPH_ASSERT(inNumberBodies > 0);
845
846
// Mark tree dirty
847
mIsDirty = true;
848
849
// Get the current root node
850
RootNode &root_node = GetCurrentRoot();
851
852
for (;;)
853
{
854
// Check if we can insert the body in the root
855
if (TryInsertLeaf(ioTracking, root_node.mIndex, inState.mLeafID, inState.mLeafBounds, inNumberBodies))
856
return;
857
858
// Check if we can create a new root
859
if (TryCreateNewRoot(ioTracking, root_node.mIndex, inState.mLeafID, inState.mLeafBounds, inNumberBodies))
860
return;
861
}
862
}
863
864
void QuadTree::AddBodiesAbort(TrackingVector &ioTracking, const AddState &inState)
865
{
866
// Collect all bodies
867
Allocator::Batch free_batch;
868
NodeID node_stack[cStackSize];
869
node_stack[0] = inState.mLeafID;
870
JPH_ASSERT(node_stack[0].IsValid());
871
int top = 0;
872
do
873
{
874
// Check if node is a body
875
NodeID child_node_id = node_stack[top];
876
if (child_node_id.IsBody())
877
{
878
// Reset location of body
879
sInvalidateBodyLocation(ioTracking, child_node_id.GetBodyID());
880
}
881
else
882
{
883
// Process normal node
884
uint32 node_idx = child_node_id.GetNodeIndex();
885
const Node &node = mAllocator->Get(node_idx);
886
for (NodeID sub_child_node_id : node.mChildNodeID)
887
if (sub_child_node_id.IsValid())
888
{
889
JPH_ASSERT(top < cStackSize);
890
node_stack[top] = sub_child_node_id;
891
top++;
892
}
893
894
// Mark it to be freed
895
mAllocator->AddObjectToBatch(free_batch, node_idx);
896
}
897
--top;
898
}
899
while (top >= 0);
900
901
// Now free all nodes as a single batch
902
mAllocator->DestructObjectBatch(free_batch);
903
}
904
905
void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber)
906
{
907
// Assert sane input
908
JPH_ASSERT(ioBodyIDs != nullptr);
909
JPH_ASSERT(inNumber > 0);
910
911
// Mark tree dirty
912
mIsDirty = true;
913
914
for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)
915
{
916
// Check if BodyID is correct
917
JPH_ASSERT(inBodies[cur->GetIndex()]->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");
918
919
// Get location of body
920
uint32 node_idx, child_idx;
921
GetBodyLocation(ioTracking, *cur, node_idx, child_idx);
922
923
// First we reset our internal bookkeeping
924
sInvalidateBodyLocation(ioTracking, *cur);
925
926
// Then we make the bounding box invalid, no queries can find this node anymore
927
Node &node = mAllocator->Get(node_idx);
928
node.InvalidateChildBounds(child_idx);
929
930
// Finally we reset the child id, this makes the node available for adds again
931
node.mChildNodeID[child_idx] = NodeID::sInvalid();
932
933
// We don't need to bubble up our bounding box changes to our parents since we never make volumes smaller, only bigger
934
// But we do need to mark the nodes as changed so that the tree can be rebuilt
935
MarkNodeAndParentsChanged(node_idx);
936
}
937
938
mNumBodies -= inNumber;
939
}
940
941
void QuadTree::NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber)
942
{
943
// Assert sane input
944
JPH_ASSERT(ioBodyIDs != nullptr);
945
JPH_ASSERT(inNumber > 0);
946
947
for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)
948
{
949
// Check if BodyID is correct
950
const Body *body = inBodies[cur->GetIndex()];
951
JPH_ASSERT(body->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");
952
953
// Get the new bounding box
954
const AABox &new_bounds = body->GetWorldSpaceBounds();
955
956
// Get location of body
957
uint32 node_idx, child_idx;
958
GetBodyLocation(inTracking, *cur, node_idx, child_idx);
959
960
// Widen bounds for node
961
Node &node = mAllocator->Get(node_idx);
962
if (node.EncapsulateChildBounds(child_idx, new_bounds))
963
{
964
// Mark tree dirty
965
mIsDirty = true;
966
967
// If bounds changed, widen the bounds for our parents too
968
WidenAndMarkNodeAndParentsChanged(node_idx, new_bounds);
969
}
970
}
971
}
972
973
template <class Visitor>
974
JPH_INLINE void QuadTree::WalkTree(const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking, Visitor &ioVisitor JPH_IF_TRACK_BROADPHASE_STATS(, LayerToStats &ioStats)) const
975
{
976
// Get the root
977
const RootNode &root_node = GetCurrentRoot();
978
979
#ifdef JPH_TRACK_BROADPHASE_STATS
980
// Start tracking stats
981
int bodies_visited = 0;
982
int hits_collected = 0;
983
int nodes_visited = 0;
984
uint64 collector_ticks = 0;
985
986
uint64 start = GetProcessorTickCount();
987
#endif // JPH_TRACK_BROADPHASE_STATS
988
989
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack_array;
990
node_stack_array.resize(cStackSize);
991
NodeID *node_stack = node_stack_array.data();
992
node_stack[0] = root_node.GetNodeID();
993
int top = 0;
994
do
995
{
996
// Check if node is a body
997
NodeID child_node_id = node_stack[top];
998
if (child_node_id.IsBody())
999
{
1000
// Track amount of bodies visited
1001
JPH_IF_TRACK_BROADPHASE_STATS(++bodies_visited;)
1002
1003
BodyID body_id = child_node_id.GetBodyID();
1004
ObjectLayer object_layer = inTracking[body_id.GetIndex()].mObjectLayer; // We're not taking a lock on the body, so it may be in the process of being removed so check if the object layer is invalid
1005
if (object_layer != cObjectLayerInvalid && inObjectLayerFilter.ShouldCollide(object_layer))
1006
{
1007
JPH_PROFILE("VisitBody");
1008
1009
// Track amount of hits
1010
JPH_IF_TRACK_BROADPHASE_STATS(++hits_collected;)
1011
1012
// Start track time the collector takes
1013
JPH_IF_TRACK_BROADPHASE_STATS(uint64 collector_start = GetProcessorTickCount();)
1014
1015
// We found a body we collide with, call our visitor
1016
ioVisitor.VisitBody(body_id, top);
1017
1018
// End track time the collector takes
1019
JPH_IF_TRACK_BROADPHASE_STATS(collector_ticks += GetProcessorTickCount() - collector_start;)
1020
1021
// Check if we're done
1022
if (ioVisitor.ShouldAbort())
1023
break;
1024
}
1025
}
1026
else if (child_node_id.IsValid())
1027
{
1028
JPH_IF_TRACK_BROADPHASE_STATS(++nodes_visited;)
1029
1030
// Ensure there is space on the stack (falls back to heap if there isn't)
1031
if (top + 4 >= (int)node_stack_array.size())
1032
{
1033
sQuadTreePerformanceWarning();
1034
node_stack_array.resize(node_stack_array.size() << 1);
1035
node_stack = node_stack_array.data();
1036
ioVisitor.OnStackResized(node_stack_array.size());
1037
}
1038
1039
// Process normal node
1040
const Node &node = mAllocator->Get(child_node_id.GetNodeIndex());
1041
JPH_ASSERT(IsAligned(&node, JPH_CACHE_LINE_SIZE));
1042
1043
// Load bounds of 4 children
1044
Vec4 bounds_minx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinX);
1045
Vec4 bounds_miny = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinY);
1046
Vec4 bounds_minz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinZ);
1047
Vec4 bounds_maxx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxX);
1048
Vec4 bounds_maxy = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxY);
1049
Vec4 bounds_maxz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxZ);
1050
1051
// Load ids for 4 children
1052
UVec4 child_ids = UVec4::sLoadInt4Aligned((const uint32 *)&node.mChildNodeID[0]);
1053
1054
// Check which sub nodes to visit
1055
int num_results = ioVisitor.VisitNodes(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, child_ids, top);
1056
child_ids.StoreInt4((uint32 *)&node_stack[top]);
1057
top += num_results;
1058
}
1059
1060
// Fetch next node until we find one that the visitor wants to see
1061
do
1062
--top;
1063
while (top >= 0 && !ioVisitor.ShouldVisitNode(top));
1064
}
1065
while (top >= 0);
1066
1067
#ifdef JPH_TRACK_BROADPHASE_STATS
1068
// Calculate total time the broadphase walk took
1069
uint64 total_ticks = GetProcessorTickCount() - start;
1070
1071
// Update stats under lock protection (slow!)
1072
{
1073
unique_lock lock(mStatsMutex);
1074
Stat &s = ioStats[inObjectLayerFilter.GetDescription()];
1075
s.mNumQueries++;
1076
s.mNodesVisited += nodes_visited;
1077
s.mBodiesVisited += bodies_visited;
1078
s.mHitsReported += hits_collected;
1079
s.mTotalTicks += total_ticks;
1080
s.mCollectorTicks += collector_ticks;
1081
}
1082
#endif // JPH_TRACK_BROADPHASE_STATS
1083
}
1084
1085
void QuadTree::CastRay(const RayCast &inRay, RayCastBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const
1086
{
1087
class Visitor
1088
{
1089
public:
1090
/// Constructor
1091
JPH_INLINE Visitor(const RayCast &inRay, RayCastBodyCollector &ioCollector) :
1092
mOrigin(inRay.mOrigin),
1093
mInvDirection(inRay.mDirection),
1094
mCollector(ioCollector)
1095
{
1096
mFractionStack.resize(cStackSize);
1097
mFractionStack[0] = -1;
1098
}
1099
1100
/// Returns true if further processing of the tree should be aborted
1101
JPH_INLINE bool ShouldAbort() const
1102
{
1103
return mCollector.ShouldEarlyOut();
1104
}
1105
1106
/// Returns true if this node / body should be visited, false if no hit can be generated
1107
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
1108
{
1109
return mFractionStack[inStackTop] < mCollector.GetEarlyOutFraction();
1110
}
1111
1112
/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.
1113
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop)
1114
{
1115
// Test the ray against 4 bounding boxes
1116
Vec4 fraction = RayAABox4(mOrigin, mInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
1117
1118
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
1119
return SortReverseAndStore(fraction, mCollector.GetEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]);
1120
}
1121
1122
/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore
1123
JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)
1124
{
1125
// Store potential hit with body
1126
BroadPhaseCastResult result { inBodyID, mFractionStack[inStackTop] };
1127
mCollector.AddHit(result);
1128
}
1129
1130
/// Called when the stack is resized, this allows us to resize the fraction stack to match the new stack size
1131
JPH_INLINE void OnStackResized(size_t inNewStackSize)
1132
{
1133
mFractionStack.resize(inNewStackSize);
1134
}
1135
1136
private:
1137
Vec3 mOrigin;
1138
RayInvDirection mInvDirection;
1139
RayCastBodyCollector & mCollector;
1140
Array<float, STLLocalAllocator<float, cStackSize>> mFractionStack;
1141
};
1142
1143
Visitor visitor(inRay, ioCollector);
1144
WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCastRayStats));
1145
}
1146
1147
void QuadTree::CollideAABox(const AABox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const
1148
{
1149
class Visitor
1150
{
1151
public:
1152
/// Constructor
1153
JPH_INLINE Visitor(const AABox &inBox, CollideShapeBodyCollector &ioCollector) :
1154
mBox(inBox),
1155
mCollector(ioCollector)
1156
{
1157
}
1158
1159
/// Returns true if further processing of the tree should be aborted
1160
JPH_INLINE bool ShouldAbort() const
1161
{
1162
return mCollector.ShouldEarlyOut();
1163
}
1164
1165
/// Returns true if this node / body should be visited, false if no hit can be generated
1166
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
1167
{
1168
return true;
1169
}
1170
1171
/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.
1172
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const
1173
{
1174
// Test the box vs 4 boxes
1175
UVec4 hitting = AABox4VsBox(mBox, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
1176
return CountAndSortTrues(hitting, ioChildNodeIDs);
1177
}
1178
1179
/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore
1180
JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)
1181
{
1182
// Store potential hit with body
1183
mCollector.AddHit(inBodyID);
1184
}
1185
1186
/// Called when the stack is resized
1187
JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const
1188
{
1189
// Nothing to do
1190
}
1191
1192
private:
1193
const AABox & mBox;
1194
CollideShapeBodyCollector & mCollector;
1195
};
1196
1197
Visitor visitor(inBox, ioCollector);
1198
WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideAABoxStats));
1199
}
1200
1201
void QuadTree::CollideSphere(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const
1202
{
1203
class Visitor
1204
{
1205
public:
1206
/// Constructor
1207
JPH_INLINE Visitor(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector) :
1208
mCenterX(inCenter.SplatX()),
1209
mCenterY(inCenter.SplatY()),
1210
mCenterZ(inCenter.SplatZ()),
1211
mRadiusSq(Vec4::sReplicate(Square(inRadius))),
1212
mCollector(ioCollector)
1213
{
1214
}
1215
1216
/// Returns true if further processing of the tree should be aborted
1217
JPH_INLINE bool ShouldAbort() const
1218
{
1219
return mCollector.ShouldEarlyOut();
1220
}
1221
1222
/// Returns true if this node / body should be visited, false if no hit can be generated
1223
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
1224
{
1225
return true;
1226
}
1227
1228
/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.
1229
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const
1230
{
1231
// Test 4 boxes vs sphere
1232
UVec4 hitting = AABox4VsSphere(mCenterX, mCenterY, mCenterZ, mRadiusSq, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
1233
return CountAndSortTrues(hitting, ioChildNodeIDs);
1234
}
1235
1236
/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore
1237
JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)
1238
{
1239
// Store potential hit with body
1240
mCollector.AddHit(inBodyID);
1241
}
1242
1243
/// Called when the stack is resized
1244
JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const
1245
{
1246
// Nothing to do
1247
}
1248
1249
private:
1250
Vec4 mCenterX;
1251
Vec4 mCenterY;
1252
Vec4 mCenterZ;
1253
Vec4 mRadiusSq;
1254
CollideShapeBodyCollector & mCollector;
1255
};
1256
1257
Visitor visitor(inCenter, inRadius, ioCollector);
1258
WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideSphereStats));
1259
}
1260
1261
void QuadTree::CollidePoint(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const
1262
{
1263
class Visitor
1264
{
1265
public:
1266
/// Constructor
1267
JPH_INLINE Visitor(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector) :
1268
mPoint(inPoint),
1269
mCollector(ioCollector)
1270
{
1271
}
1272
1273
/// Returns true if further processing of the tree should be aborted
1274
JPH_INLINE bool ShouldAbort() const
1275
{
1276
return mCollector.ShouldEarlyOut();
1277
}
1278
1279
/// Returns true if this node / body should be visited, false if no hit can be generated
1280
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
1281
{
1282
return true;
1283
}
1284
1285
/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.
1286
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const
1287
{
1288
// Test if point overlaps with box
1289
UVec4 hitting = AABox4VsPoint(mPoint, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
1290
return CountAndSortTrues(hitting, ioChildNodeIDs);
1291
}
1292
1293
/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore
1294
JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)
1295
{
1296
// Store potential hit with body
1297
mCollector.AddHit(inBodyID);
1298
}
1299
1300
/// Called when the stack is resized
1301
JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const
1302
{
1303
// Nothing to do
1304
}
1305
1306
private:
1307
Vec3 mPoint;
1308
CollideShapeBodyCollector & mCollector;
1309
};
1310
1311
Visitor visitor(inPoint, ioCollector);
1312
WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollidePointStats));
1313
}
1314
1315
void QuadTree::CollideOrientedBox(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const
1316
{
1317
class Visitor
1318
{
1319
public:
1320
/// Constructor
1321
JPH_INLINE Visitor(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector) :
1322
mBox(inBox),
1323
mCollector(ioCollector)
1324
{
1325
}
1326
1327
/// Returns true if further processing of the tree should be aborted
1328
JPH_INLINE bool ShouldAbort() const
1329
{
1330
return mCollector.ShouldEarlyOut();
1331
}
1332
1333
/// Returns true if this node / body should be visited, false if no hit can be generated
1334
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
1335
{
1336
return true;
1337
}
1338
1339
/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.
1340
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const
1341
{
1342
// Test if point overlaps with box
1343
UVec4 hitting = AABox4VsBox(mBox, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
1344
return CountAndSortTrues(hitting, ioChildNodeIDs);
1345
}
1346
1347
/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore
1348
JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)
1349
{
1350
// Store potential hit with body
1351
mCollector.AddHit(inBodyID);
1352
}
1353
1354
/// Called when the stack is resized
1355
JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const
1356
{
1357
// Nothing to do
1358
}
1359
1360
private:
1361
OrientedBox mBox;
1362
CollideShapeBodyCollector & mCollector;
1363
};
1364
1365
Visitor visitor(inBox, ioCollector);
1366
WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideOrientedBoxStats));
1367
}
1368
1369
void QuadTree::CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const
1370
{
1371
class Visitor
1372
{
1373
public:
1374
/// Constructor
1375
JPH_INLINE Visitor(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector) :
1376
mOrigin(inBox.mBox.GetCenter()),
1377
mExtent(inBox.mBox.GetExtent()),
1378
mInvDirection(inBox.mDirection),
1379
mCollector(ioCollector)
1380
{
1381
mFractionStack.resize(cStackSize);
1382
mFractionStack[0] = -1;
1383
}
1384
1385
/// Returns true if further processing of the tree should be aborted
1386
JPH_INLINE bool ShouldAbort() const
1387
{
1388
return mCollector.ShouldEarlyOut();
1389
}
1390
1391
/// Returns true if this node / body should be visited, false if no hit can be generated
1392
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
1393
{
1394
return mFractionStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();
1395
}
1396
1397
/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.
1398
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop)
1399
{
1400
// Enlarge them by the casted aabox extents
1401
Vec4 bounds_min_x = inBoundsMinX, bounds_min_y = inBoundsMinY, bounds_min_z = inBoundsMinZ, bounds_max_x = inBoundsMaxX, bounds_max_y = inBoundsMaxY, bounds_max_z = inBoundsMaxZ;
1402
AABox4EnlargeWithExtent(mExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
1403
1404
// Test 4 children
1405
Vec4 fraction = RayAABox4(mOrigin, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
1406
1407
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
1408
return SortReverseAndStore(fraction, mCollector.GetPositiveEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]);
1409
}
1410
1411
/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore
1412
JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)
1413
{
1414
// Store potential hit with body
1415
BroadPhaseCastResult result { inBodyID, mFractionStack[inStackTop] };
1416
mCollector.AddHit(result);
1417
}
1418
1419
/// Called when the stack is resized, this allows us to resize the fraction stack to match the new stack size
1420
JPH_INLINE void OnStackResized(size_t inNewStackSize)
1421
{
1422
mFractionStack.resize(inNewStackSize);
1423
}
1424
1425
private:
1426
Vec3 mOrigin;
1427
Vec3 mExtent;
1428
RayInvDirection mInvDirection;
1429
CastShapeBodyCollector & mCollector;
1430
Array<float, STLLocalAllocator<float, cStackSize>> mFractionStack;
1431
};
1432
1433
Visitor visitor(inBox, ioCollector);
1434
WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCastAABoxStats));
1435
}
1436
1437
void QuadTree::FindCollidingPairs(const BodyVector &inBodies, const BodyID *inActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, BodyPairCollector &ioPairCollector, const ObjectLayerPairFilter &inObjectLayerPairFilter) const
1438
{
1439
// Note that we don't lock the tree at this point. We know that the tree is not going to be swapped or deleted while finding collision pairs due to the way the jobs are scheduled in the PhysicsSystem::Update.
1440
// We double check this at the end of the function.
1441
const RootNode &root_node = GetCurrentRoot();
1442
JPH_ASSERT(root_node.mIndex != cInvalidNodeIndex);
1443
1444
// Assert sane input
1445
JPH_ASSERT(inActiveBodies != nullptr);
1446
JPH_ASSERT(inNumActiveBodies > 0);
1447
1448
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack_array;
1449
node_stack_array.resize(cStackSize);
1450
NodeID *node_stack = node_stack_array.data();
1451
1452
// Loop over all active bodies
1453
for (int b1 = 0; b1 < inNumActiveBodies; ++b1)
1454
{
1455
BodyID b1_id = inActiveBodies[b1];
1456
const Body &body1 = *inBodies[b1_id.GetIndex()];
1457
JPH_ASSERT(!body1.IsStatic());
1458
1459
// Expand the bounding box by the speculative contact distance
1460
AABox bounds1 = body1.GetWorldSpaceBounds();
1461
bounds1.ExpandBy(Vec3::sReplicate(inSpeculativeContactDistance));
1462
1463
// Test each body with the tree
1464
node_stack[0] = root_node.GetNodeID();
1465
int top = 0;
1466
do
1467
{
1468
// Check if node is a body
1469
NodeID child_node_id = node_stack[top];
1470
if (child_node_id.IsBody())
1471
{
1472
// Don't collide with self
1473
BodyID b2_id = child_node_id.GetBodyID();
1474
if (b1_id != b2_id)
1475
{
1476
// Collision between dynamic pairs need to be picked up only once
1477
const Body &body2 = *inBodies[b2_id.GetIndex()];
1478
if (inObjectLayerPairFilter.ShouldCollide(body1.GetObjectLayer(), body2.GetObjectLayer())
1479
&& Body::sFindCollidingPairsCanCollide(body1, body2)
1480
&& bounds1.Overlaps(body2.GetWorldSpaceBounds())) // In the broadphase we widen the bounding box when a body moves, do a final check to see if the bounding boxes actually overlap
1481
{
1482
// Store potential hit between bodies
1483
ioPairCollector.AddHit({ b1_id, b2_id });
1484
}
1485
}
1486
}
1487
else if (child_node_id.IsValid())
1488
{
1489
// Process normal node
1490
const Node &node = mAllocator->Get(child_node_id.GetNodeIndex());
1491
JPH_ASSERT(IsAligned(&node, JPH_CACHE_LINE_SIZE));
1492
1493
// Get bounds of 4 children
1494
Vec4 bounds_minx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinX);
1495
Vec4 bounds_miny = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinY);
1496
Vec4 bounds_minz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinZ);
1497
Vec4 bounds_maxx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxX);
1498
Vec4 bounds_maxy = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxY);
1499
Vec4 bounds_maxz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxZ);
1500
1501
// Test overlap
1502
UVec4 overlap = AABox4VsBox(bounds1, bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz);
1503
int num_results = overlap.CountTrues();
1504
if (num_results > 0)
1505
{
1506
// Load ids for 4 children
1507
UVec4 child_ids = UVec4::sLoadInt4Aligned((const uint32 *)&node.mChildNodeID[0]);
1508
1509
// Sort so that overlaps are first
1510
child_ids = UVec4::sSort4True(overlap, child_ids);
1511
1512
// Ensure there is space on the stack (falls back to heap if there isn't)
1513
if (top + 4 >= (int)node_stack_array.size())
1514
{
1515
sQuadTreePerformanceWarning();
1516
node_stack_array.resize(node_stack_array.size() << 1);
1517
node_stack = node_stack_array.data();
1518
}
1519
1520
// Push them onto the stack
1521
child_ids.StoreInt4((uint32 *)&node_stack[top]);
1522
top += num_results;
1523
}
1524
}
1525
--top;
1526
}
1527
while (top >= 0);
1528
}
1529
1530
// Test that the root node was not swapped while finding collision pairs.
1531
// This would mean that UpdateFinalize/DiscardOldTree ran during collision detection which should not be possible due to the way the jobs are scheduled.
1532
JPH_ASSERT(root_node.mIndex != cInvalidNodeIndex);
1533
JPH_ASSERT(&root_node == &GetCurrentRoot());
1534
}
1535
1536
#ifdef JPH_DEBUG
1537
1538
void QuadTree::ValidateTree(const BodyVector &inBodies, const TrackingVector &inTracking, uint32 inNodeIndex, uint32 inNumExpectedBodies) const
1539
{
1540
JPH_PROFILE_FUNCTION();
1541
1542
// Root should be valid
1543
JPH_ASSERT(inNodeIndex != cInvalidNodeIndex);
1544
1545
// To avoid call overhead, create a stack in place
1546
JPH_SUPPRESS_WARNING_PUSH
1547
JPH_CLANG_SUPPRESS_WARNING("-Wunused-member-function") // The default constructor of StackEntry is unused when using Jolt's Array class but not when using std::vector
1548
struct StackEntry
1549
{
1550
StackEntry() = default;
1551
inline StackEntry(uint32 inNodeIndex, uint32 inParentNodeIndex) : mNodeIndex(inNodeIndex), mParentNodeIndex(inParentNodeIndex) { }
1552
1553
uint32 mNodeIndex;
1554
uint32 mParentNodeIndex;
1555
};
1556
JPH_SUPPRESS_WARNING_POP
1557
Array<StackEntry, STLLocalAllocator<StackEntry, cStackSize>> stack;
1558
stack.reserve(cStackSize);
1559
stack.emplace_back(inNodeIndex, cInvalidNodeIndex);
1560
1561
uint32 num_bodies = 0;
1562
1563
do
1564
{
1565
// Copy entry from the stack
1566
StackEntry cur_stack = stack.back();
1567
stack.pop_back();
1568
1569
// Validate parent
1570
const Node &node = mAllocator->Get(cur_stack.mNodeIndex);
1571
JPH_ASSERT(node.mParentNodeIndex == cur_stack.mParentNodeIndex);
1572
1573
// Validate that when a parent is not-changed that all of its children are also
1574
JPH_ASSERT(cur_stack.mParentNodeIndex == cInvalidNodeIndex || mAllocator->Get(cur_stack.mParentNodeIndex).mIsChanged || !node.mIsChanged);
1575
1576
// Loop children
1577
for (uint32 i = 0; i < 4; ++i)
1578
{
1579
NodeID child_node_id = node.mChildNodeID[i];
1580
if (child_node_id.IsValid())
1581
{
1582
if (child_node_id.IsNode())
1583
{
1584
// Child is a node, recurse
1585
uint32 child_idx = child_node_id.GetNodeIndex();
1586
stack.emplace_back(child_idx, cur_stack.mNodeIndex);
1587
1588
// Validate that the bounding box is bigger or equal to the bounds in the tree
1589
// Bounding box could also be invalid if all children of our child were removed
1590
AABox child_bounds;
1591
node.GetChildBounds(i, child_bounds);
1592
AABox real_child_bounds;
1593
mAllocator->Get(child_idx).GetNodeBounds(real_child_bounds);
1594
JPH_ASSERT(child_bounds.Contains(real_child_bounds) || !real_child_bounds.IsValid());
1595
}
1596
else
1597
{
1598
// Increment number of bodies found
1599
++num_bodies;
1600
1601
// Check if tracker matches position of body
1602
uint32 node_idx, child_idx;
1603
GetBodyLocation(inTracking, child_node_id.GetBodyID(), node_idx, child_idx);
1604
JPH_ASSERT(node_idx == cur_stack.mNodeIndex);
1605
JPH_ASSERT(child_idx == i);
1606
1607
// Validate that the body cached bounds still match the actual bounds
1608
const Body *body = inBodies[child_node_id.GetBodyID().GetIndex()];
1609
body->ValidateCachedBounds();
1610
1611
// Validate that the node bounds are bigger or equal to the body bounds
1612
AABox body_bounds;
1613
node.GetChildBounds(i, body_bounds);
1614
JPH_ASSERT(body_bounds.Contains(body->GetWorldSpaceBounds()));
1615
}
1616
}
1617
}
1618
}
1619
while (!stack.empty());
1620
1621
// Check that the amount of bodies in the tree matches our counter
1622
JPH_ASSERT(num_bodies == inNumExpectedBodies);
1623
}
1624
1625
#endif
1626
1627
#ifdef JPH_DUMP_BROADPHASE_TREE
1628
1629
void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) const
1630
{
1631
// Open DOT file
1632
std::ofstream f;
1633
f.open(StringFormat("%s.dot", inFileNamePrefix).c_str(), std::ofstream::out | std::ofstream::trunc);
1634
if (!f.is_open())
1635
return;
1636
1637
// Write header
1638
f << "digraph {\n";
1639
1640
// Iterate the entire tree
1641
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;
1642
node_stack.reserve(cStackSize);
1643
node_stack.push_back(inRoot);
1644
JPH_ASSERT(inRoot.IsValid());
1645
do
1646
{
1647
// Check if node is a body
1648
NodeID node_id = node_stack.back();
1649
node_stack.pop_back();
1650
if (node_id.IsBody())
1651
{
1652
// Output body
1653
String body_id = ConvertToString(node_id.GetBodyID().GetIndex());
1654
f << "body" << body_id << "[label = \"Body " << body_id << "\"]\n";
1655
}
1656
else
1657
{
1658
// Process normal node
1659
uint32 node_idx = node_id.GetNodeIndex();
1660
const Node &node = mAllocator->Get(node_idx);
1661
1662
// Get bounding box
1663
AABox bounds;
1664
node.GetNodeBounds(bounds);
1665
1666
// Output node
1667
String node_str = ConvertToString(node_idx);
1668
f << "node" << node_str << "[label = \"Node " << node_str << "\nVolume: " << ConvertToString(bounds.GetVolume()) << "\" color=" << (node.mIsChanged? "red" : "black") << "]\n";
1669
1670
// Recurse and get all children
1671
for (NodeID child_node_id : node.mChildNodeID)
1672
if (child_node_id.IsValid())
1673
{
1674
node_stack.push_back(child_node_id);
1675
1676
// Output link
1677
f << "node" << node_str << " -> ";
1678
if (child_node_id.IsBody())
1679
f << "body" << ConvertToString(child_node_id.GetBodyID().GetIndex());
1680
else
1681
f << "node" << ConvertToString(child_node_id.GetNodeIndex());
1682
f << "\n";
1683
}
1684
}
1685
}
1686
while (!node_stack.empty());
1687
1688
// Finish DOT file
1689
f << "}\n";
1690
f.close();
1691
1692
// Convert to svg file
1693
String cmd = StringFormat("dot %s.dot -Tsvg -o %s.svg", inFileNamePrefix, inFileNamePrefix);
1694
system(cmd.c_str());
1695
}
1696
1697
#endif // JPH_DUMP_BROADPHASE_TREE
1698
1699
#ifdef JPH_TRACK_BROADPHASE_STATS
1700
1701
uint64 QuadTree::GetTicks100Pct(const LayerToStats &inLayer) const
1702
{
1703
uint64 total_ticks = 0;
1704
for (const LayerToStats::value_type &kv : inLayer)
1705
total_ticks += kv.second.mTotalTicks;
1706
return total_ticks;
1707
}
1708
1709
void QuadTree::ReportStats(const char *inName, const LayerToStats &inLayer, uint64 inTicks100Pct) const
1710
{
1711
for (const LayerToStats::value_type &kv : inLayer)
1712
{
1713
double total_pct = 100.0 * double(kv.second.mTotalTicks) / double(inTicks100Pct);
1714
double total_pct_excl_collector = 100.0 * double(kv.second.mTotalTicks - kv.second.mCollectorTicks) / double(inTicks100Pct);
1715
double hits_reported_vs_bodies_visited = kv.second.mBodiesVisited > 0? 100.0 * double(kv.second.mHitsReported) / double(kv.second.mBodiesVisited) : 100.0;
1716
double hits_reported_vs_nodes_visited = kv.second.mNodesVisited > 0? double(kv.second.mHitsReported) / double(kv.second.mNodesVisited) : -1.0;
1717
1718
std::stringstream str;
1719
str << inName << ", " << kv.first << ", " << mName << ", " << kv.second.mNumQueries << ", " << total_pct << ", " << total_pct_excl_collector << ", " << kv.second.mNodesVisited << ", " << kv.second.mBodiesVisited << ", " << kv.second.mHitsReported << ", " << hits_reported_vs_bodies_visited << ", " << hits_reported_vs_nodes_visited;
1720
Trace(str.str().c_str());
1721
}
1722
}
1723
1724
uint64 QuadTree::GetTicks100Pct() const
1725
{
1726
uint64 total_ticks = 0;
1727
total_ticks += GetTicks100Pct(mCastRayStats);
1728
total_ticks += GetTicks100Pct(mCollideAABoxStats);
1729
total_ticks += GetTicks100Pct(mCollideSphereStats);
1730
total_ticks += GetTicks100Pct(mCollidePointStats);
1731
total_ticks += GetTicks100Pct(mCollideOrientedBoxStats);
1732
total_ticks += GetTicks100Pct(mCastAABoxStats);
1733
return total_ticks;
1734
}
1735
1736
void QuadTree::ReportStats(uint64 inTicks100Pct) const
1737
{
1738
unique_lock lock(mStatsMutex);
1739
ReportStats("RayCast", mCastRayStats, inTicks100Pct);
1740
ReportStats("CollideAABox", mCollideAABoxStats, inTicks100Pct);
1741
ReportStats("CollideSphere", mCollideSphereStats, inTicks100Pct);
1742
ReportStats("CollidePoint", mCollidePointStats, inTicks100Pct);
1743
ReportStats("CollideOrientedBox", mCollideOrientedBoxStats, inTicks100Pct);
1744
ReportStats("CastAABox", mCastAABoxStats, inTicks100Pct);
1745
}
1746
1747
#endif // JPH_TRACK_BROADPHASE_STATS
1748
1749
uint QuadTree::GetMaxTreeDepth(const NodeID &inNodeID) const
1750
{
1751
// Reached a leaf?
1752
if (!inNodeID.IsValid() || inNodeID.IsBody())
1753
return 0;
1754
1755
// Recurse to children
1756
uint max_depth = 0;
1757
const Node &node = mAllocator->Get(inNodeID.GetNodeIndex());
1758
for (NodeID child_node_id : node.mChildNodeID)
1759
max_depth = max(max_depth, GetMaxTreeDepth(child_node_id));
1760
return max_depth + 1;
1761
}
1762
1763
JPH_NAMESPACE_END
1764
1765