1072 lines
35 KiB
C++
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;
|
|
}
|
|
|