This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/WWMath/aabtreecull.cpp

1616 lines
No EOL
38 KiB
C++

/*
** 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 <http://www.gnu.org/licenses/>.
*/
/***********************************************************************************************
*** 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 <string.h>
#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<AABoxClass> & 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<AABoxClass> 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<AABoxClass> & 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<AABoxClass> frontboxes(sc.FrontCount);
SimpleDynVecClass<AABoxClass> 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<AABoxClass> & boxes,
SimpleDynVecClass<AABoxClass> & frontboxes,
SimpleDynVecClass<AABoxClass> & 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.Count(); i++) {
const AABoxClass & box = boxes[i];
if (CollisionMath::Overlap_Test(sc.Plane,box.Center) == CollisionMath::FRONT) {
frontboxes.Add(box);
} else {
backboxes.Add(box);
}
}
// when we are all done, the counts should match.
WWASSERT(frontboxes.Count() == sc.FrontCount);
WWASSERT(backboxes.Count() == sc.BackCount);
}
/******************************************************************************************
**
** Partitioning code which searches for the best split for a given set of boxes. This
** code is re-used by both types of partitioning (partitioning contained objects or
** partitioning based on a set of "seed" boxes.)
**
******************************************************************************************/
void AABTreeNodeClass::Select_Splitting_Plane
(
SplitChoiceStruct * sc,
SimpleDynVecClass<AABoxClass> & 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<AABoxClass> & 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<AABoxClass> & 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; i<boxes.Count(); i++) {
const AABoxClass & box = boxes[i];
if (CollisionMath::Overlap_Test(sc->Plane,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
*/