/* ** 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/gridcull.h $* * * * Author:: Greg Hjelstrom * * * * $Modtime:: 5/10/01 10:42a $* * * * $Revision:: 14 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * GridCullSystemClass::clamp_indices_to_grid -- constrains indices to be a valid location * * GridCullSystemClass::map_point_to_cell -- determines which cell the point is in * * GridCullSystemClass::map_point_to_address -- determines the address of a point in the gri * * GridCullSystemClass::map_indices_to_address -- computes the address for given index tripl * * GridCullSystemClass::total_cell_count -- returns the total number of cells in the grid * * GridCullSystemClass::compute_box -- computes the bounding box for a grid cell * * GridCullSystemClass::compute_box -- computes bounding box for a range of grid cells * * GridCullSystemClass::init_volume -- inits volume to contain the given range * * GridCullSystemClass::init_volume -- inits volume to contain the given line segment * * GridCullSystemClass::init_volume -- inits volume to contain the given box * * GridCullSystemClass::init_volume -- inits volume to contain the given oriented box * * GridCullSystemClass::init_volume -- inits volume to contain the given frustum * * GridCullSystemClass::VolumeStruct::VolumeStruct -- constructor * * GridCullSystemClass::VolumeStruct::VolumeStruct -- constructor * * GridCullSystemClass::VolumeStruct::Is_Leaf -- check if volume is a leaf * * GridCullSystemClass::VolumeStruct::Is_Empty -- check if volume is empty (or invalid) * * GridCullSystemClass::VolumeStruct::Split -- split this volume * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #if defined(_MSC_VER) #pragma once #endif #include "cullsys.h" #include "mempool.h" #include "frustum.h" #include "aabox.h" #include "lineseg.h" #include "obbox.h" #include class ChunkLoadClass; class ChunkSaveClass; /* ** GridCullSystemClass ** This is a culling system designed for dynamic objects (objects which are moving or ** changing bounding box size). It features O(1) insertion as opposed to ** the AABTree, QuadTree, or Octree insertion times which are O(logn). It's disadvantages ** compared to the above mentioned systems are that it must uniformly divide space. The ** AABTree conforms to the geometry placed in it and can therefore cull things more ** efficiently. In actual use, this system is like an AAB-tree which is uniformly subdivided, ** its just that we can jump straight to the leaves of the tree for insertion... ** ** The bounding volume for each grid cell is considered to be the volume of the cell, expanded ** by the maximum object size. Inserting an object into the grid is a matter of determining ** which cell its center point is in. Objects which are larger than the maximum size are ** allowed but they are simply put into a linked list which is iterated with each call to ** the culling system (linearly culled rather than logarithmic...) */ class GridCullSystemClass : public CullSystemClass { public: GridCullSystemClass(void); ~GridCullSystemClass(void); virtual void Collect_Objects(const Vector3 & point); virtual void Collect_Objects(const AABoxClass & box); virtual void Collect_Objects(const OBBoxClass & box); virtual void Collect_Objects(const FrustumClass & frustum); virtual void Re_Partition(const Vector3 & min,const Vector3 & max,float objdim); virtual void Update_Culling(CullableClass * obj); virtual void Load(ChunkLoadClass & cload); virtual void Save(ChunkSaveClass & csave); virtual int Get_Object_Count (void) const { return ObjCount; } /* ** Statistics */ struct StatsStruct { int NodeCount; int NodesAccepted; int NodesTriviallyAccepted; int NodesRejected; }; void Reset_Statistics(void); const StatsStruct & Get_Statistics(void); void Get_Min_Cell_Size (Vector3 &size) const { size = MinCellSize; } void Set_Min_Cell_Size (const Vector3 &size) { MinCellSize = size; } int Get_Termination_Count (void) const { return TerminationCellCount; } void Set_Termination_Count (int count) { TerminationCellCount = count; } protected: void Collect_And_Unlink_All(void); void Add_Object_Internal(CullableClass * obj); void Remove_Object_Internal(CullableClass * obj); enum { TERMINATION_CELL_COUNT = 16384, // algorithm terminates if we ever have more than this many cells. UNGRIDDED_ADDRESS = 0xFFFFFFFF // address given to objs that didn't fit in grid }; // Constants which control the division of space: Vector3 MinCellSize; // min dimensions for a cell (don't go below this...) float MaxObjExtent; // max extent/radius (objects bigger than this are just put in a list) int TerminationCellCount; // Constants that define the division of space Vector3 Origin; Vector3 CellDim; Vector3 OOCellDim; int CellCount[3]; // 3D array of pointers to objects in each cell CullableClass ** Cells; // list of objs that were outside or too big for the grid. CullableClass * NoGridList; // number of objects in the system int ObjCount; // statistics StatsStruct Stats; // Structure used to define a volume in the grid. The volume spans from the cell indexed // by Min[0],Min[1],Min[2] to the cell indexed by Max[0]-1,Max[1]-1,Max[2]-1. struct VolumeStruct { VolumeStruct(void); VolumeStruct(int i0,int j0,int k0,int i1,int j1,int k1); bool Is_Leaf(void) const; bool Is_Empty(void) const; void Split(VolumeStruct & v0,VolumeStruct & v1) const; int Min[3]; int Max[3]; }; void link_object(CullableClass * obj); void link_object(CullableClass * obj,int address); void unlink_object(CullableClass * obj); void link_object_to_list(CullableClass ** head,CullableClass * obj); void unlink_object_from_list(CullableClass ** head,CullableClass * obj); bool map_point_to_cell(const Vector3 & pt,int & set_i,int & set_j,int & set_k); bool map_point_to_address(const Vector3 & pt,int & set_address); WWINLINE int map_indices_to_address(int i,int j,int k); void clamp_indices_to_grid(int * i,int * j,int * k); int total_cell_count(void); void compute_box(int i,int j,int k,AABoxClass * set_box); void compute_box(const VolumeStruct & area, AABoxClass * set_box); void init_volume(const Vector3 & bound_min,const Vector3 & bound_max,VolumeStruct * set_volume); void init_volume(const Vector3 & point,VolumeStruct * set_volume); void init_volume(const LineSegClass & line,VolumeStruct * set_volume); void init_volume(const AABoxClass & box,VolumeStruct * set_volume); void init_volume(const OBBoxClass & box,VolumeStruct * set_volume); void init_volume(const FrustumClass & frustum,VolumeStruct * set_volume); void collect_objects_in_leaf(const Vector3 & point,CullableClass * head); void collect_objects_in_leaf(const AABoxClass & aabox,CullableClass * head); void collect_objects_in_leaf(const OBBoxClass & obbox,CullableClass * head); void collect_objects_in_leaf(const FrustumClass & frustum,CullableClass * head); }; /* ** Macros for gathering statistics. Placed here in the header file so that ** derived classes can use them as well. */ #ifdef WWDEBUG #define GRIDCULL_NODE_ACCEPTED Stats.NodesAccepted ++; #define GRIDCULL_NODE_TRIVIALLY_ACCEPTED Stats.NodesTriviallyAccepted ++; #define GRIDCULL_NODE_REJECTED Stats.NodesRejected ++; #else #define GRIDCULL_NODE_ACCEPTED #define GRIDCULL_NODE_TRIVIALLY_ACCEPTED #define GRIDCULL_NODE_REJECTED #endif /* ** TypedGridCullSystemClass ** This class simply enforces that a certain type of object is inserted into the grid cull system. ** It exposes the add, remove, and collection iterating functions and is intended to be the class ** actually used by the end user. */ template class TypedGridCullSystemClass : public GridCullSystemClass { public: virtual void Add_Object(T * obj) { Add_Object_Internal(obj); } virtual void Remove_Object(T * obj) { Remove_Object_Internal(obj); } T * Get_First_Collected_Object(void) { return (T*)Get_First_Collected_Object_Internal(); } T * Get_Next_Collected_Object(T * obj) { return (T*)Get_Next_Collected_Object_Internal(obj); } }; /* ** GridLinkClass ** This structure is used to link cullable objects into a Grid culling system ** This class is should only be used by classes which derive from GridCullSystemClass ** not normal users. */ class GridLinkClass : public CullLinkClass, public AutoPoolClass { public: GridLinkClass(GridCullSystemClass * system); virtual ~GridLinkClass(void); int GridAddress; // address in the grid. CullableClass * Prev; // prev object in this cell CullableClass * Next; // next object in this cell }; /* ** GridListIterator ** This is just a simple iterator that contains the code for traversing the ** list of objects either in a cell in the grid or in the NoGridList... ** This class should only be used by classes which derive from GridCullSystemClass ** not normal users. */ class GridListIterator { public: GridListIterator(CullableClass * head) { First(head); } void First(CullableClass * head) { Head = head; CurObj = head; } void First(void) { CurObj = Head; } void Next(void) { if (CurObj) { CurObj = ((GridLinkClass *)CurObj->Get_Cull_Link())->Next; } } void Prev(void) { if (CurObj) { CurObj = ((GridLinkClass *)CurObj->Get_Cull_Link())->Prev; } } bool Is_Done(void) { return (CurObj == NULL); } CullableClass * Get_Obj(void) { if (CurObj) { CurObj->Add_Ref(); } return CurObj; } CullableClass * Peek_Obj(void) { return CurObj; } private: CullableClass * Head; // head of the list we're working in CullableClass * CurObj; // node we're currently at. }; /*********************************************************************************************** * GridCullSystemClass::clamp_indices_to_grid -- constrains indices to be a valid location * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::clamp_indices_to_grid(int * i,int * j,int * k) { if (*i < 0) *i = 0; if (*i >= CellCount[0]) *i = CellCount[0] - 1; if (*j < 0) *j = 0; if (*j >= CellCount[1]) *j = CellCount[1] - 1; if (*k < 0) *k = 0; if (*k >= CellCount[2]) *k = CellCount[2] - 1; } /*********************************************************************************************** * GridCullSystemClass::map_point_to_cell -- determines which cell the point is in * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE bool GridCullSystemClass::map_point_to_cell(const Vector3 & pt,int & set_i,int & set_j,int & set_k) { Vector3 dp = pt - Origin; set_i = floor(dp.X * OOCellDim.X); set_j = floor(dp.Y * OOCellDim.Y); set_k = floor(dp.Z * OOCellDim.Z); if ( (set_i >= 0) && (set_j >= 0) && (set_k >= 0) && (set_i < CellCount[0]) && (set_j < CellCount[1]) && (set_k < CellCount[2]) ) { return true; } return false; } /*********************************************************************************************** * GridCullSystemClass::map_point_to_address -- determines the address of a point in the grid * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE bool GridCullSystemClass::map_point_to_address(const Vector3 & pt,int &set_address) { int i,j,k; bool res = map_point_to_cell(pt,i,j,k); if (res) { set_address = map_indices_to_address(i,j,k); return true; } else { set_address = UNGRIDDED_ADDRESS; return false; } } /*********************************************************************************************** * GridCullSystemClass::map_indices_to_address -- computes the address for given index triplet * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE int GridCullSystemClass::map_indices_to_address(int i,int j,int k) { return i + j*CellCount[0] + k*CellCount[0]*CellCount[1]; } /*********************************************************************************************** * GridCullSystemClass::total_cell_count -- returns the total number of cells in the grid * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE int GridCullSystemClass::total_cell_count(void) { return CellCount[0] * CellCount[1] * CellCount[2]; } /*********************************************************************************************** * GridCullSystemClass::compute_box -- computes the bounding box for a grid cell * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::compute_box(int i,int j,int k,AABoxClass * set_box) { WWASSERT(set_box != NULL); WWASSERT((i >= 0) && (j >= 0) && (k >= 0)); WWASSERT((i < CellCount[0]) && (j < CellCount[1]) && (k < CellCount[2])); Vector3 min,max; min.X = Origin.X + i * CellDim.X - MaxObjExtent; min.Y = Origin.Y + j * CellDim.Y - MaxObjExtent; min.Z = Origin.Z + k * CellDim.Z - MaxObjExtent; max.X = min.X + CellDim.X + 2.0f*MaxObjExtent; max.Y = min.Y + CellDim.Y + 2.0f*MaxObjExtent; max.Z = min.Z + CellDim.Z + 2.0f*MaxObjExtent; set_box->Init((min+max)*0.5f, (min-max)*0.5f); } /*********************************************************************************************** * GridCullSystemClass::compute_box -- computes bounding box for a range of grid cells * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::compute_box(const GridCullSystemClass::VolumeStruct & vol, AABoxClass * set_box) { WWASSERT(set_box != NULL); WWASSERT((vol.Min[0] >= 0) && (vol.Min[1] >= 0) && (vol.Min[2] >= 0)); WWASSERT((vol.Max[0] <= CellCount[0]) && (vol.Max[1] <= CellCount[1]) && (vol.Max[2] <= CellCount[2])); Vector3 min,max; min.X = Origin.X + vol.Min[0] * CellDim.X - MaxObjExtent; min.Y = Origin.Y + vol.Min[1] * CellDim.Y - MaxObjExtent; min.Z = Origin.Z + vol.Min[2] * CellDim.Z - MaxObjExtent; max.X = Origin.X + vol.Max[0] * CellDim.X + MaxObjExtent; max.Y = Origin.Y + vol.Max[1] * CellDim.Y + MaxObjExtent; max.Z = Origin.Z + vol.Max[2] * CellDim.Z + MaxObjExtent; Vector3 center((max.X+min.X)*0.5f,(max.Y+min.Y)*0.5f,(max.Z+min.Z)*0.5f); Vector3 extent((max.X-min.X)*0.5f,(max.Y-min.Y)*0.5f,(max.Z-min.Z)*0.5f); set_box->Init(center,extent); } /*********************************************************************************************** * GridCullSystemClass::init_volume -- inits volume to contain the given range * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::init_volume ( const Vector3 & bound_min, const Vector3 & bound_max, VolumeStruct * set_vol ) { // expand the box by the maximum size of any object Vector3 grid_min = bound_min; grid_min.X -= MaxObjExtent; grid_min.Y -= MaxObjExtent; grid_min.Z -= MaxObjExtent; Vector3 grid_max = bound_max; grid_max.X += MaxObjExtent; grid_max.Y += MaxObjExtent; grid_max.Z += MaxObjExtent; // now compute the grid coordinates of the corners of the box GridCullSystemClass::map_point_to_cell(grid_min,set_vol->Min[0],set_vol->Min[1],set_vol->Min[2]); GridCullSystemClass::map_point_to_cell(grid_max,set_vol->Max[0],set_vol->Max[1],set_vol->Max[2]); // now clamp the volume to the actual grid clamp_indices_to_grid(&(set_vol->Min[0]),&(set_vol->Min[1]),&(set_vol->Min[2])); clamp_indices_to_grid(&(set_vol->Max[0]),&(set_vol->Max[1]),&(set_vol->Max[2])); // increment the outer edge of the box due to the way we traverse the grid... set_vol->Max[0] ++; set_vol->Max[1] ++; set_vol->Max[2] ++; } /*********************************************************************************************** * GridCullSystemClass::init_volume -- inits volume to contain the given line segment * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::init_volume(const LineSegClass & line,VolumeStruct * set_volume) { Vector3 min_pt,max_pt; min_pt.X = WWMath::Min(line.Get_P0().X,line.Get_P1().X); max_pt.X = WWMath::Max(line.Get_P0().X,line.Get_P1().X); min_pt.Y = WWMath::Min(line.Get_P0().Y,line.Get_P1().Y); max_pt.Y = WWMath::Max(line.Get_P0().Y,line.Get_P1().Y); min_pt.Z = WWMath::Min(line.Get_P0().Z,line.Get_P1().Z); max_pt.Z = WWMath::Max(line.Get_P0().Z,line.Get_P1().Z); init_volume(min_pt,max_pt,set_volume); } /*********************************************************************************************** * GridCullSystemClass::init_volume -- inits volume to contain the given box * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::init_volume(const AABoxClass & box,VolumeStruct * set_volume) { init_volume(box.Center - box.Extent,box.Center + box.Extent,set_volume); } /*********************************************************************************************** * GridCullSystemClass::init_volume -- inits volume to contain the given oriented box * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::init_volume(const OBBoxClass & box,VolumeStruct * set_volume) { Vector3 aaextent; box.Compute_Axis_Aligned_Extent(&aaextent); init_volume(box.Center - aaextent,box.Center + aaextent,set_volume); } /*********************************************************************************************** * GridCullSystemClass::init_volume -- inits volume to contain the given frustum * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::init_volume(const FrustumClass & frustum,VolumeStruct * set_volume) { init_volume(frustum.Get_Bound_Min(),frustum.Get_Bound_Max(),set_volume); } /*********************************************************************************************** * GridCullSystemClass::VolumeStruct::VolumeStruct -- constructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * *=============================================================================================*/ WWINLINE GridCullSystemClass::VolumeStruct::VolumeStruct(void) { } /*********************************************************************************************** * GridCullSystemClass::VolumeStruct::VolumeStruct -- constructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE GridCullSystemClass::VolumeStruct::VolumeStruct(int i0,int j0,int k0,int i1,int j1,int k1) { Min[0] = i0; Min[1] = j0; Min[2] = k0; Max[0] = i1; Max[1] = j1; Max[2] = k1; WWASSERT(Max[0] > Min[0]); WWASSERT(Max[1] > Min[1]); WWASSERT(Max[2] > Min[2]); } /*********************************************************************************************** * GridCullSystemClass::VolumeStruct::Is_Leaf -- check if volume is a leaf * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE bool GridCullSystemClass::VolumeStruct::Is_Leaf(void) const { return ((Max[0]-Min[0] == 1) && (Max[1]-Min[1] == 1) && (Max[2]-Min[2] == 1)); } /*********************************************************************************************** * GridCullSystemClass::VolumeStruct::Is_Empty -- check if volume is empty (or invalid) * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE bool GridCullSystemClass::VolumeStruct::Is_Empty(void) const { return ((Max[0]-Min[0] <= 0) || (Max[1]-Min[1] <= 0) || (Max[2]-Min[2] <= 0)); } /*********************************************************************************************** * GridCullSystemClass::VolumeStruct::Split -- split this volume * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 3/30/2000 gth : Created. * *=============================================================================================*/ WWINLINE void GridCullSystemClass::VolumeStruct::Split(VolumeStruct & v0,VolumeStruct & v1) const { // find the longest dimension int split_axis = 0; int delta[3]; delta[0] = Max[0] - Min[0]; delta[1] = Max[1] - Min[1]; delta[2] = Max[2] - Min[2]; if (delta[1] > delta[split_axis]) split_axis = 1; if (delta[2] > delta[split_axis]) split_axis = 2; WWASSERT(delta[split_axis] > 0); // split the volume perpendicularly to that dimension memcpy(&v0,this,sizeof(VolumeStruct)); memcpy(&v1,this,sizeof(VolumeStruct)); v0.Max[split_axis] = v1.Min[split_axis] = Min[split_axis] + (delta[split_axis] >> 1); }