#include "qbsp.h"
int32_t c_active_portals;
int32_t c_peak_portals;
int32_t c_boundary;
int32_t c_boundary_sides;
portal_t *AllocPortal(void) {
portal_t *p;
if (numthreads == 1)
c_active_portals++;
if (c_active_portals > c_peak_portals)
c_peak_portals = c_active_portals;
p = malloc(sizeof(portal_t));
memset(p, 0, sizeof(portal_t));
return p;
}
void FreePortal(portal_t *p) {
if (p->winding)
FreeWinding(p->winding);
if (numthreads == 1)
c_active_portals--;
free(p);
}
int32_t VisibleContents(int32_t contents) {
int32_t i;
for (i = 1; i <= LAST_VISIBLE_CONTENTS; i <<= 1)
if (contents & i)
return i;
return 0;
}
int32_t ClusterContents(node_t *node) {
int32_t c1, c2, c;
if (node->planenum == PLANENUM_LEAF)
return node->contents;
c1 = ClusterContents(node->children[0]);
c2 = ClusterContents(node->children[1]);
c = c1 | c2;
if (!(c1 & CONTENTS_SOLID) || !(c2 & CONTENTS_SOLID))
c &= ~CONTENTS_SOLID;
return c;
}
qboolean Portal_VisFlood(portal_t *p) {
int32_t c1, c2;
if (!p->onnode)
return false;
c1 = ClusterContents(p->nodes[0]);
c2 = ClusterContents(p->nodes[1]);
if (!VisibleContents(c1 ^ c2))
return true;
if (c1 & (CONTENTS_TRANSLUCENT | CONTENTS_DETAIL))
c1 = 0;
if (c2 & (CONTENTS_TRANSLUCENT | CONTENTS_DETAIL))
c2 = 0;
if ((c1 | c2) & CONTENTS_SOLID)
return false;
if (!(c1 ^ c2))
return true;
if (!VisibleContents(c1 ^ c2))
return true;
return false;
}
qboolean Portal_EntityFlood(portal_t *p, int32_t s) {
if (p->nodes[0]->planenum != PLANENUM_LEAF || p->nodes[1]->planenum != PLANENUM_LEAF)
Error("Portal_EntityFlood: not a leaf");
if ((p->nodes[0]->contents & CONTENTS_SOLID) || (p->nodes[1]->contents & CONTENTS_SOLID))
return false;
return true;
}
int32_t c_tinyportals;
void AddPortalToNodes(portal_t *p, node_t *front, node_t *back) {
if (p->nodes[0] || p->nodes[1])
Error("AddPortalToNode: already included");
p->nodes[0] = front;
p->next[0] = front->portals;
front->portals = p;
p->nodes[1] = back;
p->next[1] = back->portals;
back->portals = p;
}
void RemovePortalFromNode(portal_t *portal, node_t *l) {
portal_t **pp, *t;
pp = &l->portals;
while (1) {
t = *pp;
if (!t)
Error("RemovePortalFromNode: portal not in leaf");
if (t == portal)
break;
if (t->nodes[0] == l)
pp = &t->next[0];
else if (t->nodes[1] == l)
pp = &t->next[1];
else
Error("RemovePortalFromNode: portal not bounding leaf");
}
if (portal->nodes[0] == l) {
*pp = portal->next[0];
portal->nodes[0] = NULL;
} else if (portal->nodes[1] == l) {
*pp = portal->next[1];
portal->nodes[1] = NULL;
}
}
void PrintPortal(portal_t *p) {
int32_t i;
winding_t *w;
w = p->winding;
for (i = 0; i < w->numpoints; i++)
printf("(%5.0f,%5.0f,%5.0f)\n", w->p[i][0], w->p[i][1], w->p[i][2]);
}
#define SIDESPACE 8
void MakeHeadnodePortals(tree_t *tree) {
vec3_t bounds[2];
int32_t i, j, n;
portal_t *p, *portals[6];
plane_t bplanes[6], *pl;
node_t *node;
node = tree->headnode;
for (i = 0; i < 3; i++) {
bounds[0][i] = tree->mins[i] - SIDESPACE;
bounds[1][i] = tree->maxs[i] + SIDESPACE;
}
tree->outside_node.planenum = PLANENUM_LEAF;
tree->outside_node.brushlist = NULL;
tree->outside_node.portals = NULL;
tree->outside_node.contents = 0;
for (i = 0; i < 3; i++)
for (j = 0; j < 2; j++) {
n = j * 3 + i;
p = AllocPortal();
portals[n] = p;
pl = &bplanes[n];
memset(pl, 0, sizeof(*pl));
if (j) {
pl->normal[i] = -1;
pl->dist = -bounds[j][i];
} else {
pl->normal[i] = 1;
pl->dist = bounds[j][i];
}
p->plane = *pl;
p->winding = BaseWindingForPlane(pl->normal, pl->dist);
AddPortalToNodes(p, node, &tree->outside_node);
}
for (i = 0; i < 6; i++) {
for (j = 0; j < 6; j++) {
if (j == i)
continue;
ChopWindingInPlace(&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON);
}
}
}
#define BASE_WINDING_EPSILON 0.001
#define SPLIT_WINDING_EPSILON 0.001
winding_t *BaseWindingForNode(node_t *node) {
winding_t *w;
node_t *n;
plane_t *plane;
vec3_t normal;
vec_t dist;
w = BaseWindingForPlane(mapplanes[node->planenum].normal,
mapplanes[node->planenum].dist);
for (n = node->parent; n && w;) {
plane = &mapplanes[n->planenum];
if (n->children[0] == node) { ChopWindingInPlace(&w, plane->normal, plane->dist, BASE_WINDING_EPSILON);
} else { VectorSubtract(vec3_origin, plane->normal, normal);
dist = -plane->dist;
ChopWindingInPlace(&w, normal, dist, BASE_WINDING_EPSILON);
}
node = n;
n = n->parent;
}
return w;
}
qboolean WindingIsTiny(winding_t *w);
void MakeNodePortal(node_t *node) {
portal_t *new_portal, *p;
winding_t *w;
vec3_t normal;
vec_t dist = 0;
int32_t side = 0;
w = BaseWindingForNode(node);
for (p = node->portals; p && w; p = p->next[side]) {
if (p->nodes[0] == node) {
side = 0;
VectorCopy(p->plane.normal, normal);
dist = p->plane.dist;
} else if (p->nodes[1] == node) {
side = 1;
VectorSubtract(vec3_origin, p->plane.normal, normal);
dist = -p->plane.dist;
} else
Error("CutNodePortals_r: mislinked portal");
ChopWindingInPlace(&w, normal, dist, 0.1);
}
if (!w) {
return;
}
if (WindingIsTiny(w)) {
c_tinyportals++;
FreeWinding(w);
return;
}
new_portal = AllocPortal();
new_portal->plane = mapplanes[node->planenum];
new_portal->onnode = node;
new_portal->winding = w;
AddPortalToNodes(new_portal, node->children[0], node->children[1]);
}
void SplitNodePortals(node_t *node) {
portal_t *p, *next_portal, *new_portal;
node_t *f, *b, *other_node;
int32_t side = 0;
plane_t *plane;
winding_t *frontwinding, *backwinding;
plane = &mapplanes[node->planenum];
f = node->children[0];
b = node->children[1];
for (p = node->portals; p; p = next_portal) {
if (p->nodes[0] == node)
side = 0;
else if (p->nodes[1] == node)
side = 1;
else
Error("CutNodePortals_r: mislinked portal");
next_portal = p->next[side];
other_node = p->nodes[!side];
RemovePortalFromNode(p, p->nodes[0]);
RemovePortalFromNode(p, p->nodes[1]);
ClipWindingEpsilon(p->winding, plane->normal, plane->dist,
SPLIT_WINDING_EPSILON, &frontwinding, &backwinding);
if (frontwinding && WindingIsTiny(frontwinding)) {
FreeWinding(frontwinding);
frontwinding = NULL;
c_tinyportals++;
}
if (backwinding && WindingIsTiny(backwinding)) {
FreeWinding(backwinding);
backwinding = NULL;
c_tinyportals++;
}
if (!frontwinding && !backwinding) { continue;
}
if (!frontwinding) {
FreeWinding(backwinding);
if (side == 0)
AddPortalToNodes(p, b, other_node);
else
AddPortalToNodes(p, other_node, b);
continue;
}
if (!backwinding) {
FreeWinding(frontwinding);
if (side == 0)
AddPortalToNodes(p, f, other_node);
else
AddPortalToNodes(p, other_node, f);
continue;
}
new_portal = AllocPortal();
*new_portal = *p;
new_portal->winding = backwinding;
FreeWinding(p->winding);
p->winding = frontwinding;
if (side == 0) {
AddPortalToNodes(p, f, other_node);
AddPortalToNodes(new_portal, b, other_node);
} else {
AddPortalToNodes(p, other_node, f);
AddPortalToNodes(new_portal, other_node, b);
}
}
node->portals = NULL;
}
void CalcNodeBounds(node_t *node) {
portal_t *p;
int32_t s;
int32_t i;
ClearBounds(node->mins, node->maxs);
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
for (i = 0; i < p->winding->numpoints; i++)
AddPointToBounds(p->winding->p[i], node->mins, node->maxs);
}
}
void MakeTreePortals_r(node_t *node) {
int32_t i;
CalcNodeBounds(node);
for (i = 0; i < 3; i++) {
if (node->mins[i] < -2 * max_bounds || node->maxs[i] > 2 * max_bounds) {
printf("WARNING: node with unbounded volume\n");
printf(" Bounds: %g %g %g -> %g %g %g\n",
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
break;
}
}
if (node->planenum == PLANENUM_LEAF)
return;
MakeNodePortal(node);
SplitNodePortals(node);
MakeTreePortals_r(node->children[0]);
MakeTreePortals_r(node->children[1]);
}
void MakeTreePortals(tree_t *tree) {
MakeHeadnodePortals(tree);
MakeTreePortals_r(tree->headnode);
}
void FloodPortals_r(node_t *node, int32_t dist) {
portal_t *p;
int32_t s;
node->occupied = dist;
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
if (p->nodes[!s]->occupied)
continue;
if (!Portal_EntityFlood(p, s))
continue;
FloodPortals_r(p->nodes[!s], dist + 1);
}
}
qboolean PlaceOccupant(node_t *headnode, vec3_t origin, entity_t *occupant) {
node_t *node;
vec_t d;
plane_t *plane;
node = headnode;
while (node->planenum != PLANENUM_LEAF) {
plane = &mapplanes[node->planenum];
d = DotProduct(origin, plane->normal) - plane->dist;
if (d >= 0)
node = node->children[0];
else
node = node->children[1];
}
if (node->contents == CONTENTS_SOLID)
return false;
node->occupant = occupant;
FloodPortals_r(node, 1);
return true;
}
qboolean FloodEntities(tree_t *tree) {
int32_t i;
vec3_t origin;
char *cl;
qboolean inside;
node_t *headnode;
headnode = tree->headnode;
qprintf("--- FloodEntities ---\n");
inside = false;
tree->outside_node.occupied = 0;
for (i = 1; i < num_entities; i++) {
GetVectorForKey(&entities[i], "origin", origin);
if (VectorCompare(origin, vec3_origin))
continue;
cl = ValueForKey(&entities[i], "classname");
origin[2] += 1;
if (!strcmp(cl, "info_player_start")) {
int32_t x, y;
for (x = -16; x <= 16; x += 16) {
for (y = -16; y <= 16; y += 16) {
origin[0] += x;
origin[1] += y;
if (PlaceOccupant(headnode, origin, &entities[i])) {
inside = true;
goto gotit;
}
origin[0] -= x;
origin[1] -= y;
}
}
gotit:;
} else {
if (PlaceOccupant(headnode, origin, &entities[i]))
inside = true;
}
}
if (!inside) {
qprintf("no entities in open -- no filling\n");
} else if (tree->outside_node.occupied) {
qprintf("entity reached from outside -- no filling\n");
}
return (qboolean)(inside && !tree->outside_node.occupied);
}
int32_t c_areas;
void FloodAreas_r(node_t *node) {
portal_t *p;
int32_t s;
bspbrush_t *b;
entity_t *e;
if (node->contents == CONTENTS_AREAPORTAL) {
b = node->brushlist;
e = &entities[b->original->entitynum];
if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas)
return;
if (e->portalareas[1]) {
printf("WARNING: areaportal entity %i touches > 2 areas\n Node Bounds: %g %g %g -> %g %g %g\n", b->original->entitynum,
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
return;
}
if (e->portalareas[0])
e->portalareas[1] = c_areas;
else
e->portalareas[0] = c_areas;
return;
}
if (node->area)
return; node->area = c_areas;
for (p = node->portals; p; p = p->next[s]) {
s = (p->nodes[1] == node);
#if 0#endif
if (!Portal_EntityFlood(p, s))
continue;
FloodAreas_r(p->nodes[!s]);
}
}
void FindAreas_r(node_t *node) {
if (node->planenum != PLANENUM_LEAF) {
FindAreas_r(node->children[0]);
FindAreas_r(node->children[1]);
return;
}
if (node->area)
return;
if (node->contents & CONTENTS_SOLID)
return;
if (!node->occupied)
return;
if (node->contents == CONTENTS_AREAPORTAL)
return;
c_areas++;
FloodAreas_r(node);
}
void SetAreaPortalAreas_r(node_t *node) {
bspbrush_t *b;
entity_t *e;
if (node->planenum != PLANENUM_LEAF) {
SetAreaPortalAreas_r(node->children[0]);
SetAreaPortalAreas_r(node->children[1]);
return;
}
if (node->contents == CONTENTS_AREAPORTAL) {
if (node->area)
return;
b = node->brushlist;
e = &entities[b->original->entitynum];
node->area = e->portalareas[0];
if (!e->portalareas[1]) {
printf("WARNING: areaportal entity %i doesn't touch two areas\n Node Bounds: %g %g %g -> %g %g %g\n", b->original->entitynum,
node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]);
return;
}
}
}
void EmitAreaPortals(node_t *headnode) {
int32_t i, j;
entity_t *e;
dareaportal_t *dp;
if (c_areas > MAX_MAP_AREAS)
Error("MAX_MAP_AREAS");
numareas = c_areas + 1;
numareaportals = 1;
for (i = 1; i <= c_areas; i++) {
dareas[i].firstareaportal = numareaportals;
for (j = 0; j < num_entities; j++) {
e = &entities[j];
if (!e->areaportalnum)
continue;
dp = &dareaportals[numareaportals];
if (e->portalareas[0] == i) {
dp->portalnum = e->areaportalnum;
dp->otherarea = e->portalareas[1];
numareaportals++;
} else if (e->portalareas[1] == i) {
dp->portalnum = e->areaportalnum;
dp->otherarea = e->portalareas[0];
numareaportals++;
}
}
dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal;
}
qprintf("%5i numareas\n", numareas);
qprintf("%5i numareaportals\n", numareaportals);
}
void FloodAreas(tree_t *tree) {
qprintf("--- FloodAreas ---\n");
FindAreas_r(tree->headnode);
SetAreaPortalAreas_r(tree->headnode);
qprintf("%5i areas\n", c_areas);
}
int32_t c_outside;
int32_t c_inside;
int32_t c_solid;
void FillOutside_r(node_t *node) {
if (node->planenum != PLANENUM_LEAF) {
FillOutside_r(node->children[0]);
FillOutside_r(node->children[1]);
return;
}
if (!node->occupied) {
if (node->contents != CONTENTS_SOLID) {
c_outside++;
node->contents = CONTENTS_SOLID;
} else
c_solid++;
} else
c_inside++;
}
void FillOutside(node_t *headnode) {
c_outside = 0;
c_inside = 0;
c_solid = 0;
qprintf("--- FillOutside ---\n");
FillOutside_r(headnode);
qprintf("%5i solid leafs\n", c_solid);
qprintf("%5i leafs filled\n", c_outside);
qprintf("%5i inside leafs\n", c_inside);
}
void FindPortalSide(portal_t *p) {
int32_t viscontents;
bspbrush_t *bb;
mapbrush_t *brush;
node_t *n;
int32_t i, j;
int32_t planenum;
side_t *side, *bestside;
vec_t dot, bestdot;
plane_t *p1, *p2;
viscontents = VisibleContents(p->nodes[0]->contents ^ p->nodes[1]->contents);
if (!viscontents)
return;
planenum = p->onnode->planenum;
bestside = NULL;
bestdot = 0;
for (j = 0; j < 2; j++) {
n = p->nodes[j];
p1 = &mapplanes[p->onnode->planenum];
for (bb = n->brushlist; bb; bb = bb->next) {
brush = bb->original;
if (!(brush->contents & viscontents))
continue;
for (i = 0; i < brush->numsides; i++) {
side = &brush->original_sides[i];
if (side->bevel)
continue;
if (side->texinfo == TEXINFO_NODE)
continue; if ((side->planenum & ~1) == planenum) { bestside = &brush->original_sides[i];
goto gotit;
}
p2 = &mapplanes[side->planenum & ~1];
dot = DotProduct(p1->normal, p2->normal);
if (dot > bestdot) {
bestdot = dot;
bestside = side;
}
}
}
}
gotit:
if (!bestside)
qprintf("WARNING: side not found for portal\n");
p->sidefound = true;
p->side = bestside;
}
void MarkVisibleSides_r(node_t *node) {
portal_t *p;
int32_t s;
if (node->planenum != PLANENUM_LEAF) {
MarkVisibleSides_r(node->children[0]);
MarkVisibleSides_r(node->children[1]);
return;
}
if (!node->contents)
return;
for (p = node->portals; p; p = p->next[!s]) {
s = (p->nodes[0] == node);
if (!p->onnode)
continue; if (!p->sidefound)
FindPortalSide(p);
if (p->side)
p->side->visible = true;
}
}
void MarkVisibleSides(tree_t *tree, int32_t startbrush, int32_t endbrush) {
int32_t i, j;
mapbrush_t *mb;
int32_t numsides;
qprintf("--- MarkVisibleSides ---\n");
for (i = startbrush; i < endbrush; i++) {
mb = &mapbrushes[i];
numsides = mb->numsides;
for (j = 0; j < numsides; j++)
mb->original_sides[j].visible = false;
}
MarkVisibleSides_r(tree->headnode);
}