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/wwphys/bpt.cpp

2439 lines
69 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/bpt.cpp $*
* *
* Author:: Greg_h *
* *
* $Modtime:: 9/09/99 11:29a $*
* *
* $Revision:: 15 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* BptClass::BptClass -- constructor *
* BptClass::~BptClass -- destructor *
* BptClass::Free -- releases all assets in use *
* BptClass::Build -- build a bpt from the given mesh (generates a new mesh) *
* BptClass::Load -- Initialize this Bpt object from contents of a W3D file *
* BptClass::Save -- Save this Bpt object into a W3D file *
* BptClass::Render -- submit this object for rendering *
* BptClass::Cast_Ray -- Intersect a ray with this Bpt Object *
* BptClass::Cast_AABox -- Intersect a swept axis-aligned box. *
* BptClass::Cast_OBBox -- Intersect a swept oriented box with this bpt object *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#if 0 // OBSOLETE!!!
#ifdef PORT140
#include "bpt.h"
#include "vector.h"
#include "uarray.h"
#include "mesh.h"
#include "material.h"
#include "chunkio.h"
#include "vector3.h"
#include "tri.h"
#include "wwdebug.h"
#include "meshbuild.h"
#include "camera.h"
#include "sr.hpp"
#define BPT_FRONT 0x01
#define BPT_BACK 0x02
#define BPT_ON 0x04
#define BPT_BOTH 0x08
#define BPT_EPSILON 0.0001f
#define BPT_COINCIDENCE_EPSILON 0.000001f
class BptVertexClass;
class BptPolyClass;
class BptNodeClass;
class BptBuilderClass;
class BptImpNodeClass;
class BptImpClass;
class BptImpBuilderClass;
/*
** NormalHasherClass, used by the code which builds the
** table of unique Normal vectors.
*/
class NormalHasherClass : public HashCalculatorClass<Vector3>
{
public:
virtual bool Items_Match(const Vector3 & a, const Vector3 & b)
{
// looking for an exact match for normals...
return ((a.X == b.X) && (a.Y == b.Y) && (a.Z == b.Z));
}
virtual void Compute_Hash(const Vector3 & item)
{
HashVal = (int)(item.X*12345.6f + item.Y*1714.38484f + item.Z*27561.3f)&1023;
}
virtual int Num_Hash_Bits(void)
{
return 10; // 10bit hash value.
}
virtual int Num_Hash_Values(void)
{
return 1; // only one hash value for normals, require *exact* match
}
virtual int Get_Hash_Value(int index)
{
return HashVal;
}
private:
int HashVal;
};
/*
** DistanceHasherClass, used by the code which builds the
** table of unique distance values.
*/
class DistanceHasherClass : public HashCalculatorClass<float>
{
virtual bool Items_Match(const float & a, const float & b)
{
// looking for an exact match for distances
return (a == b);
}
virtual void Compute_Hash(const float & item)
{
HashVal = (int)(item)&1023;
}
virtual int Num_Hash_Bits(void)
{
return 12; // 12bit hash value.
}
virtual int Num_Hash_Values(void)
{
return 1; // only one hash value for normals, require *exact* match
}
virtual int Get_Hash_Value(int index)
{
return HashVal;
}
private:
int HashVal;
};
/*
** BPT Notes
**
** - Probably need to build the initial tree in a big straightforward recursive datastructure
** with no indirection or anything. Then, when the whole thing is being built correctly,
** process the tree down so that everything that can be shared (e.g. plane equations) are
** shared.
**
** - Hide most of the implementation details even from the bpt header file by using a
** BptImpClass. This class will contain the final "optimized" bpt information.
**
** - Maybe need a BptBuilder class that builds the initial "straightforward" tree structure
** and then compresses it into a BptImp.
**
** - Extend PlaneClass to know about special case planes such as x-y, y-z, x-z. Optimize
** the culling and clipping functions to take advantage of them.
**
** - Separate the normal from the distance in the plane equation? Many many planes will
** probably share the same normal but different distances. This probably should be a
** very late optimization.
*/
/*
** BptVertexClass
** This is the vertex representation used in building the BPT
*/
class BptVertexClass
{
public:
BptVertexClass(void);
BptVertexClass(const BptVertexClass & that);
BptVertexClass & operator = (const BptVertexClass & that);
int Which_Side(const PlaneClass & plane) const;
Vector3 Position;
Vector3 Normal;
Vector3 Color;
Vector3 TexCoord;
static BptVertexClass Lerp(const BptVertexClass & v0,const BptVertexClass & v1,double lerp);
static BptVertexClass Intersect_Plane(const BptVertexClass & v0,const BptVertexClass & v1,const PlaneClass & plane);
};
/*
** BptPolyClass
** This is the polygon representation used in building the BPT
*/
class BptPolyClass
{
public:
enum { BPT_POLY_MAX_VERTS = 16 };
BptPolyClass(void);
BptPolyClass(const BptPolyClass & that);
BptPolyClass(const BptVertexClass * points, int num);
BptPolyClass & operator = (const BptPolyClass & that);
const BptVertexClass & operator[] (int i) const { return Verts[i]; }
BptVertexClass & operator[] (int i) { return Verts[i]; }
void Compute_Plane(void);
int Which_Side(const PlaneClass & plane) const;
void Split(const PlaneClass & plane,BptPolyClass & front,BptPolyClass & back) const;
bool Is_Degenerate(void);
bool Salvage_Degenerate(void);
int MaterialId;
int NumVerts;
BptVertexClass Verts[BPT_POLY_MAX_VERTS];
PlaneClass Plane;
};
/*
** BptNodeClass
** This is the node representation used in building the BPT
*/
class BptNodeClass
{
public:
BptNodeClass(void) : NumPolys(0),Polys(NULL),Front(NULL),Back(NULL),ArrayIndex(-1) { }
~BptNodeClass(void) { if (Front) delete Front; if (Back) delete Back; if (Polys) delete[] Polys; }
void Build(int numpolys,BptPolyClass * polys);
int Num_Polys(void) const;
int Num_Tris(void) const;
int Num_Nodes(void) const;
int Max_Depth(void) const;
void Submit_Polys(MeshBuilderClass & builder) const;
void Compute_Bounding_Box(void);
int Assign_Array_Indices(int index);
void Submit_Nodes(BptImpBuilderClass & impbuilder) const;
private:
PlaneClass Plane; // splitting plane
uint32 NumPolys; // num polys on plane
BptPolyClass * Polys; // array of polys
BptNodeClass * Front; // pointer to front tree
BptNodeClass * Back; // pointer to back tree
int ArrayIndex; // used in building the tree into array form.
Vector3 Min; // bounding box for this tree.
Vector3 Max;
/*
** SplitChoiceStruct. Whenever a plane is tested to see if it is a suitable splitting
** plane, we'll cache all of the values it computes so we do not duplicate the effort elsewhere.
*/
struct SplitChoiceStruct
{
SplitChoiceStruct(void) : Score(0.0f),FrontCount(0),BackCount(0),OnCount(0),SplitCount(0),Plane(0,0,0,0) {}
float Score; // 0.0 is worst, 1.0 is "perfect"
int FrontCount; // number of polys in front of the plane
int BackCount; // number of polys behind the plane
int OnCount; // number of polys on the plane
int SplitCount; // number of polys split by the plane
PlaneClass Plane; // plane equation
};
/*
** SplitArrayStruct. This struct simply stores the result of a split operation. The
** arrays for the front, back and "on" polygons will be allocated and filled in.
*/
struct SplitArraysStruct
{
SplitArraysStruct(void) : FrontCount(0),BackCount(0),OnCount(0),FrontPolys(NULL),BackPolys(NULL),OnPolys(NULL) {}
int FrontCount;
int BackCount;
int OnCount;
BptPolyClass * FrontPolys;
BptPolyClass * BackPolys;
BptPolyClass * OnPolys;
};
SplitChoiceStruct Compute_Plane_Score(int numpolys,BptPolyClass * polys,const PlaneClass & plane);
SplitChoiceStruct Select_Splitting_Plane(int numpolys,BptPolyClass * polys);
void Split_Polys(const int num_polys,
const BptPolyClass * polys,
const SplitChoiceStruct & sc,
SplitArraysStruct * arrays);
void Max_Depth_Internal(int cur_depth, int & max_depth) const;
friend class BptImpBuilderClass;
};
/*
** BptBuilderClass
** This class builds the initial straightforward binary tree representation of
** the mesh given to it. It then "optimizes" the tree into a BptImpClass to
** save ram.
*/
class BptBuilderClass
{
public:
BptBuilderClass(void);
~BptBuilderClass(void);
BptImpClass * Build(MeshClass * mesh);
BptImpClass * Build(int numpolys,BptPolyClass * polyarray,MaterialInfoClass * matinfo);
private:
void unwind_mesh(MeshClass * mesh,int & set_numpolys,BptPolyClass * & set_polyarray);
BptImpClass * build_imp(void);
BptNodeClass * Root;
MaterialInfoClass * MatInfo;
int InputPolyCount;
int OutputPolyCount;
int InputTriCount;
int OutputTriCount;
int NodeCount;
int MaxDepth;
};
/*
** BptImpNodeClass
** This is the node representation in the final BPT implementation.
** It allows for sharing of plane equations, indirection to the polygons, etc
*/
class BptImpNodeClass
{
public:
float MinX,MinY,MinZ;
float MaxX,MaxY,MaxZ;
BptImpNodeClass * Front;
BptImpNodeClass * Back;
uint16 FirstPoly;
uint16 PolyCount;
uint16 NormalIndex;
uint16 DistanceIndex;
bool Is_Visible(const CameraClass & camera);
bool Cast_AABox_To_Polys(PhysAABoxCollisionTestClass & coltest);
};
/*
** BptImpClass
** This class contains the actual run-time binary partition tree information
** and the code for building a bpt out of an arbitrary list of polygons.
*/
class BptImpClass
{
public:
~BptImpClass(void);
void Free(void);
void Build_Apt(const CameraClass & camera);
bool Cast_Ray(PhysRayCollisionTestClass & raytest) const;
bool Cast_AABox(PhysAABoxCollisionTestClass & boxtest) const;
bool Cast_OBBox(PhysOBBoxCollisionTestClass & boxtest) const;
private:
// These functions are used by the BptImpBuilder to construct
// the BptImp...
BptImpClass(void);
void Set_Mesh(MeshClass * mesh);
void Set_Node_Array(int numnodes, BptImpNodeClass * nodes);
void Set_Normal_Array(const UniqueArrayClass<Vector3> & normals);
void Set_Distance_Array(const UniqueArrayClass<float> & distances);
void Set_Poly_Index_Array(int numremaps,int * polyremaps);
void Begin_Apt(void);
void Add_Polys_To_Apt(BptImpNodeClass * node);
void End_Apt(void);
void Build_Apt_Recursive(BptImpNodeClass * node,const CameraClass & cam);
bool Cast_Ray_Recursive(BptImpNodeClass * node,PhysRayCollisionTestClass & boxtest) const;
bool Cast_AABox_Recursive(BptImpNodeClass * node,PhysAABoxCollisionTestClass & boxtest) const;
bool Cast_OBBox_Recursive(BptImpNodeClass * node,PhysOBBoxCollisionTestClass & boxtest) const;
bool Cast_Ray_To_Polys(BptImpNodeClass * node,PhysAABoxCollisionTestClass & boxtest) const { assert(0); return false; }
bool Cast_AABox_To_Polys(BptImpNodeClass * node,PhysAABoxCollisionTestClass & boxtest) const;
bool Cast_OBBox_To_Polys(BptImpNodeClass * node,PhysAABoxCollisionTestClass & boxtest) const { assert(0); return false; }
void Verify_Node_Bounding_Volume(BptImpNodeClass * node,const Vector3 & min,const Vector3 & max) const;
// Polygon Mesh in w3d-friendly form
MeshClass * Mesh;
// Binary partition nodes
int NodeCount;
BptImpNodeClass * Nodes;
// Node Polygon remap arrays
int PolyIndexCount;
int * PolyIndices;
// Plane equation normals, these are stored separate from the distances
// because many many of the planes will share the same normal but not dist.
int NormalCount;
Vector3 * Normals;
// Plane equation distances, again, stored separately from the normals
int DistanceCount;
float * Distances;
// Active Poly Table support. These variables are used internally by
// the culling code. The ActivePolyTable pointer is set to point into
// the surrender mesh's apt.
int ActivePolyCount;
SRDWORD * ActivePolyTable;
friend class BptClass;
friend class BptImpBuilderClass;
};
/*
** BptImpBuilderClass
** This guy takes a completed bpt tree (given as a pointer to the root node)
** and churns through the thing, generating a BptImpClass.
*/
class BptImpBuilderClass
{
public:
BptImpBuilderClass(void);
~BptImpBuilderClass(void);
void Free(void);
BptImpClass * Build_Bpt_Imp(BptNodeClass * root,MaterialInfoClass * matinfo);
void Add_Bpt_Node(const BptNodeClass * node);
private:
void Remap_Triangle_Indices(void);
MeshBuilderClass MeshBuilder; // accumulate the polygons
NormalHasherClass NormalHasher; // computes hash values for the normals
DistanceHasherClass DistanceHasher; // computes hash values for the distances
UniqueArrayClass<Vector3> * UniqueNormals; // accumulates the unique normals
UniqueArrayClass<float> * UniqueDistances; // accumulates the unique distances
int NodeCount; // number of nodes in the tree
BptImpNodeClass * Nodes; // array of all of the nodes
int TriCount; // number of triangles in the tree
int CurTriIndex; // current remap index
int * TriIndexArray; // array of triangle remap indices
};
/***********************************************************************************************
* BptClass::BptClass -- constructor *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
BptClass::BptClass(void) :
BptImp(NULL)
{
}
/***********************************************************************************************
* BptClass::~BptClass -- destructor *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
BptClass::~BptClass(void)
{
Free();
}
/***********************************************************************************************
* BptClass::Free -- releases all assets in use *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
void BptClass::Free(void)
{
if (BptImp) {
delete BptImp;
BptImp = NULL;
}
}
/***********************************************************************************************
* BptClass::Build -- build a bpt from the given mesh (generates a new mesh) *
* *
* This function will process the given mesh into a binary partition tree. It has to generate *
* a new mesh due to the fact that some polygons may be split in the partioning process. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
void BptClass::Build(MeshClass * mesh)
{
assert(mesh);
/*
** First, release everything that we have!
*/
Free();
/*
** Create a new BptImp object using the given mesh
*/
BptBuilderClass builder;
BptImp = builder.Build(mesh);
}
/***********************************************************************************************
* BptClass::Load -- Initialize this Bpt object from contents of a W3D file *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
int BptClass::Load(ChunkLoadClass & cload)
{
assert(0);
return 0;
}
/***********************************************************************************************
* BptClass::Save -- Save this Bpt object into a W3D file *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
int BptClass::Save(ChunkSaveClass & csave)
{
assert(0);
return 0;
}
/***********************************************************************************************
* BptClass::Render -- submit this object for rendering *
* *
* This function sets the active polygon table for the mesh based on the camera frustum. The *
* bpt is used to cull out portions of the level which are not inside the frustum. Then the *
* mesh is added to the given surrender scene. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
* 5/29/98 GTH : Created. *
*=============================================================================================*/
void BptClass::Render( srScene * scene, const CameraClass &camera )
{
/*
** Do our APT stuff
*/
BptImp->Build_Apt(camera);
/*
** Then let Mesh render itself
*/
BptImp->Mesh->Render(scene,camera);
}
void BptClass::Update_Cached_Bounding_Volumes(void) const
{
CachedBoundingSphere = BptImp->Mesh->Get_Bounding_Sphere();
CachedBoundingBox = BptImp->Mesh->Get_Bounding_Box();
Validate_Cached_Bounding_Volumes();
}
#if 0
MeshClass * BptClass::Get_Mesh(void)
{
if (BptImp) {
if (BptImp->Mesh) BptImp->Mesh->Add_Ref();
return BptImp->Mesh;
} else {
return NULL;
}
}
#endif
/***********************************************************************************************
* BptClass::Cast_Ray -- Intersect a ray with this Bpt Object *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
bool BptClass::Cast_Ray(PhysRayCollisionTestClass & raytest) const
{
assert(BptImp);
return BptImp->Cast_Ray(raytest);
}
/***********************************************************************************************
* BptClass::Cast_AABox -- Intersect a swept axis-aligned box. *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
bool BptClass::Cast_AABox(PhysAABoxCollisionTestClass & aaboxtest) const
{
assert(BptImp);
return BptImp->Cast_AABox(aaboxtest);
}
/***********************************************************************************************
* BptClass::Cast_OBBox -- Intersect a swept oriented box with this bpt object *
* *
* INPUT: *
* *
* OUTPUT: *
* *
* WARNINGS: *
* *
* HISTORY: *
* 5/6/98 GTH : Created. *
*=============================================================================================*/
bool BptClass::Cast_OBBox(PhysOBBoxCollisionTestClass & obboxtest) const
{
assert(BptImp);
return BptImp->Cast_OBBox(obboxtest);
}
/**************************************************************
BptVertexClass Implementation
**************************************************************/
BptVertexClass::BptVertexClass(void) :
Position(0,0,0),
Normal(0,0,0),
Color(0,0,0),
TexCoord(0,0,0)
{
}
BptVertexClass::BptVertexClass(const BptVertexClass & that)
{
Position = that.Position;
Normal = that.Normal;
Color = that.Color;
TexCoord = that.TexCoord;
}
BptVertexClass & BptVertexClass::operator = (const BptVertexClass & that)
{
if (this != &that) {
Position = that.Position;
Normal = that.Normal;
Color = that.Color;
TexCoord = that.TexCoord;
}
return *this;
}
BptVertexClass BptVertexClass::Lerp(const BptVertexClass & v0,const BptVertexClass & v1,double lerp)
{
assert(lerp >= -BPT_EPSILON);
assert(lerp <= 1.0 + BPT_EPSILON);
// interpolate position
BptVertexClass res;
res.Position = ::Lerp(v0.Position,v1.Position,lerp);
// interpolate normal, renormalize
res.Normal = ::Lerp(v0.Normal,v1.Normal,lerp);
res.Normal.Normalize();
// interpolate color
res.Color = ::Lerp(v0.Color,v1.Color,lerp);
// interpolate texture coordinates
res.TexCoord = ::Lerp(v0.TexCoord,v1.TexCoord,lerp);
return res;
}
int BptVertexClass::Which_Side(const PlaneClass & plane) const
{
float d = Vector3::Dot_Product(plane.N,Position);
d -= plane.D;
if (d > BPT_EPSILON) {
return BPT_FRONT;
}
if (d < -BPT_EPSILON) {
return BPT_BACK;
}
return BPT_ON;
}
BptVertexClass BptVertexClass::Intersect_Plane
(
const BptVertexClass & p0,
const BptVertexClass & p1,
const PlaneClass & plane
)
{
double alpha = 0.0f;
plane.Compute_Intersection(p0.Position,p1.Position,&alpha);
return BptVertexClass::Lerp(p0,p1,alpha);
}
/**************************************************************
BptPolyClass Implementation
**************************************************************/
BptPolyClass::BptPolyClass(void) :
NumVerts(0)
{
}
BptPolyClass::BptPolyClass(const BptPolyClass & that)
{
NumVerts = that.NumVerts;
for (int i=0;i<NumVerts;i++) {
Verts[i] = that.Verts[i];
}
}
BptPolyClass::BptPolyClass(const BptVertexClass * points, int num)
{
NumVerts = num;
for (int i=0; i<NumVerts; i++) {
Verts[i] = points[i];
}
}
BptPolyClass & BptPolyClass::operator = (const BptPolyClass & that)
{
if (this != &that) {
MaterialId = that.MaterialId;
NumVerts = that.NumVerts;
Plane = that.Plane;
for (int i=0;i<NumVerts;i++) {
Verts[i] = that.Verts[i];
}
}
return * this;
}
void BptPolyClass::Compute_Plane(void)
{
double nx = 0;
double ny = 0;
double nz = 0;
double ax = 0;
double ay = 0;
double az = 0;
int i,j;
for (i=0; i<NumVerts; i++) {
j = (i+1) % NumVerts;
nx += (double)(Verts[i].Position.Y - Verts[j].Position.Y) * (double)(Verts[i].Position.Z + Verts[j].Position.Z);
ny += (double)(Verts[i].Position.Z - Verts[j].Position.Z) * (double)(Verts[i].Position.X + Verts[j].Position.X);
nz += (double)(Verts[i].Position.X - Verts[j].Position.X) * (double)(Verts[i].Position.Y + Verts[j].Position.Y);
ax += (double)Verts[i].Position.X;
ay += (double)Verts[i].Position.Y;
az += (double)Verts[i].Position.Z;
}
ax /= (double)NumVerts;
ay /= (double)NumVerts;
az /= (double)NumVerts;
double len = sqrt(nx*nx + ny*ny + nz*nz);
nx /= len;
ny /= len;
nz /= len;
Plane.Set(Vector3(nx,ny,nz),Vector3(ax,ay,az));
}
int BptPolyClass::Which_Side(const PlaneClass & plane) const
{
int side_mask = 0;
for (int i=0; i<NumVerts;i++) {
side_mask |= Verts[i].Which_Side(plane);
}
// check if all verts are "ON"
if (side_mask == BPT_ON) {
return BPT_ON;
}
// check if all verts are either "ON" or "FRONT"
if ((side_mask & ~(BPT_FRONT | BPT_ON)) == 0) {
return BPT_FRONT;
}
// check if all verts are either "ON" or "BACK"
if ((side_mask & ~(BPT_BACK | BPT_ON)) == 0) {
return BPT_BACK;
}
// otherwise, poly spans the plane.
return BPT_BOTH;
}
void BptPolyClass::Split(const PlaneClass & plane,BptPolyClass & front,BptPolyClass & back) const
{
front = *this;
back = *this;
front.NumVerts = back.NumVerts = 0;
assert(Which_Side(plane) == BPT_BOTH);
BptVertexClass point;
front.NumVerts = 0;
back.NumVerts = 0;
// find a vertex on one side or the other
int side = BPT_ON;
int i;
for (i = 0; (i < NumVerts) && ((side = Verts[i].Which_Side(plane)) == BPT_ON); i++);
// perform clipping
int iprev = i;
int sideprev = side;
int sidelastdefinite = 0;
i = (i+1) % NumVerts;
for (int j=0; j<NumVerts; j++) {
side = Verts[i].Which_Side(plane);
if (sideprev == BPT_FRONT) {
if (side == BPT_FRONT) {
// Previous vertex was in front of plane and this vertex is in
// front of the plane so we emit this vertex in the front poly
front.Verts[(front.NumVerts)++] = Verts[i];
} else if (side == BPT_ON) {
// Previous vert was in front, this vert is "on" so emit
// the vertex into the front poly.
sidelastdefinite = BPT_FRONT;
front.Verts[(front.NumVerts)++] = Verts[i];
} else { // side == BPT_BACK
// Previous vert was in front, this vert is behind, compute
// the intersection and emit the point in both the front
// and back polys. Then continue the edge into the back halfspace
point = BptVertexClass::Intersect_Plane(Verts[iprev],Verts[i],plane);
front.Verts[(front.NumVerts)++] = point;
back.Verts[(back.NumVerts)++] = point;
back.Verts[(back.NumVerts)++] = Verts[i];
}
} else if (sideprev == BPT_BACK) {
if (side == BPT_FRONT) {
// segment is going from the back halfspace to the front halfspace
// compute the intersection and emit it in both polys, then continue
// the edge into the front halfspace.
point = BptVertexClass::Intersect_Plane(Verts[iprev],Verts[i],plane);
back.Verts[(back.NumVerts)++] = point;
front.Verts[(front.NumVerts)++] = point;
front.Verts[(front.NumVerts)++] = Verts[i];
} else if (side == BPT_ON) {
// segment went from back halfspace to "on" the plane. Emit
// the vertex into the back poly and remember that we came
// from the back halfspace.
sidelastdefinite = BPT_BACK;
back.Verts[(back.NumVerts)++] = Verts[i];
} else { // side == BPT_BACK
// segment is completely in the back halfspace, just emit the
// vertex into the back poly
back.Verts[(back.NumVerts)++] = Verts[i];
}
} else if (sideprev == BPT_ON) {
if (side == BPT_FRONT) {
// segment is on the plane
if (sidelastdefinite == BPT_BACK) {
front.Verts[(front.NumVerts)++] = Verts[iprev];
}
front.Verts[(front.NumVerts)++] = Verts[i];
} else if (side == BPT_ON) {
if (sidelastdefinite == BPT_FRONT) {
front.Verts[(front.NumVerts)++] = Verts[i];
} else {
back.Verts[(back.NumVerts)++] = Verts[i];
}
} else { // side == BPT_BACK
if (sidelastdefinite == BPT_FRONT) {
back.Verts[(back.NumVerts)++] = Verts[iprev];
}
back.Verts[(back.NumVerts)++] = Verts[i];
}
} else {
WWASSERT_PRINT(0,"BptPolyClass::Split : invalid side\n");
}
sideprev = side;
iprev = i;
i = (i+1)%NumVerts;
}
front.Compute_Plane();
back.Compute_Plane();
// check the two polygons
if (front.Is_Degenerate()) {
front.Salvage_Degenerate();
}
if (back.Is_Degenerate()) {
back.Salvage_Degenerate();
}
}
bool BptPolyClass::Is_Degenerate(void)
{
int i,j;
if (NumVerts == 2) {
WWDEBUG_SAY(("Degenerate Poly - fewer than 3 vertices\r\n"));
return true;
}
for (i=0; i<NumVerts; i++) {
for (j = i+1; j < NumVerts; j++) {
float delta = (Verts[i].Position - Verts[j].Position).Length();
if (delta < BPT_COINCIDENCE_EPSILON) {
WWDEBUG_SAY(("Degenerate Poly - coincident vertices!\r\n"));
return true;
}
}
}
for (i=0; i<NumVerts; i++) {
int side = Verts[i].Which_Side(Plane);
if (side != BPT_ON) {
// hmmm, try to recalculate the plane, if it is still bad, then give up
Compute_Plane();
if (Verts[i].Which_Side(Plane) != BPT_ON) {
WWDEBUG_SAY(("Degenerate Poly - invalid plane!\r\n"));
return true;
}
}
}
return false;
}
bool BptPolyClass::Salvage_Degenerate(void)
{
/*
** About all we can do is combine sequential vertices which are co-incident
*/
int i = 0;
while (i < NumVerts) {
float delta = (Verts[i].Position - Verts[i+1].Position).Length();
if (delta < BPT_COINCIDENCE_EPSILON) {
for (int j=i+1; j<NumVerts-1; j++) {
Verts[j] = Verts[j+1];
}
NumVerts--;
} else {
i++;
}
}
return !Is_Degenerate();
}
/**************************************************************
BptBuilderClass Implementation
**************************************************************/
BptBuilderClass::BptBuilderClass(void) :
Root(NULL),
MatInfo(NULL),
InputPolyCount(0),
OutputPolyCount(0),
NodeCount(0),
MaxDepth(0)
{
}
BptBuilderClass::~BptBuilderClass(void)
{
if (Root) delete Root;
if (MatInfo) MatInfo->Release_Ref();
}
BptImpClass * BptBuilderClass::Build(MeshClass * mesh)
{
/*
** Here we go, unwind the mesh into BptVerts and BptPolys
*/
int numpolys;
BptPolyClass * polys;
unwind_mesh(mesh,numpolys,polys);
assert(numpolys > 0);
/*
** Jump into the generalized build method
*/
MaterialInfoClass * matinfo = mesh->Get_Material_Info();
BptImpClass * result = Build(numpolys,polys,matinfo);
matinfo->Release_Ref();
/*
** Build function deletes the poly array...
*/
polys = NULL;
return result;
}
BptImpClass * BptBuilderClass::Build(int numpolys,BptPolyClass * polys,MaterialInfoClass * matinfo)
{
/*
** Create a copy of all of the materials
*/
MatInfo = (MaterialInfoClass *)matinfo->Clone();
/*
** Store the input counts
*/
InputPolyCount = numpolys;
InputTriCount = 0;
for (int i=0; i<numpolys; i++) {
InputTriCount += polys[i].NumVerts-2;
}
WWDEBUG_SAY(("Building Binary Tree\r\n"));
/*
** Build the fat tree
*/
assert(Root == NULL);
Root = new BptNodeClass;
Root->Build(numpolys,polys);
/*
** Compile some statistics about the tree
*/
OutputPolyCount = Root->Num_Polys();
OutputTriCount = Root->Num_Tris();
NodeCount = Root->Num_Nodes();
MaxDepth = Root->Max_Depth();
WWDEBUG_SAY(("Binary Tree Generated!\r\n"));
WWDEBUG_SAY(("Node Count: %d\r\n",NodeCount));
WWDEBUG_SAY(("Max Depth: %d\r\n",MaxDepth));
WWDEBUG_SAY(("Input Poly Count: %d\r\n",InputPolyCount));
WWDEBUG_SAY(("Output Poly Count: %d\r\n",OutputPolyCount));
WWDEBUG_SAY(("Input Tri Count: %d\r\n",InputTriCount));
WWDEBUG_SAY(("Output Tri Count: %d\r\n",OutputTriCount));
WWDEBUG_SAY(("Out/In Poly Ratio: %f\r\n",(float)OutputPolyCount / (float)InputPolyCount));
WWDEBUG_SAY(("Out/In Tri Ratio: %f\r\n",(float)OutputTriCount / (float)InputTriCount));
WWDEBUG_SAY(("\r\n"));
/*
** Build the optimized BptImp and return it.
*/
return build_imp();
}
void BptBuilderClass::unwind_mesh(MeshClass * mesh,int & set_numpolys,BptPolyClass * & set_polyarray)
{
srMeshModel * srmesh = mesh->Get_Surrender_Mesh();
// get pointers to the interesting stuff in the mesh
srVector3 * loc = srmesh->getVertexLoc();
srVector3 * norm = srmesh->getVertexNormal();
srVector3 * uv = srmesh->getVertexUVW(srMaterial::DIFFUSE_COLOR,false);
srVector3 * dcg = srmesh->getVertexDCG(false);
srVector3i * polyvert = srmesh->getPolyVertex();
srMaterial ** polymtl = srmesh->getPolyMaterial(false);
// allocate a bunch of BptPolygons
set_numpolys = srmesh->getPolygonCount();
set_polyarray = new BptPolyClass[set_numpolys];
int src_idx = 0;
int dst_idx = 0;
for (src_idx = 0; src_idx<set_numpolys; src_idx++) {
set_polyarray[dst_idx].NumVerts = 3;
for (int vrt_idx=0; vrt_idx<3; vrt_idx++) {
// copy the three positions
set_polyarray[dst_idx][vrt_idx].Position.X = loc[polyvert[src_idx][vrt_idx]].x;
set_polyarray[dst_idx][vrt_idx].Position.Y = loc[polyvert[src_idx][vrt_idx]].y;
set_polyarray[dst_idx][vrt_idx].Position.Z = loc[polyvert[src_idx][vrt_idx]].z;
// copy the three normals
if (norm) {
set_polyarray[dst_idx][vrt_idx].Normal.X = norm[polyvert[src_idx][vrt_idx]].x;
set_polyarray[dst_idx][vrt_idx].Normal.Y = norm[polyvert[src_idx][vrt_idx]].y;
set_polyarray[dst_idx][vrt_idx].Normal.Z = norm[polyvert[src_idx][vrt_idx]].z;
}
// copy the three u-v's
if (uv) {
set_polyarray[dst_idx][vrt_idx].TexCoord.X = uv[polyvert[src_idx][vrt_idx]].x;
set_polyarray[dst_idx][vrt_idx].TexCoord.Y = uv[polyvert[src_idx][vrt_idx]].y;
set_polyarray[dst_idx][vrt_idx].TexCoord.Z = uv[polyvert[src_idx][vrt_idx]].z;
}
// copy the three colors
if (dcg) {
set_polyarray[dst_idx][vrt_idx].Color.X = dcg[polyvert[src_idx][vrt_idx]].x;
set_polyarray[dst_idx][vrt_idx].Color.Y = dcg[polyvert[src_idx][vrt_idx]].y;
set_polyarray[dst_idx][vrt_idx].Color.Z = dcg[polyvert[src_idx][vrt_idx]].z;
}
// store the material ID
if (polymtl) {
set_polyarray[dst_idx].MaterialId = ((MaterialClass *)(polymtl[src_idx]))->Get_Mat_Info_Index();
} else {
set_polyarray[dst_idx].MaterialId = 0;
}
}
set_polyarray[dst_idx].Compute_Plane();
// if the triangle is not degenerate, accept it; increment the counter
if (!set_polyarray[dst_idx].Is_Degenerate()) {
dst_idx++;
}
}
// set the number of polys to the actual number kept
set_numpolys = dst_idx;
assert(set_numpolys <= srmesh->getPolygonCount());
WWDEBUG_SAY(("Unwinding Input Mesh\r\n"));
WWDEBUG_SAY(("Input poly count: %d\r\n",srmesh->getPolygonCount()));
WWDEBUG_SAY(("Count after removing degenerates: %d\r\n",set_numpolys));
WWDEBUG_SAY(("\r\n"));
}
BptImpClass * BptBuilderClass::build_imp(void)
{
BptImpBuilderClass impbuilder;
BptImpClass * imp = impbuilder.Build_Bpt_Imp(Root,MatInfo);
return imp;
}
/**************************************************************
BptNodeClass Implementation
**************************************************************/
int BptNodeClass::Num_Polys(void) const
{
int count = NumPolys;
if (Front) count += Front->Num_Polys();
if (Back) count += Back->Num_Polys();
return count;
}
int BptNodeClass::Num_Tris(void) const
{
int count = 0;
for (unsigned int i=0; i<NumPolys; i++) {
count += Polys[i].NumVerts - 2;
}
if (Front) count += Front->Num_Tris();
if (Back) count += Back->Num_Tris();
return count;
}
int BptNodeClass::Num_Nodes(void) const
{
int count = 1;
if (Front) count += Front->Num_Nodes();
if (Back) count += Back->Num_Nodes();
return count;
}
int BptNodeClass::Max_Depth(void) const
{
int max_depth = 0;
Max_Depth_Internal(0,max_depth);
return max_depth;
}
void BptNodeClass::Max_Depth_Internal(int cur_depth,int & max_depth) const
{
cur_depth++;
if (cur_depth > max_depth) {
max_depth = cur_depth;
}
if (Front) {
Front->Max_Depth_Internal(cur_depth,max_depth);
}
if (Back) {
Back->Max_Depth_Internal(cur_depth,max_depth);
}
}
void BptNodeClass::Build(int numpolys,BptPolyClass * polys)
{
/*
** Select and assign the splitting plane
*/
SplitChoiceStruct sc = Select_Splitting_Plane(numpolys,polys);
Plane = sc.Plane;
/*
** Split the polygons
*/
SplitArraysStruct arrays;
Split_Polys(numpolys,polys,sc,&arrays);
/*
** Free the memory in use by polys!
*/
delete[] polys;
/*
** Store the "on" polys in this node
*/
NumPolys = arrays.OnCount;
Polys = arrays.OnPolys;
arrays.OnPolys = NULL;
/*
** Build a front tree if necessary. Remember that the Build function
** deletes the poly array.
*/
if (arrays.FrontCount) {
assert(arrays.FrontPolys != NULL);
Front = new BptNodeClass;
Front->Build(arrays.FrontCount,arrays.FrontPolys);
arrays.FrontPolys = NULL;
}
/*
** Build a back tree if necessary. Remember that the build function
** deletes the poly array.
*/
if (arrays.BackCount) {
assert(arrays.BackPolys != NULL);
Back = new BptNodeClass;
Back->Build(arrays.BackCount,arrays.BackPolys);
arrays.BackPolys = NULL;
}
}
BptNodeClass::SplitChoiceStruct
BptNodeClass::Compute_Plane_Score(int numpolys,BptPolyClass * polys,const PlaneClass & plane)
{
/*
** Suitability of a plane as a partition plane is based on the following factors:
** - How many polygons will be split
** - How many polygons end up on each side of the tree
** - How many polygons end up on the plane (do NOT want many on the plane)
** - How evenly the volume of space is divided.
** The following weights will determine how much influence each factor
** has on the final score.
*/
const float POLY_SPLIT_WEIGHT = 5.0f;
const float POLY_COUNT_BALANCE_WEIGHT = 1.0f;
const float POLYS_ON_PLANE_WEIGHT = 1.0f;
const float TOTAL_WEIGHT = POLY_SPLIT_WEIGHT + POLY_COUNT_BALANCE_WEIGHT + POLYS_ON_PLANE_WEIGHT;
const int MAX_POLYS_ON_PLANE = 10;
const int MAX_SPLITS = 10;
/*
** Count up the cases
*/
SplitChoiceStruct sc;
sc.Plane = plane;
for (int i=0; i<numpolys; i++) {
switch(polys[i].Which_Side(plane)) {
case BPT_FRONT: sc.FrontCount++; break;
case BPT_BACK: sc.BackCount++; break;
case BPT_ON: sc.OnCount++; break;
case BPT_BOTH: sc.SplitCount++; break;
}
}
/*
** Compute the score
*/
float max_splits = min(MAX_SPLITS,numpolys);
float max_on = min(MAX_POLYS_ON_PLANE,numpolys);
float split_score = (float)(max_splits - sc.SplitCount) / max_splits;
if (split_score < 0.0f) split_score = 0.0f;
float on_score = 1.0f - (float)sc.OnCount / max_on;
if (on_score < 0.0f) on_score = 0.0f;
float balance_score = 1.0f - (fabs(sc.FrontCount - sc.BackCount) / ((float)numpolys / 2.0f));
sc.Score = 0.0f;
sc.Score += split_score * POLY_SPLIT_WEIGHT;
sc.Score += balance_score * POLY_COUNT_BALANCE_WEIGHT;
sc.Score += on_score * POLYS_ON_PLANE_WEIGHT;
sc.Score /= TOTAL_WEIGHT;
/*
** Reject the plane if any of the following conditions are true:
** - plane does not reduce the tree! (all polys in front or behind)
** - more than MAX_POLYS_ON_PLANE polygons on the splitting plane
*/
if ((abs(sc.FrontCount-sc.BackCount) == numpolys) && (sc.OnCount == 0)) {
sc.Score = 0.0f;
}
if (sc.OnCount > MAX_POLYS_ON_PLANE) {
sc.Score *= 0.4f;
}
return sc;
}
BptNodeClass::SplitChoiceStruct
BptNodeClass::Select_Splitting_Plane(int numpolys,BptPolyClass * polys)
{
doitagain:
const int NUM_VERTEX_TRYS = 30;
const int NUM_POLY_TRYS = 30;
SplitChoiceStruct best_plane_stats;
SplitChoiceStruct considered_plane_stats;
PlaneClass plane;
/*
** Try putting axis-aligned planes through some random vertices
*/
for (int vertex_trys = 0; vertex_trys < min(NUM_VERTEX_TRYS,3*numpolys); vertex_trys++) {
int poly_index = rand() % numpolys;
int vert_index = rand() % polys[poly_index].NumVerts;;
Vector3 pos = polys[poly_index].Verts[vert_index].Position;
/*
** Try the x-axis
*/
plane.Set(Vector3(1,0,0),pos);
considered_plane_stats = Compute_Plane_Score(numpolys,polys,plane);
if (considered_plane_stats.Score > best_plane_stats.Score) {
best_plane_stats = considered_plane_stats;
}
/*
** Try the y-axis
*/
plane.Set(Vector3(0,1,0),pos);
considered_plane_stats = Compute_Plane_Score(numpolys,polys,plane);
if (considered_plane_stats.Score > best_plane_stats.Score) {
best_plane_stats = considered_plane_stats;
}
}
/*
** Now try "autopartition" planes which come from the input polygons
*/
for (int poly_trys=0; poly_trys<min(NUM_POLY_TRYS,numpolys); poly_trys++) {
int poly_index = rand() % numpolys;
assert(
(polys[poly_index][0].Which_Side(polys[poly_index].Plane) == BPT_ON) &&
(polys[poly_index][1].Which_Side(polys[poly_index].Plane) == BPT_ON) &&
(polys[poly_index][2].Which_Side(polys[poly_index].Plane) == BPT_ON)
);
considered_plane_stats = Compute_Plane_Score(numpolys,polys,polys[poly_index].Plane);
if (considered_plane_stats.Score > best_plane_stats.Score) {
best_plane_stats = considered_plane_stats;
}
}
if (best_plane_stats.Score <= 0.0f) {
_asm int 0x03;
goto doitagain;
}
//assert(best_plane_stats.Score > 0.0f);
return best_plane_stats;
}
void BptNodeClass::Split_Polys
(
const int num_polys,
const BptPolyClass * polys,
const SplitChoiceStruct & sc,
SplitArraysStruct * arrays
)
{
// Note that this routine allocates three arrays of polygons, an array of polys in
// front of the plane, an array of polys behind the plane, and an array of polys on the plane.
// The caller is then responsible for keeping track of the memory this routine allocates.
if (sc.FrontCount + sc.SplitCount > 0) {
arrays->FrontPolys = new BptPolyClass[sc.FrontCount + sc.SplitCount];
}
if (sc.BackCount + sc.SplitCount > 0) {
arrays->BackPolys = new BptPolyClass[sc.BackCount + sc.SplitCount];
}
if (sc.OnCount > 0) {
arrays->OnPolys = new BptPolyClass[sc.OnCount];
}
arrays->FrontCount = 0;
arrays->BackCount = 0;
arrays->OnCount = 0;
for (int i=0; i<num_polys; i++) {
switch(polys[i].Which_Side(sc.Plane)) {
case BPT_FRONT:
arrays->FrontPolys[arrays->FrontCount++] = polys[i];
break;
case BPT_BACK:
arrays->BackPolys[arrays->BackCount++] = polys[i];
break;
case BPT_ON:
arrays->OnPolys[arrays->OnCount++] = polys[i];
break;
case BPT_BOTH:
polys[i].Split(sc.Plane,arrays->FrontPolys[arrays->FrontCount],arrays->BackPolys[arrays->BackCount]);
if (!arrays->FrontPolys[arrays->FrontCount].Is_Degenerate()) {
arrays->FrontCount++;
}
if (!arrays->BackPolys[arrays->BackCount].Is_Degenerate()) {
arrays->BackCount++;
}
break;
}
}
// when we are all done, the counts should match.
assert(arrays->FrontCount <= sc.FrontCount + sc.SplitCount);
assert(arrays->BackCount <= sc.BackCount + sc.SplitCount);
assert(arrays->OnCount == sc.OnCount);
/*
** Check if we threw away all of the polys
** (this can only happen if all of them were degenerate...)
*/
if ((arrays->OnCount == 0) && (arrays->OnPolys != NULL)) {
delete[] arrays->OnPolys;
arrays->OnPolys = NULL;
}
if ((arrays->BackCount == 0) && (arrays->BackPolys != NULL)) {
delete[] arrays->BackPolys;
arrays->BackPolys = NULL;
}
if ((arrays->FrontCount == 0) && (arrays->FrontPolys != NULL)) {
delete[] arrays->FrontPolys;
arrays->FrontPolys = NULL;
}
}
void BptNodeClass::Submit_Polys(MeshBuilderClass & builder) const
{
/*
** Submit the back tree
*/
if (Back) {
Back->Submit_Polys(builder);
}
/*
** Triangulate each poly and submit it to the mesh builder
*/
MeshBuilderClass::FaceClass face;
for (unsigned int fi=0; fi<NumPolys; fi++) {
BptPolyClass * poly = &(Polys[fi]);
for (int ti = 2;ti < poly->NumVerts; ti++) {
face.MaterialIdx = poly->MaterialId;
face.SmGroup = 1; //TODO: get the correct smooth group???
face.Index = 0; //TODO: do we have an actual index?
face.Attributes = 0;
face.Verts[0].Position = poly->Verts[0].Position;
face.Verts[0].TexCoord = poly->Verts[0].TexCoord;
face.Verts[0].Color = poly->Verts[0].Color;
face.Verts[1].Position = poly->Verts[ti-1].Position;
face.Verts[1].TexCoord = poly->Verts[ti-1].TexCoord;
face.Verts[1].Color = poly->Verts[ti-1].Color;
face.Verts[2].Position = poly->Verts[ti].Position;
face.Verts[2].TexCoord = poly->Verts[ti].TexCoord;
face.Verts[2].Color = poly->Verts[ti].Color;
builder.Add_Face(face);
}
}
/*
** Submit the front tree
*/
if (Front) {
Front->Submit_Polys(builder);
}
}
void BptNodeClass::Compute_Bounding_Box(void)
{
/*
** make the children update their boxes first
*/
if (Front) {
Front->Compute_Bounding_Box();
}
if (Back) {
Back->Compute_Bounding_Box();
}
Min.Set(10000.0f,10000.0f,10000.0f);
Max.Set(-10000.0f,-10000.0f,-10000.0f);
/*
** Bound the polys in this node
*/
for (unsigned int poly_index = 0; poly_index < NumPolys; poly_index++) {
for (int vert_index = 0; vert_index < Polys[poly_index].NumVerts; vert_index++) {
Vector3 & point = Polys[poly_index].Verts[vert_index].Position;
if (point.X - BPT_EPSILON < Min.X) Min.X = point.X - BPT_EPSILON;
if (point.X + BPT_EPSILON > Max.X) Max.X = point.X + BPT_EPSILON;
if (point.Y - BPT_EPSILON < Min.Y) Min.Y = point.Y - BPT_EPSILON;
if (point.Y + BPT_EPSILON > Max.Y) Max.Y = point.Y + BPT_EPSILON;
if (point.Z - BPT_EPSILON < Min.Z) Min.Z = point.Z - BPT_EPSILON;
if (point.Z + BPT_EPSILON > Max.Z) Max.Z = point.Z + BPT_EPSILON;
}
}
/*
** bound the polys in the front child node
*/
if (Front) {
if (Front->Min.X < Min.X) Min.X = Front->Min.X;
if (Front->Max.X > Max.X) Max.X = Front->Max.X;
if (Front->Min.Y < Min.Y) Min.Y = Front->Min.Y;
if (Front->Max.Y > Max.Y) Max.Y = Front->Max.Y;
if (Front->Min.Z < Min.Z) Min.Z = Front->Min.Z;
if (Front->Max.Z > Max.Z) Max.Z = Front->Max.Z;
}
/*
** bound the polys in the back child node
*/
if (Back) {
if (Back->Min.X < Min.X) Min.X = Back->Min.X;
if (Back->Max.X > Max.X) Max.X = Back->Max.X;
if (Back->Min.Y < Min.Y) Min.Y = Back->Min.Y;
if (Back->Max.Y > Max.Y) Max.Y = Back->Max.Y;
if (Back->Min.Z < Min.Z) Min.Z = Back->Min.Z;
if (Back->Max.Z > Max.Z) Max.Z = Back->Max.Z;
}
assert(Min.X != 10000.0f);
assert(Min.Y != 10000.0f);
assert(Min.Z != 10000.0f);
assert(Max.X != -10000.0f);
assert(Max.Y != -10000.0f);
assert(Max.Z != -10000.0f);
}
int BptNodeClass::Assign_Array_Indices(int index)
{
/*
** This function is used to assign a sequential index to
** each node in the tree. The BptImp stores its nodes in
** an array so this index is used to determine which slot
** in the array to put each node into.
*/
ArrayIndex = index;
index++;
if (Front) {
index = Front->Assign_Array_Indices(index);
}
if (Back) {
index = Back->Assign_Array_Indices(index);
}
return index;
}
void BptNodeClass::Submit_Nodes(BptImpBuilderClass & imp_builder) const
{
imp_builder.Add_Bpt_Node(this);
if (Front) {
Front->Submit_Nodes(imp_builder);
}
if (Back) {
Back->Submit_Nodes(imp_builder);
}
}
/**************************************************************
BptImpClass Implementation
**************************************************************/
inline bool BptImpNodeClass::Is_Visible(const CameraClass & camera)
{
return !camera.Cull_Box(Vector3(MinX,MinY,MinZ),Vector3(MaxX,MaxY,MaxZ));
}
/**************************************************************
BptImpClass Implementation
**************************************************************/
BptImpClass::BptImpClass(void) :
Mesh(NULL),
NodeCount(0),
Nodes(NULL),
PolyIndexCount(0),
PolyIndices(NULL),
NormalCount(0),
Normals(NULL),
DistanceCount(0),
Distances(NULL),
ActivePolyCount(0),
ActivePolyTable(NULL)
{
}
BptImpClass::~BptImpClass(void)
{
Free();
}
void BptImpClass::Free(void)
{
if (Mesh!=NULL) {
Mesh->Release_Ref();
Mesh = NULL;
}
NodeCount = 0;
if (Nodes != NULL) {
delete[] Nodes;
Nodes = NULL;
}
PolyIndexCount = 0;
if (PolyIndices != NULL) {
delete[] PolyIndices;
PolyIndices = NULL;
}
NormalCount = 0;
if (Normals != NULL) {
delete[] Normals;
Normals = NULL;
}
DistanceCount = 0;
if (Distances != NULL) {
delete[] Distances;
Distances = NULL;
}
}
void BptImpClass::Set_Mesh(MeshClass * mesh)
{
if (Mesh) Mesh->Release_Ref();
Mesh = mesh;
if (Mesh) Mesh->Add_Ref();
}
void BptImpClass::Set_Node_Array(int numnodes, BptImpNodeClass * nodes)
{
if (Nodes) delete[] Nodes;
Nodes = nodes;
NodeCount = numnodes;
}
void BptImpClass::Set_Normal_Array(const UniqueArrayClass<Vector3> & normals)
{
if (normals.Count() != NormalCount) {
if (Normals) delete[] Normals;
Normals = new Vector3[normals.Count()];
}
NormalCount = normals.Count();
for (int i=0; i<NormalCount; i++) {
Normals[i] = normals.Get(i);
}
}
void BptImpClass::Set_Distance_Array(const UniqueArrayClass<float> & distances)
{
if (distances.Count() != DistanceCount) {
if (Distances) delete[] Distances;
Distances = new float[distances.Count()];
}
DistanceCount = distances.Count();
for (int i=0; i<DistanceCount; i++) {
Distances[i] = distances.Get(i);
}
}
void BptImpClass::Set_Poly_Index_Array(int numremaps,int * polyremaps)
{
if (PolyIndices) delete[] PolyIndices;
PolyIndices = polyremaps;
PolyIndexCount = numremaps;
}
void BptImpClass::Build_Apt(const CameraClass & camera)
{
WWDEBUG_PROFILE_START("BPT::Build_Apt");
Begin_Apt();
Build_Apt_Recursive(&(Nodes[0]),camera);
End_Apt();
WWDEBUG_PROFILE_STOP("BPT::Build_Apt");
}
void BptImpClass::Begin_Apt(void)
{
ActivePolyCount = 0;
ActivePolyTable = Mesh->Get_Surrender_Mesh()->getActivePolygonTable();
}
void BptImpClass::Add_Polys_To_Apt(BptImpNodeClass * node)
{
assert(node != NULL);
#if 0
memcpy(&(ActivePolyTable[ActivePolyCount]),&(PolyIndices[node->FirstPoly]),node->PolyCount);
ActivePolyCount += node->PolyCount;
#else
for (int poly_index=0; poly_index<node->PolyCount; poly_index++) {
ActivePolyTable[ActivePolyCount++] = PolyIndices[node->FirstPoly + poly_index];
}
#endif
}
void BptImpClass::End_Apt(void)
{
Mesh->Get_Surrender_Mesh()->setActivePolygonCount(ActivePolyCount);
// WWDEBUG_SAY(("Active Polys: %d\r\n",ActivePolyCount));
}
void BptImpClass::Build_Apt_Recursive(BptImpNodeClass * node,const CameraClass & cam)
{
/*
** Check the camera's frustum against this node's
** bounding volume
*/
if (!node->Is_Visible(cam)) {
Verify_Node_Bounding_Volume(node,Vector3(node->MinX,node->MinY,node->MinZ),Vector3(node->MaxX,node->MaxY,node->MaxZ));
return;
}
/*
** Ok part of this tree may be visible, compute
** which side of the splitting plane the camera is on
*/
float dist = Vector3::Dot_Product(Normals[node->NormalIndex],cam.Get_Position());
if (dist > Distances[node->DistanceIndex]) {
/*
** FRONT:
** We are in front of the plane so proceed in the following order:
** Visit the 'Back' node
** Add our polys to the APT
** Visit the 'Front' node
*/
if (node->Back) Build_Apt_Recursive(node->Back,cam);
Add_Polys_To_Apt(node);
if (node->Front) Build_Apt_Recursive(node->Front,cam);
} else {
/*
** BACK:
** We are in back of the plane so proceed in the following order:
** Visit the 'Front' node
** Add our polys to the APT
** Visit the 'Back' node
*/
if (node->Front) Build_Apt_Recursive(node->Front,cam);
Add_Polys_To_Apt(node);
if (node->Back) Build_Apt_Recursive(node->Back,cam);
}
}
bool BptImpClass::Cast_Ray(PhysRayCollisionTestClass & raytest) const
{
return Cast_Ray_Recursive(&(Nodes[0]),raytest);
}
bool BptImpClass::Cast_AABox(PhysAABoxCollisionTestClass & boxtest) const
{
/*
** Test the sweeping AABox against the BPT recursively!
*/
WWDEBUG_PROFILE_START("BPT::Cast_AABox");
bool res = Cast_AABox_Recursive(&(Nodes[0]),boxtest);
WWDEBUG_PROFILE_STOP("BPT::Cast_AABox");
return res;
}
bool BptImpClass::Cast_OBBox(PhysOBBoxCollisionTestClass & boxtest) const
{
return Cast_OBBox_Recursive(&(Nodes[0]),boxtest);
}
bool BptImpClass::Cast_Ray_Recursive
(
BptImpNodeClass * node,
PhysRayCollisionTestClass & raytest
) const
{
return false;
}
bool BptImpClass::Cast_AABox_Recursive
(
BptImpNodeClass * node,
PhysAABoxCollisionTestClass & boxtest
) const
{
/*
** First, check the bounding box of the move against the bounding box
** of this tree, if they don't intersect, bail out
*/
if ((boxtest.SweepMin.X > node->MaxX) || (boxtest.SweepMax.X < node->MinX)) {
return false;
}
if ((boxtest.SweepMin.Y > node->MaxY) || (boxtest.SweepMax.Y < node->MinY)) {
return false;
}
if ((boxtest.SweepMin.Z > node->MaxZ) || (boxtest.SweepMax.Z < node->MinZ)) {
return false;
}
if (node->Front) {
Cast_AABox_Recursive(node->Front,boxtest);
}
Cast_AABox_To_Polys(node,boxtest);
if (node->Back) {
Cast_AABox_Recursive(node->Back,boxtest);
}
return false;
}
bool BptImpClass::Cast_OBBox_Recursive
(
BptImpNodeClass * node,
PhysOBBoxCollisionTestClass & boxtest
) const
{
return false;
}
bool BptImpClass::Cast_AABox_To_Polys(BptImpNodeClass * node,PhysAABoxCollisionTestClass & boxtest) const
{
/*
** Simply loop through the polys in this node, checking each for collision
*/
TriClass tri;
srMeshModel * srmesh = Mesh->Get_Surrender_Mesh();
srVector3 * loc = srmesh->getVertexLoc();
srVector4 * norms = srmesh->getPolyEq();
srVector3i * polyverts = srmesh->getPolyVertex();
int polyhit = -1;
for (int poly_counter=0; poly_counter<node->PolyCount; poly_counter++) {
int poly_index = PolyIndices[node->FirstPoly + poly_counter];
tri.V[0] = As_Vector3(&(loc[ polyverts[poly_index][0] ]));
tri.V[1] = As_Vector3(&(loc[ polyverts[poly_index][1] ]));
tri.V[2] = As_Vector3(&(loc[ polyverts[poly_index][2] ]));
tri.N = (Vector3 *)&(norms[poly_index]);
if ( boxtest.Box.Cast_To_Tri(boxtest.Move,tri,boxtest.Result) ) {;
polyhit = poly_index;
}
if (boxtest.Result->StartBad) {
return true;
}
}
if (polyhit != -1) {
return true;
}
return false;
}
void BptImpClass::Verify_Node_Bounding_Volume(BptImpNodeClass * node,const Vector3 & min,const Vector3 & max) const
{
srMeshModel * srmesh = Mesh->Get_Surrender_Mesh();
srVector3 * loc = srmesh->getVertexLoc();
srVector3i * polyverts = srmesh->getPolyVertex();
Vector3 * v;
for (int remap_index = node->FirstPoly; remap_index < node->FirstPoly + node->PolyCount; remap_index++) {
int poly_index = PolyIndices[remap_index];
v = As_Vector3(&loc[ polyverts[poly_index][0] ]);
assert(v->X >= min.X);
assert(v->Y >= min.Y);
assert(v->Z >= min.Z);
assert(v->X <= max.X);
assert(v->Y <= max.Y);
assert(v->Z <= max.Z);
v = As_Vector3(&loc[ polyverts[poly_index][1] ]);
assert(v->X >= min.X);
assert(v->Y >= min.Y);
assert(v->Z >= min.Z);
assert(v->X <= max.X);
assert(v->Y <= max.Y);
assert(v->Z <= max.Z);
v = As_Vector3(&loc[ polyverts[poly_index][2] ]);
assert(v->X >= min.X);
assert(v->Y >= min.Y);
assert(v->Z >= min.Z);
assert(v->X <= max.X);
assert(v->Y <= max.Y);
assert(v->Z <= max.Z);
}
if (node->Back) {
Verify_Node_Bounding_Volume(node->Back,min,max);
}
if (node->Front) {
Verify_Node_Bounding_Volume(node->Front,min,max);
}
}
/**************************************************************
BptImpBuilderClass Implementation
**************************************************************/
BptImpBuilderClass::BptImpBuilderClass(void) :
MeshBuilder(true,10000,5000), // TODO: should move this to Build_Bpt_Imp so it is dynamically initialzed
UniqueNormals(NULL),
UniqueDistances(NULL),
NodeCount(0),
Nodes(NULL),
TriCount(0),
CurTriIndex(0),
TriIndexArray(NULL)
{
}
BptImpBuilderClass::~BptImpBuilderClass(void)
{
Free();
}
void BptImpBuilderClass::Free(void)
{
if (UniqueNormals != NULL) {
delete UniqueNormals;
UniqueNormals = NULL;
}
if (UniqueDistances != NULL) {
delete UniqueDistances;
UniqueDistances = NULL;
}
NodeCount = 0;
if (Nodes != NULL) {
delete[] Nodes;
Nodes = NULL;
}
TriCount = 0;
CurTriIndex = 0;
if (TriIndexArray != NULL) {
delete[] TriIndexArray;
TriIndexArray = NULL;
}
}
BptImpClass * BptImpBuilderClass::Build_Bpt_Imp(BptNodeClass * root,MaterialInfoClass * matinfo)
{
Free();
WWDEBUG_SAY(("Building the Run-Time Bpt\r\n"));
TriCount = root->Num_Tris();
/*
** Allocate the UniqueNormals and UniqueDistances objects
*/
int size = TriCount;
int growth = TriCount / 3;
if (growth < 5) growth = 5;
UniqueNormals = new UniqueArrayClass<Vector3>(size,growth,&NormalHasher);
UniqueDistances = new UniqueArrayClass<float>(size,growth,&DistanceHasher);
/*
** Set up the mesh builder
*/
MeshBuilder.Reset(true,TriCount,growth);
MeshBuilder.Disable_All_Vertex_Channels();
MeshBuilder.Enable_Vertex_Channel(MeshBuilderClass::VERTEX_CHANNEL_NORMAL);
// MeshBuilder.Enable_Vertex_Channel(MeshBuilderClass::VERTEX_CHANNEL_COLOR);
MeshBuilder.Enable_Vertex_Channel(MeshBuilderClass::VERTEX_CHANNEL_TEXCOORD);
/*
** Allocate the array of triangle indices, set
** CurTriIndex to point to the top.
*/
TriIndexArray = new int[TriCount];
CurTriIndex = 0;
/*
** Allocate the array of nodes
*/
NodeCount = root->Num_Nodes();
Nodes = new BptImpNodeClass[NodeCount];
/*
** Compute the hierarchical bounding boxes
*/
WWDEBUG_SAY(("Computing Hierarchical Bounding Volumes.\r\n"));
root->Compute_Bounding_Box();
WWDEBUG_SAY(("Hierarchical Bounding Volumes Done.\r\n"));
/*
** Have the tree "unwind" itself into our arrays
*/
root->Assign_Array_Indices(0);
root->Submit_Nodes(*this);
/*
** Process the mesh
*/
MeshBuilder.Build_Mesh(true);
/*
** Remap all of the polygon indices (account for the
** mesh builder re-ording polygons)
*/
Remap_Triangle_Indices();
/*
** Create the BptImpClass
*/
MeshClass * mesh = new MeshClass();
mesh->Init(MeshBuilder,matinfo,"BspMesh","");
BptImpClass * bptimp = new BptImpClass();
bptimp->Set_Mesh(mesh);
bptimp->Set_Node_Array(NodeCount,Nodes);
bptimp->Set_Poly_Index_Array(TriCount,TriIndexArray);
bptimp->Set_Normal_Array(*UniqueNormals);
bptimp->Set_Distance_Array(*UniqueDistances);
// the "imp" assumed ownership of this stuff
mesh->Release_Ref();
mesh = NULL;
Nodes = NULL;
TriIndexArray = NULL;
Free();
WWDEBUG_SAY(("Run-Time Bpt Done\r\n"));
WWDEBUG_SAY(("\r\n"));
return bptimp;
}
void BptImpBuilderClass::Add_Bpt_Node(const BptNodeClass * node)
{
BptImpNodeClass newnode;
/*
** initialize the front pointer
*/
if (node->Front) {
assert(node->Front->ArrayIndex >= 0);
assert(node->Front->ArrayIndex < NodeCount);
newnode.Front = &(Nodes[node->Front->ArrayIndex]);
} else {
newnode.Front = NULL; //BptImpNodeClass::NULL_NODE_INDEX;
}
/*
** initialize the back pointer
*/
if (node->Back) {
assert(node->Back->ArrayIndex >= 0);
assert(node->Back->ArrayIndex < NodeCount);
newnode.Back = &(Nodes[node->Back->ArrayIndex]);
} else {
newnode.Back = NULL; //BptImpNodeClass::NULL_NODE_INDEX;
}
/*
** Triangulate each poly and submit it to the mesh builder
** install the Triangle Remap Indices as we go
*/
newnode.FirstPoly = CurTriIndex;
newnode.PolyCount = 0;
MeshBuilderClass::FaceClass face;
for (unsigned int fi=0; fi<node->NumPolys; fi++) {
BptPolyClass * poly = &(node->Polys[fi]);
for (int ti = 2;ti < poly->NumVerts; ti++) {
face.MaterialIdx = poly->MaterialId;
face.SmGroup = 1; //TODO: get the correct smooth group???
face.Index = 0; //TODO: do we have an actual index?
face.Attributes = 0;
face.Verts[0].Position = poly->Verts[0].Position;
face.Verts[0].TexCoord = poly->Verts[0].TexCoord;
face.Verts[0].Color = poly->Verts[0].Color;
face.Verts[1].Position = poly->Verts[ti-1].Position;
face.Verts[1].TexCoord = poly->Verts[ti-1].TexCoord;
face.Verts[1].Color = poly->Verts[ti-1].Color;
face.Verts[2].Position = poly->Verts[ti].Position;
face.Verts[2].TexCoord = poly->Verts[ti].TexCoord;
face.Verts[2].Color = poly->Verts[ti].Color;
TriIndexArray[CurTriIndex] = MeshBuilder.Add_Face(face);
CurTriIndex++;
newnode.PolyCount++;
/*
** just verifying that all verts are inside the bounding box
*/
assert(face.Verts[0].Position.X >= node->Min.X);
assert(face.Verts[0].Position.Y >= node->Min.Y);
assert(face.Verts[0].Position.Z >= node->Min.Z);
assert(face.Verts[1].Position.X >= node->Min.X);
assert(face.Verts[1].Position.Y >= node->Min.Y);
assert(face.Verts[1].Position.Z >= node->Min.Z);
assert(face.Verts[2].Position.X >= node->Min.X);
assert(face.Verts[2].Position.Y >= node->Min.Y);
assert(face.Verts[2].Position.Z >= node->Min.Z);
assert(face.Verts[0].Position.X <= node->Max.X);
assert(face.Verts[0].Position.Y <= node->Max.Y);
assert(face.Verts[0].Position.Z <= node->Max.Z);
assert(face.Verts[1].Position.X <= node->Max.X);
assert(face.Verts[1].Position.Y <= node->Max.Y);
assert(face.Verts[1].Position.Z <= node->Max.Z);
assert(face.Verts[2].Position.X <= node->Max.X);
assert(face.Verts[2].Position.Y <= node->Max.Y);
assert(face.Verts[2].Position.Z <= node->Max.Z);
}
}
/*
** Bounding box
*/
newnode.MinX = node->Min.X;
newnode.MinY = node->Min.Y;
newnode.MinZ = node->Min.Z;
newnode.MaxX = node->Max.X;
newnode.MaxY = node->Max.Y;
newnode.MaxZ = node->Max.Z;
assert(newnode.MinX <= newnode.MaxX);
assert(newnode.MinY <= newnode.MaxY);
assert(newnode.MinZ <= newnode.MaxZ);
/*
** Plane Equation
*/
assert(UniqueNormals);
assert(UniqueDistances);
newnode.NormalIndex = UniqueNormals->Add(node->Plane.N);
newnode.DistanceIndex = UniqueDistances->Add(node->Plane.D);
/*
** install the node!
*/
Nodes[node->ArrayIndex] = newnode;
}
void BptImpBuilderClass::Remap_Triangle_Indices(void)
{
int pi;
int * index_remap = new int[MeshBuilder.Get_Face_Count()];
assert(MeshBuilder.Get_Face_Count() == TriCount);
for (pi=0; pi < MeshBuilder.Get_Face_Count(); pi++) {
const MeshBuilderClass::FaceClass & face = MeshBuilder.Get_Face(pi);
assert(face.AddIndex >= 0);
assert(face.AddIndex < TriCount);
index_remap[ face.AddIndex ] = pi;
}
for (pi=0; pi < TriCount; pi++) {
TriIndexArray[pi] = index_remap[TriIndexArray[pi]];
}
delete[] index_remap;
}
#endif // PORT140
#endif // OBSOLETE!