// 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. #ifndef GU_BUCKET_PRUNER_H #define GU_BUCKET_PRUNER_H #include "common/PxPhysXCommonConfig.h" #include "GuPruner.h" #include "GuSqInternal.h" #include "GuPruningPool.h" #include "foundation/PxHash.h" #define FREE_PRUNER_SIZE 16 //#define USE_REGULAR_HASH_MAP #ifdef USE_REGULAR_HASH_MAP #include "foundation/PxHashMap.h" #endif namespace physx { class PxRenderOutput; namespace Gu { typedef PxU32 BucketWord; #if PX_VC #pragma warning(push) #pragma warning( disable : 4324 ) // Padding was added at the end of a structure because of a __declspec(align) value. #endif PX_ALIGN_PREFIX(16) struct BucketBox { PxVec3 mCenter; PxU32 mData0; // Integer-encoded min value along sorting axis PxVec3 mExtents; PxU32 mData1; // Integer-encoded max value along sorting axis #if PX_DEBUG // PT: we need the original min value for debug checks. Using the center/extents version // fails because recomputing the min from them introduces FPU accuracy errors in the values. float mDebugMin; #endif PX_FORCE_INLINE PxVec3 getMin() const { return mCenter - mExtents; } PX_FORCE_INLINE PxVec3 getMax() const { return mCenter + mExtents; } PX_FORCE_INLINE void setEmpty() { mCenter = PxVec3(0.0f); mExtents = PxVec3(-PX_MAX_BOUNDS_EXTENTS); #if PX_DEBUG mDebugMin = PX_MAX_BOUNDS_EXTENTS; #endif } }PX_ALIGN_SUFFIX(16); PX_ALIGN_PREFIX(16) struct BucketPrunerNode { BucketPrunerNode(); void classifyBoxes( float limitX, float limitZ, PxU32 nb, BucketBox* PX_RESTRICT boxes, const PrunerPayload* PX_RESTRICT objects, const PxTransform* PX_RESTRICT transforms, BucketBox* PX_RESTRICT sortedBoxes, PrunerPayload* PX_RESTRICT sortedObjects, PxTransform* PX_RESTRICT sortedTransforms, bool isCrossBucket, PxU32 sortAxis); PX_FORCE_INLINE void initCounters() { for(PxU32 i=0;i<5;i++) mCounters[i] = 0; for(PxU32 i=0;i<5;i++) mOffsets[i] = 0; } BucketWord mCounters[5]; // Number of objects in each of the 5 children BucketWord mOffsets[5]; // Start index of objects for each of the 5 children BucketBox mBucketBox[5]; // AABBs around objects for each of the 5 children PxU16 mOrder[8]; // PNS: 5 children => 3 bits/index => 3*5=15 bits total, for each of the 8 canonical directions }PX_ALIGN_SUFFIX(16); PX_FORCE_INLINE PxU32 PxComputeHash(const PrunerPayload& payload) { #if PX_P64_FAMILY // const PxU32 h0 = PxHash((const void*)payload.data[0]); // const PxU32 h1 = PxHash((const void*)payload.data[1]); const PxU32 h0 = PxU32(PX_MAX_U32 & payload.data[0]); const PxU32 h1 = PxU32(PX_MAX_U32 & payload.data[1]); return physx::PxComputeHash(PxU64(h0)|(PxU64(h1)<<32)); #else return physx::PxComputeHash(PxU64(payload.data[0])|(PxU64(payload.data[1])<<32)); #endif } #ifdef USE_REGULAR_HASH_MAP struct BucketPrunerPair : public PxUserAllocated { PX_FORCE_INLINE BucketPrunerPair() {} PX_FORCE_INLINE BucketPrunerPair(PxU32 index, PxU32 stamp) : mCoreIndex(index), mTimeStamp(stamp) {} PxU32 mCoreIndex; // index in mCoreObjects PxU32 mTimeStamp; }; typedef PxHashMap BucketPrunerMap; #else struct BucketPrunerPair : public PxUserAllocated { PrunerPayload mData; PxU32 mCoreIndex; // index in mCoreObjects PxU32 mTimeStamp; }; // Custom hash-map - currently faster than the regular hash-map (PxHashMap), in particular for 'find-and-erase' operations. class BucketPrunerMap : public PxUserAllocated { public: BucketPrunerMap(); ~BucketPrunerMap(); void purge(); void shrinkMemory(); BucketPrunerPair* addPair (const PrunerPayload& payload, PxU32 coreIndex, PxU32 timeStamp); bool removePair (const PrunerPayload& payload, PxU32& coreIndex, PxU32& timeStamp); const BucketPrunerPair* findPair (const PrunerPayload& payload) const; PX_FORCE_INLINE PxU32 getPairIndex(const BucketPrunerPair* pair) const { return (PxU32((size_t(pair) - size_t(mActivePairs)))/sizeof(BucketPrunerPair)); } PxU32 mHashSize; PxU32 mMask; PxU32 mNbActivePairs; PxU32* mHashTable; PxU32* mNext; BucketPrunerPair* mActivePairs; PxU32 mReservedMemory; PX_FORCE_INLINE BucketPrunerPair* findPair(const PrunerPayload& payload, PxU32 hashValue) const; void removePairInternal(const PrunerPayload& payload, PxU32 hashValue, PxU32 pairIndex); void reallocPairs(); void reserveMemory(PxU32 memSize); }; #endif class BucketPrunerCore : public PxUserAllocated { public: PX_PHYSX_COMMON_API BucketPrunerCore(bool externalMemory=true); PX_PHYSX_COMMON_API ~BucketPrunerCore(); void release(); void setExternalMemory(PxU32 nbObjects, PxBounds3* boxes, PrunerPayload* objects, PxTransform* transforms); PX_PHYSX_COMMON_API bool addObject(const PrunerPayload& object, const PxBounds3& worldAABB, const PxTransform& transform, PxU32 timeStamp=0); bool removeObject(const PrunerPayload& object, PxU32& timeStamp); bool updateObject(const PxBounds3& worldAABB, const PrunerPayload& object, const PxTransform& transform); // PT: look for objects marked with input timestamp everywhere in the structure, and remove them. This is the same // as calling 'removeObject' individually for all these objects, but much more efficient. Returns number of removed objects. PxU32 removeMarkedObjects(PxU32 timeStamp); PX_PHYSX_COMMON_API bool raycast(const PxVec3& origin, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback&) const; PX_PHYSX_COMMON_API bool overlap(const ShapeData& queryVolume, PrunerOverlapCallback&) const; PX_PHYSX_COMMON_API bool sweep(const ShapeData& queryVolume, const PxVec3& unitDir, PxReal& inOutDistance, PrunerRaycastCallback&) const; void getGlobalBounds(PxBounds3& bounds) const; void shiftOrigin(const PxVec3& shift); void visualize(PxRenderOutput& out, PxU32 color) const; PX_FORCE_INLINE void build() { classifyBoxes(); } #ifdef FREE_PRUNER_SIZE PX_FORCE_INLINE PxU32 getNbObjects() const { return mNbFree + mCoreNbObjects; } #else PX_FORCE_INLINE PxU32 getNbObjects() const { return mCoreNbObjects; } #endif // private: PxU32 mCoreNbObjects; // Current number of objects in core arrays PxU32 mCoreCapacity; // Capacity of core arrays PxBounds3* mCoreBoxes; // Core array PrunerPayload* mCoreObjects; // Core array PxTransform* mCoreTransforms; PxU32* mCoreRemap; // Remaps core index to sorted index, i.e. sortedIndex = mCoreRemap[coreIndex] BucketBox* mSortedWorldBoxes; // Sorted array PrunerPayload* mSortedObjects; // Sorted array PxTransform* mSortedTransforms; #ifdef FREE_PRUNER_SIZE PxU32 mNbFree; // Current number of objects in the "free array" (mFreeObjects/mFreeBounds) PrunerPayload mFreeObjects[FREE_PRUNER_SIZE]; // mNbFree objects are stored here PxBounds3 mFreeBounds[FREE_PRUNER_SIZE]; // mNbFree object bounds are stored here PxTransform mFreeTransforms[FREE_PRUNER_SIZE]; // mNbFree transforms are stored here PxU32 mFreeStamps[FREE_PRUNER_SIZE]; #endif BucketPrunerMap mMap; // Maps (PrunerPayload) object to corresponding index in core array. // Objects in the free array do not appear in this map. PxU32 mSortedNb; PxU32 mSortedCapacity; PxU32 mSortAxis; BucketBox mGlobalBox; // Global bounds around all objects in the structure (except the ones in the "free" array) BucketPrunerNode mLevel1; BucketPrunerNode mLevel2[5]; BucketPrunerNode mLevel3[5][5]; bool mDirty; bool mOwnMemory; private: PX_PHYSX_COMMON_API void classifyBoxes(); void allocateSortedMemory(PxU32 nb); void resizeCore(); PX_FORCE_INLINE void addObjectInternal(const PrunerPayload& object, const PxBounds3& worldAABB, const PxTransform& transform, PxU32 timeStamp); }; #if PX_VC #pragma warning(pop) #endif class BucketPruner : public Pruner { public: PX_PHYSX_COMMON_API BucketPruner(PxU64 contextID); virtual ~BucketPruner(); // BasePruner DECLARE_BASE_PRUNER_API //~BasePruner // Pruner DECLARE_PRUNER_API_COMMON //~Pruner private: BucketPrunerCore mCore; PruningPool mPool; }; } } #endif