ericw-tools/qbsp/portals.cc

736 lines
20 KiB
C++

/*
Copyright (C) 1996-1997 Id Software, Inc.
Copyright (C) 1997 Greg Lewis
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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
See file, 'COPYING', for details.
*/
// portals.c
#include <qbsp/qbsp.hh>
node_t outside_node; // portals outside the world face this
class portal_state_t {
public:
int num_visportals;
int num_visleafs; // leafs the player can be in
int num_visclusters; // clusters of leafs
int iNodesDone;
bool uses_detail;
};
/*
==============================================================================
PORTAL FILE GENERATION
==============================================================================
*/
static void PlaneFromWinding(const winding_t *w, qbsp_plane_t *plane);
static void
WriteFloat(FILE *portalFile, vec_t v)
{
if (fabs(v - Q_rint(v)) < ZERO_EPSILON)
fprintf(portalFile, "%d ", (int)Q_rint(v));
else
fprintf(portalFile, "%f ", v);
}
static int
ClusterContents(const node_t *node)
{
int contents0, contents1;
/* Pass the leaf contents up the stack */
if (node->contents)
return node->contents;
contents0 = ClusterContents(node->children[0]);
contents1 = ClusterContents(node->children[1]);
if (contents0 == contents1)
return contents0;
/*
* Clusters may be partially solid but still be seen into
* ?? - Should we do something more explicit with mixed liquid contents?
*/
if (contents0 == CONTENTS_EMPTY || contents1 == CONTENTS_EMPTY)
return CONTENTS_EMPTY;
if (contents0 >= CONTENTS_LAVA && contents0 <= CONTENTS_WATER)
return contents0;
if (contents1 >= CONTENTS_LAVA && contents1 <= CONTENTS_WATER)
return contents1;
if (contents0 == CONTENTS_SKY || contents1 == CONTENTS_SKY)
return CONTENTS_SKY;
return CONTENTS_SOLID;
}
/*
* Return true if possible to see the through the contents of the portals nodes
*/
static bool
PortalThru(const portal_t *p)
{
int contents0 = ClusterContents(p->nodes[0]);
int contents1 = ClusterContents(p->nodes[1]);
/* Can't see through solids */
if (contents0 == CONTENTS_SOLID || contents1 == CONTENTS_SOLID)
return false;
/* If contents values are the same and not solid, can see through */
if (contents0 == contents1)
return true;
/* Can't see through func_illusionary_visblocker */
if (contents0 == CONTENTS_ILLUSIONARY_VISBLOCKER || contents1 == CONTENTS_ILLUSIONARY_VISBLOCKER)
return false;
/* If water is transparent, liquids are like empty space */
if (options.fTranswater) {
if (contents0 >= CONTENTS_LAVA && contents0 <= CONTENTS_WATER &&
contents1 == CONTENTS_EMPTY)
return true;
if (contents1 >= CONTENTS_LAVA && contents1 <= CONTENTS_WATER &&
contents0 == CONTENTS_EMPTY)
return true;
}
/* If sky is transparent, then sky is like empty space */
if (options.fTranssky) {
if (contents0 == CONTENTS_SKY && contents1 == CONTENTS_EMPTY)
return true;
if (contents0 == CONTENTS_EMPTY && contents1 == CONTENTS_SKY)
return true;
}
return false;
}
static void
WritePortals_r(node_t *node, FILE *portalFile, bool clusters)
{
const portal_t *p, *next;
const winding_t *w;
const qbsp_plane_t *pl;
int i, front, back;
qbsp_plane_t plane2;
if (!node->contents && !node->detail_separator) {
WritePortals_r(node->children[0], portalFile, clusters);
WritePortals_r(node->children[1], portalFile, clusters);
return;
}
if (node->contents == CONTENTS_SOLID)
return;
for (p = node->portals; p; p = next) {
next = (p->nodes[0] == node) ? p->next[0] : p->next[1];
if (!p->winding || p->nodes[0] != node)
continue;
if (!PortalThru(p))
continue;
w = p->winding;
front = clusters ? p->nodes[0]->viscluster : p->nodes[0]->visleafnum;
back = clusters ? p->nodes[1]->viscluster : p->nodes[1]->visleafnum;
/*
* sometimes planes get turned around when they are very near the
* changeover point between different axis. interpret the plane the
* same way vis will, and flip the side orders if needed
*/
pl = &map.planes[p->planenum];
PlaneFromWinding(w, &plane2);
if (DotProduct(pl->normal, plane2.normal) < 1.0 - ANGLEEPSILON)
fprintf(portalFile, "%d %d %d ", w->numpoints, back, front);
else
fprintf(portalFile, "%d %d %d ", w->numpoints, front, back);
for (i = 0; i < w->numpoints; i++) {
fprintf(portalFile, "(");
WriteFloat(portalFile, w->points[i][0]);
WriteFloat(portalFile, w->points[i][1]);
WriteFloat(portalFile, w->points[i][2]);
fprintf(portalFile, ") ");
}
fprintf(portalFile, "\n");
}
}
static int
WriteClusters_r(node_t *node, FILE *portalFile, int viscluster)
{
if (!node->contents) {
viscluster = WriteClusters_r(node->children[0], portalFile, viscluster);
viscluster = WriteClusters_r(node->children[1], portalFile, viscluster);
return viscluster;
}
if (node->contents == CONTENTS_SOLID)
return viscluster;
/* If we're in the next cluster, start a new line */
if (node->viscluster != viscluster) {
fprintf(portalFile, "-1\n");
viscluster++;
}
/* Sanity check */
if (node->viscluster != viscluster)
Error("Internal error: Detail cluster mismatch (%s)", __func__);
fprintf(portalFile, "%d ", node->visleafnum);
return viscluster;
}
static void
CountPortals(const node_t *node, portal_state_t *state)
{
const portal_t *portal;
for (portal = node->portals; portal;) {
/* only write out from first leaf */
if (portal->nodes[0] == node) {
if (PortalThru(portal))
state->num_visportals++;
portal = portal->next[0];
} else {
portal = portal->next[1];
}
}
}
/*
================
NumberLeafs_r
- Assigns leaf numbers and cluster numbers
- If cluster < 0, assign next available global cluster number and increment
- Otherwise, assign the given cluster number because parent splitter is detail
================
*/
static void
NumberLeafs_r(node_t *node, portal_state_t *state, int cluster)
{
/* decision node */
if (!node->contents) {
node->visleafnum = -99;
node->viscluster = -99;
if (cluster < 0 && node->detail_separator) {
state->uses_detail = true;
cluster = state->num_visclusters++;
node->viscluster = cluster;
CountPortals(node, state);
}
NumberLeafs_r(node->children[0], state, cluster);
NumberLeafs_r(node->children[1], state, cluster);
return;
}
if (node->contents == CONTENTS_SOLID) {
/* solid block, viewpoint never inside */
node->visleafnum = -1;
node->viscluster = -1;
return;
}
node->visleafnum = state->num_visleafs++;
node->viscluster = (cluster < 0) ? state->num_visclusters++ : cluster;
CountPortals(node, state);
}
/*
================
WritePortalfile
================
*/
static void
WritePortalfile(node_t *headnode, portal_state_t *state)
{
int check;
FILE *portalFile;
/*
* Set the visleafnum and viscluster field in every leaf and count the
* total number of portals.
*/
state->num_visleafs = 0;
state->num_visclusters = 0;
state->num_visportals = 0;
state->uses_detail = false;
NumberLeafs_r(headnode, state, -1);
// write the file
StripExtension(options.szBSPName);
strcat(options.szBSPName, ".prt");
portalFile = fopen(options.szBSPName, "wt");
if (!portalFile)
Error("Failed to open %s: %s", options.szBSPName, strerror(errno));
/* If no detail clusters, just use a normal PRT1 format */
if (!state->uses_detail) {
fprintf(portalFile, "PRT1\n");
fprintf(portalFile, "%d\n", state->num_visleafs);
fprintf(portalFile, "%d\n", state->num_visportals);
WritePortals_r(headnode, portalFile, false);
} else {
if (options.fForcePRT1) {
/* Write a PRT1 file for loading in the map editor. Vis will reject it. */
fprintf(portalFile, "PRT1\n");
fprintf(portalFile, "%d\n", state->num_visclusters);
fprintf(portalFile, "%d\n", state->num_visportals);
WritePortals_r(headnode, portalFile, true);
} else {
/* Write a PRT2 */
fprintf(portalFile, "PRT2\n");
fprintf(portalFile, "%d\n", state->num_visleafs);
fprintf(portalFile, "%d\n", state->num_visclusters);
fprintf(portalFile, "%d\n", state->num_visportals);
WritePortals_r(headnode, portalFile, true);
check = WriteClusters_r(headnode, portalFile, 0);
if (check != state->num_visclusters - 1)
Error("Internal error: Detail cluster mismatch (%s)", __func__);
fprintf(portalFile, "-1\n");
}
}
fclose(portalFile);
}
//=============================================================================
/*
=============
AddPortalToNodes
=============
*/
static void
AddPortalToNodes(portal_t *p, node_t *front, node_t *back)
{
if (p->nodes[0] || p->nodes[1])
Error("portal already included (%s)", __func__);
p->nodes[0] = front;
p->next[0] = front->portals;
front->portals = p;
p->nodes[1] = back;
p->next[1] = back->portals;
back->portals = p;
}
/*
=============
RemovePortalFromNode
=============
*/
static void
RemovePortalFromNode(portal_t *portal, node_t *l)
{
portal_t **pp, *t;
// remove reference to the current portal
pp = &l->portals;
while (1) {
t = *pp;
if (!t)
Error("Portal not in leaf (%s)", __func__);
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("Portal not bounding leaf (%s)", __func__);
}
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;
}
}
/*
================
MakeHeadnodePortals
The created portals will face the global outside_node
================
*/
static void
MakeHeadnodePortals(const mapentity_t *entity, node_t *node)
{
vec3_t bounds[2];
int i, j, n;
portal_t *p, *portals[6];
qbsp_plane_t bplanes[6], *pl;
int side;
// pad with some space so there will never be null volume leafs
for (i = 0; i < 3; i++) {
bounds[0][i] = entity->mins[i] - SIDESPACE;
bounds[1][i] = entity->maxs[i] + SIDESPACE;
}
outside_node.contents = CONTENTS_SOLID;
outside_node.portals = NULL;
for (i = 0; i < 3; i++)
for (j = 0; j < 2; j++) {
n = j * 3 + i;
p = (portal_t *)AllocMem(PORTAL, 1, true);
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->planenum = FindPlane(pl->normal, pl->dist, &side);
p->winding = BaseWindingForPlane(pl);
if (side)
AddPortalToNodes(p, &outside_node, node);
else
AddPortalToNodes(p, node, &outside_node);
}
// clip the basewindings by all the other planes
for (i = 0; i < 6; i++) {
for (j = 0; j < 6; j++) {
if (j == i)
continue;
portals[i]->winding =
ClipWinding(portals[i]->winding, &bplanes[j], true);
}
}
}
static void
PlaneFromWinding(const winding_t *w, qbsp_plane_t *plane)
{
vec3_t v1, v2;
// calc plane
VectorSubtract(w->points[2], w->points[1], v1);
VectorSubtract(w->points[0], w->points[1], v2);
CrossProduct(v2, v1, plane->normal);
VectorNormalize(plane->normal);
plane->dist = DotProduct(w->points[0], plane->normal);
}
//============================================================================
#ifdef PARANOID
static void
CheckWindingInNode(winding_t *w, node_t *node)
{
int i, j;
for (i = 0; i < w->numpoints; i++) {
for (j = 0; j < 3; j++)
if (w->points[i][j] < node->mins[j] - 1
|| w->points[i][j] > node->maxs[j] + 1) {
Message(msgWarning, warnWindingOutside);
return;
}
}
}
static void
CheckWindingArea(winding_t *w)
{
int i;
vec_t total, add;
vec3_t v1, v2, cross;
total = 0;
for (i = 1; i < w->numpoints; i++) {
VectorSubtract(w->points[i], w->points[0], v1);
VectorSubtract(w->points[i + 1], w->points[0], v2);
CrossProduct(v1, v2, cross);
add = VectorLength(cross);
total += add * 0.5;
}
if (total < 16)
Message(msgWarning, warnLowWindingArea, total);
}
static void
CheckLeafPortalConsistancy(node_t *node)
{
int side, side2;
portal_t *p, *p2;
qbsp_plane_t plane, plane2;
int i;
winding_t *w;
vec_t dist;
side = side2 = 0; // quiet compiler warning
for (p = node->portals; p; p = p->next[side]) {
if (p->nodes[0] == node)
side = 0;
else if (p->nodes[1] == node)
side = 1;
else
Error("Mislinked portal (%s)", __func__);
CheckWindingInNode(p->winding, node);
CheckWindingArea(p->winding);
// check that the side orders are correct
plane = map.planes[p->planenum];
PlaneFromWinding(p->winding, &plane2);
for (p2 = node->portals; p2; p2 = p2->next[side2]) {
if (p2->nodes[0] == node)
side2 = 0;
else if (p2->nodes[1] == node)
side2 = 1;
else
Error("Mislinked portal (%s)", __func__);
w = p2->winding;
for (i = 0; i < w->numpoints; i++) {
dist = DotProduct(w->points[i], plane.normal) - plane.dist;
if ((side == 0 && dist < -1) || (side == 1 && dist > 1)) {
Message(msgWarning, warnBadPortalDirection);
return;
}
}
}
}
}
#endif /* PARANOID */
//============================================================================
/*
================
CutNodePortals_r
================
*/
static void
CutNodePortals_r(node_t *node, portal_state_t *state)
{
const qbsp_plane_t *plane;
qbsp_plane_t clipplane;
node_t *front, *back, *other_node;
portal_t *portal, *new_portal, *next_portal;
winding_t *winding, *frontwinding, *backwinding;
int side;
#ifdef PARANOID
CheckLeafPortalConsistancy(node);
#endif
/* If a leaf, no more dividing */
if (node->contents)
return;
/* No portals on detail separators */
if (node->detail_separator)
return;
plane = &map.planes[node->planenum];
front = node->children[0];
back = node->children[1];
/*
* create the new portal by taking the full plane winding for the cutting
* plane and clipping it by all of the planes from the other portals
*/
new_portal = (portal_t *)AllocMem(PORTAL, 1, true);
new_portal->planenum = node->planenum;
winding = BaseWindingForPlane(plane);
for (portal = node->portals; portal; portal = portal->next[side]) {
clipplane = map.planes[portal->planenum];
if (portal->nodes[0] == node)
side = 0;
else if (portal->nodes[1] == node) {
clipplane.dist = -clipplane.dist;
VectorSubtract(vec3_origin, clipplane.normal, clipplane.normal);
side = 1;
} else
Error("Mislinked portal (%s)", __func__);
winding = ClipWinding(winding, &clipplane, true);
if (!winding) {
Message(msgWarning, warnPortalClippedAway,
portal->winding->points[0][0],
portal->winding->points[0][1],
portal->winding->points[0][2]);
break;
}
}
/* If the plane was not clipped on all sides, there was an error */
if (winding) {
new_portal->winding = winding;
AddPortalToNodes(new_portal, front, back);
}
/* partition the portals */
for (portal = node->portals; portal; portal = next_portal) {
if (portal->nodes[0] == node)
side = 0;
else if (portal->nodes[1] == node)
side = 1;
else
Error("Mislinked portal (%s)", __func__);
next_portal = portal->next[side];
other_node = portal->nodes[!side];
RemovePortalFromNode(portal, portal->nodes[0]);
RemovePortalFromNode(portal, portal->nodes[1]);
/* cut the portal into two portals, one on each side of the cut plane */
DivideWinding(portal->winding, plane, &frontwinding, &backwinding);
if (!frontwinding) {
if (backwinding)
FreeMem(backwinding, WINDING, 1);
if (side == 0)
AddPortalToNodes(portal, back, other_node);
else
AddPortalToNodes(portal, other_node, back);
continue;
}
if (!backwinding) {
if (frontwinding)
FreeMem(frontwinding, WINDING, 1);
if (side == 0)
AddPortalToNodes(portal, front, other_node);
else
AddPortalToNodes(portal, other_node, front);
continue;
}
/* the winding is split */
new_portal = (portal_t *)AllocMem(PORTAL, 1, true);
*new_portal = *portal;
new_portal->winding = backwinding;
FreeMem(portal->winding, WINDING, 1);
portal->winding = frontwinding;
if (side == 0) {
AddPortalToNodes(portal, front, other_node);
AddPortalToNodes(new_portal, back, other_node);
} else {
AddPortalToNodes(portal, other_node, front);
AddPortalToNodes(new_portal, other_node, back);
}
}
/* Display progress */
state->iNodesDone++;
Message(msgPercent, state->iNodesDone, splitnodes.load());
CutNodePortals_r(front, state);
CutNodePortals_r(back, state);
}
/*
==================
PortalizeWorld
Builds the exact polyhedrons for the nodes and leafs
==================
*/
void
PortalizeWorld(const mapentity_t *entity, node_t *headnode, const int hullnum)
{
Message(msgProgress, "Portalize");
portal_state_t state;
memset(&state, 0, sizeof(state));
state.iNodesDone = 0;
MakeHeadnodePortals(entity, headnode);
CutNodePortals_r(headnode, &state);
if (!hullnum) {
/* save portal file for vis tracing */
WritePortalfile(headnode, &state);
Message(msgStat, "%8d vis leafs", state.num_visleafs);
Message(msgStat, "%8d vis clusters", state.num_visclusters);
Message(msgStat, "%8d vis portals", state.num_visportals);
}
}
/*
==================
FreeAllPortals
==================
*/
void
FreeAllPortals(node_t *node)
{
portal_t *p, *nextp;
if (!node->contents) {
FreeAllPortals(node->children[0]);
FreeAllPortals(node->children[1]);
}
for (p = node->portals; p; p = nextp) {
if (p->nodes[0] == node)
nextp = p->next[0];
else
nextp = p->next[1];
RemovePortalFromNode(p, p->nodes[0]);
RemovePortalFromNode(p, p->nodes[1]);
FreeMem(p->winding, WINDING, 1);
FreeMem(p, PORTAL, 1);
}
node->portals = NULL;
}