Revision control

++ // Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
++ // Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
++ // SPDX-FileCopyrightText: Copyright (c) 2008-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
++ // SPDX-License-Identifier: Apache-2.0
++
++ #include "GuQuerySystem.h"
++ #include "GuBounds.h"
++ #include "GuBVH.h"
++ #include "foundation/PxAlloca.h"
++ #include "common/PxProfileZone.h"
++
++ using namespace physx;
++ using namespace Gu;
++
++ ///////////////////////////////////////////////////////////////////////////////
++
++ bool contains(PxArray<PxU32>& pruners, PxU32 index)
++ {
++ const PxU32 nb = pruners.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ if(pruners[i]==index)
++ return true;
++ }
++ return false;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////////
++
++ QuerySystem::PrunerExt::PrunerExt(Pruner* pruner, PxU32 preallocated) : mPruner(pruner), mDirtyList("QuerySystem::PrunerExt::mDirtyList"), mNbStatic(0), mNbDynamic(0), mDirtyStatic(false)
++ {
++ if(pruner&& preallocated)
++ pruner->preallocate(preallocated);
++ }
++
++ QuerySystem::PrunerExt::~PrunerExt()
++ {
++ PX_DELETE(mPruner);
++ }
++
++ void QuerySystem::PrunerExt::flushMemory()
++ {
++ if(!mDirtyList.size())
++ mDirtyList.reset();
++
++ // PT: TODO: flush bitmap here
++
++ // PT: TODO: flush pruner here?
++ }
++
++ // PT: ok things became more complicated than before here. We'd like to delay the update of *both* the transform and the bounds,
++ // since immediately updating only one of them doesn't make much sense (it invalidates the pruner's data structure anyway). When both
++ // are delayed it gives users the ability to query the pruners *without* commiting the changes, i.e. they can query the old snapshot
++ // for as long as they please (i.e. a raycast wouldn't automatically trigger a structure update).
++ //
++ // Now the problem is that we need to store (at least) the transform until the update actually happens, and the initial code didn't
++ // support this. We also want to do this in an efficient way, which of course makes things more difficult.
++ //
++ // A naive version would simply use a per-pruner hashmap between the PrunerHandle and its data. Might be slower than before.
++ //
++ // Another version could build on the initial bitmap-based solution and use arrays of transforms/bounds as companions to the array
++ // of PrunerHandle (or we could mix all that data in a single structure). The issue with this is that two consecutive updates on the
++ // same object wouldn't work anymore: the second call would check the bitmap, see that the bit is set already, and skip the work.
++ // We'd need to update the cached data instead, i.e. we'd need a mapping between the PrunerHandle and its position in mDirtyList.
++ // And we don't have that.
++ //
++ // A potential way to fix this could be to allow the same PrunerHandle to appear multiple times in mDirtyList, with the assumption
++ // that users will not update the same object multiple times very often (...). The way it would work:
++ // - during "add", dirtyMap is set, handle/transform/bounds are pushed to mDirtyList.
++ // - during "remove", dirtyMap is reset *and that's it*. We don't bother purging mDirtyList (i.e. we kill the current O(n) search there)
++ // - during "process" we use dirtyMap to validate the update. If bit is cleared, ignore mDirtyList entry. Duplicate entries work as long
++ // as mDirtyList is processed in linear order. One issue is that the current mDirtyList is also passed to the pruner as-is for the
++ // update, so we'd need to rebuild a separate array for that and/or make sure all pruners accept duplicate entries in that array.
++ // Deep down that specific rabbit hole we'll actually find the recently discovered issue regarding the mToRefit array...
++ //
++ // Bit tricky. This is only for user-updates anyway (as opposed to sim updates) so this probably doesn't need ultimate perf? Note however
++ // that we "remove from dirty list" when an object is removed, which happens all the time with or without user updates (e.g. streaming etc).
++
++ static const bool gUseOldCode = false;
++
++ void QuerySystem::PrunerExt::addToDirtyList(PrunerHandle handle, PxU32 dynamic, const PxTransform& transform, const PxBounds3* userBounds)
++ {
++ PxBitMap& dirtyMap = mDirtyMap;
++ {
++ if(dirtyMap.size() <= handle)
++ {
++ PxU32 size = PxMax<PxU32>(dirtyMap.size()*2, 1024);
++ const PxU32 minSize = handle+1;
++ if(minSize>size)
++ size = minSize*2;
++ dirtyMap.resize(size);
++ PX_ASSERT(handle<dirtyMap.size());
++ PX_ASSERT(!dirtyMap.test(handle));
++ }
++ }
++
++ if(gUseOldCode)
++ {
++ if(!dirtyMap.test(handle))
++ {
++ dirtyMap.set(handle);
++ mDirtyList.pushBack(handle);
++ }
++ }
++ else
++ {
++ dirtyMap.set(handle);
++ mDirtyList.pushBack(handle);
++
++ Data& d = *mDirtyData.insert();
++ d.mPose = transform;
++ if(userBounds)
++ d.mBounds = *userBounds;
++ else
++ d.mBounds.setEmpty();
++ }
++
++ if(!dynamic)
++ mDirtyStatic = true;
++ }
++
++ void QuerySystem::PrunerExt::removeFromDirtyList(PrunerHandle handle)
++ {
++ PxBitMap& dirtyMap = mDirtyMap;
++ if(gUseOldCode)
++ {
++ if(dirtyMap.boundedTest(handle))
++ {
++ dirtyMap.reset(handle);
++ mDirtyList.findAndReplaceWithLast(handle);
++ }
++ }
++ else
++ {
++ dirtyMap.boundedReset(handle);
++ }
++
++ // PT: if we remove the object that made us set mDirtyStatic to true, tough luck,
++ // we don't bother fixing that bool here. It's going to potentially cause an
++ // unnecessary update of the character controller's caches, which is not a big deal.
++ }
++
++ bool QuerySystem::PrunerExt::processDirtyList(const Adapter& adapter, float inflation)
++ {
++ const PxU32 numDirtyList = mDirtyList.size();
++ if(!numDirtyList)
++ return false;
++
++ if(gUseOldCode)
++ {
++ const PrunerHandle* const prunerHandles = mDirtyList.begin();
++ for(PxU32 i=0; i<numDirtyList; i++)
++ {
++ const PrunerHandle handle = prunerHandles[i];
++ mDirtyMap.reset(handle);
++
++ // PT: we compute the new bounds and store them directly in the pruner structure to avoid copies. We delay the updateObjects() call
++ // to take advantage of batching.
++ PrunerPayloadData payloadData;
++ const PrunerPayload& pp = mPruner->getPayloadData(handle, &payloadData);
++
++ computeBounds(*payloadData.mBounds, adapter.getGeometry(pp), *payloadData.mTransform, 0.0f, inflation);
++ }
++ // PT: batch update happens after the loop instead of once per loop iteration
++ mPruner->updateObjects(prunerHandles, numDirtyList);
++ mDirtyList.clear();
++ }
++ else
++ {
++ // PT: TODO: this stuff is not 100% satisfying, since we do allow the same object to be updated multiple times.
++ // Would be nice to revisit & improve at some point.
++
++ PrunerHandle* prunerHandles = mDirtyList.begin();
++
++ PxU32 nbValid = 0;
++ for(PxU32 i=0; i<numDirtyList; i++)
++ {
++ const PrunerHandle handle = prunerHandles[i];
++ if(mDirtyMap.test(handle))
++ {
++ // PT: we compute the new bounds and store them directly in the pruner structure to avoid copies. We delay the updateObjects() call
++ // to take advantage of batching.
++ PrunerPayloadData payloadData;
++ const PrunerPayload& pp = mPruner->getPayloadData(handle, &payloadData);
++
++ *payloadData.mTransform = mDirtyData[i].mPose;
++
++ if(mDirtyData[i].mBounds.isEmpty())
++ computeBounds(*payloadData.mBounds, adapter.getGeometry(pp), mDirtyData[i].mPose, 0.0f, inflation);
++ else
++ *payloadData.mBounds = mDirtyData[i].mBounds;
++
++ prunerHandles[nbValid++] = handle;
++ }
++ else
++ {
++ // PT: if not set, object has been added to the list then removed
++ }
++ }
++
++ // PT: batch update happens after the loop instead of once per loop iteration
++ mPruner->updateObjects(prunerHandles, nbValid);
++
++ // PT: have to reset the bits *after* the above loop now. Unclear if clearing the
++ // whole map would be faster ("it depends" I guess).
++ while(nbValid--)
++ {
++ const PrunerHandle handle = *prunerHandles++;
++ mDirtyMap.reset(handle);
++ }
++
++ mDirtyList.clear();
++ mDirtyData.clear();
++ }
++
++ const bool ret = mDirtyStatic;
++ mDirtyStatic = false;
++ return ret;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////////
++
++ static PX_FORCE_INLINE QuerySystem::PrunerExt* checkPrunerIndex(PxU32 prunerIndex, const PxArray<QuerySystem::PrunerExt*>& prunerExt)
++ {
++ if(prunerIndex>=prunerExt.size() || !prunerExt[prunerIndex])
++ {
++ PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
++ return NULL;
++ }
++
++ return prunerExt[prunerIndex];
++ }
++
++ QuerySystem::QuerySystem(PxU64 contextID, float inflation, const Adapter& adapter, bool usesTreeOfPruners) :
++ mAdapter (adapter),
++ mTreeOfPruners (NULL),
++ mContextID (contextID),
++ mStaticTimestamp (0),
++ mInflation (inflation),
++ mPrunerNeedsUpdating (false),
++ mTimestampNeedsUpdating (false),
++ mUsesTreeOfPruners (usesTreeOfPruners)
++ //mBatchUserUpdates (batchUserUpdates)
++ {
++ }
++
++ QuerySystem::~QuerySystem()
++ {
++ PX_DELETE(mTreeOfPruners);
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ PX_DELETE(pe);
++ }
++ }
++
++ PxU32 QuerySystem::addPruner(Pruner* pruner, PxU32 preallocated)
++ {
++ PrunerExt* pe = PX_NEW(PrunerExt)(pruner, preallocated);
++
++ PxU32 prunerIndex;
++ if(mFreePruners.size())
++ {
++ prunerIndex = mFreePruners.popBack();
++ mPrunerExt[prunerIndex] = pe;
++ }
++ else
++ {
++ prunerIndex = mPrunerExt.size();
++ mPrunerExt.pushBack(pe);
++ }
++ return prunerIndex;
++ }
++
++ void QuerySystem::removePruner(PxU32 prunerIndex)
++ {
++ PrunerExt* pe = checkPrunerIndex(prunerIndex, mPrunerExt);
++ if(!pe)
++ return;
++
++ // PT: it is legal to delete a pruner that still contains objects, but we should still properly update the static timestamp.
++ if(pe->mNbStatic)
++ invalidateStaticTimestamp();
++
++ PX_DELETE(pe);
++ mPrunerExt[prunerIndex] = NULL;
++
++ mFreePruners.pushBack(prunerIndex);
++
++ // We don't bother searching mDirtyPruners since it's going to be cleared next frame
++ }
++
++ void QuerySystem::flushMemory()
++ {
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(pe)
++ pe->flushMemory();
++ }
++ }
++
++ ActorShapeData QuerySystem::addPrunerShape(const PrunerPayload& payload, PxU32 prunerIndex, bool dynamic, const PxTransform& transform, const PxBounds3* userBounds)
++ {
++ PrunerExt* pe = checkPrunerIndex(prunerIndex, mPrunerExt);
++ if(!pe)
++ return INVALID_ACTOR_SHAPE_DATA;
++
++ mPrunerNeedsUpdating = true;
++
++ if(dynamic)
++ {
++ pe->mNbDynamic++;
++ }
++ else
++ {
++ pe->mNbStatic++;
++ invalidateStaticTimestamp();
++ }
++
++ PX_ASSERT(pe->mPruner);
++
++ const PxBounds3* boundsPtr;
++ PxBounds3 bounds;
++ if(userBounds)
++ {
++ boundsPtr = userBounds;
++ }
++ else
++ {
++ computeBounds(bounds, mAdapter.getGeometry(payload), transform, 0.0f, 1.0f + mInflation);
++ boundsPtr = &bounds;
++ }
++
++ PrunerHandle handle;
++ pe->mPruner->addObjects(&handle, boundsPtr, &payload, &transform, 1, false);
++
++ return createActorShapeData(createPrunerInfo(prunerIndex, dynamic), handle);
++ }
++
++ void QuerySystem::removePrunerShape(ActorShapeData data, PrunerPayloadRemovalCallback* removalCallback)
++ {
++ const PrunerInfo info = getPrunerInfo(data);
++ const PxU32 prunerIndex = getPrunerIndex(info);
++
++ PrunerExt* pe = checkPrunerIndex(prunerIndex, mPrunerExt);
++ if(!pe)
++ return;
++
++ mPrunerNeedsUpdating = true;
++ const PxU32 dynamic = getDynamic(info);
++
++ const PrunerHandle handle = getPrunerHandle(data);
++
++ PX_ASSERT(pe->mPruner);
++
++ if(dynamic)
++ {
++ PX_ASSERT(pe->mNbDynamic);
++ pe->mNbDynamic--;
++ }
++ else
++ {
++ PX_ASSERT(pe->mNbStatic);
++ pe->mNbStatic--;
++ invalidateStaticTimestamp();
++ }
++
++ //if(mBatchUserUpdates)
++ pe->removeFromDirtyList(handle);
++ pe->mPruner->removeObjects(&handle, 1, removalCallback);
++ }
++
++ void QuerySystem::updatePrunerShape(ActorShapeData data, bool immediately, const PxTransform& transform, const PxBounds3* userBounds)
++ {
++ const PrunerInfo info = getPrunerInfo(data);
++ const PxU32 prunerIndex = getPrunerIndex(info);
++
++ PrunerExt* pe = checkPrunerIndex(prunerIndex, mPrunerExt);
++ if(!pe)
++ return;
++
++ mPrunerNeedsUpdating = true;
++ const PxU32 dynamic = getDynamic(info);
++ const PrunerHandle handle = getPrunerHandle(data);
++
++ PX_ASSERT(pe->mPruner);
++
++ Pruner* pruner = pe->mPruner;
++
++ if(immediately)
++ {
++ if(!dynamic)
++ invalidateStaticTimestamp();
++
++ PrunerPayloadData payloadData;
++ const PrunerPayload& pp = pruner->getPayloadData(handle, &payloadData);
++
++ *payloadData.mTransform = transform;
++
++ if(userBounds)
++ *payloadData.mBounds = *userBounds;
++ else
++ computeBounds(*payloadData.mBounds, mAdapter.getGeometry(pp), transform, 0.0f, 1.0f + mInflation);
++
++ // PT: TODO: would it be better to pass the bounds & transform directly to this function?
++ pruner->updateObjects(&handle, 1);
++ }
++ else
++ {
++ // PT: we don't update the static timestamp immediately, so that users can query the
++ // old state of the structure without invalidating their caches. This will be resolved
++ // in processDirtyLists.
++
++ if(gUseOldCode)
++ pruner->setTransform(handle, transform);
++
++ // PT: we don't shrink mDirtyList anymore in removePrunerShape so the size of that array can be reused as
++ // a flag telling us whether we already encountered this pruner or not. If not, we add its index to mDirtyPruners.
++ // Goal is to avoid processing all pruners in processDirtyLists.
++ if(!pe->mDirtyList.size())
++ {
++ PX_ASSERT(!contains(mDirtyPruners, prunerIndex));
++ mDirtyPruners.pushBack(prunerIndex);
++ }
++ else
++ {
++ PX_ASSERT(contains(mDirtyPruners, prunerIndex));
++ }
++
++ pe->addToDirtyList(handle, dynamic, transform, userBounds);
++ }
++ }
++
++ const PrunerPayload& QuerySystem::getPayloadData(ActorShapeData data, PrunerPayloadData* ppd) const
++ {
++ const PrunerInfo info = getPrunerInfo(data);
++ const PxU32 prunerIndex = getPrunerIndex(info);
++
++ PX_ASSERT(checkPrunerIndex(prunerIndex, mPrunerExt));
++
++ const PrunerHandle handle = getPrunerHandle(data);
++
++ PX_ASSERT(mPrunerExt[prunerIndex]->mPruner);
++ return mPrunerExt[prunerIndex]->mPruner->getPayloadData(handle, ppd);
++ }
++
++ void QuerySystem::processDirtyLists()
++ {
++ PX_PROFILE_ZONE("QuerySystem.processDirtyLists", mContextID);
++
++ const PxU32 nbDirtyPruners = mDirtyPruners.size();
++ if(!nbDirtyPruners)
++ return;
++
++ // must already have acquired writer lock here
++
++ const float inflation = 1.0f + mInflation;
++
++ bool mustInvalidateStaticTimestamp = false;
++ for(PxU32 ii=0;ii<nbDirtyPruners;ii++)
++ {
++ const PxU32 i = mDirtyPruners[ii];
++
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(pe && pe->processDirtyList(mAdapter, inflation))
++ mustInvalidateStaticTimestamp = true;
++ }
++
++ if(mustInvalidateStaticTimestamp)
++ invalidateStaticTimestamp();
++
++ mDirtyPruners.clear();
++ }
++
++ void QuerySystem::update(bool buildStep, bool commit)
++ {
++ PX_PROFILE_ZONE("QuerySystem::update", mContextID);
++
++ if(!buildStep && !commit)
++ {
++ //mPrunerNeedsUpdating = true; // PT: removed, why was it here?
++ return;
++ }
++
++ // flush user modified objects
++ // if(mBatchUserUpdates)
++ processDirtyLists();
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(!pe)
++ continue;
++
++ Pruner* pruner = pe->mPruner;
++ if(pruner)
++ {
++ if(buildStep && pruner->isDynamic())
++ static_cast<DynamicPruner*>(pruner)->buildStep(true);
++
++ if(commit)
++ pruner->commit();
++ }
++ }
++
++ if(commit)
++ {
++ if(mUsesTreeOfPruners)
++ createTreeOfPruners();
++ }
++
++ mPrunerNeedsUpdating = !commit;
++ }
++
++ void QuerySystem::commitUpdates()
++ {
++ PX_PROFILE_ZONE("QuerySystem.commitUpdates", mContextID);
++
++ if(mPrunerNeedsUpdating)
++ {
++ mSQLock.lock();
++
++ if(mPrunerNeedsUpdating)
++ {
++ //if(mBatchUserUpdates)
++ processDirtyLists();
++
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(!pe)
++ continue;
++
++ Pruner* pruner = pe->mPruner;
++ if(pruner)
++ pruner->commit();
++ }
++
++ if(mUsesTreeOfPruners)
++ createTreeOfPruners();
++
++ PxMemoryBarrier();
++ mPrunerNeedsUpdating = false;
++ }
++ mSQLock.unlock();
++ }
++ }
++
++ PxU32 QuerySystem::startCustomBuildstep()
++ {
++ PX_PROFILE_ZONE("QuerySystem.startCustomBuildstep", mContextID);
++
++ mTimestampNeedsUpdating = false;
++
++ return mPrunerExt.size();
++ }
++
++ void QuerySystem::customBuildstep(PxU32 index)
++ {
++ PX_PROFILE_ZONE("QuerySystem.customBuildstep", mContextID);
++
++ PX_ASSERT(index<mPrunerExt.size());
++
++ // PT: TODO: would be better to not schedule the update of removed pruners at all
++ PrunerExt* pe = mPrunerExt[index]; // Can be NULL if the pruner has been removed
++ if(!pe)
++ return;
++
++ Pruner* pruner = pe->mPruner;
++
++ //void QuerySystem::processDirtyLists()
++ {
++ PX_PROFILE_ZONE("QuerySystem.processDirtyLists", mContextID);
++ // must already have acquired writer lock here
++ const float inflation = 1.0f + mInflation;
++ // PT: note that we don't use the mDirtyPruners array here
++ if(pe->processDirtyList(mAdapter, inflation))
++ mTimestampNeedsUpdating = true;
++ }
++
++ if(pruner)
++ {
++ if(pruner->isDynamic())
++ static_cast<DynamicPruner*>(pruner)->buildStep(true); // PT: "true" because that parameter was made for PxSceneQuerySystem::sceneQueryBuildStep(), not us
++
++ pruner->commit();
++ }
++ }
++
++ void QuerySystem::finishCustomBuildstep()
++ {
++ PX_PROFILE_ZONE("QuerySystem.finishCustomBuildstep", mContextID);
++
++ if(mUsesTreeOfPruners)
++ createTreeOfPruners();
++
++ mPrunerNeedsUpdating = false;
++ if(mTimestampNeedsUpdating)
++ invalidateStaticTimestamp();
++
++ mDirtyPruners.clear();
++ }
++
++ void QuerySystem::sync(PxU32 prunerIndex, const PrunerHandle* handles, const PxU32* boundsIndices, const PxBounds3* bounds, const PxTransform32* transforms, PxU32 count)
++ {
++ if(!count)
++ return;
++
++ PrunerExt* pe = checkPrunerIndex(prunerIndex, mPrunerExt);
++ if(!pe)
++ return;
++
++ Pruner* pruner = pe->mPruner;
++ if(pruner)
++ pruner->updateObjects(handles, count, mInflation, boundsIndices, bounds, transforms);
++ }
++
++ ///////////////////////////////////////////////////////////////////////////////
++
++ namespace
++ {
++ struct LocalRaycastCB : PxBVH::RaycastCallback
++ {
++ LocalRaycastCB(const PxArray<QuerySystem::PrunerExt*>& pruners, const PrunerFilter* prunerFilter, const PxVec3& origin, const PxVec3& unitDir, PrunerRaycastCallback& cb) :
++ mPrunerExt(pruners), mPrunerFilter(prunerFilter), mOrigin(origin), mUnitDir(unitDir), mCB(cb) {}
++
++ virtual bool reportHit(PxU32 boundsIndex, PxReal& distance) PX_OVERRIDE
++ {
++ QuerySystem::PrunerExt* pe = mPrunerExt[boundsIndex]; // Can be NULL if the pruner has been removed
++ if(pe && (!mPrunerFilter || mPrunerFilter->processPruner(boundsIndex)))
++ {
++ Pruner* pruner = pe->mPruner;
++ if(!pruner->raycast(mOrigin, mUnitDir, distance, mCB))
++ return false;
++ }
++ return true;
++ }
++
++ const PxArray<QuerySystem::PrunerExt*>& mPrunerExt;
++ const PrunerFilter* mPrunerFilter;
++ const PxVec3& mOrigin;
++ const PxVec3& mUnitDir;
++ PrunerRaycastCallback& mCB;
++
++ PX_NOCOPY(LocalRaycastCB)
++ };
++
++ struct LocalOverlapCB : PxBVH::OverlapCallback
++ {
++ LocalOverlapCB(const PxArray<QuerySystem::PrunerExt*>& pruners, const PrunerFilter* prunerFilter, const ShapeData& queryVolume, PrunerOverlapCallback& cb) :
++ mPrunerExt(pruners), mPrunerFilter(prunerFilter), mQueryVolume(queryVolume), mCB(cb) {}
++
++ virtual bool reportHit(PxU32 boundsIndex) PX_OVERRIDE
++ {
++ QuerySystem::PrunerExt* pe = mPrunerExt[boundsIndex]; // Can be NULL if the pruner has been removed
++ if(pe && (!mPrunerFilter || mPrunerFilter->processPruner(boundsIndex)))
++ {
++ Pruner* pruner = pe->mPruner;
++ if(!pruner->overlap(mQueryVolume, mCB))
++ return false;
++ }
++ return true;
++ }
++
++ const PxArray<QuerySystem::PrunerExt*>& mPrunerExt;
++ const PrunerFilter* mPrunerFilter;
++ const ShapeData& mQueryVolume;
++ PrunerOverlapCallback& mCB;
++
++ PX_NOCOPY(LocalOverlapCB)
++ };
++
++ struct LocalSweepCB : PxBVH::RaycastCallback
++ {
++ LocalSweepCB(const PxArray<QuerySystem::PrunerExt*>& pruners, const PrunerFilter* prunerFilter, const ShapeData& queryVolume, const PxVec3& unitDir, PrunerRaycastCallback& cb) :
++ mPrunerExt(pruners), mPrunerFilter(prunerFilter), mQueryVolume(queryVolume), mUnitDir(unitDir), mCB(cb) {}
++
++ virtual bool reportHit(PxU32 boundsIndex, PxReal& distance) PX_OVERRIDE
++ {
++ QuerySystem::PrunerExt* pe = mPrunerExt[boundsIndex]; // Can be NULL if the pruner has been removed
++ if(pe && (!mPrunerFilter || mPrunerFilter->processPruner(boundsIndex)))
++ {
++ Pruner* pruner = pe->mPruner;
++ if(!pruner->sweep(mQueryVolume, mUnitDir, distance, mCB))
++ return false;
++ }
++ return true;
++ }
++
++ const PxArray<QuerySystem::PrunerExt*>& mPrunerExt;
++ const PrunerFilter* mPrunerFilter;
++ const ShapeData& mQueryVolume;
++ const PxVec3& mUnitDir;
++ PrunerRaycastCallback& mCB;
++
++ PX_NOCOPY(LocalSweepCB)
++ };
++ }
++
++ void QuerySystem::raycast(const PxVec3& origin, const PxVec3& unitDir, float& inOutDistance, PrunerRaycastCallback& cb, const PrunerFilter* prunerFilter) const
++ {
++ if(mTreeOfPruners)
++ {
++ LocalRaycastCB localCB(mPrunerExt, prunerFilter, origin, unitDir, cb);
++ mTreeOfPruners->raycast(origin, unitDir, inOutDistance, localCB, PxGeometryQueryFlag::Enum(0));
++ }
++ else
++ {
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(!pe)
++ continue;
++
++ if(!prunerFilter || prunerFilter->processPruner(i))
++ {
++ Pruner* pruner = pe->mPruner;
++ if(!pruner->raycast(origin, unitDir, inOutDistance, cb))
++ return;
++ }
++ }
++ }
++ }
++
++ void QuerySystem::overlap(const ShapeData& queryVolume, PrunerOverlapCallback& cb, const PrunerFilter* prunerFilter) const
++ {
++ if(mTreeOfPruners)
++ {
++ LocalOverlapCB localCB(mPrunerExt, prunerFilter, queryVolume, cb);
++ mTreeOfPruners->overlap(queryVolume, localCB, PxGeometryQueryFlag::Enum(0));
++ }
++ else
++ {
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(!pe)
++ continue;
++
++ if(!prunerFilter || prunerFilter->processPruner(i))
++ {
++ Pruner* pruner = pe->mPruner;
++ if(!pruner->overlap(queryVolume, cb))
++ return;
++ }
++ }
++ }
++ }
++
++ void QuerySystem::sweep(const ShapeData& queryVolume, const PxVec3& unitDir, float& inOutDistance, PrunerRaycastCallback& cb, const PrunerFilter* prunerFilter) const
++ {
++ if(mTreeOfPruners)
++ {
++ LocalSweepCB localCB(mPrunerExt, prunerFilter, queryVolume, unitDir, cb);
++ mTreeOfPruners->sweep(queryVolume, unitDir, inOutDistance, localCB, PxGeometryQueryFlag::Enum(0));
++ }
++ else
++ {
++ const PxU32 nb = mPrunerExt.size();
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i]; // Can be NULL if the pruner has been removed
++ if(!pe)
++ continue;
++
++ if(!prunerFilter || prunerFilter->processPruner(i))
++ {
++ Pruner* pruner = pe->mPruner;
++ if(!pruner->sweep(queryVolume, unitDir, inOutDistance, cb))
++ return;
++ }
++ }
++ }
++ }
++
++ void QuerySystem::createTreeOfPruners()
++ {
++ PX_PROFILE_ZONE("QuerySystem.createTreeOfPruners", mContextID);
++
++ PX_DELETE(mTreeOfPruners);
++
++ mTreeOfPruners = PX_NEW(BVH)(NULL);
++
++ const PxU32 nb = mPrunerExt.size();
++
++ PxBounds3* prunerBounds = reinterpret_cast<PxBounds3*>(PxAlloca(sizeof(PxBounds3)*(nb+1)));
++
++ PxU32 nbBounds = 0;
++ for(PxU32 i=0;i<nb;i++)
++ {
++ PrunerExt* pe = mPrunerExt[i];
++ Pruner* pruner = pe->mPruner;
++ if(pruner)
++ pruner->getGlobalBounds(prunerBounds[nbBounds++]);
++ }
++
++ mTreeOfPruners->init(nbBounds, NULL, prunerBounds, sizeof(PxBounds3), BVH_SPLATTER_POINTS, 1, 0.01f);
++ }