/************************************************************************* * * * Open Dynamics Engine, Copyright (C) 2001-2003 Russell L. Smith. * * All rights reserved. Email: russ@q12.org Web: www.q12.org * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of EITHER: * * (1) The GNU Lesser General Public License as published by the Free * * Software Foundation; either version 2.1 of the License, or (at * * your option) any later version. The text of the GNU Lesser * * General Public License is included with this library in the * * file LICENSE.TXT. * * (2) The BSD-style license that is included with this library in * * the file LICENSE-BSD.TXT. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files * * LICENSE.TXT and LICENSE-BSD.TXT for more details. * * * *************************************************************************/ // QuadTreeSpace by Erwin de Vries. // With math corrections by Oleh Derevenko. ;) #include #include #include #include "config.h" #include "matrix.h" #include "collision_kernel.h" #include "collision_space_internal.h" #define AXIS0 0 #define AXIS1 1 #define UP 2 //#define DRAWBLOCKS const int SPLITAXIS = 2; const int SPLITS = SPLITAXIS * SPLITAXIS; #define GEOM_ENABLED(g) (((g)->gflags & GEOM_ENABLE_TEST_MASK) == GEOM_ENABLE_TEST_VALUE) class Block{ public: dReal mMinX, mMaxX; dReal mMinZ, mMaxZ; dGeomID mFirst; int mGeomCount; Block* mParent; Block* mChildren; void Create(const dReal MinX, const dReal MaxX, const dReal MinZ, const dReal MaxZ, Block* Parent, int Depth, Block*& Blocks); void Collide(void* UserData, dNearCallback* Callback); void Collide(dGeomID g1, dGeomID g2, void* UserData, dNearCallback* Callback); void CollideLocal(dGeomID g2, void* UserData, dNearCallback* Callback); void AddObject(dGeomID Object); void DelObject(dGeomID Object); void Traverse(dGeomID Object); bool Inside(const dReal* AABB); Block* GetBlock(const dReal* AABB); Block* GetBlockChild(const dReal* AABB); }; #ifdef DRAWBLOCKS #include "..\..\Include\drawstuff\\drawstuff.h" static void DrawBlock(Block* Block){ dVector3 v[8]; v[0][AXIS0] = Block->mMinX; v[0][UP] = REAL(-1.0); v[0][AXIS1] = Block->mMinZ; v[1][AXIS0] = Block->mMinX; v[1][UP] = REAL(-1.0); v[1][AXIS1] = Block->mMaxZ; v[2][AXIS0] = Block->mMaxX; v[2][UP] = REAL(-1.0); v[2][AXIS1] = Block->mMinZ; v[3][AXIS0] = Block->mMaxX; v[3][UP] = REAL(-1.0); v[3][AXIS1] = Block->mMaxZ; v[4][AXIS0] = Block->mMinX; v[4][UP] = REAL(1.0); v[4][AXIS1] = Block->mMinZ; v[5][AXIS0] = Block->mMinX; v[5][UP] = REAL(1.0); v[5][AXIS1] = Block->mMaxZ; v[6][AXIS0] = Block->mMaxX; v[6][UP] = REAL(1.0); v[6][AXIS1] = Block->mMinZ; v[7][AXIS0] = Block->mMaxX; v[7][UP] = REAL(1.0); v[7][AXIS1] = Block->mMaxZ; // Bottom dsDrawLine(v[0], v[1]); dsDrawLine(v[1], v[3]); dsDrawLine(v[3], v[2]); dsDrawLine(v[2], v[0]); // Top dsDrawLine(v[4], v[5]); dsDrawLine(v[5], v[7]); dsDrawLine(v[7], v[6]); dsDrawLine(v[6], v[4]); // Sides dsDrawLine(v[0], v[4]); dsDrawLine(v[1], v[5]); dsDrawLine(v[2], v[6]); dsDrawLine(v[3], v[7]); } #endif //DRAWBLOCKS void Block::Create(const dReal MinX, const dReal MaxX, const dReal MinZ, const dReal MaxZ, Block* Parent, int Depth, Block*& Blocks){ dIASSERT(MinX <= MaxX); dIASSERT(MinZ <= MaxZ); mGeomCount = 0; mFirst = 0; mMinX = MinX; mMaxX = MaxX; mMinZ = MinZ; mMaxZ = MaxZ; this->mParent = Parent; if (Depth > 0){ mChildren = Blocks; Blocks += SPLITS; const dReal ChildExtentX = (MaxX - MinX) / SPLITAXIS; const dReal ChildExtentZ = (MaxZ - MinZ) / SPLITAXIS; const int ChildDepth = Depth - 1; int Index = 0; dReal ChildRightX = MinX; for (int i = 0; i < SPLITAXIS; i++){ const dReal ChildLeftX = ChildRightX; ChildRightX = (i != SPLITAXIS - 1) ? ChildLeftX + ChildExtentX : MaxX; dReal ChildRightZ = MinZ; for (int j = 0; j < SPLITAXIS; j++){ const dReal ChildLeftZ = ChildRightZ; ChildRightZ = (j != SPLITAXIS - 1) ? ChildLeftZ + ChildExtentZ : MaxZ; mChildren[Index].Create(ChildLeftX, ChildRightX, ChildLeftZ, ChildRightZ, this, ChildDepth, Blocks); ++Index; } } } else mChildren = 0; } void Block::Collide(void* UserData, dNearCallback* Callback){ #ifdef DRAWBLOCKS DrawBlock(this); #endif // Collide the local list dxGeom* g = mFirst; while (g){ if (GEOM_ENABLED(g)){ Collide(g, g->next_ex, UserData, Callback); } g = g->next_ex; } // Recurse for children if (mChildren){ for (int i = 0; i < SPLITS; i++){ Block &CurrentChild = mChildren[i]; if (CurrentChild.mGeomCount <= 1){ // Early out continue; } CurrentChild.Collide(UserData, Callback); } } } // Note: g2 is assumed to be in this Block void Block::Collide(dxGeom* g1, dxGeom* g2, void* UserData, dNearCallback* Callback){ #ifdef DRAWBLOCKS DrawBlock(this); #endif // Collide against local list while (g2){ if (GEOM_ENABLED(g2)){ collideAABBs (g1, g2, UserData, Callback); } g2 = g2->next_ex; } // Collide against children if (mChildren){ for (int i = 0; i < SPLITS; i++){ Block &CurrentChild = mChildren[i]; // Early out for empty blocks if (CurrentChild.mGeomCount == 0){ continue; } // Does the geom's AABB collide with the block? // Don't do AABB tests for single geom blocks. if (CurrentChild.mGeomCount == 1){ // } else if (true){ if (g1->aabb[AXIS0 * 2 + 0] >= CurrentChild.mMaxX || g1->aabb[AXIS0 * 2 + 1] < CurrentChild.mMinX || g1->aabb[AXIS1 * 2 + 0] >= CurrentChild.mMaxZ || g1->aabb[AXIS1 * 2 + 1] < CurrentChild.mMinZ) continue; } CurrentChild.Collide(g1, CurrentChild.mFirst, UserData, Callback); } } } void Block::CollideLocal(dxGeom* g2, void* UserData, dNearCallback* Callback){ // Collide against local list dxGeom* g1 = mFirst; while (g1){ if (GEOM_ENABLED(g1)){ collideAABBs (g1, g2, UserData, Callback); } g1 = g1->next_ex; } } void Block::AddObject(dGeomID Object){ // Add the geom Object->next_ex = mFirst; mFirst = Object; Object->tome_ex = (dxGeom**)this; // Now traverse upwards to tell that we have a geom Block* Block = this; do{ Block->mGeomCount++; Block = Block->mParent; } while (Block); } void Block::DelObject(dGeomID Object){ // Del the geom dxGeom* g = mFirst; dxGeom* Last = 0; while (g){ if (g == Object){ if (Last){ Last->next_ex = g->next_ex; } else mFirst = g->next_ex; break; } Last = g; g = g->next_ex; } Object->tome_ex = 0; // Now traverse upwards to tell that we have lost a geom Block* Block = this; do{ Block->mGeomCount--; Block = Block->mParent; } while (Block); } void Block::Traverse(dGeomID Object){ Block* NewBlock = GetBlock(Object->aabb); if (NewBlock != this){ // Remove the geom from the old block and add it to the new block. // This could be more optimal, but the loss should be very small. DelObject(Object); NewBlock->AddObject(Object); } } bool Block::Inside(const dReal* AABB){ return AABB[AXIS0 * 2 + 0] >= mMinX && AABB[AXIS0 * 2 + 1] < mMaxX && AABB[AXIS1 * 2 + 0] >= mMinZ && AABB[AXIS1 * 2 + 1] < mMaxZ; } Block* Block::GetBlock(const dReal* AABB){ if (Inside(AABB)){ return GetBlockChild(AABB); // Child or this will have a good block } else if (mParent){ return mParent->GetBlock(AABB); // Parent has a good block } else return this; // We are at the root, so we have little choice } Block* Block::GetBlockChild(const dReal* AABB){ if (mChildren){ for (int i = 0; i < SPLITS; i++){ Block &CurrentChild = mChildren[i]; if (CurrentChild.Inside(AABB)){ return CurrentChild.GetBlockChild(AABB); // Child will have good block } } } return this; // This is the best block } //**************************************************************************** // quadtree space struct dxQuadTreeSpace : public dxSpace{ Block* Blocks; // Blocks[0] is the root dArray DirtyList; dxQuadTreeSpace(dSpaceID _space, const dVector3 Center, const dVector3 Extents, int Depth); ~dxQuadTreeSpace(); dxGeom* getGeom(int i); void add(dxGeom* g); void remove(dxGeom* g); void dirty(dxGeom* g); void computeAABB(); void cleanGeoms(); void collide(void* UserData, dNearCallback* Callback); void collide2(void* UserData, dxGeom* g1, dNearCallback* Callback); // Temp data Block* CurrentBlock; // Only used while enumerating int* CurrentChild; // Only used while enumerating int CurrentLevel; // Only used while enumerating dxGeom* CurrentObject; // Only used while enumerating int CurrentIndex; }; namespace { inline sizeint numNodes(int depth) { // A 4-ary tree has (4^(depth+1) - 1)/3 nodes // Note: split up into multiple constant expressions for readability const int k = depth+1; const sizeint fourToNthPlusOne = (sizeint)1 << (2*k); // 4^k = 2^(2k) return (fourToNthPlusOne - 1) / 3; } } dxQuadTreeSpace::dxQuadTreeSpace(dSpaceID _space, const dVector3 Center, const dVector3 Extents, int Depth) : dxSpace(_space){ type = dQuadTreeSpaceClass; sizeint BlockCount = numNodes(Depth); Blocks = (Block*)dAlloc(BlockCount * sizeof(Block)); Block* Blocks = this->Blocks + 1; // This pointer gets modified! dReal MinX = Center[AXIS0] - Extents[AXIS0]; dReal MaxX = dNextAfter((Center[AXIS0] + Extents[AXIS0]), (dReal)dInfinity); dReal MinZ = Center[AXIS1] - Extents[AXIS1]; dReal MaxZ = dNextAfter((Center[AXIS1] + Extents[AXIS1]), (dReal)dInfinity); this->Blocks[0].Create(MinX, MaxX, MinZ, MaxZ, 0, Depth, Blocks); CurrentBlock = 0; CurrentChild = (int*)dAlloc((Depth + 1) * sizeof(int)); CurrentLevel = 0; CurrentObject = 0; CurrentIndex = -1; // Init AABB. We initialize to infinity because it is not illegal for an object to be outside of the tree. Its simply inserted in the root block aabb[0] = -dInfinity; aabb[1] = dInfinity; aabb[2] = -dInfinity; aabb[3] = dInfinity; aabb[4] = -dInfinity; aabb[5] = dInfinity; } dxQuadTreeSpace::~dxQuadTreeSpace(){ int Depth = 0; Block* Current = &Blocks[0]; while (Current){ Depth++; Current = Current->mChildren; } sizeint BlockCount = numNodes(Depth); dFree(Blocks, BlockCount * sizeof(Block)); dFree(CurrentChild, (Depth + 1) * sizeof(int)); } dxGeom* dxQuadTreeSpace::getGeom(int Index){ dUASSERT(Index >= 0 && Index < count, "index out of range"); //@@@ dDebug (0,"dxQuadTreeSpace::getGeom() not yet implemented"); return 0; // This doesnt work /* if (CurrentIndex == Index){ // Loop through all objects in the local list CHILDRECURSE: if (CurrentObject){ dGeomID g = CurrentObject; CurrentObject = CurrentObject->next_ex; CurrentIndex++; #ifdef DRAWBLOCKS DrawBlock(CurrentBlock); #endif //DRAWBLOCKS return g; } else{ // Now lets loop through our children. Starting at index 0. if (CurrentBlock->Children){ CurrentChild[CurrentLevel] = 0; PARENTRECURSE: for (int& i = CurrentChild[CurrentLevel]; i < SPLITS; i++){ if (CurrentBlock->Children[i].GeomCount == 0){ continue; } CurrentBlock = &CurrentBlock->Children[i]; CurrentObject = CurrentBlock->First; i++; CurrentLevel++; goto CHILDRECURSE; } } } // Now lets go back to the parent so it can continue processing its other children. if (CurrentBlock->Parent){ CurrentBlock = CurrentBlock->Parent; CurrentLevel--; goto PARENTRECURSE; } } else{ CurrentBlock = &Blocks[0]; CurrentLevel = 0; CurrentObject = CurrentObject; CurrentIndex = 0; // Other states are already set CurrentObject = CurrentBlock->First; } if (current_geom && current_index == Index - 1){ //current_geom = current_geom->next_ex; // next current_index = Index; return current_geom; } else for (int i = 0; i < Index; i++){ // this will be verrrrrrry slow getGeom(i); } */ return 0; } void dxQuadTreeSpace::add(dxGeom* g){ CHECK_NOT_LOCKED (this); dAASSERT(g); dUASSERT(g->tome_ex == 0 && g->next_ex == 0, "geom is already in a space"); DirtyList.push(g); Blocks[0].GetBlock(g->aabb)->AddObject(g); // Add to best block dxSpace::add(g); } void dxQuadTreeSpace::remove(dxGeom* g){ CHECK_NOT_LOCKED(this); dAASSERT(g); dUASSERT(g->parent_space == this,"object is not in this space"); // remove ((Block*)g->tome_ex)->DelObject(g); for (int i = 0; i < DirtyList.size(); i++){ if (DirtyList[i] == g){ DirtyList.remove(i); // (mg) there can be multiple instances of a dirty object on stack be sure to remove ALL and not just first, for this we decrement i --i; } } dxSpace::remove(g); } void dxQuadTreeSpace::dirty(dxGeom* g){ DirtyList.push(g); } void dxQuadTreeSpace::computeAABB(){ // } void dxQuadTreeSpace::cleanGeoms(){ // compute the AABBs of all dirty geoms, and clear the dirty flags lock_count++; for (int i = 0; i < DirtyList.size(); i++){ dxGeom* g = DirtyList[i]; if (IS_SPACE(g)){ ((dxSpace*)g)->cleanGeoms(); } g->recomputeAABB(); dIASSERT((g->gflags & GEOM_AABB_BAD) == 0); g->gflags &= ~GEOM_DIRTY; ((Block*)g->tome_ex)->Traverse(g); } DirtyList.setSize(0); lock_count--; } void dxQuadTreeSpace::collide(void* UserData, dNearCallback* Callback){ dAASSERT(Callback); lock_count++; cleanGeoms(); Blocks[0].Collide(UserData, Callback); lock_count--; } struct DataCallback { void *data; dNearCallback *callback; }; // Invokes the callback with arguments swapped static void swap_callback(void *data, dxGeom *g1, dxGeom *g2) { DataCallback *dc = (DataCallback*)data; dc->callback(dc->data, g2, g1); } void dxQuadTreeSpace::collide2(void* UserData, dxGeom* g2, dNearCallback* Callback){ dAASSERT(g2 && Callback); lock_count++; cleanGeoms(); g2->recomputeAABB(); if (g2->parent_space == this){ // The block the geom is in Block* CurrentBlock = (Block*)g2->tome_ex; // Collide against block and its children DataCallback dc = {UserData, Callback}; CurrentBlock->Collide(g2, CurrentBlock->mFirst, &dc, swap_callback); // Collide against parents while ((CurrentBlock = CurrentBlock->mParent)) CurrentBlock->CollideLocal(g2, UserData, Callback); } else { DataCallback dc = {UserData, Callback}; Blocks[0].Collide(g2, Blocks[0].mFirst, &dc, swap_callback); } lock_count--; } dSpaceID dQuadTreeSpaceCreate(dxSpace* space, const dVector3 Center, const dVector3 Extents, int Depth){ return new dxQuadTreeSpace(space, Center, Extents, Depth); }