ericw-tools/qbsp/writebsp.c

490 lines
12 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.
*/
// writebsp.c
#include "qbsp.h"
#include "wad.h"
static int firstface;
static int *planemapping;
static void
ExportNodePlanes_r(node_t *node)
{
plane_t *plane;
dplane_t *dplane;
int i;
vec3_t tmp;
struct lumpdata *planes = &pWorldEnt->lumps[BSPPLANE];
if (node->planenum == -1)
return;
if (planemapping[node->planenum] == -1) {
plane = &map.planes[node->planenum];
dplane = (dplane_t *)planes->data;
// search for an equivalent plane
for (i = 0; i < planes->index; i++, dplane++) {
tmp[0] = dplane->normal[0];
tmp[1] = dplane->normal[1];
tmp[2] = dplane->normal[2];
if (DotProduct(tmp, plane->normal) > 1 - 0.00001 &&
fabs(dplane->dist - plane->dist) < 0.01 &&
dplane->type == plane->type)
break;
}
// a new plane
planemapping[node->planenum] = i;
if (i == planes->index) {
if (planes->index >= planes->count)
Error(errLowPlaneCount);
plane = &map.planes[node->planenum];
dplane = (dplane_t *)planes->data + planes->index;
dplane->normal[0] = plane->normal[0];
dplane->normal[1] = plane->normal[1];
dplane->normal[2] = plane->normal[2];
dplane->dist = plane->dist;
dplane->type = plane->type;
planes->index++;
map.cTotal[BSPPLANE]++;
}
}
node->outputplanenum = planemapping[node->planenum];
ExportNodePlanes_r(node->children[0]);
ExportNodePlanes_r(node->children[1]);
}
/*
==================
ExportNodePlanes
==================
*/
void
ExportNodePlanes(node_t *nodes)
{
struct lumpdata *planes = &pWorldEnt->lumps[BSPPLANE];
// OK just need one plane array, stick it in worldmodel
if (!planes->data) {
// I'd like to use map.numplanes here but we haven't seen every entity yet...
planes->count = map.maxplanes;
planes->data = AllocMem(BSPPLANE, planes->count, true);
}
// TODO: make one-time allocation?
planemapping = AllocMem(OTHER, sizeof(int) * planes->count, true);
memset(planemapping, -1, sizeof(int) * planes->count);
ExportNodePlanes_r(nodes);
FreeMem(planemapping, OTHER, sizeof(int) * planes->count);
}
//===========================================================================
/*
==================
CountClipNodes_r
==================
*/
static void
CountClipNodes_r(mapentity_t *ent, node_t *node)
{
if (node->planenum == -1)
return;
ent->lumps[BSPCLIPNODE].count++;
CountClipNodes_r(ent, node->children[0]);
CountClipNodes_r(ent, node->children[1]);
}
/*
==================
ExportClipNodes_r
==================
*/
static int
ExportClipNodes_r(mapentity_t *ent, node_t *node)
{
int i, c;
dclipnode_t *cn;
face_t *f, *next;
struct lumpdata *clipnodes = &ent->lumps[BSPCLIPNODE];
// FIXME: free more stuff?
if (node->planenum == -1) {
c = node->contents;
FreeMem(node, NODE, 1);
return c;
}
// emit a clipnode
c = map.cTotal[BSPCLIPNODE];
cn = (dclipnode_t *)clipnodes->data + clipnodes->index;
clipnodes->index++;
map.cTotal[BSPCLIPNODE]++;
cn->planenum = node->outputplanenum;
for (i = 0; i < 2; i++)
cn->children[i] = ExportClipNodes_r(ent, node->children[i]);
for (f = node->faces; f; f = next) {
next = f->next;
memset(f, 0, sizeof(face_t));
FreeMem(f, FACE, 1);
}
FreeMem(node, NODE, 1);
return c;
}
/*
==================
ExportClipNodes
Called after the clipping hull is completed. Generates a disk format
representation and frees the original memory.
This gets real ugly. Gets called twice per entity, once for each clip hull.
First time just store away data, second time fix up reference points to
accomodate new data interleaved with old.
==================
*/
void
ExportClipNodes(mapentity_t *ent, node_t *nodes, const int hullnum)
{
int oldcount, i, diff;
int clipcount = 0;
dclipnode_t *pTemp;
struct lumpdata *clipnodes = &ent->lumps[BSPCLIPNODE];
dmodel_t *model = (dmodel_t *)ent->lumps[BSPMODEL].data;
oldcount = clipnodes->count;
/* Count nodes before this one */
for (i = 0; i < ent - map.entities; i++)
clipcount += map.entities[i].lumps[BSPCLIPNODE].count;
model->headnode[hullnum] = clipcount + oldcount;
CountClipNodes_r(ent, nodes);
if (clipnodes->count > MAX_BSP_CLIPNODES)
Error(errTooManyClipnodes);
pTemp = clipnodes->data;
clipnodes->data = AllocMem(BSPCLIPNODE, clipnodes->count, true);
if (pTemp) {
memcpy(clipnodes->data, pTemp, oldcount * rgcMemSize[BSPCLIPNODE]);
FreeMem(pTemp, BSPCLIPNODE, oldcount);
/* Worth special-casing for entity 0 (no modification needed) */
diff = clipcount - model->headnode[1];
if (diff != 0) {
model->headnode[1] += diff;
for (i = 0; i < oldcount; i++) {
pTemp = (dclipnode_t *)clipnodes->data + i;
if (pTemp->children[0] < MAX_BSP_CLIPNODES)
pTemp->children[0] += diff;
if (pTemp->children[1] < MAX_BSP_CLIPNODES)
pTemp->children[1] += diff;
}
}
}
map.cTotal[BSPCLIPNODE] = clipcount + oldcount;
ExportClipNodes_r(ent, nodes);
}
//===========================================================================
/*
==================
CountLeaves
==================
*/
static void
CountLeaves(mapentity_t *ent, node_t *node)
{
face_t **fp, *f;
const texinfo_t *texinfo = pWorldEnt->lumps[BSPTEXINFO].data;
ent->lumps[BSPLEAF].count++;
for (fp = node->markfaces; *fp; fp++) {
if (texinfo[(*fp)->texturenum].flags & TEX_SKIP)
continue;
for (f = *fp; f; f = f->original)
ent->lumps[BSPMARKSURF].count++;
}
}
/*
==================
CountNodes_r
==================
*/
static void
CountNodes_r(mapentity_t *ent, node_t *node)
{
int i;
ent->lumps[BSPNODE].count++;
for (i = 0; i < 2; i++) {
if (node->children[i]->planenum == -1) {
if (node->children[i]->contents != CONTENTS_SOLID)
CountLeaves(ent, node->children[i]);
} else
CountNodes_r(ent, node->children[i]);
}
}
/*
==================
CountNodes
==================
*/
static void
CountNodes(mapentity_t *ent, node_t *headnode)
{
if (headnode->contents < 0)
CountLeaves(ent, headnode);
else
CountNodes_r(ent, headnode);
}
/*
==================
ExportLeaf
==================
*/
static void
ExportLeaf(mapentity_t *ent, node_t *node)
{
face_t **fp, *f;
dleaf_t *dleaf;
const texinfo_t *texinfo = pWorldEnt->lumps[BSPTEXINFO].data;
struct lumpdata *leaves = &ent->lumps[BSPLEAF];
struct lumpdata *marksurfs = &ent->lumps[BSPMARKSURF];
unsigned short *marksurfnums = marksurfs->data;
// ptr arithmetic to get correct leaf in memory
dleaf = (dleaf_t *)leaves->data + leaves->index;
leaves->index++;
map.cTotal[BSPLEAF]++;
dleaf->contents = node->contents;
//
// write bounding box info
//
// VectorCopy don't work since dest are shorts
dleaf->mins[0] = (short)node->mins[0];
dleaf->mins[1] = (short)node->mins[1];
dleaf->mins[2] = (short)node->mins[2];
dleaf->maxs[0] = (short)node->maxs[0];
dleaf->maxs[1] = (short)node->maxs[1];
dleaf->maxs[2] = (short)node->maxs[2];
dleaf->visofs = -1; // no vis info yet
// write the marksurfaces
dleaf->firstmarksurface = map.cTotal[BSPMARKSURF];
for (fp = node->markfaces; *fp; fp++) {
f = *fp;
if (texinfo[f->texturenum].flags & TEX_SKIP)
continue;
/* emit a marksurface */
do {
marksurfnums[marksurfs->index] = f->outputnumber;
marksurfs->index++;
map.cTotal[BSPMARKSURF]++;
f = f->original; /* grab tjunction split faces */
} while (f);
}
dleaf->nummarksurfaces = map.cTotal[BSPMARKSURF] - dleaf->firstmarksurface;
}
/*
==================
ExportDrawNodes_r
==================
*/
static void
ExportDrawNodes_r(mapentity_t *ent, node_t *node)
{
dnode_t *n;
int i;
struct lumpdata *nodes = &ent->lumps[BSPNODE];
// ptr arithmetic to get correct node in memory
n = (dnode_t *)nodes->data + nodes->index;
nodes->index++;
map.cTotal[BSPNODE]++;
// VectorCopy doesn't work since dest are shorts
n->mins[0] = (short)node->mins[0];
n->mins[1] = (short)node->mins[1];
n->mins[2] = (short)node->mins[2];
n->maxs[0] = (short)node->maxs[0];
n->maxs[1] = (short)node->maxs[1];
n->maxs[2] = (short)node->maxs[2];
n->planenum = node->outputplanenum;
n->firstface = node->firstface;
n->numfaces = node->numfaces;
// recursively output the other nodes
for (i = 0; i < 2; i++) {
if (node->children[i]->planenum == -1) {
if (node->children[i]->contents == CONTENTS_SOLID)
n->children[i] = -1;
else {
n->children[i] = -(map.cTotal[BSPLEAF] + 1);
ExportLeaf(ent, node->children[i]);
}
} else {
n->children[i] = map.cTotal[BSPNODE];
ExportDrawNodes_r(ent, node->children[i]);
}
}
}
/*
==================
ExportDrawNodes
==================
*/
void
ExportDrawNodes(mapentity_t *ent, node_t *headnode)
{
int i;
dmodel_t *bm;
struct lumpdata *nodes = &ent->lumps[BSPNODE];
struct lumpdata *leaves = &ent->lumps[BSPLEAF];
struct lumpdata *marksurfs = &ent->lumps[BSPMARKSURF];
// Get a feel for how many of these things there are.
CountNodes(ent, headnode);
// emit a model
nodes->data = AllocMem(BSPNODE, nodes->count, true);
leaves->data = AllocMem(BSPLEAF, leaves->count, true);
marksurfs->data = AllocMem(BSPMARKSURF, marksurfs->count, true);
/*
* Set leaf 0 properly (must be solid). cLeaves etc incremented in
* BeginBSPFile.
*/
((dleaf_t *)pWorldEnt->lumps[BSPLEAF].data)->contents = CONTENTS_SOLID;
bm = (dmodel_t *)ent->lumps[BSPMODEL].data;
bm->headnode[0] = map.cTotal[BSPNODE];
bm->firstface = firstface;
bm->numfaces = map.cTotal[BSPFACE] - firstface;
firstface = map.cTotal[BSPFACE];
if (headnode->contents < 0)
ExportLeaf(ent, headnode);
else
ExportDrawNodes_r(ent, headnode);
// Not counting initial vis leaf
bm->visleafs = leaves->count;
if (ent == pWorldEnt)
bm->visleafs--;
for (i = 0; i < 3; i++) {
bm->mins[i] = headnode->mins[i] + SIDESPACE + 1; // remove the padding
bm->maxs[i] = headnode->maxs[i] - SIDESPACE - 1;
}
}
//=============================================================================
/*
==================
BeginBSPFile
==================
*/
void
BeginBSPFile(void)
{
firstface = 0;
// First edge must remain unused because 0 can't be negated
pWorldEnt->lumps[BSPEDGE].count++;
pWorldEnt->lumps[BSPEDGE].index++;
map.cTotal[BSPEDGE]++;
// Leave room for leaf 0 (must be solid)
pWorldEnt->lumps[BSPLEAF].count++;
pWorldEnt->lumps[BSPLEAF].index++;
map.cTotal[BSPLEAF]++;
}
/*
* Remove any extra texinfo flags we added that are not normally written
* Standard quake utils only ever write the TEX_SPECIAL flag.
*/
static void
CleanBSPTexinfoFlags(void)
{
texinfo_t *texinfo = pWorldEnt->lumps[BSPTEXINFO].data;
const int num_texinfo = pWorldEnt->lumps[BSPTEXINFO].index;
int i;
for (i = 0; i < num_texinfo; i++, texinfo++)
texinfo->flags &= TEX_SPECIAL;
}
/*
==================
FinishBSPFile
==================
*/
void
FinishBSPFile(void)
{
dplane_t *pTemp;
struct lumpdata *planes = &pWorldEnt->lumps[BSPPLANE];
options.fVerbose = true;
Message(msgProgress, "WriteBSPFile");
// TODO: Fix this somewhere else?
pTemp = AllocMem(BSPPLANE, map.cTotal[BSPPLANE], true);
memcpy(pTemp, planes->data, map.cTotal[BSPPLANE] * rgcMemSize[BSPPLANE]);
FreeMem(planes->data, BSPPLANE, planes->count);
planes->data = pTemp;
planes->count = map.cTotal[BSPPLANE];
PrintBSPFileSizes();
CleanBSPTexinfoFlags();
WriteBSPFile();
options.fVerbose = options.fAllverbose;
}