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