533 lines
16 KiB
C++
533 lines
16 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/qbsp.hh>
|
|
#include <qbsp/wad.hh>
|
|
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
|
|
static void
|
|
AssertVanillaContentType(int content)
|
|
{
|
|
if (options.target_version->quake2) {
|
|
return;
|
|
}
|
|
|
|
switch (content) {
|
|
case CONTENTS_EMPTY:
|
|
case CONTENTS_SOLID:
|
|
case CONTENTS_WATER:
|
|
case CONTENTS_SLIME:
|
|
case CONTENTS_LAVA:
|
|
case CONTENTS_SKY:
|
|
break;
|
|
default:
|
|
Error("Internal error: Tried to save compiler-internal contents type %s\n", GetContentsName(content));
|
|
}
|
|
}
|
|
|
|
static int
|
|
RemapContentsForExport_(int content)
|
|
{
|
|
if (content == CONTENTS_DETAIL_FENCE) {
|
|
/*
|
|
* This is for func_detail_wall.. we want to write a solid leaf that has faces,
|
|
* because it may be possible to see inside (fence textures).
|
|
*
|
|
* Normally solid leafs are not written and just referenced as leaf 0.
|
|
*/
|
|
return CONTENTS_SOLID;
|
|
}
|
|
if (content == CONTENTS_ILLUSIONARY_VISBLOCKER) {
|
|
return CONTENTS_EMPTY;
|
|
}
|
|
return content;
|
|
}
|
|
|
|
static int
|
|
RemapContentsForExport(int content)
|
|
{
|
|
content = RemapContentsForExport_(content);
|
|
|
|
if (options.target_version->quake2) {
|
|
switch (content) {
|
|
case CONTENTS_EMPTY:
|
|
return 0;
|
|
case CONTENTS_SOLID:
|
|
case CONTENTS_SKY:
|
|
return Q2_CONTENTS_SOLID;
|
|
case CONTENTS_WATER:
|
|
return Q2_CONTENTS_WATER;
|
|
case CONTENTS_SLIME:
|
|
return Q2_CONTENTS_SLIME;
|
|
case CONTENTS_LAVA:
|
|
return Q2_CONTENTS_LAVA;
|
|
case CONTENTS_CLIP:
|
|
return Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP;
|
|
default:
|
|
Error("dunno what to do with contents %i\n", content);
|
|
}
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Returns the output plane number
|
|
*/
|
|
int
|
|
ExportMapPlane(int planenum)
|
|
{
|
|
qbsp_plane_t *plane = &map.planes.at(planenum);
|
|
|
|
if (plane->outputplanenum != PLANENUM_LEAF)
|
|
return plane->outputplanenum; // already output.
|
|
|
|
const int newIndex = static_cast<int>(map.exported_planes.size());
|
|
map.exported_planes.push_back({});
|
|
|
|
dplane_t *dplane = &map.exported_planes.back();
|
|
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;
|
|
|
|
plane->outputplanenum = newIndex;
|
|
return newIndex;
|
|
}
|
|
|
|
int
|
|
ExportMapTexinfo(int texinfonum)
|
|
{
|
|
mtexinfo_t *src = &map.mtexinfos.at(texinfonum);
|
|
if (src->outputnum != PLANENUM_LEAF)
|
|
return src->outputnum;
|
|
|
|
// this will be the index of the exported texinfo in the BSP lump
|
|
const int i = static_cast<int>(map.exported_texinfos.size());
|
|
|
|
map.exported_texinfos.push_back({});
|
|
gtexinfo_t* dest = &map.exported_texinfos.back();
|
|
memset(dest, 0, sizeof(dest));
|
|
|
|
dest->flags = static_cast<int32_t>(src->flags & TEX_SPECIAL);
|
|
dest->miptex = src->miptex;
|
|
for (int j=0; j<2; j++) {
|
|
for (int k=0; k<4; k++) {
|
|
dest->vecs[j][k] = src->vecs[j][k];
|
|
}
|
|
}
|
|
|
|
strcpy(dest->texture, map.texinfoTextureName(texinfonum).c_str());
|
|
//dest->flags = map.miptex[src->miptex].flags;
|
|
dest->value = map.miptex[src->miptex].value;
|
|
|
|
src->outputnum = i;
|
|
return i;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
/*
|
|
==================
|
|
ExportClipNodes
|
|
==================
|
|
*/
|
|
static int
|
|
ExportClipNodes(mapentity_t *entity, node_t *node)
|
|
{
|
|
bsp2_dclipnode_t *clipnode;
|
|
face_t *face, *next;
|
|
|
|
// FIXME: free more stuff?
|
|
if (node->planenum == PLANENUM_LEAF) {
|
|
int contents = node->contents;
|
|
free(node);
|
|
return contents;
|
|
}
|
|
|
|
/* emit a clipnode */
|
|
const int nodenum = static_cast<int>(map.exported_clipnodes.size());
|
|
map.exported_clipnodes.push_back({});
|
|
|
|
const int child0 = ExportClipNodes(entity, node->children[0]);
|
|
const int child1 = ExportClipNodes(entity, node->children[1]);
|
|
|
|
// Careful not to modify the vector while using this clipnode pointer
|
|
clipnode = &map.exported_clipnodes.at(nodenum);
|
|
clipnode->planenum = ExportMapPlane(node->planenum);
|
|
clipnode->children[0] = child0;
|
|
clipnode->children[1] = child1;
|
|
|
|
for (face = node->faces; face; face = next) {
|
|
next = face->next;
|
|
memset(face, 0, sizeof(face_t));
|
|
free(face);
|
|
}
|
|
free(node);
|
|
|
|
return nodenum;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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 *entity, node_t *nodes, const int hullnum)
|
|
{
|
|
auto *model = &map.exported_models.at(static_cast<size_t>(entity->outputmodelnumber));
|
|
|
|
model->headnode[hullnum] = ExportClipNodes(entity, nodes);
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
/*
|
|
==================
|
|
ExportLeaf
|
|
==================
|
|
*/
|
|
static void
|
|
ExportLeaf(mapentity_t *entity, node_t *node)
|
|
{
|
|
map.exported_leafs_bsp29.push_back({});
|
|
mleaf_t *dleaf = &map.exported_leafs_bsp29.back();
|
|
|
|
dleaf->contents = RemapContentsForExport(node->contents);
|
|
AssertVanillaContentType(dleaf->contents);
|
|
|
|
/*
|
|
* write bounding box info
|
|
*/
|
|
for (int32_t i = 0; i < 3; i++) {
|
|
dleaf->mins[i] = node->mins[i];
|
|
dleaf->maxs[i] = node->maxs[i];
|
|
}
|
|
|
|
dleaf->visofs = -1; // no vis info yet
|
|
|
|
// write the marksurfaces
|
|
dleaf->firstmarksurface = static_cast<int>(map.exported_marksurfaces.size());
|
|
|
|
for (face_t **markfaces = node->markfaces; *markfaces; markfaces++) {
|
|
face_t *face = *markfaces;
|
|
if (map.mtexinfos.at(face->texinfo).flags & TEX_SKIP)
|
|
continue;
|
|
|
|
/* emit a marksurface */
|
|
do {
|
|
map.exported_marksurfaces.push_back(face->outputnumber);
|
|
face = face->original; /* grab tjunction split faces */
|
|
} while (face);
|
|
}
|
|
dleaf->nummarksurfaces =
|
|
static_cast<int>(map.exported_marksurfaces.size()) - dleaf->firstmarksurface;
|
|
|
|
// FIXME-Q2: fill in other things
|
|
dleaf->area = 0;
|
|
dleaf->cluster = node->viscluster;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ExportDrawNodes
|
|
==================
|
|
*/
|
|
static void
|
|
ExportDrawNodes(mapentity_t *entity, node_t *node)
|
|
{
|
|
bsp2_dnode_t *dnode;
|
|
int i;
|
|
|
|
const size_t ourNodeIndex = map.exported_nodes_bsp29.size();
|
|
map.exported_nodes_bsp29.push_back({});
|
|
|
|
dnode = &map.exported_nodes_bsp29[ourNodeIndex];
|
|
|
|
for (int32_t i = 0; i < 3; i++) {
|
|
dnode->mins[i] = node->mins[i];
|
|
dnode->maxs[i] = node->maxs[i];
|
|
}
|
|
|
|
dnode->planenum = ExportMapPlane(node->planenum);
|
|
dnode->firstface = node->firstface;
|
|
dnode->numfaces = node->numfaces;
|
|
|
|
// recursively output the other nodes
|
|
for (i = 0; i < 2; i++) {
|
|
if (node->children[i]->planenum == PLANENUM_LEAF) {
|
|
// In Q2, all leaves must have their own ID even if they share solidity.
|
|
// (probably for collision purposes? makes sense given they store leafbrushes)
|
|
if (!options.target_version->quake2 && node->children[i]->contents == CONTENTS_SOLID)
|
|
dnode->children[i] = -1;
|
|
else {
|
|
int nextLeafIndex = static_cast<int>(map.exported_leafs_bsp29.size());
|
|
const int childnum = -(nextLeafIndex + 1);
|
|
dnode->children[i] = childnum;
|
|
ExportLeaf(entity, node->children[i]);
|
|
}
|
|
} else {
|
|
const int childnum = static_cast<int>(map.exported_nodes_bsp29.size());
|
|
dnode->children[i] = childnum;
|
|
ExportDrawNodes(entity, node->children[i]);
|
|
|
|
// Important: our dnode pointer may be invalid after the recursive call, if the vector got resized.
|
|
// So re-set the pointer.
|
|
dnode = &map.exported_nodes_bsp29[ourNodeIndex];
|
|
}
|
|
}
|
|
|
|
// DarkPlaces asserts that the leaf numbers are different
|
|
// if mod_bsp_portalize is 1 (default)
|
|
// The most likely way it could fail is if both sides are the
|
|
// shared CONTENTS_SOLID leaf (-1)
|
|
Q_assert(!(dnode->children[0] == -1 && dnode->children[1] == -1));
|
|
Q_assert(dnode->children[0] != dnode->children[1]);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ExportDrawNodes
|
|
==================
|
|
*/
|
|
void
|
|
ExportDrawNodes(mapentity_t *entity, node_t *headnode, int firstface)
|
|
{
|
|
int i;
|
|
dmodelh2_t *dmodel;
|
|
|
|
// populate model struct (which was emitted previously)
|
|
dmodel = &map.exported_models.at(static_cast<size_t>(entity->outputmodelnumber));
|
|
dmodel->headnode[0] = static_cast<int>(map.exported_nodes_bsp29.size());
|
|
dmodel->firstface = firstface;
|
|
dmodel->numfaces = static_cast<int>(map.exported_faces.size()) - firstface;
|
|
|
|
const size_t mapleafsAtStart = map.exported_leafs_bsp29.size();
|
|
|
|
if (headnode->planenum == PLANENUM_LEAF)
|
|
ExportLeaf(entity, headnode);
|
|
else
|
|
ExportDrawNodes(entity, headnode);
|
|
|
|
// count how many leafs were exported by the above calls
|
|
dmodel->visleafs = static_cast<int>(map.exported_leafs_bsp29.size() - mapleafsAtStart);
|
|
|
|
/* remove the headnode padding */
|
|
for (i = 0; i < 3; i++) {
|
|
dmodel->mins[i] = headnode->mins[i] + SIDESPACE + 1;
|
|
dmodel->maxs[i] = headnode->maxs[i] - SIDESPACE - 1;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
/*
|
|
==================
|
|
BeginBSPFile
|
|
==================
|
|
*/
|
|
void
|
|
BeginBSPFile(void)
|
|
{
|
|
// First edge must remain unused because 0 can't be negated
|
|
map.exported_edges.push_back({});
|
|
Q_assert(map.exported_edges.size() == 1);
|
|
|
|
// Leave room for leaf 0 (must be solid)
|
|
map.exported_leafs_bsp29.push_back({});
|
|
map.exported_leafs_bsp29.back().contents = RemapContentsForExport(CONTENTS_SOLID);
|
|
Q_assert(map.exported_leafs_bsp29.size() == 1);
|
|
}
|
|
|
|
/*
|
|
* Writes extended texinfo flags to a file so they can be read by the light tool.
|
|
* Used for phong shading and other lighting settings on func_detail.
|
|
*/
|
|
static void
|
|
WriteExtendedTexinfoFlags(void)
|
|
{
|
|
bool needwrite = false;
|
|
const int num_texinfo = map.numtexinfo();
|
|
|
|
for (int i = 0; i < num_texinfo; i++) {
|
|
if (map.mtexinfos.at(i).flags & ~(TEX_SPECIAL | TEX_SKIP | TEX_HINT)) {
|
|
// this texinfo uses some extended flags, write them to a file
|
|
needwrite = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!needwrite)
|
|
return;
|
|
|
|
// sort by output texinfo number
|
|
std::vector<mtexinfo_t> texinfos_sorted(map.mtexinfos);
|
|
std::sort(texinfos_sorted.begin(), texinfos_sorted.end(), [](const mtexinfo_t &a, const mtexinfo_t &b) {
|
|
return a.outputnum < b.outputnum;
|
|
});
|
|
|
|
FILE *texinfofile;
|
|
StripExtension(options.szBSPName);
|
|
strcat(options.szBSPName, ".texinfo");
|
|
texinfofile = fopen(options.szBSPName, "wt");
|
|
if (!texinfofile)
|
|
Error("Failed to open %s: %s", options.szBSPName, strerror(errno));
|
|
|
|
int count = 0;
|
|
for (const auto &tx : texinfos_sorted) {
|
|
if (tx.outputnum == -1)
|
|
continue;
|
|
|
|
Q_assert(count == tx.outputnum); // check we are outputting them in the proper sequence
|
|
|
|
fprintf(texinfofile, "%llu\n", static_cast<unsigned long long>(tx.flags));
|
|
count++;
|
|
}
|
|
Q_assert(count == static_cast<int>(map.exported_texinfos.size()));
|
|
|
|
fclose(texinfofile);
|
|
}
|
|
|
|
template <class C>
|
|
static void
|
|
CopyVector(const std::vector<C>& vec, int* elementCountOut, C** arrayCopyOut)
|
|
{
|
|
const size_t numBytes = sizeof(C) * vec.size();
|
|
void* data = (void*)malloc(numBytes);
|
|
memcpy(data, vec.data(), numBytes);
|
|
|
|
*elementCountOut = vec.size();
|
|
*arrayCopyOut = (C*)data;
|
|
}
|
|
|
|
static void
|
|
CopyString(const std::string& string, bool addNullTermination, int* elementCountOut, void** arrayCopyOut)
|
|
{
|
|
const size_t numBytes = addNullTermination ? string.size() + 1 : string.size();
|
|
void* data = malloc(numBytes);
|
|
memcpy(data, string.data(), numBytes); // std::string::data() has null termination, so it's safe to copy it
|
|
|
|
*elementCountOut = numBytes;
|
|
*arrayCopyOut = data;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
WriteBSPFile
|
|
=============
|
|
*/
|
|
static void
|
|
WriteBSPFile()
|
|
{
|
|
bspdata_t bspdata{};
|
|
|
|
bspdata.version = &bspver_generic;
|
|
|
|
CopyVector(map.exported_planes, &bspdata.data.mbsp.numplanes, &bspdata.data.mbsp.dplanes);
|
|
CopyVector(map.exported_leafs_bsp29, &bspdata.data.mbsp.numleafs, &bspdata.data.mbsp.dleafs);
|
|
CopyVector(map.exported_vertexes, &bspdata.data.mbsp.numvertexes, &bspdata.data.mbsp.dvertexes);
|
|
CopyVector(map.exported_nodes_bsp29, &bspdata.data.mbsp.numnodes, &bspdata.data.mbsp.dnodes);
|
|
CopyVector(map.exported_texinfos, &bspdata.data.mbsp.numtexinfo, &bspdata.data.mbsp.texinfo);
|
|
CopyVector(map.exported_faces, &bspdata.data.mbsp.numfaces, &bspdata.data.mbsp.dfaces);
|
|
CopyVector(map.exported_clipnodes, &bspdata.data.mbsp.numclipnodes, &bspdata.data.mbsp.dclipnodes);
|
|
CopyVector(map.exported_marksurfaces, &bspdata.data.mbsp.numleaffaces, &bspdata.data.mbsp.dleaffaces);
|
|
CopyVector(map.exported_surfedges, &bspdata.data.mbsp.numsurfedges, &bspdata.data.mbsp.dsurfedges);
|
|
CopyVector(map.exported_edges, &bspdata.data.mbsp.numedges, &bspdata.data.mbsp.dedges);
|
|
CopyVector(map.exported_models, &bspdata.data.mbsp.nummodels, &bspdata.data.mbsp.dmodels);
|
|
|
|
CopyString(map.exported_entities, true, &bspdata.data.mbsp.entdatasize, (void**)&bspdata.data.mbsp.dentdata);
|
|
CopyString(map.exported_texdata, false, &bspdata.data.mbsp.texdatasize, (void**)&bspdata.data.mbsp.dtexdata);
|
|
|
|
bspdata.data.mbsp.numareas = 1;
|
|
bspdata.data.mbsp.dareas = (darea_t *) malloc(sizeof(darea_t));
|
|
bspdata.data.mbsp.dareas->firstareaportal = bspdata.data.mbsp.dareas->numareaportals = 0;
|
|
|
|
// TODO: pass bspx lumps to generic bsp code so they are written
|
|
|
|
//GenLump("LMSHIFT", BSPX_LMSHIFT, 1);
|
|
|
|
ConvertBSPFormat(&bspdata, options.target_version);
|
|
|
|
StripExtension(options.szBSPName);
|
|
strcat(options.szBSPName, ".bsp");
|
|
|
|
WriteBSPFile(options.szBSPName, &bspdata);
|
|
logprint("Wrote %s\n", options.szBSPName);
|
|
|
|
PrintBSPFileSizes(&bspdata);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FinishBSPFile
|
|
==================
|
|
*/
|
|
void
|
|
FinishBSPFile(void)
|
|
{
|
|
options.fVerbose = true;
|
|
Message(msgProgress, "WriteBSPFile");
|
|
|
|
WriteExtendedTexinfoFlags();
|
|
WriteBSPFile();
|
|
|
|
options.fVerbose = options.fAllverbose;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
UpdateBSPFileEntitiesLump
|
|
==================
|
|
*/
|
|
void
|
|
UpdateBSPFileEntitiesLump()
|
|
{
|
|
bspdata_t bspdata;
|
|
StripExtension(options.szBSPName);
|
|
DefaultExtension(options.szBSPName, ".bsp");
|
|
|
|
// load the .bsp
|
|
LoadBSPFile(options.szBSPName, &bspdata);
|
|
ConvertBSPFormat(&bspdata, &bspver_generic);
|
|
|
|
// replace the existing entities lump with map.exported_entities
|
|
CopyString(map.exported_entities, true, &bspdata.data.mbsp.entdatasize, (void**)&bspdata.data.mbsp.dentdata);
|
|
|
|
// write the .bsp back to disk
|
|
ConvertBSPFormat(&bspdata, bspdata.loadversion);
|
|
WriteBSPFile(options.szBSPName, &bspdata);
|
|
|
|
logprint("Wrote %s\n", options.szBSPName);
|
|
}
|