#include "qbsp.h"
int32_t c_nodes;
int32_t c_nonvis;
int32_t c_active_brushes;
#define PSIDE_FRONT 1
#define PSIDE_BACK 2
#define PSIDE_BOTH (PSIDE_FRONT | PSIDE_BACK)
#define PSIDE_FACING 4
int32_t BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane);
void FindBrushInTree(node_t *node, int32_t brushnum) {
bspbrush_t *b;
if (node->planenum == PLANENUM_LEAF) {
for (b = node->brushlist; b; b = b->next)
if (b->original->brushnum == brushnum)
return;
}
FindBrushInTree(node->children[0], brushnum);
FindBrushInTree(node->children[1], brushnum);
}
void BoundBrush(bspbrush_t *brush) {
int32_t 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);
}
}
void CreateBrushWindings(bspbrush_t *brush) {
int32_t i, j;
winding_t *w;
side_t *side;
plane_t *plane;
for (i = 0; i < brush->numsides; i++) {
side = &brush->sides[i];
plane = &mapplanes[side->planenum];
w = BaseWindingForPlane(plane->normal, plane->dist);
for (j = 0; j < brush->numsides && w; j++) {
if (i == j)
continue;
if (brush->sides[j].bevel)
continue;
plane = &mapplanes[brush->sides[j].planenum ^ 1];
ChopWindingInPlace(&w, plane->normal, plane->dist, 0);
}
side->winding = w;
}
BoundBrush(brush);
}
bspbrush_t *BrushFromBounds(vec3_t mins, vec3_t maxs) {
bspbrush_t *b;
int32_t i;
vec3_t 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 = FindFloatPlane(normal, dist, 0);
normal[i] = -1;
dist = -mins[i];
b->sides[3 + i].planenum = FindFloatPlane(normal, dist, 0);
}
CreateBrushWindings(b);
return b;
}
vec_t BrushVolume(bspbrush_t *brush) {
int32_t i;
winding_t *w;
vec3_t corner;
vec_t d, area, volume;
plane_t *plane;
if (!brush)
return 0;
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);
volume = 0;
for (; i < brush->numsides; i++) {
w = brush->sides[i].winding;
if (!w)
continue;
plane = &mapplanes[brush->sides[i].planenum];
d = -(DotProduct(corner, plane->normal) - plane->dist);
area = WindingArea(w);
volume += d * area;
}
volume /= 3;
return volume;
}
int32_t CountBrushList(bspbrush_t *brushes) {
int32_t c;
c = 0;
for (; brushes; brushes = brushes->next)
c++;
return c;
}
tree_t *AllocTree(void) {
tree_t *tree;
tree = malloc(sizeof(*tree));
memset(tree, 0, sizeof(*tree));
ClearBounds(tree->mins, tree->maxs);
return tree;
}
node_t *AllocNode(void) {
node_t *node;
node = malloc(sizeof(*node));
memset(node, 0, sizeof(*node));
return node;
}
bspbrush_t *AllocBrush(int32_t numsides) {
bspbrush_t *bb;
int32_t c;
c = (intptr_t) & (((bspbrush_t *)0)->sides[numsides]);
bb = malloc(c);
memset(bb, 0, c);
if (numthreads == 1)
c_active_brushes++;
return bb;
}
void FreeBrush(bspbrush_t *brushes) {
int32_t 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--;
}
void FreeBrushList(bspbrush_t *brushes) {
bspbrush_t *next;
for (; brushes; brushes = next) {
next = brushes->next;
FreeBrush(brushes);
}
}
bspbrush_t *CopyBrush(bspbrush_t *brush) {
bspbrush_t *newbrush;
int32_t size;
int32_t i;
size = (intptr_t) & (((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;
}
node_t *PointInLeaf(node_t *node, vec3_t point) {
vec_t d;
plane_t *plane;
while (node->planenum != PLANENUM_LEAF) {
plane = &mapplanes[node->planenum];
d = DotProduct(point, plane->normal) - plane->dist;
if (d > 0)
node = node->children[0];
else
node = node->children[1];
}
return node;
}
int32_t BoxOnPlaneSide(vec3_t mins, vec3_t maxs, plane_t *plane) {
int32_t side;
int32_t i;
vec3_t corners[2];
vec_t dist1, dist2;
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;
}
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;
}
int32_t QuickTestBrushToPlanenum(bspbrush_t *brush, int32_t planenum, int32_t *numsplits) {
int32_t i, num;
plane_t *plane;
int32_t s;
*numsplits = 0;
for (i = 0; i < brush->numsides; i++) {
num = brush->sides[i].planenum;
if (num >= MAX_MAP_PLANES_QBSP)
Error("bad planenum");
if (num == planenum)
return PSIDE_BACK | PSIDE_FACING;
if (num == (planenum ^ 1))
return PSIDE_FRONT | PSIDE_FACING;
}
plane = &mapplanes[planenum];
s = BoxOnPlaneSide(brush->mins, brush->maxs, plane);
if (s == PSIDE_BOTH) {
*numsplits += 3;
}
return s;
}
int32_t TestBrushToPlanenum(bspbrush_t *brush, int32_t planenum,
int32_t *numsplits, qboolean *hintsplit, qboolean *detailsplit, int32_t *epsilonbrush) {
int32_t i, j, num;
plane_t *plane;
int32_t s;
winding_t *w;
vec_t d, d_front, d_back;
int32_t front, back;
*numsplits = 0;
*hintsplit = false;
for (i = 0; i < brush->numsides; i++) {
num = brush->sides[i].planenum;
if (num >= MAX_MAP_PLANES_QBSP)
Error("bad planenum");
if (num == planenum)
return PSIDE_BACK | PSIDE_FACING;
if (num == (planenum ^ 1))
return PSIDE_FRONT | PSIDE_FACING;
}
plane = &mapplanes[planenum];
s = BoxOnPlaneSide(brush->mins, brush->maxs, plane);
if (s != PSIDE_BOTH)
return s;
d_front = d_back = 0;
for (i = 0; i < brush->numsides; i++) {
if (brush->sides[i].texinfo == TEXINFO_NODE)
continue; if (!brush->sides[i].visible)
continue; 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 > ON_EPSILON) front = 1;
if (d < -ON_EPSILON) back = 1;
}
if (front && back) {
if (!(brush->sides[i].surf & SURF_SKIP)) {
(*numsplits)++;
if (brush->sides[i].surf & SURF_HINT)
*hintsplit = true;
if (brush->sides[i].contents & CONTENTS_DETAIL)
*detailsplit = true;
}
}
}
if ((d_front > 0.0 && d_front < 1.0) || (d_back < 0.0 && d_back > -1.0))
(*epsilonbrush)++;
#if 0#endif
return s;
}
#define EDGE_LENGTH 0.2
qboolean WindingIsTiny(winding_t *w) {
#if 0#else
int32_t i, j;
vec_t len;
vec3_t delta;
int32_t 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;
#endif
}
qboolean WindingIsHuge(winding_t *w) {
int32_t i, j;
for (i = 0; i < w->numpoints; i++) {
for (j = 0; j < 3; j++)
if (w->p[i][j] < -2 * max_bounds || w->p[i][j] > 2 * max_bounds)
return true;
}
return false;
}
void LeafNode(node_t *node, bspbrush_t *brushes) {
bspbrush_t *b;
int32_t i;
node->planenum = PLANENUM_LEAF;
node->contents = 0;
for (b = brushes; b; b = b->next) {
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 CheckPlaneAgainstParents(int32_t pnum, node_t *node, bspbrush_t *brush) {
node_t *p;
for (p = node->parent; p; p = p->parent) {
if (p->planenum == pnum) {
Error("Tried parent\n Brush Bounds: %g %g %g -> %g %g %g\n",
brush->mins[0], brush->mins[1], brush->mins[2], brush->maxs[0], brush->maxs[1], brush->maxs[2]);
}
}
}
qboolean CheckPlaneAgainstVolume(int32_t 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;
}
side_t *SelectSplitSide(bspbrush_t *brushes, node_t *node) {
int32_t value, bestvalue;
bspbrush_t *brush, *test;
side_t *side, *bestside;
int32_t i, j, pass, numpasses;
int32_t pnum;
int32_t s;
int32_t front, back, both, facing, splits;
int32_t bsplits;
int32_t epsilonbrush;
qboolean hintsplit, detailsplit;
bestside = NULL;
bestvalue = -BOGUS_RANGE;
numpasses = 4;
for (pass = 0; pass < numpasses; pass++) {
for (brush = brushes; brush; brush = brush->next) {
if ((pass & 1) && !(brush->original->contents & CONTENTS_DETAIL))
continue;
if (!(pass & 1) && (brush->original->contents & CONTENTS_DETAIL))
continue;
for (i = 0; i < brush->numsides; i++) {
side = brush->sides + i;
if (side->bevel)
continue; if (!side->winding)
continue; if (side->texinfo == TEXINFO_NODE)
continue; if (side->tested)
continue; if (side->surf & SURF_SKIP)
continue; if (side->visible ^ (pass < 2))
continue;
pnum = side->planenum;
pnum &= ~1;
CheckPlaneAgainstParents(pnum, node, brush);
if (!CheckPlaneAgainstVolume(pnum, node))
continue;
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, &detailsplit, &epsilonbrush);
splits += bsplits;
if (bsplits && (s & PSIDE_FACING))
Error("PSIDE_FACING with splits");
test->testside = s;
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++;
}
value = 5 * facing - 5 * splits - abs(front - back);
if (mapplanes[pnum].type < 3)
value += 5; value -= epsilonbrush * 1000;
if ((hintsplit && !(side->surf & SURF_HINT)) && (!detailsplit || (side->contents & CONTENTS_DETAIL)))
value = -BOGUS_RANGE;
if (value > bestvalue) {
bestvalue = value;
bestside = side;
for (test = brushes; test; test = test->next)
test->side = test->testside;
}
}
}
if (bestside) {
if (pass > 1) {
if (numthreads == 1)
c_nonvis++;
}
if (pass > 0)
node->detail_seperator = true; break;
}
}
for (brush = brushes; brush; brush = brush->next) {
for (i = 0; i < brush->numsides; i++)
brush->sides[i].tested = false;
}
return bestside;
}
int32_t BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane) {
int32_t i, j;
winding_t *w;
vec_t d, max;
int32_t 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;
}
void SplitBrush(bspbrush_t *brush, int32_t planenum,
bspbrush_t **front, bspbrush_t **back) {
bspbrush_t *b[2];
int32_t i, j;
winding_t *w, *cw[2], *midwinding;
plane_t *plane, *plane2;
side_t *s, *cs;
vec_t d, d_front, d_back;
*front = *back = NULL;
plane = &mapplanes[planenum];
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 < ON_EPSILON) {
*back = CopyBrush(brush);
return;
}
if (d_back > -ON_EPSILON) {
*front = CopyBrush(brush);
return;
}
w = BaseWindingForPlane(plane->normal, plane->dist);
for (i = 0; i < brush->numsides && w; i++) {
plane2 = &mapplanes[brush->sides[i].planenum ^ 1];
ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); }
if (!w || WindingIsTiny(w)) {
int32_t 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");
}
midwinding = w;
for (i = 0; i < 2; i++) {
b[i] = AllocBrush(brush->numsides + 1);
b[i]->original = brush->original;
}
for (i = 0; i < brush->numsides; i++) {
s = &brush->sides[i];
w = s->winding;
if (!w)
continue;
ClipWindingEpsilon(w, plane->normal, plane->dist,
0 , &cw[0], &cw[1]); for (j = 0; j < 2; j++) {
if (!cw[j])
continue;
#if 0#endif
cs = &b[j]->sides[b[j]->numsides];
b[j]->numsides++;
*cs = *s;
cs->winding = cw[j];
cs->tested = false;
}
}
for (i = 0; i < 2; i++) {
BoundBrush(b[i]);
for (j = 0; j < 3; j++) {
if (b[i]->mins[j] < -max_bounds || b[i]->maxs[j] > max_bounds) {
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;
}
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;
cs->visible = false;
cs->tested = false;
if (i == 0)
cs->winding = CopyWinding(midwinding);
else
cs->winding = midwinding;
}
{
vec_t v1;
int32_t i;
for (i = 0; i < 2; i++) {
v1 = BrushVolume(b[i]);
if (v1 < microvolume) {
FreeBrush(b[i]);
b[i] = NULL;
}
}
}
*front = b[0];
*back = b[1];
}
void SplitBrushList(bspbrush_t *brushes,
node_t *node, bspbrush_t **front, bspbrush_t **back) {
bspbrush_t *brush, *newbrush, *newbrush2;
side_t *side;
int32_t sides;
int32_t i;
*front = *back = NULL;
for (brush = brushes; brush; brush = brush->next) {
sides = brush->side;
if (sides == PSIDE_BOTH) {
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 (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;
}
}
}
node_t *BuildTree_r(node_t *node, bspbrush_t *brushes) {
node_t *newnode;
side_t *bestside;
int32_t i;
bspbrush_t *children[2];
if (numthreads == 1)
c_nodes++;
bestside = SelectSplitSide(brushes, node);
if (!bestside) {
node->side = NULL;
node->planenum = -1;
LeafNode(node, brushes);
return node;
}
node->side = bestside;
node->planenum = bestside->planenum & ~1;
SplitBrushList(brushes, node, &children[0], &children[1]);
FreeBrushList(brushes);
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);
for (i = 0; i < 2; i++) {
node->children[i] = BuildTree_r(node->children[i], children[i]);
}
return node;
}
tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) {
node_t *node;
bspbrush_t *b;
int32_t c_faces, c_nonvisfaces;
int32_t c_brushes;
tree_t *tree;
int32_t 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) {
printf("WARNING: Entity %i, Brush %i, Line %i: microbrush\n Bounds: %g %g %g -> %g %g %g\n",
b->original->entitynum, b->original->brushnum, scriptline + 1, b->mins[0], b->mins[1], b->mins[2], b->maxs[0], b->maxs[1], b->maxs[2]);
}
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#endif
return tree;
}