Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp
9918 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Collision/BroadPhase/QuadTree.h>7#include <Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.h>8#include <Jolt/Physics/Collision/RayCast.h>9#include <Jolt/Physics/Collision/AABoxCast.h>10#include <Jolt/Physics/Collision/CastResult.h>11#include <Jolt/Physics/Collision/SortReverseAndStore.h>12#include <Jolt/Physics/Body/BodyPair.h>13#include <Jolt/Physics/PhysicsLock.h>14#include <Jolt/Geometry/AABox4.h>15#include <Jolt/Geometry/RayAABox.h>16#include <Jolt/Geometry/OrientedBox.h>17#include <Jolt/Core/STLLocalAllocator.h>1819#ifdef JPH_DUMP_BROADPHASE_TREE20JPH_SUPPRESS_WARNINGS_STD_BEGIN21#include <fstream>22JPH_SUPPRESS_WARNINGS_STD_END23#endif // JPH_DUMP_BROADPHASE_TREE2425JPH_NAMESPACE_BEGIN2627////////////////////////////////////////////////////////////////////////////////////////////////////////28// QuadTree::Node29////////////////////////////////////////////////////////////////////////////////////////////////////////3031QuadTree::Node::Node(bool inIsChanged) :32mIsChanged(inIsChanged)33{34// First reset bounds35Vec4 val = Vec4::sReplicate(cLargeFloat);36val.StoreFloat4((Float4 *)&mBoundsMinX);37val.StoreFloat4((Float4 *)&mBoundsMinY);38val.StoreFloat4((Float4 *)&mBoundsMinZ);39val = Vec4::sReplicate(-cLargeFloat);40val.StoreFloat4((Float4 *)&mBoundsMaxX);41val.StoreFloat4((Float4 *)&mBoundsMaxY);42val.StoreFloat4((Float4 *)&mBoundsMaxZ);4344// Reset child node ids45mChildNodeID[0] = NodeID::sInvalid();46mChildNodeID[1] = NodeID::sInvalid();47mChildNodeID[2] = NodeID::sInvalid();48mChildNodeID[3] = NodeID::sInvalid();49}5051void QuadTree::Node::GetChildBounds(int inChildIndex, AABox &outBounds) const52{53// Read bounding box in order min -> max54outBounds.mMin = Vec3(mBoundsMinX[inChildIndex], mBoundsMinY[inChildIndex], mBoundsMinZ[inChildIndex]);55outBounds.mMax = Vec3(mBoundsMaxX[inChildIndex], mBoundsMaxY[inChildIndex], mBoundsMaxZ[inChildIndex]);56}5758void QuadTree::Node::SetChildBounds(int inChildIndex, const AABox &inBounds)59{60// Bounding boxes provided to the quad tree should never be larger than cLargeFloat because this may trigger overflow exceptions61// e.g. when squaring the value while testing sphere overlaps62JPH_ASSERT(inBounds.mMin.GetX() >= -cLargeFloat && inBounds.mMin.GetX() <= cLargeFloat63&& inBounds.mMin.GetY() >= -cLargeFloat && inBounds.mMin.GetY() <= cLargeFloat64&& inBounds.mMin.GetZ() >= -cLargeFloat && inBounds.mMin.GetZ() <= cLargeFloat65&& inBounds.mMax.GetX() >= -cLargeFloat && inBounds.mMax.GetX() <= cLargeFloat66&& inBounds.mMax.GetY() >= -cLargeFloat && inBounds.mMax.GetY() <= cLargeFloat67&& inBounds.mMax.GetZ() >= -cLargeFloat && inBounds.mMax.GetZ() <= cLargeFloat);6869// Set max first (this keeps the bounding box invalid for reading threads)70mBoundsMaxZ[inChildIndex] = inBounds.mMax.GetZ();71mBoundsMaxY[inChildIndex] = inBounds.mMax.GetY();72mBoundsMaxX[inChildIndex] = inBounds.mMax.GetX();7374// Then set min (and make box valid)75mBoundsMinZ[inChildIndex] = inBounds.mMin.GetZ();76mBoundsMinY[inChildIndex] = inBounds.mMin.GetY();77mBoundsMinX[inChildIndex] = inBounds.mMin.GetX(); // Min X becomes valid last78}7980void QuadTree::Node::InvalidateChildBounds(int inChildIndex)81{82// First we make the box invalid by setting the min to cLargeFloat83mBoundsMinX[inChildIndex] = cLargeFloat; // Min X becomes invalid first84mBoundsMinY[inChildIndex] = cLargeFloat;85mBoundsMinZ[inChildIndex] = cLargeFloat;8687// Then we reset the max values too88mBoundsMaxX[inChildIndex] = -cLargeFloat;89mBoundsMaxY[inChildIndex] = -cLargeFloat;90mBoundsMaxZ[inChildIndex] = -cLargeFloat;91}9293void QuadTree::Node::GetNodeBounds(AABox &outBounds) const94{95// Get first child bounds96GetChildBounds(0, outBounds);9798// Encapsulate other child bounds99for (int child_idx = 1; child_idx < 4; ++child_idx)100{101AABox tmp;102GetChildBounds(child_idx, tmp);103outBounds.Encapsulate(tmp);104}105}106107bool QuadTree::Node::EncapsulateChildBounds(int inChildIndex, const AABox &inBounds)108{109bool changed = AtomicMin(mBoundsMinX[inChildIndex], inBounds.mMin.GetX());110changed |= AtomicMin(mBoundsMinY[inChildIndex], inBounds.mMin.GetY());111changed |= AtomicMin(mBoundsMinZ[inChildIndex], inBounds.mMin.GetZ());112changed |= AtomicMax(mBoundsMaxX[inChildIndex], inBounds.mMax.GetX());113changed |= AtomicMax(mBoundsMaxY[inChildIndex], inBounds.mMax.GetY());114changed |= AtomicMax(mBoundsMaxZ[inChildIndex], inBounds.mMax.GetZ());115return changed;116}117118////////////////////////////////////////////////////////////////////////////////////////////////////////119// QuadTree120////////////////////////////////////////////////////////////////////////////////////////////////////////121122const AABox QuadTree::cInvalidBounds(Vec3::sReplicate(cLargeFloat), Vec3::sReplicate(-cLargeFloat));123124static inline void sQuadTreePerformanceWarning()125{126#ifdef JPH_ENABLE_ASSERTS127static atomic<bool> triggered_report { false };128bool expected = false;129if (triggered_report.compare_exchange_strong(expected, true))130Trace("QuadTree: Performance warning: Stack full!\n"131"This must be a very deep tree. Are you batch adding bodies through BodyInterface::AddBodiesPrepare/AddBodiesFinalize?\n"132"If you add lots of bodies through BodyInterface::AddBody you may need to call PhysicsSystem::OptimizeBroadPhase to rebuild the tree.");133#endif134}135136void QuadTree::GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const137{138uint32 body_location = inTracking[inBodyID.GetIndex()].mBodyLocation;139JPH_ASSERT(body_location != Tracking::cInvalidBodyLocation);140outNodeIdx = body_location & 0x3fffffff;141outChildIdx = body_location >> 30;142JPH_ASSERT(mAllocator->Get(outNodeIdx).mChildNodeID[outChildIdx] == inBodyID, "Make sure that the body is in the node where it should be");143}144145void QuadTree::SetBodyLocation(TrackingVector &ioTracking, BodyID inBodyID, uint32 inNodeIdx, uint32 inChildIdx) const146{147JPH_ASSERT(inNodeIdx <= 0x3fffffff);148JPH_ASSERT(inChildIdx < 4);149JPH_ASSERT(mAllocator->Get(inNodeIdx).mChildNodeID[inChildIdx] == inBodyID, "Make sure that the body is in the node where it should be");150ioTracking[inBodyID.GetIndex()].mBodyLocation = inNodeIdx + (inChildIdx << 30);151152#ifdef JPH_ENABLE_ASSERTS153uint32 v1, v2;154GetBodyLocation(ioTracking, inBodyID, v1, v2);155JPH_ASSERT(v1 == inNodeIdx);156JPH_ASSERT(v2 == inChildIdx);157#endif158}159160void QuadTree::sInvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID)161{162ioTracking[inBodyID.GetIndex()].mBodyLocation = Tracking::cInvalidBodyLocation;163}164165QuadTree::~QuadTree()166{167// Get rid of any nodes that are still to be freed168DiscardOldTree();169170// Get the current root node171const RootNode &root_node = GetCurrentRoot();172173// Collect all bodies174Allocator::Batch free_batch;175Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;176node_stack.reserve(cStackSize);177node_stack.push_back(root_node.GetNodeID());178JPH_ASSERT(node_stack.front().IsValid());179if (node_stack.front().IsNode())180{181do182{183// Process node184NodeID node_id = node_stack.back();185node_stack.pop_back();186JPH_ASSERT(!node_id.IsBody());187uint32 node_idx = node_id.GetNodeIndex();188const Node &node = mAllocator->Get(node_idx);189190// Recurse and get all child nodes191for (NodeID child_node_id : node.mChildNodeID)192if (child_node_id.IsValid() && child_node_id.IsNode())193node_stack.push_back(child_node_id);194195// Mark node to be freed196mAllocator->AddObjectToBatch(free_batch, node_idx);197}198while (!node_stack.empty());199}200201// Now free all nodes202mAllocator->DestructObjectBatch(free_batch);203}204205uint32 QuadTree::AllocateNode(bool inIsChanged)206{207uint32 index = mAllocator->ConstructObject(inIsChanged);208if (index == Allocator::cInvalidObjectIndex)209{210// If you're running out of nodes, you're most likely adding too many individual bodies to the tree.211// Because of the lock free nature of this tree, any individual body is added to the root of the tree.212// This means that if you add a lot of bodies individually, you will end up with a very deep tree and you'll be213// using a lot more nodes than you would if you added them in batches.214// Please look at BodyInterface::AddBodiesPrepare/AddBodiesFinalize.215//216// If you have created a wrapper around Jolt then a possible solution is to activate a mode during loading217// that queues up any bodies that need to be added. When loading is done, insert all of them as a single batch.218// This could be implemented as a 'start batching' / 'end batching' call to switch in and out of that mode.219// The rest of the code can then just use the regular 'add single body' call on your wrapper and doesn't need to know220// if this mode is active or not.221//222// Calling PhysicsSystem::Update or PhysicsSystem::OptimizeBroadPhase will perform maintenance223// on the tree and will make it efficient again. If you're not calling these functions and are adding a lot of bodies224// you could still be running out of nodes because the tree is not being maintained. If your application is paused,225// consider still calling PhysicsSystem::Update with a delta time of 0 to keep the tree in good shape.226//227// The system keeps track of a previous and a current tree, this allows for queries to continue using the old tree228// while the new tree is being built. If you completely clean the PhysicsSystem and rebuild it from scratch, you may229// want to call PhysicsSystem::OptimizeBroadPhase two times after clearing to completely get rid of any lingering nodes.230//231// The number of nodes that is allocated is related to the max number of bodies that is passed in PhysicsSystem::Init.232// For normal situations there are plenty of nodes available. If all else fails, you can increase the number of nodes233// by increasing the maximum number of bodies.234Trace("QuadTree: Out of nodes!");235std::abort();236}237return index;238}239240void QuadTree::Init(Allocator &inAllocator)241{242// Store allocator243mAllocator = &inAllocator;244245// Allocate root node246mRootNode[mRootNodeIndex].mIndex = AllocateNode(false);247}248249void QuadTree::DiscardOldTree()250{251// Check if there is an old tree252RootNode &old_root_node = mRootNode[mRootNodeIndex ^ 1];253if (old_root_node.mIndex != cInvalidNodeIndex)254{255// Clear the root256old_root_node.mIndex = cInvalidNodeIndex;257258// Now free all old nodes259mAllocator->DestructObjectBatch(mFreeNodeBatch);260261// Clear the batch262mFreeNodeBatch = Allocator::Batch();263}264}265266AABox QuadTree::GetBounds() const267{268uint32 node_idx = GetCurrentRoot().mIndex;269JPH_ASSERT(node_idx != cInvalidNodeIndex);270const Node &node = mAllocator->Get(node_idx);271272AABox bounds;273node.GetNodeBounds(bounds);274return bounds;275}276277void QuadTree::UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild)278{279#ifdef JPH_ENABLE_ASSERTS280// We only read positions281BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);282#endif283284// Assert we have no nodes pending deletion, this means DiscardOldTree wasn't called yet285JPH_ASSERT(mFreeNodeBatch.mNumObjects == 0);286287// Mark tree non-dirty288mIsDirty = false;289290// Get the current root node291const RootNode &root_node = GetCurrentRoot();292293#ifdef JPH_DUMP_BROADPHASE_TREE294DumpTree(root_node.GetNodeID(), StringFormat("%s_PRE", mName).c_str());295#endif296297// Assert sane data298#ifdef JPH_DEBUG299ValidateTree(inBodies, ioTracking, root_node.mIndex, mNumBodies);300#endif301302// Create space for all body ID's303NodeID *node_ids = mNumBodies > 0? new NodeID [mNumBodies] : nullptr;304NodeID *cur_node_id = node_ids;305306// Collect all bodies307Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;308node_stack.reserve(cStackSize);309node_stack.push_back(root_node.GetNodeID());310JPH_ASSERT(node_stack.front().IsValid());311do312{313// Pop node from stack314NodeID node_id = node_stack.back();315node_stack.pop_back();316317// Check if node is a body318if (node_id.IsBody())319{320// Validate that we're still in the right layer321#ifdef JPH_ENABLE_ASSERTS322uint32 body_index = node_id.GetBodyID().GetIndex();323JPH_ASSERT(ioTracking[body_index].mObjectLayer == inBodies[body_index]->GetObjectLayer());324#endif325326// Store body327*cur_node_id = node_id;328++cur_node_id;329}330else331{332// Process normal node333uint32 node_idx = node_id.GetNodeIndex();334const Node &node = mAllocator->Get(node_idx);335336if (!node.mIsChanged && !inFullRebuild)337{338// Node is unchanged, treat it as a whole339*cur_node_id = node_id;340++cur_node_id;341}342else343{344// Node is changed, recurse and get all children345for (NodeID child_node_id : node.mChildNodeID)346if (child_node_id.IsValid())347node_stack.push_back(child_node_id);348349// Mark node to be freed350mAllocator->AddObjectToBatch(mFreeNodeBatch, node_idx);351}352}353}354while (!node_stack.empty());355356// Check that our book keeping matches357uint32 num_node_ids = uint32(cur_node_id - node_ids);358JPH_ASSERT(inFullRebuild? num_node_ids == mNumBodies : num_node_ids <= mNumBodies);359360// This will be the new root node id361NodeID root_node_id;362363if (num_node_ids > 0)364{365// We mark the first 5 levels (max 1024 nodes) of the newly built tree as 'changed' so that366// those nodes get recreated every time when we rebuild the tree. This balances the amount of367// time we spend on rebuilding the tree ('unchanged' nodes will be put in the new tree as a whole)368// vs the quality of the built tree.369constexpr uint cMaxDepthMarkChanged = 5;370371// Build new tree372AABox root_bounds;373root_node_id = BuildTree(inBodies, ioTracking, node_ids, num_node_ids, cMaxDepthMarkChanged, root_bounds);374375if (root_node_id.IsBody())376{377// For a single body we need to allocate a new root node378uint32 root_idx = AllocateNode(false);379Node &root = mAllocator->Get(root_idx);380root.SetChildBounds(0, root_bounds);381root.mChildNodeID[0] = root_node_id;382SetBodyLocation(ioTracking, root_node_id.GetBodyID(), root_idx, 0);383root_node_id = NodeID::sFromNodeIndex(root_idx);384}385}386else387{388// Empty tree, create root node389uint32 root_idx = AllocateNode(false);390root_node_id = NodeID::sFromNodeIndex(root_idx);391}392393// Delete temporary data394delete [] node_ids;395396outUpdateState.mRootNodeID = root_node_id;397}398399void QuadTree::UpdateFinalize([[maybe_unused]] const BodyVector &inBodies, [[maybe_unused]] const TrackingVector &inTracking, const UpdateState &inUpdateState)400{401// Tree building is complete, now we switch the old with the new tree402uint32 new_root_idx = mRootNodeIndex ^ 1;403RootNode &new_root_node = mRootNode[new_root_idx];404{405// Note: We don't need to lock here as the old tree stays available so any queries406// that use it can continue using it until DiscardOldTree is called. This slot407// should be empty and unused at this moment.408JPH_ASSERT(new_root_node.mIndex == cInvalidNodeIndex);409new_root_node.mIndex = inUpdateState.mRootNodeID.GetNodeIndex();410}411412// All queries that start from now on will use this new tree413mRootNodeIndex = new_root_idx;414415#ifdef JPH_DUMP_BROADPHASE_TREE416DumpTree(new_root_node.GetNodeID(), StringFormat("%s_POST", mName).c_str());417#endif418419#ifdef JPH_DEBUG420ValidateTree(inBodies, inTracking, new_root_node.mIndex, mNumBodies);421#endif422}423424void QuadTree::sPartition(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inNumber, int &outMidPoint)425{426// Handle trivial case427if (inNumber <= 4)428{429outMidPoint = inNumber / 2;430return;431}432433// Calculate bounding box of box centers434Vec3 center_min = Vec3::sReplicate(cLargeFloat);435Vec3 center_max = Vec3::sReplicate(-cLargeFloat);436for (const Vec3 *c = ioNodeCenters, *c_end = ioNodeCenters + inNumber; c < c_end; ++c)437{438Vec3 center = *c;439center_min = Vec3::sMin(center_min, center);440center_max = Vec3::sMax(center_max, center);441}442443// Calculate split plane444int dimension = (center_max - center_min).GetHighestComponentIndex();445float split = 0.5f * (center_min + center_max)[dimension];446447// Divide bodies448int start = 0, end = inNumber;449while (start < end)450{451// Search for first element that is on the right hand side of the split plane452while (start < end && ioNodeCenters[start][dimension] < split)453++start;454455// Search for the first element that is on the left hand side of the split plane456while (start < end && ioNodeCenters[end - 1][dimension] >= split)457--end;458459if (start < end)460{461// Swap the two elements462std::swap(ioNodeIDs[start], ioNodeIDs[end - 1]);463std::swap(ioNodeCenters[start], ioNodeCenters[end - 1]);464++start;465--end;466}467}468JPH_ASSERT(start == end);469470if (start > 0 && start < inNumber)471{472// Success!473outMidPoint = start;474}475else476{477// Failed to divide bodies478outMidPoint = inNumber / 2;479}480}481482void QuadTree::sPartition4(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inBegin, int inEnd, int *outSplit)483{484NodeID *node_ids = ioNodeIDs + inBegin;485Vec3 *node_centers = ioNodeCenters + inBegin;486int number = inEnd - inBegin;487488// Partition entire range489sPartition(node_ids, node_centers, number, outSplit[2]);490491// Partition lower half492sPartition(node_ids, node_centers, outSplit[2], outSplit[1]);493494// Partition upper half495sPartition(node_ids + outSplit[2], node_centers + outSplit[2], number - outSplit[2], outSplit[3]);496497// Convert to proper range498outSplit[0] = inBegin;499outSplit[1] += inBegin;500outSplit[2] += inBegin;501outSplit[3] += outSplit[2];502outSplit[4] = inEnd;503}504505AABox QuadTree::GetNodeOrBodyBounds(const BodyVector &inBodies, NodeID inNodeID) const506{507if (inNodeID.IsNode())508{509// It is a node510uint32 node_idx = inNodeID.GetNodeIndex();511const Node &node = mAllocator->Get(node_idx);512513AABox bounds;514node.GetNodeBounds(bounds);515return bounds;516}517else518{519// It is a body520return inBodies[inNodeID.GetBodyID().GetIndex()]->GetWorldSpaceBounds();521}522}523524QuadTree::NodeID QuadTree::BuildTree(const BodyVector &inBodies, TrackingVector &ioTracking, NodeID *ioNodeIDs, int inNumber, uint inMaxDepthMarkChanged, AABox &outBounds)525{526// Trivial case: No bodies in tree527if (inNumber == 0)528{529outBounds = cInvalidBounds;530return NodeID::sInvalid();531}532533// Trivial case: When we have 1 body or node, return it534if (inNumber == 1)535{536if (ioNodeIDs->IsNode())537{538// When returning an existing node as root, ensure that no parent has been set539Node &node = mAllocator->Get(ioNodeIDs->GetNodeIndex());540node.mParentNodeIndex = cInvalidNodeIndex;541}542outBounds = GetNodeOrBodyBounds(inBodies, *ioNodeIDs);543return *ioNodeIDs;544}545546// Calculate centers of all bodies that are to be inserted547Vec3 *centers = new Vec3 [inNumber];548JPH_ASSERT(IsAligned(centers, JPH_VECTOR_ALIGNMENT));549Vec3 *c = centers;550for (const NodeID *n = ioNodeIDs, *n_end = ioNodeIDs + inNumber; n < n_end; ++n, ++c)551*c = GetNodeOrBodyBounds(inBodies, *n).GetCenter();552553// The algorithm is a recursive tree build, but to avoid the call overhead we keep track of a stack here554struct StackEntry555{556uint32 mNodeIdx; // Node index of node that is generated557int mChildIdx; // Index of child that we're currently processing558int mSplit[5]; // Indices where the node ID's have been split to form 4 partitions559uint32 mDepth; // Depth of this node in the tree560Vec3 mNodeBoundsMin; // Bounding box of this node, accumulated while iterating over children561Vec3 mNodeBoundsMax;562};563static_assert(sizeof(StackEntry) == 64);564StackEntry stack[cStackSize / 4]; // We don't process 4 at a time in this loop but 1, so the stack can be 4x as small565int top = 0;566567// Create root node568stack[0].mNodeIdx = AllocateNode(inMaxDepthMarkChanged > 0);569stack[0].mChildIdx = -1;570stack[0].mDepth = 0;571stack[0].mNodeBoundsMin = Vec3::sReplicate(cLargeFloat);572stack[0].mNodeBoundsMax = Vec3::sReplicate(-cLargeFloat);573sPartition4(ioNodeIDs, centers, 0, inNumber, stack[0].mSplit);574575for (;;)576{577StackEntry &cur_stack = stack[top];578579// Next child580cur_stack.mChildIdx++;581582// Check if all children processed583if (cur_stack.mChildIdx >= 4)584{585// Terminate if there's nothing left to pop586if (top <= 0)587break;588589// Add our bounds to our parents bounds590StackEntry &prev_stack = stack[top - 1];591prev_stack.mNodeBoundsMin = Vec3::sMin(prev_stack.mNodeBoundsMin, cur_stack.mNodeBoundsMin);592prev_stack.mNodeBoundsMax = Vec3::sMax(prev_stack.mNodeBoundsMax, cur_stack.mNodeBoundsMax);593594// Store parent node595Node &node = mAllocator->Get(cur_stack.mNodeIdx);596node.mParentNodeIndex = prev_stack.mNodeIdx;597598// Store this node's properties in the parent node599Node &parent_node = mAllocator->Get(prev_stack.mNodeIdx);600parent_node.mChildNodeID[prev_stack.mChildIdx] = NodeID::sFromNodeIndex(cur_stack.mNodeIdx);601parent_node.SetChildBounds(prev_stack.mChildIdx, AABox(cur_stack.mNodeBoundsMin, cur_stack.mNodeBoundsMax));602603// Pop entry from stack604--top;605}606else607{608// Get low and high index to bodies to process609int low = cur_stack.mSplit[cur_stack.mChildIdx];610int high = cur_stack.mSplit[cur_stack.mChildIdx + 1];611int num_bodies = high - low;612613if (num_bodies == 1)614{615// Get body info616NodeID child_node_id = ioNodeIDs[low];617AABox bounds = GetNodeOrBodyBounds(inBodies, child_node_id);618619// Update node620Node &node = mAllocator->Get(cur_stack.mNodeIdx);621node.mChildNodeID[cur_stack.mChildIdx] = child_node_id;622node.SetChildBounds(cur_stack.mChildIdx, bounds);623624if (child_node_id.IsNode())625{626// Update parent for this node627Node &child_node = mAllocator->Get(child_node_id.GetNodeIndex());628child_node.mParentNodeIndex = cur_stack.mNodeIdx;629}630else631{632// Set location in tracking633SetBodyLocation(ioTracking, child_node_id.GetBodyID(), cur_stack.mNodeIdx, cur_stack.mChildIdx);634}635636// Encapsulate bounding box in parent637cur_stack.mNodeBoundsMin = Vec3::sMin(cur_stack.mNodeBoundsMin, bounds.mMin);638cur_stack.mNodeBoundsMax = Vec3::sMax(cur_stack.mNodeBoundsMax, bounds.mMax);639}640else if (num_bodies > 1)641{642// Allocate new node643StackEntry &new_stack = stack[++top];644JPH_ASSERT(top < cStackSize / 4);645uint32 next_depth = cur_stack.mDepth + 1;646new_stack.mNodeIdx = AllocateNode(inMaxDepthMarkChanged > next_depth);647new_stack.mChildIdx = -1;648new_stack.mDepth = next_depth;649new_stack.mNodeBoundsMin = Vec3::sReplicate(cLargeFloat);650new_stack.mNodeBoundsMax = Vec3::sReplicate(-cLargeFloat);651sPartition4(ioNodeIDs, centers, low, high, new_stack.mSplit);652}653}654}655656// Delete temporary data657delete [] centers;658659// Store bounding box of root660outBounds.mMin = stack[0].mNodeBoundsMin;661outBounds.mMax = stack[0].mNodeBoundsMax;662663// Return root664return NodeID::sFromNodeIndex(stack[0].mNodeIdx);665}666667void QuadTree::MarkNodeAndParentsChanged(uint32 inNodeIndex)668{669uint32 node_idx = inNodeIndex;670671do672{673// If node has changed, parent will be too674Node &node = mAllocator->Get(node_idx);675if (node.mIsChanged)676break;677678// Mark node as changed679node.mIsChanged = true;680681// Get our parent682node_idx = node.mParentNodeIndex;683}684while (node_idx != cInvalidNodeIndex);685}686687void QuadTree::WidenAndMarkNodeAndParentsChanged(uint32 inNodeIndex, const AABox &inNewBounds)688{689uint32 node_idx = inNodeIndex;690691for (;;)692{693// Mark node as changed694Node &node = mAllocator->Get(node_idx);695node.mIsChanged = true;696697// Get our parent698uint32 parent_idx = node.mParentNodeIndex;699if (parent_idx == cInvalidNodeIndex)700break;701702// Find which child of the parent we're in703Node &parent_node = mAllocator->Get(parent_idx);704NodeID node_id = NodeID::sFromNodeIndex(node_idx);705int child_idx = -1;706for (int i = 0; i < 4; ++i)707if (parent_node.mChildNodeID[i] == node_id)708{709// Found one, set the node index and child index and update the bounding box too710child_idx = i;711break;712}713JPH_ASSERT(child_idx != -1, "Nodes don't get removed from the tree, we must have found it");714715// To avoid any race conditions with other threads we only enlarge bounding boxes716if (!parent_node.EncapsulateChildBounds(child_idx, inNewBounds))717{718// No changes to bounding box, only marking as changed remains to be done719if (!parent_node.mIsChanged)720MarkNodeAndParentsChanged(parent_idx);721break;722}723724// Update node index725node_idx = parent_idx;726}727}728729bool QuadTree::TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)730{731// Tentatively assign the node as parent732bool leaf_is_node = inLeafID.IsNode();733if (leaf_is_node)734{735uint32 leaf_idx = inLeafID.GetNodeIndex();736mAllocator->Get(leaf_idx).mParentNodeIndex = inNodeIndex;737}738739// Fetch node that we're adding to740Node &node = mAllocator->Get(inNodeIndex);741742// Find an empty child743for (uint32 child_idx = 0; child_idx < 4; ++child_idx)744if (node.mChildNodeID[child_idx].CompareExchange(NodeID::sInvalid(), inLeafID)) // Check if we can claim it745{746// We managed to add it to the node747748// If leaf was a body, we need to update its bookkeeping749if (!leaf_is_node)750SetBodyLocation(ioTracking, inLeafID.GetBodyID(), inNodeIndex, child_idx);751752// Now set the bounding box making the child valid for queries753node.SetChildBounds(child_idx, inLeafBounds);754755// Widen the bounds for our parents too756WidenAndMarkNodeAndParentsChanged(inNodeIndex, inLeafBounds);757758// Update body counter759mNumBodies += inLeafNumBodies;760761// And we're done762return true;763}764765return false;766}767768bool QuadTree::TryCreateNewRoot(TrackingVector &ioTracking, atomic<uint32> &ioRootNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)769{770// Fetch old root771uint32 root_idx = ioRootNodeIndex;772Node &root = mAllocator->Get(root_idx);773774// Create new root, mark this new root as changed as we're not creating a very efficient tree at this point775uint32 new_root_idx = AllocateNode(true);776Node &new_root = mAllocator->Get(new_root_idx);777778// 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 box779new_root.mChildNodeID[0] = NodeID::sFromNodeIndex(root_idx);780new_root.SetChildBounds(0, AABox(Vec3::sReplicate(-cLargeFloat), Vec3::sReplicate(cLargeFloat)));781782// Second child is new leaf783new_root.mChildNodeID[1] = inLeafID;784new_root.SetChildBounds(1, inLeafBounds);785786// Tentatively assign new root as parent787bool leaf_is_node = inLeafID.IsNode();788if (leaf_is_node)789{790uint32 leaf_idx = inLeafID.GetNodeIndex();791mAllocator->Get(leaf_idx).mParentNodeIndex = new_root_idx;792}793794// Try to swap it795if (ioRootNodeIndex.compare_exchange_strong(root_idx, new_root_idx))796{797// We managed to set the new root798799// If leaf was a body, we need to update its bookkeeping800if (!leaf_is_node)801SetBodyLocation(ioTracking, inLeafID.GetBodyID(), new_root_idx, 1);802803// Store parent node for old root804root.mParentNodeIndex = new_root_idx;805806// Update body counter807mNumBodies += inLeafNumBodies;808809// And we're done810return true;811}812813// Failed to swap, someone else must have created a new root, try again814mAllocator->DestructObject(new_root_idx);815return false;816}817818void QuadTree::AddBodiesPrepare(const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber, AddState &outState)819{820// Assert sane input821JPH_ASSERT(ioBodyIDs != nullptr);822JPH_ASSERT(inNumber > 0);823824#ifdef JPH_ENABLE_ASSERTS825// Below we just cast the body ID's to node ID's, check here that that is valid826for (const BodyID *b = ioBodyIDs, *b_end = ioBodyIDs + inNumber; b < b_end; ++b)827NodeID::sFromBodyID(*b);828#endif829830// Build subtree for the new bodies, note that we mark all nodes as 'not changed'831// so they will stay together as a batch and will make the tree rebuild cheaper832outState.mLeafID = BuildTree(inBodies, ioTracking, (NodeID *)ioBodyIDs, inNumber, 0, outState.mLeafBounds);833834#ifdef JPH_DEBUG835if (outState.mLeafID.IsNode())836ValidateTree(inBodies, ioTracking, outState.mLeafID.GetNodeIndex(), inNumber);837#endif838}839840void QuadTree::AddBodiesFinalize(TrackingVector &ioTracking, int inNumberBodies, const AddState &inState)841{842// Assert sane input843JPH_ASSERT(inNumberBodies > 0);844845// Mark tree dirty846mIsDirty = true;847848// Get the current root node849RootNode &root_node = GetCurrentRoot();850851for (;;)852{853// Check if we can insert the body in the root854if (TryInsertLeaf(ioTracking, root_node.mIndex, inState.mLeafID, inState.mLeafBounds, inNumberBodies))855return;856857// Check if we can create a new root858if (TryCreateNewRoot(ioTracking, root_node.mIndex, inState.mLeafID, inState.mLeafBounds, inNumberBodies))859return;860}861}862863void QuadTree::AddBodiesAbort(TrackingVector &ioTracking, const AddState &inState)864{865// Collect all bodies866Allocator::Batch free_batch;867NodeID node_stack[cStackSize];868node_stack[0] = inState.mLeafID;869JPH_ASSERT(node_stack[0].IsValid());870int top = 0;871do872{873// Check if node is a body874NodeID child_node_id = node_stack[top];875if (child_node_id.IsBody())876{877// Reset location of body878sInvalidateBodyLocation(ioTracking, child_node_id.GetBodyID());879}880else881{882// Process normal node883uint32 node_idx = child_node_id.GetNodeIndex();884const Node &node = mAllocator->Get(node_idx);885for (NodeID sub_child_node_id : node.mChildNodeID)886if (sub_child_node_id.IsValid())887{888JPH_ASSERT(top < cStackSize);889node_stack[top] = sub_child_node_id;890top++;891}892893// Mark it to be freed894mAllocator->AddObjectToBatch(free_batch, node_idx);895}896--top;897}898while (top >= 0);899900// Now free all nodes as a single batch901mAllocator->DestructObjectBatch(free_batch);902}903904void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber)905{906// Assert sane input907JPH_ASSERT(ioBodyIDs != nullptr);908JPH_ASSERT(inNumber > 0);909910// Mark tree dirty911mIsDirty = true;912913for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)914{915// Check if BodyID is correct916JPH_ASSERT(inBodies[cur->GetIndex()]->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");917918// Get location of body919uint32 node_idx, child_idx;920GetBodyLocation(ioTracking, *cur, node_idx, child_idx);921922// First we reset our internal bookkeeping923sInvalidateBodyLocation(ioTracking, *cur);924925// Then we make the bounding box invalid, no queries can find this node anymore926Node &node = mAllocator->Get(node_idx);927node.InvalidateChildBounds(child_idx);928929// Finally we reset the child id, this makes the node available for adds again930node.mChildNodeID[child_idx] = NodeID::sInvalid();931932// We don't need to bubble up our bounding box changes to our parents since we never make volumes smaller, only bigger933// But we do need to mark the nodes as changed so that the tree can be rebuilt934MarkNodeAndParentsChanged(node_idx);935}936937mNumBodies -= inNumber;938}939940void QuadTree::NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber)941{942// Assert sane input943JPH_ASSERT(ioBodyIDs != nullptr);944JPH_ASSERT(inNumber > 0);945946for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)947{948// Check if BodyID is correct949const Body *body = inBodies[cur->GetIndex()];950JPH_ASSERT(body->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");951952// Get the new bounding box953const AABox &new_bounds = body->GetWorldSpaceBounds();954955// Get location of body956uint32 node_idx, child_idx;957GetBodyLocation(inTracking, *cur, node_idx, child_idx);958959// Widen bounds for node960Node &node = mAllocator->Get(node_idx);961if (node.EncapsulateChildBounds(child_idx, new_bounds))962{963// Mark tree dirty964mIsDirty = true;965966// If bounds changed, widen the bounds for our parents too967WidenAndMarkNodeAndParentsChanged(node_idx, new_bounds);968}969}970}971972template <class Visitor>973JPH_INLINE void QuadTree::WalkTree(const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking, Visitor &ioVisitor JPH_IF_TRACK_BROADPHASE_STATS(, LayerToStats &ioStats)) const974{975// Get the root976const RootNode &root_node = GetCurrentRoot();977978#ifdef JPH_TRACK_BROADPHASE_STATS979// Start tracking stats980int bodies_visited = 0;981int hits_collected = 0;982int nodes_visited = 0;983uint64 collector_ticks = 0;984985uint64 start = GetProcessorTickCount();986#endif // JPH_TRACK_BROADPHASE_STATS987988Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack_array;989node_stack_array.resize(cStackSize);990NodeID *node_stack = node_stack_array.data();991node_stack[0] = root_node.GetNodeID();992int top = 0;993do994{995// Check if node is a body996NodeID child_node_id = node_stack[top];997if (child_node_id.IsBody())998{999// Track amount of bodies visited1000JPH_IF_TRACK_BROADPHASE_STATS(++bodies_visited;)10011002BodyID body_id = child_node_id.GetBodyID();1003ObjectLayer 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 invalid1004if (object_layer != cObjectLayerInvalid && inObjectLayerFilter.ShouldCollide(object_layer))1005{1006JPH_PROFILE("VisitBody");10071008// Track amount of hits1009JPH_IF_TRACK_BROADPHASE_STATS(++hits_collected;)10101011// Start track time the collector takes1012JPH_IF_TRACK_BROADPHASE_STATS(uint64 collector_start = GetProcessorTickCount();)10131014// We found a body we collide with, call our visitor1015ioVisitor.VisitBody(body_id, top);10161017// End track time the collector takes1018JPH_IF_TRACK_BROADPHASE_STATS(collector_ticks += GetProcessorTickCount() - collector_start;)10191020// Check if we're done1021if (ioVisitor.ShouldAbort())1022break;1023}1024}1025else if (child_node_id.IsValid())1026{1027JPH_IF_TRACK_BROADPHASE_STATS(++nodes_visited;)10281029// Ensure there is space on the stack (falls back to heap if there isn't)1030if (top + 4 >= (int)node_stack_array.size())1031{1032sQuadTreePerformanceWarning();1033node_stack_array.resize(node_stack_array.size() << 1);1034node_stack = node_stack_array.data();1035ioVisitor.OnStackResized(node_stack_array.size());1036}10371038// Process normal node1039const Node &node = mAllocator->Get(child_node_id.GetNodeIndex());1040JPH_ASSERT(IsAligned(&node, JPH_CACHE_LINE_SIZE));10411042// Load bounds of 4 children1043Vec4 bounds_minx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinX);1044Vec4 bounds_miny = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinY);1045Vec4 bounds_minz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinZ);1046Vec4 bounds_maxx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxX);1047Vec4 bounds_maxy = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxY);1048Vec4 bounds_maxz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxZ);10491050// Load ids for 4 children1051UVec4 child_ids = UVec4::sLoadInt4Aligned((const uint32 *)&node.mChildNodeID[0]);10521053// Check which sub nodes to visit1054int num_results = ioVisitor.VisitNodes(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, child_ids, top);1055child_ids.StoreInt4((uint32 *)&node_stack[top]);1056top += num_results;1057}10581059// Fetch next node until we find one that the visitor wants to see1060do1061--top;1062while (top >= 0 && !ioVisitor.ShouldVisitNode(top));1063}1064while (top >= 0);10651066#ifdef JPH_TRACK_BROADPHASE_STATS1067// Calculate total time the broadphase walk took1068uint64 total_ticks = GetProcessorTickCount() - start;10691070// Update stats under lock protection (slow!)1071{1072unique_lock lock(mStatsMutex);1073Stat &s = ioStats[inObjectLayerFilter.GetDescription()];1074s.mNumQueries++;1075s.mNodesVisited += nodes_visited;1076s.mBodiesVisited += bodies_visited;1077s.mHitsReported += hits_collected;1078s.mTotalTicks += total_ticks;1079s.mCollectorTicks += collector_ticks;1080}1081#endif // JPH_TRACK_BROADPHASE_STATS1082}10831084void QuadTree::CastRay(const RayCast &inRay, RayCastBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1085{1086class Visitor1087{1088public:1089/// Constructor1090JPH_INLINE Visitor(const RayCast &inRay, RayCastBodyCollector &ioCollector) :1091mOrigin(inRay.mOrigin),1092mInvDirection(inRay.mDirection),1093mCollector(ioCollector)1094{1095mFractionStack.resize(cStackSize);1096mFractionStack[0] = -1;1097}10981099/// Returns true if further processing of the tree should be aborted1100JPH_INLINE bool ShouldAbort() const1101{1102return mCollector.ShouldEarlyOut();1103}11041105/// Returns true if this node / body should be visited, false if no hit can be generated1106JPH_INLINE bool ShouldVisitNode(int inStackTop) const1107{1108return mFractionStack[inStackTop] < mCollector.GetEarlyOutFraction();1109}11101111/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1112JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop)1113{1114// Test the ray against 4 bounding boxes1115Vec4 fraction = RayAABox4(mOrigin, mInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);11161117// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)1118return SortReverseAndStore(fraction, mCollector.GetEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]);1119}11201121/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1122JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1123{1124// Store potential hit with body1125BroadPhaseCastResult result { inBodyID, mFractionStack[inStackTop] };1126mCollector.AddHit(result);1127}11281129/// Called when the stack is resized, this allows us to resize the fraction stack to match the new stack size1130JPH_INLINE void OnStackResized(size_t inNewStackSize)1131{1132mFractionStack.resize(inNewStackSize);1133}11341135private:1136Vec3 mOrigin;1137RayInvDirection mInvDirection;1138RayCastBodyCollector & mCollector;1139Array<float, STLLocalAllocator<float, cStackSize>> mFractionStack;1140};11411142Visitor visitor(inRay, ioCollector);1143WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCastRayStats));1144}11451146void QuadTree::CollideAABox(const AABox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1147{1148class Visitor1149{1150public:1151/// Constructor1152JPH_INLINE Visitor(const AABox &inBox, CollideShapeBodyCollector &ioCollector) :1153mBox(inBox),1154mCollector(ioCollector)1155{1156}11571158/// Returns true if further processing of the tree should be aborted1159JPH_INLINE bool ShouldAbort() const1160{1161return mCollector.ShouldEarlyOut();1162}11631164/// Returns true if this node / body should be visited, false if no hit can be generated1165JPH_INLINE bool ShouldVisitNode(int inStackTop) const1166{1167return true;1168}11691170/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1171JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1172{1173// Test the box vs 4 boxes1174UVec4 hitting = AABox4VsBox(mBox, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1175return CountAndSortTrues(hitting, ioChildNodeIDs);1176}11771178/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1179JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1180{1181// Store potential hit with body1182mCollector.AddHit(inBodyID);1183}11841185/// Called when the stack is resized1186JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1187{1188// Nothing to do1189}11901191private:1192const AABox & mBox;1193CollideShapeBodyCollector & mCollector;1194};11951196Visitor visitor(inBox, ioCollector);1197WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideAABoxStats));1198}11991200void QuadTree::CollideSphere(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1201{1202class Visitor1203{1204public:1205/// Constructor1206JPH_INLINE Visitor(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector) :1207mCenterX(inCenter.SplatX()),1208mCenterY(inCenter.SplatY()),1209mCenterZ(inCenter.SplatZ()),1210mRadiusSq(Vec4::sReplicate(Square(inRadius))),1211mCollector(ioCollector)1212{1213}12141215/// Returns true if further processing of the tree should be aborted1216JPH_INLINE bool ShouldAbort() const1217{1218return mCollector.ShouldEarlyOut();1219}12201221/// Returns true if this node / body should be visited, false if no hit can be generated1222JPH_INLINE bool ShouldVisitNode(int inStackTop) const1223{1224return true;1225}12261227/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1228JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1229{1230// Test 4 boxes vs sphere1231UVec4 hitting = AABox4VsSphere(mCenterX, mCenterY, mCenterZ, mRadiusSq, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1232return CountAndSortTrues(hitting, ioChildNodeIDs);1233}12341235/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1236JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1237{1238// Store potential hit with body1239mCollector.AddHit(inBodyID);1240}12411242/// Called when the stack is resized1243JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1244{1245// Nothing to do1246}12471248private:1249Vec4 mCenterX;1250Vec4 mCenterY;1251Vec4 mCenterZ;1252Vec4 mRadiusSq;1253CollideShapeBodyCollector & mCollector;1254};12551256Visitor visitor(inCenter, inRadius, ioCollector);1257WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideSphereStats));1258}12591260void QuadTree::CollidePoint(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1261{1262class Visitor1263{1264public:1265/// Constructor1266JPH_INLINE Visitor(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector) :1267mPoint(inPoint),1268mCollector(ioCollector)1269{1270}12711272/// Returns true if further processing of the tree should be aborted1273JPH_INLINE bool ShouldAbort() const1274{1275return mCollector.ShouldEarlyOut();1276}12771278/// Returns true if this node / body should be visited, false if no hit can be generated1279JPH_INLINE bool ShouldVisitNode(int inStackTop) const1280{1281return true;1282}12831284/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1285JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1286{1287// Test if point overlaps with box1288UVec4 hitting = AABox4VsPoint(mPoint, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1289return CountAndSortTrues(hitting, ioChildNodeIDs);1290}12911292/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1293JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1294{1295// Store potential hit with body1296mCollector.AddHit(inBodyID);1297}12981299/// Called when the stack is resized1300JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1301{1302// Nothing to do1303}13041305private:1306Vec3 mPoint;1307CollideShapeBodyCollector & mCollector;1308};13091310Visitor visitor(inPoint, ioCollector);1311WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollidePointStats));1312}13131314void QuadTree::CollideOrientedBox(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1315{1316class Visitor1317{1318public:1319/// Constructor1320JPH_INLINE Visitor(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector) :1321mBox(inBox),1322mCollector(ioCollector)1323{1324}13251326/// Returns true if further processing of the tree should be aborted1327JPH_INLINE bool ShouldAbort() const1328{1329return mCollector.ShouldEarlyOut();1330}13311332/// Returns true if this node / body should be visited, false if no hit can be generated1333JPH_INLINE bool ShouldVisitNode(int inStackTop) const1334{1335return true;1336}13371338/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1339JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1340{1341// Test if point overlaps with box1342UVec4 hitting = AABox4VsBox(mBox, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1343return CountAndSortTrues(hitting, ioChildNodeIDs);1344}13451346/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1347JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1348{1349// Store potential hit with body1350mCollector.AddHit(inBodyID);1351}13521353/// Called when the stack is resized1354JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1355{1356// Nothing to do1357}13581359private:1360OrientedBox mBox;1361CollideShapeBodyCollector & mCollector;1362};13631364Visitor visitor(inBox, ioCollector);1365WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideOrientedBoxStats));1366}13671368void QuadTree::CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1369{1370class Visitor1371{1372public:1373/// Constructor1374JPH_INLINE Visitor(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector) :1375mOrigin(inBox.mBox.GetCenter()),1376mExtent(inBox.mBox.GetExtent()),1377mInvDirection(inBox.mDirection),1378mCollector(ioCollector)1379{1380mFractionStack.resize(cStackSize);1381mFractionStack[0] = -1;1382}13831384/// Returns true if further processing of the tree should be aborted1385JPH_INLINE bool ShouldAbort() const1386{1387return mCollector.ShouldEarlyOut();1388}13891390/// Returns true if this node / body should be visited, false if no hit can be generated1391JPH_INLINE bool ShouldVisitNode(int inStackTop) const1392{1393return mFractionStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();1394}13951396/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1397JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop)1398{1399// Enlarge them by the casted aabox extents1400Vec4 bounds_min_x = inBoundsMinX, bounds_min_y = inBoundsMinY, bounds_min_z = inBoundsMinZ, bounds_max_x = inBoundsMaxX, bounds_max_y = inBoundsMaxY, bounds_max_z = inBoundsMaxZ;1401AABox4EnlargeWithExtent(mExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);14021403// Test 4 children1404Vec4 fraction = RayAABox4(mOrigin, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);14051406// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)1407return SortReverseAndStore(fraction, mCollector.GetPositiveEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]);1408}14091410/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1411JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1412{1413// Store potential hit with body1414BroadPhaseCastResult result { inBodyID, mFractionStack[inStackTop] };1415mCollector.AddHit(result);1416}14171418/// Called when the stack is resized, this allows us to resize the fraction stack to match the new stack size1419JPH_INLINE void OnStackResized(size_t inNewStackSize)1420{1421mFractionStack.resize(inNewStackSize);1422}14231424private:1425Vec3 mOrigin;1426Vec3 mExtent;1427RayInvDirection mInvDirection;1428CastShapeBodyCollector & mCollector;1429Array<float, STLLocalAllocator<float, cStackSize>> mFractionStack;1430};14311432Visitor visitor(inBox, ioCollector);1433WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCastAABoxStats));1434}14351436void QuadTree::FindCollidingPairs(const BodyVector &inBodies, const BodyID *inActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, BodyPairCollector &ioPairCollector, const ObjectLayerPairFilter &inObjectLayerPairFilter) const1437{1438// 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.1439// We double check this at the end of the function.1440const RootNode &root_node = GetCurrentRoot();1441JPH_ASSERT(root_node.mIndex != cInvalidNodeIndex);14421443// Assert sane input1444JPH_ASSERT(inActiveBodies != nullptr);1445JPH_ASSERT(inNumActiveBodies > 0);14461447Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack_array;1448node_stack_array.resize(cStackSize);1449NodeID *node_stack = node_stack_array.data();14501451// Loop over all active bodies1452for (int b1 = 0; b1 < inNumActiveBodies; ++b1)1453{1454BodyID b1_id = inActiveBodies[b1];1455const Body &body1 = *inBodies[b1_id.GetIndex()];1456JPH_ASSERT(!body1.IsStatic());14571458// Expand the bounding box by the speculative contact distance1459AABox bounds1 = body1.GetWorldSpaceBounds();1460bounds1.ExpandBy(Vec3::sReplicate(inSpeculativeContactDistance));14611462// Test each body with the tree1463node_stack[0] = root_node.GetNodeID();1464int top = 0;1465do1466{1467// Check if node is a body1468NodeID child_node_id = node_stack[top];1469if (child_node_id.IsBody())1470{1471// Don't collide with self1472BodyID b2_id = child_node_id.GetBodyID();1473if (b1_id != b2_id)1474{1475// Collision between dynamic pairs need to be picked up only once1476const Body &body2 = *inBodies[b2_id.GetIndex()];1477if (inObjectLayerPairFilter.ShouldCollide(body1.GetObjectLayer(), body2.GetObjectLayer())1478&& Body::sFindCollidingPairsCanCollide(body1, body2)1479&& 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 overlap1480{1481// Store potential hit between bodies1482ioPairCollector.AddHit({ b1_id, b2_id });1483}1484}1485}1486else if (child_node_id.IsValid())1487{1488// Process normal node1489const Node &node = mAllocator->Get(child_node_id.GetNodeIndex());1490JPH_ASSERT(IsAligned(&node, JPH_CACHE_LINE_SIZE));14911492// Get bounds of 4 children1493Vec4 bounds_minx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinX);1494Vec4 bounds_miny = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinY);1495Vec4 bounds_minz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinZ);1496Vec4 bounds_maxx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxX);1497Vec4 bounds_maxy = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxY);1498Vec4 bounds_maxz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxZ);14991500// Test overlap1501UVec4 overlap = AABox4VsBox(bounds1, bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz);1502int num_results = overlap.CountTrues();1503if (num_results > 0)1504{1505// Load ids for 4 children1506UVec4 child_ids = UVec4::sLoadInt4Aligned((const uint32 *)&node.mChildNodeID[0]);15071508// Sort so that overlaps are first1509child_ids = UVec4::sSort4True(overlap, child_ids);15101511// Ensure there is space on the stack (falls back to heap if there isn't)1512if (top + 4 >= (int)node_stack_array.size())1513{1514sQuadTreePerformanceWarning();1515node_stack_array.resize(node_stack_array.size() << 1);1516node_stack = node_stack_array.data();1517}15181519// Push them onto the stack1520child_ids.StoreInt4((uint32 *)&node_stack[top]);1521top += num_results;1522}1523}1524--top;1525}1526while (top >= 0);1527}15281529// Test that the root node was not swapped while finding collision pairs.1530// This would mean that UpdateFinalize/DiscardOldTree ran during collision detection which should not be possible due to the way the jobs are scheduled.1531JPH_ASSERT(root_node.mIndex != cInvalidNodeIndex);1532JPH_ASSERT(&root_node == &GetCurrentRoot());1533}15341535#ifdef JPH_DEBUG15361537void QuadTree::ValidateTree(const BodyVector &inBodies, const TrackingVector &inTracking, uint32 inNodeIndex, uint32 inNumExpectedBodies) const1538{1539JPH_PROFILE_FUNCTION();15401541// Root should be valid1542JPH_ASSERT(inNodeIndex != cInvalidNodeIndex);15431544// To avoid call overhead, create a stack in place1545JPH_SUPPRESS_WARNING_PUSH1546JPH_CLANG_SUPPRESS_WARNING("-Wunused-member-function") // The default constructor of StackEntry is unused when using Jolt's Array class but not when using std::vector1547struct StackEntry1548{1549StackEntry() = default;1550inline StackEntry(uint32 inNodeIndex, uint32 inParentNodeIndex) : mNodeIndex(inNodeIndex), mParentNodeIndex(inParentNodeIndex) { }15511552uint32 mNodeIndex;1553uint32 mParentNodeIndex;1554};1555JPH_SUPPRESS_WARNING_POP1556Array<StackEntry, STLLocalAllocator<StackEntry, cStackSize>> stack;1557stack.reserve(cStackSize);1558stack.emplace_back(inNodeIndex, cInvalidNodeIndex);15591560uint32 num_bodies = 0;15611562do1563{1564// Copy entry from the stack1565StackEntry cur_stack = stack.back();1566stack.pop_back();15671568// Validate parent1569const Node &node = mAllocator->Get(cur_stack.mNodeIndex);1570JPH_ASSERT(node.mParentNodeIndex == cur_stack.mParentNodeIndex);15711572// Validate that when a parent is not-changed that all of its children are also1573JPH_ASSERT(cur_stack.mParentNodeIndex == cInvalidNodeIndex || mAllocator->Get(cur_stack.mParentNodeIndex).mIsChanged || !node.mIsChanged);15741575// Loop children1576for (uint32 i = 0; i < 4; ++i)1577{1578NodeID child_node_id = node.mChildNodeID[i];1579if (child_node_id.IsValid())1580{1581if (child_node_id.IsNode())1582{1583// Child is a node, recurse1584uint32 child_idx = child_node_id.GetNodeIndex();1585stack.emplace_back(child_idx, cur_stack.mNodeIndex);15861587// Validate that the bounding box is bigger or equal to the bounds in the tree1588// Bounding box could also be invalid if all children of our child were removed1589AABox child_bounds;1590node.GetChildBounds(i, child_bounds);1591AABox real_child_bounds;1592mAllocator->Get(child_idx).GetNodeBounds(real_child_bounds);1593JPH_ASSERT(child_bounds.Contains(real_child_bounds) || !real_child_bounds.IsValid());1594}1595else1596{1597// Increment number of bodies found1598++num_bodies;15991600// Check if tracker matches position of body1601uint32 node_idx, child_idx;1602GetBodyLocation(inTracking, child_node_id.GetBodyID(), node_idx, child_idx);1603JPH_ASSERT(node_idx == cur_stack.mNodeIndex);1604JPH_ASSERT(child_idx == i);16051606// Validate that the body cached bounds still match the actual bounds1607const Body *body = inBodies[child_node_id.GetBodyID().GetIndex()];1608body->ValidateCachedBounds();16091610// Validate that the node bounds are bigger or equal to the body bounds1611AABox body_bounds;1612node.GetChildBounds(i, body_bounds);1613JPH_ASSERT(body_bounds.Contains(body->GetWorldSpaceBounds()));1614}1615}1616}1617}1618while (!stack.empty());16191620// Check that the amount of bodies in the tree matches our counter1621JPH_ASSERT(num_bodies == inNumExpectedBodies);1622}16231624#endif16251626#ifdef JPH_DUMP_BROADPHASE_TREE16271628void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) const1629{1630// Open DOT file1631std::ofstream f;1632f.open(StringFormat("%s.dot", inFileNamePrefix).c_str(), std::ofstream::out | std::ofstream::trunc);1633if (!f.is_open())1634return;16351636// Write header1637f << "digraph {\n";16381639// Iterate the entire tree1640Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;1641node_stack.reserve(cStackSize);1642node_stack.push_back(inRoot);1643JPH_ASSERT(inRoot.IsValid());1644do1645{1646// Check if node is a body1647NodeID node_id = node_stack.back();1648node_stack.pop_back();1649if (node_id.IsBody())1650{1651// Output body1652String body_id = ConvertToString(node_id.GetBodyID().GetIndex());1653f << "body" << body_id << "[label = \"Body " << body_id << "\"]\n";1654}1655else1656{1657// Process normal node1658uint32 node_idx = node_id.GetNodeIndex();1659const Node &node = mAllocator->Get(node_idx);16601661// Get bounding box1662AABox bounds;1663node.GetNodeBounds(bounds);16641665// Output node1666String node_str = ConvertToString(node_idx);1667f << "node" << node_str << "[label = \"Node " << node_str << "\nVolume: " << ConvertToString(bounds.GetVolume()) << "\" color=" << (node.mIsChanged? "red" : "black") << "]\n";16681669// Recurse and get all children1670for (NodeID child_node_id : node.mChildNodeID)1671if (child_node_id.IsValid())1672{1673node_stack.push_back(child_node_id);16741675// Output link1676f << "node" << node_str << " -> ";1677if (child_node_id.IsBody())1678f << "body" << ConvertToString(child_node_id.GetBodyID().GetIndex());1679else1680f << "node" << ConvertToString(child_node_id.GetNodeIndex());1681f << "\n";1682}1683}1684}1685while (!node_stack.empty());16861687// Finish DOT file1688f << "}\n";1689f.close();16901691// Convert to svg file1692String cmd = StringFormat("dot %s.dot -Tsvg -o %s.svg", inFileNamePrefix, inFileNamePrefix);1693system(cmd.c_str());1694}16951696#endif // JPH_DUMP_BROADPHASE_TREE16971698#ifdef JPH_TRACK_BROADPHASE_STATS16991700uint64 QuadTree::GetTicks100Pct(const LayerToStats &inLayer) const1701{1702uint64 total_ticks = 0;1703for (const LayerToStats::value_type &kv : inLayer)1704total_ticks += kv.second.mTotalTicks;1705return total_ticks;1706}17071708void QuadTree::ReportStats(const char *inName, const LayerToStats &inLayer, uint64 inTicks100Pct) const1709{1710for (const LayerToStats::value_type &kv : inLayer)1711{1712double total_pct = 100.0 * double(kv.second.mTotalTicks) / double(inTicks100Pct);1713double total_pct_excl_collector = 100.0 * double(kv.second.mTotalTicks - kv.second.mCollectorTicks) / double(inTicks100Pct);1714double hits_reported_vs_bodies_visited = kv.second.mBodiesVisited > 0? 100.0 * double(kv.second.mHitsReported) / double(kv.second.mBodiesVisited) : 100.0;1715double hits_reported_vs_nodes_visited = kv.second.mNodesVisited > 0? double(kv.second.mHitsReported) / double(kv.second.mNodesVisited) : -1.0;17161717std::stringstream str;1718str << 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;1719Trace(str.str().c_str());1720}1721}17221723uint64 QuadTree::GetTicks100Pct() const1724{1725uint64 total_ticks = 0;1726total_ticks += GetTicks100Pct(mCastRayStats);1727total_ticks += GetTicks100Pct(mCollideAABoxStats);1728total_ticks += GetTicks100Pct(mCollideSphereStats);1729total_ticks += GetTicks100Pct(mCollidePointStats);1730total_ticks += GetTicks100Pct(mCollideOrientedBoxStats);1731total_ticks += GetTicks100Pct(mCastAABoxStats);1732return total_ticks;1733}17341735void QuadTree::ReportStats(uint64 inTicks100Pct) const1736{1737unique_lock lock(mStatsMutex);1738ReportStats("RayCast", mCastRayStats, inTicks100Pct);1739ReportStats("CollideAABox", mCollideAABoxStats, inTicks100Pct);1740ReportStats("CollideSphere", mCollideSphereStats, inTicks100Pct);1741ReportStats("CollidePoint", mCollidePointStats, inTicks100Pct);1742ReportStats("CollideOrientedBox", mCollideOrientedBoxStats, inTicks100Pct);1743ReportStats("CastAABox", mCastAABoxStats, inTicks100Pct);1744}17451746#endif // JPH_TRACK_BROADPHASE_STATS17471748uint QuadTree::GetMaxTreeDepth(const NodeID &inNodeID) const1749{1750// Reached a leaf?1751if (!inNodeID.IsValid() || inNodeID.IsBody())1752return 0;17531754// Recurse to children1755uint max_depth = 0;1756const Node &node = mAllocator->Get(inNodeID.GetNodeIndex());1757for (NodeID child_node_id : node.mChildNodeID)1758max_depth = max(max_depth, GetMaxTreeDepth(child_node_id));1759return max_depth + 1;1760}17611762JPH_NAMESPACE_END176317641765