/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program 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 ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : WWMath * * * * $Archive:: /Commando/Code/wwmath/aabtreecull.cpp $* * * * Author:: Greg Hjelstrom * * * * $Modtime:: 8/26/01 2:18p $* * * * $Revision:: 25 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "aabtreecull.h" #include "chunkio.h" #include "iostruct.h" #include #include "sphere.h" #include "colmath.h" #include "colmathinlines.h" /* ** Declare the pools */ DEFINE_AUTO_POOL(AABTreeLinkClass,256); DEFINE_AUTO_POOL(AABTreeNodeClass,256); /* ** Current version of the file format */ const uint32 AABTREE_CURRENT_VERSION = 0x00010000; /* ** Chunk Id's used by the aabtree code to save itself into a file */ enum { AABTREE_CHUNK_VERSION = 0x00000001, // version wrapper, contains 32bit version # AABTREE_CHUNK_AABNODE = 0x00000101, // generic aab-node wrapper AABTREE_CHUNK_AABNODE_INFO, // OBSOLETE! generic aab-node definition (IOAABNodeStruct) AABTREE_CHUNK_AABNODE_CONTENTS, // wrapper around contents of the node AABTREE_CHUNK_AABNODE_VARIABLES, // wrapper around variables for a node AABTREE_CHUNK_NODE_INDEX = 0x00000200, // wrapper around the node index for an object AABTREE_VARIABLE_NODESTRUCT = 0x00, AABTREE_VARIABLE_USERDATA }; /* ** IOAABNodeStruct ** Data structure for the contents of a node in the AAB-Tree */ #define AABNODE_ATTRIBUTE_FRONT_CHILD 0x00000001 #define AABNODE_ATTRIBUTE_BACK_CHILD 0x00000002 struct IOAABNodeStruct { IOVector3Struct Center; IOVector3Struct Extent; uint32 Attributes; }; /************************************************************************* ** ** Utility functions for walking the object list in an AABTree Node ** *************************************************************************/ static inline CullableClass * get_first_object(AABTreeNodeClass * node) { return node->Object; } static inline CullableClass * get_next_object(CullableClass * obj) { return ((AABTreeLinkClass *)obj->Get_Cull_Link())->NextObject; } /************************************************************************* ** ** AABTreeCullSystemClass Implementation ** *************************************************************************/ AABTreeCullSystemClass::AABTreeCullSystemClass(void) : ObjectCount(0), NodeCount(0), IndexedNodes(NULL) { RootNode = new AABTreeNodeClass; Re_Index_Nodes(); } AABTreeCullSystemClass::~AABTreeCullSystemClass(void) { // Delete all links and release-ref all cullables: int nidx; for (nidx = 0; nidx < NodeCount; nidx++) { while(IndexedNodes[nidx]->Object) Remove_Object_Internal(IndexedNodes[nidx]->Object); } // Delete node tree (deleting the root recursively deletes all nodes) delete RootNode; // Delete indexed node pointer array if (IndexedNodes) { delete[] IndexedNodes; IndexedNodes = NULL; } } void AABTreeCullSystemClass::Add_Object_Internal(CullableClass * obj,int node_index) { WWASSERT_PRINT ( (obj->Get_Culling_System() == NULL), "AABTreeCullSystemClass::Add -- Object is already in another culling system!\n" ); AABTreeLinkClass * new_link = new AABTreeLinkClass(this); obj->Set_Cull_Link(new_link); if (node_index == -1) { Add_Object_Recursive(RootNode,obj); } else { WWASSERT(node_index < NodeCount); IndexedNodes[node_index]->Add_Object(obj,false); ObjectCount++; } obj->Add_Ref(); } void AABTreeCullSystemClass::Remove_Object_Internal(CullableClass * obj) { WWASSERT(obj); WWASSERT(obj->Get_Culling_System() == this); AABTreeLinkClass * link = (AABTreeLinkClass *)obj->Get_Cull_Link(); WWASSERT(link); AABTreeNodeClass * node = link->Node; WWASSERT(node); node->Remove_Object(obj); link->Set_Culling_System(NULL); delete link; obj->Set_Cull_Link(NULL); ObjectCount--; WWASSERT(ObjectCount >= 0); obj->Release_Ref(); } void AABTreeCullSystemClass::Update_Culling(CullableClass * obj) { WWASSERT(obj); WWASSERT(obj->Get_Culling_System() == this); // unlink it from the node it is currently in AABTreeLinkClass * link = (AABTreeLinkClass *)obj->Get_Cull_Link(); WWASSERT(link); AABTreeNodeClass * node = link->Node; WWASSERT(node); node->Remove_Object(obj); // decrement the object counter, the node can't // decrement it for us... ObjectCount--; // drop it into the tree again Add_Object_Recursive(RootNode,obj); } void AABTreeCullSystemClass::Collect_Objects(const Vector3 & point) { Collect_Objects_Recursive(RootNode,point); } void AABTreeCullSystemClass::Collect_Objects(const AABoxClass & box) { Collect_Objects_Recursive(RootNode,box); } void AABTreeCullSystemClass::Collect_Objects(const OBBoxClass & box) { Collect_Objects_Recursive(RootNode,box); } void AABTreeCullSystemClass::Collect_Objects(const FrustumClass & frustum) { Collect_Objects_Recursive(RootNode,frustum,0); } void AABTreeCullSystemClass::Collect_Objects(const SphereClass & sphere) { Collect_Objects_Recursive(RootNode,sphere); } int AABTreeCullSystemClass::Partition_Node_Count(void) const { return Partition_Node_Count_Recursive(RootNode); } int AABTreeCullSystemClass::Partition_Tree_Depth(void) const { int get_max_depth = 0; Partition_Tree_Depth_Recursive(RootNode,0,get_max_depth); return get_max_depth; } int AABTreeCullSystemClass::Object_Count(void) const { return ObjectCount; } int AABTreeCullSystemClass::Partition_Node_Count_Recursive(AABTreeNodeClass * node) const { int curcount = 1; if (node->Front) { curcount += Partition_Node_Count_Recursive(node->Front); } if (node->Back) { curcount += Partition_Node_Count_Recursive(node->Back); } return curcount; } void AABTreeCullSystemClass::Partition_Tree_Depth_Recursive(AABTreeNodeClass * node,int cur_depth,int & max_depth) const { cur_depth++; if (cur_depth > max_depth) { max_depth = cur_depth; } if (node->Front) { Partition_Tree_Depth_Recursive(node->Front,cur_depth,max_depth); } if (node->Back) { Partition_Tree_Depth_Recursive(node->Back,cur_depth,max_depth); } } void AABTreeCullSystemClass::Add_Object_Recursive(AABTreeNodeClass * node,CullableClass * obj) { // order the children in terms of size AABTreeNodeClass * big_child = node->Front; AABTreeNodeClass * small_child = node->Back; if (big_child && small_child && (big_child->Compute_Volume() < small_child->Compute_Volume())) { AABTreeNodeClass * tmp = big_child; big_child = small_child; small_child = tmp; } // Can we fit in the smaller child? if (small_child && small_child->Box.Contains(obj->Get_Cull_Box())) { Add_Object_Recursive(small_child,obj); return; } // Can we fit in the bigger child? if (big_child && big_child->Box.Contains(obj->Get_Cull_Box())) { Add_Object_Recursive(big_child,obj); return; } // Ok, we have to fit in this node. node->Add_Object(obj); ObjectCount++; } void AABTreeCullSystemClass::Add_Loaded_Object(AABTreeNodeClass * node,CullableClass * obj) { WWASSERT(node); WWASSERT(obj); WWASSERT_PRINT ( (obj->Get_Culling_System() == NULL), "AABTreeCullSystemClass::Add_Loaded_Object -- Object is already in another culling system!\n" ); AABTreeLinkClass * new_link = new AABTreeLinkClass(this); obj->Set_Cull_Link(new_link); node->Add_Object(obj); ObjectCount++; obj->Add_Ref(); } void AABTreeCullSystemClass::Re_Partition(void) { /* ** transfer all objects to a temporary node */ AABTreeNodeClass * dummy_node = new AABTreeNodeClass; RootNode->Transfer_Objects(dummy_node); /* ** delete the old tree and make the dummy node the new root */ delete RootNode; RootNode = dummy_node; /* ** partition the objects */ RootNode->Partition(); /* ** re-index the nodes */ Re_Index_Nodes(); /* ** reset the statistics */ Reset_Statistics(); } void AABTreeCullSystemClass::Re_Partition(const AABoxClass & bounds,SimpleDynVecClass & boxes) { /* ** transfer all objects to a temporary node */ AABTreeNodeClass * dummy_node = new AABTreeNodeClass; RootNode->Transfer_Objects(dummy_node); /* ** delete the old tree */ delete RootNode; /* ** allocate a new root node and tell it to partition the given array of boxes */ RootNode = new AABTreeNodeClass; RootNode->Partition(bounds,boxes); /* ** re-index the nodes */ Re_Index_Nodes(); /* ** reset statistics */ Reset_Statistics(); /* ** re-insert all objects and delete the temporary node */ dummy_node->Box.Extent.Set(0,0,0); CullableClass * obj = get_first_object(dummy_node); while (obj != NULL) { Update_Culling(obj); obj = get_first_object(dummy_node); } delete dummy_node; /* ** Modify the root node so that any object can be added into the tree */ RootNode->Box.Extent.Set(FLT_MAX,FLT_MAX,FLT_MAX); } void AABTreeCullSystemClass::Update_Bounding_Boxes(void) { Update_Bounding_Boxes_Recursive(RootNode); } const AABoxClass & AABTreeCullSystemClass::Get_Bounding_Box(void) { WWASSERT(RootNode); return RootNode->Box; } void AABTreeCullSystemClass::Get_Node_Bounds(int node_id,AABoxClass * set_bounds) { if ((node_id >= 0) && (node_id < NodeCount)) { *set_bounds = IndexedNodes[node_id]->Box; } else { *set_bounds = IndexedNodes[0]->Box; } } void AABTreeCullSystemClass::Reset_Statistics(void) { Stats.NodeCount = NodeCount; Stats.NodesAccepted = 0; Stats.NodesTriviallyAccepted = 0; Stats.NodesRejected = 0; } const AABTreeCullSystemClass::StatsStruct & AABTreeCullSystemClass::Get_Statistics(void) { return Stats; } void AABTreeCullSystemClass::Collect_Objects_Recursive(AABTreeNodeClass * node) { /* ** Collect any objects in this node */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { Add_To_Collection(obj); obj = get_next_object(obj); } } /* ** Statistics */ NODE_TRIVIALLY_ACCEPTED(); /* ** Descend into the children */ if (node->Back) { Collect_Objects_Recursive(node->Back); } if (node->Front) { Collect_Objects_Recursive(node->Front); } } void AABTreeCullSystemClass::Collect_Objects_Recursive(AABTreeNodeClass * node,const Vector3 & point) { /* ** Is the point inside this volume? */ if (node->Box.Contains(point) == false) { NODE_REJECTED(); return; } NODE_ACCEPTED(); /* ** Collect any objects in this node */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { if (obj->Get_Cull_Box().Contains(point)) { Add_To_Collection(obj); } obj = get_next_object(obj); } } /* ** Descend into the children */ if (node->Back) { Collect_Objects_Recursive(node->Back,point); } if (node->Front) { Collect_Objects_Recursive(node->Front,point); } } void AABTreeCullSystemClass::Collect_Objects_Recursive(AABTreeNodeClass * node,const AABoxClass & box) { /* ** Cull the given box against the bounding volume of this node ** If it is culled, stop descending the tree. If the current node is ** completely contained inside the given box, jump into the collection function ** that doesn't do any more volume checking... */ CollisionMath::OverlapType overlap = CollisionMath::Overlap_Test(box,node->Box); if (overlap == CollisionMath::OUTSIDE) { NODE_REJECTED(); return; } else if (overlap == CollisionMath::INSIDE) { Collect_Objects_Recursive(node); return; } NODE_ACCEPTED(); /* ** Test any objects in this node */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { if (CollisionMath::Overlap_Test(box,obj->Get_Cull_Box()) != CollisionMath::OUTSIDE) { Add_To_Collection(obj); } obj = get_next_object(obj); } } /* ** Recurse into any children */ if (node->Back) { Collect_Objects_Recursive(node->Back,box); } if (node->Front) { Collect_Objects_Recursive(node->Front,box); } } void AABTreeCullSystemClass::Collect_Objects_Recursive(AABTreeNodeClass * node,const OBBoxClass & box) { /* ** Cull the given box against the bounding volume of this node ** If it is culled, stop descending the tree. If the current node is ** completely contained inside the given box, jump into the collection function ** that doesn't do any more volume checking... */ CollisionMath::OverlapType overlap = CollisionMath::Overlap_Test(box,node->Box); if (overlap == CollisionMath::OUTSIDE) { NODE_REJECTED(); return; } else if (overlap == CollisionMath::INSIDE) { Collect_Objects_Recursive(node); return; } NODE_ACCEPTED(); /* ** Test any objects in this node */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { if (CollisionMath::Overlap_Test(box,obj->Get_Cull_Box()) != CollisionMath::OUTSIDE) { Add_To_Collection(obj); } obj = get_next_object(obj); } } /* ** Recurse into any children */ if (node->Back) { Collect_Objects_Recursive(node->Back,box); } if (node->Front) { Collect_Objects_Recursive(node->Front,box); } } void AABTreeCullSystemClass::Collect_Objects_Recursive ( AABTreeNodeClass * node, const FrustumClass & frustum, int planes_passed ) { /* ** Cull the bounding volume of this node against the frustum. ** If it is culled, stop descending the tree. */ CollisionMath::OverlapType overlap = CollisionMath::Overlap_Test(frustum,node->Box,planes_passed); if (overlap == CollisionMath::OUTSIDE) { NODE_REJECTED(); return; } else if (overlap == CollisionMath::INSIDE) { Collect_Objects_Recursive(node); return; } NODE_ACCEPTED(); /* ** Test any objects in this node */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { if (CollisionMath::Overlap_Test(frustum,obj->Get_Cull_Box()) != CollisionMath::OUTSIDE) { Add_To_Collection(obj); } obj = get_next_object(obj); } } /* ** Recurse into any children */ if (node->Back) { Collect_Objects_Recursive(node->Back,frustum,planes_passed); } if (node->Front) { Collect_Objects_Recursive(node->Front,frustum,planes_passed); } } void AABTreeCullSystemClass::Collect_Objects_Recursive(AABTreeNodeClass * node,const SphereClass & sphere) { /* ** Is the point inside this volume? */ if (CollisionMath::Overlap_Test (node->Box, sphere) == CollisionMath::OUTSIDE) { NODE_REJECTED(); return; } NODE_ACCEPTED(); /* ** Collect any objects in this node */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { if (CollisionMath::Overlap_Test (obj->Get_Cull_Box(), sphere) != CollisionMath::OUTSIDE) { Add_To_Collection(obj); } obj = get_next_object(obj); } } /* ** Descend into the children */ if (node->Back) { Collect_Objects_Recursive(node->Back,sphere); } if (node->Front) { Collect_Objects_Recursive(node->Front,sphere); } } void AABTreeCullSystemClass::Update_Bounding_Boxes_Recursive(AABTreeNodeClass * node) { MinMaxAABoxClass minmaxbox(node->Box); /* ** Update child boxes first and ensure that we bound them */ if (node->Front) { Update_Bounding_Boxes_Recursive(node->Front); minmaxbox.Add_Box(node->Front->Box); } if (node->Back) { Update_Bounding_Boxes_Recursive(node->Back); minmaxbox.Add_Box(node->Back->Box); } /* ** Make sure we bound our contained objects */ if (node->Object) { CullableClass * obj = get_first_object(node); while (obj) { minmaxbox.Add_Box(obj->Get_Cull_Box()); obj = get_next_object(obj); } } node->Box.Init_Min_Max(minmaxbox.MinCorner,minmaxbox.MaxCorner); } void AABTreeCullSystemClass::Load(ChunkLoadClass & cload) { WWASSERT_PRINT(Object_Count() == 0, "Remove all objects from AAB-Culling system before loading!\n"); delete RootNode; RootNode = new AABTreeNodeClass; // The first chunk should be a version chunk cload.Open_Chunk(); if (cload.Cur_Chunk_ID() != AABTREE_CHUNK_VERSION) { WWDEBUG_ERROR(("Attempting to read an obsolete AAB-Tree!")); cload.Close_Chunk(); return; } // read in the version and verify that it is the current version uint32 version; cload.Read(&version,sizeof(version)); if (version != AABTREE_CURRENT_VERSION) { WWDEBUG_ERROR(("Attempting to read an obsolete AAB-Tree!")); cload.Close_Chunk(); return; } cload.Close_Chunk(); // read in the tree Load_Nodes(RootNode,cload); // re-index all nodes Re_Index_Nodes(); // reset the statistics Reset_Statistics(); } void AABTreeCullSystemClass::Load_Nodes(AABTreeNodeClass * node,ChunkLoadClass & cload) { // Open the node description cload.Open_Chunk(); WWASSERT(cload.Cur_Chunk_ID() == AABTREE_CHUNK_AABNODE); // Load the node description. // Older files will contain a chunk named AABTREE_CHUNK_AABNODE_INFO while newer // files will contain AABTREE_CHUNK_AABNODE_VARIABLES which contains the IOAABNodeStruct // in a micro-chunk. IOAABNodeStruct node_desc; memset(&node_desc,0,sizeof(IOAABNodeStruct)); cload.Open_Chunk(); if (cload.Cur_Chunk_ID() == AABTREE_CHUNK_AABNODE_INFO) { // Loading the legacy format... WWASSERT(cload.Cur_Chunk_ID() == AABTREE_CHUNK_AABNODE_INFO); cload.Read(&node_desc,sizeof(node_desc)); } else if (cload.Cur_Chunk_ID() == AABTREE_CHUNK_AABNODE_VARIABLES) { // Loading the new format, contains micro chunks... while (cload.Open_Micro_Chunk()) { switch(cload.Cur_Micro_Chunk_ID()) { READ_MICRO_CHUNK(cload,AABTREE_VARIABLE_NODESTRUCT,node_desc); READ_MICRO_CHUNK(cload,AABTREE_VARIABLE_USERDATA,node->UserData); } cload.Close_Micro_Chunk(); } } cload.Close_Chunk(); // Initialize the node bounds. node->Box.Center.X = node_desc.Center.X; node->Box.Center.Y = node_desc.Center.Y; node->Box.Center.Z = node_desc.Center.Z; node->Box.Extent.X = node_desc.Extent.X; node->Box.Extent.Y = node_desc.Extent.Y; node->Box.Extent.Z = node_desc.Extent.Z; // Load the contents of the node. cload.Open_Chunk(); WWASSERT(cload.Cur_Chunk_ID() == AABTREE_CHUNK_AABNODE_CONTENTS); Load_Node_Contents(node,cload); cload.Close_Chunk(); // Close the node description cload.Close_Chunk(); // if we are supposed to have a front child, load it if (node_desc.Attributes & AABNODE_ATTRIBUTE_FRONT_CHILD) { WWASSERT(node->Front == NULL); node->Front = new AABTreeNodeClass(); node->Front->Parent = node; Load_Nodes(node->Front,cload); } // if we have a back child, load it if (node_desc.Attributes & AABNODE_ATTRIBUTE_BACK_CHILD) { WWASSERT(node->Back == NULL); node->Back = new AABTreeNodeClass(); node->Back->Parent = node; Load_Nodes(node->Back,cload); } } void AABTreeCullSystemClass::Save(ChunkSaveClass & csave) { csave.Begin_Chunk(AABTREE_CHUNK_VERSION); uint32 version = AABTREE_CURRENT_VERSION; csave.Write(&version,sizeof(uint32)); csave.End_Chunk(); Save_Nodes(RootNode,csave); } void AABTreeCullSystemClass::Save_Nodes(AABTreeNodeClass * node,ChunkSaveClass & csave) { WWASSERT(node); csave.Begin_Chunk(AABTREE_CHUNK_AABNODE); csave.Begin_Chunk(AABTREE_CHUNK_AABNODE_VARIABLES); IOAABNodeStruct node_desc; memset(&node_desc,0,sizeof(node_desc)); node_desc.Center.X = node->Box.Center.X; node_desc.Center.Y = node->Box.Center.Y; node_desc.Center.Z = node->Box.Center.Z; node_desc.Extent.X = node->Box.Extent.X; node_desc.Extent.Y = node->Box.Extent.Y; node_desc.Extent.Z = node->Box.Extent.Z; if (node->Front) { node_desc.Attributes |= AABNODE_ATTRIBUTE_FRONT_CHILD; } if (node->Back) { node_desc.Attributes |= AABNODE_ATTRIBUTE_BACK_CHILD; } WRITE_MICRO_CHUNK(csave,AABTREE_VARIABLE_NODESTRUCT,node_desc); WRITE_MICRO_CHUNK(csave,AABTREE_VARIABLE_USERDATA,node->UserData); csave.End_Chunk(); csave.Begin_Chunk(AABTREE_CHUNK_AABNODE_CONTENTS); Save_Node_Contents(node,csave); csave.End_Chunk(); csave.End_Chunk(); if (node->Front) { Save_Nodes(node->Front,csave); } if (node->Back) { Save_Nodes(node->Back,csave); } } void AABTreeCullSystemClass::Load_Object_Linkage(ChunkLoadClass & cload,CullableClass * obj) { uint32 index; cload.Open_Chunk(); WWASSERT(cload.Cur_Chunk_ID() == AABTREE_CHUNK_NODE_INDEX); cload.Read(&index,sizeof(index)); cload.Close_Chunk(); Add_Object_Internal(obj,index); } void AABTreeCullSystemClass::Save_Object_Linkage(ChunkSaveClass & csave,CullableClass * obj) { WWASSERT(obj); WWASSERT(obj->Get_Culling_System() == this); AABTreeLinkClass * link = (AABTreeLinkClass *)obj->Get_Cull_Link(); WWASSERT(link); AABTreeNodeClass * node = link->Node; WWASSERT(node); uint32 index = node->Index; csave.Begin_Chunk(AABTREE_CHUNK_NODE_INDEX); csave.Write(&index,sizeof(index)); csave.End_Chunk(); } void AABTreeCullSystemClass::Re_Index_Nodes(void) { if (IndexedNodes != NULL) { delete[] IndexedNodes; IndexedNodes = NULL; } NodeCount = Partition_Node_Count(); WWASSERT(NodeCount > 0); IndexedNodes = new AABTreeNodeClass *[NodeCount]; int counter = 0; Re_Index_Nodes_Recursive(RootNode,counter); WWASSERT(counter == NodeCount); } void AABTreeCullSystemClass::Re_Index_Nodes_Recursive(AABTreeNodeClass * node,int & counter) { node->Index = counter; IndexedNodes[counter] = node; counter++; if (node->Front) { Re_Index_Nodes_Recursive(node->Front,counter); } if (node->Back) { Re_Index_Nodes_Recursive(node->Back,counter); } } /************************************************************************* ** ** AABTreeNodeClass Implementation ** *************************************************************************/ AABTreeNodeClass::AABTreeNodeClass(void) : Index(0), Box(Vector3(0,0,0),Vector3(0,0,0)), Parent(NULL), Front(NULL), Back(NULL), Object(NULL), UserData(0) { } AABTreeNodeClass::~AABTreeNodeClass(void) { // objects should be removed before deleting the partition tree WWASSERT(Object == NULL); // delete our children if (Front) { delete Front; Front = NULL; } if (Back) { delete Back; Back = NULL; } } void AABTreeNodeClass::Compute_Bounding_Box(void) { /* ** make the children update their boxes first */ if (Front) { Front->Compute_Bounding_Box(); } if (Back) { Back->Compute_Bounding_Box(); } Compute_Local_Bounding_Box(); } void AABTreeNodeClass::Compute_Local_Bounding_Box(void) { /* ** Now make sure we bound our children */ MinMaxAABoxClass box(Vector3(FLT_MAX,FLT_MAX,FLT_MAX),Vector3(-FLT_MAX,-FLT_MAX,-FLT_MAX)); if (Front) { box.Add_Box(Front->Box); } if (Back) { box.Add_Box(Back->Box); } /* ** bound the objects in this node */ CullableClass * obj = Object; while (obj) { box.Add_Box(obj->Get_Cull_Box()); AABTreeLinkClass * link = (AABTreeLinkClass *)obj->Get_Cull_Link(); obj = link->NextObject; } Box.Init(box); } float AABTreeNodeClass::Compute_Volume(void) { return Box.Volume(); } void AABTreeNodeClass::Add_Object(CullableClass * obj,bool update_bounds) { AABTreeLinkClass * link = (AABTreeLinkClass *)obj->Get_Cull_Link(); WWASSERT(link); link->Node = this; link->NextObject = Object; Object = obj; if (update_bounds) { // if this is the only object and we have no children, just copy // the object's bounding box, otherwise, add it to what we have if ((Object_Count() == 1) && (Front == NULL) && (Back == NULL)) { Box = obj->Get_Cull_Box(); } else { Box.Add_Box(obj->Get_Cull_Box()); } } } void AABTreeNodeClass::Remove_Object(CullableClass * obj) { WWASSERT(obj); // find the given object in our linked list CullableClass * prevobj = NULL; CullableClass * curobj = Object; while (curobj) { AABTreeLinkClass * link = (AABTreeLinkClass *)curobj->Get_Cull_Link(); if (curobj == obj) { // found the object, unlink it. if (prevobj) { AABTreeLinkClass * prevlink = (AABTreeLinkClass *)prevobj->Get_Cull_Link(); prevlink->NextObject = link->NextObject; } else { Object = link->NextObject; } link->NextObject = NULL; link->Node = NULL; return; } // move to the next object prevobj = curobj; curobj = link->NextObject; } } void AABTreeNodeClass::Transfer_Objects(AABTreeNodeClass * dummy_node) { // unlink all of our objects, relinking them to the dummy_node while (Object) { CullableClass * obj = Object; Remove_Object(obj); dummy_node->Add_Object(obj); } // do the same with our children if (Front) { Front->Transfer_Objects(dummy_node); } if (Back) { Back->Transfer_Objects(dummy_node); } } int AABTreeNodeClass::Object_Count(void) { CullableClass * obj = Object; int count = 0; while (obj) { count++; obj = ((AABTreeLinkClass *)obj->Get_Cull_Link())->NextObject; } return count; } CullableClass * AABTreeNodeClass::Peek_Object(int index) { int count = 0; CullableClass * obj = Object; WWASSERT(obj != NULL); while (obj && (count != index)) { count++; obj = ((AABTreeLinkClass *)obj->Get_Cull_Link())->NextObject; } WWASSERT(count == index); return obj; } /****************************************************************************************** ** ** Partitioning code which partitions the objects in the tree and passes them into ** the new children ** ******************************************************************************************/ void AABTreeNodeClass::Partition(void) { /* ** if we're down to only 2 objects, we're done */ int obj_count = Object_Count(); if (obj_count <= 2) return; /* ** Create an array of the bounding boxes of our objects */ SimpleDynVecClass boxes(obj_count); CullableClass * obj = Object; while (obj != NULL) { boxes.Add(obj->Get_Cull_Box()); obj = get_next_object(obj); } /* ** Select and assign the splitting plane ** De-allocate the array of boxes to conserve memory */ SplitChoiceStruct sc; Select_Splitting_Plane(&sc,boxes); boxes.Resize(0); /* ** If there was no good split, just leave all of ** the objects in this node */ if (sc.Cost == FLT_MAX) { return; } /* ** Split the tiles */ AABTreeNodeClass * front = new AABTreeNodeClass; AABTreeNodeClass * back = new AABTreeNodeClass; Split_Objects(sc,front,back); /* ** Build a front tree if necessary. */ if (front->Object_Count() > 0) { Front = front; Front->Parent = this; Front->Partition(); } else { delete front; front = NULL; } /* ** Build a back tree if necessary. */ if (back->Object_Count() > 0) { Back = back; Back->Parent = this; Back->Partition(); } else { delete back; back = NULL; } } void AABTreeNodeClass::Split_Objects(const AABTreeNodeClass::SplitChoiceStruct & sc,AABTreeNodeClass * front,AABTreeNodeClass * back) { // This function assumes that this node is a leaf WWASSERT(Front == NULL); WWASSERT(Back == NULL); WWASSERT(Object_Count() == sc.FrontCount + sc.BackCount); int fcount = 0; int bcount = 0; // unlink all of our objects, relinking them to the appropriate node while (Object) { // pull the object out of this node CullableClass * obj = Object; Remove_Object(Object); // decide which node to add the object to, // NOTE: we have to use the same convention as Compute_Score! const AABoxClass & box = obj->Get_Cull_Box(); if (CollisionMath::Overlap_Test(sc.Plane,box.Center) == CollisionMath::FRONT) { front->Add_Object(obj); fcount++; } else { back->Add_Object(obj); bcount++; } } // copy the bounding boxes front->Box = sc.FrontBox; front->Box.Extent += Vector3(WWMATH_EPSILON,WWMATH_EPSILON,WWMATH_EPSILON); back->Box = sc.BackBox; back->Box.Extent += Vector3(WWMATH_EPSILON,WWMATH_EPSILON,WWMATH_EPSILON); // when we are all done, the counts should match. WWASSERT(fcount == sc.FrontCount); WWASSERT(bcount == sc.BackCount); } /****************************************************************************************** ** ** Partitioning code which generates the tree based on a set of input boxes ** ******************************************************************************************/ void AABTreeNodeClass::Partition(const AABoxClass & bounds,SimpleDynVecClass & boxes) { Box = bounds; /* ** if we're down to only 1 box, we're done */ if (boxes.Count() <= 1) return; /* ** Select and assign the splitting plane */ SplitChoiceStruct sc; Select_Splitting_Plane(&sc,boxes); /* ** If there was no good split, just leave all of ** the objects in this node */ if (sc.Cost == FLT_MAX) { return; } /* ** Split the boxes ** Deallocate the input box array to conserve RAM */ SimpleDynVecClass frontboxes(sc.FrontCount); SimpleDynVecClass backboxes(sc.BackCount); Split_Boxes(sc,boxes,frontboxes,backboxes); boxes.Delete_All(); /* ** Build a front tree if necessary. */ if (frontboxes.Count() > 0) { Front = new AABTreeNodeClass; Front->Parent = this; Front->Partition(sc.FrontBox,frontboxes); } else { Front = NULL; } /* ** Build a back tree if necessary. */ if (backboxes.Count() > 0) { Back = new AABTreeNodeClass; Back->Parent = this; Back->Partition(sc.BackBox,backboxes); } else { Back = NULL; } } void AABTreeNodeClass::Split_Boxes ( const AABTreeNodeClass::SplitChoiceStruct & sc, SimpleDynVecClass & boxes, SimpleDynVecClass & frontboxes, SimpleDynVecClass & backboxes ) { WWASSERT(boxes.Count() == sc.FrontCount + sc.BackCount); // copy each box in the input array into the appropriate output array for (int i=0; i & boxes ) { const int NUM_TRYS = 300; /* ** Try putting axis-aligned planes through some random vertices */ int objcount = boxes.Count(); int trys = 0; for (trys = 0; trys < MIN(NUM_TRYS,objcount); trys++) { int obj_index; SplitChoiceStruct test; /* ** Select a random object */ obj_index = rand() % objcount; const AABoxClass & box = boxes[obj_index]; /* ** Select a random plane which co-incides with one of the faces ** of the object's bounding box */ switch(rand() % 6) { case 0: test.Plane.Set(AAPlaneClass::XNORMAL,box.Center.X + box.Extent.X); break; case 1: test.Plane.Set(AAPlaneClass::XNORMAL,box.Center.X - box.Extent.X); break; case 2: test.Plane.Set(AAPlaneClass::YNORMAL,box.Center.Y + box.Extent.Y); break; case 3: test.Plane.Set(AAPlaneClass::YNORMAL,box.Center.Y - box.Extent.Y); break; case 4: test.Plane.Set(AAPlaneClass::ZNORMAL,box.Center.Z + box.Extent.Z); break; case 5: test.Plane.Set(AAPlaneClass::ZNORMAL,box.Center.Z - box.Extent.Z); break; }; /* ** Get the score for this plane */ Compute_Score(&test,boxes); if (test.Cost < sc->Cost) { *sc = test; } } /* ** Still haven't found a valid splitting plane, uh-oh. */ if ((trys >= MIN(NUM_TRYS,objcount)) && (sc->Cost == FLT_MAX)) { Select_Splitting_Plane_Brute_Force(sc,boxes); return; } } void AABTreeNodeClass::Select_Splitting_Plane_Brute_Force ( AABTreeNodeClass::SplitChoiceStruct * sc, SimpleDynVecClass & boxes ) { /* ** Try putting axis-aligned planes along each face of each box */ int objcount = boxes.Count(); for (int obj_index = 0; obj_index < objcount; obj_index++) { AAPlaneClass plane; const AABoxClass & box = boxes[obj_index]; /* ** Try each face of this box */ for (int plane_index = 0; plane_index < 6; plane_index++) { SplitChoiceStruct test; switch(plane_index % 6) { case 0: test.Plane.Set(AAPlaneClass::XNORMAL,box.Center.X + box.Extent.X); break; case 1: test.Plane.Set(AAPlaneClass::XNORMAL,box.Center.X - box.Center.Y); break; case 2: test.Plane.Set(AAPlaneClass::YNORMAL,box.Center.Y + box.Center.Y); break; case 3: test.Plane.Set(AAPlaneClass::YNORMAL,box.Center.Y - box.Center.Y); break; case 4: test.Plane.Set(AAPlaneClass::ZNORMAL,box.Center.Z + box.Center.Z); break; case 5: test.Plane.Set(AAPlaneClass::ZNORMAL,box.Center.Z - box.Center.Z); break; }; test.FrontBox.Init_Empty(); test.BackBox.Init_Empty(); /* ** Get the score for this plane */ Compute_Score(&test,boxes); if (test.Cost < sc->Cost) { *sc = test; } } } /* ** Notify user that we couldn't split this node */ #ifdef WWDEBUG if (sc->Cost == FLT_MAX) { WWDEBUG_SAY(("Unable to split node! objcount = %d. (%.2f,%.2f,%.2f)\r\n",objcount,Box.Center.X, Box.Center.Y, Box.Center.Z)); } #endif } void AABTreeNodeClass::Compute_Score ( AABTreeNodeClass::SplitChoiceStruct * sc, SimpleDynVecClass & boxes ) { /* ** Suitability of a plane as a partition plane is based on the following factors: ** - How many "tiles" are on each side of the plane, */ for (int i=0; iPlane,box.Center) == CollisionMath::FRONT) { sc->FrontCount++; sc->FrontBox.Add_Box(box); } else { sc->BackCount++; sc->BackBox.Add_Box(box); } } /* ** Compute the cost. */ float back_cost = sc->BackBox.Volume() * sc->BackCount; float front_cost = sc->FrontBox.Volume() * sc->FrontCount; sc->Cost = front_cost + back_cost; if ((sc->FrontCount == 0) || (sc->BackCount == 0)) { sc->Cost = FLT_MAX; } } /************************************************************************************** AABTreeIterator Implemenation **************************************************************************************/ AABTreeIterator::AABTreeIterator(AABTreeCullSystemClass * tree) : Tree(tree), CurNodeIndex(0) { WWASSERT(Tree != NULL); } void AABTreeIterator::Reset(void) { CurNodeIndex = 0; } bool AABTreeIterator::Enter_Parent(void) { validate(); if (CurNodeIndex != 0) { CurNodeIndex = Tree->IndexedNodes[CurNodeIndex]->Parent->Index; return true; } else { return false; } } bool AABTreeIterator::Enter_Sibling(void) { validate(); if (CurNodeIndex != 0) { /* ** find which child of our parent we are */ AABTreeNodeClass * parent = Tree->IndexedNodes[CurNodeIndex]->Parent; AABTreeNodeClass * parent_front = parent->Front; AABTreeNodeClass * parent_back = parent->Back; /* ** if our parent doesn't have two children, we don't have a sibling */ if ((parent_front == NULL) || (parent_back == NULL)) { return false; } /* ** if we our our parent's front child, go to its back child */ if ((int)parent_front->Index == CurNodeIndex) { CurNodeIndex = parent_back->Index; return true; } /* ** if we our our parent's back child, go to its front child */ if ((int)parent_back->Index == (int)CurNodeIndex) { CurNodeIndex = parent_front->Index; return true; } } return false; } bool AABTreeIterator::Has_Front_Child(void) { validate(); return (Tree->IndexedNodes[CurNodeIndex]->Front != NULL); } bool AABTreeIterator::Enter_Front_Child(void) { validate(); if (Has_Front_Child()) { CurNodeIndex = Tree->IndexedNodes[CurNodeIndex]->Front->Index; return true; } return false; } bool AABTreeIterator::Has_Back_Child(void) { validate(); return (Tree->IndexedNodes[CurNodeIndex]->Back != NULL); } bool AABTreeIterator::Enter_Back_Child(void) { if (Has_Back_Child()) { CurNodeIndex = Tree->IndexedNodes[CurNodeIndex]->Back->Index; return true; } return false; } int AABTreeIterator::Get_Current_Node_Index(void) { return CurNodeIndex; } void AABTreeIterator::Get_Current_Box(AABoxClass * set_box) { Tree->Get_Node_Bounds(CurNodeIndex,set_box); } void AABTreeIterator::validate(void) { if ((CurNodeIndex < 0) || (CurNodeIndex >= Tree->NodeCount)) { CurNodeIndex = 0; } } /* Can we make a more compact AABTree? Here is the existing data in each Node: uint32 Index; // Index of this node AABoxClass Box; // Bounding box of the node AABTreeNodeClass * Parent; // parent of this node AABTreeNodeClass * Front; // front node AABTreeNodeClass * Back; // back node CullableClass * Object; // objects in this node uint32 UserData; // 32bit field for the user, initialized to 0 Total Size: 48 bytes Here is a possible replacement: uint16 Index; short x,y,z,w,l,h; // Need origin and scale factors for the boxes stored in tree uint16 ParentIndex; uint16 FrontIndex; uint16 BackIndex; CullableClass * Object; uint32 UserData; Total Size: 28 bytes */