959 lines
25 KiB
C++
959 lines
25 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 : WWPhys *
|
||
|
* *
|
||
|
* $Archive:: /Commando/Code/wwphys/dynamicaabtreecull.cpp $*
|
||
|
* *
|
||
|
* Original Author:: Greg Hjelstrom *
|
||
|
* *
|
||
|
* $Author:: Greg_h $*
|
||
|
* *
|
||
|
* $Modtime:: 3/06/02 11:24a $*
|
||
|
* *
|
||
|
* $Revision:: 22 $*
|
||
|
* *
|
||
|
*---------------------------------------------------------------------------------------------*
|
||
|
* Functions: *
|
||
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
|
||
|
#include "dynamicaabtreecull.h"
|
||
|
#include "pscene.h"
|
||
|
#include "visrendercontext.h"
|
||
|
#include "visoptimizationcontext.h"
|
||
|
#include "visoptprogress.h"
|
||
|
#include "visrasterizer.h"
|
||
|
#include "boxrobj.h"
|
||
|
#include "vector3i.h"
|
||
|
#include "chunkio.h"
|
||
|
#include "colmathfrustum.h"
|
||
|
#include "colmathplane.h"
|
||
|
#include "colmathaabox.h"
|
||
|
#include "wwmemlog.h"
|
||
|
|
||
|
const float DEFUALT_MAXOBJRADIUS = 7.5f;
|
||
|
const float MIN_CELL_HEIGHT = 3.5f;
|
||
|
|
||
|
/*
|
||
|
** Chunk ID's used by DynamicAABTreeClass
|
||
|
*/
|
||
|
enum
|
||
|
{
|
||
|
DYNAMICAABTREE_CHUNK_AABTREE_CLASS_DATA = 622120600,
|
||
|
DYNAMICAABTREE_CHUNK_VARIABLES,
|
||
|
|
||
|
DYNAMICAABTREE_VARIABLE_BASEVISID = 0x00,
|
||
|
DYNAMICAABTREE_VARIABLE_MAXOBJRADIUS,
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
** DynamicAABTreeCullClass is a derived AABTree which assumes it contains PhysClasses
|
||
|
** these two functions encapsulate some typecasting which happens in a lot
|
||
|
** of places...
|
||
|
*/
|
||
|
inline PhysClass * get_first_object(AABTreeNodeClass * node)
|
||
|
{
|
||
|
return (PhysClass *)(node->Object);
|
||
|
}
|
||
|
|
||
|
inline PhysClass * get_next_object(PhysClass * tile)
|
||
|
{
|
||
|
return (PhysClass *)(((AABTreeLinkClass *)tile->Get_Cull_Link())->NextObject);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
** DynamicAABTreeCullClass Implementation
|
||
|
*/
|
||
|
DynamicAABTreeCullClass::DynamicAABTreeCullClass(PhysicsSceneClass * pscene) :
|
||
|
PhysAABTreeCullClass(pscene),
|
||
|
MaxObjRadius(DEFUALT_MAXOBJRADIUS),
|
||
|
RenderBox(NULL),
|
||
|
DebugIterator(this)
|
||
|
{
|
||
|
/*
|
||
|
** Modify the root node so that any object can be added into the tree
|
||
|
*/
|
||
|
WWASSERT(RootNode != NULL);
|
||
|
RootNode->Box.Extent.Set(FLT_MAX/4.0f,FLT_MAX/4.0f,FLT_MAX/4.0f);
|
||
|
}
|
||
|
|
||
|
DynamicAABTreeCullClass::~DynamicAABTreeCullClass(void)
|
||
|
{
|
||
|
REF_PTR_RELEASE(RenderBox);
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::Add_Object(PhysClass * obj)
|
||
|
{
|
||
|
/*
|
||
|
** Find the appropriate node to insert this object into.
|
||
|
*/
|
||
|
int node_index = find_insertion_node(obj);
|
||
|
|
||
|
/*
|
||
|
** Add the object to that node
|
||
|
*/
|
||
|
Add_Object_Internal(obj,node_index);
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::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);
|
||
|
|
||
|
/*
|
||
|
** link it back into the tree
|
||
|
*/
|
||
|
int node_index = find_insertion_node(obj);
|
||
|
WWASSERT(node_index < NodeCount);
|
||
|
IndexedNodes[node_index]->Add_Object(obj,false);
|
||
|
}
|
||
|
|
||
|
|
||
|
uint32 DynamicAABTreeCullClass::Get_Dynamic_Object_Vis_ID(const AABoxClass & obj_bounds,int * node_id)
|
||
|
{
|
||
|
bool is_big_obj = ( (obj_bounds.Extent.X > MaxObjRadius) ||
|
||
|
(obj_bounds.Extent.Y > MaxObjRadius) ||
|
||
|
(obj_bounds.Extent.Z > MaxObjRadius) );
|
||
|
|
||
|
/*
|
||
|
** If the user supplied a node_id, we'll start searching from there
|
||
|
*/
|
||
|
AABTreeNodeClass * start_node = RootNode;
|
||
|
|
||
|
#if 0 // Disabling coherency because characters are getting stuck in local minima in the tree...
|
||
|
if ((node_id != NULL) && (*node_id >= 0) && (*node_id < NodeCount)) {
|
||
|
start_node = IndexedNodes[*node_id];
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Step 1: Walk towards the root of the tree until we find a node that still
|
||
|
** contains this object (hopefully the starting node does!)
|
||
|
*/
|
||
|
if (is_big_obj) {
|
||
|
|
||
|
/*
|
||
|
** For "big" objects, we check the entire box against the node's box
|
||
|
*/
|
||
|
while ( (CollisionMath::Overlap_Test(start_node->Box,obj_bounds) != CollisionMath::INSIDE) &&
|
||
|
(start_node != RootNode) )
|
||
|
{
|
||
|
start_node = start_node->Parent;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
/*
|
||
|
** For "normal" objects, we check the center point against the "deflated" boxes. This
|
||
|
** is more correct for the way that VIS was generated.
|
||
|
*/
|
||
|
while ( (deflated_box_contains_point(start_node->Box,obj_bounds.Center) == false) &&
|
||
|
(start_node != RootNode) )
|
||
|
{
|
||
|
start_node = start_node->Parent;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
** Step 2: Now recurse down the tree to find the node this object should derive its visibility
|
||
|
** from
|
||
|
*/
|
||
|
int node_index = 0;
|
||
|
if (is_big_obj) {
|
||
|
find_optimal_node(start_node,obj_bounds,node_index);
|
||
|
} else {
|
||
|
find_optimal_deflated_node(start_node,obj_bounds.Center,node_index);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Step 3: return the results to the user!
|
||
|
*/
|
||
|
if (node_id != NULL) {
|
||
|
*node_id = node_index;
|
||
|
}
|
||
|
return IndexedNodes[node_index]->UserData;
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::Re_Partition
|
||
|
(
|
||
|
DynamicVectorClass<AABoxClass> * seed_boxes,
|
||
|
const Vector3 & grid_min,
|
||
|
const Vector3 & grid_max,
|
||
|
const Vector3 & min_grid_cell_size,
|
||
|
int max_grid_cell_count,
|
||
|
float max_obj_radius
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** Remember the max object radius
|
||
|
*/
|
||
|
MaxObjRadius = max_obj_radius;
|
||
|
|
||
|
/*
|
||
|
** Determine the grid parameters for the world. We want to get the grid cell size
|
||
|
** as close to min_grid_cell_size without exceeding max_grid_cell_count.
|
||
|
*/
|
||
|
AABoxClass grid_bounds;
|
||
|
grid_bounds.Init_Min_Max(grid_min,grid_max);
|
||
|
|
||
|
Vector3 worldsize = grid_max - grid_min;
|
||
|
|
||
|
Vector3i cellcount;
|
||
|
cellcount.I = worldsize.X / min_grid_cell_size.X;
|
||
|
cellcount.J = worldsize.Y / min_grid_cell_size.Y;
|
||
|
cellcount.K = worldsize.Z / min_grid_cell_size.Z;
|
||
|
|
||
|
int total_grid_count = cellcount.I * cellcount.J * cellcount.K;
|
||
|
while (total_grid_count > max_grid_cell_count) {
|
||
|
|
||
|
int i;
|
||
|
Vector3 cellfraction;
|
||
|
for (i=0; i<3; i++) {
|
||
|
cellfraction[i] = (worldsize[i] / cellcount[i]) / min_grid_cell_size[i];
|
||
|
}
|
||
|
|
||
|
int smalldim = 0;
|
||
|
float smallfraction = cellfraction[0];
|
||
|
for (i=1; i<2; i++) {
|
||
|
if (cellfraction[i] < smallfraction) {
|
||
|
smalldim = i;
|
||
|
smallfraction = cellfraction[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cellcount[smalldim]--;
|
||
|
total_grid_count = cellcount.I * cellcount.J * cellcount.K;
|
||
|
}
|
||
|
|
||
|
Vector3 cellsize;
|
||
|
cellsize.X = worldsize.X / cellcount.I;
|
||
|
cellsize.Y = worldsize.Y / cellcount.J;
|
||
|
cellsize.Z = worldsize.Z / cellcount.K;
|
||
|
|
||
|
/*
|
||
|
** Allocate the complete set of seed boxes. This will be *either* a voxelization
|
||
|
** of the level *or* a set of seed boxes passed in (which were presumably generated
|
||
|
** from a pathfind floodfill of the level).
|
||
|
*/
|
||
|
SimpleDynVecClass<AABoxClass> boxes;
|
||
|
Vector3 boxmin,boxmax;
|
||
|
AABoxClass box;
|
||
|
|
||
|
if ((seed_boxes != NULL) && (seed_boxes->Count() > 0)) {
|
||
|
|
||
|
/*
|
||
|
** Add in the seed_boxes
|
||
|
*/
|
||
|
for (int i=0; i<seed_boxes->Count(); i++) {
|
||
|
|
||
|
/*
|
||
|
** Copy the input box, inflate it, add it to the array
|
||
|
*/
|
||
|
box = (*seed_boxes)[i];
|
||
|
float height = 2.0f * box.Extent.Z;
|
||
|
if (height < MIN_CELL_HEIGHT) {
|
||
|
box.Extent.Z += (MIN_CELL_HEIGHT - height) / 2.0f;
|
||
|
box.Center.Z += (MIN_CELL_HEIGHT - height) / 2.0f;
|
||
|
}
|
||
|
box.Extent += Vector3(MaxObjRadius,MaxObjRadius,MaxObjRadius);
|
||
|
boxes.Add(box);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Add in the voxelization boxes
|
||
|
*/
|
||
|
for (int k=0; k<cellcount.K; k++) {
|
||
|
for (int j=0; j<cellcount.J; j++) {
|
||
|
for (int i=0; i<cellcount.I; i++) {
|
||
|
|
||
|
/*
|
||
|
** Compute the bounds of the grid cell
|
||
|
*/
|
||
|
boxmin.X = grid_min.X + i * cellsize.X;
|
||
|
boxmin.Y = grid_min.Y + j * cellsize.Y;
|
||
|
boxmin.Z = grid_min.Z + k * cellsize.Z;
|
||
|
boxmax = boxmin + cellsize;
|
||
|
|
||
|
/*
|
||
|
** Inflate by MaxObjRadius
|
||
|
*/
|
||
|
boxmin -= Vector3(MaxObjRadius,MaxObjRadius,MaxObjRadius);
|
||
|
boxmax += Vector3(MaxObjRadius,MaxObjRadius,MaxObjRadius);
|
||
|
|
||
|
/*
|
||
|
** Add to the array
|
||
|
*/
|
||
|
box.Init_Min_Max(boxmin,boxmax);
|
||
|
boxes.Add(box);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Call the base class re-partition function with our list of leaf boxes
|
||
|
*/
|
||
|
PhysAABTreeCullClass::Re_Partition(grid_bounds,boxes);
|
||
|
|
||
|
/*
|
||
|
** Mark the Visibility system dirty since our boxes all changed
|
||
|
*/
|
||
|
Scene->Reset_Vis();
|
||
|
|
||
|
/*
|
||
|
** Modify the root node so that any object can be added into the tree
|
||
|
*/
|
||
|
RootNode->Box.Extent.Set(FLT_MAX/4.0f,FLT_MAX/4.0f,FLT_MAX/4.0f);
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::Collect_Visible_Objects
|
||
|
(
|
||
|
const FrustumClass & frustum,
|
||
|
VisTableClass * pvs,
|
||
|
RefPhysListClass & visobjlist
|
||
|
)
|
||
|
{
|
||
|
WWASSERT(RootNode != NULL);
|
||
|
|
||
|
if (pvs != NULL) {
|
||
|
|
||
|
/*
|
||
|
** Recursively collect objects directly into the specified list.
|
||
|
*/
|
||
|
VisObjCollectContextClass collection_context(frustum,*pvs,visobjlist);
|
||
|
if (Scene->Is_Vis_Inverted() || !Is_Hierarchical_Vis_Culling_Enabled()) {
|
||
|
collect_visible_objects_no_hvis_recursive(RootNode,collection_context);
|
||
|
} else {
|
||
|
collect_visible_objects_recursive(RootNode,collection_context);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
/*
|
||
|
** Reset the internal collection, call the built-in frustum collection function
|
||
|
*/
|
||
|
Reset_Collection();
|
||
|
Collect_Objects(frustum);
|
||
|
|
||
|
/*
|
||
|
** Loop over each collected object, adding it into the list
|
||
|
*/
|
||
|
PhysClass * obj;
|
||
|
for ( obj = Get_First_Collected_Object();
|
||
|
obj != NULL;
|
||
|
obj = Get_Next_Collected_Object(obj))
|
||
|
{
|
||
|
visobjlist.Add(obj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::Render_Visible_Cells(RenderInfoClass & rinfo,VisTableClass * pvs,DisplayModeType mode)
|
||
|
{
|
||
|
AABTreeNodeClass * start = IndexedNodes[DebugIterator.Get_Current_Node_Index()];
|
||
|
render_visible_cells_recursive(start,rinfo,pvs,mode);
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::Assign_Vis_IDs(void)
|
||
|
{
|
||
|
/*
|
||
|
** Allocate a vis id for each node in the tree.
|
||
|
*/
|
||
|
int nodecount = Partition_Node_Count();
|
||
|
for (int i=0; i<nodecount; i++) {
|
||
|
IndexedNodes[i]->UserData = Scene->Allocate_Vis_Object_ID();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::Evaluate_Non_Occluder_Visibility(VisRenderContextClass & context)
|
||
|
{
|
||
|
WWASSERT(context.VisRasterizer != NULL);
|
||
|
|
||
|
context.VisRasterizer->Set_Render_Mode(IDBufferClass::NON_OCCLUDER_MODE);
|
||
|
evaluate_non_occluder_visibility_recursive(RootNode,context);
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::Save_Static_Data(ChunkSaveClass & csave)
|
||
|
{
|
||
|
csave.Begin_Chunk(DYNAMICAABTREE_CHUNK_AABTREE_CLASS_DATA);
|
||
|
TypedAABTreeCullSystemClass<PhysClass>::Save(csave);
|
||
|
csave.End_Chunk();
|
||
|
|
||
|
csave.Begin_Chunk(DYNAMICAABTREE_CHUNK_VARIABLES);
|
||
|
WRITE_MICRO_CHUNK(csave,DYNAMICAABTREE_VARIABLE_MAXOBJRADIUS,MaxObjRadius);
|
||
|
csave.End_Chunk();
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::Load_Static_Data(ChunkLoadClass & cload)
|
||
|
{
|
||
|
WWMEMLOG(MEM_CULLINGDATA);
|
||
|
while (cload.Open_Chunk()) {
|
||
|
switch(cload.Cur_Chunk_ID()) {
|
||
|
|
||
|
case DYNAMICAABTREE_CHUNK_AABTREE_CLASS_DATA:
|
||
|
TypedAABTreeCullSystemClass<PhysClass>::Load(cload);
|
||
|
break;
|
||
|
|
||
|
case DYNAMICAABTREE_CHUNK_VARIABLES:
|
||
|
while (cload.Open_Micro_Chunk()) {
|
||
|
switch(cload.Cur_Micro_Chunk_ID()) {
|
||
|
READ_MICRO_CHUNK(cload,DYNAMICAABTREE_VARIABLE_MAXOBJRADIUS,MaxObjRadius);
|
||
|
}
|
||
|
cload.Close_Micro_Chunk();
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
|
||
|
break;
|
||
|
}
|
||
|
cload.Close_Chunk();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::evaluate_non_occluder_visibility_recursive
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
VisRenderContextClass & context
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** Try to avoid this processing if possible. If this node and all of its
|
||
|
** children are already considered potentially visible, return
|
||
|
*/
|
||
|
if (subtree_is_visible(node,context)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** now, determine whether this bounding box is visible. Note that
|
||
|
** we consider it visible if the view-point is inside the box, otherwise
|
||
|
** we have to render the box and scan for it.
|
||
|
** Note also that I use the "un-inflated" box for this.
|
||
|
*/
|
||
|
AABoxClass nodebox = node->Box;
|
||
|
nodebox.Extent -= Vector3(MaxObjRadius,MaxObjRadius,MaxObjRadius);
|
||
|
|
||
|
bool box_is_visible = false;
|
||
|
|
||
|
int vis_id = node->UserData;
|
||
|
|
||
|
if ( (nodebox.Contains(context.Camera.Get_Position())) ||
|
||
|
(context.VisTable.Get_Bit(vis_id) != 0) ||
|
||
|
(context.Is_Vis_Quick_And_Dirty()))
|
||
|
{
|
||
|
|
||
|
box_is_visible = true;
|
||
|
context.VisTable.Set_Bit(vis_id,true);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
if (!context.Camera.Cull_Box(node->Box)) {
|
||
|
|
||
|
context.Set_Vis_ID(vis_id); // use the node's vis-id
|
||
|
context.VisRasterizer->Reset_Pixel_Counter();
|
||
|
|
||
|
AABoxRenderObjClass * rbox = get_render_box();
|
||
|
rbox->Set_Local_Center_Extent(nodebox.Center,nodebox.Extent);
|
||
|
rbox->Special_Render(context); // render the bounding volume
|
||
|
REF_PTR_RELEASE(rbox);
|
||
|
|
||
|
if (context.VisRasterizer->Get_Pixel_Counter() > 0) {
|
||
|
context.VisTable.Set_Bit(vis_id,true);
|
||
|
box_is_visible = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Recursively test the child nodes.
|
||
|
*/
|
||
|
if (box_is_visible) {
|
||
|
|
||
|
if (node->Front != NULL) {
|
||
|
evaluate_non_occluder_visibility_recursive(node->Front,context);
|
||
|
}
|
||
|
|
||
|
if (node->Back != NULL) {
|
||
|
evaluate_non_occluder_visibility_recursive(node->Back,context);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool DynamicAABTreeCullClass::subtree_is_visible
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
VisRenderContextClass & context
|
||
|
)
|
||
|
{
|
||
|
if (context.VisTable.Get_Bit(node->UserData) == false) return false;
|
||
|
if (node->Front && !subtree_is_visible(node->Front,context)) return false;
|
||
|
if (node->Back && !subtree_is_visible(node->Back,context)) return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::set_tree_visibility
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
VisRenderContextClass & context,
|
||
|
bool onoff
|
||
|
)
|
||
|
{
|
||
|
if (node == NULL) return;
|
||
|
context.VisTable.Set_Bit(node->UserData,onoff);
|
||
|
set_tree_visibility(node->Front,context,onoff);
|
||
|
set_tree_visibility(node->Back,context,onoff);
|
||
|
}
|
||
|
|
||
|
AABoxRenderObjClass * DynamicAABTreeCullClass::get_render_box(void)
|
||
|
{
|
||
|
if (RenderBox == NULL) {
|
||
|
RenderBox = NEW_REF(AABoxRenderObjClass,());
|
||
|
}
|
||
|
|
||
|
WWASSERT(RenderBox != NULL);
|
||
|
RenderBox->Add_Ref();
|
||
|
return RenderBox;
|
||
|
}
|
||
|
|
||
|
|
||
|
int DynamicAABTreeCullClass::find_insertion_node(CullableClass * obj)
|
||
|
{
|
||
|
int node_index = 0;
|
||
|
const AABoxClass & cullbox = obj->Get_Cull_Box();
|
||
|
if ( (cullbox.Extent.X > MaxObjRadius) ||
|
||
|
(cullbox.Extent.Y > MaxObjRadius) ||
|
||
|
(cullbox.Extent.Z > MaxObjRadius) )
|
||
|
{
|
||
|
find_optimal_node(RootNode,cullbox,node_index);
|
||
|
} else {
|
||
|
find_optimal_deflated_node(RootNode,cullbox.Center,node_index);
|
||
|
}
|
||
|
return node_index;
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::find_optimal_node
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
const AABoxClass & box,
|
||
|
int & node_index
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** We want to find the smallest node in the tree that contains
|
||
|
** the given bounding box.
|
||
|
*/
|
||
|
int input_node_index = node_index;
|
||
|
|
||
|
if (node->Front) {
|
||
|
if (CollisionMath::Overlap_Test(node->Front->Box,box) == CollisionMath::INSIDE) {
|
||
|
find_optimal_node(node->Front,box,node_index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (node->Back) {
|
||
|
if (CollisionMath::Overlap_Test(node->Back->Box,box) == CollisionMath::INSIDE) {
|
||
|
find_optimal_node(node->Back,box,node_index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** If we have no children or none of our children contained the object,
|
||
|
** then we check if we contain the object.
|
||
|
*/
|
||
|
if (node_index == input_node_index) {
|
||
|
if (node->Box.Extent.Quick_Length() < IndexedNodes[node_index]->Box.Extent.Quick_Length()) {
|
||
|
node_index = node->Index;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::find_optimal_deflated_node
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
const Vector3 & center,
|
||
|
int & node_index
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** We want to find the smallest "de-flated" leaf node that contains the
|
||
|
** given center point.
|
||
|
*/
|
||
|
int input_node_index = node_index;
|
||
|
|
||
|
if (node->Front) {
|
||
|
if (deflated_box_contains_point(node->Front->Box,center)) {
|
||
|
find_optimal_deflated_node(node->Front,center,node_index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (node->Back) {
|
||
|
if (deflated_box_contains_point(node->Back->Box,center)) {
|
||
|
find_optimal_deflated_node(node->Back,center,node_index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** If we have no children or none of our children contained the object,
|
||
|
** then we check if we contain the object.
|
||
|
*/
|
||
|
if (node_index == input_node_index) {
|
||
|
if (node->Box.Extent.Quick_Length() < IndexedNodes[node_index]->Box.Extent.Quick_Length()) {
|
||
|
node_index = node->Index;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::collect_visible_objects_recursive
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
VisObjCollectContextClass & context
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** If this node is not visible, stop
|
||
|
** In Debug and Profile builds, we also recurse if vis was inverted (we can no-longer
|
||
|
** early-exit the entire sub-tree in this case)
|
||
|
*/
|
||
|
if (context.PVS.Get_Bit(node->UserData) == 0) {
|
||
|
NODE_REJECTED();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Cull the bounding volume of this node against the frustum.
|
||
|
** If it is culled, stop descending the tree.
|
||
|
*/
|
||
|
CollisionMath::OverlapType overlap;
|
||
|
overlap = CollisionMath::Overlap_Test(context.Frustum,node->Box,context.PlanesPassed);
|
||
|
if (overlap == CollisionMath::OUTSIDE) {
|
||
|
NODE_REJECTED();
|
||
|
return;
|
||
|
}
|
||
|
// else if (overlap == CollisionMath::INSIDE) {
|
||
|
// collect_visible_objects_recursive(node,context);
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
NODE_ACCEPTED();
|
||
|
|
||
|
/*
|
||
|
** Test any objects in this node
|
||
|
*/
|
||
|
if (node->Object) {
|
||
|
|
||
|
if (context.PVS.Get_Bit(node->UserData) != 0) {
|
||
|
|
||
|
PhysClass * obj = get_first_object(node);
|
||
|
while (obj) {
|
||
|
if (CollisionMath::Overlap_Test(context.Frustum,obj->Get_Cull_Box(),context.PlanesPassed) != CollisionMath::OUTSIDE) {
|
||
|
context.VisObjList.Add(obj);
|
||
|
}
|
||
|
obj = get_next_object(obj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Recurse into the children
|
||
|
*/
|
||
|
if (node->Back) {
|
||
|
collect_visible_objects_recursive(node->Back,context);
|
||
|
}
|
||
|
if (node->Front) {
|
||
|
collect_visible_objects_recursive(node->Front,context);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::collect_visible_objects_no_hvis_recursive
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
VisObjCollectContextClass & context
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** Cull the bounding volume of this node against the frustum.
|
||
|
** If it is culled, stop descending the tree.
|
||
|
*/
|
||
|
CollisionMath::OverlapType overlap;
|
||
|
overlap = CollisionMath::Overlap_Test(context.Frustum,node->Box,context.PlanesPassed);
|
||
|
if (overlap == CollisionMath::OUTSIDE) {
|
||
|
NODE_REJECTED();
|
||
|
return;
|
||
|
}
|
||
|
// else if (overlap == CollisionMath::INSIDE) {
|
||
|
// collect_visible_objects_recursive(node,context);
|
||
|
// return;
|
||
|
// }
|
||
|
|
||
|
NODE_ACCEPTED();
|
||
|
|
||
|
/*
|
||
|
** Test any objects in this node
|
||
|
*/
|
||
|
if (node->Object) {
|
||
|
|
||
|
if (context.PVS.Get_Bit(node->UserData) != 0) {
|
||
|
|
||
|
PhysClass * obj = get_first_object(node);
|
||
|
while (obj) {
|
||
|
if (CollisionMath::Overlap_Test(context.Frustum,obj->Get_Cull_Box(),context.PlanesPassed) != CollisionMath::OUTSIDE) {
|
||
|
context.VisObjList.Add(obj);
|
||
|
}
|
||
|
obj = get_next_object(obj);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Recurse into the children
|
||
|
*/
|
||
|
if (node->Back) {
|
||
|
collect_visible_objects_no_hvis_recursive(node->Back,context);
|
||
|
}
|
||
|
if (node->Front) {
|
||
|
collect_visible_objects_no_hvis_recursive(node->Front,context);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::render_visible_cells_recursive
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
RenderInfoClass & rinfo,
|
||
|
VisTableClass * pvs,
|
||
|
DisplayModeType mode
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** Cull the bounding volume of this node against the frustum.
|
||
|
** If it is culled or not visible, stop descending the tree.
|
||
|
*/
|
||
|
int visid = node->UserData;
|
||
|
if ( (CollisionMath::Overlap_Test(rinfo.Camera.Get_Frustum(),node->Box) == CollisionMath::OUTSIDE) ||
|
||
|
(!Scene->Is_Vis_Inverted() && (pvs != NULL) && (pvs->Get_Bit(visid) == 0)) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** If we're in a leaf or node with one child that is visible, draw a translucent box
|
||
|
*/
|
||
|
int child_count = 0;
|
||
|
if (node->Front != NULL) child_count++;
|
||
|
if (node->Back != NULL) child_count++;
|
||
|
|
||
|
if ((child_count <= 1) || (mode == DISPLAY_OCCUPIED)) {
|
||
|
|
||
|
if ((pvs == NULL) || (pvs->Get_Bit(visid))) {
|
||
|
|
||
|
if ((mode != DISPLAY_OCCUPIED) || (node->Object != NULL)) {
|
||
|
|
||
|
// force boxes to get rendered (yuck)
|
||
|
int oldmask = WW3D::Get_Collision_Box_Display_Mask();
|
||
|
WW3D::Set_Collision_Box_Display_Mask(oldmask | 0x01);
|
||
|
|
||
|
AABoxRenderObjClass * rbox = get_render_box();
|
||
|
|
||
|
if (mode == DISPLAY_CENTERS) {
|
||
|
rbox->Set_Local_Center_Extent(node->Box.Center,Vector3(0.3f,0.3f,0.3f));
|
||
|
} else {
|
||
|
rbox->Set_Local_Center_Extent(node->Box.Center,node->Box.Extent - Vector3(MaxObjRadius,MaxObjRadius,MaxObjRadius)); // set the box dimensions
|
||
|
}
|
||
|
|
||
|
float red,green,blue;
|
||
|
if (child_count == 0) {
|
||
|
green = 1.0f; //((visid % 13468) & 0xFF) / 255.0f;
|
||
|
blue = green / 2.0f;
|
||
|
red = green / 3.0f;
|
||
|
} else if (child_count == 1) {
|
||
|
blue = 1.0f; //((visid % 13468) & 0xFF) / 255.0f;
|
||
|
red = blue / 2.0f;
|
||
|
green = blue / 3.0f;
|
||
|
} else {
|
||
|
red = 1.0f; //((visid % 13468) & 0xFF) / 255.0f;
|
||
|
blue = red / 2.0f;
|
||
|
green = red / 3.0f;
|
||
|
}
|
||
|
rbox->Set_Color(Vector3(red,green,blue));
|
||
|
|
||
|
rbox->Render(rinfo); // render the bounding volume
|
||
|
REF_PTR_RELEASE(rbox);
|
||
|
|
||
|
// restore the old box mask
|
||
|
WW3D::Set_Collision_Box_Display_Mask(oldmask);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** recurse into children
|
||
|
*/
|
||
|
if (node->Front) {
|
||
|
render_visible_cells_recursive(node->Front,rinfo,pvs,mode);
|
||
|
}
|
||
|
if (node->Back) {
|
||
|
render_visible_cells_recursive(node->Back,rinfo,pvs,mode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::Prune_Redundant_Leaf_Nodes(VisOptimizationContextClass & context)
|
||
|
{
|
||
|
prune_redundant_leaf_nodes_recursive(RootNode,context);
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::prune_redundant_leaf_nodes_recursive
|
||
|
(
|
||
|
AABTreeNodeClass * node,
|
||
|
VisOptimizationContextClass & context
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** Recurse down to the leaves first
|
||
|
*/
|
||
|
if (node->Front) {
|
||
|
prune_redundant_leaf_nodes_recursive(node->Front,context);
|
||
|
}
|
||
|
if (node->Back) {
|
||
|
prune_redundant_leaf_nodes_recursive(node->Back,context);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** Try to prune our children. They will only be removed if their
|
||
|
** vis matches ours and they are a leaf node.
|
||
|
*/
|
||
|
prune_child(node,node->Front,context);
|
||
|
prune_child(node,node->Back,context);
|
||
|
|
||
|
context.Get_Progress_Object().Increment_Completed_Operations();
|
||
|
}
|
||
|
|
||
|
void DynamicAABTreeCullClass::prune_child
|
||
|
(
|
||
|
AABTreeNodeClass * parent,
|
||
|
AABTreeNodeClass * child,
|
||
|
VisOptimizationContextClass & context
|
||
|
)
|
||
|
{
|
||
|
/*
|
||
|
** If the child node is NULL just return
|
||
|
*/
|
||
|
if ((parent == NULL) || (child == NULL)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** If the child is a leaf node and its VIS matches the parent vis, delete the
|
||
|
** child, fixup the vis tables and ID's, and re-insert the child's objects into the tree.
|
||
|
*/
|
||
|
if ((child->Front == NULL) && (child->Back == NULL)) {
|
||
|
|
||
|
int parent_object_id = parent->UserData;
|
||
|
int child_object_id = child->UserData;
|
||
|
|
||
|
float match_fraction = context.Compute_Object_Table_Match_Fraction(parent_object_id,child_object_id);
|
||
|
WWDEBUG_SAY(("parent: %d, child: %d, match fraction: %f\n",parent_object_id,child_object_id,match_fraction));
|
||
|
if (match_fraction >= context.Get_Dyn_Cell_Prune_Match_Fraction()) {
|
||
|
|
||
|
/*
|
||
|
** unlink the node from its parent so that subsequent calls back into the tree will not see it.
|
||
|
*/
|
||
|
if (parent->Front == child) {
|
||
|
parent->Front = NULL;
|
||
|
}
|
||
|
if (parent->Back == child) {
|
||
|
parent->Back = NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** re-index the tree to remove the final dangling pointer.
|
||
|
*/
|
||
|
Re_Index_Nodes();
|
||
|
|
||
|
/*
|
||
|
** re-insert all objects into the tree
|
||
|
** zero the bounding box of the node to remove any chance of the objects from being left
|
||
|
** in the child node!
|
||
|
*/
|
||
|
child->Box.Extent.Set(0,0,0);
|
||
|
PhysClass * obj = get_first_object(child);
|
||
|
while (obj) {
|
||
|
Update_Culling(obj);
|
||
|
obj = get_first_object(child);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
** delete the node
|
||
|
*/
|
||
|
delete child;
|
||
|
|
||
|
/*
|
||
|
** Tell the optimizer to combine the tables. This is going to call back into the tree
|
||
|
** so make sure it is in a consistent state...
|
||
|
*/
|
||
|
context.Combine_Object_Tables(parent_object_id,child_object_id);
|
||
|
|
||
|
context.Get_Progress_Object().Increment_Dynamic_Cells_Removed();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void DynamicAABTreeCullClass::Merge_Vis_Object_IDs(uint32 id0,uint32 id1)
|
||
|
{
|
||
|
/*
|
||
|
** Each node has a vis object id.
|
||
|
** Whenever we encounter one of these with id1, set it to id0.
|
||
|
** Whenever we encounter one with id > id1, subtract one from it.
|
||
|
*/
|
||
|
for (int i=0; i<NodeCount; i++) {
|
||
|
|
||
|
AABTreeNodeClass * node = IndexedNodes[i];
|
||
|
if (node->UserData == id1) {
|
||
|
node->UserData = id0;
|
||
|
} else if (node->UserData > id1) {
|
||
|
node->UserData--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|