Files
XCEngine/engine/third_party/physx/source/geomutils/src/GuSecondaryPruner.cpp

1072 lines
35 KiB
C++

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "GuSecondaryPruner.h"
#include "GuBucketPruner.h"
#include "GuIncrementalAABBPrunerCore.h"
//#define USE_DEBUG_PRINTF
#ifdef USE_DEBUG_PRINTF
#include <stdio.h>
#endif
using namespace physx;
using namespace Gu;
class CompanionPrunerBucket : public CompanionPruner
{
public:
CompanionPrunerBucket() : mPrunerCore(false) {}
virtual ~CompanionPrunerBucket() {}
virtual bool addObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PxU32 timeStamp, PoolIndex poolIndex)
{
PX_UNUSED(poolIndex);
PX_UNUSED(handle);
return mPrunerCore.addObject(object, worldAABB, transform, timeStamp);
}
virtual bool updateObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PoolIndex poolIndex)
{
PX_UNUSED(poolIndex);
PX_UNUSED(handle);
return mPrunerCore.updateObject(worldAABB, object, transform);
}
virtual bool removeObject(const PrunerPayload& object, PrunerHandle handle, PxU32 objectIndex, PxU32 swapObjectIndex)
{
PX_UNUSED(objectIndex);
PX_UNUSED(swapObjectIndex);
PX_UNUSED(handle);
PxU32 timeStamp;
return mPrunerCore.removeObject(object, timeStamp);
}
virtual void swapIndex(PxU32 objectIndex, PxU32 swapObjectIndex)
{
PX_UNUSED(objectIndex);
PX_UNUSED(swapObjectIndex);
}
virtual PxU32 removeMarkedObjects(PxU32 timeStamp) { return mPrunerCore.removeMarkedObjects(timeStamp); }
virtual void shiftOrigin(const PxVec3& shift) { mPrunerCore.shiftOrigin(shift); }
virtual void timeStampChange() { }
virtual void build() { mPrunerCore.build(); }
virtual PxU32 getNbObjects() const { return mPrunerCore.getNbObjects(); }
virtual void release() { mPrunerCore.release(); }
virtual void visualize(PxRenderOutput& out, PxU32 color) const { mPrunerCore.visualize(out, color); }
virtual bool raycast(const PxVec3& origin, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const
{
if(mPrunerCore.getNbObjects())
return mPrunerCore.raycast(origin, unitDir, inOutDistance, prunerCallback);
return true;
}
virtual bool overlap(const ShapeData& queryVolume, PrunerOverlapCallback& prunerCallback) const
{
if(mPrunerCore.getNbObjects())
return mPrunerCore.overlap(queryVolume, prunerCallback);
return true;
}
virtual bool sweep(const ShapeData& queryVolume, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const
{
if(mPrunerCore.getNbObjects())
return mPrunerCore.sweep(queryVolume, unitDir, inOutDistance, prunerCallback);
return true;
}
virtual void getGlobalBounds(PxBounds3& bounds) const
{
mPrunerCore.getGlobalBounds(bounds);
}
BucketPrunerCore mPrunerCore;
};
class CompanionPrunerIncremental : public CompanionPruner
{
public:
CompanionPrunerIncremental(const PruningPool* pool) : mPrunerCore(pool) {}
virtual ~CompanionPrunerIncremental() {}
virtual bool addObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PxU32 timeStamp, PoolIndex poolIndex)
{
PX_UNUSED(worldAABB);
PX_UNUSED(transform);
PX_UNUSED(object);
PX_UNUSED(handle);
return mPrunerCore.addObject(poolIndex, timeStamp);
}
virtual bool updateObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PoolIndex poolIndex)
{
PX_UNUSED(worldAABB);
PX_UNUSED(transform);
PX_UNUSED(object);
PX_UNUSED(handle);
return mPrunerCore.updateObject(poolIndex);
}
virtual bool removeObject(const PrunerPayload& object, PrunerHandle handle, PxU32 objectIndex, PxU32 swapObjectIndex)
{
PX_UNUSED(object);
PX_UNUSED(handle);
PxU32 timeStamp;
return mPrunerCore.removeObject(objectIndex, swapObjectIndex, timeStamp);
}
virtual void swapIndex(PxU32 objectIndex, PxU32 swapObjectIndex)
{
mPrunerCore.swapIndex(objectIndex, swapObjectIndex);
}
virtual PxU32 removeMarkedObjects(PxU32 timeStamp) { return mPrunerCore.removeMarkedObjects(timeStamp); }
virtual void shiftOrigin(const PxVec3& shift) { mPrunerCore.shiftOrigin(shift); }
virtual void timeStampChange() { mPrunerCore.timeStampChange(); }
virtual void build() { mPrunerCore.build(); }
virtual PxU32 getNbObjects() const { return mPrunerCore.getNbObjects(); }
virtual void release() { mPrunerCore.release(); }
virtual void visualize(PxRenderOutput& out, PxU32 color) const { mPrunerCore.visualize(out, color); }
virtual bool raycast(const PxVec3& origin, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const
{
if(mPrunerCore.getNbObjects())
return mPrunerCore.raycast(origin, unitDir, inOutDistance, prunerCallback);
return true;
}
virtual bool overlap(const ShapeData& queryVolume, PrunerOverlapCallback& prunerCallback) const
{
if(mPrunerCore.getNbObjects())
return mPrunerCore.overlap(queryVolume, prunerCallback);
return true;
}
virtual bool sweep(const ShapeData& queryVolume, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const
{
if(mPrunerCore.getNbObjects())
return mPrunerCore.sweep(queryVolume, unitDir, inOutDistance, prunerCallback);
return true;
}
virtual void getGlobalBounds(PxBounds3& bounds) const
{
mPrunerCore.getGlobalBounds(bounds);
}
IncrementalAABBPrunerCore mPrunerCore;
};
#define USE_MAVERICK_NODE
#include "GuActorShapeMap.h"
#include "GuBVH.h"
#include "GuAABBTreeNode.h"
#include "GuAABBTreeBuildStats.h"
#include "GuAABBTreeQuery.h"
#include "GuQuery.h"
#ifdef USE_MAVERICK_NODE
#include "GuMaverickNode.h"
#endif
static const bool gUpdateTreeWhenRemovingObject = false;
static const bool gUpdateObjectBoundsWhenRemovingObject = true;
class CompanionPrunerAABBTree : public CompanionPruner
{
enum DirtyFlags
{
NEEDS_REBUILD = (1<<0),
NEEDS_REFIT = (1<<1)
};
public:
CompanionPrunerAABBTree(PxU64 contextID, const PruningPool* pool);
virtual ~CompanionPrunerAABBTree();
virtual bool addObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PxU32 timeStamp, PoolIndex poolIndex);
virtual bool updateObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PoolIndex poolIndex);
virtual bool removeObject(const PrunerPayload& object, PrunerHandle handle, PxU32 objectIndex, PxU32 swapObjectIndex);
virtual void swapIndex(PxU32 objectIndex, PxU32 swapObjectIndex);
virtual PxU32 removeMarkedObjects(PxU32 timeStamp);
virtual void shiftOrigin(const PxVec3& shift);
virtual void timeStampChange();
virtual void build();
virtual PxU32 getNbObjects() const;
virtual void release();
virtual void visualize(PxRenderOutput& out, PxU32 color) const;
virtual bool raycast(const PxVec3& origin, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const;
virtual bool overlap(const ShapeData& queryVolume, PrunerOverlapCallback& prunerCallback) const;
virtual bool sweep(const ShapeData& queryVolume, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const;
virtual void getGlobalBounds(PxBounds3& bounds) const;
// PT: we have multiple options here, not sure which one is best:
// - use a Gu:BVH
// - use a Gu:AABBTree
// - use a full blown Pruner
// - use/reference the master PruningPool or not
// - use a hashmap
// - use PoolIndex
// - use PrunerHandle
// - somehow return our own local index to caller and use that
//
// The current implementation uses a PxBVH, a reference to the master PruningPool, and PrunerHandles.
#ifdef USE_MAVERICK_NODE
MaverickNode mMaverick;
#endif
const PruningPool* mPool;
struct LocalData
{
PX_FORCE_INLINE LocalData(PxU32 timestamp, PrunerHandle handle) : mTimestamp(timestamp), mHandle(handle) {}
PxU32 mTimestamp;
PrunerHandle mHandle;
PX_FORCE_INLINE void setRemoved()
{
mTimestamp = 0xffffffff;
mHandle = 0xffffffff;
}
PX_FORCE_INLINE bool isValid(PxU32 lastValidTimestamp) const
{
return mHandle != 0xffffffff && mTimestamp>=lastValidTimestamp;
}
};
PxArray<LocalData> mLocalData;
BVH* mBVH;
PxU32* mRemap; // Pruner handle to local index
PxU32 mMapSize;
PxU32 mDirtyFlags;
PxU32 mLastValidTimestamp;
PX_FORCE_INLINE PxU32 getNbObjectsFast() const { return mLocalData.size(); }
bool addObjectInternal(PrunerHandle handle, PxU32 timeStamp);
void releaseInternal();
void resizeMap(PxU32 index);
};
CompanionPrunerAABBTree::CompanionPrunerAABBTree(PxU64 /*contextID*/, const PruningPool* pool) : mPool(pool),
mBVH (NULL),
mRemap (NULL),
mMapSize (0),
mDirtyFlags (0),
mLastValidTimestamp (0)
{
}
CompanionPrunerAABBTree::~CompanionPrunerAABBTree()
{
releaseInternal();
}
void CompanionPrunerAABBTree::releaseInternal()
{
PX_DELETE(mBVH);
PX_FREE(mRemap);
mMapSize = 0;
mDirtyFlags = 0;
mLastValidTimestamp = 0;
}
void CompanionPrunerAABBTree::resizeMap(PxU32 index)
{
PxU32 size = mMapSize ? mMapSize*2 : 64;
const PxU32 minSize = index+1;
if(minSize>size)
size = minSize*2;
PxU32* items = PX_ALLOCATE(PxU32, size, "Map");
if(mRemap)
PxMemCopy(items, mRemap, mMapSize*sizeof(PxU32));
PxMemSet(items+mMapSize, 0xff, (size-mMapSize)*sizeof(PxU32));
PX_FREE(mRemap);
mRemap = items;
mMapSize = size;
}
bool CompanionPrunerAABBTree::addObjectInternal(PrunerHandle handle, PxU32 timeStamp)
{
const PxU32 localIndex = getNbObjectsFast();
#ifdef USE_DEBUG_PRINTF
printf("add %d %d to local %d\n", handle, timeStamp, localIndex);
#endif
PX_ASSERT(handle!=0xffffffff);
if(handle>=mMapSize)
resizeMap(handle);
PX_ASSERT(mRemap[handle]==0xffffffff || !mLocalData[mRemap[handle]].isValid(mLastValidTimestamp));
mRemap[handle] = localIndex;
mLocalData.pushBack(LocalData(timeStamp, handle));
PX_DELETE(mBVH);
mDirtyFlags = NEEDS_REBUILD;
// PT: TODO: why didn't we return a secondary pruner handle from here? Could have been stored in the padding bytes of the pruning pool's transform array for example
return true;
}
bool CompanionPrunerAABBTree::addObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PxU32 timeStamp, PoolIndex poolIndex)
{
PX_UNUSED(object);
PX_UNUSED(worldAABB);
PX_UNUSED(transform);
PX_UNUSED(timeStamp);
PX_UNUSED(poolIndex);
#ifdef USE_MAVERICK_NODE
if(mMaverick.addObject(object, handle, worldAABB, transform, timeStamp))
return true;
PxU32 nbToAdd = mMaverick.mNbFree;
for(PxU32 i=0;i<nbToAdd;i++)
addObjectInternal(mMaverick.mFreeHandles[i], mMaverick.mFreeStamps[i]);
mMaverick.mNbFree = 0;
#endif
return addObjectInternal(handle, timeStamp);
}
bool CompanionPrunerAABBTree::updateObject(const PrunerPayload& object, PrunerHandle handle, const PxBounds3& worldAABB, const PxTransform& transform, PoolIndex poolIndex)
{
PX_UNUSED(object);
PX_UNUSED(worldAABB);
PX_UNUSED(transform);
PX_UNUSED(poolIndex);
PX_UNUSED(handle);
#ifdef USE_MAVERICK_NODE
if(mMaverick.updateObject(handle, worldAABB, transform))
return true;
#endif
// PT: the bounds & transform have already been updated in the source pruning pool.
// We just need to mark the corresponding node for refit.
PX_ASSERT(handle<mMapSize);
const PxU32 localIndex = mRemap[handle];
PX_ASSERT(localIndex<getNbObjectsFast());
PX_ASSERT(localIndex!=0xffffffff);
PX_ASSERT(mLocalData[localIndex].mHandle==handle);
if(mBVH && mBVH->updateBoundsInternal(localIndex, worldAABB))
mDirtyFlags |= NEEDS_REFIT;
return true;
}
bool CompanionPrunerAABBTree::removeObject(const PrunerPayload& object, PrunerHandle handle, PxU32 objectIndex, PxU32 swapObjectIndex)
{
PX_UNUSED(object);
PX_UNUSED(objectIndex);
PX_UNUSED(swapObjectIndex);
PX_UNUSED(handle);
#ifdef USE_MAVERICK_NODE
PxU32 unused;
if(mMaverick.removeObject(handle, unused))
return true;
#endif
PX_ASSERT(handle<mMapSize);
const PxU32 localIndex = mRemap[handle];
PX_ASSERT(localIndex<getNbObjectsFast());
PX_ASSERT(localIndex!=0xffffffff);
PX_ASSERT(mLocalData[localIndex].mHandle==handle);
// PT: technically this is all we need to mark the object as removed. We can then test the handle against 0xffffffff during
// queries and skip the object. This is optimal in terms of remove performance, but not optimal in terms of query performance.
// There are a number of extra steps we could do here:
//
// - invalidate the *object* bounds. This means the tree structure itself doesn't change, it's only the object bounds used in
// leaf nodes that do. Empty bounds for removed objects mean we discard the object before reaching the previously mentioned
// handle test. This does not need an "update map".
//
// - update the number of primitives in the node. In this case we update the contents of a leaf node, which means decreasing
// the number of primitives there and reordering them so that there is no hole in the list. This requires an update map so
// it uses more memory and more CPU time during the remove call. It also has a really, really, really vile side-effect of
// invalidating the optimization that skips the object bounds test in the traversal code when the number of primitives is 1. (*)
//
// - the next step would be to recompute the *node* bounds, to take into account the fact that one of the bounds involved in
// its computation is now empty. This would avoid visiting the node at all in some queries, so it is probably worth doing
// if we already do the previous step. (It also requires an update map and pretty much visiting the same memory).
//
// - finally the last step would be to then refit the branch involving that node. This is more complicated because it needs
// support for partial refit in the tree, i.e. links to parent nodes etc. If we do that though, the previous step can be
// skipped since the node bounds recomputation will happen automatically as part of the refit procedure. The previous step
// is only useful as a limited refit (limited to a single node) when parent pointers are not available. The previous step
// can also be used as an optimization if the recomputed bounds is the same as the old one, then we can skip the more
// costly refit procedure. In fact this could probably be used as an optimization for the refit loop: if the box is the
// same as before we could break out of the loop. Note that it is possible to skip this last step here because the new
// bounds are guaranteed to be smaller than or equal to the previous bounds. We couldn't skip this part in the "update
// object" codepath for example.
//
// (*) the optimization relies on the fact that the narrow-phase test is roughly as expensive as the AABB test within the
// tree, so it skips it if there is only one primitive in the node. (With multiple primitives it's worth doing the test
// anyway since one AABB test can skip N narrow-phase tests). The annoying bit is that removing an object can suddenly mean
// the AABB test isn't done anymore, and while it isn't a big deal in practice it's enough to break unit tests that don't
// expect that.
#ifdef USE_DEBUG_PRINTF
printf("remove %d %d from %d\n", handle, mLocalData[localIndex].mTimestamp, localIndex);
#endif
mRemap[handle] = 0xffffffff;
mLocalData[localIndex].setRemoved();
if(mBVH)
{
BVHData& data = const_cast<BVHData&>(mBVH->getData());
const PxU32 nbNodes = data.mNbNodes;
PX_UNUSED(nbNodes);
BVHNode* nodes = data.mNodes;
PxU32* indices = data.mIndices;
PxBounds3* bounds = data.mBounds.getBounds();
if(gUpdateObjectBoundsWhenRemovingObject)
{
// Invalidates the object bounds, not always needed
// The node bounds would need recomputing, and the branch refit
bounds[localIndex].minimum = PxVec3(GU_EMPTY_BOUNDS_EXTENTS);
bounds[localIndex].maximum = PxVec3(-GU_EMPTY_BOUNDS_EXTENTS);
}
PxU32* mMapping = data.getUpdateMap();
if(gUpdateTreeWhenRemovingObject && mMapping)
{
// PT: note: the following codepath has only one part (as opposed to the equivalent code in AABBTreeUpdateMap)
// because it operates on our local indices, not on (pruning) pool indices. The difference is that our local
// array can have holes in it for removed objects, while the AABBTree's update code works with the PruningPool
// (no holes).
const PxU32 treeNodeIndex = mMapping[localIndex];
if(treeNodeIndex!=0xffffffff)
{
PX_ASSERT(treeNodeIndex < nbNodes);
PX_ASSERT(nodes[treeNodeIndex].isLeaf());
BVHNode* node = nodes + treeNodeIndex;
const PxU32 nbPrims = node->getNbRuntimePrimitives();
PX_ASSERT(nbPrims < 16);
// retrieve the primitives pointer
PxU32* primitives = node->getPrimitives(indices);
PX_ASSERT(primitives);
// PT: look for desired local index in the leaf
bool foundIt = false;
for(PxU32 i=0;i<nbPrims;i++)
{
PX_ASSERT(mMapping[primitives[i]] == treeNodeIndex); // PT: all primitives should point to the same leaf node
if(localIndex == primitives[i])
{
foundIt = true;
const PxU32 last = nbPrims-1;
node->setNbRunTimePrimitives(last);
primitives[i] = 0xffffffff; // Mark primitive index as invalid in the node
mMapping[localIndex] = 0xffffffff; // invalidate the node index for pool 0
// PT: swap within the leaf node. No need to update the mapping since they should all point
// to the same tree node anyway.
if(last!=i)
PxSwap(primitives[i], primitives[last]);
// PT: breaking here means we couldn't reuse that loop to update the node bounds
break;
}
}
PX_ASSERT(foundIt);
PX_UNUSED(foundIt);
}
}
}
return true;
}
void CompanionPrunerAABBTree::swapIndex(PxU32 objectIndex, PxU32 swapObjectIndex)
{
PX_UNUSED(objectIndex);
PX_UNUSED(swapObjectIndex);
}
PxU32 CompanionPrunerAABBTree::removeMarkedObjects(PxU32 timeStamp)
{
#ifdef USE_DEBUG_PRINTF
printf("removeMarkedObjects %d\n", timeStamp);
#endif
PX_UNUSED(timeStamp);
//printf("removeMarkedObjects %d\n", timeStamp);
mLastValidTimestamp = timeStamp+1;
// PT: TODO: consider updating our local data as well here but is it worth it?
if(0)
{
const PxU32 nbObjects = getNbObjectsFast();
for(PxU32 i=0;i<nbObjects;i++)
{
LocalData& localData = mLocalData[i];
if(localData.mTimestamp==timeStamp)
{
localData.setRemoved();
}
}
}
#ifdef USE_MAVERICK_NODE
mMaverick.removeMarkedObjects(timeStamp);
#endif
return 0;
}
void CompanionPrunerAABBTree::shiftOrigin(const PxVec3& shift)
{
if(mBVH)
{
BVHData& data = const_cast<BVHData&>(mBVH->getData());
PxU32 nbNodes = data.mNbNodes;
BVHNode* nodes = data.mNodes;
while(nbNodes--)
{
nodes->mBV.minimum -= shift;
nodes->mBV.maximum -= shift;
nodes++;
}
PxU32 nbObjects = getNbObjectsFast();
PxBounds3* bounds = data.mBounds.getBounds();
while(nbObjects--)
{
if(!bounds->isEmpty())
{
bounds->minimum -= shift;
bounds->maximum -= shift;
}
bounds++;
}
}
#ifdef USE_MAVERICK_NODE
mMaverick.shiftOrigin(shift);
#endif
}
void CompanionPrunerAABBTree::timeStampChange()
{
}
void CompanionPrunerAABBTree::build()
{
if(!mDirtyFlags) // PT: necessary, extended bucket pruner calls this without checking first
return;
const PxU32 needsRebuild = mDirtyFlags & NEEDS_REBUILD;
const PxU32 needsRefit = mDirtyFlags & NEEDS_REFIT;
mDirtyFlags = 0;
// PT: we want fast build for this one
const PxU32 numPrimsPerLeaf = 15;
if(needsRebuild)
{
PX_DELETE(mBVH);
PxU32 nbObjects = getNbObjectsFast();
if(!nbObjects)
return;
if(1)
{
// PT: you know what forget it just rebuild the whole map
PX_FREE(mRemap);
PxU32* newRemap = PX_ALLOCATE(PxU32, mMapSize, "Map");
PxMemSet(newRemap, 0xff, mMapSize*sizeof(PxU32));
mRemap = newRemap;
PxU32 offset = 0;
PxU32 nb = nbObjects;
while(nb--)
{
if(!mLocalData[offset].isValid(mLastValidTimestamp))
{
if(0 && mLocalData[offset].mHandle!=0xffffffff)
{
//PX_ASSERT(mRemap[mLocalData[offset].mHandle]==offset);
mRemap[mLocalData[offset].mHandle] = 0xffffffff;
}
// This object has been removed, plug the hole
const LocalData& movedData = mLocalData[--nbObjects];
if(movedData.isValid(mLastValidTimestamp))
{
#ifdef USE_DEBUG_PRINTF
printf("move %d %d from %d to %d\n", movedData.mHandle, movedData.mTimestamp, nbObjects, offset);
if(movedData.mHandle==22)
{
int stop = 1;
(void)stop;
}
#endif
//PX_ASSERT(mRemap[movedData.mHandle]==nbObjects);
//mRemap[movedData.mHandle] = offset;
mRemap[movedData.mHandle] = offset;
}
#ifdef USE_DEBUG_PRINTF
else
printf("skip remap %d %d from %d to %d\n", movedData.mHandle, movedData.mTimestamp, nbObjects, offset);
#endif
mLocalData[offset] = movedData;
}
else
{
mRemap[mLocalData[offset].mHandle] = offset;
offset++;
}
}
nbObjects = offset;
mLocalData.forceSize_Unsafe(offset);
if(!nbObjects)
return;
}
if(1)
{
AABBTreeBounds bounds;
bounds.init(nbObjects);
// PT: TODO: inflation?
const PxBounds3* currentBounds = mPool->getCurrentWorldBoxes();
PxBounds3* dst = bounds.getBounds();
for(PxU32 i=0; i<nbObjects; i++)
{
const LocalData& localData = mLocalData[i];
const PoolIndex poolIndex = mPool->getIndex(localData.mHandle);
dst[i] = currentBounds[poolIndex];
}
mBVH = PX_NEW(BVH)(NULL);
bool status = mBVH->init(nbObjects, &bounds, NULL, 0, BVH_SPLATTER_POINTS, numPrimsPerLeaf, 0.0);
PX_ASSERT(status);
PX_UNUSED(status);
}
{
BVHData& data = const_cast<BVHData&>(mBVH->getData());
data.createUpdateMap(getNbObjectsFast());
}
return;
}
if(needsRefit && mBVH)
{
BVHData& data = const_cast<BVHData&>(mBVH->getData());
data.refitMarkedNodes(data.mBounds.getBounds());
}
}
PxU32 CompanionPrunerAABBTree::getNbObjects() const
{
PxU32 nb = getNbObjectsFast();
#ifdef USE_MAVERICK_NODE
nb += mMaverick.getNbPrimitives();
#endif
return nb;
}
void CompanionPrunerAABBTree::release()
{
releaseInternal();
}
void CompanionPrunerAABBTree::visualize(PxRenderOutput& out, PxU32 color) const
{
visualizeTree(out, color, mBVH);
}
namespace
{
struct BVHTree
{
PX_FORCE_INLINE BVHTree(const BVHData& data) : mRootNode(data.mNodes), mIndices(data.mIndices) {}
const BVHNode* getNodes() const { return mRootNode; }
const PxU32* getIndices() const { return mIndices; }
const BVHNode* mRootNode;
const PxU32* mIndices;
};
struct RaycastAdapter
{
RaycastAdapter(const CompanionPrunerAABBTree& owner, PrunerRaycastCallback& cb, PxU32 lastValidTimestamp) : mOwner(owner), mCallback(cb), mLastValidTimestamp(lastValidTimestamp), mAbort(false) {}
PX_FORCE_INLINE bool invoke(PxReal& distance, PxU32 index)
{
if(!mOwner.mLocalData[index].isValid(mLastValidTimestamp))
return true; // PT: object has been removed, tree data hasn't been updated accordingly
const PxU32 handle = mOwner.mLocalData[index].mHandle;
// if(gUpdateTreeWhenRemovingObject)
{
PX_ASSERT(handle!=0xffffffff);
}
/* else
{
if(handle==0xffffffff)
{
// PT: object has been removed, tree data hasn't been updated accordingly
return true;
}
}*/
const PoolIndex poolIndex = mOwner.mPool->getIndex(handle);
const PxTransform* currentTransforms = mOwner.mPool->getTransforms();
const PrunerPayload* currentPayloads = mOwner.mPool->getObjects();
if(mAbort || !mCallback.invoke(distance, poolIndex, currentPayloads, currentTransforms))
{
mAbort = true;
return false;
}
return true;
}
const CompanionPrunerAABBTree& mOwner;
PrunerRaycastCallback& mCallback;
const PxU32 mLastValidTimestamp;
bool mAbort;
PX_NOCOPY(RaycastAdapter)
};
struct OverlapAdapter
{
OverlapAdapter(const CompanionPrunerAABBTree& owner, PrunerOverlapCallback& cb, PxU32 lastValidTimestamp) : mOwner(owner), mCallback(cb), mLastValidTimestamp(lastValidTimestamp), mAbort(false) {}
PX_FORCE_INLINE bool invoke(PxU32 index)
{
if(!mOwner.mLocalData[index].isValid(mLastValidTimestamp))
return true; // PT: object has been removed, tree data hasn't been updated accordingly
const PxU32 handle = mOwner.mLocalData[index].mHandle;
PX_ASSERT(handle!=0xffffffff);
const PoolIndex poolIndex = mOwner.mPool->getIndex(handle);
const PxTransform* currentTransforms = mOwner.mPool->getTransforms();
const PrunerPayload* currentPayloads = mOwner.mPool->getObjects();
if(mAbort || !mCallback.invoke(poolIndex, currentPayloads, currentTransforms))
{
mAbort = true;
return false;
}
return true;
}
const CompanionPrunerAABBTree& mOwner;
PrunerOverlapCallback& mCallback;
const PxU32 mLastValidTimestamp;
bool mAbort;
PX_NOCOPY(OverlapAdapter)
};
#ifdef USE_MAVERICK_NODE
struct MaverickRaycastAdapter
{
MaverickRaycastAdapter(const MaverickNode& owner, PrunerRaycastCallback& cb) : mOwner(owner), mCallback(cb), mAbort(false) {}
PX_FORCE_INLINE bool invoke(PxReal& distance, PxU32 index)
{
if(mAbort || !mCallback.invoke(distance, index, mOwner.mFreeObjects, mOwner.mFreeTransforms))
{
mAbort = true;
return false;
}
return true;
}
const MaverickNode& mOwner;
PrunerRaycastCallback& mCallback;
bool mAbort;
PX_NOCOPY(MaverickRaycastAdapter)
};
struct MaverickOverlapAdapter
{
MaverickOverlapAdapter(const MaverickNode& owner, PrunerOverlapCallback& cb) : mOwner(owner), mCallback(cb), mAbort(false) {}
PX_FORCE_INLINE bool invoke(PxU32 index)
{
if(mAbort || !mCallback.invoke(index, mOwner.mFreeObjects, mOwner.mFreeTransforms))
{
mAbort = true;
return false;
}
return true;
}
const MaverickNode& mOwner;
PrunerOverlapCallback& mCallback;
bool mAbort;
PX_NOCOPY(MaverickOverlapAdapter)
};
#endif
}
bool CompanionPrunerAABBTree::raycast(const PxVec3& origin, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const
{
PX_UNUSED(origin);
PX_UNUSED(unitDir);
PX_UNUSED(inOutDistance);
PX_UNUSED(prunerCallback);
PX_ASSERT(!mDirtyFlags);
// if(mDirtyFlags)
// const_cast<CompanionPrunerAABBTree*>(this)->build();
#ifdef USE_MAVERICK_NODE
{
MaverickRaycastAdapter ra(mMaverick, prunerCallback);
Gu::RayAABBTest test(origin*2.0f, unitDir*2.0f, inOutDistance, PxVec3(0.0f));
if(!doLeafTest<false, true, MaverickNode, MaverickRaycastAdapter>(&mMaverick, test, mMaverick.mFreeBounds, NULL, inOutDistance, ra))
return false;
}
#endif
if(mBVH)
{
RaycastAdapter ra(*this, prunerCallback, mLastValidTimestamp);
return AABBTreeRaycast<false, true, BVHTree, BVHNode, RaycastAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), origin, unitDir, inOutDistance, PxVec3(0.0f), ra);
}
return true;
}
// PT: TODO: this is copied from SqBounds.h, should be either moved to Gu and shared or passed as a user parameter
#define SQ_PRUNER_EPSILON 0.005f
#define SQ_PRUNER_INFLATION (1.0f + SQ_PRUNER_EPSILON) // pruner test shape inflation (not narrow phase shape)
bool CompanionPrunerAABBTree::overlap(const ShapeData& queryVolume, PrunerOverlapCallback& prunerCallback) const
{
PX_UNUSED(queryVolume);
PX_UNUSED(prunerCallback);
PX_ASSERT(!mDirtyFlags);
// if(mDirtyFlags)
// const_cast<CompanionPrunerAABBTree*>(this)->build();
#ifdef USE_MAVERICK_NODE
{
MaverickOverlapAdapter ra(mMaverick, prunerCallback);
switch(queryVolume.getType())
{
case PxGeometryType::eBOX:
{
if(queryVolume.isOBB())
{
const DefaultOBBAABBTest test(queryVolume);
if(!doOverlapLeafTest<true, OBBAABBTest, MaverickNode, MaverickOverlapAdapter>(test, &mMaverick, mMaverick.mFreeBounds, NULL, ra))
return false;
}
else
{
const DefaultAABBAABBTest test(queryVolume);
if(!doOverlapLeafTest<true, AABBAABBTest, MaverickNode, MaverickOverlapAdapter>(test, &mMaverick, mMaverick.mFreeBounds, NULL, ra))
return false;
}
}
break;
case PxGeometryType::eCAPSULE:
{
const DefaultCapsuleAABBTest test(queryVolume, SQ_PRUNER_INFLATION);
if(!doOverlapLeafTest<true, CapsuleAABBTest, MaverickNode, MaverickOverlapAdapter>(test, &mMaverick, mMaverick.mFreeBounds, NULL, ra))
return false;
}
break;
case PxGeometryType::eSPHERE:
{
const DefaultSphereAABBTest test(queryVolume);
if(!doOverlapLeafTest<true, SphereAABBTest, MaverickNode, MaverickOverlapAdapter>(test, &mMaverick, mMaverick.mFreeBounds, NULL, ra))
return false;
}
break;
case PxGeometryType::eCONVEXMESH:
{
const DefaultOBBAABBTest test(queryVolume);
if(!doOverlapLeafTest<true, OBBAABBTest, MaverickNode, MaverickOverlapAdapter>(test, &mMaverick, mMaverick.mFreeBounds, NULL, ra))
return false;
}
break;
default:
PX_ALWAYS_ASSERT_MESSAGE("unsupported overlap query volume geometry type");
}
}
#endif
if(mBVH)
{
OverlapAdapter ra(*this, prunerCallback, mLastValidTimestamp);
switch(queryVolume.getType())
{
case PxGeometryType::eBOX:
{
if(queryVolume.isOBB())
{
const DefaultOBBAABBTest test(queryVolume);
return AABBTreeOverlap<true, OBBAABBTest, BVHTree, BVHNode, OverlapAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), test, ra);
}
else
{
const DefaultAABBAABBTest test(queryVolume);
return AABBTreeOverlap<true, AABBAABBTest, BVHTree, BVHNode, OverlapAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), test, ra);
}
}
case PxGeometryType::eCAPSULE:
{
const DefaultCapsuleAABBTest test(queryVolume, SQ_PRUNER_INFLATION);
//const DefaultCapsuleAABBTest test(queryVolume, 1.0f);
return AABBTreeOverlap<true, CapsuleAABBTest, BVHTree, BVHNode, OverlapAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), test, ra);
}
case PxGeometryType::eSPHERE:
{
const DefaultSphereAABBTest test(queryVolume);
return AABBTreeOverlap<true, SphereAABBTest, BVHTree, BVHNode, OverlapAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), test, ra);
}
case PxGeometryType::eCONVEXMESH:
{
const DefaultOBBAABBTest test(queryVolume);
return AABBTreeOverlap<true, OBBAABBTest, BVHTree, BVHNode, OverlapAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), test, ra);
}
default:
PX_ALWAYS_ASSERT_MESSAGE("unsupported overlap query volume geometry type");
}
}
return true;
}
bool CompanionPrunerAABBTree::sweep(const ShapeData& queryVolume, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback& prunerCallback) const
{
PX_UNUSED(queryVolume);
PX_UNUSED(unitDir);
PX_UNUSED(inOutDistance);
PX_UNUSED(prunerCallback);
PX_ASSERT(!mDirtyFlags);
// if(mDirtyFlags)
// const_cast<CompanionPrunerAABBTree*>(this)->build();
#ifdef USE_MAVERICK_NODE
{
MaverickRaycastAdapter ra(mMaverick, prunerCallback);
const PxBounds3& aabb = queryVolume.getPrunerInflatedWorldAABB();
Gu::RayAABBTest test(aabb.getCenter()*2.0f, unitDir*2.0f, inOutDistance, aabb.getExtents());
if(!doLeafTest<true, true, MaverickNode, MaverickRaycastAdapter>(&mMaverick, test, mMaverick.mFreeBounds, NULL, inOutDistance, ra))
return false;
}
#endif
if(mBVH)
{
RaycastAdapter ra(*this, prunerCallback, mLastValidTimestamp);
const PxBounds3& aabb = queryVolume.getPrunerInflatedWorldAABB();
return AABBTreeRaycast<true, true, BVHTree, BVHNode, RaycastAdapter>()(mBVH->getData().mBounds, BVHTree(mBVH->getData()), aabb.getCenter(), unitDir, inOutDistance, aabb.getExtents(), ra);
}
return true;
}
class PxBounds3Padded : public PxBounds3
{
public:
PX_FORCE_INLINE PxBounds3Padded() {}
PX_FORCE_INLINE ~PxBounds3Padded() {}
PxU32 padding;
};
void CompanionPrunerAABBTree::getGlobalBounds(PxBounds3& bounds) const
{
PxBounds3Padded tmp;
if(mBVH)
{
tmp.minimum = mBVH->getNodes()->mBV.minimum;
tmp.maximum = mBVH->getNodes()->mBV.maximum;
}
else
tmp.setEmpty();
Vec4V minV = V4LoadU(&tmp.minimum.x);
Vec4V maxV = V4LoadU(&tmp.maximum.x);
#ifdef USE_MAVERICK_NODE
{
PxU32 nbFree = mMaverick.mNbFree;
if(nbFree)
{
const PxBounds3* freeBounds = mMaverick.mFreeBounds;
while(nbFree--)
{
minV = V4Min(minV, V4LoadU(&freeBounds->minimum.x));
maxV = V4Max(maxV, V4LoadU(&freeBounds->maximum.x));
freeBounds++;
}
}
}
#endif
StoreBounds(bounds, minV, maxV);
}
CompanionPruner* physx::Gu::createCompanionPruner(PxU64 contextID, CompanionPrunerType type, const PruningPool* pool)
{
if(0)
// return NULL;
return PX_NEW(CompanionPrunerAABBTree)(contextID, pool);
//return PX_NEW(CompanionPrunerBucket);
// return PX_NEW(CompanionPrunerIncremental)(pool);
PX_UNUSED(contextID);
switch(type)
{
case COMPANION_PRUNER_NONE: return NULL;
case COMPANION_PRUNER_BUCKET: return PX_NEW(CompanionPrunerBucket);
case COMPANION_PRUNER_INCREMENTAL: return PX_NEW(CompanionPrunerIncremental)(pool);
case COMPANION_PRUNER_AABB_TREE: return PX_NEW(CompanionPrunerAABBTree)(contextID, pool);
}
return NULL;
}