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.h
9918 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#pragma once
6
7
#include <Jolt/Core/FixedSizeFreeList.h>
8
#include <Jolt/Core/Atomics.h>
9
#include <Jolt/Core/NonCopyable.h>
10
#include <Jolt/Physics/Body/BodyManager.h>
11
#include <Jolt/Physics/Collision/BroadPhase/BroadPhase.h>
12
13
//#define JPH_DUMP_BROADPHASE_TREE
14
15
JPH_NAMESPACE_BEGIN
16
17
/// Internal tree structure in broadphase, is essentially a quad AABB tree.
18
/// Tree is lockless (except for UpdatePrepare/Finalize() function), modifying objects in the tree will widen the aabbs of parent nodes to make the node fit.
19
/// During the UpdatePrepare/Finalize() call the tree is rebuilt to achieve a tight fit again.
20
class JPH_EXPORT QuadTree : public NonCopyable
21
{
22
public:
23
JPH_OVERRIDE_NEW_DELETE
24
25
private:
26
// Forward declare
27
class AtomicNodeID;
28
29
/// Class that points to either a body or a node in the tree
30
class NodeID
31
{
32
public:
33
JPH_OVERRIDE_NEW_DELETE
34
35
/// Default constructor does not initialize
36
inline NodeID() = default;
37
38
/// Construct a node ID
39
static inline NodeID sInvalid() { return NodeID(cInvalidNodeIndex); }
40
static inline NodeID sFromBodyID(BodyID inID) { NodeID node_id(inID.GetIndexAndSequenceNumber()); JPH_ASSERT(node_id.IsBody()); return node_id; }
41
static inline NodeID sFromNodeIndex(uint32 inIdx) { JPH_ASSERT((inIdx & cIsNode) == 0); return NodeID(inIdx | cIsNode); }
42
43
/// Check what type of ID it is
44
inline bool IsValid() const { return mID != cInvalidNodeIndex; }
45
inline bool IsBody() const { return (mID & cIsNode) == 0; }
46
inline bool IsNode() const { return (mID & cIsNode) != 0; }
47
48
/// Get body or node index
49
inline BodyID GetBodyID() const { JPH_ASSERT(IsBody()); return BodyID(mID); }
50
inline uint32 GetNodeIndex() const { JPH_ASSERT(IsNode()); return mID & ~cIsNode; }
51
52
/// Comparison
53
inline bool operator == (const BodyID &inRHS) const { return mID == inRHS.GetIndexAndSequenceNumber(); }
54
inline bool operator == (const NodeID &inRHS) const { return mID == inRHS.mID; }
55
56
private:
57
friend class AtomicNodeID;
58
59
inline explicit NodeID(uint32 inID) : mID(inID) { }
60
61
static const uint32 cIsNode = BodyID::cBroadPhaseBit; ///< If this bit is set it means that the ID refers to a node, otherwise it refers to a body
62
63
uint32 mID;
64
};
65
66
static_assert(sizeof(NodeID) == sizeof(BodyID), "Body id's should have the same size as NodeIDs");
67
68
/// A NodeID that uses atomics to store the value
69
class AtomicNodeID
70
{
71
public:
72
/// Constructor
73
AtomicNodeID() = default;
74
explicit AtomicNodeID(const NodeID &inRHS) : mID(inRHS.mID) { }
75
76
/// Assignment
77
inline void operator = (const NodeID &inRHS) { mID = inRHS.mID; }
78
79
/// Getting the value
80
inline operator NodeID () const { return NodeID(mID); }
81
82
/// Check if the ID is valid
83
inline bool IsValid() const { return mID != cInvalidNodeIndex; }
84
85
/// Comparison
86
inline bool operator == (const BodyID &inRHS) const { return mID == inRHS.GetIndexAndSequenceNumber(); }
87
inline bool operator == (const NodeID &inRHS) const { return mID == inRHS.mID; }
88
89
/// Atomically compare and swap value. Expects inOld value, replaces with inNew value or returns false
90
inline bool CompareExchange(NodeID inOld, NodeID inNew) { return mID.compare_exchange_strong(inOld.mID, inNew.mID); }
91
92
private:
93
atomic<uint32> mID;
94
};
95
96
/// Class that represents a node in the tree
97
class Node
98
{
99
public:
100
/// Construct node
101
explicit Node(bool inIsChanged);
102
103
/// Get bounding box encapsulating all children
104
void GetNodeBounds(AABox &outBounds) const;
105
106
/// Get bounding box in a consistent way with the functions below (check outBounds.IsValid() before using the box)
107
void GetChildBounds(int inChildIndex, AABox &outBounds) const;
108
109
/// Set the bounds in such a way that other threads will either see a fully correct bounding box or a bounding box with no volume
110
void SetChildBounds(int inChildIndex, const AABox &inBounds);
111
112
/// Invalidate bounding box in such a way that other threads will not temporarily see a very large bounding box
113
void InvalidateChildBounds(int inChildIndex);
114
115
/// Encapsulate inBounds in node bounds, returns true if there were changes
116
bool EncapsulateChildBounds(int inChildIndex, const AABox &inBounds);
117
118
/// Bounding box for child nodes or bodies (all initially set to invalid so no collision test will ever traverse to the leaf)
119
atomic<float> mBoundsMinX[4];
120
atomic<float> mBoundsMinY[4];
121
atomic<float> mBoundsMinZ[4];
122
atomic<float> mBoundsMaxX[4];
123
atomic<float> mBoundsMaxY[4];
124
atomic<float> mBoundsMaxZ[4];
125
126
/// Index of child node or body ID.
127
AtomicNodeID mChildNodeID[4];
128
129
/// Index of the parent node.
130
/// Note: This value is unreliable during the UpdatePrepare/Finalize() function as a node may be relinked to the newly built tree.
131
atomic<uint32> mParentNodeIndex = cInvalidNodeIndex;
132
133
/// If this part of the tree has changed, if not, we will treat this sub tree as a single body during the UpdatePrepare/Finalize().
134
/// If any changes are made to an object inside this sub tree then the direct path from the body to the top of the tree will become changed.
135
atomic<uint32> mIsChanged;
136
137
// Padding to align to 124 bytes
138
uint32 mPadding = 0;
139
};
140
141
// Maximum size of the stack during tree walk
142
static constexpr int cStackSize = 128;
143
144
static_assert(sizeof(atomic<float>) == 4, "Assuming that an atomic doesn't add any additional storage");
145
static_assert(sizeof(atomic<uint32>) == 4, "Assuming that an atomic doesn't add any additional storage");
146
static_assert(std::is_trivially_destructible<Node>(), "Assuming that we don't have a destructor");
147
148
public:
149
/// Class that allocates tree nodes, can be shared between multiple trees
150
using Allocator = FixedSizeFreeList<Node>;
151
152
static_assert(Allocator::ObjectStorageSize == 128, "Node should be 128 bytes");
153
154
/// Data to track location of a Body in the tree
155
struct Tracking
156
{
157
/// Constructor to satisfy the vector class
158
Tracking() = default;
159
Tracking(const Tracking &inRHS) : mBroadPhaseLayer(inRHS.mBroadPhaseLayer.load()), mObjectLayer(inRHS.mObjectLayer.load()), mBodyLocation(inRHS.mBodyLocation.load()) { }
160
161
/// Invalid body location identifier
162
static const uint32 cInvalidBodyLocation = 0xffffffff;
163
164
atomic<BroadPhaseLayer::Type> mBroadPhaseLayer = (BroadPhaseLayer::Type)cBroadPhaseLayerInvalid;
165
atomic<ObjectLayer> mObjectLayer = cObjectLayerInvalid;
166
atomic<uint32> mBodyLocation { cInvalidBodyLocation };
167
};
168
169
using TrackingVector = Array<Tracking>;
170
171
/// Destructor
172
~QuadTree();
173
174
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
175
/// Name of the tree for debugging purposes
176
void SetName(const char *inName) { mName = inName; }
177
inline const char * GetName() const { return mName; }
178
#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
179
180
/// Check if there is anything in the tree
181
inline bool HasBodies() const { return mNumBodies != 0; }
182
183
/// Check if the tree needs an UpdatePrepare/Finalize()
184
inline bool IsDirty() const { return mIsDirty; }
185
186
/// Check if this tree can get an UpdatePrepare/Finalize() or if it needs a DiscardOldTree() first
187
inline bool CanBeUpdated() const { return mFreeNodeBatch.mNumObjects == 0; }
188
189
/// Initialization
190
void Init(Allocator &inAllocator);
191
192
struct UpdateState
193
{
194
NodeID mRootNodeID; ///< This will be the new root node id
195
};
196
197
/// Will throw away the previous frame's nodes so that we can start building a new tree in the background
198
void DiscardOldTree();
199
200
/// Get the bounding box for this tree
201
AABox GetBounds() const;
202
203
/// Update the broadphase, needs to be called regularly to achieve a tight fit of the tree when bodies have been modified.
204
/// UpdatePrepare() will build the tree, UpdateFinalize() will lock the root of the tree shortly and swap the trees and afterwards clean up temporary data structures.
205
void UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild);
206
void UpdateFinalize(const BodyVector &inBodies, const TrackingVector &inTracking, const UpdateState &inUpdateState);
207
208
/// Temporary data structure to pass information between AddBodiesPrepare and AddBodiesFinalize/Abort
209
struct AddState
210
{
211
NodeID mLeafID = NodeID::sInvalid();
212
AABox mLeafBounds;
213
};
214
215
/// Prepare adding inNumber bodies at ioBodyIDs to the quad tree, returns the state in outState that should be used in AddBodiesFinalize.
216
/// This can be done on a background thread without influencing the broadphase.
217
/// ioBodyIDs may be shuffled around by this function.
218
void AddBodiesPrepare(const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber, AddState &outState);
219
220
/// Finalize adding bodies to the quadtree, supply the same number of bodies as in AddBodiesPrepare.
221
void AddBodiesFinalize(TrackingVector &ioTracking, int inNumberBodies, const AddState &inState);
222
223
/// Abort adding bodies to the quadtree, supply the same bodies and state as in AddBodiesPrepare.
224
/// This can be done on a background thread without influencing the broadphase.
225
void AddBodiesAbort(TrackingVector &ioTracking, const AddState &inState);
226
227
/// Remove inNumber bodies in ioBodyIDs from the quadtree.
228
void RemoveBodies(const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber);
229
230
/// Call whenever the aabb of a body changes.
231
void NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber);
232
233
/// Cast a ray and get the intersecting bodies in ioCollector.
234
void CastRay(const RayCast &inRay, RayCastBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
235
236
/// Get bodies intersecting with inBox in ioCollector
237
void CollideAABox(const AABox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
238
239
/// Get bodies intersecting with a sphere in ioCollector
240
void CollideSphere(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
241
242
/// Get bodies intersecting with a point and any hits to ioCollector
243
void CollidePoint(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
244
245
/// Get bodies intersecting with an oriented box and any hits to ioCollector
246
void CollideOrientedBox(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
247
248
/// Cast a box and get intersecting bodies in ioCollector
249
void CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
250
251
/// Find all colliding pairs between dynamic bodies, calls ioPairCollector for every pair found
252
void FindCollidingPairs(const BodyVector &inBodies, const BodyID *inActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, BodyPairCollector &ioPairCollector, const ObjectLayerPairFilter &inObjectLayerPairFilter) const;
253
254
#ifdef JPH_TRACK_BROADPHASE_STATS
255
/// Sum up all the ticks spent in the various layers
256
uint64 GetTicks100Pct() const;
257
258
/// Trace the stats of this tree to the TTY
259
void ReportStats(uint64 inTicks100Pct) const;
260
#endif // JPH_TRACK_BROADPHASE_STATS
261
262
private:
263
/// Constants
264
static constexpr uint32 cInvalidNodeIndex = 0xffffffff; ///< Value used to indicate node index is invalid
265
static const AABox cInvalidBounds; ///< Invalid bounding box using cLargeFloat
266
267
/// We alternate between two trees in order to let collision queries complete in parallel to adding/removing objects to the tree
268
struct RootNode
269
{
270
/// Get the ID of the root node
271
inline NodeID GetNodeID() const { return NodeID::sFromNodeIndex(mIndex); }
272
273
/// Index of the root node of the tree (this is always a node, never a body id)
274
atomic<uint32> mIndex { cInvalidNodeIndex };
275
};
276
277
/// Caches location of body inBodyID in the tracker, body can be found in mNodes[inNodeIdx].mChildNodeID[inChildIdx]
278
void GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const;
279
void SetBodyLocation(TrackingVector &ioTracking, BodyID inBodyID, uint32 inNodeIdx, uint32 inChildIdx) const;
280
static void sInvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID);
281
282
/// Get the current root of the tree
283
JPH_INLINE const RootNode & GetCurrentRoot() const { return mRootNode[mRootNodeIndex]; }
284
JPH_INLINE RootNode & GetCurrentRoot() { return mRootNode[mRootNodeIndex]; }
285
286
/// Depending on if inNodeID is a body or tree node return the bounding box
287
inline AABox GetNodeOrBodyBounds(const BodyVector &inBodies, NodeID inNodeID) const;
288
289
/// Mark node and all of its parents as changed
290
inline void MarkNodeAndParentsChanged(uint32 inNodeIndex);
291
292
/// Widen parent bounds of node inNodeIndex to encapsulate inNewBounds, also mark node and all of its parents as changed
293
inline void WidenAndMarkNodeAndParentsChanged(uint32 inNodeIndex, const AABox &inNewBounds);
294
295
/// Allocate a new node
296
inline uint32 AllocateNode(bool inIsChanged);
297
298
/// Try to insert a new leaf to the tree at inNodeIndex
299
inline bool TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies);
300
301
/// Try to replace the existing root with a new root that contains both the existing root and the new leaf
302
inline bool TryCreateNewRoot(TrackingVector &ioTracking, atomic<uint32> &ioRootNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies);
303
304
/// Build a tree for ioBodyIDs, returns the NodeID of the root (which will be the ID of a single body if inNumber = 1). All tree levels up to inMaxDepthMarkChanged will be marked as 'changed'.
305
NodeID BuildTree(const BodyVector &inBodies, TrackingVector &ioTracking, NodeID *ioNodeIDs, int inNumber, uint inMaxDepthMarkChanged, AABox &outBounds);
306
307
/// Sorts ioNodeIDs spatially into 2 groups. Second groups starts at ioNodeIDs + outMidPoint.
308
/// After the function returns ioNodeIDs and ioNodeCenters will be shuffled
309
static void sPartition(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inNumber, int &outMidPoint);
310
311
/// Sorts ioNodeIDs from inBegin to (but excluding) inEnd spatially into 4 groups.
312
/// outSplit needs to be 5 ints long, when the function returns each group runs from outSplit[i] to (but excluding) outSplit[i + 1]
313
/// After the function returns ioNodeIDs and ioNodeCenters will be shuffled
314
static void sPartition4(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inBegin, int inEnd, int *outSplit);
315
316
#ifdef JPH_DEBUG
317
/// Validate that the tree is consistent.
318
/// Note: This function only works if the tree is not modified while we're traversing it.
319
void ValidateTree(const BodyVector &inBodies, const TrackingVector &inTracking, uint32 inNodeIndex, uint32 inNumExpectedBodies) const;
320
#endif
321
322
#ifdef JPH_DUMP_BROADPHASE_TREE
323
/// Dump the tree in DOT format (see: https://graphviz.org/)
324
void DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) const;
325
#endif
326
327
/// Allocator that controls adding / freeing nodes
328
Allocator * mAllocator = nullptr;
329
330
/// This is a list of nodes that must be deleted after the trees are swapped and the old tree is no longer in use
331
Allocator::Batch mFreeNodeBatch;
332
333
/// Number of bodies currently in the tree
334
/// This is aligned to be in a different cache line from the `Allocator` pointer to prevent cross-thread syncs
335
/// when reading nodes.
336
alignas(JPH_CACHE_LINE_SIZE) atomic<uint32> mNumBodies { 0 };
337
338
/// We alternate between two tree root nodes. When updating, we activate the new tree and we keep the old tree alive.
339
/// for queries that are in progress until the next time DiscardOldTree() is called.
340
RootNode mRootNode[2];
341
atomic<uint32> mRootNodeIndex { 0 };
342
343
/// Flag to keep track of changes to the broadphase, if false, we don't need to UpdatePrepare/Finalize()
344
atomic<bool> mIsDirty = false;
345
346
#ifdef JPH_TRACK_BROADPHASE_STATS
347
/// Mutex protecting the various LayerToStats members
348
mutable Mutex mStatsMutex;
349
350
struct Stat
351
{
352
uint64 mNumQueries = 0;
353
uint64 mNodesVisited = 0;
354
uint64 mBodiesVisited = 0;
355
uint64 mHitsReported = 0;
356
uint64 mTotalTicks = 0;
357
uint64 mCollectorTicks = 0;
358
};
359
360
using LayerToStats = UnorderedMap<String, Stat>;
361
362
/// Sum up all the ticks in a layer
363
uint64 GetTicks100Pct(const LayerToStats &inLayer) const;
364
365
/// Trace the stats of a single query type to the TTY
366
void ReportStats(const char *inName, const LayerToStats &inLayer, uint64 inTicks100Pct) const;
367
368
mutable LayerToStats mCastRayStats;
369
mutable LayerToStats mCollideAABoxStats;
370
mutable LayerToStats mCollideSphereStats;
371
mutable LayerToStats mCollidePointStats;
372
mutable LayerToStats mCollideOrientedBoxStats;
373
mutable LayerToStats mCastAABoxStats;
374
#endif // JPH_TRACK_BROADPHASE_STATS
375
376
/// Debug function to get the depth of the tree from node inNodeID
377
uint GetMaxTreeDepth(const NodeID &inNodeID) const;
378
379
/// Walk the node tree calling the Visitor::VisitNodes for each node encountered and Visitor::VisitBody for each body encountered
380
template <class Visitor>
381
JPH_INLINE void WalkTree(const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking, Visitor &ioVisitor JPH_IF_TRACK_BROADPHASE_STATS(, LayerToStats &ioStats)) const;
382
383
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
384
/// Name of this tree for debugging purposes
385
const char * mName = "Layer";
386
#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
387
};
388
389
JPH_NAMESPACE_END
390
391