/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /* * OPCODE - Optimized Collision Detection * Copyright (C) 2001 Pierre Terdiman * Homepage: http://www.codercorner.com/Opcode.htm */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Contains an array-based version of the sweep-and-prune algorithm * \file OPC_ArraySAP.cpp * \author Pierre Terdiman * \date December, 2, 2007 */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Precompiled Header #include "StdAfx.h" using namespace Opcode; //#include "SAP_Utils.h" #define INVALID_USER_ID 0xffff inline_ void Sort(uword& id0, uword& id1) { if(id0>id1) TSwap(id0, id1); } inline_ void Sort(uword& id0, uword& id1, const void*& obj0, const void*& obj1) { if(id0>id1) { TSwap(id0, id1); TSwap(obj0, obj1); } } struct Opcode::IAABB : public Allocateable { udword mMinX; udword mMinY; udword mMinZ; udword mMaxX; udword mMaxY; udword mMaxZ; inline_ udword GetMin(udword i) const { return (&mMinX)[i]; } inline_ udword GetMax(udword i) const { return (&mMaxX)[i]; } }; /* - already sorted for batch create? - better axis selection batch create */ //#define USE_WORDS // Use words or dwords for box indices. Words save memory but seriously limit the max number of objects in the SAP. #define USE_PREFETCH #define USE_INTEGERS #define PAIR_USER_DATA #define USE_OVERLAP_TEST_ON_REMOVES // "Useless" but faster overall because seriously reduces number of calls (from ~10000 to ~3 sometimes!) #define RELEASE_ON_RESET // Release memory instead of just doing a reset #include "OPC_ArraySAP.h" //#include "SAP_PairManager.h" //#include "SAP_PairManager.cpp" inline_ udword Hash(uword id0, uword id1) { return Hash32Bits_1( udword(id0)|(udword(id1)<<16) ); } inline_ bool DifferentPair(const ASAP_Pair& p, uword id0, uword id1) { return (id0!=p.id0) || (id1!=p.id1); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ASAP_PairManager::ASAP_PairManager() : mHashSize (0), mMask (0), mHashTable (null), mNext (null), mNbActivePairs (0), mActivePairs (null) { } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ASAP_PairManager::~ASAP_PairManager() { Purge(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ASAP_PairManager::Purge() { ICE_FREE(mNext); ICE_FREE(mActivePairs); ICE_FREE(mHashTable); mHashSize = 0; mMask = 0; mNbActivePairs = 0; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const ASAP_Pair* ASAP_PairManager::FindPair(uword id0, uword id1) const { if(!mHashTable) return null; // Nothing has been allocated yet // Order the ids Sort(id0, id1); // Compute hash value for this pair udword HashValue = Hash(id0, id1) & mMask; // Look for it in the table udword Offset = mHashTable[HashValue]; while(Offset!=INVALID_ID && DifferentPair(mActivePairs[Offset], id0, id1)) { ASSERT(mActivePairs[Offset].id0!=INVALID_USER_ID); Offset = mNext[Offset]; // Better to have a separate array for this } if(Offset==INVALID_ID) return null; ASSERT(Offset the pair is persistent return &mActivePairs[Offset]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Internal version saving hash computation inline_ ASAP_Pair* ASAP_PairManager::FindPair(uword id0, uword id1, udword hash_value) const { if(!mHashTable) return null; // Nothing has been allocated yet // Look for it in the table udword Offset = mHashTable[hash_value]; while(Offset!=INVALID_ID && DifferentPair(mActivePairs[Offset], id0, id1)) { ASSERT(mActivePairs[Offset].id0!=INVALID_USER_ID); Offset = mNext[Offset]; // Better to have a separate array for this } if(Offset==INVALID_ID) return null; ASSERT(Offset the pair is persistent return &mActivePairs[Offset]; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const ASAP_Pair* ASAP_PairManager::AddPair(uword id0, uword id1, const void* object0, const void* object1) { // Order the ids Sort(id0, id1, object0, object1); udword HashValue = Hash(id0, id1) & mMask; ASAP_Pair* P = FindPair(id0, id1, HashValue); if(P) { return P; // Persistent pair } // This is a new pair if(mNbActivePairs >= mHashSize) { // Get more entries mHashSize = NextPowerOfTwo(mNbActivePairs+1); mMask = mHashSize-1; ReallocPairs(); // Recompute hash value with new hash size HashValue = Hash(id0, id1) & mMask; } ASAP_Pair* p = &mActivePairs[mNbActivePairs]; p->id0 = id0; // ### CMOVs would be nice here p->id1 = id1; p->object0 = object0; p->object1 = object1; mNext[mNbActivePairs] = mHashTable[HashValue]; mHashTable[HashValue] = mNbActivePairs++; return p; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void ASAP_PairManager::RemovePair(uword id0, uword id1, udword hash_value, udword pair_index) { // Walk the hash table to fix mNext udword Offset = mHashTable[hash_value]; ASSERT(Offset!=INVALID_ID); udword Previous=INVALID_ID; while(Offset!=pair_index) { Previous = Offset; Offset = mNext[Offset]; } // Let us go/jump us if(Previous!=INVALID_ID) { ASSERT(mNext[Previous]==pair_index); mNext[Previous] = mNext[pair_index]; } // else we were the first else mHashTable[hash_value] = mNext[pair_index]; // we're now free to reuse mNext[PairIndex] without breaking the list #ifdef _DEBUG mNext[pair_index]=INVALID_ID; #endif // Invalidate entry // Fill holes if(1) { // 1) Remove last pair const udword LastPairIndex = mNbActivePairs-1; if(LastPairIndex==pair_index) { mNbActivePairs--; } else { const ASAP_Pair* Last = &mActivePairs[LastPairIndex]; const udword LastHashValue = Hash(Last->id0, Last->id1) & mMask; // Walk the hash table to fix mNext udword Offset = mHashTable[LastHashValue]; ASSERT(Offset!=INVALID_ID); udword Previous=INVALID_ID; while(Offset!=LastPairIndex) { Previous = Offset; Offset = mNext[Offset]; } // Let us go/jump us if(Previous!=INVALID_ID) { 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 #ifdef _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[pair_index] = mActivePairs[LastPairIndex]; #ifdef _DEBUG ASSERT(mNext[pair_index]==INVALID_ID); #endif mNext[pair_index] = mHashTable[LastHashValue]; mHashTable[LastHashValue] = pair_index; mNbActivePairs--; } } } bool ASAP_PairManager::RemovePair(uword id0, uword id1) { // Order the ids Sort(id0, id1); const udword HashValue = Hash(id0, id1) & mMask; const ASAP_Pair* P = FindPair(id0, id1, HashValue); if(!P) return false; ASSERT(P->id0==id0); ASSERT(P->id1==id1); RemovePair(id0, id1, HashValue, GetPairIndex(P)); ShrinkMemory(); return true; } bool ASAP_PairManager::RemovePairs(const BitArray& array) { udword i=0; while(i only less efficient but still ok for(udword i=0;i>2; } }; class Opcode::ASAP_Box : public Allocateable { public: inline_ ASAP_Box() {} inline_ ~ASAP_Box() {} IndexType mMin[3]; IndexType mMax[3]; void* mObject; udword mGUID; inline_ void SetInvalid() { mMin[0]=INVALID_INDEX; } inline_ bool IsValid() const { return mMin[0]!=INVALID_INDEX; } inline_ ValType GetMaxValue(udword i, const ASAP_EndPoint* base) const { return base[mMax[i]].mValue; } inline_ ValType GetMinValue(udword i, const ASAP_EndPoint* base) const { return base[mMin[i]].mValue; } #ifdef _DEBUG bool HasBeenInserted() const { assert(mMin[0]!=INVALID_INDEX); assert(mMax[0]!=INVALID_INDEX); assert(mMin[1]!=INVALID_INDEX); assert(mMax[1]!=INVALID_INDEX); assert(mMin[2]!=INVALID_INDEX); assert(mMax[2]!=INVALID_INDEX); return true; } #endif }; inline_ BOOL Intersect1D_Min(const SAP_AABB& a, const ASAP_Box& b, const ASAP_EndPoint* const base, udword axis) { if(b.GetMaxValue(axis, base) < a.GetMin(axis)) return FALSE; return TRUE; } inline_ BOOL Intersect2D(const ASAP_Box& c, const ASAP_Box& b, udword axis1, udword axis2) { if( b.mMax[axis1] < c.mMin[axis1] || c.mMax[axis1] < b.mMin[axis1] || b.mMax[axis2] < c.mMin[axis2] || c.mMax[axis2] < b.mMin[axis2]) return FALSE; return TRUE; } ArraySAP::ArraySAP() { mNbBoxes = 0; mMaxNbBoxes = 0; mBoxes = null; mEndPoints[0] = mEndPoints[1] = mEndPoints[2] = null; mFirstFree = INVALID_ID; } ArraySAP::~ArraySAP() { mNbBoxes = 0; mMaxNbBoxes = 0; DELETEARRAY(mBoxes); for(udword i=0;i<3;i++) { DELETEARRAY(mEndPoints[i]); } } void ArraySAP::ResizeBoxArray() { const udword NewMaxBoxes = mMaxNbBoxes ? mMaxNbBoxes*2 : 64; ASAP_Box* NewBoxes = ICE_NEW_TMP(ASAP_Box)[NewMaxBoxes]; const udword NbSentinels=2; ASAP_EndPoint* NewEndPointsX = ICE_NEW_TMP(ASAP_EndPoint)[NewMaxBoxes*2+NbSentinels]; ASAP_EndPoint* NewEndPointsY = ICE_NEW_TMP(ASAP_EndPoint)[NewMaxBoxes*2+NbSentinels]; ASAP_EndPoint* NewEndPointsZ = ICE_NEW_TMP(ASAP_EndPoint)[NewMaxBoxes*2+NbSentinels]; if(mNbBoxes) { CopyMemory(NewBoxes, mBoxes, sizeof(ASAP_Box)*mNbBoxes); CopyMemory(NewEndPointsX, mEndPoints[0], sizeof(ASAP_EndPoint)*(mNbBoxes*2+NbSentinels)); CopyMemory(NewEndPointsY, mEndPoints[1], sizeof(ASAP_EndPoint)*(mNbBoxes*2+NbSentinels)); CopyMemory(NewEndPointsZ, mEndPoints[2], sizeof(ASAP_EndPoint)*(mNbBoxes*2+NbSentinels)); } else { // Initialize sentinels #ifdef USE_INTEGERS const udword Min = EncodeFloat(MIN_FLOAT); const udword Max = EncodeFloat(MAX_FLOAT); #else const float Min = MIN_FLOAT; const float Max = MAX_FLOAT; #endif NewEndPointsX[0].SetData(Min, INVALID_INDEX, FALSE); NewEndPointsX[1].SetData(Max, INVALID_INDEX, TRUE); NewEndPointsY[0].SetData(Min, INVALID_INDEX, FALSE); NewEndPointsY[1].SetData(Max, INVALID_INDEX, TRUE); NewEndPointsZ[0].SetData(Min, INVALID_INDEX, FALSE); NewEndPointsZ[1].SetData(Max, INVALID_INDEX, TRUE); } DELETEARRAY(mBoxes); DELETEARRAY(mEndPoints[2]); DELETEARRAY(mEndPoints[1]); DELETEARRAY(mEndPoints[0]); mBoxes = NewBoxes; mEndPoints[0] = NewEndPointsX; mEndPoints[1] = NewEndPointsY; mEndPoints[2] = NewEndPointsZ; mMaxNbBoxes = NewMaxBoxes; } inline_ BOOL Intersect(const IAABB& a, const IAABB& b, udword axis) { if(b.GetMax(axis) < a.GetMin(axis) || a.GetMax(axis) < b.GetMin(axis)) return FALSE; return TRUE; } // ### TODO: the sorts here might be useless, as the values have been sorted already bool ArraySAP::CompleteBoxPruning2(udword nb, const IAABB* array, const Axes& axes, const CreateData* batched) { // Checkings if(!nb || !array) return false; // Catch axes const udword Axis0 = axes.mAxis0; const udword Axis1 = axes.mAxis1; const udword Axis2 = axes.mAxis2; // Allocate some temporary data udword* PosList = (udword*)ICE_ALLOC_TMP(sizeof(udword)*(nb+1)); // 1) Build main list using the primary axis for(udword i=0;iSort(PosList, nb, RADIX_SIGNED).GetRanks(); const udword* Sorted = RS->Sort(PosList, nb, RADIX_UNSIGNED).GetRanks(); // 3) Prune the list const udword* const LastSorted = &Sorted[nb]; const udword* RunningAddress = Sorted; udword Index0, Index1; while(RunningAddressmObject, Box1->mObject, Box0->mGUID, Box1->mGUID); } } } } } ICE_FREE(PosList); return true; } bool ArraySAP::BipartiteBoxPruning2(udword nb0, const IAABB* array0, udword nb1, const IAABB* array1, const Axes& axes, const CreateData* batched, const udword* box_indices) { // Checkings if(!nb0 || !array0 || !nb1 || !array1) return false; // Catch axes const udword Axis0 = axes.mAxis0; const udword Axis1 = axes.mAxis1; const udword Axis2 = axes.mAxis2; // Allocate some temporary data udword* MinPosList0 = (udword*)ICE_ALLOC_TMP(sizeof(udword)*nb0); udword* MinPosList1 = (udword*)ICE_ALLOC_TMP(sizeof(udword)*nb1); // 1) Build main lists using the primary axis for(udword i=0;iSort(MinPosList0, nb0, RADIX_UNSIGNED).GetRanks(); const udword* Sorted1 = RS1->Sort(MinPosList1, nb1, RADIX_UNSIGNED).GetRanks(); // 3) Prune the lists udword Index0, Index1; const udword* const LastSorted0 = &Sorted0[nb0]; const udword* const LastSorted1 = &Sorted1[nb1]; const udword* RunningAddress0 = Sorted0; const udword* RunningAddress1 = Sorted1; while(RunningAddress1mObject, Box1->mObject, Box0->mGUID, Box1->mGUID); } } } } //// while(RunningAddress0mObject, Box1->mObject, Box0->mGUID, Box1->mGUID); } } } } ICE_FREE(MinPosList0); ICE_FREE(MinPosList1); return true; } udword ArraySAP::AddObject(void* object, uword guid, const AABB& box) { assert(!(size_t(object)&3)); // We will use the 2 LSBs #ifdef _DEBUG int a = sizeof(ASAP_Box); // 32 int b = sizeof(ASAP_EndPoint); // 8 #endif udword BoxIndex; if(mFirstFree!=INVALID_ID) { BoxIndex = mFirstFree; mFirstFree = mBoxes[BoxIndex].mGUID; } else { if(mNbBoxes==mMaxNbBoxes) ResizeBoxArray(); BoxIndex = mNbBoxes; } ASAP_Box* Box = &mBoxes[BoxIndex]; // Initialize box Box->mObject = object; Box->mGUID = guid; for(udword i=0;i<3;i++) { Box->mMin[i] = INVALID_INDEX; Box->mMax[i] = INVALID_INDEX; } mNbBoxes++; CreateData* CD = (CreateData*)mCreated.Reserve(sizeof(CreateData)/sizeof(udword)); CD->mHandle = BoxIndex; CD->mBox = box; return BoxIndex; } void ArraySAP::InsertEndPoints(udword axis, const ASAP_EndPoint* end_points, udword nb_endpoints) { ASAP_EndPoint* const BaseEP = mEndPoints[axis]; const udword OldSize = mNbBoxes*2 - nb_endpoints; const udword NewSize = mNbBoxes*2; BaseEP[NewSize + 1] = BaseEP[OldSize + 1]; sdword WriteIdx = NewSize; udword CurrInsIdx = 0; const ASAP_EndPoint* First = &BaseEP[0]; const ASAP_EndPoint* Current = &BaseEP[OldSize]; while(Current>=First) { const ASAP_EndPoint& Src = *Current; const ASAP_EndPoint& Ins = end_points[CurrInsIdx]; // We need to make sure we insert maxs before mins to handle exactly equal endpoints correctly const bool ShouldInsert = Ins.IsMax() ? (Src.mValue <= Ins.mValue) : (Src.mValue < Ins.mValue); const ASAP_EndPoint& Moved = ShouldInsert ? Ins : Src; BaseEP[WriteIdx] = Moved; mBoxes[Moved.GetOwner()].mMin[axis + Moved.IsMax()] = WriteIdx--; if(ShouldInsert) { CurrInsIdx++; if(CurrInsIdx >= nb_endpoints) break;//we just inserted the last endpoint } else { Current--; } } } void ArraySAP::BatchCreate() { udword NbBatched = mCreated.GetNbEntries(); if(!NbBatched) return; // Early-exit if no object has been created NbBatched /= sizeof(CreateData)/sizeof(udword); const CreateData* Batched = (const CreateData*)mCreated.GetEntries(); mCreated.Reset(); { const udword NbEndPoints = NbBatched*2; ASAP_EndPoint* NewEPSorted = ICE_NEW_TMP(ASAP_EndPoint)[NbEndPoints]; ASAP_EndPoint* Buffer = (ASAP_EndPoint*)ICE_ALLOC_TMP(sizeof(ASAP_EndPoint)*NbEndPoints); RadixSort RS; for(udword Axis=0;Axis<3;Axis++) { for(udword i=0;iHasBeenInserted()); } for(udword i=0;imMin[0]; NewBoxes[i].mMaxX = Box->mMax[0]; NewBoxes[i].mMinY = Box->mMin[1]; NewBoxes[i].mMaxY = Box->mMax[1]; NewBoxes[i].mMinZ = Box->mMin[2]; NewBoxes[i].mMaxZ = Box->mMax[2]; } CompleteBoxPruning2(NbBatched, NewBoxes, Axes(AXES_XZY), Batched); // the old boxes are not the first ones in the array const udword NbOldBoxes = mNbBoxes - NbBatched; if(NbOldBoxes) { IAABB* OldBoxes = ICE_NEW_TMP(IAABB)[NbOldBoxes]; udword* OldBoxesIndices = (udword*)ICE_ALLOC_TMP(sizeof(udword)*NbOldBoxes); udword Offset=0; udword i=0; while(imObject) if(Box->IsValid()) { OldBoxesIndices[Offset] = i; OldBoxes[Offset].mMinX = Box->mMin[0]; OldBoxes[Offset].mMaxX = Box->mMax[0]; OldBoxes[Offset].mMinY = Box->mMin[1]; OldBoxes[Offset].mMaxY = Box->mMax[1]; OldBoxes[Offset].mMinZ = Box->mMin[2]; OldBoxes[Offset].mMaxZ = Box->mMax[2]; Offset++; } } i++; } // assert(i==NbOldBoxes); BipartiteBoxPruning2(NbBatched, NewBoxes, Offset, OldBoxes, Axes(AXES_XZY), Batched, OldBoxesIndices); ICE_FREE(OldBoxesIndices); DELETEARRAY(OldBoxes); } DELETEARRAY(NewBoxes); } #ifdef RELEASE_ON_RESET mCreated.Empty(); #endif } void ArraySAP::BatchRemove() { udword NbRemoved = mRemoved.GetNbEntries(); if(!NbRemoved) return; // Early-exit if no object has been removed const udword* Removed = mRemoved.GetEntries(); mRemoved.Reset(); for(udword Axis=0;Axis<3;Axis++) { ASAP_EndPoint* const BaseEP = mEndPoints[Axis]; udword MinMinIndex = MAX_UDWORD; for(udword i=0;imMin[Axis]; assert(MinIndexmMax[Axis]; assert(MaxIndexmMin[Axis + BaseEP[DestIndex].IsMax()] = DestIndex; } } DestIndex++; ReadIndex++; } } } BitArray BA(65536); const udword Saved = NbRemoved; while(NbRemoved--) { udword Index = *Removed++; assert(IndexmGUID < 65536); BA.SetBit(Object->mGUID); Object->mGUID = mFirstFree; // Object->mObject = null; // ########### Object->SetInvalid(); mFirstFree = Index; } mNbBoxes -= Saved; mPairs.RemovePairs(BA); #ifdef RELEASE_ON_RESET mRemoved.Empty(); #endif } bool ArraySAP::RemoveObject(udword handle) { mRemoved.Add(handle); return true; } #ifdef USE_INTEGERS bool ArraySAP::UpdateObject(udword handle, const AABB& box_) #else bool ArraySAP::UpdateObject(udword handle, const AABB& box) #endif { const ASAP_Box* Object = mBoxes + handle; assert(Object->HasBeenInserted()); const void* UserObject = Object->mObject; const udword UserGUID = Object->mGUID; #ifdef USE_INTEGERS IAABB box; box.mMinX = EncodeFloat(box_.GetMin(0)); box.mMinY = EncodeFloat(box_.GetMin(1)); box.mMinZ = EncodeFloat(box_.GetMin(2)); box.mMaxX = EncodeFloat(box_.GetMax(0)); box.mMaxY = EncodeFloat(box_.GetMax(1)); box.mMaxZ = EncodeFloat(box_.GetMax(2)); #endif for(udword Axis=0;Axis<3;Axis++) { const udword Axis1 = (1 << Axis) & 3; const udword Axis2 = (1 << Axis1) & 3; ASAP_EndPoint* const BaseEP = mEndPoints[Axis]; // Update min { ASAP_EndPoint* CurrentMin = BaseEP + Object->mMin[Axis]; ASSERT(!CurrentMin->IsMax()); const ValType Limit = box.GetMin(Axis); if(Limit < CurrentMin->mValue) { CurrentMin->mValue = Limit; // Min is moving left: ASAP_EndPoint Saved = *CurrentMin; udword EPIndex = (size_t(CurrentMin) - size_t(BaseEP))/sizeof(ASAP_EndPoint); const udword SavedIndex = EPIndex; while((--CurrentMin)->mValue > Limit) { #ifdef USE_PREFETCH _prefetch(CurrentMin-1); #endif ASAP_Box* id1 = mBoxes + CurrentMin->GetOwner(); const BOOL IsMax = CurrentMin->IsMax(); if(IsMax) { // Our min passed a max => start overlap if(Object!=id1 && Intersect2D(*Object, *id1, Axis1, Axis2) && Intersect1D_Min(box, *id1, BaseEP, Axis) ) AddPair(UserObject, id1->mObject, UserGUID, id1->mGUID); } id1->mMin[Axis + IsMax] = EPIndex--; *(CurrentMin+1) = *CurrentMin; } if(SavedIndex!=EPIndex) { mBoxes[Saved.GetOwner()].mMin[Axis + Saved.IsMax()] = EPIndex; BaseEP[EPIndex] = Saved; } } else if(Limit > CurrentMin->mValue) { CurrentMin->mValue = Limit; // Min is moving right: ASAP_EndPoint Saved = *CurrentMin; udword EPIndex = (size_t(CurrentMin) - size_t(BaseEP))/sizeof(ASAP_EndPoint); const udword SavedIndex = EPIndex; while((++CurrentMin)->mValue < Limit) { #ifdef USE_PREFETCH _prefetch(CurrentMin+1); #endif ASAP_Box* id1 = mBoxes + CurrentMin->GetOwner(); const BOOL IsMax = CurrentMin->IsMax(); if(IsMax) { // Our min passed a max => stop overlap if(Object!=id1 #ifdef USE_OVERLAP_TEST_ON_REMOVES && Intersect2D(*Object, *id1, Axis1, Axis2) #endif ) RemovePair(UserObject, id1->mObject, UserGUID, id1->mGUID); } id1->mMin[Axis + IsMax] = EPIndex++; *(CurrentMin-1) = *CurrentMin; } if(SavedIndex!=EPIndex) { mBoxes[Saved.GetOwner()].mMin[Axis + Saved.IsMax()] = EPIndex; BaseEP[EPIndex] = Saved; } } } // Update max { ASAP_EndPoint* CurrentMax = BaseEP + Object->mMax[Axis]; ASSERT(CurrentMax->IsMax()); const ValType Limit = box.GetMax(Axis); if(Limit > CurrentMax->mValue) { CurrentMax->mValue = Limit; // Max is moving right: ASAP_EndPoint Saved = *CurrentMax; udword EPIndex = (size_t(CurrentMax) - size_t(BaseEP))/sizeof(ASAP_EndPoint); const udword SavedIndex = EPIndex; while((++CurrentMax)->mValue < Limit) { #ifdef USE_PREFETCH _prefetch(CurrentMax+1); #endif ASAP_Box* id1 = mBoxes + CurrentMax->GetOwner(); const BOOL IsMax = CurrentMax->IsMax(); if(!IsMax) { // Our max passed a min => start overlap if(Object!=id1 && Intersect2D(*Object, *id1, Axis1, Axis2) && Intersect1D_Min(box, *id1, BaseEP, Axis) ) AddPair(UserObject, id1->mObject, UserGUID, id1->mGUID); } id1->mMin[Axis + IsMax] = EPIndex++; *(CurrentMax-1) = *CurrentMax; } if(SavedIndex!=EPIndex) { mBoxes[Saved.GetOwner()].mMin[Axis + Saved.IsMax()] = EPIndex; BaseEP[EPIndex] = Saved; } } else if(Limit < CurrentMax->mValue) { CurrentMax->mValue = Limit; // Max is moving left: ASAP_EndPoint Saved = *CurrentMax; udword EPIndex = (size_t(CurrentMax) - size_t(BaseEP))/sizeof(ASAP_EndPoint); const udword SavedIndex = EPIndex; while((--CurrentMax)->mValue > Limit) { #ifdef USE_PREFETCH _prefetch(CurrentMax-1); #endif ASAP_Box* id1 = mBoxes + CurrentMax->GetOwner(); const BOOL IsMax = CurrentMax->IsMax(); if(!IsMax) { // Our max passed a min => stop overlap if(Object!=id1 #ifdef USE_OVERLAP_TEST_ON_REMOVES && Intersect2D(*Object, *id1, Axis1, Axis2) #endif ) RemovePair(UserObject, id1->mObject, UserGUID, id1->mGUID); } id1->mMin[Axis + IsMax] = EPIndex--; *(CurrentMax+1) = *CurrentMax; } if(SavedIndex!=EPIndex) { mBoxes[Saved.GetOwner()].mMin[Axis + Saved.IsMax()] = EPIndex; BaseEP[EPIndex] = Saved; } } } } return true; } udword ArraySAP::DumpPairs(SAP_CreatePair create_cb, SAP_DeletePair delete_cb, void* cb_user_data, ASAP_Pair** pairs) { BatchCreate(); const udword* Entries = mData.GetEntries(); const udword* Last = Entries + mData.GetNbEntries(); mData.Reset(); udword* ToRemove = (udword*)Entries; while(Entries!=Last) { const udword ID = *Entries++; ASAP_Pair* UP = mPairs.mActivePairs + ID; { ASSERT(UP->IsInArray()); if(UP->IsRemoved()) { // No need to call "ClearInArray" in this case, since the pair will get removed anyway // Remove if(delete_cb && !UP->IsNew()) { #ifdef PAIR_USER_DATA (delete_cb)(UP->GetObject0(), UP->GetObject1(), cb_user_data, UP->userData); #else (delete_cb)(UP->GetObject0(), UP->GetObject1(), cb_user_data, null); #endif } *ToRemove++ = udword(UP->id0)<<16|UP->id1; } else { UP->ClearInArray(); // Add => already there... Might want to create user data, though if(UP->IsNew()) { if(create_cb) { #ifdef PAIR_USER_DATA UP->userData = (create_cb)(UP->GetObject0(), UP->GetObject1(), cb_user_data); #else (create_cb)(UP->GetObject0(), UP->GetObject1(), cb_user_data); #endif } UP->ClearNew(); } } } } // #### try batch removal here Entries = mData.GetEntries(); while(Entries!=ToRemove) { const udword ID = *Entries++; const udword id0 = ID>>16; const udword id1 = ID&0xffff; bool Status = mPairs.RemovePair(id0, id1); ASSERT(Status); } #ifdef RELEASE_ON_RESET mData.Empty(); #endif BatchRemove(); if(pairs) *pairs = mPairs.mActivePairs; return mPairs.mNbActivePairs; }