/* ** Command & Conquer Generals Zero Hour(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 : WW3D * * * * $Archive:: /Commando/Code/Tools/max2w3d/aabtreebuilder.cpp $* * * * Original Author:: Greg Hjelstrom * * * * $Author:: Greg_h $* * * * $Modtime:: 5/24/00 8:41a $* * * * $Revision:: 4 $* * * *---------------------------------------------------------------------------------------------* * Functions: * * AABTreeBuilderClass::AABTreeBuilderClass -- Constructor * * AABTreeBuilderClass::~AABTreeBuilderClass -- Destructor * * AABTreeBuilderClass::Reset -- reset the builder, delete all arrays * * AABTreeBuilderClass::Build_AABTree -- Build an AABTree for the given mesh. * * AABTreeBuilderClass::Build_Tree -- recursivly builds the culling tree * * AABTreeBuilderClass::Select_Splitting_Plane -- select a partition for the given polys * * AABTreeBuilderClass::Compute_Plane_Score -- evaluate the suitability of a partition plane * * AABTreeBuilderClass::Which_Side -- which side of a plane is the given poly * * AABTreeBuilderClass::Split_Polys -- partition the polys with a plane * * AABTreeBuilderClass::Compute_Bounding_Box -- compute bounding boxes for the cull nodes * * AABTreeBuilderClass::Assign_Index -- assign an array index to each node * * AABTreeBuilderClass::Node_Count -- Count the nodes in the tree * * AABTreeBuilderClass::Poly_Count -- returns number of polys * * AABTreeBuilderClass::Node_Count_Recursive -- internal implementation of Node_Count * * AABTreeBuilderClass::Submit_Tree -- install nodes into an AABTreeClass * * AABTreeBuilderClass::Submit_Tree_Recursive -- internal implementation of Submit_Tree * * AABTreeBuilderClass::Update_Min -- ensure given vector is < min of the poly * * AABTreeBuilderClass::Update_Max -- ensure given vector is > max of poly * * AABTreeBuilderClass::Update_Min_Max -- ensure given vector is in min max of poly * * AABTreeBuilderClass::Export -- Saves this AABTree into a W3D chunk * * AABTreeBuilderClass::Build_W3D_AABTree_Recursive -- Build array of indices and W3dMeshAAB * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #include "aabtreebuilder.h" #include "chunkio.h" #include "w3d_file.h" #include #include #include #define WWASSERT assert // can't use WWASSERT because we use this module in the MAX plugin... const float COINCIDENCE_EPSILON = 0.001f; /*********************************************************************************************** * AABTreeBuilderClass::AABTreeBuilderClass -- Constructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * *=============================================================================================*/ AABTreeBuilderClass::AABTreeBuilderClass(void) : Root(NULL), CurPolyIndex(0), PolyCount(0), Polys(NULL), VertCount(0), Verts(NULL) { } /*********************************************************************************************** * AABTreeBuilderClass::~AABTreeBuilderClass -- Destructor * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/19/2000 gth : Created. * *=============================================================================================*/ AABTreeBuilderClass::~AABTreeBuilderClass(void) { Reset(); } /*********************************************************************************************** * AABTreeBuilderClass::Reset -- reset the builder, delete all arrays * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/19/2000 gth : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Reset(void) { if (Root) { delete Root; Root = NULL; } if (Verts != NULL) { delete[] Verts; Verts = NULL; } if (Polys != NULL) { delete[] Polys; Polys = NULL; } } /*********************************************************************************************** * AABTreeBuilderClass::Build_AABTree -- Build an AABTree for the given mesh. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Build_AABTree(int polycount,Vector3i * polys,int vertcount,Vector3 * verts) { WWASSERT(polycount > 0); WWASSERT(vertcount > 0); WWASSERT(polys != NULL); WWASSERT(verts != NULL); /* ** If we already have allocated data, release it */ Reset(); /* ** Copy the mesh data */ VertCount = vertcount; PolyCount = polycount; Verts = new Vector3[VertCount]; Polys = new Vector3i[PolyCount]; for (int vi=0; viPolyCount = polycount; node->PolyIndices = polyindices; return; } /* ** Try to find a suitable partitioning plane. */ SplitChoiceStruct sc; sc = Select_Splitting_Plane(polycount,polyindices); /* ** If the algorithm could not separate any polys, just install the polys ** in this node and terminate. TODO: explore how this happens. */ if (sc.FrontCount + sc.BackCount != polycount) { node->PolyCount = polycount; node->PolyIndices = polyindices; return; } /* ** Decide whether to actually partition this node. If the partitioning ** will not gain us anything, just install the polys in this node and terminate ** the tree. */ #if 0 if (sc.Cost == MAX_COST) { node->PolyCount = polycount; node->PolyIndices = polyindices; return; } #endif /* ** Ok, split the polys */ SplitArraysStruct arrays; Split_Polys(polycount,polyindices,sc,&arrays); /* ** Free the memory in use by the input tile-list */ delete[] polyindices; /* ** Build a front tree if necessary. Remember that the Build function ** deletes the poly array. */ if (arrays.FrontCount) { WWASSERT(arrays.FrontPolys != NULL); node->Front = new CullNodeStruct; Build_Tree(node->Front,arrays.FrontCount,arrays.FrontPolys); arrays.FrontPolys = NULL; } /* ** Build a back tree if necessary. Remember that the build function ** deletes the tile array. */ if (arrays.BackCount) { WWASSERT(arrays.BackPolys != NULL); node->Back = new CullNodeStruct; Build_Tree(node->Back,arrays.BackCount,arrays.BackPolys); arrays.BackPolys = NULL; } } /*********************************************************************************************** * AABTreeBuilderClass::Select_Splitting_Plane -- select a partition for the given polys * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ AABTreeBuilderClass::SplitChoiceStruct AABTreeBuilderClass::Select_Splitting_Plane(int polycount,int * polyindices) { WWASSERT(polyindices != NULL); const int NUM_TRYS = 50; SplitChoiceStruct best_plane_stats; SplitChoiceStruct considered_plane_stats; /* ** Try putting axis-aligned planes through some random vertices */ for (int trys = 0; trys < MIN(NUM_TRYS,polycount); trys++) { AAPlaneClass plane; /* ** Select a random poly and vertex index; */ int poly_index = polyindices[rand() % polycount]; int vert_index = rand() % 3; const Vector3i * polyverts = Polys + poly_index; const Vector3 * vert = Verts + (*polyverts)[vert_index]; /* ** Select a random plane */ switch(rand() % 3) { case 0: plane.Set(AAPlaneClass::XNORMAL,vert->X); break; case 1: plane.Set(AAPlaneClass::YNORMAL,vert->Y); break; case 2: plane.Set(AAPlaneClass::ZNORMAL,vert->Z); break; }; /* ** Get the score for this plane */ considered_plane_stats = Compute_Plane_Score(polycount,polyindices,plane); if (considered_plane_stats.Cost < best_plane_stats.Cost) { best_plane_stats = considered_plane_stats; } } return best_plane_stats; } /*********************************************************************************************** * AABTreeBuilderClass::Compute_Plane_Score -- evaluate the suitability of a partition plane * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ AABTreeBuilderClass::SplitChoiceStruct AABTreeBuilderClass::Compute_Plane_Score(int polycount,int * polyindices,const AAPlaneClass & plane) { /* ** The score of a splitting plane is based on the following factors: ** - the volumes of the resulting two children volumes, ** - the number of polys in each child volume */ SplitChoiceStruct sc; sc.Plane = plane; for (int i=0; i COINCIDENCE_EPSILON) { mask |= POS; } if (delta < -COINCIDENCE_EPSILON) { mask |= NEG; } mask |= ON; } /* ** Now evaluate the status of all of the verts to determine whether the ** triangle is in front, behind, on or overlapping the plane */ /* ** If all verts were ON the plane, the triangle is ON the plane */ if (mask == ON) { return ON; } /* ** If all verts were POS or ON, the triangle is POS (IN_FRONT) */ if ((mask & ~(POS | ON)) == 0) { return POS; } /* ** If all verts were NEG or ON, the triangle is NEG (BEHIND) */ if ((mask & ~(NEG | ON)) == 0) { return NEG; } /* ** Otherwise, the triangle spans the plane */ return BOTH; } /*********************************************************************************************** * AABTreeBuilderClass::Split_Polys -- partition the polys with a plane * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Split_Polys ( int polycount, int * polyindices, const SplitChoiceStruct & sc, SplitArraysStruct * arrays ) { /* ** Note that this routine arrays of polygons. The caller is then responsible for keeping ** track of the memory this routine allocates. */ if (sc.FrontCount > 0) { arrays->FrontPolys = new int[sc.FrontCount]; } if (sc.BackCount > 0) { arrays->BackPolys = new int[sc.BackCount]; } arrays->FrontCount = 0; arrays->BackCount = 0; for (int i=0; iFrontPolys[arrays->FrontCount++] = polyindices[i]; break; case BACK: arrays->BackPolys[arrays->BackCount++] = polyindices[i]; break; } } /* ** when we are all done, the counts should match. */ WWASSERT(arrays->FrontCount == sc.FrontCount); WWASSERT(arrays->BackCount == sc.BackCount); } /*********************************************************************************************** * AABTreeBuilderClass::Compute_Bounding_Box -- compute bounding boxes for the cull nodes * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Compute_Bounding_Box(CullNodeStruct * node) { /* ** compute bounding volumes of the children */ if (node->Front) { Compute_Bounding_Box(node->Front); } if (node->Back) { Compute_Bounding_Box(node->Back); } /* ** compute bounding volume for the polys in this node */ node->Min.Set(100000.0f,100000.0f,100000.0f); node->Max.Set(-100000.0f,-100000.0f,-100000.0f); for (int poly_index = 0; poly_index < node->PolyCount; poly_index++) { Update_Min_Max(node->PolyIndices[poly_index],node->Min,node->Max ); } /* ** bound the polys in the front child node */ if (node->Front) { if (node->Front->Min.X < node->Min.X) node->Min.X = node->Front->Min.X; if (node->Front->Max.X > node->Max.X) node->Max.X = node->Front->Max.X; if (node->Front->Min.Y < node->Min.Y) node->Min.Y = node->Front->Min.Y; if (node->Front->Max.Y > node->Max.Y) node->Max.Y = node->Front->Max.Y; if (node->Front->Min.Z < node->Min.Z) node->Min.Z = node->Front->Min.Z; if (node->Front->Max.Z > node->Max.Z) node->Max.Z = node->Front->Max.Z; } /* ** bound the polys in the back child node */ if (node->Back) { if (node->Back->Min.X < node->Min.X) node->Min.X = node->Back->Min.X; if (node->Back->Max.X > node->Max.X) node->Max.X = node->Back->Max.X; if (node->Back->Min.Y < node->Min.Y) node->Min.Y = node->Back->Min.Y; if (node->Back->Max.Y > node->Max.Y) node->Max.Y = node->Back->Max.Y; if (node->Back->Min.Z < node->Min.Z) node->Min.Z = node->Back->Min.Z; if (node->Back->Max.Z > node->Max.Z) node->Max.Z = node->Back->Max.Z; } WWASSERT(node->Min.X != 100000.0f); WWASSERT(node->Min.Y != 100000.0f); WWASSERT(node->Min.Z != 100000.0f); WWASSERT(node->Max.X != -100000.0f); WWASSERT(node->Max.Y != -100000.0f); WWASSERT(node->Max.Z != -100000.0f); } /*********************************************************************************************** * AABTreeBuilderClass::Assign_Index -- assign an array index to each node * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ int AABTreeBuilderClass::Assign_Index(CullNodeStruct * node,int index) { /* ** This function is used to assign a sequential index to ** each node in the tree. The AABTree stores its nodes in ** an array so this index is used to determine which slot ** in the array to put each node into. */ WWASSERT(node); node->Index = index; index++; if (node->Front) { index = Assign_Index(node->Front,index); } if (node->Back) { index = Assign_Index(node->Back,index); } return index; } /*********************************************************************************************** * AABTreeBuilderClass::Node_Count -- Count the nodes in the tree * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ int AABTreeBuilderClass::Node_Count(void) { if (Root) { return Node_Count_Recursive(Root,0); } else { return 0; } } /*********************************************************************************************** * AABTreeBuilderClass::Poly_Count -- returns number of polys * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 10/23/98 GTH : Created. * *=============================================================================================*/ int AABTreeBuilderClass::Poly_Count(void) { return PolyCount; } /*********************************************************************************************** * AABTreeBuilderClass::Node_Count_Recursive -- internal implementation of Node_Count * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/19/98 GTH : Created. * *=============================================================================================*/ int AABTreeBuilderClass::Node_Count_Recursive(CullNodeStruct * node,int curcount) { curcount++; if (node->Front) { curcount = Node_Count_Recursive(node->Front,curcount); } if (node->Back) { curcount = Node_Count_Recursive(node->Back,curcount); } return curcount; } /*********************************************************************************************** * AABTreeBuilderClass::Update_Min -- ensure given vector is < min of the poly * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/22/98 GTH : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Update_Min(int poly_index,Vector3 & min) { for (int vert_index = 0; vert_index < 3; vert_index++) { const Vector3i * polyverts = Polys + poly_index; const Vector3 * point = Verts + (*polyverts)[vert_index]; if (point->X < min.X) min.X = point->X; if (point->Y < min.Y) min.Y = point->Y; if (point->Z < min.Z) min.Z = point->Z; } } /*********************************************************************************************** * AABTreeBuilderClass::Update_Max -- ensure given vector is > max of poly * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 6/22/98 GTH : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Update_Max(int poly_index,Vector3 & max) { for (int vert_index = 0; vert_index < 3; vert_index++) { const Vector3i * polyverts = Polys + poly_index; const Vector3 * point = Verts + (*polyverts)[vert_index]; if (point->X > max.X) max.X = point->X; if (point->Y > max.Y) max.Y = point->Y; if (point->Z > max.Z) max.Z = point->Z; } } /*********************************************************************************************** * AABTreeBuilderClass::Update_Min_Max -- ensure given vector is in min max of poly * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 9/24/98 BMG : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Update_Min_Max(int poly_index, Vector3 & min, Vector3 & max) { for (int vert_index = 0; vert_index < 3; vert_index++) { const Vector3i * polyverts = Polys + poly_index; const Vector3 * point = Verts + (*polyverts)[vert_index]; if (point->X < min.X) min.X = point->X; if (point->Y < min.Y) min.Y = point->Y; if (point->Z < min.Z) min.Z = point->Z; if (point->X > max.X) max.X = point->X; if (point->Y > max.Y) max.Y = point->Y; if (point->Z > max.Z) max.Z = point->Z; } } /*********************************************************************************************** * AABTreeBuilderClass::Export -- Saves this AABTree into a W3D chunk * * * * This function will export the AABTree into a W3D chunk so that it can be loaded by its * * sister class "AABTreeClass" in the WW3D library. * * * * INPUT: * * * * OUTPUT: * * * * WARNINGS: * * * * HISTORY: * * 5/22/2000 gth : Created. * *=============================================================================================*/ void AABTreeBuilderClass::Export(ChunkSaveClass & csave) { csave.Begin_Chunk(W3D_CHUNK_AABTREE); /* ** Pack the tree into an array of W3dMeshAABTreeNode's and polygon indices */ W3dMeshAABTreeNode * nodes = new W3dMeshAABTreeNode[Node_Count()]; uint32 * poly_indices = new uint32[Poly_Count()]; int cur_node = 0; int cur_poly = 0; Build_W3D_AABTree_Recursive(Root,nodes,poly_indices,cur_node,cur_poly); /* ** Write out the header */ csave.Begin_Chunk(W3D_CHUNK_AABTREE_HEADER); W3dMeshAABTreeHeader header; memset(&header,0,sizeof(header)); header.NodeCount = Node_Count(); header.PolyCount = Poly_Count(); csave.Write(&header,sizeof(header)); csave.End_Chunk(); /* ** Write out the array of polygon indices */ csave.Begin_Chunk(W3D_CHUNK_AABTREE_POLYINDICES); csave.Write(poly_indices,Poly_Count() * sizeof(uint32)); csave.End_Chunk(); /* ** Write out the array of nodes */ csave.Begin_Chunk(W3D_CHUNK_AABTREE_NODES); for (int ni=0; niIndex]); newnode->Min.X = node->Min.X; newnode->Min.Y = node->Min.Y; newnode->Min.Z = node->Min.Z; newnode->Max.X = node->Max.X; newnode->Max.Y = node->Max.Y; newnode->Max.Z = node->Max.Z; /* ** If this is a non-leaf node, set up the child indices, otherwise set up the polygon indices */ if (node->Front != NULL) { WWASSERT(node->Back != NULL); // if we have one child, we better have both! newnode->FrontOrPoly0 = node->Front->Index; newnode->BackOrPolyCount = node->Back->Index; } else { newnode->FrontOrPoly0 = cur_poly | 0x80000000; newnode->BackOrPolyCount = node->PolyCount; } /* ** Copy the polygon indices for this node into our array */ for (int pcounter = 0; pcounter < node->PolyCount; pcounter++) { poly_indices[cur_poly++] = node->PolyIndices[pcounter]; } /* ** Install the children */ if (node->Front) { Build_W3D_AABTree_Recursive(node->Front,w3d_nodes,poly_indices,cur_node,cur_poly); } if (node->Back) { Build_W3D_AABTree_Recursive(node->Back,w3d_nodes,poly_indices,cur_node,cur_poly); } }