ericw-tools/qbsp/outside.cc

543 lines
15 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.
*/
#include "qbsp.hh"
typedef struct {
bool header; /* Flag true once header has been written */
int backdraw; /* Limit the length of the leak line */
int numwritten; /* Number of portals written to .por file */
const mapentity_t *entity; /* Entity that outside filling reached */
const node_t *node; /* Node where entity was reached */
const portal_t **portals; /* Portals traversed by leak line */
int numportals;
int maxportals;
FILE *ptsfile;
FILE *porfile;
} leakstate_t;
/*
===========
PointInLeaf
===========
*/
static node_t *
PointInLeaf(node_t *node, const vec3_t point)
{
vec_t dist;
const plane_t *plane;
while (!node->contents) {
plane = &map.planes[node->planenum];
dist = DotProduct(plane->normal, point) - plane->dist;
node = (dist > 0) ? node->children[0] : node->children[1];
}
return node;
}
/*
===========
PlaceOccupant
===========
*/
static bool
PlaceOccupant(int num, const vec3_t point, node_t *headnode)
{
node_t *node;
node = PointInLeaf(headnode, point);
if (node->contents == CONTENTS_SOLID)
return false;
node->occupied = num;
return true;
}
static FILE *
InitPorFile(void)
{
FILE *porfile;
StripExtension(options.szBSPName);
strcat(options.szBSPName, ".por");
porfile = fopen(options.szBSPName, "wt");
if (!porfile)
Error("Failed to open %s: %s", options.szBSPName, strerror(errno));
fprintf(porfile, "PLACEHOLDER\r\n");
return porfile;
}
static FILE *
InitPtsFile(void)
{
FILE *ptsfile;
StripExtension(options.szBSPName);
strcat(options.szBSPName, ".pts");
ptsfile = fopen(options.szBSPName, "wt");
if (!ptsfile)
Error("Failed to open %s: %s", options.szBSPName, strerror(errno));
return ptsfile;
}
static void
WriteLeakNode(FILE *porfile, const node_t *node)
{
const portal_t *portal;
int i, side, count;
if (!node)
Error("Internal error: no leak node! (%s)", __func__);
count = 0;
for (portal = node->portals; portal; portal = portal->next[!side]) {
side = (portal->nodes[0] == node);
if (portal->nodes[side]->contents == CONTENTS_SOLID)
continue;
if (portal->nodes[side]->contents == CONTENTS_SKY)
continue;
count++;
}
if (options.fBspleak)
fprintf(porfile, "%d\n", count);
for (portal = node->portals; portal; portal = portal->next[!side]) {
side = (portal->nodes[0] == node);
if (portal->nodes[side]->contents == CONTENTS_SOLID)
continue;
if (portal->nodes[side]->contents == CONTENTS_SKY)
continue;
if (options.fBspleak) {
fprintf(porfile, "%d ", portal->winding->numpoints);
for (i = 0; i < portal->winding->numpoints; i++) {
const vec_t *point = portal->winding->points[i];
fprintf(porfile, "%f %f %f ", point[0], point[1], point[2]);
}
fprintf(porfile, "\n");
}
}
}
/*
===============
WriteLeakTrail
===============
*/
static void
WriteLeakTrail(FILE *leakfile, const vec3_t point1, const vec3_t point2)
{
vec3_t vector, trail;
vec_t dist;
VectorSubtract(point2, point1, vector);
dist = VectorNormalize(vector);
VectorCopy(point1, trail);
while (dist > options.dxLeakDist) {
fprintf(leakfile, "%f %f %f\n", trail[0], trail[1], trail[2]);
VectorMA(trail, options.dxLeakDist, vector, trail);
dist -= options.dxLeakDist;
}
}
/*
==============
MarkLeakTrail
==============
*/
__attribute__((noinline))
static void
MarkLeakTrail(leakstate_t *leak, const portal_t *portal2)
{
int i;
vec3_t point1, point2;
const portal_t *portal1;
if (leak->numportals >= leak->maxportals)
Error("Internal error: numportals > maxportals (%s)", __func__);
leak->portals[leak->numportals++] = portal2;
MidpointWinding(portal2->winding, point1);
if (options.fBspleak) {
if (!leak->porfile)
leak->porfile = InitPorFile();
/* Write the header if needed */
if (!leak->header) {
const vec_t *origin = leak->entity->origin;
fprintf(leak->porfile, "%f %f %f\n", origin[0], origin[1], origin[2]);
WriteLeakNode(leak->porfile, leak->node);
leak->header = true;
}
/* Write the portal center and winding */
fprintf(leak->porfile, "%f %f %f ", point1[0], point1[1], point1[2]);
fprintf(leak->porfile, "%d ", portal2->winding->numpoints);
for (i = 0; i < portal2->winding->numpoints; i++) {
const vec_t *point = portal2->winding->points[i];
fprintf(leak->porfile, "%f %f %f ", point[0], point[1], point[2]);
}
fprintf(leak->porfile, "\n");
leak->numwritten++;
}
if (leak->numportals < 2 || !options.fOldleak)
return;
if (!leak->ptsfile)
leak->ptsfile = InitPtsFile();
portal1 = leak->portals[leak->numportals - 2];
MidpointWinding(portal1->winding, point2);
WriteLeakTrail(leak->ptsfile, point1, point2);
}
/*
=================
LineIntersect_r
Returns true if the line segment from point1 to point2 does not intersect any
of the faces in the node, false if it does.
=================
*/
static bool
LineIntersect_Leafnode(const node_t *node,
const vec3_t point1, const vec3_t point2)
{
face_t *const *markfaces;
const face_t *face;
for (markfaces = node->markfaces; *markfaces; markfaces++) {
for (face = *markfaces; face; face = face->original) {
const plane_t *const plane = &map.planes[face->planenum];
const vec_t dist1 = DotProduct(point1, plane->normal) - plane->dist;
const vec_t dist2 = DotProduct(point2, plane->normal) - plane->dist;
vec3_t mid, mins, maxs;
int i, j;
// Line segment doesn't cross the plane
if (dist1 < -ON_EPSILON && dist2 < -ON_EPSILON)
continue;
if (dist1 > ON_EPSILON && dist2 > ON_EPSILON)
continue;
if (fabs(dist1) < ON_EPSILON) {
if (fabs(dist2) < ON_EPSILON)
return false; /* too short/close */
VectorCopy(point1, mid);
} else if (fabs(dist2) < ON_EPSILON) {
VectorCopy(point2, mid);
} else {
/* Find the midpoint on the plane of the face */
vec3_t pointvec;
VectorSubtract(point2, point1, pointvec);
VectorMA(point1, dist1 / (dist1 - dist2), pointvec, mid);
}
// Do test here for point in polygon (face)
// Quick hack
mins[0] = mins[1] = mins[2] = VECT_MAX;
maxs[0] = maxs[1] = maxs[2] = -VECT_MAX;
for (i = 0; i < face->w.numpoints; i++)
for (j = 0; j < 3; j++) {
if (face->w.points[i][j] < mins[j])
mins[j] = face->w.points[i][j];
if (face->w.points[i][j] > maxs[j])
maxs[j] = face->w.points[i][j];
}
if (mid[0] < mins[0] - ON_EPSILON ||
mid[1] < mins[1] - ON_EPSILON ||
mid[2] < mins[2] - ON_EPSILON ||
mid[0] > maxs[0] + ON_EPSILON ||
mid[1] > maxs[1] + ON_EPSILON ||
mid[2] > maxs[2] + ON_EPSILON)
continue;
return false;
}
}
return true;
}
static bool
LineIntersect_r(const node_t *node, const vec3_t point1, const vec3_t point2)
{
if (node->contents)
return LineIntersect_Leafnode(node, point1, point2);
const plane_t *const plane = &map.planes[node->planenum];
const vec_t dist1 = DotProduct(point1, plane->normal) - plane->dist;
const vec_t dist2 = DotProduct(point2, plane->normal) - plane->dist;
if (dist1 < -ON_EPSILON && dist2 < -ON_EPSILON)
return LineIntersect_r(node->children[1], point1, point2);
if (dist1 > ON_EPSILON && dist2 > ON_EPSILON)
return LineIntersect_r(node->children[0], point1, point2);
if (!LineIntersect_r(node->children[0], point1, point2))
return false;
if (!LineIntersect_r(node->children[1], point1, point2))
return false;
return true;
}
/*
=================
SimplifyLeakline
=================
*/
static void
SimplifyLeakline(leakstate_t *leak, node_t *headnode)
{
int i, j;
const portal_t *portal1, *portal2;
vec3_t point1, point2;
if (leak->numportals < 2)
return;
i = 0;
portal1 = leak->portals[i];
while (i < leak->numportals - 1) {
MidpointWinding(portal1->winding, point1);
j = leak->numportals - 1;
while (j > i + 1) {
portal2 = leak->portals[j];
MidpointWinding(portal2->winding, point2);
if (LineIntersect_r(headnode, point1, point2))
break;
else
j--;
}
if (!leak->ptsfile)
leak->ptsfile = InitPtsFile();
portal2 = leak->portals[j];
MidpointWinding(portal2->winding, point2);
WriteLeakTrail(leak->ptsfile, point1, point2);
i = j;
portal1 = leak->portals[i];
}
}
static bool
FindLeaks_r(leakstate_t *leak, const int fillmark, node_t *node)
{
portal_t *portal;
int side;
bool leak_found;
if (node->contents == CONTENTS_SOLID || node->contents == CONTENTS_SKY)
return false;
if (node->fillmark == fillmark)
return false;
if (node->occupied) {
leak->entity = &map.entities[node->occupied];
leak->node = node;
leak->backdraw = 4000;
return true;
}
/* Mark this node so we don't visit it again */
node->fillmark = fillmark;
for (portal = node->portals; portal; portal = portal->next[!side]) {
side = (portal->nodes[0] == node);
leak_found = FindLeaks_r(leak, fillmark, portal->nodes[side]);
if (leak_found) {
/* If we're already written or written too much, bail */
if (map.leakfile || !leak->backdraw)
return true;
leak->backdraw--;
MarkLeakTrail(leak, portal);
return true;
}
}
return false;
}
/*
==================
RecursiveFillOutside
Already assumed here that we have checked for leaks, so just fill
==================
*/
static int
FillOutside_r(node_t *node, const int fillmark, int outleafs)
{
portal_t *portal;
int side;
if (node->contents == CONTENTS_SOLID || node->contents == CONTENTS_SKY)
return outleafs;
if (node->fillmark == fillmark)
return outleafs;
node->fillmark = fillmark;
node->contents = CONTENTS_SOLID;
outleafs++;
for (portal = node->portals; portal; portal = portal->next[!side]) {
side = (portal->nodes[0] == node);
outleafs = FillOutside_r(portal->nodes[side], fillmark, outleafs);
}
return outleafs;
}
/*
==================
ClearOutFaces
==================
*/
static void
ClearOutFaces(node_t *node)
{
face_t **markfaces;
if (node->planenum != -1) {
ClearOutFaces(node->children[0]);
ClearOutFaces(node->children[1]);
return;
}
if (node->contents != CONTENTS_SOLID)
return;
for (markfaces = node->markfaces; *markfaces; markfaces++) {
// mark all the original faces that are removed
(*markfaces)->w.numpoints = 0;
}
node->faces = NULL;
}
//=============================================================================
/*
===========
FillOutside
===========
*/
bool
FillOutside(node_t *node, const int hullnum, const int numportals)
{
int i, side, outleafs;
bool inside, leak_found;
leakstate_t leak;
const mapentity_t *entity;
node_t *fillnode;
Message(msgProgress, "FillOutside");
if (options.fNofill) {
Message(msgStat, "skipped");
return false;
}
inside = false;
for (i = 1; i < map.numentities(); i++) {
entity = &map.entities.at(i);
if (!VectorCompare(entity->origin, vec3_origin)) {
if (PlaceOccupant(i, entity->origin, node))
inside = true;
}
}
if (!inside) {
Message(msgWarning, warnNoFilling, hullnum);
return false;
}
/* Set up state for the recursive fill */
memset(&leak, 0, sizeof(leak));
if (!map.leakfile) {
leak.portals = (const portal_t **)AllocMem(OTHER, sizeof(portal_t *) * numportals, true);
leak.maxportals = numportals;
}
/* first check to see if an occupied leaf is hit */
side = (outside_node.portals->nodes[0] == &outside_node);
fillnode = outside_node.portals->nodes[side];
leak_found = FindLeaks_r(&leak, ++map.fillmark, fillnode);
if (leak_found) {
const vec_t *origin = leak.entity->origin;
Message(msgWarning, warnMapLeak, origin[0], origin[1], origin[2]);
if (map.leakfile)
return false;
if (!options.fOldleak)
SimplifyLeakline(&leak, node);
StripExtension(options.szBSPName);
if (leak.ptsfile) {
fclose(leak.ptsfile);
Message(msgLiteral, "Leak file written to %s.pts\n",
options.szBSPName);
}
if (options.fBspleak && leak.porfile) {
fseek(leak.porfile, 0, SEEK_SET);
fprintf(leak.porfile, "%11d", leak.numwritten);
fclose(leak.porfile);
Message(msgLiteral, "BSP portal file written to %s.por\n",
options.szBSPName);
}
FreeMem(leak.portals, OTHER, sizeof(portal_t *) * numportals);
map.leakfile = true;
/* Get rid of the .prt file since the map has a leak */
strcat(options.szBSPName, ".prt");
remove(options.szBSPName);
return false;
}
if (leak.portals) {
FreeMem(leak.portals, OTHER, sizeof(portal_t *) * numportals);
leak.portals = NULL;
}
/* now go back and fill outside with solid contents */
fillnode = outside_node.portals->nodes[side];
outleafs = FillOutside_r(fillnode, ++map.fillmark, 0);
/* remove faces from filled in leafs */
ClearOutFaces(node);
Message(msgStat, "%8d outleafs", outleafs);
return true;
}