source-engine/utils/vbsp/brushbsp.cpp

1470 lines
27 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "vbsp.h"
int c_nodes;
int c_nonvis;
int c_active_brushes;
// if a brush just barely pokes onto the other side,
// let it slide by without chopping
#define PLANESIDE_EPSILON 0.001
//0.1
void FindBrushInTree (node_t *node, int brushnum)
{
bspbrush_t *b;
if (node->planenum == PLANENUM_LEAF)
{
for (b=node->brushlist ; b ; b=b->next)
if (b->original->brushnum == brushnum)
Msg("here\n");
return;
}
FindBrushInTree (node->children[0], brushnum);
FindBrushInTree (node->children[1], brushnum);
}
//==================================================
/*
================
DrawBrushList
================
*/
void DrawBrushList (bspbrush_t *brush, node_t *node)
{
int i;
side_t *s;
GLS_BeginScene ();
for ( ; brush ; brush=brush->next)
{
for (i=0 ; i<brush->numsides ; i++)
{
s = &brush->sides[i];
if (!s->winding)
continue;
if (s->texinfo == TEXINFO_NODE)
GLS_Winding (s->winding, 1);
else if (!s->visible)
GLS_Winding (s->winding, 2);
else
GLS_Winding (s->winding, 0);
}
}
GLS_EndScene ();
}
/*
================
WriteBrushList
================
*/
void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis)
{
int i;
side_t *s;
qprintf ("writing %s\n", name);
FileHandle_t f = g_pFileSystem->Open(name, "w");
for ( ; brush ; brush=brush->next)
{
for (i=0 ; i<brush->numsides ; i++)
{
s = &brush->sides[i];
if (!s->winding)
continue;
if (onlyvis && !s->visible)
continue;
OutputWinding (brush->sides[i].winding, f);
}
}
g_pFileSystem->Close (f);
}
void PrintBrush (bspbrush_t *brush)
{
int i;
Msg("brush: %p\n", brush);
for (i=0;i<brush->numsides ; i++)
{
pw(brush->sides[i].winding);
Msg("\n");
}
}
/*
==================
BoundBrush
Sets the mins/maxs based on the windings
==================
*/
void BoundBrush (bspbrush_t *brush)
{
int i, j;
winding_t *w;
ClearBounds (brush->mins, brush->maxs);
for (i=0 ; i<brush->numsides ; i++)
{
w = brush->sides[i].winding;
if (!w)
continue;
for (j=0 ; j<w->numpoints ; j++)
AddPointToBounds (w->p[j], brush->mins, brush->maxs);
}
}
Vector PointInsideBrush( bspbrush_t *brush )
{
Vector insidePoint = vec3_origin;
bool bInside = false;
for ( int k = 0; k < 4 && !bInside; k++ )
{
bInside = true;
for (int i = 0; i < brush->numsides; i++)
{
side_t *side = &brush->sides[i];
plane_t *plane = &g_MainMap->mapplanes[side->planenum];
float d = DotProduct( plane->normal, insidePoint ) - plane->dist;
if ( d < 0 )
{
bInside = false;
insidePoint -= d * plane->normal;
}
}
}
return insidePoint;
}
/*
==================
CreateBrushWindings
==================
*/
void CreateBrushWindings (bspbrush_t *brush)
{
int i, j;
winding_t *w;
side_t *side;
plane_t *plane;
// translate the CSG problem to improve precision
Vector insidePoint = PointInsideBrush( brush );
Vector offset = -insidePoint;
for (i=0 ; i<brush->numsides ; i++)
{
side = &brush->sides[i];
plane = &g_MainMap->mapplanes[side->planenum];
w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal, offset));
for (j=0 ; j<brush->numsides && w; j++)
{
if (i == j)
continue;
if (brush->sides[j].bevel)
continue;
plane = &g_MainMap->mapplanes[brush->sides[j].planenum^1];
ChopWindingInPlace (&w, plane->normal, plane->dist + DotProduct(plane->normal, offset), 0); //CLIP_EPSILON);
}
TranslateWinding( w, -offset );
side->winding = w;
}
BoundBrush (brush);
}
/*
==================
BrushFromBounds
Creates a new axial brush
==================
*/
bspbrush_t *BrushFromBounds (Vector& mins, Vector& maxs)
{
bspbrush_t *b;
int i;
Vector normal;
vec_t dist;
b = AllocBrush (6);
b->numsides = 6;
for (i=0 ; i<3 ; i++)
{
VectorClear (normal);
normal[i] = 1;
dist = maxs[i];
b->sides[i].planenum = g_MainMap->FindFloatPlane (normal, dist);
normal[i] = -1;
dist = -mins[i];
b->sides[3+i].planenum = g_MainMap->FindFloatPlane (normal, dist);
}
CreateBrushWindings (b);
return b;
}
/*
==================
BrushVolume
==================
*/
vec_t BrushVolume (bspbrush_t *brush)
{
int i;
winding_t *w;
Vector corner;
vec_t d, area, volume;
plane_t *plane;
if (!brush)
return 0;
// grab the first valid point as the corner
w = NULL;
for (i=0 ; i<brush->numsides ; i++)
{
w = brush->sides[i].winding;
if (w)
break;
}
if (!w)
return 0;
VectorCopy (w->p[0], corner);
// make tetrahedrons to all other faces
volume = 0;
for ( ; i<brush->numsides ; i++)
{
w = brush->sides[i].winding;
if (!w)
continue;
plane = &g_MainMap->mapplanes[brush->sides[i].planenum];
d = -(DotProduct (corner, plane->normal) - plane->dist);
area = WindingArea (w);
volume += d*area;
}
volume /= 3;
return volume;
}
/*
================
CountBrushList
================
*/
int CountBrushList (bspbrush_t *brushes)
{
int c;
c = 0;
for ( ; brushes ; brushes = brushes->next)
c++;
return c;
}
/*
================
AllocTree
================
*/
tree_t *AllocTree (void)
{
tree_t *tree;
tree = (tree_t*)malloc(sizeof(*tree));
memset (tree, 0, sizeof(*tree));
ClearBounds (tree->mins, tree->maxs);
return tree;
}
/*
================
AllocNode
================
*/
node_t *AllocNode (void)
{
static int s_NodeCount = 0;
node_t *node;
node = (node_t*)malloc(sizeof(*node));
memset (node, 0, sizeof(*node));
node->id = s_NodeCount;
node->diskId = -1;
s_NodeCount++;
return node;
}
/*
================
AllocBrush
================
*/
bspbrush_t *AllocBrush (int numsides)
{
static int s_BrushId = 0;
bspbrush_t *bb;
int c;
c = (int)&(((bspbrush_t *)0)->sides[numsides]);
bb = (bspbrush_t*)malloc(c);
memset (bb, 0, c);
bb->id = s_BrushId++;
if (numthreads == 1)
c_active_brushes++;
return bb;
}
/*
================
FreeBrush
================
*/
void FreeBrush (bspbrush_t *brushes)
{
int i;
for (i=0 ; i<brushes->numsides ; i++)
if (brushes->sides[i].winding)
FreeWinding(brushes->sides[i].winding);
free (brushes);
if (numthreads == 1)
c_active_brushes--;
}
/*
================
FreeBrushList
================
*/
void FreeBrushList (bspbrush_t *brushes)
{
bspbrush_t *next;
for ( ; brushes ; brushes = next)
{
next = brushes->next;
FreeBrush (brushes);
}
}
/*
==================
CopyBrush
Duplicates the brush, the sides, and the windings
==================
*/
bspbrush_t *CopyBrush (bspbrush_t *brush)
{
bspbrush_t *newbrush;
int size;
int i;
size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]);
newbrush = AllocBrush (brush->numsides);
memcpy (newbrush, brush, size);
for (i=0 ; i<brush->numsides ; i++)
{
if (brush->sides[i].winding)
newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding);
}
return newbrush;
}
/*
==================
PointInLeaf
==================
*/
node_t *PointInLeaf (node_t *node, Vector& point)
{
vec_t d;
plane_t *plane;
while (node->planenum != PLANENUM_LEAF)
{
plane = &g_MainMap->mapplanes[node->planenum];
if (plane->type < 3)
{
d = point[plane->type] - plane->dist;
}
else
{
d = DotProduct (point, plane->normal) - plane->dist;
}
if (d >= 0)
node = node->children[0];
else
node = node->children[1];
}
return node;
}
//========================================================
/*
==============
BoxOnPlaneSide
Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH
==============
*/
int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane)
{
int side;
int i;
Vector corners[2];
vec_t dist1, dist2;
// axial planes are easy
if (plane->type < 3)
{
side = 0;
if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON)
side |= PSIDE_FRONT;
if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON)
side |= PSIDE_BACK;
return side;
}
// create the proper leading and trailing verts for the box
for (i=0 ; i<3 ; i++)
{
if (plane->normal[i] < 0)
{
corners[0][i] = mins[i];
corners[1][i] = maxs[i];
}
else
{
corners[1][i] = mins[i];
corners[0][i] = maxs[i];
}
}
dist1 = DotProduct (plane->normal, corners[0]) - plane->dist;
dist2 = DotProduct (plane->normal, corners[1]) - plane->dist;
side = 0;
if (dist1 >= PLANESIDE_EPSILON)
side = PSIDE_FRONT;
if (dist2 < PLANESIDE_EPSILON)
side |= PSIDE_BACK;
return side;
}
/*
============
QuickTestBrushToPlanenum
============
*/
int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits)
{
int i, num;
plane_t *plane;
int s;
*numsplits = 0;
// if the brush actually uses the planenum,
// we can tell the side for sure
for (i=0 ; i<brush->numsides ; i++)
{
num = brush->sides[i].planenum;
if (num >= 0x10000)
Error ("bad planenum");
if (num == planenum)
return PSIDE_BACK|PSIDE_FACING;
if (num == (planenum ^ 1) )
return PSIDE_FRONT|PSIDE_FACING;
}
// box on plane side
plane = &g_MainMap->mapplanes[planenum];
s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane);
// if both sides, count the visible faces split
if (s == PSIDE_BOTH)
{
*numsplits += 3;
}
return s;
}
/*
============
TestBrushToPlanenum
============
*/
int TestBrushToPlanenum (bspbrush_t *brush, int planenum,
int *numsplits, qboolean *hintsplit, int *epsilonbrush)
{
int i, j, num;
plane_t *plane;
int s;
winding_t *w;
vec_t d, d_front, d_back;
int front, back;
*numsplits = 0;
*hintsplit = false;
// if the brush actually uses the planenum,
// we can tell the side for sure
for (i=0 ; i<brush->numsides ; i++)
{
num = brush->sides[i].planenum;
if (num >= 0x10000)
Error ("bad planenum");
if (num == planenum)
return PSIDE_BACK|PSIDE_FACING;
if (num == (planenum ^ 1) )
return PSIDE_FRONT|PSIDE_FACING;
}
// box on plane side
plane = &g_MainMap->mapplanes[planenum];
s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane);
if (s != PSIDE_BOTH)
return s;
// if both sides, count the visible faces split
d_front = d_back = 0;
for (i=0 ; i<brush->numsides ; i++)
{
if (brush->sides[i].texinfo == TEXINFO_NODE)
continue; // on node, don't worry about splits
if (!brush->sides[i].visible)
continue; // we don't care about non-visible
w = brush->sides[i].winding;
if (!w)
continue;
front = back = 0;
for (j=0 ; j<w->numpoints; j++)
{
d = DotProduct (w->p[j], plane->normal) - plane->dist;
if (d > d_front)
d_front = d;
if (d < d_back)
d_back = d;
if (d > 0.1) // PLANESIDE_EPSILON)
front = 1;
if (d < -0.1) // PLANESIDE_EPSILON)
back = 1;
}
if (front && back)
{
if ( !(brush->sides[i].surf & SURF_SKIP) )
{
(*numsplits)++;
if (brush->sides[i].surf & SURF_HINT)
*hintsplit = true;
}
}
}
if ( (d_front > 0.0 && d_front < 1.0)
|| (d_back < 0.0 && d_back > -1.0) )
(*epsilonbrush)++;
#if 0
if (*numsplits == 0)
{ // didn't really need to be split
if (front)
s = PSIDE_FRONT;
else if (back)
s = PSIDE_BACK;
else
s = 0;
}
#endif
return s;
}
//========================================================
/*
================
WindingIsTiny
Returns true if the winding would be crunched out of
existance by the vertex snapping.
================
*/
#define EDGE_LENGTH 0.2
qboolean WindingIsTiny (winding_t *w)
{
int i, j;
vec_t len;
Vector delta;
int edges;
edges = 0;
for (i=0 ; i<w->numpoints ; i++)
{
j = i == w->numpoints - 1 ? 0 : i+1;
VectorSubtract (w->p[j], w->p[i], delta);
len = VectorLength (delta);
if (len > EDGE_LENGTH)
{
if (++edges == 3)
return false;
}
}
return true;
}
// UNDONE: JAY: This should be a slightly better heuristic - it builds an OBB
// around the winding and tests planar dimensions. NOTE: This can fail when a
// winding normal cannot be constructed (or is degenerate), but that is probably
// desired in this case.
// UNDONE: Test & use this instead.
#if 0
qboolean WindingIsTiny2 (winding_t *w)
{
int i, j;
vec_t len;
Vector delta;
int edges;
vec_t maxLen = 0;
Vector maxEdge = vec3_origin;
edges = 0;
for (i=0 ; i<w->numpoints ; i++)
{
j = i == w->numpoints - 1 ? 0 : i+1;
VectorSubtract (w->p[j], w->p[i], delta);
len = VectorLength (delta);
if (len > maxLen)
{
maxEdge = delta;
maxLen = len;
}
}
Vector normal;
vec_t dist;
WindingPlane (w, normal, &dist); // normal can come back vec3_origin in some cases
VectorNormalize(maxEdge);
Vector cross = CrossProduct(normal, maxEdge);
VectorNormalize(cross);
Vector mins, maxs;
ClearBounds( mins, maxs );
for (i=0 ; i<w->numpoints ; i++)
{
Vector point;
point.x = DotProduct( w->p[i], maxEdge );
point.y = DotProduct( w->p[i], cross );
point.z = DotProduct( w->p[i], normal );
AddPointToBounds( point, mins, maxs );
}
// check to see if the size in the plane is too small in either dimension
Vector size = maxs - mins;
for ( i = 0; i < 2; i++ )
{
if ( size[i] < EDGE_LENGTH )
return true;
}
return false;
}
#endif
/*
================
WindingIsHuge
Returns true if the winding still has one of the points
from basewinding for plane
================
*/
qboolean WindingIsHuge (winding_t *w)
{
int i, j;
for (i=0 ; i<w->numpoints ; i++)
{
for (j=0 ; j<3 ; j++)
if (w->p[i][j] < MIN_COORD_INTEGER || w->p[i][j] > MAX_COORD_INTEGER)
return true;
}
return false;
}
//============================================================
/*
================
Leafnode
================
*/
void LeafNode (node_t *node, bspbrush_t *brushes)
{
bspbrush_t *b;
int i;
node->planenum = PLANENUM_LEAF;
node->contents = 0;
for (b=brushes ; b ; b=b->next)
{
// if the brush is solid and all of its sides are on nodes,
// it eats everything
if (b->original->contents & CONTENTS_SOLID)
{
for (i=0 ; i<b->numsides ; i++)
if (b->sides[i].texinfo != TEXINFO_NODE)
break;
if (i == b->numsides)
{
node->contents = CONTENTS_SOLID;
break;
}
}
node->contents |= b->original->contents;
}
node->brushlist = brushes;
}
void RemoveAreaPortalBrushes_R( node_t *node )
{
if( node->planenum == PLANENUM_LEAF )
{
// Remove any CONTENTS_AREAPORTAL brushes we added. We don't want them in the engine
// at runtime but we do want their flags in the leaves.
bspbrush_t **pPrev = &node->brushlist;
for( bspbrush_t *b=node->brushlist; b; b=b->next )
{
if( b->original->contents == CONTENTS_AREAPORTAL )
{
*pPrev = b->next;
}
else
{
pPrev = &b->next;
}
}
}
else
{
RemoveAreaPortalBrushes_R( node->children[0] );
RemoveAreaPortalBrushes_R( node->children[1] );
}
}
//============================================================
void CheckPlaneAgainstParents (int pnum, node_t *node)
{
node_t *p;
for (p=node->parent ; p ; p=p->parent)
{
if (p->planenum == pnum)
Error ("Tried parent");
}
}
qboolean CheckPlaneAgainstVolume (int pnum, node_t *node)
{
bspbrush_t *front, *back;
qboolean good;
SplitBrush (node->volume, pnum, &front, &back);
good = (front && back);
if (front)
FreeBrush (front);
if (back)
FreeBrush (back);
return good;
}
/*
================
SelectSplitSide
Using a hueristic, choses one of the sides out of the brushlist
to partition the brushes with.
Returns NULL if there are no valid planes to split with..
================
*/
side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node)
{
int value, bestvalue;
bspbrush_t *brush, *test;
side_t *side, *bestside;
int i, j, pass, numpasses;
int pnum;
int s;
int front, back, both, facing, splits;
int bsplits;
int bestsplits;
int epsilonbrush;
qboolean hintsplit = false;
bestside = NULL;
bestvalue = -99999;
bestsplits = 0;
// the search order goes: visible-structural, nonvisible-structural
// If any valid plane is available in a pass, no further
// passes will be tried.
numpasses = 2;
for (pass = 0 ; pass < numpasses ; pass++)
{
for (brush = brushes ; brush ; brush=brush->next)
{
for (i=0 ; i<brush->numsides ; i++)
{
side = brush->sides + i;
if (side->bevel)
continue; // never use a bevel as a spliter
if (!side->winding)
continue; // nothing visible, so it can't split
if (side->texinfo == TEXINFO_NODE)
continue; // allready a node splitter
if (side->tested)
continue; // we allready have metrics for this plane
if (side->surf & SURF_SKIP)
continue; // skip surfaces are never chosen
if ( side->visible ^ (pass<1) )
continue; // only check visible faces on first pass
pnum = side->planenum;
pnum &= ~1; // allways use positive facing plane
CheckPlaneAgainstParents (pnum, node);
if (!CheckPlaneAgainstVolume (pnum, node))
continue; // would produce a tiny volume
front = 0;
back = 0;
both = 0;
facing = 0;
splits = 0;
epsilonbrush = 0;
for (test = brushes ; test ; test=test->next)
{
s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush);
splits += bsplits;
if (bsplits && (s&PSIDE_FACING) )
Error ("PSIDE_FACING with splits");
test->testside = s;
// if the brush shares this face, don't bother
// testing that facenum as a splitter again
if (s & PSIDE_FACING)
{
facing++;
for (j=0 ; j<test->numsides ; j++)
{
if ( (test->sides[j].planenum&~1) == pnum)
test->sides[j].tested = true;
}
}
if (s & PSIDE_FRONT)
front++;
if (s & PSIDE_BACK)
back++;
if (s == PSIDE_BOTH)
both++;
}
// give a value estimate for using this plane
value = 5*facing - 5*splits - abs(front-back);
// value = -5*splits;
// value = 5*facing - 5*splits;
if (g_MainMap->mapplanes[pnum].type < 3)
value+=5; // axial is better
value -= epsilonbrush*1000; // avoid!
// trans should split last
if ( side->surf & SURF_TRANS )
{
value -= 500;
}
// never split a hint side except with another hint
if (hintsplit && !(side->surf & SURF_HINT) )
value = -9999999;
// water should split first
if (side->contents & (CONTENTS_WATER | CONTENTS_SLIME))
value = 9999999;
// save off the side test so we don't need
// to recalculate it when we actually seperate
// the brushes
if (value > bestvalue)
{
bestvalue = value;
bestside = side;
bestsplits = splits;
for (test = brushes ; test ; test=test->next)
test->side = test->testside;
}
}
}
// if we found a good plane, don't bother trying any
// other passes
if (bestside)
{
if (pass > 0)
{
if (numthreads == 1)
c_nonvis++;
}
break;
}
}
//
// clear all the tested flags we set
//
for (brush = brushes ; brush ; brush=brush->next)
{
for (i=0 ; i<brush->numsides ; i++)
brush->sides[i].tested = false;
}
return bestside;
}
/*
==================
BrushMostlyOnSide
==================
*/
int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane)
{
int i, j;
winding_t *w;
vec_t d, max;
int side;
max = 0;
side = PSIDE_FRONT;
for (i=0 ; i<brush->numsides ; i++)
{
w = brush->sides[i].winding;
if (!w)
continue;
for (j=0 ; j<w->numpoints ; j++)
{
d = DotProduct (w->p[j], plane->normal) - plane->dist;
if (d > max)
{
max = d;
side = PSIDE_FRONT;
}
if (-d > max)
{
max = -d;
side = PSIDE_BACK;
}
}
}
return side;
}
/*
================
SplitBrush
Generates two new brushes, leaving the original
unchanged
================
*/
void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back )
{
bspbrush_t *b[2];
int i, j;
winding_t *w, *cw[2], *midwinding;
plane_t *plane, *plane2;
side_t *s, *cs;
float d, d_front, d_back;
*front = *back = NULL;
plane = &g_MainMap->mapplanes[planenum];
// check all points
d_front = d_back = 0;
for (i=0 ; i<brush->numsides ; i++)
{
w = brush->sides[i].winding;
if (!w)
continue;
for (j=0 ; j<w->numpoints ; j++)
{
d = DotProduct (w->p[j], plane->normal) - plane->dist;
if (d > 0 && d > d_front)
d_front = d;
if (d < 0 && d < d_back)
d_back = d;
}
}
if (d_front < 0.1) // PLANESIDE_EPSILON)
{ // only on back
*back = CopyBrush (brush);
return;
}
if (d_back > -0.1) // PLANESIDE_EPSILON)
{ // only on front
*front = CopyBrush (brush);
return;
}
// Move the CSG problem so that offset is at the origin
// This gives us much better floating point precision in the clipping operations
Vector offset = -0.5f * (brush->mins + brush->maxs);
// create a new winding from the split plane
w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal,offset));
for (i=0 ; i<brush->numsides && w ; i++)
{
plane2 = &g_MainMap->mapplanes[brush->sides[i].planenum ^ 1];
ChopWindingInPlace (&w, plane2->normal, plane2->dist+DotProduct(plane2->normal,offset), 0); // PLANESIDE_EPSILON);
}
if (!w || WindingIsTiny (w) )
{ // the brush isn't really split
int side;
side = BrushMostlyOnSide (brush, plane);
if (side == PSIDE_FRONT)
*front = CopyBrush (brush);
if (side == PSIDE_BACK)
*back = CopyBrush (brush);
return;
}
if (WindingIsHuge (w))
{
qprintf ("WARNING: huge winding\n");
}
TranslateWinding( w, -offset );
midwinding = w;
//
//
// split it for real
//
//
//
// allocate two new brushes referencing the original
//
for( i = 0; i < 2; i++ )
{
b[i] = AllocBrush( brush->numsides + 1 );
b[i]->original = brush->original;
}
//
// split all the current windings
//
for( i = 0; i < brush->numsides; i++ )
{
// get the current side
s = &brush->sides[i];
// get the sides winding
w = s->winding;
if( !w )
continue;
// clip the winding
ClipWindingEpsilon_Offset( w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1], offset );
for( j = 0; j < 2; j++ )
{
// does winding exist?
if( !cw[j] )
continue;
#if 0
if (WindingIsTiny (cw[j]))
{
FreeWinding (cw[j]);
continue;
}
#endif
//
// create a clipped "side" with the new winding
//
cs = &b[j]->sides[b[j]->numsides];
b[j]->numsides++;
*cs = *s;
cs->winding = cw[j];
cs->tested = false;
// save the original side information
//cs->original = s->original;
}
}
// see if we have valid polygons on both sides
for (i=0 ; i<2 ; i++)
{
BoundBrush (b[i]);
for (j=0 ; j<3 ; j++)
{
if (b[i]->mins[j] < MIN_COORD_INTEGER || b[i]->maxs[j] > MAX_COORD_INTEGER)
{
qprintf ("bogus brush after clip\n");
break;
}
}
if (b[i]->numsides < 3 || j < 3)
{
FreeBrush (b[i]);
b[i] = NULL;
}
}
if ( !(b[0] && b[1]) )
{
if (!b[0] && !b[1])
qprintf ("split removed brush\n");
else
qprintf ("split not on both sides\n");
if (b[0])
{
FreeBrush (b[0]);
*front = CopyBrush (brush);
}
if (b[1])
{
FreeBrush (b[1]);
*back = CopyBrush (brush);
}
return;
}
// add the midwinding to both sides
for (i=0 ; i<2 ; i++)
{
cs = &b[i]->sides[b[i]->numsides];
b[i]->numsides++;
cs->planenum = planenum^i^1;
cs->texinfo = TEXINFO_NODE;
// initialize the displacement map index
cs->pMapDisp = NULL;
cs->visible = false;
cs->tested = false;
if (i==0)
cs->winding = CopyWinding (midwinding);
else
cs->winding = midwinding;
}
{
vec_t v1;
int i;
for (i=0 ; i<2 ; i++)
{
v1 = BrushVolume (b[i]);
if (v1 < 1.0)
{
FreeBrush (b[i]);
b[i] = NULL;
// qprintf ("tiny volume after clip\n");
}
}
}
*front = b[0];
*back = b[1];
}
/*
================
SplitBrushList
================
*/
void SplitBrushList (bspbrush_t *brushes,
node_t *node, bspbrush_t **front, bspbrush_t **back)
{
bspbrush_t *brush, *newbrush, *newbrush2;
side_t *side;
int sides;
int i;
*front = *back = NULL;
for (brush = brushes ; brush ; brush=brush->next)
{
sides = brush->side;
if (sides == PSIDE_BOTH)
{ // split into two brushes
SplitBrush (brush, node->planenum, &newbrush, &newbrush2);
if (newbrush)
{
newbrush->next = *front;
*front = newbrush;
}
if (newbrush2)
{
newbrush2->next = *back;
*back = newbrush2;
}
continue;
}
newbrush = CopyBrush (brush);
// if the planenum is actualy a part of the brush
// find the plane and flag it as used so it won't be tried
// as a splitter again
if (sides & PSIDE_FACING)
{
for (i=0 ; i<newbrush->numsides ; i++)
{
side = newbrush->sides + i;
if ( (side->planenum& ~1) == node->planenum)
side->texinfo = TEXINFO_NODE;
}
}
if (sides & PSIDE_FRONT)
{
newbrush->next = *front;
*front = newbrush;
continue;
}
if (sides & PSIDE_BACK)
{
newbrush->next = *back;
*back = newbrush;
continue;
}
}
}
/*
================
BuildTree_r
================
*/
node_t *BuildTree_r (node_t *node, bspbrush_t *brushes)
{
node_t *newnode;
side_t *bestside;
int i;
bspbrush_t *children[2];
if (numthreads == 1)
c_nodes++;
// find the best plane to use as a splitter
bestside = SelectSplitSide (brushes, node);
if (!bestside)
{
// leaf node
node->side = NULL;
node->planenum = -1;
LeafNode (node, brushes);
return node;
}
// this is a splitplane node
node->side = bestside;
node->planenum = bestside->planenum & ~1; // always use front facing
SplitBrushList (brushes, node, &children[0], &children[1]);
FreeBrushList (brushes);
// allocate children before recursing
for (i=0 ; i<2 ; i++)
{
newnode = AllocNode ();
newnode->parent = node;
node->children[i] = newnode;
}
SplitBrush (node->volume, node->planenum, &node->children[0]->volume,
&node->children[1]->volume);
// recursively process children
for (i=0 ; i<2 ; i++)
{
node->children[i] = BuildTree_r (node->children[i], children[i]);
}
return node;
}
//===========================================================
/*
=================
BrushBSP
The incoming list will be freed before exiting
=================
*/
tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs)
{
node_t *node;
bspbrush_t *b;
int c_faces, c_nonvisfaces;
int c_brushes;
tree_t *tree;
int i;
vec_t volume;
qprintf ("--- BrushBSP ---\n");
tree = AllocTree ();
c_faces = 0;
c_nonvisfaces = 0;
c_brushes = 0;
for (b=brushlist ; b ; b=b->next)
{
c_brushes++;
volume = BrushVolume (b);
if (volume < microvolume)
{
Warning("Brush %i: WARNING, microbrush\n", b->original->id);
}
for (i=0 ; i<b->numsides ; i++)
{
if (b->sides[i].bevel)
continue;
if (!b->sides[i].winding)
continue;
if (b->sides[i].texinfo == TEXINFO_NODE)
continue;
if (b->sides[i].visible)
c_faces++;
else
c_nonvisfaces++;
}
AddPointToBounds (b->mins, tree->mins, tree->maxs);
AddPointToBounds (b->maxs, tree->mins, tree->maxs);
}
qprintf ("%5i brushes\n", c_brushes);
qprintf ("%5i visible faces\n", c_faces);
qprintf ("%5i nonvisible faces\n", c_nonvisfaces);
c_nodes = 0;
c_nonvis = 0;
node = AllocNode ();
node->volume = BrushFromBounds (mins, maxs);
tree->headnode = node;
node = BuildTree_r (node, brushlist);
qprintf ("%5i visible nodes\n", c_nodes/2 - c_nonvis);
qprintf ("%5i nonvis nodes\n", c_nonvis);
qprintf ("%5i leafs\n", (c_nodes+1)/2);
#if 0
{ // debug code
static node_t *tnode;
Vector p;
p[0] = -1469;
p[1] = -118;
p[2] = 119;
tnode = PointInLeaf (tree->headnode, p);
Msg("contents: %i\n", tnode->contents);
p[0] = 0;
}
#endif
return tree;
}