Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp
21520 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 number of nodes that is allocated is related to the max number of bodies that is passed in PhysicsSystem::Init.228// For normal situations there are plenty of nodes available. If all else fails, you can increase the number of nodes229// by increasing the maximum number of bodies.230Trace("QuadTree: Out of nodes!");231std::abort();232}233return index;234}235236void QuadTree::Init(Allocator &inAllocator)237{238// Store allocator239mAllocator = &inAllocator;240241// Allocate root node242mRootNode[mRootNodeIndex].mIndex = AllocateNode(false);243}244245void QuadTree::DiscardOldTree()246{247// Check if there is an old tree248RootNode &old_root_node = mRootNode[mRootNodeIndex ^ 1];249if (old_root_node.mIndex != cInvalidNodeIndex)250{251// Clear the root252old_root_node.mIndex = cInvalidNodeIndex;253254// Now free all old nodes255mAllocator->DestructObjectBatch(mFreeNodeBatch);256257// Clear the batch258mFreeNodeBatch = Allocator::Batch();259}260}261262AABox QuadTree::GetBounds() const263{264uint32 node_idx = GetCurrentRoot().mIndex;265JPH_ASSERT(node_idx != cInvalidNodeIndex);266const Node &node = mAllocator->Get(node_idx);267268AABox bounds;269node.GetNodeBounds(bounds);270return bounds;271}272273void QuadTree::UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTracking, UpdateState &outUpdateState, bool inFullRebuild)274{275#ifdef JPH_ENABLE_ASSERTS276// We only read positions277BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);278#endif279280// Assert we have no nodes pending deletion, this means DiscardOldTree wasn't called yet281JPH_ASSERT(mFreeNodeBatch.mNumObjects == 0);282283// Mark tree non-dirty284mIsDirty = false;285286// Get the current root node287const RootNode &root_node = GetCurrentRoot();288289#ifdef JPH_DUMP_BROADPHASE_TREE290DumpTree(root_node.GetNodeID(), StringFormat("%s_PRE", mName).c_str());291#endif292293// Assert sane data294#ifdef JPH_DEBUG295ValidateTree(inBodies, ioTracking, root_node.mIndex, mNumBodies);296#endif297298// Create space for all body ID's299NodeID *node_ids = mNumBodies > 0? new NodeID [mNumBodies] : nullptr;300NodeID *cur_node_id = node_ids;301302// Collect all bodies303Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;304node_stack.reserve(cStackSize);305node_stack.push_back(root_node.GetNodeID());306JPH_ASSERT(node_stack.front().IsValid());307do308{309// Pop node from stack310NodeID node_id = node_stack.back();311node_stack.pop_back();312313// Check if node is a body314if (node_id.IsBody())315{316// Validate that we're still in the right layer317#ifdef JPH_ENABLE_ASSERTS318uint32 body_index = node_id.GetBodyID().GetIndex();319JPH_ASSERT(ioTracking[body_index].mObjectLayer == inBodies[body_index]->GetObjectLayer());320#endif321322// Store body323*cur_node_id = node_id;324++cur_node_id;325}326else327{328// Process normal node329uint32 node_idx = node_id.GetNodeIndex();330const Node &node = mAllocator->Get(node_idx);331332if (!node.mIsChanged && !inFullRebuild)333{334// Node is unchanged, treat it as a whole335*cur_node_id = node_id;336++cur_node_id;337}338else339{340// Node is changed, recurse and get all children341for (NodeID child_node_id : node.mChildNodeID)342if (child_node_id.IsValid())343node_stack.push_back(child_node_id);344345// Mark node to be freed346mAllocator->AddObjectToBatch(mFreeNodeBatch, node_idx);347}348}349}350while (!node_stack.empty());351352// Check that our book keeping matches353uint32 num_node_ids = uint32(cur_node_id - node_ids);354JPH_ASSERT(inFullRebuild? num_node_ids == mNumBodies : num_node_ids <= mNumBodies);355356// This will be the new root node id357NodeID root_node_id;358359if (num_node_ids > 0)360{361// We mark the first 5 levels (max 1024 nodes) of the newly built tree as 'changed' so that362// those nodes get recreated every time when we rebuild the tree. This balances the amount of363// time we spend on rebuilding the tree ('unchanged' nodes will be put in the new tree as a whole)364// vs the quality of the built tree.365constexpr uint cMaxDepthMarkChanged = 5;366367// Build new tree368AABox root_bounds;369root_node_id = BuildTree(inBodies, ioTracking, node_ids, num_node_ids, cMaxDepthMarkChanged, root_bounds);370371if (root_node_id.IsBody())372{373// For a single body we need to allocate a new root node374uint32 root_idx = AllocateNode(false);375Node &root = mAllocator->Get(root_idx);376root.SetChildBounds(0, root_bounds);377root.mChildNodeID[0] = root_node_id;378SetBodyLocation(ioTracking, root_node_id.GetBodyID(), root_idx, 0);379root_node_id = NodeID::sFromNodeIndex(root_idx);380}381}382else383{384// Empty tree, create root node385uint32 root_idx = AllocateNode(false);386root_node_id = NodeID::sFromNodeIndex(root_idx);387}388389// Delete temporary data390delete [] node_ids;391392outUpdateState.mRootNodeID = root_node_id;393}394395void QuadTree::UpdateFinalize([[maybe_unused]] const BodyVector &inBodies, [[maybe_unused]] const TrackingVector &inTracking, const UpdateState &inUpdateState)396{397// Tree building is complete, now we switch the old with the new tree398uint32 new_root_idx = mRootNodeIndex ^ 1;399RootNode &new_root_node = mRootNode[new_root_idx];400{401// Note: We don't need to lock here as the old tree stays available so any queries402// that use it can continue using it until DiscardOldTree is called. This slot403// should be empty and unused at this moment.404JPH_ASSERT(new_root_node.mIndex == cInvalidNodeIndex);405new_root_node.mIndex = inUpdateState.mRootNodeID.GetNodeIndex();406}407408// All queries that start from now on will use this new tree409mRootNodeIndex = new_root_idx;410411#ifdef JPH_DUMP_BROADPHASE_TREE412DumpTree(new_root_node.GetNodeID(), StringFormat("%s_POST", mName).c_str());413#endif414415#ifdef JPH_DEBUG416ValidateTree(inBodies, inTracking, new_root_node.mIndex, mNumBodies);417#endif418}419420void QuadTree::sPartition(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inNumber, int &outMidPoint)421{422// Handle trivial case423if (inNumber <= 4)424{425outMidPoint = inNumber / 2;426return;427}428429// Calculate bounding box of box centers430Vec3 center_min = Vec3::sReplicate(cLargeFloat);431Vec3 center_max = Vec3::sReplicate(-cLargeFloat);432for (const Vec3 *c = ioNodeCenters, *c_end = ioNodeCenters + inNumber; c < c_end; ++c)433{434Vec3 center = *c;435center_min = Vec3::sMin(center_min, center);436center_max = Vec3::sMax(center_max, center);437}438439// Calculate split plane440int dimension = (center_max - center_min).GetHighestComponentIndex();441float split = 0.5f * (center_min + center_max)[dimension];442443// Divide bodies444int start = 0, end = inNumber;445while (start < end)446{447// Search for first element that is on the right hand side of the split plane448while (start < end && ioNodeCenters[start][dimension] < split)449++start;450451// Search for the first element that is on the left hand side of the split plane452while (start < end && ioNodeCenters[end - 1][dimension] >= split)453--end;454455if (start < end)456{457// Swap the two elements458std::swap(ioNodeIDs[start], ioNodeIDs[end - 1]);459std::swap(ioNodeCenters[start], ioNodeCenters[end - 1]);460++start;461--end;462}463}464JPH_ASSERT(start == end);465466if (start > 0 && start < inNumber)467{468// Success!469outMidPoint = start;470}471else472{473// Failed to divide bodies474outMidPoint = inNumber / 2;475}476}477478void QuadTree::sPartition4(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inBegin, int inEnd, int *outSplit)479{480NodeID *node_ids = ioNodeIDs + inBegin;481Vec3 *node_centers = ioNodeCenters + inBegin;482int number = inEnd - inBegin;483484// Partition entire range485sPartition(node_ids, node_centers, number, outSplit[2]);486487// Partition lower half488sPartition(node_ids, node_centers, outSplit[2], outSplit[1]);489490// Partition upper half491sPartition(node_ids + outSplit[2], node_centers + outSplit[2], number - outSplit[2], outSplit[3]);492493// Convert to proper range494outSplit[0] = inBegin;495outSplit[1] += inBegin;496outSplit[2] += inBegin;497outSplit[3] += outSplit[2];498outSplit[4] = inEnd;499}500501AABox QuadTree::GetNodeOrBodyBounds(const BodyVector &inBodies, NodeID inNodeID) const502{503if (inNodeID.IsNode())504{505// It is a node506uint32 node_idx = inNodeID.GetNodeIndex();507const Node &node = mAllocator->Get(node_idx);508509AABox bounds;510node.GetNodeBounds(bounds);511return bounds;512}513else514{515// It is a body516return inBodies[inNodeID.GetBodyID().GetIndex()]->GetWorldSpaceBounds();517}518}519520QuadTree::NodeID QuadTree::BuildTree(const BodyVector &inBodies, TrackingVector &ioTracking, NodeID *ioNodeIDs, int inNumber, uint inMaxDepthMarkChanged, AABox &outBounds)521{522// Trivial case: No bodies in tree523if (inNumber == 0)524{525outBounds = cInvalidBounds;526return NodeID::sInvalid();527}528529// Trivial case: When we have 1 body or node, return it530if (inNumber == 1)531{532if (ioNodeIDs->IsNode())533{534// When returning an existing node as root, ensure that no parent has been set535Node &node = mAllocator->Get(ioNodeIDs->GetNodeIndex());536node.mParentNodeIndex = cInvalidNodeIndex;537}538outBounds = GetNodeOrBodyBounds(inBodies, *ioNodeIDs);539return *ioNodeIDs;540}541542// Calculate centers of all bodies that are to be inserted543Vec3 *centers = new Vec3 [inNumber];544JPH_ASSERT(IsAligned(centers, JPH_VECTOR_ALIGNMENT));545Vec3 *c = centers;546for (const NodeID *n = ioNodeIDs, *n_end = ioNodeIDs + inNumber; n < n_end; ++n, ++c)547*c = GetNodeOrBodyBounds(inBodies, *n).GetCenter();548549// The algorithm is a recursive tree build, but to avoid the call overhead we keep track of a stack here550struct StackEntry551{552uint32 mNodeIdx; // Node index of node that is generated553int mChildIdx; // Index of child that we're currently processing554int mSplit[5]; // Indices where the node ID's have been split to form 4 partitions555uint32 mDepth; // Depth of this node in the tree556Vec3 mNodeBoundsMin; // Bounding box of this node, accumulated while iterating over children557Vec3 mNodeBoundsMax;558};559static_assert(sizeof(StackEntry) == 64);560StackEntry stack[cStackSize / 4]; // We don't process 4 at a time in this loop but 1, so the stack can be 4x as small561int top = 0;562563// Create root node564stack[0].mNodeIdx = AllocateNode(inMaxDepthMarkChanged > 0);565stack[0].mChildIdx = -1;566stack[0].mDepth = 0;567stack[0].mNodeBoundsMin = Vec3::sReplicate(cLargeFloat);568stack[0].mNodeBoundsMax = Vec3::sReplicate(-cLargeFloat);569sPartition4(ioNodeIDs, centers, 0, inNumber, stack[0].mSplit);570571for (;;)572{573StackEntry &cur_stack = stack[top];574575// Next child576cur_stack.mChildIdx++;577578// Check if all children processed579if (cur_stack.mChildIdx >= 4)580{581// Terminate if there's nothing left to pop582if (top <= 0)583break;584585// Add our bounds to our parents bounds586StackEntry &prev_stack = stack[top - 1];587prev_stack.mNodeBoundsMin = Vec3::sMin(prev_stack.mNodeBoundsMin, cur_stack.mNodeBoundsMin);588prev_stack.mNodeBoundsMax = Vec3::sMax(prev_stack.mNodeBoundsMax, cur_stack.mNodeBoundsMax);589590// Store parent node591Node &node = mAllocator->Get(cur_stack.mNodeIdx);592node.mParentNodeIndex = prev_stack.mNodeIdx;593594// Store this node's properties in the parent node595Node &parent_node = mAllocator->Get(prev_stack.mNodeIdx);596parent_node.mChildNodeID[prev_stack.mChildIdx] = NodeID::sFromNodeIndex(cur_stack.mNodeIdx);597parent_node.SetChildBounds(prev_stack.mChildIdx, AABox(cur_stack.mNodeBoundsMin, cur_stack.mNodeBoundsMax));598599// Pop entry from stack600--top;601}602else603{604// Get low and high index to bodies to process605int low = cur_stack.mSplit[cur_stack.mChildIdx];606int high = cur_stack.mSplit[cur_stack.mChildIdx + 1];607int num_bodies = high - low;608609if (num_bodies == 1)610{611// Get body info612NodeID child_node_id = ioNodeIDs[low];613AABox bounds = GetNodeOrBodyBounds(inBodies, child_node_id);614615// Update node616Node &node = mAllocator->Get(cur_stack.mNodeIdx);617node.mChildNodeID[cur_stack.mChildIdx] = child_node_id;618node.SetChildBounds(cur_stack.mChildIdx, bounds);619620if (child_node_id.IsNode())621{622// Update parent for this node623Node &child_node = mAllocator->Get(child_node_id.GetNodeIndex());624child_node.mParentNodeIndex = cur_stack.mNodeIdx;625}626else627{628// Set location in tracking629SetBodyLocation(ioTracking, child_node_id.GetBodyID(), cur_stack.mNodeIdx, cur_stack.mChildIdx);630}631632// Encapsulate bounding box in parent633cur_stack.mNodeBoundsMin = Vec3::sMin(cur_stack.mNodeBoundsMin, bounds.mMin);634cur_stack.mNodeBoundsMax = Vec3::sMax(cur_stack.mNodeBoundsMax, bounds.mMax);635}636else if (num_bodies > 1)637{638// Allocate new node639StackEntry &new_stack = stack[++top];640JPH_ASSERT(top < cStackSize / 4);641uint32 next_depth = cur_stack.mDepth + 1;642new_stack.mNodeIdx = AllocateNode(inMaxDepthMarkChanged > next_depth);643new_stack.mChildIdx = -1;644new_stack.mDepth = next_depth;645new_stack.mNodeBoundsMin = Vec3::sReplicate(cLargeFloat);646new_stack.mNodeBoundsMax = Vec3::sReplicate(-cLargeFloat);647sPartition4(ioNodeIDs, centers, low, high, new_stack.mSplit);648}649}650}651652// Delete temporary data653delete [] centers;654655// Store bounding box of root656outBounds.mMin = stack[0].mNodeBoundsMin;657outBounds.mMax = stack[0].mNodeBoundsMax;658659// Return root660return NodeID::sFromNodeIndex(stack[0].mNodeIdx);661}662663void QuadTree::MarkNodeAndParentsChanged(uint32 inNodeIndex)664{665uint32 node_idx = inNodeIndex;666667do668{669// If node has changed, parent will be too670Node &node = mAllocator->Get(node_idx);671if (node.mIsChanged)672break;673674// Mark node as changed675node.mIsChanged = true;676677// Get our parent678node_idx = node.mParentNodeIndex;679}680while (node_idx != cInvalidNodeIndex);681}682683void QuadTree::WidenAndMarkNodeAndParentsChanged(uint32 inNodeIndex, const AABox &inNewBounds)684{685uint32 node_idx = inNodeIndex;686687for (;;)688{689// Mark node as changed690Node &node = mAllocator->Get(node_idx);691node.mIsChanged = true;692693// Get our parent694uint32 parent_idx = node.mParentNodeIndex;695if (parent_idx == cInvalidNodeIndex)696break;697698// Find which child of the parent we're in699Node &parent_node = mAllocator->Get(parent_idx);700NodeID node_id = NodeID::sFromNodeIndex(node_idx);701int child_idx = -1;702for (int i = 0; i < 4; ++i)703if (parent_node.mChildNodeID[i] == node_id)704{705// Found one, set the node index and child index and update the bounding box too706child_idx = i;707break;708}709JPH_ASSERT(child_idx != -1, "Nodes don't get removed from the tree, we must have found it");710711// To avoid any race conditions with other threads we only enlarge bounding boxes712if (!parent_node.EncapsulateChildBounds(child_idx, inNewBounds))713{714// No changes to bounding box, only marking as changed remains to be done715if (!parent_node.mIsChanged)716MarkNodeAndParentsChanged(parent_idx);717break;718}719720// Update node index721node_idx = parent_idx;722}723}724725bool QuadTree::TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)726{727// Tentatively assign the node as parent728bool leaf_is_node = inLeafID.IsNode();729if (leaf_is_node)730{731uint32 leaf_idx = inLeafID.GetNodeIndex();732mAllocator->Get(leaf_idx).mParentNodeIndex = inNodeIndex;733}734735// Fetch node that we're adding to736Node &node = mAllocator->Get(inNodeIndex);737738// Find an empty child739for (uint32 child_idx = 0; child_idx < 4; ++child_idx)740if (node.mChildNodeID[child_idx].CompareExchange(NodeID::sInvalid(), inLeafID)) // Check if we can claim it741{742// We managed to add it to the node743744// If leaf was a body, we need to update its bookkeeping745if (!leaf_is_node)746SetBodyLocation(ioTracking, inLeafID.GetBodyID(), inNodeIndex, child_idx);747748// Now set the bounding box making the child valid for queries749node.SetChildBounds(child_idx, inLeafBounds);750751// Widen the bounds for our parents too752WidenAndMarkNodeAndParentsChanged(inNodeIndex, inLeafBounds);753754// Update body counter755mNumBodies += inLeafNumBodies;756757// And we're done758return true;759}760761return false;762}763764bool QuadTree::TryCreateNewRoot(TrackingVector &ioTracking, atomic<uint32> &ioRootNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)765{766// Fetch old root767uint32 root_idx = ioRootNodeIndex;768Node &root = mAllocator->Get(root_idx);769770// Create new root, mark this new root as changed as we're not creating a very efficient tree at this point771uint32 new_root_idx = AllocateNode(true);772Node &new_root = mAllocator->Get(new_root_idx);773774// 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 box775new_root.mChildNodeID[0] = NodeID::sFromNodeIndex(root_idx);776new_root.SetChildBounds(0, AABox(Vec3::sReplicate(-cLargeFloat), Vec3::sReplicate(cLargeFloat)));777778// Second child is new leaf779new_root.mChildNodeID[1] = inLeafID;780new_root.SetChildBounds(1, inLeafBounds);781782// Tentatively assign new root as parent783bool leaf_is_node = inLeafID.IsNode();784if (leaf_is_node)785{786uint32 leaf_idx = inLeafID.GetNodeIndex();787mAllocator->Get(leaf_idx).mParentNodeIndex = new_root_idx;788}789790// Try to swap it791if (ioRootNodeIndex.compare_exchange_strong(root_idx, new_root_idx))792{793// We managed to set the new root794795// If leaf was a body, we need to update its bookkeeping796if (!leaf_is_node)797SetBodyLocation(ioTracking, inLeafID.GetBodyID(), new_root_idx, 1);798799// Store parent node for old root800root.mParentNodeIndex = new_root_idx;801802// Update body counter803mNumBodies += inLeafNumBodies;804805// And we're done806return true;807}808809// Failed to swap, someone else must have created a new root, try again810mAllocator->DestructObject(new_root_idx);811return false;812}813814void QuadTree::AddBodiesPrepare(const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber, AddState &outState)815{816// Assert sane input817JPH_ASSERT(ioBodyIDs != nullptr);818JPH_ASSERT(inNumber > 0);819820#ifdef JPH_ENABLE_ASSERTS821// Below we just cast the body ID's to node ID's, check here that that is valid822for (const BodyID *b = ioBodyIDs, *b_end = ioBodyIDs + inNumber; b < b_end; ++b)823NodeID::sFromBodyID(*b);824#endif825826// Build subtree for the new bodies, note that we mark all nodes as 'not changed'827// so they will stay together as a batch and will make the tree rebuild cheaper828outState.mLeafID = BuildTree(inBodies, ioTracking, (NodeID *)ioBodyIDs, inNumber, 0, outState.mLeafBounds);829830#ifdef JPH_DEBUG831if (outState.mLeafID.IsNode())832ValidateTree(inBodies, ioTracking, outState.mLeafID.GetNodeIndex(), inNumber);833#endif834}835836void QuadTree::AddBodiesFinalize(TrackingVector &ioTracking, int inNumberBodies, const AddState &inState)837{838// Assert sane input839JPH_ASSERT(inNumberBodies > 0);840841// Mark tree dirty842mIsDirty = true;843844// Get the current root node845RootNode &root_node = GetCurrentRoot();846847for (;;)848{849// Check if we can insert the body in the root850if (TryInsertLeaf(ioTracking, root_node.mIndex, inState.mLeafID, inState.mLeafBounds, inNumberBodies))851return;852853// Check if we can create a new root854if (TryCreateNewRoot(ioTracking, root_node.mIndex, inState.mLeafID, inState.mLeafBounds, inNumberBodies))855return;856}857}858859void QuadTree::AddBodiesAbort(TrackingVector &ioTracking, const AddState &inState)860{861// Collect all bodies862Allocator::Batch free_batch;863NodeID node_stack[cStackSize];864node_stack[0] = inState.mLeafID;865JPH_ASSERT(node_stack[0].IsValid());866int top = 0;867do868{869// Check if node is a body870NodeID child_node_id = node_stack[top];871if (child_node_id.IsBody())872{873// Reset location of body874sInvalidateBodyLocation(ioTracking, child_node_id.GetBodyID());875}876else877{878// Process normal node879uint32 node_idx = child_node_id.GetNodeIndex();880const Node &node = mAllocator->Get(node_idx);881for (NodeID sub_child_node_id : node.mChildNodeID)882if (sub_child_node_id.IsValid())883{884JPH_ASSERT(top < cStackSize);885node_stack[top] = sub_child_node_id;886top++;887}888889// Mark it to be freed890mAllocator->AddObjectToBatch(free_batch, node_idx);891}892--top;893}894while (top >= 0);895896// Now free all nodes as a single batch897mAllocator->DestructObjectBatch(free_batch);898}899900void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber)901{902// Assert sane input903JPH_ASSERT(ioBodyIDs != nullptr);904JPH_ASSERT(inNumber > 0);905906// Mark tree dirty907mIsDirty = true;908909for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)910{911// Check if BodyID is correct912JPH_ASSERT(inBodies[cur->GetIndex()]->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");913914// Get location of body915uint32 node_idx, child_idx;916GetBodyLocation(ioTracking, *cur, node_idx, child_idx);917918// First we reset our internal bookkeeping919sInvalidateBodyLocation(ioTracking, *cur);920921// Then we make the bounding box invalid, no queries can find this node anymore922Node &node = mAllocator->Get(node_idx);923node.InvalidateChildBounds(child_idx);924925// Finally we reset the child id, this makes the node available for adds again926node.mChildNodeID[child_idx] = NodeID::sInvalid();927928// We don't need to bubble up our bounding box changes to our parents since we never make volumes smaller, only bigger929// But we do need to mark the nodes as changed so that the tree can be rebuilt930MarkNodeAndParentsChanged(node_idx);931}932933mNumBodies -= inNumber;934}935936void QuadTree::NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber)937{938// Assert sane input939JPH_ASSERT(ioBodyIDs != nullptr);940JPH_ASSERT(inNumber > 0);941942for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)943{944// Check if BodyID is correct945const Body *body = inBodies[cur->GetIndex()];946JPH_ASSERT(body->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");947948// Get the new bounding box949const AABox &new_bounds = body->GetWorldSpaceBounds();950951// Get location of body952uint32 node_idx, child_idx;953GetBodyLocation(inTracking, *cur, node_idx, child_idx);954955// Widen bounds for node956Node &node = mAllocator->Get(node_idx);957if (node.EncapsulateChildBounds(child_idx, new_bounds))958{959// Mark tree dirty960mIsDirty = true;961962// If bounds changed, widen the bounds for our parents too963WidenAndMarkNodeAndParentsChanged(node_idx, new_bounds);964}965}966}967968template <class Visitor>969JPH_INLINE void QuadTree::WalkTree(const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking, Visitor &ioVisitor JPH_IF_TRACK_BROADPHASE_STATS(, LayerToStats &ioStats)) const970{971// Get the root972const RootNode &root_node = GetCurrentRoot();973974#ifdef JPH_TRACK_BROADPHASE_STATS975// Start tracking stats976int bodies_visited = 0;977int hits_collected = 0;978int nodes_visited = 0;979uint64 collector_ticks = 0;980981uint64 start = GetProcessorTickCount();982#endif // JPH_TRACK_BROADPHASE_STATS983984Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack_array;985node_stack_array.resize(cStackSize);986NodeID *node_stack = node_stack_array.data();987node_stack[0] = root_node.GetNodeID();988int top = 0;989do990{991// Check if node is a body992NodeID child_node_id = node_stack[top];993if (child_node_id.IsBody())994{995// Track amount of bodies visited996JPH_IF_TRACK_BROADPHASE_STATS(++bodies_visited;)997998BodyID body_id = child_node_id.GetBodyID();999ObjectLayer 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 invalid1000if (object_layer != cObjectLayerInvalid && inObjectLayerFilter.ShouldCollide(object_layer))1001{1002JPH_PROFILE("VisitBody");10031004// Track amount of hits1005JPH_IF_TRACK_BROADPHASE_STATS(++hits_collected;)10061007// Start track time the collector takes1008JPH_IF_TRACK_BROADPHASE_STATS(uint64 collector_start = GetProcessorTickCount();)10091010// We found a body we collide with, call our visitor1011ioVisitor.VisitBody(body_id, top);10121013// End track time the collector takes1014JPH_IF_TRACK_BROADPHASE_STATS(collector_ticks += GetProcessorTickCount() - collector_start;)10151016// Check if we're done1017if (ioVisitor.ShouldAbort())1018break;1019}1020}1021else if (child_node_id.IsValid())1022{1023JPH_IF_TRACK_BROADPHASE_STATS(++nodes_visited;)10241025// Ensure there is space on the stack (falls back to heap if there isn't)1026if (top + 4 >= (int)node_stack_array.size())1027{1028sQuadTreePerformanceWarning();1029node_stack_array.resize(node_stack_array.size() << 1);1030node_stack = node_stack_array.data();1031ioVisitor.OnStackResized(node_stack_array.size());1032}10331034// Process normal node1035const Node &node = mAllocator->Get(child_node_id.GetNodeIndex());1036JPH_ASSERT(IsAligned(&node, JPH_CACHE_LINE_SIZE));10371038// Load bounds of 4 children1039Vec4 bounds_minx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinX);1040Vec4 bounds_miny = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinY);1041Vec4 bounds_minz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinZ);1042Vec4 bounds_maxx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxX);1043Vec4 bounds_maxy = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxY);1044Vec4 bounds_maxz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxZ);10451046// Load ids for 4 children1047UVec4 child_ids = UVec4::sLoadInt4Aligned((const uint32 *)&node.mChildNodeID[0]);10481049// Check which sub nodes to visit1050int num_results = ioVisitor.VisitNodes(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, child_ids, top);1051child_ids.StoreInt4((uint32 *)&node_stack[top]);1052top += num_results;1053}10541055// Fetch next node until we find one that the visitor wants to see1056do1057--top;1058while (top >= 0 && !ioVisitor.ShouldVisitNode(top));1059}1060while (top >= 0);10611062#ifdef JPH_TRACK_BROADPHASE_STATS1063// Calculate total time the broadphase walk took1064uint64 total_ticks = GetProcessorTickCount() - start;10651066// Update stats under lock protection (slow!)1067{1068unique_lock lock(mStatsMutex);1069Stat &s = ioStats[inObjectLayerFilter.GetDescription()];1070s.mNumQueries++;1071s.mNodesVisited += nodes_visited;1072s.mBodiesVisited += bodies_visited;1073s.mHitsReported += hits_collected;1074s.mTotalTicks += total_ticks;1075s.mCollectorTicks += collector_ticks;1076}1077#endif // JPH_TRACK_BROADPHASE_STATS1078}10791080void QuadTree::CastRay(const RayCast &inRay, RayCastBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1081{1082class Visitor1083{1084public:1085/// Constructor1086JPH_INLINE Visitor(const RayCast &inRay, RayCastBodyCollector &ioCollector) :1087mOrigin(inRay.mOrigin),1088mInvDirection(inRay.mDirection),1089mCollector(ioCollector)1090{1091mFractionStack.resize(cStackSize);1092mFractionStack[0] = -1;1093}10941095/// Returns true if further processing of the tree should be aborted1096JPH_INLINE bool ShouldAbort() const1097{1098return mCollector.ShouldEarlyOut();1099}11001101/// Returns true if this node / body should be visited, false if no hit can be generated1102JPH_INLINE bool ShouldVisitNode(int inStackTop) const1103{1104return mFractionStack[inStackTop] < mCollector.GetEarlyOutFraction();1105}11061107/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1108JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop)1109{1110// Test the ray against 4 bounding boxes1111Vec4 fraction = RayAABox4(mOrigin, mInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);11121113// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)1114return SortReverseAndStore(fraction, mCollector.GetEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]);1115}11161117/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1118JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1119{1120// Store potential hit with body1121BroadPhaseCastResult result { inBodyID, mFractionStack[inStackTop] };1122mCollector.AddHit(result);1123}11241125/// Called when the stack is resized, this allows us to resize the fraction stack to match the new stack size1126JPH_INLINE void OnStackResized(size_t inNewStackSize)1127{1128mFractionStack.resize(inNewStackSize);1129}11301131private:1132Vec3 mOrigin;1133RayInvDirection mInvDirection;1134RayCastBodyCollector & mCollector;1135Array<float, STLLocalAllocator<float, cStackSize>> mFractionStack;1136};11371138Visitor visitor(inRay, ioCollector);1139WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCastRayStats));1140}11411142void QuadTree::CollideAABox(const AABox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1143{1144class Visitor1145{1146public:1147/// Constructor1148JPH_INLINE Visitor(const AABox &inBox, CollideShapeBodyCollector &ioCollector) :1149mBox(inBox),1150mCollector(ioCollector)1151{1152}11531154/// Returns true if further processing of the tree should be aborted1155JPH_INLINE bool ShouldAbort() const1156{1157return mCollector.ShouldEarlyOut();1158}11591160/// Returns true if this node / body should be visited, false if no hit can be generated1161JPH_INLINE bool ShouldVisitNode(int inStackTop) const1162{1163return true;1164}11651166/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1167JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1168{1169// Test the box vs 4 boxes1170UVec4 hitting = AABox4VsBox(mBox, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1171return CountAndSortTrues(hitting, ioChildNodeIDs);1172}11731174/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1175JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1176{1177// Store potential hit with body1178mCollector.AddHit(inBodyID);1179}11801181/// Called when the stack is resized1182JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1183{1184// Nothing to do1185}11861187private:1188const AABox & mBox;1189CollideShapeBodyCollector & mCollector;1190};11911192Visitor visitor(inBox, ioCollector);1193WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideAABoxStats));1194}11951196void QuadTree::CollideSphere(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1197{1198class Visitor1199{1200public:1201/// Constructor1202JPH_INLINE Visitor(Vec3Arg inCenter, float inRadius, CollideShapeBodyCollector &ioCollector) :1203mCenterX(inCenter.SplatX()),1204mCenterY(inCenter.SplatY()),1205mCenterZ(inCenter.SplatZ()),1206mRadiusSq(Vec4::sReplicate(Square(inRadius))),1207mCollector(ioCollector)1208{1209}12101211/// Returns true if further processing of the tree should be aborted1212JPH_INLINE bool ShouldAbort() const1213{1214return mCollector.ShouldEarlyOut();1215}12161217/// Returns true if this node / body should be visited, false if no hit can be generated1218JPH_INLINE bool ShouldVisitNode(int inStackTop) const1219{1220return true;1221}12221223/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1224JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1225{1226// Test 4 boxes vs sphere1227UVec4 hitting = AABox4VsSphere(mCenterX, mCenterY, mCenterZ, mRadiusSq, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1228return CountAndSortTrues(hitting, ioChildNodeIDs);1229}12301231/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1232JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1233{1234// Store potential hit with body1235mCollector.AddHit(inBodyID);1236}12371238/// Called when the stack is resized1239JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1240{1241// Nothing to do1242}12431244private:1245Vec4 mCenterX;1246Vec4 mCenterY;1247Vec4 mCenterZ;1248Vec4 mRadiusSq;1249CollideShapeBodyCollector & mCollector;1250};12511252Visitor visitor(inCenter, inRadius, ioCollector);1253WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideSphereStats));1254}12551256void QuadTree::CollidePoint(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1257{1258class Visitor1259{1260public:1261/// Constructor1262JPH_INLINE Visitor(Vec3Arg inPoint, CollideShapeBodyCollector &ioCollector) :1263mPoint(inPoint),1264mCollector(ioCollector)1265{1266}12671268/// Returns true if further processing of the tree should be aborted1269JPH_INLINE bool ShouldAbort() const1270{1271return mCollector.ShouldEarlyOut();1272}12731274/// Returns true if this node / body should be visited, false if no hit can be generated1275JPH_INLINE bool ShouldVisitNode(int inStackTop) const1276{1277return true;1278}12791280/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1281JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1282{1283// Test if point overlaps with box1284UVec4 hitting = AABox4VsPoint(mPoint, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1285return CountAndSortTrues(hitting, ioChildNodeIDs);1286}12871288/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1289JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1290{1291// Store potential hit with body1292mCollector.AddHit(inBodyID);1293}12941295/// Called when the stack is resized1296JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1297{1298// Nothing to do1299}13001301private:1302Vec3 mPoint;1303CollideShapeBodyCollector & mCollector;1304};13051306Visitor visitor(inPoint, ioCollector);1307WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollidePointStats));1308}13091310void QuadTree::CollideOrientedBox(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1311{1312class Visitor1313{1314public:1315/// Constructor1316JPH_INLINE Visitor(const OrientedBox &inBox, CollideShapeBodyCollector &ioCollector) :1317mBox(inBox),1318mCollector(ioCollector)1319{1320}13211322/// Returns true if further processing of the tree should be aborted1323JPH_INLINE bool ShouldAbort() const1324{1325return mCollector.ShouldEarlyOut();1326}13271328/// Returns true if this node / body should be visited, false if no hit can be generated1329JPH_INLINE bool ShouldVisitNode(int inStackTop) const1330{1331return true;1332}13331334/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1335JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop) const1336{1337// Test if point overlaps with box1338UVec4 hitting = AABox4VsBox(mBox, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);1339return CountAndSortTrues(hitting, ioChildNodeIDs);1340}13411342/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1343JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1344{1345// Store potential hit with body1346mCollector.AddHit(inBodyID);1347}13481349/// Called when the stack is resized1350JPH_INLINE void OnStackResized([[maybe_unused]] size_t inNewStackSize) const1351{1352// Nothing to do1353}13541355private:1356OrientedBox mBox;1357CollideShapeBodyCollector & mCollector;1358};13591360Visitor visitor(inBox, ioCollector);1361WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCollideOrientedBoxStats));1362}13631364void QuadTree::CastAABox(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const1365{1366class Visitor1367{1368public:1369/// Constructor1370JPH_INLINE Visitor(const AABoxCast &inBox, CastShapeBodyCollector &ioCollector) :1371mOrigin(inBox.mBox.GetCenter()),1372mExtent(inBox.mBox.GetExtent()),1373mInvDirection(inBox.mDirection),1374mCollector(ioCollector)1375{1376mFractionStack.resize(cStackSize);1377mFractionStack[0] = -1;1378}13791380/// Returns true if further processing of the tree should be aborted1381JPH_INLINE bool ShouldAbort() const1382{1383return mCollector.ShouldEarlyOut();1384}13851386/// Returns true if this node / body should be visited, false if no hit can be generated1387JPH_INLINE bool ShouldVisitNode(int inStackTop) const1388{1389return mFractionStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();1390}13911392/// Visit nodes, returns number of hits found and sorts ioChildNodeIDs so that they are at the beginning of the vector.1393JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioChildNodeIDs, int inStackTop)1394{1395// Enlarge them by the casted aabox extents1396Vec4 bounds_min_x = inBoundsMinX, bounds_min_y = inBoundsMinY, bounds_min_z = inBoundsMinZ, bounds_max_x = inBoundsMaxX, bounds_max_y = inBoundsMaxY, bounds_max_z = inBoundsMaxZ;1397AABox4EnlargeWithExtent(mExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);13981399// Test 4 children1400Vec4 fraction = RayAABox4(mOrigin, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);14011402// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)1403return SortReverseAndStore(fraction, mCollector.GetPositiveEarlyOutFraction(), ioChildNodeIDs, &mFractionStack[inStackTop]);1404}14051406/// Visit a body, returns false if the algorithm should terminate because no hits can be generated anymore1407JPH_INLINE void VisitBody(const BodyID &inBodyID, int inStackTop)1408{1409// Store potential hit with body1410BroadPhaseCastResult result { inBodyID, mFractionStack[inStackTop] };1411mCollector.AddHit(result);1412}14131414/// Called when the stack is resized, this allows us to resize the fraction stack to match the new stack size1415JPH_INLINE void OnStackResized(size_t inNewStackSize)1416{1417mFractionStack.resize(inNewStackSize);1418}14191420private:1421Vec3 mOrigin;1422Vec3 mExtent;1423RayInvDirection mInvDirection;1424CastShapeBodyCollector & mCollector;1425Array<float, STLLocalAllocator<float, cStackSize>> mFractionStack;1426};14271428Visitor visitor(inBox, ioCollector);1429WalkTree(inObjectLayerFilter, inTracking, visitor JPH_IF_TRACK_BROADPHASE_STATS(, mCastAABoxStats));1430}14311432void QuadTree::FindCollidingPairs(const BodyVector &inBodies, const BodyID *inActiveBodies, int inNumActiveBodies, float inSpeculativeContactDistance, BodyPairCollector &ioPairCollector, const ObjectLayerPairFilter &inObjectLayerPairFilter) const1433{1434// 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.1435// We double check this at the end of the function.1436const RootNode &root_node = GetCurrentRoot();1437JPH_ASSERT(root_node.mIndex != cInvalidNodeIndex);14381439// Assert sane input1440JPH_ASSERT(inActiveBodies != nullptr);1441JPH_ASSERT(inNumActiveBodies > 0);14421443Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack_array;1444node_stack_array.resize(cStackSize);1445NodeID *node_stack = node_stack_array.data();14461447// Loop over all active bodies1448for (int b1 = 0; b1 < inNumActiveBodies; ++b1)1449{1450BodyID b1_id = inActiveBodies[b1];1451const Body &body1 = *inBodies[b1_id.GetIndex()];1452JPH_ASSERT(!body1.IsStatic());14531454// Expand the bounding box by the speculative contact distance1455AABox bounds1 = body1.GetWorldSpaceBounds();1456bounds1.ExpandBy(Vec3::sReplicate(inSpeculativeContactDistance));14571458// Test each body with the tree1459node_stack[0] = root_node.GetNodeID();1460int top = 0;1461do1462{1463// Check if node is a body1464NodeID child_node_id = node_stack[top];1465if (child_node_id.IsBody())1466{1467// Don't collide with self1468BodyID b2_id = child_node_id.GetBodyID();1469if (b1_id != b2_id)1470{1471// Collision between dynamic pairs need to be picked up only once1472const Body &body2 = *inBodies[b2_id.GetIndex()];1473if (inObjectLayerPairFilter.ShouldCollide(body1.GetObjectLayer(), body2.GetObjectLayer())1474&& Body::sFindCollidingPairsCanCollide(body1, body2)1475&& 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 overlap1476{1477// Store potential hit between bodies1478ioPairCollector.AddHit({ b1_id, b2_id });1479}1480}1481}1482else if (child_node_id.IsValid())1483{1484// Process normal node1485const Node &node = mAllocator->Get(child_node_id.GetNodeIndex());1486JPH_ASSERT(IsAligned(&node, JPH_CACHE_LINE_SIZE));14871488// Get bounds of 4 children1489Vec4 bounds_minx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinX);1490Vec4 bounds_miny = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinY);1491Vec4 bounds_minz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMinZ);1492Vec4 bounds_maxx = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxX);1493Vec4 bounds_maxy = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxY);1494Vec4 bounds_maxz = Vec4::sLoadFloat4Aligned((const Float4 *)&node.mBoundsMaxZ);14951496// Test overlap1497UVec4 overlap = AABox4VsBox(bounds1, bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz);1498int num_results = overlap.CountTrues();1499if (num_results > 0)1500{1501// Load ids for 4 children1502UVec4 child_ids = UVec4::sLoadInt4Aligned((const uint32 *)&node.mChildNodeID[0]);15031504// Sort so that overlaps are first1505child_ids = UVec4::sSort4True(overlap, child_ids);15061507// Ensure there is space on the stack (falls back to heap if there isn't)1508if (top + 4 >= (int)node_stack_array.size())1509{1510sQuadTreePerformanceWarning();1511node_stack_array.resize(node_stack_array.size() << 1);1512node_stack = node_stack_array.data();1513}15141515// Push them onto the stack1516child_ids.StoreInt4((uint32 *)&node_stack[top]);1517top += num_results;1518}1519}1520--top;1521}1522while (top >= 0);1523}15241525// Test that the root node was not swapped while finding collision pairs.1526// This would mean that UpdateFinalize/DiscardOldTree ran during collision detection which should not be possible due to the way the jobs are scheduled.1527JPH_ASSERT(root_node.mIndex != cInvalidNodeIndex);1528JPH_ASSERT(&root_node == &GetCurrentRoot());1529}15301531#ifdef JPH_DEBUG15321533void QuadTree::ValidateTree(const BodyVector &inBodies, const TrackingVector &inTracking, uint32 inNodeIndex, uint32 inNumExpectedBodies) const1534{1535JPH_PROFILE_FUNCTION();15361537// Root should be valid1538JPH_ASSERT(inNodeIndex != cInvalidNodeIndex);15391540// To avoid call overhead, create a stack in place1541JPH_SUPPRESS_WARNING_PUSH1542JPH_CLANG_SUPPRESS_WARNING("-Wunused-member-function") // The default constructor of StackEntry is unused when using Jolt's Array class but not when using std::vector1543struct StackEntry1544{1545StackEntry() = default;1546inline StackEntry(uint32 inNodeIndex, uint32 inParentNodeIndex) : mNodeIndex(inNodeIndex), mParentNodeIndex(inParentNodeIndex) { }15471548uint32 mNodeIndex;1549uint32 mParentNodeIndex;1550};1551JPH_SUPPRESS_WARNING_POP1552Array<StackEntry, STLLocalAllocator<StackEntry, cStackSize>> stack;1553stack.reserve(cStackSize);1554stack.emplace_back(inNodeIndex, cInvalidNodeIndex);15551556uint32 num_bodies = 0;15571558do1559{1560// Copy entry from the stack1561StackEntry cur_stack = stack.back();1562stack.pop_back();15631564// Validate parent1565const Node &node = mAllocator->Get(cur_stack.mNodeIndex);1566JPH_ASSERT(node.mParentNodeIndex == cur_stack.mParentNodeIndex);15671568// Validate that when a parent is not-changed that all of its children are also1569JPH_ASSERT(cur_stack.mParentNodeIndex == cInvalidNodeIndex || mAllocator->Get(cur_stack.mParentNodeIndex).mIsChanged || !node.mIsChanged);15701571// Loop children1572for (uint32 i = 0; i < 4; ++i)1573{1574NodeID child_node_id = node.mChildNodeID[i];1575if (child_node_id.IsValid())1576{1577if (child_node_id.IsNode())1578{1579// Child is a node, recurse1580uint32 child_idx = child_node_id.GetNodeIndex();1581stack.emplace_back(child_idx, cur_stack.mNodeIndex);15821583// Validate that the bounding box is bigger or equal to the bounds in the tree1584// Bounding box could also be invalid if all children of our child were removed1585AABox child_bounds;1586node.GetChildBounds(i, child_bounds);1587AABox real_child_bounds;1588mAllocator->Get(child_idx).GetNodeBounds(real_child_bounds);1589JPH_ASSERT(child_bounds.Contains(real_child_bounds) || !real_child_bounds.IsValid());1590}1591else1592{1593// Increment number of bodies found1594++num_bodies;15951596// Check if tracker matches position of body1597uint32 node_idx, child_idx;1598GetBodyLocation(inTracking, child_node_id.GetBodyID(), node_idx, child_idx);1599JPH_ASSERT(node_idx == cur_stack.mNodeIndex);1600JPH_ASSERT(child_idx == i);16011602// Validate that the body cached bounds still match the actual bounds1603const Body *body = inBodies[child_node_id.GetBodyID().GetIndex()];1604body->ValidateCachedBounds();16051606// Validate that the node bounds are bigger or equal to the body bounds1607AABox body_bounds;1608node.GetChildBounds(i, body_bounds);1609JPH_ASSERT(body_bounds.Contains(body->GetWorldSpaceBounds()));1610}1611}1612}1613}1614while (!stack.empty());16151616// Check that the amount of bodies in the tree matches our counter1617JPH_ASSERT(num_bodies == inNumExpectedBodies);1618}16191620#endif16211622#ifdef JPH_DUMP_BROADPHASE_TREE16231624void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) const1625{1626// Open DOT file1627std::ofstream f;1628f.open(StringFormat("%s.dot", inFileNamePrefix).c_str(), std::ofstream::out | std::ofstream::trunc);1629if (!f.is_open())1630return;16311632// Write header1633f << "digraph {\n";16341635// Iterate the entire tree1636Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;1637node_stack.reserve(cStackSize);1638node_stack.push_back(inRoot);1639JPH_ASSERT(inRoot.IsValid());1640do1641{1642// Check if node is a body1643NodeID node_id = node_stack.back();1644node_stack.pop_back();1645if (node_id.IsBody())1646{1647// Output body1648String body_id = ConvertToString(node_id.GetBodyID().GetIndex());1649f << "body" << body_id << "[label = \"Body " << body_id << "\"]\n";1650}1651else1652{1653// Process normal node1654uint32 node_idx = node_id.GetNodeIndex();1655const Node &node = mAllocator->Get(node_idx);16561657// Get bounding box1658AABox bounds;1659node.GetNodeBounds(bounds);16601661// Output node1662String node_str = ConvertToString(node_idx);1663f << "node" << node_str << "[label = \"Node " << node_str << "\nVolume: " << ConvertToString(bounds.GetVolume()) << "\" color=" << (node.mIsChanged? "red" : "black") << "]\n";16641665// Recurse and get all children1666for (NodeID child_node_id : node.mChildNodeID)1667if (child_node_id.IsValid())1668{1669node_stack.push_back(child_node_id);16701671// Output link1672f << "node" << node_str << " -> ";1673if (child_node_id.IsBody())1674f << "body" << ConvertToString(child_node_id.GetBodyID().GetIndex());1675else1676f << "node" << ConvertToString(child_node_id.GetNodeIndex());1677f << "\n";1678}1679}1680}1681while (!node_stack.empty());16821683// Finish DOT file1684f << "}\n";1685f.close();16861687// Convert to svg file1688String cmd = StringFormat("dot %s.dot -Tsvg -o %s.svg", inFileNamePrefix, inFileNamePrefix);1689system(cmd.c_str());1690}16911692#endif // JPH_DUMP_BROADPHASE_TREE16931694#ifdef JPH_TRACK_BROADPHASE_STATS16951696uint64 QuadTree::GetTicks100Pct(const LayerToStats &inLayer) const1697{1698uint64 total_ticks = 0;1699for (const LayerToStats::value_type &kv : inLayer)1700total_ticks += kv.second.mTotalTicks;1701return total_ticks;1702}17031704void QuadTree::ReportStats(const char *inName, const LayerToStats &inLayer, uint64 inTicks100Pct) const1705{1706for (const LayerToStats::value_type &kv : inLayer)1707{1708double total_pct = 100.0 * double(kv.second.mTotalTicks) / double(inTicks100Pct);1709double total_pct_excl_collector = 100.0 * double(kv.second.mTotalTicks - kv.second.mCollectorTicks) / double(inTicks100Pct);1710double hits_reported_vs_bodies_visited = kv.second.mBodiesVisited > 0? 100.0 * double(kv.second.mHitsReported) / double(kv.second.mBodiesVisited) : 100.0;1711double hits_reported_vs_nodes_visited = kv.second.mNodesVisited > 0? double(kv.second.mHitsReported) / double(kv.second.mNodesVisited) : -1.0;17121713std::stringstream str;1714str << 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;1715Trace(str.str().c_str());1716}1717}17181719uint64 QuadTree::GetTicks100Pct() const1720{1721uint64 total_ticks = 0;1722total_ticks += GetTicks100Pct(mCastRayStats);1723total_ticks += GetTicks100Pct(mCollideAABoxStats);1724total_ticks += GetTicks100Pct(mCollideSphereStats);1725total_ticks += GetTicks100Pct(mCollidePointStats);1726total_ticks += GetTicks100Pct(mCollideOrientedBoxStats);1727total_ticks += GetTicks100Pct(mCastAABoxStats);1728return total_ticks;1729}17301731void QuadTree::ReportStats(uint64 inTicks100Pct) const1732{1733unique_lock lock(mStatsMutex);1734ReportStats("RayCast", mCastRayStats, inTicks100Pct);1735ReportStats("CollideAABox", mCollideAABoxStats, inTicks100Pct);1736ReportStats("CollideSphere", mCollideSphereStats, inTicks100Pct);1737ReportStats("CollidePoint", mCollidePointStats, inTicks100Pct);1738ReportStats("CollideOrientedBox", mCollideOrientedBoxStats, inTicks100Pct);1739ReportStats("CastAABox", mCastAABoxStats, inTicks100Pct);1740}17411742#endif // JPH_TRACK_BROADPHASE_STATS17431744uint QuadTree::GetMaxTreeDepth(const NodeID &inNodeID) const1745{1746// Reached a leaf?1747if (!inNodeID.IsValid() || inNodeID.IsBody())1748return 0;17491750// Recurse to children1751uint max_depth = 0;1752const Node &node = mAllocator->Get(inNodeID.GetNodeIndex());1753for (NodeID child_node_id : node.mChildNodeID)1754max_depth = max(max_depth, GetMaxTreeDepth(child_node_id));1755return max_depth + 1;1756}17571758JPH_NAMESPACE_END175917601761