// 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 "GuPruningPool.h" #include "foundation/PxMemory.h" #include "common/PxProfileZone.h" using namespace physx; using namespace Gu; PruningPool::PruningPool(PxU64 contextID, TransformCacheMode mode) : mNbObjects (0), mMaxNbObjects (0), mObjects (NULL), mTransforms (NULL), mTransformCacheMode (mode), mHandleToIndex (NULL), mIndexToHandle (NULL), mFirstRecycledHandle(INVALID_PRUNERHANDLE), mContextID (contextID) { } PruningPool::~PruningPool() { mWorldBoxes.release(); PX_FREE(mIndexToHandle); PX_FREE(mHandleToIndex); PX_FREE(mTransforms); PX_FREE(mObjects); } bool PruningPool::resize(PxU32 newCapacity) { PX_PROFILE_ZONE("PruningPool::resize", mContextID); const bool useTransforms = mTransformCacheMode!=TRANSFORM_CACHE_UNUSED; PxTransform* newTransforms = useTransforms ? PX_ALLOCATE(PxTransform, newCapacity, "Pruner transforms") : NULL; if(useTransforms && !newTransforms) return false; PrunerPayload* newData = PX_ALLOCATE(PrunerPayload, newCapacity, "PrunerPayload*"); PrunerHandle* newIndexToHandle = PX_ALLOCATE(PrunerHandle, newCapacity, "Pruner Index Mapping"); PoolIndex* newHandleToIndex = PX_ALLOCATE(PoolIndex, newCapacity, "Pruner Index Mapping"); if( (!newData) || (!newIndexToHandle) || (!newHandleToIndex)) { PX_FREE(newHandleToIndex); PX_FREE(newIndexToHandle); PX_FREE(newTransforms); PX_FREE(newData); return false; } mWorldBoxes.resize(newCapacity, mNbObjects); if(mObjects) PxMemCopy(newData, mObjects, mNbObjects*sizeof(PrunerPayload)); if(mTransforms) PxMemCopy(newTransforms, mTransforms, mNbObjects*sizeof(PxTransform)); if(mIndexToHandle) PxMemCopy(newIndexToHandle, mIndexToHandle, mNbObjects*sizeof(PrunerHandle)); if(mHandleToIndex) PxMemCopy(newHandleToIndex, mHandleToIndex, mMaxNbObjects*sizeof(PoolIndex)); // PT: why mMaxNbObjects here? on purpose? mMaxNbObjects = newCapacity; PX_FREE(mIndexToHandle); PX_FREE(mHandleToIndex); PX_FREE(mTransforms); PX_FREE(mObjects); mObjects = newData; mTransforms = newTransforms; mHandleToIndex = newHandleToIndex; mIndexToHandle = newIndexToHandle; return true; } void PruningPool::preallocate(PxU32 newCapacity) { if(newCapacity>mMaxNbObjects) resize(newCapacity); } PxU32 PruningPool::addObjects(PrunerHandle* results, const PxBounds3* bounds, const PrunerPayload* data, const PxTransform* transforms, PxU32 count) { PX_PROFILE_ZONE("PruningPool::addObjects", mContextID); PX_ASSERT((!transforms && mTransformCacheMode==TRANSFORM_CACHE_UNUSED) || (transforms && mTransformCacheMode!=TRANSFORM_CACHE_UNUSED)); for(PxU32 i=0;i(newCapacity, 64))) //if(!resize(PxMax(mMaxNbObjects*2, 64))) { // pool can return an invalid handle if memory alloc fails // should probably have an error here or not handle this results[i] = INVALID_PRUNERHANDLE; // PT: we need to write the potentially invalid handle to let users know which object failed first return i; } } PX_ASSERT(mNbObjects!=mMaxNbObjects); const PoolIndex index = mNbObjects++; // update mHandleToIndex and mIndexToHandle mappings PrunerHandle handle; if(mFirstRecycledHandle != INVALID_PRUNERHANDLE) { // mFirstRecycledHandle is an entry into a freelist for removed slots // this path is only taken if we have any removed slots handle = mFirstRecycledHandle; mFirstRecycledHandle = mHandleToIndex[handle]; } else { handle = index; } // PT: TODO: investigate why we added mIndexToHandle/mHandleToIndex. The initial design with 'Prunable' objects didn't need these arrays. // PT: these arrays are "parallel" mWorldBoxes.getBounds() [index] = bounds[i]; // store the payload/userData and AABB in parallel arrays mObjects [index] = data[i]; mIndexToHandle [index] = handle; if(transforms && mTransforms) mTransforms [index] = transforms[i]; mHandleToIndex[handle] = index; results[i] = handle; } return count; } PoolIndex PruningPool::removeObject(PrunerHandle h, PrunerPayloadRemovalCallback* removalCallback) { PX_PROFILE_ZONE("PruningPool::removeObject", mContextID); PX_ASSERT(mNbObjects); // remove the object and its AABB by provided PrunerHandle and update mHandleToIndex and mIndexToHandle mappings const PoolIndex indexOfRemovedObject = mHandleToIndex[h]; // retrieve object's index from handle if(removalCallback) removalCallback->invoke(1, &mObjects[indexOfRemovedObject]); const PoolIndex indexOfLastObject = --mNbObjects; // swap the object at last index with index if(indexOfLastObject!=indexOfRemovedObject) { // PT: move last object's data to recycled spot (from removed object) // PT: the last object has moved so we need to handle the mappings for this object // PT: TODO: investigate where this double-mapping comes from. It was not needed in the original design. // PT: these arrays are "parallel" PxBounds3* bounds = mWorldBoxes.getBounds(); const PrunerHandle handleOfLastObject = mIndexToHandle[indexOfLastObject]; bounds [indexOfRemovedObject] = bounds [indexOfLastObject]; mObjects [indexOfRemovedObject] = mObjects [indexOfLastObject]; if(mTransforms) mTransforms [indexOfRemovedObject] = mTransforms [indexOfLastObject]; mIndexToHandle [indexOfRemovedObject] = handleOfLastObject; mHandleToIndex[handleOfLastObject] = indexOfRemovedObject; } // mHandleToIndex also stores the freelist for removed handles (in place of holes formed by removed handles) mHandleToIndex[h] = mFirstRecycledHandle; // update linked list of available recycled handles mFirstRecycledHandle = h; // update the list head return indexOfLastObject; } void PruningPool::shiftOrigin(const PxVec3& shift) { PX_PROFILE_ZONE("PruningPool::shiftOrigin", mContextID); const PxU32 nb = mNbObjects; PxBounds3* bounds = mWorldBoxes.getBounds(); for(PxU32 i=0; i static void updateAndInflateBounds(PruningPool& pool, const PrunerHandle* PX_RESTRICT handles, const PxU32* PX_RESTRICT boundsIndices, const PxBounds3* PX_RESTRICT newBounds, const PxTransform32* PX_RESTRICT newTransforms, PxU32 count, float epsilon) { PxBounds3* PX_RESTRICT bounds = pool.mWorldBoxes.getBounds(); PxTransform* PX_RESTRICT transforms = hasTransforms ? pool.mTransforms : NULL; if(boundsIndices) { while(count--) { const PoolIndex poolIndex = pool.getIndex(*handles++); PX_ASSERT(poolIndex!=INVALID_PRUNERHANDLE); const PxU32 remappedIndex = *boundsIndices++; if(hasTransforms) transforms[poolIndex] = newTransforms[remappedIndex]; inflateBounds(bounds[poolIndex], newBounds[remappedIndex], epsilon); } } else { while(count--) { const PoolIndex poolIndex = pool.getIndex(*handles++); PX_ASSERT(poolIndex!=INVALID_PRUNERHANDLE); if(hasTransforms) { transforms[poolIndex] = *newTransforms; newTransforms++; } inflateBounds(bounds[poolIndex], *newBounds++, epsilon); } } } void PruningPool::updateAndInflateBounds(const PrunerHandle* handles, const PxU32* boundsIndices, const PxBounds3* newBounds, const PxTransform32* newTransforms, PxU32 count, float epsilon) { PX_PROFILE_ZONE("PruningPool::updateAndInflateBounds", mContextID); if(mTransforms) ::updateAndInflateBounds<1>(*this, handles, boundsIndices, newBounds, newTransforms, count, epsilon); else ::updateAndInflateBounds<0>(*this, handles, boundsIndices, newBounds, NULL, count, epsilon); }