// // 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-2018 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 "foundation/PxProfiler.h" #include "PsHash.h" #include "BpBroadPhaseMBP.h" #include "CmRadixSortBuffered.h" #include "CmUtils.h" #include "PsUtilities.h" #include "PsFoundation.h" #include "PsVecMath.h" using namespace physx::shdfnd::aos; //#define CHECK_NB_OVERLAPS #define USE_FULLY_INSIDE_FLAG //#define MBP_USE_NO_CMP_OVERLAP_3D // Seems slower //HWSCAN: reverse bits in fully-inside-flag bitmaps because the code gives us indices for which bits are set (and we want the opposite) #define HWSCAN using namespace physx; using namespace Bp; using namespace Cm; static PX_FORCE_INLINE MBP_Handle encodeHandle(MBP_ObjectIndex objectIndex, PxU32 flipFlop, bool isStatic) { /* objectIndex += objectIndex; objectIndex |= flipFlop; return objectIndex;*/ return (objectIndex<<2)|(flipFlop<<1)|PxU32(isStatic); } static PX_FORCE_INLINE MBP_ObjectIndex decodeHandle_Index(MBP_Handle handle) { // return handle>>1; return handle>>2; } static PX_FORCE_INLINE PxU32 decodeHandle_IsStatic(MBP_Handle handle) { return handle&1; } static PX_FORCE_INLINE void storeDwords(PxU32* dest, PxU32 nb, PxU32 value) { while(nb--) *dest++ = value; } #define MBP_ALLOC(x) PX_ALLOC(x, "MBP") #define MBP_ALLOC_TMP(x) PX_ALLOC_TEMP(x, "MBP_TMP") #define MBP_FREE(x) if(x) PX_FREE_AND_RESET(x) #define DELETESINGLE(x) if (x) { delete x; x = NULL; } #define DELETEARRAY(x) if (x) { delete []x; x = NULL; } #define INVALID_ID 0xffffffff typedef MBP_Index* MBP_Mapping; /* PX_FORCE_INLINE PxU32 encodeFloat(const float val) { // We may need to check on -0 and 0 // But it should make no practical difference. PxU32 ir = IR(val); if(ir & 0x80000000) //negative? ir = ~ir;//reverse sequence of negative numbers else ir |= 0x80000000; // flip sign return ir; }*/ struct RegionHandle : public Ps::UserAllocated { PxU16 mHandle; // Handle from region PxU16 mInternalBPHandle; // Index of region data within mRegions }; enum MBPFlags { MBP_FLIP_FLOP = (1<<1), MBP_REMOVED = (1<<2) // ### added for TA24714, not needed otherwise }; // We have one of those for each of the "200K" objects so we should optimize this size as much as possible struct MBP_Object : public Ps::UserAllocated { BpHandle mUserID; // Handle sent to us by the AABB manager PxU16 mNbHandles; // Number of regions the object is part of PxU16 mFlags; // MBPFlags ### only 1 bit used in the end PX_FORCE_INLINE bool getFlipFlop() const { return (mFlags & MBP_FLIP_FLOP)==0; } union { RegionHandle mHandle; PxU32 mHandlesIndex; }; }; // This one is used in each Region struct MBPEntry : public Ps::UserAllocated { PX_FORCE_INLINE MBPEntry() { mMBPHandle = INVALID_ID; } // ### mIndex could be PxU16 but beware, we store mFirstFree there PxU32 mIndex; // Out-to-in, maps user handle to internal array. mIndex indexes either the static or dynamic array. MBP_Handle mMBPHandle; // MBP-level handle (the one returned to users) #if PX_DEBUG bool mUpdated; #endif PX_FORCE_INLINE PxU32 isStatic() const { return decodeHandle_IsStatic(mMBPHandle); } }; /////////////////////////////////////////////////////////////////////////////// //#define BIT_ARRAY_STACK 512 static PX_FORCE_INLINE PxU32 bitsToDwords(PxU32 nbBits) { return (nbBits>>5) + ((nbBits&31) ? 1 : 0); } // Use that one instead of an array of bools. Takes less ram, nearly as fast [no bounds checkings and so on]. class BitArray { public: BitArray(); BitArray(PxU32 nbBits); ~BitArray(); bool init(PxU32 nbBits); void empty(); void resize(PxU32 nbBits); PX_FORCE_INLINE void setBitChecked(PxU32 bitNumber) { const PxU32 index = bitNumber>>5; if(index>=mSize) resize(bitNumber); mBits[index] |= 1<<(bitNumber&31); } PX_FORCE_INLINE void clearBitChecked(PxU32 bitNumber) { const PxU32 index = bitNumber>>5; if(index>=mSize) resize(bitNumber); mBits[index] &= ~(1<<(bitNumber&31)); } // Data management PX_FORCE_INLINE void setBit(PxU32 bitNumber) { mBits[bitNumber>>5] |= 1<<(bitNumber&31); } PX_FORCE_INLINE void clearBit(PxU32 bitNumber) { mBits[bitNumber>>5] &= ~(1<<(bitNumber&31)); } PX_FORCE_INLINE void toggleBit(PxU32 bitNumber) { mBits[bitNumber>>5] ^= 1<<(bitNumber&31); } PX_FORCE_INLINE void clearAll() { PxMemZero(mBits, mSize*4); } PX_FORCE_INLINE void setAll() { PxMemSet(mBits, 0xff, mSize*4); } // Data access PX_FORCE_INLINE Ps::IntBool isSet(PxU32 bitNumber) const { return Ps::IntBool(mBits[bitNumber>>5] & (1<<(bitNumber&31))); } PX_FORCE_INLINE Ps::IntBool isSetChecked(PxU32 bitNumber) const { const PxU32 index = bitNumber>>5; if(index>=mSize) return 0; return Ps::IntBool(mBits[index] & (1<<(bitNumber&31))); } PX_FORCE_INLINE const PxU32* getBits() const { return mBits; } PX_FORCE_INLINE PxU32 getSize() const { return mSize; } // PT: replicate Cm::BitMap stuff for temp testing PxU32 findLast() const { for(PxU32 i = mSize; i-- > 0;) { if(mBits[i]) return (i<<5)+Ps::highestSetBit(mBits[i]); } return PxU32(0); } protected: PxU32* mBits; //!< Array of bits PxU32 mSize; //!< Size of the array in dwords #ifdef BIT_ARRAY_STACK PxU32 mStack[BIT_ARRAY_STACK]; #endif }; /////////////////////////////////////////////////////////////////////////////// BitArray::BitArray() : mBits(NULL), mSize(0) { } BitArray::BitArray(PxU32 nbBits) : mBits(NULL), mSize(0) { init(nbBits); } BitArray::~BitArray() { empty(); } void BitArray::empty() { #ifdef BIT_ARRAY_STACK if(mBits!=mStack) #endif MBP_FREE(mBits); mBits = NULL; mSize = 0; } bool BitArray::init(PxU32 nbBits) { mSize = bitsToDwords(nbBits); // Get ram for n bits #ifdef BIT_ARRAY_STACK if(mBits!=mStack) #endif MBP_FREE(mBits); #ifdef BIT_ARRAY_STACK if(mSize>BIT_ARRAY_STACK) #endif mBits = reinterpret_cast(MBP_ALLOC(sizeof(PxU32)*mSize)); #ifdef BIT_ARRAY_STACK else mBits = mStack; #endif // Set all bits to 0 clearAll(); return true; } void BitArray::resize(PxU32 nbBits) { const PxU32 newSize = bitsToDwords(nbBits+128); PxU32* newBits = NULL; #ifdef BIT_ARRAY_STACK if(newSize>BIT_ARRAY_STACK) #endif { // Old buffer was stack or allocated, new buffer is allocated newBits = reinterpret_cast(MBP_ALLOC(sizeof(PxU32)*newSize)); if(mSize) PxMemCopy(newBits, mBits, sizeof(PxU32)*mSize); } #ifdef BIT_ARRAY_STACK else { newBits = mStack; if(mSize>BIT_ARRAY_STACK) { // Old buffer was allocated, new buffer is stack => copy to stack, shrink CopyMemory(newBits, mBits, sizeof(PxU32)*BIT_ARRAY_STACK); } else { // Old buffer was stack, new buffer is stack => keep working on the same stack buffer, nothing to do } } #endif const PxU32 remain = newSize - mSize; if(remain) PxMemZero(newBits + mSize, remain*sizeof(PxU32)); #ifdef BIT_ARRAY_STACK if(mBits!=mStack) #endif MBP_FREE(mBits); mBits = newBits; mSize = newSize; } #ifdef USE_FULLY_INSIDE_FLAG static PX_FORCE_INLINE void setBit(BitArray& bitmap, MBP_ObjectIndex objectIndex) { #ifdef HWSCAN bitmap.clearBitChecked(objectIndex); //HWSCAN #else bitmap.setBitChecked(objectIndex); #endif } static PX_FORCE_INLINE void clearBit(BitArray& bitmap, MBP_ObjectIndex objectIndex) { #ifdef HWSCAN bitmap.setBitChecked(objectIndex); // HWSCAN #else bitmap.clearBitChecked(objectIndex); #endif } #endif /////////////////////////////////////////////////////////////////////////////// #ifdef MBP_SIMD_OVERLAP typedef SIMD_AABB MBP_AABB; #else typedef IAABB MBP_AABB; #endif // PT: TODO: Consider sharing with AABB Manager dup now struct MBP_Pair : public Ps::UserAllocated { PxU32 id0; PxU32 id1; // TODO: optimize memory here bool isNew; bool isUpdated; }; struct MBPEntry; struct RegionHandle; struct MBP_Object; class MBP_PairManager : public Ps::UserAllocated { public: MBP_PairManager(); ~MBP_PairManager(); void purge(); void shrinkMemory(); MBP_Pair* addPair (PxU32 id0, PxU32 id1); bool removePair (PxU32 id0, PxU32 id1); bool computeCreatedDeletedPairs (const MBP_Object* objects, BroadPhaseMBP* mbp, const BitArray& updated, const BitArray& removed); PX_FORCE_INLINE PxU32 getPairIndex (const MBP_Pair* pair) const { return (PxU32((size_t(pair) - size_t(mActivePairs)))/sizeof(MBP_Pair)); } PxU32 mHashSize; PxU32 mMask; PxU32 mNbActivePairs; PxU32* mHashTable; PxU32* mNext; MBP_Pair* mActivePairs; PxU32 mReservedMemory; const Bp::FilterGroup::Enum* mGroups; const MBP_Object* mObjects; #ifdef BP_FILTERING_USES_TYPE_IN_GROUP const bool* mLUT; #endif PX_FORCE_INLINE MBP_Pair* findPair(PxU32 id0, PxU32 id1, PxU32 hashValue) const; void removePair(PxU32 id0, PxU32 id1, PxU32 hashValue, PxU32 pairIndex); void reallocPairs(); void reserveMemory(PxU32 memSize); }; /////////////////////////////////////////////////////////////////////////// #define STACK_BUFFER_SIZE 256 struct MBPOS_TmpBuffers { MBPOS_TmpBuffers(); ~MBPOS_TmpBuffers(); void allocateSleeping(PxU32 nbSleeping, PxU32 nbSentinels); void allocateUpdated(PxU32 nbUpdated, PxU32 nbSentinels); // PT: wtf, why doesn't the 128 version compile? // MBP_AABB PX_ALIGN(128, mSleepingDynamicBoxes_Stack[STACK_BUFFER_SIZE]); // MBP_AABB PX_ALIGN(128, mUpdatedDynamicBoxes_Stack[STACK_BUFFER_SIZE]); MBP_AABB PX_ALIGN(16, mSleepingDynamicBoxes_Stack[STACK_BUFFER_SIZE]); MBP_AABB PX_ALIGN(16, mUpdatedDynamicBoxes_Stack[STACK_BUFFER_SIZE]); MBP_Index mInToOut_Dynamic_Sleeping_Stack[STACK_BUFFER_SIZE]; PxU32 mNbSleeping; PxU32 mNbUpdated; MBP_Index* mInToOut_Dynamic_Sleeping; MBP_AABB* mSleepingDynamicBoxes; MBP_AABB* mUpdatedDynamicBoxes; }; struct BIP_Input { BIP_Input() : mObjects (NULL), mNbUpdatedBoxes (0), mNbStaticBoxes (0), mDynamicBoxes (NULL), mStaticBoxes (NULL), mInToOut_Static (NULL), mInToOut_Dynamic(NULL), mNeeded (false) { } const MBPEntry* mObjects; PxU32 mNbUpdatedBoxes; PxU32 mNbStaticBoxes; const MBP_AABB* mDynamicBoxes; const MBP_AABB* mStaticBoxes; const MBP_Index* mInToOut_Static; const MBP_Index* mInToOut_Dynamic; bool mNeeded; }; struct BoxPruning_Input { BoxPruning_Input() : mObjects (NULL), mUpdatedDynamicBoxes (NULL), mSleepingDynamicBoxes (NULL), mInToOut_Dynamic (NULL), mInToOut_Dynamic_Sleeping (NULL), mNbUpdated (0), mNbNonUpdated (0), mNeeded (false) { } const MBPEntry* mObjects; const MBP_AABB* mUpdatedDynamicBoxes; const MBP_AABB* mSleepingDynamicBoxes; const MBP_Index* mInToOut_Dynamic; const MBP_Index* mInToOut_Dynamic_Sleeping; PxU32 mNbUpdated; PxU32 mNbNonUpdated; bool mNeeded; BIP_Input mBIPInput; }; class Region : public Ps::UserAllocated { PX_NOCOPY(Region) public: Region(); ~Region(); void updateObject(const MBP_AABB& bounds, MBP_Index handle); MBP_Index addObject(const MBP_AABB& bounds, MBP_Handle mbpHandle, bool isStatic); void removeObject(MBP_Index handle); MBP_Handle retrieveBounds(MBP_AABB& bounds, MBP_Index handle) const; void setBounds(MBP_Index handle, const MBP_AABB& bounds); void prepareOverlaps(); void findOverlaps(MBP_PairManager& pairManager); // private: BoxPruning_Input PX_ALIGN(16, mInput); PxU32 mNbObjects; PxU32 mMaxNbObjects; PxU32 mFirstFree; MBPEntry* mObjects; // All objects, indexed by user handle PxU32 mMaxNbStaticBoxes; PxU32 mNbStaticBoxes; PxU32 mMaxNbDynamicBoxes; PxU32 mNbDynamicBoxes; MBP_AABB* mStaticBoxes; MBP_AABB* mDynamicBoxes; MBP_Mapping mInToOut_Static; // Maps static boxes to mObjects MBP_Mapping mInToOut_Dynamic; // Maps dynamic boxes to mObjects PxU32* mPosList; PxU32 mNbUpdatedBoxes; PxU32 mPrevNbUpdatedBoxes; BitArray mStaticBits; RadixSortBuffered mRS; bool mNeedsSorting; bool mNeedsSortingSleeping; MBPOS_TmpBuffers mTmpBuffers; void optimizeMemory(); void resizeObjects(); void staticSort(); void preparePruning(MBPOS_TmpBuffers& buffers); void prepareBIPPruning(const MBPOS_TmpBuffers& buffers); }; /////////////////////////////////////////////////////////////////////////// // We have one of those for each Region within the MBP struct RegionData : public Ps::UserAllocated { MBP_AABB mBox; // Volume of space controlled by this Region Region* mBP; // Pointer to Region itself Ps::IntBool mOverlap; // True if overlaps other regions void* mUserData; // Region identifier, reused to contain "first free ID" }; #define MAX_NB_MBP 256 // #define MAX_NB_MBP 16 class MBP : public Ps::UserAllocated { public: MBP(); ~MBP(); void preallocate(PxU32 nbRegions, PxU32 nbObjects, PxU32 maxNbOverlaps); void reset(); void freeBuffers(); PxU32 addRegion(const PxBroadPhaseRegion& region, bool populateRegion); bool removeRegion(PxU32 handle); const Region* getRegion(PxU32 i) const; PX_FORCE_INLINE PxU32 getNbRegions() const { return mNbRegions; } MBP_Handle addObject(const MBP_AABB& box, BpHandle userID, bool isStatic); bool removeObject(MBP_Handle handle); bool updateObject(MBP_Handle handle, const MBP_AABB& box); bool updateObjectAfterRegionRemoval(MBP_Handle handle, Region* removedRegion); bool updateObjectAfterNewRegionAdded(MBP_Handle handle, const MBP_AABB& box, Region* addedRegion, PxU32 regionIndex); void prepareOverlaps(); void findOverlaps(const Bp::FilterGroup::Enum* PX_RESTRICT groups #ifdef BP_FILTERING_USES_TYPE_IN_GROUP , const bool* PX_RESTRICT lut #endif ); PxU32 finalize(BroadPhaseMBP* mbp); void shiftOrigin(const PxVec3& shift); void setTransientBounds(const PxBounds3* bounds, const PxReal* contactDistance); // private: PxU32 mNbRegions; MBP_ObjectIndex mFirstFreeIndex; // First free recycled index for mMBP_Objects PxU32 mFirstFreeIndexBP; // First free recycled index for mRegions Ps::Array mRegions; Ps::Array mMBP_Objects; MBP_PairManager mPairManager; BitArray mUpdatedObjects; // Indexed by MBP_ObjectIndex BitArray mRemoved; // Indexed by MBP_ObjectIndex Ps::Array mHandles[MAX_NB_MBP+1]; PxU32 mFirstFree[MAX_NB_MBP+1]; PX_FORCE_INLINE RegionHandle* getHandles(MBP_Object& currentObject, PxU32 nbHandles); void purgeHandles(MBP_Object* PX_RESTRICT object, PxU32 nbHandles); void storeHandles(MBP_Object* PX_RESTRICT object, PxU32 nbHandles, const RegionHandle* PX_RESTRICT handles); Ps::Array mOutOfBoundsObjects; // These are BpHandle but the BP interface expects PxU32s void addToOutOfBoundsArray(BpHandle id); const PxBounds3* mTransientBounds; const PxReal* mTransientContactDistance; #ifdef USE_FULLY_INSIDE_FLAG BitArray mFullyInsideBitmap; // Indexed by MBP_ObjectIndex #endif void populateNewRegion(const MBP_AABB& box, Region* addedRegion, PxU32 regionIndex); #ifdef MBP_REGION_BOX_PRUNING void buildRegionData(); MBP_AABB mSortedRegionBoxes[MAX_NB_MBP]; PxU32 mSortedRegionIndices[MAX_NB_MBP]; PxU32 mNbActiveRegions; bool mDirtyRegions; #endif }; #ifdef MBP_SIMD_OVERLAP #define MBP_OVERLAP_TEST(x) SIMD_OVERLAP_TEST(x) #else #define MBP_OVERLAP_TEST(x) if(intersect2D(box0, x)) #endif #define DEFAULT_NB_ENTRIES 128 #define INVALID_USER_ID 0xffffffff #ifdef MBP_SIMD_OVERLAP static PX_FORCE_INLINE void initSentinel(SIMD_AABB& box) { // box.mMinX = encodeFloat(FLT_MAX)>>1; box.mMinX = 0xffffffff; } #if PX_DEBUG static PX_FORCE_INLINE bool isSentinel(const SIMD_AABB& box) { return box.mMinX == 0xffffffff; } #endif #else static PX_FORCE_INLINE void initSentinel(MBP_AABB& box) { // box.mMinX = encodeFloat(FLT_MAX)>>1; box.mMinX = 0xffffffff; } #if PX_DEBUG static PX_FORCE_INLINE bool isSentinel(const MBP_AABB& box) { return box.mMinX == 0xffffffff; } #endif #endif /////////////////////////////////////////////////////////////////////////////// static PX_FORCE_INLINE void sort(PxU32& id0, PxU32& id1) { if(id0>id1) Ps::swap(id0, id1); } static PX_FORCE_INLINE bool differentPair(const MBP_Pair& p, PxU32 id0, PxU32 id1) { return (id0!=p.id0) || (id1!=p.id1); } /////////////////////////////////////////////////////////////////////////////// MBP_PairManager::MBP_PairManager() : mHashSize (0), mMask (0), mNbActivePairs (0), mHashTable (NULL), mNext (NULL), mActivePairs (NULL), mReservedMemory (0), mGroups (NULL), mObjects (NULL) #ifdef BP_FILTERING_USES_TYPE_IN_GROUP ,mLUT (NULL) #endif { } /////////////////////////////////////////////////////////////////////////////// MBP_PairManager::~MBP_PairManager() { purge(); } /////////////////////////////////////////////////////////////////////////////// void MBP_PairManager::purge() { MBP_FREE(mNext); MBP_FREE(mActivePairs); MBP_FREE(mHashTable); mHashSize = 0; mMask = 0; mNbActivePairs = 0; } /////////////////////////////////////////////////////////////////////////////// static PX_FORCE_INLINE PxU32 hash(PxU32 id0, PxU32 id1) { return PxU32(Ps::hash( (id0&0xffff)|(id1<<16)) ); } /////////////////////////////////////////////////////////////////////////////// // Internal version saving hash computation PX_FORCE_INLINE MBP_Pair* MBP_PairManager::findPair(PxU32 id0, PxU32 id1, PxU32 hashValue) const { if(!mHashTable) return NULL; // Nothing has been allocated yet MBP_Pair* PX_RESTRICT activePairs = mActivePairs; const PxU32* PX_RESTRICT next = mNext; // Look for it in the table PxU32 offset = mHashTable[hashValue]; while(offset!=INVALID_ID && differentPair(activePairs[offset], id0, id1)) { PX_ASSERT(activePairs[offset].id0!=INVALID_USER_ID); offset = next[offset]; // Better to have a separate array for this } if(offset==INVALID_ID) return NULL; PX_ASSERT(offset the pair is persistent return &activePairs[offset]; } /////////////////////////////////////////////////////////////////////////////// MBP_Pair* MBP_PairManager::addPair(PxU32 id0, PxU32 id1) { PX_ASSERT(id0!=INVALID_ID); PX_ASSERT(id1!=INVALID_ID); PX_ASSERT(mGroups); PX_ASSERT(mObjects); { const MBP_ObjectIndex index0 = decodeHandle_Index(id0); const MBP_ObjectIndex index1 = decodeHandle_Index(id1); const BpHandle object0 = mObjects[index0].mUserID; const BpHandle object1 = mObjects[index1].mUserID; #ifdef BP_FILTERING_USES_TYPE_IN_GROUP if(!groupFiltering(mGroups[object0], mGroups[object1], mLUT)) #else if(!groupFiltering(mGroups[object0], mGroups[object1])) #endif return NULL; } // Order the ids sort(id0, id1); const PxU32 fullHashValue = hash(id0, id1); PxU32 hashValue = fullHashValue & mMask; { MBP_Pair* PX_RESTRICT p = findPair(id0, id1, hashValue); if(p) { p->isUpdated = true; return p; // Persistent pair } } // This is a new pair if(mNbActivePairs >= mHashSize) { // Get more entries mHashSize = Ps::nextPowerOfTwo(mNbActivePairs+1); mMask = mHashSize-1; reallocPairs(); // Recompute hash value with new hash size hashValue = fullHashValue & mMask; } MBP_Pair* PX_RESTRICT p = &mActivePairs[mNbActivePairs]; p->id0 = id0; // ### CMOVs would be nice here p->id1 = id1; p->isNew = true; p->isUpdated= false; mNext[mNbActivePairs] = mHashTable[hashValue]; mHashTable[hashValue] = mNbActivePairs++; return p; } /////////////////////////////////////////////////////////////////////////////// void MBP_PairManager::removePair(PxU32 /*id0*/, PxU32 /*id1*/, PxU32 hashValue, PxU32 pairIndex) { // Walk the hash table to fix mNext { PxU32 offset = mHashTable[hashValue]; PX_ASSERT(offset!=INVALID_ID); PxU32 previous=INVALID_ID; while(offset!=pairIndex) { previous = offset; offset = mNext[offset]; } // Let us go/jump us if(previous!=INVALID_ID) { PX_ASSERT(mNext[previous]==pairIndex); mNext[previous] = mNext[pairIndex]; } // else we were the first else mHashTable[hashValue] = mNext[pairIndex]; // we're now free to reuse mNext[pairIndex] without breaking the list } #if PX_DEBUG mNext[pairIndex]=INVALID_ID; #endif // Invalidate entry // Fill holes { // 1) Remove last pair const PxU32 lastPairIndex = mNbActivePairs-1; if(lastPairIndex==pairIndex) { mNbActivePairs--; } else { const MBP_Pair* last = &mActivePairs[lastPairIndex]; const PxU32 lastHashValue = hash(last->id0, last->id1) & mMask; // Walk the hash table to fix mNext PxU32 offset = mHashTable[lastHashValue]; PX_ASSERT(offset!=INVALID_ID); PxU32 previous=INVALID_ID; while(offset!=lastPairIndex) { previous = offset; offset = mNext[offset]; } // Let us go/jump us if(previous!=INVALID_ID) { PX_ASSERT(mNext[previous]==lastPairIndex); mNext[previous] = mNext[lastPairIndex]; } // else we were the first else mHashTable[lastHashValue] = mNext[lastPairIndex]; // we're now free to reuse mNext[lastPairIndex] without breaking the list #if PX_DEBUG mNext[lastPairIndex]=INVALID_ID; #endif // Don't invalidate entry since we're going to shrink the array // 2) Re-insert in free slot mActivePairs[pairIndex] = mActivePairs[lastPairIndex]; #if PX_DEBUG PX_ASSERT(mNext[pairIndex]==INVALID_ID); #endif mNext[pairIndex] = mHashTable[lastHashValue]; mHashTable[lastHashValue] = pairIndex; mNbActivePairs--; } } } /////////////////////////////////////////////////////////////////////////////// bool MBP_PairManager::removePair(PxU32 id0, PxU32 id1) { // Order the ids sort(id0, id1); const PxU32 hashValue = hash(id0, id1) & mMask; const MBP_Pair* p = findPair(id0, id1, hashValue); if(!p) return false; PX_ASSERT(p->id0==id0); PX_ASSERT(p->id1==id1); removePair(id0, id1, hashValue, getPairIndex(p)); shrinkMemory(); return true; } /////////////////////////////////////////////////////////////////////////////// void MBP_PairManager::shrinkMemory() { // Check correct memory against actually used memory const PxU32 correctHashSize = Ps::nextPowerOfTwo(mNbActivePairs); if(mHashSize==correctHashSize) return; if(mReservedMemory && correctHashSize < mReservedMemory) return; // Reduce memory used mHashSize = correctHashSize; mMask = mHashSize-1; reallocPairs(); } /////////////////////////////////////////////////////////////////////////////// void MBP_PairManager::reallocPairs() { MBP_FREE(mHashTable); mHashTable = reinterpret_cast(MBP_ALLOC(mHashSize*sizeof(PxU32))); storeDwords(mHashTable, mHashSize, INVALID_ID); // Get some bytes for new entries MBP_Pair* newPairs = reinterpret_cast(MBP_ALLOC(mHashSize * sizeof(MBP_Pair))); PX_ASSERT(newPairs); PxU32* newNext = reinterpret_cast(MBP_ALLOC(mHashSize * sizeof(PxU32))); PX_ASSERT(newNext); // Copy old data if needed if(mNbActivePairs) PxMemCopy(newPairs, mActivePairs, mNbActivePairs*sizeof(MBP_Pair)); // ### check it's actually needed... probably only for pairs whose hash value was cut by the and // yeah, since hash(id0, id1) is a constant // However it might not be needed to recompute them => only less efficient but still ok for(PxU32 i=0;i(&box0.mMinY)); \ b = _mm_shuffle_epi32(b, 78); // PT: technically we don't need the 16 bits from _mm_movemask_epi8, we only // need the 4 bits from _mm_movemask_ps. Would it be faster? In any case this // works thanks to the _mm_cmpgt_epi32 which puts the same values in each byte // of each separate 32bits components. #define SIMD_OVERLAP_TEST(x) \ const __m128i a = _mm_loadu_si128(reinterpret_cast(&x.mMinY)); \ const __m128i d = _mm_cmpgt_epi32(a, b); \ const int mask = _mm_movemask_epi8(d); \ if(mask==0x0000ff00) #else #define SIMD_OVERLAP_PRELOAD_BOX0 #endif #ifdef MBP_USE_NO_CMP_OVERLAP /*static PX_FORCE_INLINE void initBox(IAABB& box, const PxBounds3& src) { box.initFrom2(src); }*/ #else static PX_FORCE_INLINE void initBox(IAABB& box, const PxBounds3& src) { box.initFrom(src); } #endif Region::Region() : mNbObjects (0), mMaxNbObjects (0), mFirstFree (INVALID_ID), mObjects (NULL), mMaxNbStaticBoxes (0), mNbStaticBoxes (0), mMaxNbDynamicBoxes (0), mNbDynamicBoxes (0), mStaticBoxes (NULL), mDynamicBoxes (NULL), mInToOut_Static (NULL), mInToOut_Dynamic (NULL), mPosList (NULL), mNbUpdatedBoxes (0), mPrevNbUpdatedBoxes (0), mNeedsSorting (false), mNeedsSortingSleeping (true) { } Region::~Region() { DELETEARRAY(mObjects); MBP_FREE(mPosList); MBP_FREE(mInToOut_Dynamic); MBP_FREE(mInToOut_Static); DELETEARRAY(mDynamicBoxes); DELETEARRAY(mStaticBoxes); } // Pre-sort static boxes #define STACK_BUFFER_SIZE_STATIC_SORT 8192 #define DEFAULT_NUM_DYNAMIC_BOXES 1024 void Region::staticSort() { // For now this version is only compatible with: // MBP_USE_WORDS // MBP_USE_SENTINELS mNeedsSorting = false; const PxU32 nbStaticBoxes = mNbStaticBoxes; if(!nbStaticBoxes) { mStaticBits.empty(); return; } // PxU32 Time; // StartProfile(Time); // Roadmap: // - gather updated/modified static boxes // - sort those, and those only // - merge sorted set with previously existing (and previously sorted set) // Separate things-to-sort and things-already-sorted const PxU32 totalSize = sizeof(PxU32)*nbStaticBoxes*4; PxU8 stackBuffer[STACK_BUFFER_SIZE_STATIC_SORT]; PxU8* tempMemory = totalSize<=STACK_BUFFER_SIZE_STATIC_SORT ? stackBuffer : reinterpret_cast(MBP_ALLOC_TMP(totalSize)); PxU32* minPosList_ToSort = reinterpret_cast(tempMemory); PxU32* minPosList_Sorted = reinterpret_cast(tempMemory + sizeof(PxU32)*nbStaticBoxes); PxU32* boxIndices_ToSort = reinterpret_cast(tempMemory + sizeof(PxU32)*nbStaticBoxes*2); PxU32* boxIndices_Sorted = reinterpret_cast(tempMemory + sizeof(PxU32)*nbStaticBoxes*3); PxU32 nbToSort = 0; PxU32 nbSorted = 0; for(PxU32 i=0;i(MBP_ALLOC(sizeof(MBP_Index)*mMaxNbStaticBoxes)); const PxU32 nbStaticSentinels = 2; MBP_AABB* sortedBoxes = PX_NEW(MBP_AABB)[mMaxNbStaticBoxes+nbStaticSentinels]; initSentinel(sortedBoxes[nbStaticBoxes]); initSentinel(sortedBoxes[nbStaticBoxes+1]); // EndProfile(Time); // printf("Part2b: %d\n", Time); // StartProfile(Time); // Merge streams to final buffers PxU32 offsetSorted = 0; PxU32 offsetNonSorted = 0; PxU32 nextCandidateNonSorted = offsetNonSorted(MBP_ALLOC(sizeof(MBP_Index)*newNbBoxes)); if(oldNbBoxes) PxMemCopy(newMapping, mapping, oldNbBoxes*sizeof(MBP_Index)); MBP_FREE(mapping); return newMapping; } static PX_FORCE_INLINE void MTF(MBP_AABB* PX_RESTRICT dynamicBoxes, MBP_Index* PX_RESTRICT inToOut_Dynamic, MBPEntry* PX_RESTRICT objects, const MBP_AABB& bounds, PxU32 frontIndex, MBPEntry& updatedObject) { const PxU32 updatedIndex = updatedObject.mIndex; if(frontIndex!=updatedIndex) { const MBP_AABB box0 = dynamicBoxes[frontIndex]; dynamicBoxes[frontIndex] = bounds; dynamicBoxes[updatedIndex] = box0; const MBP_Index index0 = inToOut_Dynamic[frontIndex]; inToOut_Dynamic[frontIndex] = inToOut_Dynamic[updatedIndex]; inToOut_Dynamic[updatedIndex] = index0; objects[index0].mIndex = updatedIndex; updatedObject.mIndex = frontIndex; } else { dynamicBoxes[frontIndex] = bounds; } } MBP_Index Region::addObject(const MBP_AABB& bounds, MBP_Handle mbpHandle, bool isStatic) { #ifdef MBP_USE_WORDS PX_ASSERT(mNbObjects<0xffff); #endif PX_ASSERT((decodeHandle_IsStatic(mbpHandle) && isStatic) || (!decodeHandle_IsStatic(mbpHandle) && !isStatic)); MBP_Index handle; if(mFirstFree!=INVALID_ID) { handle = MBP_Index(mFirstFree); mFirstFree = mObjects[handle].mIndex; } else { if(mMaxNbObjects==mNbObjects) resizeObjects(); handle = MBP_Index(mNbObjects); } mNbObjects++; /// PxU32 boxIndex; if(isStatic) { if(mMaxNbStaticBoxes==mNbStaticBoxes) { const PxU32 newMaxNbBoxes = mMaxNbStaticBoxes ? mMaxNbStaticBoxes + DEFAULT_NB_ENTRIES : DEFAULT_NB_ENTRIES; // const PxU32 newMaxNbBoxes = mMaxNbStaticBoxes ? mMaxNbStaticBoxes*2 : DEFAULT_NB_ENTRIES; mStaticBoxes = resizeBoxes(mNbStaticBoxes, newMaxNbBoxes, mStaticBoxes); mInToOut_Static = resizeMapping(mNbStaticBoxes, newMaxNbBoxes, mInToOut_Static); mMaxNbStaticBoxes = newMaxNbBoxes; } boxIndex = mNbStaticBoxes++; mStaticBoxes[boxIndex] = bounds; mInToOut_Static[boxIndex] = handle; mNeedsSorting = true; mStaticBits.setBitChecked(boxIndex); } else { if(mMaxNbDynamicBoxes==mNbDynamicBoxes) { const PxU32 newMaxNbBoxes = mMaxNbDynamicBoxes ? mMaxNbDynamicBoxes + DEFAULT_NB_ENTRIES : DEFAULT_NB_ENTRIES; // const PxU32 newMaxNbBoxes = mMaxNbDynamicBoxes ? mMaxNbDynamicBoxes*2 : DEFAULT_NB_ENTRIES; mDynamicBoxes = resizeBoxes(mNbDynamicBoxes, newMaxNbBoxes, mDynamicBoxes); mInToOut_Dynamic = resizeMapping(mNbDynamicBoxes, newMaxNbBoxes, mInToOut_Dynamic); mMaxNbDynamicBoxes = newMaxNbBoxes; MBP_FREE(mPosList); mPosList = reinterpret_cast(MBP_ALLOC((newMaxNbBoxes+1)*sizeof(PxU32))); } boxIndex = mNbDynamicBoxes++; mDynamicBoxes[boxIndex] = bounds; mInToOut_Dynamic[boxIndex] = handle; } mObjects[handle].mIndex = boxIndex; mObjects[handle].mMBPHandle = mbpHandle; #if PX_DEBUG mObjects[handle].mUpdated = !isStatic; #endif if(!isStatic) { MTF(mDynamicBoxes, mInToOut_Dynamic, mObjects, bounds, mNbUpdatedBoxes, mObjects[handle]); mNbUpdatedBoxes++; mPrevNbUpdatedBoxes = 0; mNeedsSortingSleeping = true; PX_ASSERT(mNbUpdatedBoxes<=mNbDynamicBoxes); } return handle; } // Moves box 'lastIndex' to location 'removedBoxIndex' static PX_FORCE_INLINE void remove(MBPEntry* PX_RESTRICT objects, MBP_Index* PX_RESTRICT mapping, MBP_AABB* PX_RESTRICT boxes, PxU32 removedBoxIndex, PxU32 lastIndex) { const PxU32 movedBoxHandle = mapping[lastIndex]; boxes[removedBoxIndex] = boxes[lastIndex]; // Relocate box data mapping[removedBoxIndex] = MBP_Index(movedBoxHandle); // Relocate mapping data MBPEntry& movedObject = objects[movedBoxHandle]; PX_ASSERT(movedObject.mIndex==lastIndex); // Checks index of moved box was indeed its old location movedObject.mIndex = removedBoxIndex; // Adjust index of moved box to reflect its new location } void Region::removeObject(MBP_Index handle) { PX_ASSERT(handle>1)|(bits2>>2)|(bits3>>3); return !mask; /* const PxU32 d0 = (b.mMaxY<<16)|a.mMaxY; const PxU32 d0b = (b.mMaxZ<<16)|a.mMaxZ; const PxU32 d1 = (a.mMinY<<16)|b.mMinY; const PxU32 d1b = (a.mMinZ<<16)|b.mMinZ; const PxU32 mask = (d0 - d1) | (d0b - d1b); return !(mask & 0x80008000);*/ #else if(//mMaxX < a.mMinX || a.mMaxX < mMinX // || b.mMaxY < a.mMinY || a.mMaxY < b.mMinY || b.mMaxZ < a.mMinZ || a.mMaxZ < b.mMinZ ) return FALSE; return TRUE; #endif } #endif #ifdef MBP_USE_NO_CMP_OVERLAP_3D static PX_FORCE_INLINE bool intersect3D(const MBP_AABB& a, const MBP_AABB& b) { // PT: warning, only valid with the special encoding in InitFrom2 const PxU32 bits0 = (b.mMaxY - a.mMinY)&0x80000000; const PxU32 bits1 = (b.mMaxZ - a.mMinZ)&0x80000000; const PxU32 bits2 = (a.mMaxY - b.mMinY)&0x80000000; const PxU32 bits3 = (a.mMaxZ - b.mMinZ)&0x80000000; const PxU32 bits4 = (b.mMaxX - a.mMinX)&0x80000000; const PxU32 bits5 = (a.mMaxX - b.mMinX)&0x80000000; const PxU32 mask = bits0|(bits1>>1)|(bits2>>2)|(bits3>>3)|(bits4>>4)|(bits5>>5); return !mask; } #endif #ifdef CHECK_NB_OVERLAPS static PxU32 gNbOverlaps = 0; #endif static PX_FORCE_INLINE void outputPair( MBP_PairManager& pairManager, PxU32 index0, PxU32 index1, const MBP_Index* PX_RESTRICT inToOut0, const MBP_Index* PX_RESTRICT inToOut1, const MBPEntry* PX_RESTRICT objects) { #ifdef CHECK_NB_OVERLAPS gNbOverlaps++; #endif const MBP_Index objectIndex0 = inToOut0[index0]; const MBP_Index objectIndex1 = inToOut1[index1]; PX_ASSERT(objectIndex0!=objectIndex1); const MBP_Handle id0 = objects[objectIndex0].mMBPHandle; const MBP_Handle id1 = objects[objectIndex1].mMBPHandle; // printf("2: %d %d\n", index0, index1); // printf("3: %d %d\n", objectIndex0, objectIndex1); pairManager.addPair(id0, id1); } MBPOS_TmpBuffers::MBPOS_TmpBuffers() : mNbSleeping (0), mNbUpdated (0), mInToOut_Dynamic_Sleeping (NULL), mSleepingDynamicBoxes (NULL), mUpdatedDynamicBoxes (NULL) { } MBPOS_TmpBuffers::~MBPOS_TmpBuffers() { // printf("mNbSleeping: %d\n", mNbSleeping); if(mInToOut_Dynamic_Sleeping!=mInToOut_Dynamic_Sleeping_Stack) MBP_FREE(mInToOut_Dynamic_Sleeping); if(mSleepingDynamicBoxes!=mSleepingDynamicBoxes_Stack) DELETEARRAY(mSleepingDynamicBoxes); if(mUpdatedDynamicBoxes!=mUpdatedDynamicBoxes_Stack) DELETEARRAY(mUpdatedDynamicBoxes); mNbSleeping = 0; mNbUpdated = 0; } void MBPOS_TmpBuffers::allocateSleeping(PxU32 nbSleeping, PxU32 nbSentinels) { if(nbSleeping>mNbSleeping) { if(mInToOut_Dynamic_Sleeping!=mInToOut_Dynamic_Sleeping_Stack) MBP_FREE(mInToOut_Dynamic_Sleeping); if(mSleepingDynamicBoxes!=mSleepingDynamicBoxes_Stack) DELETEARRAY(mSleepingDynamicBoxes); if(nbSleeping+nbSentinels<=STACK_BUFFER_SIZE) { mSleepingDynamicBoxes = mSleepingDynamicBoxes_Stack; mInToOut_Dynamic_Sleeping = mInToOut_Dynamic_Sleeping_Stack; } else { mSleepingDynamicBoxes = PX_NEW_TEMP(MBP_AABB)[nbSleeping+nbSentinels]; mInToOut_Dynamic_Sleeping = reinterpret_cast(MBP_ALLOC(sizeof(MBP_Index)*nbSleeping)); } mNbSleeping = nbSleeping; } } void MBPOS_TmpBuffers::allocateUpdated(PxU32 nbUpdated, PxU32 nbSentinels) { if(nbUpdated>mNbUpdated) { if(mUpdatedDynamicBoxes!=mUpdatedDynamicBoxes_Stack) DELETEARRAY(mUpdatedDynamicBoxes); if(nbUpdated+nbSentinels<=STACK_BUFFER_SIZE) mUpdatedDynamicBoxes = mUpdatedDynamicBoxes_Stack; else mUpdatedDynamicBoxes = PX_NEW_TEMP(MBP_AABB)[nbUpdated+nbSentinels]; mNbUpdated = nbUpdated; } } void Region::preparePruning(MBPOS_TmpBuffers& buffers) { PxU32 _saved = mNbUpdatedBoxes; mNbUpdatedBoxes = 0; if(mPrevNbUpdatedBoxes!=_saved) mNeedsSortingSleeping = true; PxU32 nb = mNbDynamicBoxes; if(!nb) { mInput.mNeeded = false; mPrevNbUpdatedBoxes = 0; mNeedsSortingSleeping = true; return; } const MBP_AABB* PX_RESTRICT dynamicBoxes = mDynamicBoxes; PxU32* PX_RESTRICT posList = mPosList; #if PX_DEBUG PxU32 verifyNbUpdated = 0; for(PxU32 i=0;i(mRS.GetRecyclable()); for(PxU32 i=0;i buffers.mUpdatedDynamicBoxes; mInput.mSleepingDynamicBoxes = sleepingDynamicBoxes; mInput.mInToOut_Dynamic = inToOut_Dynamic; // Can be shared (3) => (MBP_Index*)mRS.GetRecyclable(); mInput.mInToOut_Dynamic_Sleeping = inToOut_Dynamic_Sleeping; mInput.mNbUpdated = nbUpdated; // Can be shared (4) mInput.mNbNonUpdated = nbNonUpdated; mInput.mNeeded = true; } void Region::prepareBIPPruning(const MBPOS_TmpBuffers& buffers) { if(!mNbUpdatedBoxes || !mNbStaticBoxes) { mInput.mBIPInput.mNeeded = false; return; } mInput.mBIPInput.mObjects = mObjects; // Can be shared (1) mInput.mBIPInput.mNbUpdatedBoxes = mNbUpdatedBoxes; // Can be shared (4) mInput.mBIPInput.mNbStaticBoxes = mNbStaticBoxes; // mInput.mBIPInput.mDynamicBoxes = mDynamicBoxes; mInput.mBIPInput.mDynamicBoxes = buffers.mUpdatedDynamicBoxes; // Can be shared (2) mInput.mBIPInput.mStaticBoxes = mStaticBoxes; mInput.mBIPInput.mInToOut_Static = mInToOut_Static; mInput.mBIPInput.mInToOut_Dynamic = reinterpret_cast(mRS.GetRecyclable()); // Can be shared (3) mInput.mBIPInput.mNeeded = true; } static void doCompleteBoxPruning(MBP_PairManager* PX_RESTRICT pairManager, const BoxPruning_Input& input) { const MBPEntry* PX_RESTRICT objects = input.mObjects; const MBP_AABB* PX_RESTRICT updatedDynamicBoxes = input.mUpdatedDynamicBoxes; const MBP_AABB* PX_RESTRICT sleepingDynamicBoxes = input.mSleepingDynamicBoxes; const MBP_Index* PX_RESTRICT inToOut_Dynamic = input.mInToOut_Dynamic; const MBP_Index* PX_RESTRICT inToOut_Dynamic_Sleeping = input.mInToOut_Dynamic_Sleeping; const PxU32 nbUpdated = input.mNbUpdated; const PxU32 nbNonUpdated = input.mNbNonUpdated; // // PT: find sleeping-dynamics-vs-active-dynamics overlaps if(nbNonUpdated) { const PxU32 nb0 = nbUpdated; const PxU32 nb1 = nbNonUpdated; // PxU32 index0 = 0; PxU32 runningIndex1 = 0; while(runningIndex1 #endif // PT: TODO: // - We could try to keep bounds around all objects (for each region), and then test the new region's bounds against these instead of // testing all objects one by one. These new bounds (around all objects of a given region) would be delicate to maintain though. // - Just store these "fully inside flags" (i.e. objects) in a separate list? Or can we do MTF on objects? (probably not, else we // wouldn't have holes in the array due to removed objects) // PT: automatically populate new region with overlapping objects. // Brute-force version checking all existing objects, potentially optimized using "fully inside" flags. //#define FIRST_VERSION #ifdef FIRST_VERSION void MBP::populateNewRegion(const MBP_AABB& box, Region* addedRegion, PxU32 regionIndex) { const RegionData* PX_RESTRICT regions = mRegions.begin(); const PxU32 nbObjects = mMBP_Objects.size(); MBP_Object* PX_RESTRICT objects = mMBP_Objects.begin(); #ifdef PRINT_STATS PxU32 nbObjectsFound = 0; PxU32 nbObjectsTested = 0; #endif #ifdef USE_FULLY_INSIDE_FLAG const PxU32* fullyInsideFlags = mFullyInsideBitmap.getBits(); #endif PxU32 j=0; while(j>5]; #ifdef HWSCAN if(blockFlags==0) //HWSCAN #else if(blockFlags==0xffffffff) #endif { j+=32; continue; } PxU32 nbToGo = PxMin(nbObjects - j, PxU32(32)); PxU32 mask = 1; while(nbToGo--) { MBP_Object& currentObject = objects[j]; // PT: if an object A is fully contained inside all the regions S it overlaps, we don't need to test it against the new region R. // The rationale is that even if R does overlap A, any new object B must touch S to overlap with A. So B would be added to S and // the (A,B) overlap would be detected in S, even if it's not detected in R. const PxU32 res = blockFlags & mask; PX_ASSERT((mFullyInsideBitmap.isSet(j) && res) || (!mFullyInsideBitmap.isSet(j) && !res)); mask+=mask; j++; #ifdef HWSCAN if(!res) //HWSCAN #else if(res) #endif continue; PX_ASSERT(!(currentObject.mFlags & MBP_REMOVED)); #else MBP_Object& currentObject = objects[j++]; if(currentObject.mFlags & MBP_REMOVED) continue; // PT: object is in the free list #endif #ifdef PRINT_STATS nbObjectsTested++; #endif MBP_AABB bounds; MBP_Handle mbpHandle; const PxU32 nbHandles = currentObject.mNbHandles; if(nbHandles) { RegionHandle* PX_RESTRICT handles = getHandles(currentObject, nbHandles); // PT: no need to test all regions since they should contain the same info. Just retrieve bounds from the first one. PxU32 i=0; // for(PxU32 i=0;iretrieveBounds(bounds, h.mHandle); } } else { PX_ASSERT(mManager); // PT: if the object is out-of-bounds, we're out-of-luck. We don't have the object bounds, so we need to retrieve them // from the AABB manager - and then re-encode them. This is not very elegant or efficient, but it should rarely happen // so this is good enough for now. const PxBounds3 decodedBounds = mManager->getBPBounds(currentObject.mUserID); bounds.initFrom2(decodedBounds); mbpHandle = currentObject.mHandlesIndex; } if(bounds.intersect(box)) { // updateObject(mbpHandle, bounds); updateObjectAfterNewRegionAdded(mbpHandle, bounds, addedRegion, regionIndex); #ifdef PRINT_STATS nbObjectsFound++; #endif } #ifdef USE_FULLY_INSIDE_FLAG } #endif } #ifdef PRINT_STATS printf("Populating new region with %d objects (tested %d/%d object)\n", nbObjectsFound, nbObjectsTested, nbObjects); #endif } #endif // PT: version using lowestSetBit #define SECOND_VERSION #ifdef SECOND_VERSION /* PX_FORCE_INLINE PxU32 lowestSetBitUnsafe64(PxU64 v) { unsigned long retval; _BitScanForward64(&retval, v); return retval; }*/ void MBP::populateNewRegion(const MBP_AABB& box, Region* addedRegion, PxU32 regionIndex) { const RegionData* PX_RESTRICT regions = mRegions.begin(); const PxU32 nbObjects = mMBP_Objects.size(); PX_UNUSED(nbObjects); MBP_Object* PX_RESTRICT objects = mMBP_Objects.begin(); const PxU32* fullyInsideFlags = mFullyInsideBitmap.getBits(); // const PxU64* fullyInsideFlags = (const PxU64*)mFullyInsideBitmap.getBits(); if(!fullyInsideFlags) return; const PxU32 lastSetBit = mFullyInsideBitmap.findLast(); // const PxU64 lastSetBit = mFullyInsideBitmap.findLast(); #ifdef PRINT_STATS PxU32 nbObjectsFound = 0; PxU32 nbObjectsTested = 0; #endif for(PxU32 w = 0; w <= lastSetBit >> 5; ++w) // for(PxU64 w = 0; w <= lastSetBit >> 6; ++w) { for(PxU32 b = fullyInsideFlags[w]; b; b &= b-1) // for(PxU64 b = fullyInsideFlags[w]; b; b &= b-1) { const PxU32 index = PxU32(w<<5|Ps::lowestSetBit(b)); // const PxU64 index = (PxU64)(w<<6|::lowestSetBitUnsafe64(b)); PX_ASSERT(indexretrieveBounds(bounds, h.mHandle); } } else { // PT: if the object is out-of-bounds, we're out-of-luck. We don't have the object bounds, so we need to retrieve them // from the AABB manager - and then re-encode them. This is not very elegant or efficient, but it should rarely happen // so this is good enough for now. const PxBounds3 rawBounds = mTransientBounds[currentObject.mUserID]; PxVec3 c(mTransientContactDistance[currentObject.mUserID]); const PxBounds3 decodedBounds(rawBounds.minimum - c, rawBounds.maximum + c); bounds.initFrom2(decodedBounds); mbpHandle = currentObject.mHandlesIndex; } if(bounds.intersects(box)) { // updateObject(mbpHandle, bounds); updateObjectAfterNewRegionAdded(mbpHandle, bounds, addedRegion, regionIndex); #ifdef PRINT_STATS nbObjectsFound++; #endif } } } #ifdef PRINT_STATS printf("Populating new region with %d objects (tested %d/%d object)\n", nbObjectsFound, nbObjectsTested, nbObjects); #endif } #endif //#define THIRD_VERSION #ifdef THIRD_VERSION void MBP::populateNewRegion(const MBP_AABB& box, Region* addedRegion, PxU32 regionIndex) { const RegionData* PX_RESTRICT regions = mRegions.begin(); const PxU32 nbObjects = mMBP_Objects.size(); PX_UNUSED(nbObjects); MBP_Object* PX_RESTRICT objects = mMBP_Objects.begin(); const PxU32* fullyInsideFlags = mFullyInsideBitmap.getBits(); if(!fullyInsideFlags) return; #ifdef PRINT_STATS PxU32 nbObjectsFound = 0; PxU32 nbObjectsTested = 0; #endif Cm::BitMap bm; bm.importData(mFullyInsideBitmap.getSize(), (PxU32*)fullyInsideFlags); Cm::BitMap::Iterator it(bm); PxU32 index = it.getNext(); while(index != Cm::BitMap::Iterator::DONE) { PX_ASSERT(indexretrieveBounds(bounds, h.mHandle); } } else { PX_ASSERT(mManager); // PT: if the object is out-of-bounds, we're out-of-luck. We don't have the object bounds, so we need to retrieve them // from the AABB manager - and then re-encode them. This is not very elegant or efficient, but it should rarely happen // so this is good enough for now. const PxBounds3 decodedBounds = mManager->getBPBounds(currentObject.mUserID); bounds.initFrom2(decodedBounds); mbpHandle = currentObject.mHandlesIndex; } if(bounds.intersect(box)) { // updateObject(mbpHandle, bounds); updateObjectAfterNewRegionAdded(mbpHandle, bounds, addedRegion, regionIndex); #ifdef PRINT_STATS nbObjectsFound++; #endif } index = it.getNext(); } #ifdef PRINT_STATS printf("Populating new region with %d objects (tested %d/%d object)\n", nbObjectsFound, nbObjectsTested, nbObjects); #endif } #endif PxU32 MBP::addRegion(const PxBroadPhaseRegion& region, bool populateRegion) { PxU32 regionHandle; RegionData* PX_RESTRICT buffer; if(mFirstFreeIndexBP!=INVALID_ID) { regionHandle = mFirstFreeIndexBP; buffer = mRegions.begin(); buffer += regionHandle; mFirstFreeIndexBP = PxU32(size_t(buffer->mUserData)); // PT: this is safe, we previously stored a PxU32 in there } else { if(mNbRegions>=MAX_NB_MBP) { Ps::getFoundation().error(PxErrorCode::eOUT_OF_MEMORY, __FILE__, __LINE__, "MBP::addRegion: max number of regions reached."); return INVALID_ID; } regionHandle = mNbRegions++; buffer = reserveContainerMemory(mRegions, 1); } Region* newRegion = PX_NEW(Region); buffer->mBox.initFrom2(region.bounds); buffer->mBP = newRegion; buffer->mUserData = region.userData; setupOverlapFlags(mNbRegions, mRegions.begin()); // PT: automatically populate new region with overlapping objects if(populateRegion) populateNewRegion(buffer->mBox, newRegion, regionHandle); #ifdef MBP_REGION_BOX_PRUNING mDirtyRegions = true; #endif return regionHandle; } // ### TODO: recycle regions, make sure objects are properly deleted/transferred, etc // ### TODO: what happens if we delete a zone then immediately add it back? Do objects get deleted? // ### TODO: in fact if we remove a zone but we keep the objects, what happens to their current overlaps? Are they kept or discarded? bool MBP::removeRegion(PxU32 handle) { if(handle>=mNbRegions) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "MBP::removeRegion: invalid handle."); return false; } RegionData* PX_RESTRICT region = mRegions.begin(); region += handle; Region* bp = region->mBP; if(!bp) { Ps::getFoundation().error(PxErrorCode::eINVALID_PARAMETER, __FILE__, __LINE__, "MBP::removeRegion: invalid handle."); return false; } PxBounds3 empty; empty.setEmpty(); region->mBox.initFrom2(empty); { // We are going to remove the region but it can still contain objects. We need to update // those objects so that their handles and out-of-bounds status are modified. // // Unfortunately there is no way to iterate over active objects in a region, so we need // to iterate over the max amount of objects. ### TODO: optimize this const PxU32 maxNbObjects = bp->mMaxNbObjects; MBPEntry* PX_RESTRICT objects = bp->mObjects; for(PxU32 j=0;jmBP = NULL; region->mUserData = reinterpret_cast(size_t(mFirstFreeIndexBP)); mFirstFreeIndexBP = handle; #ifdef MBP_REGION_BOX_PRUNING mDirtyRegions = true; #endif // A region has been removed so we need to update the overlap flags for all remaining regions // ### TODO: optimize this setupOverlapFlags(mNbRegions, mRegions.begin()); return true; } const Region* MBP::getRegion(PxU32 i) const { if(i>=mNbRegions) return NULL; const RegionData* PX_RESTRICT regions = mRegions.begin(); return regions[i].mBP; } #ifdef MBP_REGION_BOX_PRUNING void MBP::buildRegionData() { const PxU32 size = mNbRegions; PxU32 nbValidRegions = 0; if(size) { const RegionData* PX_RESTRICT regions = mRegions.begin(); // Gather valid regions PxU32 minPosList[MAX_NB_MBP]; for(PxU32 i=0;i& c = mHandles[nbHandles]; handles = reinterpret_cast(c.begin()+handlesIndex); } return handles; } void MBP::purgeHandles(MBP_Object* PX_RESTRICT object, PxU32 nbHandles) { if(nbHandles>1) { const PxU32 handlesIndex = object->mHandlesIndex; Ps::Array& c = mHandles[nbHandles]; PxU32* recycled = c.begin() + handlesIndex; *recycled = mFirstFree[nbHandles]; mFirstFree[nbHandles] = handlesIndex; } } void MBP::storeHandles(MBP_Object* PX_RESTRICT object, PxU32 nbHandles, const RegionHandle* PX_RESTRICT handles) { if(nbHandles==1) { object->mHandle = handles[0]; } else if(nbHandles) { Ps::Array& c = mHandles[nbHandles]; const PxU32 firstFree = mFirstFree[nbHandles]; PxU32* handlesMemory; if(firstFree!=INVALID_ID) { object->mHandlesIndex = firstFree; handlesMemory = c.begin() + firstFree; mFirstFree[nbHandles] = *handlesMemory; } else { const PxU32 handlesIndex = c.size(); object->mHandlesIndex = handlesIndex; handlesMemory = reserveContainerMemory(c, sizeof(RegionHandle)*nbHandles/sizeof(PxU32)); } PxMemCopy(handlesMemory, handles, sizeof(RegionHandle)*nbHandles); } } MBP_Handle MBP::addObject(const MBP_AABB& box, BpHandle userID, bool isStatic) { MBP_ObjectIndex objectIndex; MBP_Object* objectMemory; PxU32 flipFlop; if(mFirstFreeIndex!=INVALID_ID) { objectIndex = mFirstFreeIndex; MBP_Object* objects = mMBP_Objects.begin(); objectMemory = &objects[objectIndex]; PX_ASSERT(!objectMemory->mNbHandles); mFirstFreeIndex = objectMemory->mHandlesIndex; flipFlop = PxU32(objectMemory->getFlipFlop()); } else { objectIndex = mMBP_Objects.size(); objectMemory = reserveContainerMemory(mMBP_Objects, 1); flipFlop = 0; } const MBP_Handle MBPObjectHandle = encodeHandle(objectIndex, flipFlop, isStatic); // mMBP_Objects.Shrink(); PxU32 nbHandles = 0; #ifdef USE_FULLY_INSIDE_FLAG bool newObjectIsFullyInsideRegions = true; #endif const PxU32 nb = mNbRegions; const RegionData* PX_RESTRICT regions = mRegions.begin(); RegionHandle tmpHandles[MAX_NB_MBP+1]; for(PxU32 i=0;imNbObjects==0xffff) Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "MBP::addObject: 64K objects in single region reached. Some collisions might be lost."); else #endif { RegionHandle& h = tmpHandles[nbHandles++]; h.mHandle = regions[i].mBP->addObject(box, MBPObjectHandle, isStatic); h.mInternalBPHandle = Ps::to16(i); } } } storeHandles(objectMemory, nbHandles, tmpHandles); objectMemory->mNbHandles = Ps::to16(nbHandles); PxU16 flags = 0; if(flipFlop) flags |= MBP_FLIP_FLOP; #ifdef USE_FULLY_INSIDE_FLAG if(nbHandles && newObjectIsFullyInsideRegions) setBit(mFullyInsideBitmap, objectIndex); else clearBit(mFullyInsideBitmap, objectIndex); #endif if(!nbHandles) { objectMemory->mHandlesIndex = MBPObjectHandle; addToOutOfBoundsArray(userID); } if(!isStatic) mUpdatedObjects.setBitChecked(objectIndex); // objectMemory->mUpdated = !isStatic; objectMemory->mFlags = flags; objectMemory->mUserID = userID; return MBPObjectHandle; } bool MBP::removeObject(MBP_Handle handle) { const MBP_ObjectIndex objectIndex = decodeHandle_Index(handle); MBP_Object* PX_RESTRICT objects = mMBP_Objects.begin(); MBP_Object& currentObject = objects[objectIndex]; const RegionData* PX_RESTRICT regions = mRegions.begin(); // Parse previously overlapping regions. If still overlapping, update object. Else remove from region. const PxU32 nbHandles = currentObject.mNbHandles; if(nbHandles) { RegionHandle* handles = getHandles(currentObject, nbHandles); for(PxU32 i=0;iremoveObject(h.mHandle); } purgeHandles(¤tObject, nbHandles); } currentObject.mNbHandles = 0; currentObject.mFlags |= MBP_REMOVED; currentObject.mHandlesIndex = mFirstFreeIndex; // if(!decodeHandle_IsStatic(handle)) // if(!currentObject.IsStatic()) mUpdatedObjects.setBitChecked(objectIndex); mFirstFreeIndex = objectIndex; mRemoved.setBitChecked(objectIndex); // PT: this is cleared each frame so it's not a replacement for the MBP_REMOVED flag #ifdef USE_FULLY_INSIDE_FLAG // PT: when removing an object we mark it as "fully inside" so that it is automatically // discarded in the "populateNewRegion" function, without the need for MBP_REMOVED. setBit(mFullyInsideBitmap, objectIndex); #endif return true; } static PX_FORCE_INLINE bool stillIntersects(PxU32 handle, PxU32& _nb, PxU32* PX_RESTRICT currentOverlaps) { const PxU32 nb = _nb; for(PxU32 i=0;i nbHandles changes from 2 to 1 while MBP_FULLY_INSIDE is not set setBit(mFullyInsideBitmap, objectIndex); #endif currentRegion.mBP->updateObject(box, h.mHandle); return true; } } // Find regions overlapping object's new position #ifdef USE_FULLY_INSIDE_FLAG bool objectIsFullyInsideRegions = true; #endif PxU32 nbCurrentOverlaps = 0; PxU32 currentOverlaps[MAX_NB_MBP+1]; // PT: here, we may still parse regions which have been removed. But their boxes have been set to empty, // so nothing will happen. for(PxU32 i=0;iUpdateObject(box, h.mHandle); if(stillIntersects(h.mInternalBPHandle, nbCurrentOverlaps, currentOverlaps)) { currentRegion.mBP->updateObject(box, h.mHandle); // Still collides => keep handle for this frame newHandles[nbNewHandles++] = h; } else { PX_ASSERT(!currentRegion.mBox.intersects(box)); // if(currentRegion.mBP) PX_ASSERT(currentRegion.mBP); currentRegion.mBP->removeObject(h.mHandle); } } // Add to new regions if needed for(PxU32 i=0;iaddObject(box, handle, isStatic!=0); newHandles[nbNewHandles].mHandle = Ps::to16(BPHandle); newHandles[nbNewHandles].mInternalBPHandle = Ps::to16(regionIndex); nbNewHandles++; } if(nbHandles==nbNewHandles) { for(PxU32 i=0;iaddObject(box, handle, isStatic!=0); newHandles[nbNewHandles].mHandle = Ps::to16(BPHandle); newHandles[nbNewHandles].mInternalBPHandle = Ps::to16(regionIndex); nbNewHandles++; } // PT: we know that we have one more handle than before, no need to test purgeHandles(¤tObject, nbHandles); storeHandles(¤tObject, nbNewHandles, newHandles); currentObject.mNbHandles = Ps::to16(nbNewHandles); // PT: we know that we have at least one handle (from the newly added region), so we can't be "out of bounds" here. PX_ASSERT(nbNewHandles); #ifdef USE_FULLY_INSIDE_FLAG // PT: we know that the object was not "fully inside" before, so even if it is fully inside the new region, it // will not be fully inside all of them => no need to change its fully inside flag // TODO: an exception to this would be the case where the object was out-of-bounds, and it's now fully inside the new region // => we could set the flag in that case. #endif return true; } bool MBP_PairManager::computeCreatedDeletedPairs(const MBP_Object* objects, BroadPhaseMBP* mbp, const BitArray& updated, const BitArray& removed) { // PT: parse all currently active pairs. The goal here is to generate the found/lost pairs, compared to previous frame. PxU32 i=0; PxU32 nbActivePairs = mNbActivePairs; while(imCreated.pushBack(BroadPhasePair(object0, object1/*, i*/)); // PT: this was wrong anyway! "i" is not an invariant for the pair p.isNew = false; p.isUpdated = false; i++; } else if(p.isUpdated) { // Persistent pair // PT: this pair already existed in the structure, and has been found again this frame. Since // MBP reports "all pairs" each frame (as opposed to SAP), this happens quite often, for each // active persistent pair. p.isUpdated = false; i++; } else { // Lost pair // PT: if the pair is not new and not 'updated', it might be a lost (separated) pair. But this // is not always the case since we now handle "sleeping" objects directly within MBP. A pair // of sleeping objects does not generate an 'addPair' call, so it ends up in this codepath. // Nonetheless the sleeping pair should not be deleted. We can only delete pairs involving // objects that have been actually moved during the frame. This is the only case in which // a pair can indeed become 'lost'. const PxU32 id0 = p.id0; const PxU32 id1 = p.id1; PX_ASSERT(id0!=INVALID_ID); PX_ASSERT(id1!=INVALID_ID); const MBP_ObjectIndex index0 = decodeHandle_Index(id0); const MBP_ObjectIndex index1 = decodeHandle_Index(id1); // PT: if none of the involved objects have been updated, the pair is just sleeping: keep it and skip it. if(updated.isSetChecked(index0) || updated.isSetChecked(index1)) { // PT: by design (for better or worse) we do not report pairs to the client when // one of the involved objects has been deleted. The pair must still be deleted // from the MBP structure though. if(!removed.isSetChecked(index0) && !removed.isSetChecked(index1)) { // PT: doing the group-based filtering here is useless. The pair should not have // been added in the first place. const BpHandle object0 = objects[index0].mUserID; const BpHandle object1 = objects[index1].mUserID; mbp->mDeleted.pushBack(BroadPhasePair(object0, object1/*, i*/)); } const PxU32 hashValue = hash(id0, id1) & mMask; removePair(id0, id1, hashValue, i); nbActivePairs--; } else i++; } } shrinkMemory(); return true; } void MBP::prepareOverlaps() { const PxU32 nb = mNbRegions; const RegionData* PX_RESTRICT regions = mRegions.begin(); for(PxU32 i=0;iprepareOverlaps(); } } void MBP::findOverlaps(const Bp::FilterGroup::Enum* PX_RESTRICT groups #ifdef BP_FILTERING_USES_TYPE_IN_GROUP , const bool* PX_RESTRICT lut #endif ) { PxU32 nb = mNbRegions; const RegionData* PX_RESTRICT regions = mRegions.begin(); const MBP_Object* objects = mMBP_Objects.begin(); mPairManager.mObjects = objects; mPairManager.mGroups = groups; #ifdef BP_FILTERING_USES_TYPE_IN_GROUP mPairManager.mLUT = lut; #endif for(PxU32 i=0;ifindOverlaps(mPairManager); } } PxU32 MBP::finalize(BroadPhaseMBP* mbp) { const MBP_Object* objects = mMBP_Objects.begin(); mPairManager.computeCreatedDeletedPairs(objects, mbp, mUpdatedObjects, mRemoved); mUpdatedObjects.clearAll(); return mPairManager.mNbActivePairs; } void MBP::reset() { PxU32 nb = mNbRegions; RegionData* PX_RESTRICT regions = mRegions.begin(); while(nb--) { // printf("%d objects in region\n", regions->mBP->mNbObjects); DELETESINGLE(regions->mBP); regions++; } mNbRegions = 0; mFirstFreeIndex = INVALID_ID; mFirstFreeIndexBP = INVALID_ID; for(PxU32 i=0;isetBounds(h.mHandle, bounds); } } } } void MBP::setTransientBounds(const PxBounds3* bounds, const PxReal* contactDistance) { mTransientBounds = bounds; mTransientContactDistance = contactDistance; } /////////////////////////////////////////////////////////////////////////////// // Below is the PhysX wrapper = link between AABBManager and MBP #define DEFAULT_CREATED_DELETED_PAIRS_CAPACITY 1024 BroadPhaseMBP::BroadPhaseMBP( PxU32 maxNbRegions, PxU32 maxNbBroadPhaseOverlaps, PxU32 maxNbStaticShapes, PxU32 maxNbDynamicShapes, PxU64 contextID) : mMBPUpdateWorkTask (contextID), mMBPPostUpdateWorkTask (contextID), mMapping (NULL), mCapacity (0), mGroups (NULL) #ifdef BP_FILTERING_USES_TYPE_IN_GROUP ,mLUT (NULL) #endif { mMBP = PX_NEW(MBP)(); const PxU32 nbObjects = maxNbStaticShapes + maxNbDynamicShapes; mMBP->preallocate(maxNbRegions, nbObjects, maxNbBroadPhaseOverlaps); if(nbObjects) allocateMappingArray(nbObjects); mCreated.reserve(DEFAULT_CREATED_DELETED_PAIRS_CAPACITY); mDeleted.reserve(DEFAULT_CREATED_DELETED_PAIRS_CAPACITY); } BroadPhaseMBP::~BroadPhaseMBP() { DELETESINGLE(mMBP); PX_FREE(mMapping); } void BroadPhaseMBP::allocateMappingArray(PxU32 newCapacity) { PX_ASSERT(newCapacity>mCapacity); MBP_Handle* newMapping = reinterpret_cast(PX_ALLOC(sizeof(MBP_Handle)*newCapacity, "MBP")); if(mCapacity) PxMemCopy(newMapping, mMapping, mCapacity*sizeof(MBP_Handle)); for(PxU32 i=mCapacity;imNbRegions; /* const RegionData* PX_RESTRICT regions = (const RegionData*)mMBP->mRegions.GetEntries(); PxU32 nbActiveRegions = 0; for(PxU32 i=0;imNbRegions; const RegionData* PX_RESTRICT regions = mMBP->mRegions.begin(); regions += startIndex; const PxU32 writeCount = PxMin(size, bufferSize); for(PxU32 i=0;imNbStaticBoxes; userBuffer[i].nbDynamicObjects = regions[i].mBP->mNbDynamicBoxes; } else { userBuffer[i].region.bounds.setEmpty(); userBuffer[i].region.userData = NULL; userBuffer[i].active = false; userBuffer[i].overlap = false; userBuffer[i].nbStaticObjects = 0; userBuffer[i].nbDynamicObjects = 0; } } return writeCount; } PxU32 BroadPhaseMBP::addRegion(const PxBroadPhaseRegion& region, bool populateRegion) { return mMBP->addRegion(region, populateRegion); } bool BroadPhaseMBP::removeRegion(PxU32 handle) { return mMBP->removeRegion(handle); } void BroadPhaseMBP::update(const PxU32 numCpuTasks, PxcScratchAllocator* scratchAllocator, const BroadPhaseUpdateData& updateData, physx::PxBaseTask* continuation, physx::PxBaseTask* narrowPhaseUnblockTask) { #if PX_CHECKED PX_CHECK_AND_RETURN(scratchAllocator, "BroadPhaseMBP::update - scratchAllocator must be non-NULL \n"); #endif // PT: TODO: move this out of update function if(narrowPhaseUnblockTask) narrowPhaseUnblockTask->removeReference(); setUpdateData(updateData); if(1) { update(); postUpdate(); } else { mMBPPostUpdateWorkTask.set(this, scratchAllocator, numCpuTasks); mMBPUpdateWorkTask.set(this, scratchAllocator, numCpuTasks); mMBPPostUpdateWorkTask.setContinuation(continuation); mMBPUpdateWorkTask.setContinuation(&mMBPPostUpdateWorkTask); mMBPPostUpdateWorkTask.removeReference(); mMBPUpdateWorkTask.removeReference(); } } void BroadPhaseMBP::singleThreadedUpdate(PxcScratchAllocator* /*scratchAllocator*/, const BroadPhaseUpdateData& updateData) { // PT: TODO: the scratchAllocator isn't actually needed, is it? setUpdateData(updateData); update(); postUpdate(); } static PX_FORCE_INLINE void computeMBPBounds(MBP_AABB& aabb, const PxBounds3* PX_RESTRICT boundsXYZ, const PxReal* PX_RESTRICT contactDistances, const BpHandle index) { const PxBounds3& b = boundsXYZ[index]; const Vec4V contactDistanceV = V4Load(contactDistances[index]); const Vec4V inflatedMinV = V4Sub(V4LoadU(&b.minimum.x), contactDistanceV); const Vec4V inflatedMaxV = V4Add(V4LoadU(&b.maximum.x), contactDistanceV); // PT: this one is safe because we allocated one more box in the array (in BoundsArray::initEntry) PX_ALIGN(16, PxVec4) boxMin; PX_ALIGN(16, PxVec4) boxMax; V4StoreA(inflatedMinV, &boxMin.x); V4StoreA(inflatedMaxV, &boxMax.x); const PxU32* PX_RESTRICT min = PxUnionCast(&boxMin.x); const PxU32* PX_RESTRICT max = PxUnionCast(&boxMax.x); //Avoid min=max by enforcing the rule that mins are even and maxs are odd. aabb.mMinX = IntegerAABB::encodeFloatMin(min[0])>>1; aabb.mMinY = IntegerAABB::encodeFloatMin(min[1])>>1; aabb.mMinZ = IntegerAABB::encodeFloatMin(min[2])>>1; aabb.mMaxX = (IntegerAABB::encodeFloatMax(max[0]) | (1<<2))>>1; aabb.mMaxY = (IntegerAABB::encodeFloatMax(max[1]) | (1<<2))>>1; aabb.mMaxZ = (IntegerAABB::encodeFloatMax(max[2]) | (1<<2))>>1; /* const IntegerAABB bounds(boundsXYZ[index], contactDistances[index]); aabb.mMinX = bounds.mMinMax[IntegerAABB::MIN_X]>>1; aabb.mMinY = bounds.mMinMax[IntegerAABB::MIN_Y]>>1; aabb.mMinZ = bounds.mMinMax[IntegerAABB::MIN_Z]>>1; aabb.mMaxX = bounds.mMinMax[IntegerAABB::MAX_X]>>1; aabb.mMaxY = bounds.mMinMax[IntegerAABB::MAX_Y]>>1; aabb.mMaxZ = bounds.mMinMax[IntegerAABB::MAX_Z]>>1;*/ /* aabb.mMinX &= ~1; aabb.mMinY &= ~1; aabb.mMinZ &= ~1; aabb.mMaxX |= 1; aabb.mMaxY |= 1; aabb.mMaxZ |= 1; */ /*#if PX_DEBUG PxBounds3 decodedBox; PxU32* bin = reinterpret_cast(&decodedBox.minimum.x); bin[0] = decodeFloat(bounds.mMinMax[IntegerAABB::MIN_X]); bin[1] = decodeFloat(bounds.mMinMax[IntegerAABB::MIN_Y]); bin[2] = decodeFloat(bounds.mMinMax[IntegerAABB::MIN_Z]); bin[3] = decodeFloat(bounds.mMinMax[IntegerAABB::MAX_X]); bin[4] = decodeFloat(bounds.mMinMax[IntegerAABB::MAX_Y]); bin[5] = decodeFloat(bounds.mMinMax[IntegerAABB::MAX_Z]); MBP_AABB PrunerBox; PrunerBox.initFrom2(decodedBox); PX_ASSERT(PrunerBox.mMinX==aabb.mMinX); PX_ASSERT(PrunerBox.mMinY==aabb.mMinY); PX_ASSERT(PrunerBox.mMinZ==aabb.mMinZ); PX_ASSERT(PrunerBox.mMaxX==aabb.mMaxX); PX_ASSERT(PrunerBox.mMaxY==aabb.mMaxY); PX_ASSERT(PrunerBox.mMaxZ==aabb.mMaxZ); #endif*/ } void MBPUpdateWorkTask::runInternal() { mMBP->update(); } void MBPPostUpdateWorkTask::runInternal() { mMBP->postUpdate(); } void BroadPhaseMBP::removeObjects(const BroadPhaseUpdateData& updateData) { const BpHandle* PX_RESTRICT removed = updateData.getRemovedHandles(); if(removed) { PxU32 nbToGo = updateData.getNumRemovedHandles(); while(nbToGo--) { const BpHandle index = *removed++; PX_ASSERT(index+1removeObject(mMapping[index]); PX_ASSERT(status); PX_UNUSED(status); mMapping[index] = PX_INVALID_U32; } } } void BroadPhaseMBP::updateObjects(const BroadPhaseUpdateData& updateData) { const BpHandle* PX_RESTRICT updated = updateData.getUpdatedHandles(); if(updated) { const PxBounds3* PX_RESTRICT boundsXYZ = updateData.getAABBs(); PxU32 nbToGo = updateData.getNumUpdatedHandles(); while(nbToGo--) { const BpHandle index = *updated++; PX_ASSERT(index+1updateObject(mMapping[index], aabb); PX_ASSERT(status); PX_UNUSED(status); } } } void BroadPhaseMBP::addObjects(const BroadPhaseUpdateData& updateData) { const BpHandle* PX_RESTRICT created = updateData.getCreatedHandles(); if(created) { const PxBounds3* PX_RESTRICT boundsXYZ = updateData.getAABBs(); const Bp::FilterGroup::Enum* PX_RESTRICT groups = updateData.getGroups(); PxU32 nbToGo = updateData.getNumCreatedHandles(); while(nbToGo--) { const BpHandle index = *created++; PX_ASSERT(index+1addObject(aabb, index, isStatic); } } } void BroadPhaseMBP::setUpdateData(const BroadPhaseUpdateData& updateData) { mMBP->setTransientBounds(updateData.getAABBs(), updateData.getContactDistance()); const PxU32 newCapacity = updateData.getCapacity(); if(newCapacity>mCapacity) allocateMappingArray(newCapacity); #if PX_CHECKED // PT: WARNING: this must be done after the allocateMappingArray call if(!BroadPhaseUpdateData::isValid(updateData, *this)) { PX_CHECK_MSG(false, "Illegal BroadPhaseUpdateData \n"); return; } #endif mGroups = updateData.getGroups(); #ifdef BP_FILTERING_USES_TYPE_IN_GROUP mLUT = updateData.getLUT(); #endif // ### TODO: handle groups inside MBP // ### TODO: get rid of AABB conversions removeObjects(updateData); addObjects(updateData); updateObjects(updateData); PX_ASSERT(!mCreated.size()); PX_ASSERT(!mDeleted.size()); mMBP->prepareOverlaps(); } void BroadPhaseMBP::update() { #ifdef CHECK_NB_OVERLAPS gNbOverlaps = 0; #endif mMBP->findOverlaps(mGroups #ifdef BP_FILTERING_USES_TYPE_IN_GROUP , mLUT #endif ); #ifdef CHECK_NB_OVERLAPS printf("PPU: %d overlaps\n", gNbOverlaps); #endif } void BroadPhaseMBP::postUpdate() { { PxU32 Nb = mMBP->mNbRegions; const RegionData* PX_RESTRICT regions = mMBP->mRegions.begin(); for(PxU32 i=0;imNbUpdatedBoxes = 0; } } mMBP->finalize(this); } PxU32 BroadPhaseMBP::getNbCreatedPairs() const { return mCreated.size(); } BroadPhasePair* BroadPhaseMBP::getCreatedPairs() { return mCreated.begin(); } PxU32 BroadPhaseMBP::getNbDeletedPairs() const { return mDeleted.size(); } BroadPhasePair* BroadPhaseMBP::getDeletedPairs() { return mDeleted.begin(); } PxU32 BroadPhaseMBP::getNbOutOfBoundsObjects() const { return mMBP->mOutOfBoundsObjects.size(); } const PxU32* BroadPhaseMBP::getOutOfBoundsObjects() const { return mMBP->mOutOfBoundsObjects.begin(); } static void freeBuffer(Ps::Array& buffer) { const PxU32 size = buffer.size(); if(size>DEFAULT_CREATED_DELETED_PAIRS_CAPACITY) { buffer.reset(); buffer.reserve(DEFAULT_CREATED_DELETED_PAIRS_CAPACITY); } else { buffer.clear(); } } void BroadPhaseMBP::freeBuffers() { mMBP->freeBuffers(); freeBuffer(mCreated); freeBuffer(mDeleted); } #if PX_CHECKED bool BroadPhaseMBP::isValid(const BroadPhaseUpdateData& updateData) const { const BpHandle* created = updateData.getCreatedHandles(); if(created) { Ps::HashSet set; PxU32 nbObjects = mMBP->mMBP_Objects.size(); const MBP_Object* PX_RESTRICT objects = mMBP->mMBP_Objects.begin(); while(nbObjects--) { if(!(objects->mFlags & MBP_REMOVED)) set.insert(objects->mUserID); objects++; } PxU32 nbToGo = updateData.getNumCreatedHandles(); while(nbToGo--) { const BpHandle index = *created++; PX_ASSERT(indexshiftOrigin(shift); }