Descent3/Descent3/bsp.cpp
2024-09-24 23:03:02 +03:00

1208 lines
33 KiB
C++

/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* 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/>.
--- HISTORICAL COMMENTS FOLLOW ---
* $Logfile: /DescentIII/main/bsp.cpp $
* $Revision: 32 $
* $Date: 4/19/00 10:18a $
* $Author: Matt $
*
* Some more of BSP tree code, I presume.
*
* $Log: /DescentIII/main/bsp.cpp $
*
* 32 4/19/00 10:18a Matt
* Added check for BSP tree initialized before freeing it.
*
* 31 4/21/99 11:05a Kevin
* new ps_rand and ps_srand to replace rand & srand
*
* 30 1/21/99 11:15p Jeff
* pulled out some structs and defines from header files and moved them
* into separate header files so that multiplayer dlls don't require major
* game headers, just those new headers. Side effect is a shorter build
* time. Also cleaned up some header file #includes that weren't needed.
* This affected polymodel.h, object.h, player.h, vecmat.h, room.h,
* manage.h and multi.h
*
* 29 9/22/98 12:01p Matt
* Added SourceSafe headers
*
*/
#include <cstdlib>
#include "bsp.h"
#include "room.h"
#include "log.h"
#include "mem.h"
#include "polymodel.h"
#include "object.h"
#include "pserror.h"
#include "psrand.h"
#define BSP_TREE_VERSION 10003
bsptree MineBSP;
uint8_t BSP_initted = 0;
static int ConvexSubspaces = 0, ConvexPolys = 0;
static int Solids = 0, Empty = 0;
int BSPChecksum = -1;
// TODO: MTS: Only used here?
uint8_t UseBSP = 0;
// Goes through all the valid points in the indoor engine and returns a unique
// checksum
int BSPGetMineChecksum() {
int i, t, k;
float total = 0;
int checksum;
for (i = 0; i <= Highest_room_index; i++) {
room *rp = &Rooms[i];
if (!Rooms[i].used)
continue;
if (Rooms[i].flags & RF_EXTERNAL)
continue;
for (t = 0; t < rp->num_faces; t++) {
face *fp = &rp->faces[t];
for (k = 0; k < fp->num_verts; k++) {
total += rp->verts[fp->face_verts[k]].x;
total += rp->verts[fp->face_verts[k]].y;
total += rp->verts[fp->face_verts[k]].z;
}
}
}
for (t = 0; t <= Highest_object_index; t++) {
object *obj = &Objects[t];
vector vert;
if (obj->type == OBJ_NONE)
continue;
if (OBJECT_OUTSIDE(obj))
continue;
if (obj->lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[obj->rtype.pobj_info.model_num];
if (!po->new_style)
continue;
for (k = 0; k < po->n_models; k++) {
bsp_info *sm = &po->submodel[k];
if (IsNonRenderableSubmodel(po, k))
continue;
for (int j = 0; j < sm->num_faces; j++) {
for (int x = 0; x < sm->faces[j].nverts; x++) {
GetObjectPointInWorld(&vert, obj, k, sm->faces[j].vertnums[x]);
total += vert.x;
total += vert.y;
total += vert.z;
}
}
}
}
}
checksum = total * BSP_TREE_VERSION;
return checksum;
}
// Allocates memory for a bspnode
// Returns pointer to node
bspnode *NewBSPNode(void) {
bspnode *node;
if ((node = mem_rmalloc<bspnode>()) == NULL) {
return NULL;
}
/* Initialize the node, set its variables to zero */
node->type = BSP_NODE;
node->polylist = NULL;
node->num_polys = 0;
node->node_facenum = 0;
node->node_roomnum = 0;
node->node_subnum = 0;
node->plane.a = 0.0;
node->plane.b = 0.0;
node->plane.c = 0.0;
node->plane.d = 0.0;
node->front = NULL;
node->back = NULL;
return node;
}
// Allocates memory for a polygon
// Returns pointer to polygon
bsppolygon *NewPolygon(int roomnum, int facenum, int numverts) {
bsppolygon *newpoly;
vector *verts;
newpoly = mem_rmalloc<bsppolygon>();
if (newpoly == NULL) {
LOG_ERROR << "NewPolygon: Couldn't allocate polygon";
return NULL;
}
verts = mem_rmalloc<vector>(numverts);
ASSERT(verts != NULL);
newpoly->nv = numverts;
newpoly->verts = verts;
newpoly->roomnum = roomnum;
newpoly->facenum = facenum;
newpoly->color = ps_rand() * ps_rand();
return newpoly;
}
// Saves and BSP node to an open file and recurses with the nodes children
void SaveBSPNode(CFILE *outfile, bspnode *node) {
if (node->type == BSP_SOLID_LEAF) {
cf_WriteByte(outfile, BSP_SOLID_LEAF);
} else if (node->type == BSP_EMPTY_LEAF) {
cf_WriteByte(outfile, BSP_EMPTY_LEAF);
} else {
ASSERT(node->front);
ASSERT(node->back);
cf_WriteByte(outfile, BSP_NODE);
// Write out this planes node
cf_WriteFloat(outfile, node->plane.a);
cf_WriteFloat(outfile, node->plane.b);
cf_WriteFloat(outfile, node->plane.c);
cf_WriteFloat(outfile, node->plane.d);
cf_WriteShort(outfile, node->node_roomnum);
cf_WriteShort(outfile, node->node_facenum);
cf_WriteByte(outfile, node->node_subnum);
// Write out its children
SaveBSPNode(outfile, node->front);
SaveBSPNode(outfile, node->back);
}
}
// Loads a bsp node from an open file and recurses with its children
void LoadBSPNode(CFILE *infile, bspnode **node) {
// Get the node type
uint8_t type = cf_ReadByte(infile);
// Allocate the node and set its type
*node = NewBSPNode();
ASSERT(*node);
bspnode *localnode = *node;
localnode->type = type;
if (type == BSP_EMPTY_LEAF || type == BSP_SOLID_LEAF)
return;
// Load the nodes plane
localnode->plane.a = cf_ReadFloat(infile);
localnode->plane.b = cf_ReadFloat(infile);
localnode->plane.c = cf_ReadFloat(infile);
localnode->plane.d = cf_ReadFloat(infile);
localnode->node_roomnum = cf_ReadShort(infile);
localnode->node_facenum = cf_ReadShort(infile);
localnode->node_subnum = cf_ReadByte(infile);
// Recurse to get the children
LoadBSPNode(infile, &localnode->front);
LoadBSPNode(infile, &localnode->back);
}
// Frees up a polygon and all the vertices associated with it
void FreePolygon(bsppolygon *poly) {
ASSERT(poly != NULL);
ASSERT(poly->nv > 2);
if (poly->nv > 0) {
mem_free(poly->verts);
}
mem_free(poly);
}
// Calculates the plane equation for a given polygon
void CalculatePolygonPlane(bsppolygon *poly) {
ASSERT(poly->nv >= 3);
vector *vec = &poly->verts[0];
poly->plane.d = -(vec->x * poly->plane.a + vec->y * poly->plane.b + vec->z * poly->plane.c);
}
// Classifies whether or not a point is in front,behind, or on a plane
int ClassifyVector(bspplane *plane, vector *vec) {
float dist;
dist = vec->x * plane->a + vec->y * plane->b + vec->z * plane->c + plane->d;
if (dist > BSP_EPSILON)
return BSP_IN_FRONT;
else if (dist < -BSP_EPSILON)
return BSP_BEHIND;
return BSP_ON_PLANE;
}
// Classifies whether or not a polygon is behind,in front, or straddles a plane
int ClassifyPolygon(bspplane *plane, bsppolygon *poly) {
int numfront, numback, numon;
int i;
int nv = poly->nv;
ASSERT(nv >= 3);
numfront = numback = numon = 0;
for (i = 0; i < nv; i++) {
int fate;
vector *vec = &poly->verts[i];
fate = ClassifyVector(plane, vec);
switch (fate) {
case BSP_IN_FRONT:
numfront++;
break;
case BSP_BEHIND:
numback++;
break;
case BSP_ON_PLANE:
numfront++;
numback++;
numon++;
break;
}
}
if (numon == nv) {
return BSP_COINCIDENT;
} else if (numfront == nv) {
return BSP_IN_FRONT;
} else if (numback == nv) {
return BSP_BEHIND;
}
// The polygon straddles the plane
return BSP_SPANNING;
}
// Tries to split a polygon across a plane
int SplitPolygon(bspplane *plane, bsppolygon *testpoly, bsppolygon **frontpoly, bsppolygon **backpoly) {
float dists[256], t;
int numvert, numfront, numback, i, codes[256] = {};
vector *frontvert[256], *backvert[256], *polyvert[256];
vector *vertptr1, *vertptr2;
vector delta, *newvert[256];
int num_new_verts = 0;
/* Set up the function. Set all counters, variables, lists etc to
* zero or NULL, which provides a fall back result, should nothing
* be changed, and simply means that particular result is to be
* ignored
*/
numvert = testpoly->nv;
numfront = numback = 0;
/* Now, we shall classify each vertex in the polygon, with both
* a plane distance, and a classification code. This is done here,
* as the results are often re-used, which saves time.
*/
for (i = 0; i < numvert; i++) {
vertptr1 = &testpoly->verts[i];
polyvert[i] = vertptr1;
codes[i] = ClassifyVector(plane, vertptr1);
dists[i] = plane->a * vertptr1->x + plane->b * vertptr1->y + plane->c * vertptr1->z + plane->d;
}
/* We must duplicate the first entry in the numvert+1 slot, so that
* we can use wraparound to easily check things
*/
codes[numvert] = codes[0];
dists[numvert] = dists[0];
/* Now, the actual splitting work. We must work through each vertex
* of the polygon, examining it with regard to the plane
* Where the classification codes differ, we have to split
*/
for (i = 0; i < numvert; i++) {
vertptr1 = polyvert[i];
/* A point is found to be on the plane. This counts for
* both polygons, as often a vertex may lie along a plane,
* but will not be splitting it
*/
if (codes[i] == BSP_ON_PLANE) {
frontvert[numfront++] = vertptr1;
backvert[numback++] = vertptr1;
} else if (codes[i] == BSP_IN_FRONT) {
/* Simple cases of point being in front or behind
* the plane. Just insert them to the appropriate
* lists.
*/
frontvert[numfront++] = vertptr1;
} else if (codes[i] == BSP_BEHIND) {
backvert[numback++] = vertptr1;
}
/* If we're on the plane, or, the next point is the same
* side of the plane as this point, go to the next vertex
*/
if ((codes[i] == BSP_ON_PLANE) || (codes[i] == codes[i + 1]))
continue;
/* Now, we need to generate a split point. This is just
* standard ray-plane intersection stuff. Now this is where
* pre-calculating the distances etc comes in handy, as
* we're using them a lot...
*/
vertptr2 = polyvert[(i + 1) % numvert];
t = dists[i] / (dists[i] - dists[i + 1]);
newvert[num_new_verts] = mem_rmalloc<vector>();
/* Now we must generate the split point. This is simply
* an equation in the form Origin + t*Direction
*/
delta = *vertptr2 - *vertptr1;
*newvert[num_new_verts] = *vertptr1 + (t * delta);
/* vector tempvec=*newvert[num_new_verts]-*vertptr1;
float mag=vm_GetMagnitude(&tempvec);
if (mag<=BSP_EPSILON)
Int3(); */
frontvert[numfront++] = newvert[num_new_verts];
backvert[numback++] = newvert[num_new_verts];
num_new_verts++;
}
/* Now, we must construct the polygons, given the vertex lists
* If the number of vertices is invalid, we don't do anything ...
*/
ASSERT(numfront >= 3);
ASSERT(numback >= 3);
(*frontpoly) = NewPolygon(testpoly->roomnum, testpoly->facenum, numfront);
(*backpoly) = NewPolygon(testpoly->roomnum, testpoly->facenum, numback);
for (i = 0; i < numfront; i++) {
bsppolygon *temppoly = *frontpoly;
temppoly->verts[i] = *frontvert[i];
temppoly->plane = testpoly->plane;
temppoly->facenum = testpoly->facenum;
temppoly->roomnum = testpoly->roomnum;
temppoly->subnum = testpoly->subnum;
}
for (i = 0; i < numback; i++) {
bsppolygon *temppoly = *backpoly;
temppoly->verts[i] = *backvert[i];
temppoly->plane = testpoly->plane;
temppoly->facenum = testpoly->facenum;
temppoly->roomnum = testpoly->roomnum;
temppoly->subnum = testpoly->subnum;
}
for (i = 0; i < num_new_verts; i++)
mem_free(newvert[i]);
return 1;
}
char Twirly[] = {'|', '/', '-', '\\'};
uint8_t Plane_twirl = 0;
uint8_t Node_twirl = 0;
// Selects the best plane to partition with, returning the pointer to polygon to split with
bsppolygon *SelectPlane(listnode **polylist) {
listnode *outer, *inner;
bsppolygon *outerpoly, *innerpoly, *bestpoly;
int splits, front, back, planar, result, balance;
float score, bestscore;
float g_kbalance = 1.0;
float g_ksplit = 0.0;
float g_kplanar = 0.0;
int checked = 0;
bestscore = 9999999.0;
bestpoly = NULL;
/* Iterate through every polygon, checking its plane against all
* the other polygons. But don't check it against itself
*/
for (outer = *polylist; outer != NULL; outer = outer->next) {
outerpoly = (bsppolygon *)outer->data;
splits = front = back = planar = 0;
score = 0.0;
if (outerpoly->plane.used)
continue;
// Only check 300 times
if (checked >= 300)
return bestpoly;
for (inner = *polylist; inner != NULL; inner = inner->next) {
innerpoly = (bsppolygon *)inner->data;
if (innerpoly != outerpoly) {
result = ClassifyPolygon(&outerpoly->plane, innerpoly);
switch (result) {
case BSP_SPANNING:
splits++;
break;
case BSP_IN_FRONT:
front++;
break;
case BSP_BEHIND:
back++;
break;
case BSP_COINCIDENT:
planar++;
break;
}
}
}
/* Calculate the score. If we have a "perfect" tree, ie
* it has either no splits, or is perfectly balanced,
* return the plane to the caller
*/
balance = abs(front - back);
score = g_kbalance * balance + g_ksplit * splits + g_kplanar * planar;
checked++;
if (score < 0.005)
return outerpoly;
if (score < bestscore) {
bestscore = score;
bestpoly = outerpoly;
}
}
return bestpoly;
}
// This is the routine that recursively builds the bsptree
int BuildBSPNode(bspnode *tree, listnode **polylist, int numpolys) {
bsppolygon *partition_poly;
listnode *lnode = *polylist;
listnode *next;
bspnode *frontnode, *backnode;
listnode *frontlist = NULL, *backlist = NULL;
bspplane partition_plane;
int numfront = 0, numback = 0, numsplits = 0;
ASSERT(numpolys > 0);
partition_poly = SelectPlane(polylist);
if (partition_poly == NULL) {
// We hit a leaf! Fill in the appropriate leaf stuff
/*vector center;
int total=0;
vm_MakeZero (&center);
for (int i=0;i<numpolys;i++)
{
bsppolygon *poly=(bsppolygon *)GetListItem (polylist,i);
ASSERT (poly!=NULL);
AddListItem (&tree->polylist,poly);
tree->num_polys++;
vector polycenter;
ConvexPolys++;
vm_MakeZero (&polycenter);
// Find out if this is a solid or empty leaf
// This routine may cause some problems because of convex subspace
for (int t=0;t<poly->nv;t++)
{
polycenter+=poly->verts[t];
}
polycenter/=poly->nv;
center+=polycenter;
}
center/=numpolys;
int ins=0,outs=0;
for (i=0;i<numpolys;i++)
{
bsppolygon *thispoly=(bsppolygon *)GetListItem (polylist,i);
int fate=ClassifyVector (&thispoly->plane,&center);
if (fate==BSP_IN_FRONT || fate==BSP_ON_PLANE)
{
outs++;
}
else
ins++;
}
if (ins==numpolys)
{
tree->type=BSP_SOLID_LEAF;
Solids++;
}
else if (outs==numpolys)
{
tree->type=BSP_EMPTY_LEAF;
Empty++;
}
else
{
mprintf(0,"Ins/outs don't match! ins=%d outs=%d np=%d\n",ins,outs,numpolys);
for (i=0;i<numpolys;i++)
{
bsppolygon *thispoly=(bsppolygon *)GetListItem (polylist,i);
mprintf(0,"Poly %d: Room=%d facenum=%d\n",i,thispoly->roomnum,thispoly->facenum);
}
if (ins>outs)
{
tree->type=BSP_SOLID_LEAF;
Solids++;
}
else
{
tree->type=BSP_EMPTY_LEAF;
Empty++;
}
}*/
tree->type = BSP_EMPTY_LEAF;
ConvexSubspaces++;
return 1;
}
// We need to process this node and classify all child polygons
tree->node_facenum = partition_poly->facenum;
tree->node_roomnum = partition_poly->roomnum;
tree->node_subnum = partition_poly->subnum;
partition_poly->plane.used = 1;
tree->plane = partition_poly->plane;
partition_plane = partition_poly->plane;
frontlist = backlist = NULL;
for (lnode = *polylist; lnode != NULL; lnode = next) {
next = lnode->next;
bsppolygon *testpoly = (bsppolygon *)lnode->data;
int fate = ClassifyPolygon(&partition_plane, testpoly);
if (fate == BSP_IN_FRONT) {
AddListItem(&frontlist, testpoly);
numfront++;
} else if (fate == BSP_BEHIND) {
AddListItem(&backlist, testpoly);
numback++;
} else if (fate == BSP_COINCIDENT) {
// Test to see if this plane is exactly the same as the partition plane
// If so, remove it from the list. If it is facing the other way, send it down the back list.
float dot = testpoly->plane.a * partition_plane.a + testpoly->plane.b * partition_plane.b +
testpoly->plane.c * partition_plane.c;
if (dot > -BSP_EPSILON) {
// Send down front list and deactivate this plane
AddListItem(&frontlist, testpoly);
numfront++;
// testpoly->plane.used=1;
} else {
AddListItem(&frontlist, testpoly);
// testpoly->plane.used=1;
numfront++;
}
} else {
// Must split this polygon
ASSERT(fate == BSP_SPANNING);
bsppolygon *frontpoly, *backpoly;
numsplits++;
SplitPolygon(&partition_plane, testpoly, &frontpoly, &backpoly);
numfront++;
numback++;
AddListItem(&frontlist, frontpoly);
AddListItem(&backlist, backpoly);
RemoveListItem(polylist, testpoly);
FreePolygon(testpoly);
}
}
if (frontlist != NULL) {
if ((frontnode = NewBSPNode()) == NULL) {
LOG_ERROR << "BuildBSPNode: Error, can't allocate front node";
return 0;
}
if (!BuildBSPNode((bspnode *)frontnode, &frontlist, numfront)) {
LOG_ERROR << "BuildBSPNode: Error building front node";
return 0;
}
tree->front = frontnode;
DestroyList(&frontlist);
} else {
if ((frontnode = NewBSPNode()) == NULL) {
LOG_ERROR << "BuildBSPNode: Error, can't allocate front node";
return 0;
}
frontnode->type = BSP_EMPTY_LEAF;
tree->front = frontnode;
}
if (backlist != NULL) {
if ((backnode = NewBSPNode()) == NULL) {
LOG_ERROR << "BuildBSPNode: Error, can't allocate front node";
return 0;
}
if (!BuildBSPNode((bspnode *)backnode, &backlist, numback)) {
LOG_ERROR << "BuildBSPNode: Error building back node";
return 0;
}
tree->back = backnode;
DestroyList(&backlist);
} else {
if ((backnode = NewBSPNode()) == NULL) {
LOG_ERROR << "BuildBSPNode: Error, can't allocate front node";
return 0;
}
backnode->type = BSP_SOLID_LEAF;
tree->back = backnode;
}
return 1;
}
// Releases memory for a bspnode. This function calls itself recursively
void DestroyBSPNode(bspnode *node) {
if (node->type == BSP_NODE) {
if (node->front)
DestroyBSPNode(node->front);
if (node->back)
DestroyBSPNode(node->back);
}
int np = CountListItems(&node->polylist);
for (int i = 0; i < np; i++) {
bsppolygon *poly = (bsppolygon *)GetListItem(&node->polylist, i);
FreePolygon(poly);
}
if (np > 0)
DestroyList(&node->polylist);
mem_free(node);
}
// Walks the BSP tree and frees up any nodes/polygons that we might be using
void DestroyBSPTree(bsptree *tree) {
if (BSP_initted == 0)
return;
LOG_INFO << "Destroying bsptree!";
DestroyBSPNode(tree->root);
BSP_initted = 0;
}
// Destroys the mines BSP tree at shutdown
void DestroyDefaultBSPTree() { DestroyBSPTree(&MineBSP); }
// Given an object, a submodel, and a vertex number, calculates the world position
// of that point
void GetObjectPointInWorld(vector *dest, object *obj, int subnum, int vertnum);
// Builds a bsp tree for the indoor rooms
void BuildBSPTree() {
int i, t, k, j, x;
int numpolys = 0;
int check;
if (!UseBSP)
return;
// Check to see if we even need to build a new tree
check = BSPGetMineChecksum();
if (check == BSPChecksum) {
LOG_INFO << "BSP tree already built!";
return; // The BSP tree has already been built for this mine
}
BSPChecksum = check;
// Free up any BSP trees that we might have lying around
InitDefaultBSP();
MineBSP.root = NewBSPNode();
ASSERT(MineBSP.root);
LOG_INFO << "Building BSP Tree...";
LOG_DEBUG << "Adding polygons to tree";
// Go through the whole mine and add each polygon to the possible BSP
// partition list. Don't include portals...
for (i = 0; i <= Highest_room_index; i++) {
bsppolygon *newpoly;
if (Rooms[i].used == 0 || (Rooms[i].flags & RF_EXTERNAL))
continue;
room *rp = &Rooms[i];
for (t = rp->objects; (t != -1); t = Objects[t].next) {
object *obj = &Objects[t];
if (obj->type == OBJ_NONE)
continue;
if (OBJECT_OUTSIDE(obj))
continue;
if (obj->lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[obj->rtype.pobj_info.model_num];
if (!po->new_style)
continue;
for (k = 0; k < po->n_models; k++) {
bsp_info *sm = &po->submodel[k];
if (IsNonRenderableSubmodel(po, k))
continue;
for (j = 0; j < sm->num_faces; j++) {
vector world_verts[64];
newpoly = NewPolygon(i, t, sm->faces[j].nverts);
if (!newpoly) {
LOG_FATAL << "Couldn't get a new polygon!";
Int3();
return;
}
ASSERT(sm->faces[j].nverts < 64);
for (x = 0; x < sm->faces[j].nverts; x++)
GetObjectPointInWorld(&world_verts[x], obj, k, sm->faces[j].vertnums[x]);
vector norm;
vm_GetNormal(&norm, &world_verts[0], &world_verts[1], &world_verts[2]);
// Add the vertices to this poly
for (x = 0; x < sm->faces[j].nverts; x++)
newpoly->verts[x] = world_verts[x];
newpoly->plane.a = norm.x;
newpoly->plane.b = norm.y;
newpoly->plane.c = norm.z;
CalculatePolygonPlane(newpoly);
// Add it to our bsp list
AddListItem(&MineBSP.polylist, newpoly);
newpoly->plane.used = 0;
numpolys++;
newpoly->roomnum = t;
newpoly->facenum = j;
newpoly->subnum = k;
}
}
}
}
for (t = 0; t < Rooms[i].num_faces; t++) {
// Don't include portals in BSP calculation
// Unless they go outside
if (Rooms[i].faces[t].portal_num != -1) {
if (Rooms[i].portals[Rooms[i].faces[t].portal_num].croom >= 0)
continue;
}
// Construct a new polygon
newpoly = NewPolygon(i, t, Rooms[i].faces[t].num_verts);
if (!newpoly) {
LOG_FATAL << "Couldn't get a new polygon!";
Int3();
return;
}
// Add the vertices to this poly
for (k = 0; k < Rooms[i].faces[t].num_verts; k++)
newpoly->verts[k] = Rooms[i].verts[Rooms[i].faces[t].face_verts[k]];
newpoly->plane.a = Rooms[i].faces[t].normal.x;
newpoly->plane.b = Rooms[i].faces[t].normal.y;
newpoly->plane.c = Rooms[i].faces[t].normal.z;
CalculatePolygonPlane(newpoly);
// Add it to our bsp list
AddListItem(&MineBSP.polylist, newpoly);
newpoly->plane.used = 0;
newpoly->roomnum = i;
newpoly->facenum = t;
newpoly->subnum = -1;
numpolys++;
}
}
ConvexSubspaces = 0;
ConvexPolys = 0;
Solids = 0;
Empty = 0;
LOG_DEBUG.printf("%d polygons added, starting node building...", numpolys);
// Build the BSP tree!
BuildBSPNode(MineBSP.root, &MineBSP.polylist, numpolys);
DestroyList(&MineBSP.polylist);
// Print some stats
LOG_DEBUG.printf("Total number of convex subspaces=%d", ConvexSubspaces);
LOG_DEBUG.printf("Total number of convex polys=%d", ConvexPolys);
LOG_DEBUG.printf("Solid=%d Empty=%d", Solids, Empty);
}
// Builds a bsp tree for a single room
void BuildSingleBSPTree(int roomnum) {
int i, t, k, j, x;
int numpolys = 0;
if (!UseBSP)
return;
// Free up any BSP trees that we might have lying around
InitDefaultBSP();
MineBSP.root = NewBSPNode();
ASSERT(MineBSP.root);
LOG_INFO << "Building BSP Tree...";
// Go through the whole mine and add each polygon to the possible BSP
// partition list. Don't include portals...
i = roomnum;
bsppolygon *newpoly;
for (t = 0; t <= Highest_object_index; t++) {
object *obj = &Objects[t];
if (obj->type == OBJ_NONE)
continue;
if (OBJECT_OUTSIDE(obj))
continue;
if (obj->roomnum != i)
continue;
if (obj->lighting_render_type == LRT_LIGHTMAPS) {
poly_model *po = &Poly_models[obj->rtype.pobj_info.model_num];
if (!po->new_style)
continue;
for (k = 0; k < po->n_models; k++) {
bsp_info *sm = &po->submodel[k];
if (IsNonRenderableSubmodel(po, k))
continue;
for (j = 0; j < sm->num_faces; j++) {
vector world_verts[64];
newpoly = NewPolygon(i, t, sm->faces[j].nverts);
if (!newpoly) {
LOG_FATAL << "Couldn't get a new polygon!";
Int3();
return;
}
for (x = 0; x < sm->faces[j].nverts; x++)
GetObjectPointInWorld(&world_verts[x], obj, k, sm->faces[j].vertnums[x]);
vector norm;
vm_GetNormal(&norm, &world_verts[0], &world_verts[1], &world_verts[2]);
// Add the vertices to this poly
for (x = 0; x < sm->faces[j].nverts; x++)
newpoly->verts[x] = world_verts[x];
newpoly->plane.a = norm.x;
newpoly->plane.b = norm.y;
newpoly->plane.c = norm.z;
CalculatePolygonPlane(newpoly);
// Add it to our bsp list
AddListItem(&MineBSP.polylist, newpoly);
newpoly->plane.used = 0;
numpolys++;
}
}
}
}
for (t = 0; t < Rooms[i].num_faces; t++) {
// Don't include portals in BSP calculation
if (Rooms[i].faces[t].portal_num != -1)
continue;
// Construct a new polygon
newpoly = NewPolygon(i, t, Rooms[i].faces[t].num_verts);
if (!newpoly) {
LOG_FATAL << "Couldn't get a new polygon!";
Int3();
return;
}
// Add the vertices to this poly
for (k = 0; k < Rooms[i].faces[t].num_verts; k++)
newpoly->verts[k] = Rooms[i].verts[Rooms[i].faces[t].face_verts[k]];
newpoly->plane.a = Rooms[i].faces[t].normal.x;
newpoly->plane.b = Rooms[i].faces[t].normal.y;
newpoly->plane.c = Rooms[i].faces[t].normal.z;
CalculatePolygonPlane(newpoly);
// Add it to our bsp list
AddListItem(&MineBSP.polylist, newpoly);
newpoly->plane.used = 0;
numpolys++;
}
ConvexSubspaces = 0;
ConvexPolys = 0;
Solids = 0;
Empty = 0;
// Build the BSP tree!
BuildBSPNode(MineBSP.root, &MineBSP.polylist, numpolys);
DestroyList(&MineBSP.polylist);
// Print some stats
LOG_DEBUG.printf("Total number of convex subspaces=%d", ConvexSubspaces);
LOG_DEBUG.printf("Total number of convex polys=%d", ConvexPolys);
LOG_DEBUG.printf("Solid=%d Empty=%d", Solids, Empty);
BSPChecksum = -1;
}
// Returns true if the point is inside the min,max
static inline int BSPInMinMax(vector *pos, vector *min_xyz, vector *max_xyz) {
if (pos->x < min_xyz->x || pos->y < min_xyz->y || pos->z < min_xyz->z || pos->x > max_xyz->x || pos->y > max_xyz->y ||
pos->z > max_xyz->z)
return 0;
return 1;
}
// see if a point in inside a face by projecting into 2d
extern uint32_t check_point_to_face(vector *colp, vector *face_normal, int nv, vector **vertex_ptr_list);
// Returns true if passed in point collides with a nodes polygon
static inline int BSPPointInPolygon(vector *pos, bspnode *node) {
if (node->node_subnum < 0) // Room face
{
room *rp = &Rooms[node->node_roomnum];
face *fp = &rp->faces[node->node_facenum];
if (!(BSPInMinMax(pos, &fp->min_xyz, &fp->max_xyz)))
return 0;
// Test to see if this point in inside the polygon
vector verts[MAX_VERTS_PER_FACE], *vertp[MAX_VERTS_PER_FACE];
for (int i = 0; i < fp->num_verts; i++) {
verts[i] = rp->verts[fp->face_verts[i]];
vertp[i] = &verts[i];
}
if ((check_point_to_face(pos, &fp->normal, fp->num_verts, vertp)))
return 0;
return 1;
} else // Object face
{
object *obj = &Objects[node->node_roomnum];
if (!(BSPInMinMax(pos, &obj->min_xyz, &obj->max_xyz)))
return 0;
// Test to see if this point in inside the polygon
vector verts[MAX_VERTS_PER_FACE], *vertp[MAX_VERTS_PER_FACE];
vector norm;
poly_model *po = &Poly_models[obj->rtype.pobj_info.model_num];
bsp_info *sm = &po->submodel[node->node_subnum];
for (int i = 0; i < sm->faces[node->node_facenum].nverts; i++) {
GetObjectPointInWorld(&verts[i], obj, node->node_subnum, sm->faces[node->node_facenum].vertnums[i]);
vertp[i] = &verts[i];
}
norm.x = node->plane.a;
norm.y = node->plane.b;
norm.z = node->plane.c;
if ((check_point_to_face(pos, &norm, sm->faces[node->node_facenum].nverts, vertp)))
return 0;
return 1;
}
return 0;
}
// Runs a ray through the bsp tree
// Returns true if a ray is occludes
#define MAX_RAY_STACK 1000
#define PUSH_BSP_RAY(s, e, n) \
{ \
stack_start[si] = s; \
stack_end[si] = e; \
stack_node[si] = n; \
si++; \
ASSERT(si < MAX_RAY_STACK); \
}
#define POP_BSP_RAY() \
{ \
si--; \
start = stack_start[si]; \
end = stack_end[si]; \
node = stack_node[si]; \
}
vector stack_start[MAX_RAY_STACK], stack_end[MAX_RAY_STACK];
bspnode *stack_node[MAX_RAY_STACK];
static int si;
int BSPRayOccluded(vector *line_start, vector *line_end, bspnode *start_node) {
si = 0;
vector start, end;
bspnode *node;
PUSH_BSP_RAY(*line_start, *line_end, start_node);
while (si > 0) {
POP_BSP_RAY();
while (node->type == BSP_NODE) {
float dist1 = node->plane.a * start.x + node->plane.b * start.y + node->plane.c * start.z + node->plane.d;
float dist2 = node->plane.a * end.x + node->plane.b * end.y + node->plane.c * end.z + node->plane.d;
if (dist1 >= 0 && dist2 >= 0) {
node = (bspnode *)node->front;
} else if (dist1 < 0 && dist2 < 0) {
node = (bspnode *)node->back;
} else {
vector mid, delta;
float t;
// Generate split point
t = dist1 / (dist1 - dist2);
delta = end - start;
mid = start + (t * delta);
if (BSPPointInPolygon(&mid, node))
return 1;
// vm_NormalizeVectorFast (&delta);
vector mid1 = mid; //-(delta*BSP_EPSILON);
vector mid2 = mid; //+(delta*BSP_EPSILON);
if (dist1 >= 0.0) {
PUSH_BSP_RAY(mid1, end, (bspnode *)node->back);
PUSH_BSP_RAY(start, mid2, (bspnode *)node->front);
} else {
PUSH_BSP_RAY(mid2, end, (bspnode *)node->front);
PUSH_BSP_RAY(start, mid1, (bspnode *)node->back);
}
goto break_out;
}
}
break_out:;
}
return 0;
}
// Initializes some variables for the indoor bsp tree
void InitDefaultBSP() {
if (BSP_initted) {
DestroyBSPTree(&MineBSP);
} else {
atexit(DestroyDefaultBSPTree);
}
MineBSP.polylist = NULL;
MineBSP.vertlist = NULL;
BSP_initted = 1;
}