Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.h
9918 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Core/FixedSizeFreeList.h>7#include <Jolt/Core/Atomics.h>8#include <Jolt/Core/NonCopyable.h>9#include <Jolt/Physics/Body/BodyManager.h>10#include <Jolt/Physics/Collision/BroadPhase/BroadPhase.h>1112//#define JPH_DUMP_BROADPHASE_TREE1314JPH_NAMESPACE_BEGIN1516/// Internal tree structure in broadphase, is essentially a quad AABB tree.17/// 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.18/// During the UpdatePrepare/Finalize() call the tree is rebuilt to achieve a tight fit again.19class JPH_EXPORT QuadTree : public NonCopyable20{21public:22JPH_OVERRIDE_NEW_DELETE2324private:25// Forward declare26class AtomicNodeID;2728/// Class that points to either a body or a node in the tree29class NodeID30{31public:32JPH_OVERRIDE_NEW_DELETE3334/// Default constructor does not initialize35inline NodeID() = default;3637/// Construct a node ID38static inline NodeID sInvalid() { return NodeID(cInvalidNodeIndex); }39static inline NodeID sFromBodyID(BodyID inID) { NodeID node_id(inID.GetIndexAndSequenceNumber()); JPH_ASSERT(node_id.IsBody()); return node_id; }40static inline NodeID sFromNodeIndex(uint32 inIdx) { JPH_ASSERT((inIdx & cIsNode) == 0); return NodeID(inIdx | cIsNode); }4142/// Check what type of ID it is43inline bool IsValid() const { return mID != cInvalidNodeIndex; }44inline bool IsBody() const { return (mID & cIsNode) == 0; }45inline bool IsNode() const { return (mID & cIsNode) != 0; }4647/// Get body or node index48inline BodyID GetBodyID() const { JPH_ASSERT(IsBody()); return BodyID(mID); }49inline uint32 GetNodeIndex() const { JPH_ASSERT(IsNode()); return mID & ~cIsNode; }5051/// Comparison52inline bool operator == (const BodyID &inRHS) const { return mID == inRHS.GetIndexAndSequenceNumber(); }53inline bool operator == (const NodeID &inRHS) const { return mID == inRHS.mID; }5455private:56friend class AtomicNodeID;5758inline explicit NodeID(uint32 inID) : mID(inID) { }5960static const uint32 cIsNode = BodyID::cBroadPhaseBit; ///< If this bit is set it means that the ID refers to a node, otherwise it refers to a body6162uint32 mID;63};6465static_assert(sizeof(NodeID) == sizeof(BodyID), "Body id's should have the same size as NodeIDs");6667/// A NodeID that uses atomics to store the value68class AtomicNodeID69{70public:71/// Constructor72AtomicNodeID() = default;73explicit AtomicNodeID(const NodeID &inRHS) : mID(inRHS.mID) { }7475/// Assignment76inline void operator = (const NodeID &inRHS) { mID = inRHS.mID; }7778/// Getting the value79inline operator NodeID () const { return NodeID(mID); }8081/// Check if the ID is valid82inline bool IsValid() const { return mID != cInvalidNodeIndex; }8384/// Comparison85inline bool operator == (const BodyID &inRHS) const { return mID == inRHS.GetIndexAndSequenceNumber(); }86inline bool operator == (const NodeID &inRHS) const { return mID == inRHS.mID; }8788/// Atomically compare and swap value. Expects inOld value, replaces with inNew value or returns false89inline bool CompareExchange(NodeID inOld, NodeID inNew) { return mID.compare_exchange_strong(inOld.mID, inNew.mID); }9091private:92atomic<uint32> mID;93};9495/// Class that represents a node in the tree96class Node97{98public:99/// Construct node100explicit Node(bool inIsChanged);101102/// Get bounding box encapsulating all children103void GetNodeBounds(AABox &outBounds) const;104105/// Get bounding box in a consistent way with the functions below (check outBounds.IsValid() before using the box)106void GetChildBounds(int inChildIndex, AABox &outBounds) const;107108/// Set the bounds in such a way that other threads will either see a fully correct bounding box or a bounding box with no volume109void SetChildBounds(int inChildIndex, const AABox &inBounds);110111/// Invalidate bounding box in such a way that other threads will not temporarily see a very large bounding box112void InvalidateChildBounds(int inChildIndex);113114/// Encapsulate inBounds in node bounds, returns true if there were changes115bool EncapsulateChildBounds(int inChildIndex, const AABox &inBounds);116117/// Bounding box for child nodes or bodies (all initially set to invalid so no collision test will ever traverse to the leaf)118atomic<float> mBoundsMinX[4];119atomic<float> mBoundsMinY[4];120atomic<float> mBoundsMinZ[4];121atomic<float> mBoundsMaxX[4];122atomic<float> mBoundsMaxY[4];123atomic<float> mBoundsMaxZ[4];124125/// Index of child node or body ID.126AtomicNodeID mChildNodeID[4];127128/// Index of the parent node.129/// Note: This value is unreliable during the UpdatePrepare/Finalize() function as a node may be relinked to the newly built tree.130atomic<uint32> mParentNodeIndex = cInvalidNodeIndex;131132/// If this part of the tree has changed, if not, we will treat this sub tree as a single body during the UpdatePrepare/Finalize().133/// 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.134atomic<uint32> mIsChanged;135136// Padding to align to 124 bytes137uint32 mPadding = 0;138};139140// Maximum size of the stack during tree walk141static constexpr int cStackSize = 128;142143static_assert(sizeof(atomic<float>) == 4, "Assuming that an atomic doesn't add any additional storage");144static_assert(sizeof(atomic<uint32>) == 4, "Assuming that an atomic doesn't add any additional storage");145static_assert(std::is_trivially_destructible<Node>(), "Assuming that we don't have a destructor");146147public:148/// Class that allocates tree nodes, can be shared between multiple trees149using Allocator = FixedSizeFreeList<Node>;150151static_assert(Allocator::ObjectStorageSize == 128, "Node should be 128 bytes");152153/// Data to track location of a Body in the tree154struct Tracking155{156/// Constructor to satisfy the vector class157Tracking() = default;158Tracking(const Tracking &inRHS) : mBroadPhaseLayer(inRHS.mBroadPhaseLayer.load()), mObjectLayer(inRHS.mObjectLayer.load()), mBodyLocation(inRHS.mBodyLocation.load()) { }159160/// Invalid body location identifier161static const uint32 cInvalidBodyLocation = 0xffffffff;162163atomic<BroadPhaseLayer::Type> mBroadPhaseLayer = (BroadPhaseLayer::Type)cBroadPhaseLayerInvalid;164atomic<ObjectLayer> mObjectLayer = cObjectLayerInvalid;165atomic<uint32> mBodyLocation { cInvalidBodyLocation };166};167168using TrackingVector = Array<Tracking>;169170/// Destructor171~QuadTree();172173#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)174/// Name of the tree for debugging purposes175void SetName(const char *inName) { mName = inName; }176inline const char * GetName() const { return mName; }177#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED178179/// Check if there is anything in the tree180inline bool HasBodies() const { return mNumBodies != 0; }181182/// Check if the tree needs an UpdatePrepare/Finalize()183inline bool IsDirty() const { return mIsDirty; }184185/// Check if this tree can get an UpdatePrepare/Finalize() or if it needs a DiscardOldTree() first186inline bool CanBeUpdated() const { return mFreeNodeBatch.mNumObjects == 0; }187188/// Initialization189void Init(Allocator &inAllocator);190191struct UpdateState192{193NodeID mRootNodeID; ///< This will be the new root node id194};195196/// Will throw away the previous frame's nodes so that we can start building a new tree in the background197void DiscardOldTree();198199/// Get the bounding box for this tree200AABox GetBounds() const;201202/// Update the broadphase, needs to be called regularly to achieve a tight fit of the tree when bodies have been modified.203/// 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.204void UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild);205void UpdateFinalize(const BodyVector &inBodies, const TrackingVector &inTracking, const UpdateState &inUpdateState);206207/// Temporary data structure to pass information between AddBodiesPrepare and AddBodiesFinalize/Abort208struct AddState209{210NodeID mLeafID = NodeID::sInvalid();211AABox mLeafBounds;212};213214/// Prepare adding inNumber bodies at ioBodyIDs to the quad tree, returns the state in outState that should be used in AddBodiesFinalize.215/// This can be done on a background thread without influencing the broadphase.216/// ioBodyIDs may be shuffled around by this function.217void AddBodiesPrepare(const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber, AddState &outState);218219/// Finalize adding bodies to the quadtree, supply the same number of bodies as in AddBodiesPrepare.220void AddBodiesFinalize(TrackingVector &ioTracking, int inNumberBodies, const AddState &inState);221222/// Abort adding bodies to the quadtree, supply the same bodies and state as in AddBodiesPrepare.223/// This can be done on a background thread without influencing the broadphase.224void AddBodiesAbort(TrackingVector &ioTracking, const AddState &inState);225226/// Remove inNumber bodies in ioBodyIDs from the quadtree.227void RemoveBodies(const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber);228229/// Call whenever the aabb of a body changes.230void NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber);231232/// Cast a ray and get the intersecting bodies in ioCollector.233void CastRay(const RayCast &inRay, RayCastBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;234235/// Get bodies intersecting with inBox in ioCollector236void CollideAABox(const AABox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;237238/// Get bodies intersecting with a sphere in ioCollector239void CollideSphere(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;240241/// Get bodies intersecting with a point and any hits to ioCollector242void CollidePoint(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;243244/// Get bodies intersecting with an oriented box and any hits to ioCollector245void CollideOrientedBox(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;246247/// Cast a box and get intersecting bodies in ioCollector248void CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;249250/// Find all colliding pairs between dynamic bodies, calls ioPairCollector for every pair found251void FindCollidingPairs(const BodyVector &inBodies, const BodyID *inActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, BodyPairCollector &ioPairCollector, const ObjectLayerPairFilter &inObjectLayerPairFilter) const;252253#ifdef JPH_TRACK_BROADPHASE_STATS254/// Sum up all the ticks spent in the various layers255uint64 GetTicks100Pct() const;256257/// Trace the stats of this tree to the TTY258void ReportStats(uint64 inTicks100Pct) const;259#endif // JPH_TRACK_BROADPHASE_STATS260261private:262/// Constants263static constexpr uint32 cInvalidNodeIndex = 0xffffffff; ///< Value used to indicate node index is invalid264static const AABox cInvalidBounds; ///< Invalid bounding box using cLargeFloat265266/// We alternate between two trees in order to let collision queries complete in parallel to adding/removing objects to the tree267struct RootNode268{269/// Get the ID of the root node270inline NodeID GetNodeID() const { return NodeID::sFromNodeIndex(mIndex); }271272/// Index of the root node of the tree (this is always a node, never a body id)273atomic<uint32> mIndex { cInvalidNodeIndex };274};275276/// Caches location of body inBodyID in the tracker, body can be found in mNodes[inNodeIdx].mChildNodeID[inChildIdx]277void GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const;278void SetBodyLocation(TrackingVector &ioTracking, BodyID inBodyID, uint32 inNodeIdx, uint32 inChildIdx) const;279static void sInvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID);280281/// Get the current root of the tree282JPH_INLINE const RootNode & GetCurrentRoot() const { return mRootNode[mRootNodeIndex]; }283JPH_INLINE RootNode & GetCurrentRoot() { return mRootNode[mRootNodeIndex]; }284285/// Depending on if inNodeID is a body or tree node return the bounding box286inline AABox GetNodeOrBodyBounds(const BodyVector &inBodies, NodeID inNodeID) const;287288/// Mark node and all of its parents as changed289inline void MarkNodeAndParentsChanged(uint32 inNodeIndex);290291/// Widen parent bounds of node inNodeIndex to encapsulate inNewBounds, also mark node and all of its parents as changed292inline void WidenAndMarkNodeAndParentsChanged(uint32 inNodeIndex, const AABox &inNewBounds);293294/// Allocate a new node295inline uint32 AllocateNode(bool inIsChanged);296297/// Try to insert a new leaf to the tree at inNodeIndex298inline bool TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies);299300/// Try to replace the existing root with a new root that contains both the existing root and the new leaf301inline bool TryCreateNewRoot(TrackingVector &ioTracking, atomic<uint32> &ioRootNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies);302303/// 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'.304NodeID BuildTree(const BodyVector &inBodies, TrackingVector &ioTracking, NodeID *ioNodeIDs, int inNumber, uint inMaxDepthMarkChanged, AABox &outBounds);305306/// Sorts ioNodeIDs spatially into 2 groups. Second groups starts at ioNodeIDs + outMidPoint.307/// After the function returns ioNodeIDs and ioNodeCenters will be shuffled308static void sPartition(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inNumber, int &outMidPoint);309310/// Sorts ioNodeIDs from inBegin to (but excluding) inEnd spatially into 4 groups.311/// outSplit needs to be 5 ints long, when the function returns each group runs from outSplit[i] to (but excluding) outSplit[i + 1]312/// After the function returns ioNodeIDs and ioNodeCenters will be shuffled313static void sPartition4(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inBegin, int inEnd, int *outSplit);314315#ifdef JPH_DEBUG316/// Validate that the tree is consistent.317/// Note: This function only works if the tree is not modified while we're traversing it.318void ValidateTree(const BodyVector &inBodies, const TrackingVector &inTracking, uint32 inNodeIndex, uint32 inNumExpectedBodies) const;319#endif320321#ifdef JPH_DUMP_BROADPHASE_TREE322/// Dump the tree in DOT format (see: https://graphviz.org/)323void DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) const;324#endif325326/// Allocator that controls adding / freeing nodes327Allocator * mAllocator = nullptr;328329/// This is a list of nodes that must be deleted after the trees are swapped and the old tree is no longer in use330Allocator::Batch mFreeNodeBatch;331332/// Number of bodies currently in the tree333/// This is aligned to be in a different cache line from the `Allocator` pointer to prevent cross-thread syncs334/// when reading nodes.335alignas(JPH_CACHE_LINE_SIZE) atomic<uint32> mNumBodies { 0 };336337/// We alternate between two tree root nodes. When updating, we activate the new tree and we keep the old tree alive.338/// for queries that are in progress until the next time DiscardOldTree() is called.339RootNode mRootNode[2];340atomic<uint32> mRootNodeIndex { 0 };341342/// Flag to keep track of changes to the broadphase, if false, we don't need to UpdatePrepare/Finalize()343atomic<bool> mIsDirty = false;344345#ifdef JPH_TRACK_BROADPHASE_STATS346/// Mutex protecting the various LayerToStats members347mutable Mutex mStatsMutex;348349struct Stat350{351uint64 mNumQueries = 0;352uint64 mNodesVisited = 0;353uint64 mBodiesVisited = 0;354uint64 mHitsReported = 0;355uint64 mTotalTicks = 0;356uint64 mCollectorTicks = 0;357};358359using LayerToStats = UnorderedMap<String, Stat>;360361/// Sum up all the ticks in a layer362uint64 GetTicks100Pct(const LayerToStats &inLayer) const;363364/// Trace the stats of a single query type to the TTY365void ReportStats(const char *inName, const LayerToStats &inLayer, uint64 inTicks100Pct) const;366367mutable LayerToStats mCastRayStats;368mutable LayerToStats mCollideAABoxStats;369mutable LayerToStats mCollideSphereStats;370mutable LayerToStats mCollidePointStats;371mutable LayerToStats mCollideOrientedBoxStats;372mutable LayerToStats mCastAABoxStats;373#endif // JPH_TRACK_BROADPHASE_STATS374375/// Debug function to get the depth of the tree from node inNodeID376uint GetMaxTreeDepth(const NodeID &inNodeID) const;377378/// Walk the node tree calling the Visitor::VisitNodes for each node encountered and Visitor::VisitBody for each body encountered379template <class Visitor>380JPH_INLINE void WalkTree(const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking, Visitor &ioVisitor JPH_IF_TRACK_BROADPHASE_STATS(, LayerToStats &ioStats)) const;381382#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)383/// Name of this tree for debugging purposes384const char * mName = "Layer";385#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED386};387388JPH_NAMESPACE_END389390391