mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-27 06:34:11 +00:00
532 lines
11 KiB
C
532 lines
11 KiB
C
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
|
|
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.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
static __attribute__ ((used)) const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
|
|
#include "QF/dstring.h"
|
|
#include "QF/qendian.h"
|
|
#include "QF/sys.h"
|
|
#include "QF/va.h"
|
|
#include "QF/wad.h"
|
|
|
|
#include "brush.h"
|
|
#include "bsp5.h"
|
|
#include "options.h"
|
|
#include "writebsp.h"
|
|
|
|
/** \addtogroup qfbsp_writebsp
|
|
*/
|
|
//@{
|
|
|
|
int headclipnode;
|
|
int firstface;
|
|
|
|
|
|
int
|
|
FindFinalPlane (const dplane_t *p)
|
|
{
|
|
const dplane_t *dplane;
|
|
int i;
|
|
|
|
for (i = 0, dplane = bsp->planes; i < bsp->numplanes; i++, dplane++) {
|
|
if (p->type != dplane->type)
|
|
continue;
|
|
if (p->dist != dplane->dist)
|
|
continue;
|
|
if (!VectorCompare (p->normal, dplane->normal))
|
|
continue;
|
|
return i;
|
|
}
|
|
|
|
// new plane
|
|
if (bsp->numplanes == MAX_MAP_PLANES)
|
|
Sys_Error ("numplanes == MAX_MAP_PLANES");
|
|
BSP_AddPlane (bsp, p);
|
|
|
|
return bsp->numplanes - 1;
|
|
}
|
|
|
|
int planemapping[MAX_MAP_PLANES];
|
|
|
|
/** Recursively write the nodes' planes to the bsp file.
|
|
|
|
If the plane has already been written, do not write it again. Set the
|
|
node's output plane number.
|
|
|
|
\param node The current node of which to write the plane.
|
|
*/
|
|
static void
|
|
WriteNodePlanes_r (node_t *node)
|
|
{
|
|
dplane_t dplane;
|
|
plane_t *plane;
|
|
|
|
if (node->planenum == -1)
|
|
return;
|
|
if (planemapping[node->planenum] == -1) { // a new plane
|
|
planemapping[node->planenum] = bsp->numplanes;
|
|
|
|
plane = &planes[node->planenum];
|
|
|
|
VectorCopy (plane->normal, dplane.normal);
|
|
dplane.dist = plane->dist;
|
|
dplane.type = plane->type;
|
|
BSP_AddPlane (bsp, &dplane);
|
|
}
|
|
|
|
node->outputplanenum = planemapping[node->planenum];
|
|
|
|
WriteNodePlanes_r (node->children[0]);
|
|
WriteNodePlanes_r (node->children[1]);
|
|
}
|
|
|
|
void
|
|
WriteNodePlanes (node_t *nodes)
|
|
{
|
|
memset (planemapping, -1, sizeof (planemapping));
|
|
WriteNodePlanes_r (nodes);
|
|
}
|
|
|
|
/** Recursively write clip nodes to the bsp file.
|
|
|
|
\note The node will be freed.
|
|
|
|
\param node The node to be written to the bsp file.
|
|
*/
|
|
static int
|
|
WriteClipNodes_r (node_t *node)
|
|
{
|
|
dclipnode_t cn;
|
|
int num, c, i;
|
|
|
|
// FIXME: free more stuff?
|
|
if (node->planenum == -1) {
|
|
num = node->contents;
|
|
free (node);
|
|
return num;
|
|
}
|
|
// emit a clipnode
|
|
c = bsp->numclipnodes;
|
|
BSP_AddClipnode (bsp, &cn);
|
|
cn.planenum = node->outputplanenum;
|
|
for (i = 0; i < 2; i++)
|
|
cn.children[i] = WriteClipNodes_r (node->children[i]);
|
|
bsp->clipnodes[c] = cn;
|
|
|
|
free (node);
|
|
return c;
|
|
}
|
|
|
|
void
|
|
WriteClipNodes (node_t *nodes)
|
|
{
|
|
headclipnode = bsp->numclipnodes;
|
|
WriteClipNodes_r (nodes);
|
|
}
|
|
|
|
/** Write a leaf node to the bsp file.
|
|
|
|
\param node The leaf node to be written to the bsp file.
|
|
*/
|
|
static void
|
|
WriteLeaf (const node_t *node)
|
|
{
|
|
dleaf_t leaf_p;
|
|
face_t **fp, *f;
|
|
|
|
// emit a leaf
|
|
leaf_p.contents = node->contents;
|
|
|
|
// write bounding box info
|
|
VectorCopy (node->mins, leaf_p.mins);
|
|
VectorCopy (node->maxs, leaf_p.maxs);
|
|
|
|
leaf_p.visofs = -1; // no vis info yet
|
|
|
|
// write the marksurfaces
|
|
leaf_p.firstmarksurface = bsp->nummarksurfaces;
|
|
|
|
for (fp = node->markfaces; *fp; fp++) {
|
|
// emit a marksurface
|
|
if (bsp->nummarksurfaces == MAX_MAP_MARKSURFACES)
|
|
Sys_Error ("nummarksurfaces == MAX_MAP_MARKSURFACES");
|
|
f = *fp;
|
|
if (f->texturenum < 0)
|
|
continue;
|
|
do {
|
|
BSP_AddMarkSurface (bsp, f->outputnumber);
|
|
f = f->original; // grab tjunction split faces
|
|
} while (f);
|
|
}
|
|
|
|
leaf_p.nummarksurfaces = bsp->nummarksurfaces - leaf_p.firstmarksurface;
|
|
|
|
memset (leaf_p.ambient_level, 0, sizeof (leaf_p.ambient_level));
|
|
BSP_AddLeaf (bsp, &leaf_p);
|
|
}
|
|
|
|
/** Recursively write the draw nodes of the map bsp.
|
|
|
|
\param node The current node to be written.
|
|
*/
|
|
static void
|
|
WriteDrawNodes_r (const node_t *node)
|
|
{
|
|
static dnode_t dummy;
|
|
dnode_t *n;
|
|
int i;
|
|
int nodenum = bsp->numnodes;
|
|
|
|
// emit a node
|
|
if (bsp->numnodes == MAX_MAP_NODES)
|
|
Sys_Error ("numnodes == MAX_MAP_NODES");
|
|
BSP_AddNode (bsp, &dummy);
|
|
n = &bsp->nodes[nodenum];
|
|
|
|
VectorCopy (node->mins, n->mins);
|
|
VectorCopy (node->maxs, n->maxs);
|
|
|
|
n->planenum = node->outputplanenum;
|
|
n->firstface = node->firstface;
|
|
n->numfaces = node->numfaces;
|
|
|
|
// recursively output the other nodes
|
|
for (i = 0; i < 2; i++) {
|
|
n = &bsp->nodes[nodenum];
|
|
if (node->children[i]->planenum == -1) {
|
|
if (node->children[i]->contents == CONTENTS_SOLID)
|
|
n->children[i] = -1;
|
|
else {
|
|
n->children[i] = -(bsp->numleafs + 1);
|
|
WriteLeaf (node->children[i]);
|
|
}
|
|
} else {
|
|
n->children[i] = bsp->numnodes;
|
|
WriteDrawNodes_r (node->children[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
WriteDrawNodes (const node_t *headnode)
|
|
{
|
|
dmodel_t bm;
|
|
int start, i;
|
|
|
|
#if 0
|
|
if (headnode->contents < 0)
|
|
Sys_Error ("FinishBSPModel: empty model");
|
|
#endif
|
|
|
|
// emit a model
|
|
if (bsp->nummodels == MAX_MAP_MODELS)
|
|
Sys_Error ("nummodels == MAX_MAP_MODELS");
|
|
|
|
bm.headnode[0] = bsp->numnodes;
|
|
for (i = 1; i < MAX_MAP_HULLS; i++)
|
|
bm.headnode[i] = 0;
|
|
bm.firstface = firstface;
|
|
bm.numfaces = bsp->numfaces - firstface;
|
|
firstface = bsp->numfaces;
|
|
|
|
start = bsp->numleafs;
|
|
|
|
if (headnode->contents < 0)
|
|
WriteLeaf (headnode);
|
|
else
|
|
WriteDrawNodes_r (headnode);
|
|
bm.visleafs = bsp->numleafs - start;
|
|
|
|
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;
|
|
}
|
|
VectorZero (bm.origin);
|
|
BSP_AddModel (bsp, &bm);
|
|
// FIXME: are all the children decendant of padded nodes?
|
|
}
|
|
|
|
void
|
|
BumpModel (int hullnum)
|
|
{
|
|
static dmodel_t bm;
|
|
|
|
// emit a model
|
|
bm.headnode[hullnum] = headclipnode;
|
|
BSP_AddModel (bsp, &bm);
|
|
}
|
|
|
|
typedef struct wadlist_s {
|
|
struct wadlist_s *next;
|
|
const char *path;
|
|
wad_t *wad;
|
|
} wadlist_t;
|
|
|
|
wadlist_t *wadlist;
|
|
|
|
/** Load a texture wad file.
|
|
\param path The path to the wad file.
|
|
*/
|
|
static int
|
|
TEX_InitFromWad (const char *path)
|
|
{
|
|
wadlist_t *wl;
|
|
wad_t *wad;
|
|
|
|
wad = wad_open (path);
|
|
#ifdef HAVE_ZLIB
|
|
if (!wad)
|
|
wad = wad_open (path = va ("%s.gz", path));
|
|
#endif
|
|
if (!wad)
|
|
return -1;
|
|
printf ("wadfile: %s\n", path);
|
|
|
|
wl = calloc (1, sizeof (wadlist_t));
|
|
wl->path = strdup (path);
|
|
|
|
wl->wad = wad;
|
|
|
|
wl->next = wadlist;
|
|
wadlist = wl;
|
|
return 0;
|
|
}
|
|
|
|
/** Read a lump's data.
|
|
|
|
Searches all loaded wad files for the lump.
|
|
|
|
\param name The name of the lump for which to search.
|
|
\param dest The destination buffer to which the lump's data will be
|
|
written.
|
|
\return The size of the lump's data or 0 if the lump was not
|
|
found.
|
|
*/
|
|
static int
|
|
LoadLump (const char *name, dstring_t *dest)
|
|
{
|
|
int r;
|
|
int ofs = dest->size;
|
|
wadlist_t *wl;
|
|
lumpinfo_t *lump;
|
|
QFile *texfile;
|
|
|
|
for (wl = wadlist; wl; wl = wl->next) {
|
|
if ((lump = wad_find_lump (wl->wad, name))) {
|
|
dest->size += lump->disksize;
|
|
dstring_adjust (dest);
|
|
texfile = Qopen (wl->path, "rbz");
|
|
if (!texfile) {
|
|
printf ("couldn't open %s\n", wl->path);
|
|
continue;
|
|
}
|
|
r = Qseek (texfile, lump->filepos, SEEK_SET);
|
|
if (r == -1) {
|
|
printf ("seek error: %s:%s %d\n", wl->path,
|
|
lump->name, lump->filepos);
|
|
Qclose (texfile);
|
|
continue;
|
|
}
|
|
r = Qread (texfile, dest->str + ofs, lump->disksize);
|
|
Qclose (texfile);
|
|
if (r != lump->disksize) {
|
|
printf ("seek error: %s:%s %d\n", wl->path,
|
|
lump->name, lump->disksize);
|
|
continue;
|
|
}
|
|
return lump->disksize;
|
|
}
|
|
}
|
|
|
|
printf ("WARNING: texture %s not found\n", name);
|
|
return 0;
|
|
}
|
|
|
|
/** Search loaded miptex for animated textures and load their animations.
|
|
*/
|
|
static void
|
|
AddAnimatingTextures (void)
|
|
{
|
|
int base, i, j;
|
|
char name[32];
|
|
wadlist_t *wl;
|
|
|
|
base = nummiptex;
|
|
|
|
name[sizeof (name) - 1] = 0;
|
|
|
|
for (i = 0; i < base; i++) {
|
|
if (miptex[i][0] != '+')
|
|
continue;
|
|
strncpy (name, miptex[i], sizeof (name) - 1);
|
|
|
|
for (j = 0; j < 20; j++) {
|
|
if (j < 10)
|
|
name[1] = '0' + j;
|
|
else
|
|
name[1] = 'A' + j - 10; // alternate animation
|
|
|
|
// see if this name exists in the wadfile
|
|
for (wl = wadlist; wl; wl = wl->next) {
|
|
if (wad_find_lump (wl->wad, name)) {
|
|
FindMiptex (name); // add to the miptex list
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nummiptex - base)
|
|
printf ("added %i texture frames\n", nummiptex - base);
|
|
}
|
|
|
|
/** Write the miptex data to the bsp file.
|
|
|
|
The miptex data is read from the wad files specified by the \c wad or
|
|
\c _wad key of the world entity. The files are a semi-colon separated list
|
|
(this is a QuakeForge extension). The \c wadpath is used to search for the
|
|
files.
|
|
*/
|
|
static void
|
|
WriteMiptex (void)
|
|
{
|
|
dstring_t *data;
|
|
char *wad_list, *wad, *w;
|
|
char *path_list, *path, *p;
|
|
dstring_t *fullpath;
|
|
dmiptexlump_t *l;
|
|
int i, len, res = -1;
|
|
|
|
wad_list = (char *) ValueForKey (&entities[0], "_wad");
|
|
if (!wad_list || !wad_list[0]) {
|
|
wad_list = (char *) ValueForKey (&entities[0], "wad");
|
|
if (!wad_list || !wad_list[0]) {
|
|
printf ("WARNING: no wadfile specified\n");
|
|
bsp->texdatasize = 0;
|
|
return;
|
|
}
|
|
}
|
|
fullpath = dstring_new ();
|
|
wad = wad_list = strdup (wad_list);
|
|
w = strchr (wad, ';');
|
|
if (w)
|
|
*w++ = 0;
|
|
while (1) {
|
|
path = path_list = strdup (options.wadpath);
|
|
p = strchr (path, ';');
|
|
if (p)
|
|
*p++ = 0;
|
|
while (1) {
|
|
dsprintf (fullpath, "%s%s%s", path, path[0] ? "/" : "", wad);
|
|
res = TEX_InitFromWad (fullpath->str);
|
|
if (!res)
|
|
break;
|
|
path = p;
|
|
if (!path || !*path)
|
|
break;
|
|
p = strchr (path, ';');
|
|
if (p)
|
|
*p++ = 0;
|
|
}
|
|
free (path_list);
|
|
if (res == -1)
|
|
Sys_Error ("couldn't open %s[.gz]", wad);
|
|
|
|
wad = w;
|
|
if (!wad || !*wad)
|
|
break;
|
|
w = strchr (wad, ';');
|
|
if (w)
|
|
*w++ = 0;
|
|
}
|
|
free (wad_list);
|
|
dstring_delete (fullpath);
|
|
|
|
AddAnimatingTextures ();
|
|
|
|
data = dstring_new ();
|
|
data->size = sizeof (dmiptexlump_t);
|
|
dstring_adjust (data);
|
|
|
|
l = (dmiptexlump_t *) data->str;
|
|
l->nummiptex = nummiptex;
|
|
data->size = (char *) &l->dataofs[nummiptex] - data->str;
|
|
dstring_adjust (data);
|
|
|
|
for (i = 0; i < nummiptex; i++) {
|
|
l = (dmiptexlump_t *) data->str;
|
|
l->dataofs[i] = data->size;
|
|
len = LoadLump (miptex[i], data);
|
|
l = (dmiptexlump_t *) data->str;
|
|
if (!len)
|
|
l->dataofs[i] = -1; // didn't find the texture
|
|
}
|
|
BSP_AddTextures (bsp, (byte *) data->str, data->size);
|
|
}
|
|
|
|
void
|
|
BeginBSPFile (void)
|
|
{
|
|
static dedge_t edge;
|
|
static dleaf_t leaf;
|
|
// edge 0 is not used, because 0 can't be negated
|
|
BSP_AddEdge (bsp, &edge);
|
|
|
|
// leaf 0 is common solid with no faces
|
|
leaf.contents = CONTENTS_SOLID;
|
|
leaf.visofs = -1;
|
|
BSP_AddLeaf (bsp, &leaf);
|
|
|
|
firstface = 0;
|
|
}
|
|
|
|
void
|
|
FinishBSPFile (void)
|
|
{
|
|
QFile *f;
|
|
|
|
printf ("--- FinishBSPFile ---\n");
|
|
|
|
WriteMiptex ();
|
|
|
|
// XXX PrintBSPFileSizes ();
|
|
printf ("WriteBSPFile: %s\n", options.bspfile);
|
|
f = Qopen (options.bspfile, "wb");
|
|
if (!f)
|
|
Sys_Error ("couldn't open %s. %s", options.bspfile, strerror(errno));
|
|
WriteBSPFile (bsp, f);
|
|
Qclose (f);
|
|
}
|
|
|
|
//@}
|