/* 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 void ExportNodePlanes_r(node_t *node, int *planemap) { struct lumpdata *planes = &pWorldEnt()->lumps[LUMP_PLANES]; plane_t *plane; dplane_t *dplane; int i; if (node->planenum == -1) return; if (planemap[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++) { vec3_t normal; normal[0] = dplane->normal[0]; normal[1] = dplane->normal[1]; normal[2] = dplane->normal[2]; if (DotProduct(normal, plane->normal) > 1 - 0.00001 && fabs(dplane->dist - plane->dist) < 0.01 && dplane->type == plane->type) break; } // a new plane planemap[node->planenum] = i; if (i == planes->index) { if (planes->index >= planes->count) Error("Internal error: plane count mismatch (%s)", __func__); 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[LUMP_PLANES]++; } } node->outputplanenum = planemap[node->planenum]; ExportNodePlanes_r(node->children[0], planemap); ExportNodePlanes_r(node->children[1], planemap); } /* ================== ExportNodePlanes ================== */ void ExportNodePlanes(node_t *nodes) { struct lumpdata *planes = &pWorldEnt()->lumps[LUMP_PLANES]; int *planemap; // OK just need one plane array, stick it in worldmodel if (map.numplanes() > planes->count) { int newcount = map.numplanes(); struct lumpdata *newplanes = (struct lumpdata *)AllocMem(BSP_PLANE, newcount, true); memcpy(newplanes, planes->data, MemSize[BSP_PLANE] * planes->count); FreeMem(planes->data, BSP_PLANE, planes->count); planes->count = newcount; planes->data = newplanes; } // TODO: make one-time allocation? planemap = (int *)AllocMem(OTHER, sizeof(int) * planes->count, true); memset(planemap, -1, sizeof(int) * planes->count); ExportNodePlanes_r(nodes, planemap); FreeMem(planemap, OTHER, sizeof(int) * planes->count); } //=========================================================================== /* ================== CountClipNodes_r ================== */ static void CountClipNodes_r(mapentity_t *entity, node_t *node) { if (node->planenum == -1) return; entity->lumps[LUMP_CLIPNODES].count++; CountClipNodes_r(entity, node->children[0]); CountClipNodes_r(entity, node->children[1]); } /* ================== ExportClipNodes ================== */ static int ExportClipNodes_BSP29(mapentity_t *entity, node_t *node) { int nodenum; bsp29_dclipnode_t *clipnode; face_t *face, *next; struct lumpdata *clipnodes = &entity->lumps[LUMP_CLIPNODES]; // FIXME: free more stuff? if (node->planenum == -1) { int contents = node->contents; FreeMem(node, NODE, 1); return contents; } /* emit a clipnode */ clipnode = (bsp29_dclipnode_t *)clipnodes->data + clipnodes->index; clipnodes->index++; nodenum = map.cTotal[LUMP_CLIPNODES]; map.cTotal[LUMP_CLIPNODES]++; clipnode->planenum = node->outputplanenum; clipnode->children[0] = ExportClipNodes_BSP29(entity, node->children[0]); clipnode->children[1] = ExportClipNodes_BSP29(entity, node->children[1]); for (face = node->faces; face; face = next) { next = face->next; memset(face, 0, sizeof(face_t)); FreeMem(face, FACE, 1); } FreeMem(node, NODE, 1); return nodenum; } static int ExportClipNodes_BSP2(mapentity_t *entity, node_t *node) { int nodenum; bsp2_dclipnode_t *clipnode; face_t *face, *next; struct lumpdata *clipnodes = &entity->lumps[LUMP_CLIPNODES]; // FIXME: free more stuff? if (node->planenum == -1) { int contents = node->contents; FreeMem(node, NODE, 1); return contents; } /* emit a clipnode */ clipnode = (bsp2_dclipnode_t *)clipnodes->data + clipnodes->index; clipnodes->index++; nodenum = map.cTotal[LUMP_CLIPNODES]; map.cTotal[LUMP_CLIPNODES]++; clipnode->planenum = node->outputplanenum; clipnode->children[0] = ExportClipNodes_BSP2(entity, node->children[0]); clipnode->children[1] = ExportClipNodes_BSP2(entity, node->children[1]); for (face = node->faces; face; face = next) { next = face->next; memset(face, 0, sizeof(face_t)); FreeMem(face, FACE, 1); } FreeMem(node, NODE, 1); 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) { int oldcount, i, diff; int clipcount = 0; void *olddata; struct lumpdata *clipnodes = &entity->lumps[LUMP_CLIPNODES]; dmodel_t *model = (dmodel_t *)entity->lumps[LUMP_MODELS].data; oldcount = clipnodes->count; /* Count nodes before this one */ const int entnum = entity - &map.entities.at(0); for (i = 0; i < entnum; i++) clipcount += map.entities[i].lumps[LUMP_CLIPNODES].count; model->headnode[hullnum] = clipcount + oldcount; CountClipNodes_r(entity, nodes); if (clipnodes->count > MAX_BSP_CLIPNODES && options.BSPVersion == BSPVERSION) Error("Clipnode count exceeds bsp 29 max (%d > %d)", clipnodes->count, MAX_BSP_CLIPNODES); olddata = clipnodes->data; clipnodes->data = AllocMem(BSP_CLIPNODE, clipnodes->count, true); if (olddata) { memcpy(clipnodes->data, olddata, oldcount * MemSize[BSP_CLIPNODE]); FreeMem(olddata, BSP_CLIPNODE, oldcount); /* Worth special-casing for entity 0 (no modification needed) */ diff = clipcount - model->headnode[1]; if (diff != 0) { for (i = 1; i < hullnum; i++) model->headnode[i] += diff; if (options.BSPVersion == BSPVERSION) { bsp29_dclipnode_t *clipnode = (bsp29_dclipnode_t *)clipnodes->data; for (i = 0; i < oldcount; i++, clipnode++) { if (clipnode->children[0] < MAX_BSP_CLIPNODES) clipnode->children[0] += diff; if (clipnode->children[1] < MAX_BSP_CLIPNODES) clipnode->children[1] += diff; } } else { bsp2_dclipnode_t *clipnode = (bsp2_dclipnode_t *)clipnodes->data; for (i = 0; i < oldcount; i++, clipnode++) { if (clipnode->children[0] >= 0) clipnode->children[0] += diff; if (clipnode->children[1] >= 0) clipnode->children[1] += diff; } } } } map.cTotal[LUMP_CLIPNODES] = clipcount + oldcount; if (options.BSPVersion == BSPVERSION) ExportClipNodes_BSP29(entity, nodes); else ExportClipNodes_BSP2(entity, nodes); } //=========================================================================== /* ================== CountLeaves ================== */ static void CountLeaves(mapentity_t *entity, node_t *node) { face_t **markfaces, *face; const texinfo_t *texinfo = (const texinfo_t *)pWorldEnt()->lumps[LUMP_TEXINFO].data; entity->lumps[LUMP_LEAFS].count++; for (markfaces = node->markfaces; *markfaces; markfaces++) { if (texinfo[(*markfaces)->texinfo].flags & TEX_SKIP) continue; for (face = *markfaces; face; face = face->original) entity->lumps[LUMP_MARKSURFACES].count++; } } /* ================== CountNodes_r ================== */ static void CountNodes_r(mapentity_t *entity, node_t *node) { int i; entity->lumps[LUMP_NODES].count++; for (i = 0; i < 2; i++) { if (node->children[i]->planenum == -1) { if (node->children[i]->contents != CONTENTS_SOLID) CountLeaves(entity, node->children[i]); } else CountNodes_r(entity, node->children[i]); } } /* ================== CountNodes ================== */ static void CountNodes(mapentity_t *entity, node_t *headnode) { if (headnode->contents < 0) CountLeaves(entity, headnode); else CountNodes_r(entity, headnode); } /* ================== ExportLeaf ================== */ static void ExportLeaf_BSP29(mapentity_t *entity, node_t *node) { const texinfo_t *texinfo = (const texinfo_t *)pWorldEnt()->lumps[LUMP_TEXINFO].data; struct lumpdata *leaves = &entity->lumps[LUMP_LEAFS]; struct lumpdata *marksurfs = &entity->lumps[LUMP_MARKSURFACES]; uint16_t *marksurfnums = (uint16_t *)marksurfs->data; face_t **markfaces, *face; bsp29_dleaf_t *dleaf; // ptr arithmetic to get correct leaf in memory dleaf = (bsp29_dleaf_t *)leaves->data + leaves->index; leaves->index++; map.cTotal[LUMP_LEAFS]++; dleaf->contents = node->contents; /* * write bounding box info * (VectorCopy doesn'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[LUMP_MARKSURFACES]; for (markfaces = node->markfaces; *markfaces; markfaces++) { face = *markfaces; if (texinfo[face->texinfo].flags & TEX_SKIP) continue; /* emit a marksurface */ do { marksurfnums[marksurfs->index] = face->outputnumber; marksurfs->index++; map.cTotal[LUMP_MARKSURFACES]++; face = face->original; /* grab tjunction split faces */ } while (face); } dleaf->nummarksurfaces = map.cTotal[LUMP_MARKSURFACES] - dleaf->firstmarksurface; } static void ExportLeaf_BSP2(mapentity_t *entity, node_t *node) { const texinfo_t *texinfo = (const texinfo_t *)pWorldEnt()->lumps[LUMP_TEXINFO].data; struct lumpdata *leaves = &entity->lumps[LUMP_LEAFS]; struct lumpdata *marksurfs = &entity->lumps[LUMP_MARKSURFACES]; uint32_t *marksurfnums = (uint32_t *)marksurfs->data; face_t **markfaces, *face; bsp2_dleaf_t *dleaf; // ptr arithmetic to get correct leaf in memory dleaf = (bsp2_dleaf_t *)leaves->data + leaves->index; leaves->index++; map.cTotal[LUMP_LEAFS]++; dleaf->contents = node->contents; /* * write bounding box info * (VectorCopy doesn't work double->float) */ dleaf->mins[0] = node->mins[0]; dleaf->mins[1] = node->mins[1]; dleaf->mins[2] = node->mins[2]; dleaf->maxs[0] = node->maxs[0]; dleaf->maxs[1] = node->maxs[1]; dleaf->maxs[2] = node->maxs[2]; dleaf->visofs = -1; // no vis info yet // write the marksurfaces dleaf->firstmarksurface = map.cTotal[LUMP_MARKSURFACES]; for (markfaces = node->markfaces; *markfaces; markfaces++) { face = *markfaces; if (texinfo[face->texinfo].flags & TEX_SKIP) continue; /* emit a marksurface */ do { marksurfnums[marksurfs->index] = face->outputnumber; marksurfs->index++; map.cTotal[LUMP_MARKSURFACES]++; face = face->original; /* grab tjunction split faces */ } while (face); } dleaf->nummarksurfaces = map.cTotal[LUMP_MARKSURFACES] - dleaf->firstmarksurface; } static void ExportLeaf_BSP2rmq(mapentity_t *entity, node_t *node) { const texinfo_t *texinfo = (const texinfo_t *)pWorldEnt()->lumps[LUMP_TEXINFO].data; struct lumpdata *leaves = &entity->lumps[LUMP_LEAFS]; struct lumpdata *marksurfs = &entity->lumps[LUMP_MARKSURFACES]; uint32_t *marksurfnums = (uint32_t *)marksurfs->data; face_t **markfaces, *face; bsp2rmq_dleaf_t *dleaf; // ptr arithmetic to get correct leaf in memory dleaf = (bsp2rmq_dleaf_t *)leaves->data + leaves->index; leaves->index++; map.cTotal[LUMP_LEAFS]++; dleaf->contents = node->contents; /* * write bounding box info * (VectorCopy doesn'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[LUMP_MARKSURFACES]; for (markfaces = node->markfaces; *markfaces; markfaces++) { face = *markfaces; if (texinfo[face->texinfo].flags & TEX_SKIP) continue; /* emit a marksurface */ do { marksurfnums[marksurfs->index] = face->outputnumber; marksurfs->index++; map.cTotal[LUMP_MARKSURFACES]++; face = face->original; /* grab tjunction split faces */ } while (face); } dleaf->nummarksurfaces = map.cTotal[LUMP_MARKSURFACES] - dleaf->firstmarksurface; } /* ================== ExportDrawNodes ================== */ static void ExportDrawNodes_BSP29(mapentity_t *entity, node_t *node) { struct lumpdata *nodes = &entity->lumps[LUMP_NODES]; bsp29_dnode_t *dnode; int i; dnode = (bsp29_dnode_t *)nodes->data + nodes->index; nodes->index++; map.cTotal[LUMP_NODES]++; // VectorCopy doesn't work since dest are shorts dnode->mins[0] = (short)node->mins[0]; dnode->mins[1] = (short)node->mins[1]; dnode->mins[2] = (short)node->mins[2]; dnode->maxs[0] = (short)node->maxs[0]; dnode->maxs[1] = (short)node->maxs[1]; dnode->maxs[2] = (short)node->maxs[2]; dnode->planenum = node->outputplanenum; dnode->firstface = node->firstface; dnode->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) dnode->children[i] = -1; else { dnode->children[i] = -(map.cTotal[LUMP_LEAFS] + 1); ExportLeaf_BSP29(entity, node->children[i]); } } else { dnode->children[i] = map.cTotal[LUMP_NODES]; ExportDrawNodes_BSP29(entity, node->children[i]); } } } static void ExportDrawNodes_BSP2(mapentity_t *entity, node_t *node) { struct lumpdata *nodes = &entity->lumps[LUMP_NODES]; bsp2_dnode_t *dnode; int i; dnode = (bsp2_dnode_t *)nodes->data + nodes->index; nodes->index++; map.cTotal[LUMP_NODES]++; // VectorCopy doesn't work double->float dnode->mins[0] = node->mins[0]; dnode->mins[1] = node->mins[1]; dnode->mins[2] = node->mins[2]; dnode->maxs[0] = node->maxs[0]; dnode->maxs[1] = node->maxs[1]; dnode->maxs[2] = node->maxs[2]; dnode->planenum = node->outputplanenum; dnode->firstface = node->firstface; dnode->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) dnode->children[i] = -1; else { dnode->children[i] = -(map.cTotal[LUMP_LEAFS] + 1); ExportLeaf_BSP2(entity, node->children[i]); } } else { dnode->children[i] = map.cTotal[LUMP_NODES]; ExportDrawNodes_BSP2(entity, node->children[i]); } } } static void ExportDrawNodes_BSP2rmq(mapentity_t *entity, node_t *node) { struct lumpdata *nodes = &entity->lumps[LUMP_NODES]; bsp2rmq_dnode_t *dnode; int i; dnode = (bsp2rmq_dnode_t *)nodes->data + nodes->index; nodes->index++; map.cTotal[LUMP_NODES]++; // VectorCopy doesn't work since dest are shorts dnode->mins[0] = node->mins[0]; dnode->mins[1] = node->mins[1]; dnode->mins[2] = node->mins[2]; dnode->maxs[0] = node->maxs[0]; dnode->maxs[1] = node->maxs[1]; dnode->maxs[2] = node->maxs[2]; dnode->planenum = node->outputplanenum; dnode->firstface = node->firstface; dnode->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) dnode->children[i] = -1; else { dnode->children[i] = -(map.cTotal[LUMP_LEAFS] + 1); ExportLeaf_BSP2rmq(entity, node->children[i]); } } else { dnode->children[i] = map.cTotal[LUMP_NODES]; ExportDrawNodes_BSP2rmq(entity, node->children[i]); } } } /* ================== ExportDrawNodes ================== */ void ExportDrawNodes(mapentity_t *entity, node_t *headnode, int firstface) { int i; dmodel_t *dmodel; struct lumpdata *nodes = &entity->lumps[LUMP_NODES]; struct lumpdata *leaves = &entity->lumps[LUMP_LEAFS]; struct lumpdata *marksurfs = &entity->lumps[LUMP_MARKSURFACES]; // Allocate a model entity->lumps[LUMP_MODELS].data = AllocMem(BSP_MODEL, 1, true); entity->lumps[LUMP_MODELS].count = 1; // Get a feel for how many of these things there are. CountNodes(entity, headnode); // emit a model nodes->data = AllocMem(BSP_NODE, nodes->count, true); leaves->data = AllocMem(BSP_LEAF, leaves->count, true); marksurfs->data = AllocMem(BSP_MARKSURF, marksurfs->count, true); /* * Set leaf 0 properly (must be solid). cLeaves etc incremented in * BeginBSPFile. */ if (options.BSPVersion == BSP2VERSION) { bsp2_dleaf_t *leaf = (bsp2_dleaf_t *)pWorldEnt()->lumps[LUMP_LEAFS].data; leaf->contents = CONTENTS_SOLID; } else if (options.BSPVersion == BSP2RMQVERSION) { bsp2rmq_dleaf_t *leaf = (bsp2rmq_dleaf_t *)pWorldEnt()->lumps[LUMP_LEAFS].data; leaf->contents = CONTENTS_SOLID; } else { bsp29_dleaf_t *leaf = (bsp29_dleaf_t *)pWorldEnt()->lumps[LUMP_LEAFS].data; leaf->contents = CONTENTS_SOLID; } dmodel = (dmodel_t *)entity->lumps[LUMP_MODELS].data; dmodel->headnode[0] = map.cTotal[LUMP_NODES]; dmodel->firstface = firstface; dmodel->numfaces = map.cTotal[LUMP_FACES] - firstface; if (options.BSPVersion == BSP2VERSION) { if (headnode->contents < 0) ExportLeaf_BSP2(entity, headnode); else ExportDrawNodes_BSP2(entity, headnode); } else if (options.BSPVersion == BSP2RMQVERSION) { if (headnode->contents < 0) ExportLeaf_BSP2rmq(entity, headnode); else ExportDrawNodes_BSP2rmq(entity, headnode); } else { if (headnode->contents < 0) ExportLeaf_BSP29(entity, headnode); else ExportDrawNodes_BSP29(entity, headnode); } /* Not counting initial vis leaf */ dmodel->visleafs = leaves->count; if (entity == pWorldEnt()) dmodel->visleafs--; /* 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 pWorldEnt()->lumps[LUMP_EDGES].count++; pWorldEnt()->lumps[LUMP_EDGES].index++; map.cTotal[LUMP_EDGES]++; // Leave room for leaf 0 (must be solid) pWorldEnt()->lumps[LUMP_LEAFS].count++; pWorldEnt()->lumps[LUMP_LEAFS].index++; map.cTotal[LUMP_LEAFS]++; } /* * 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 texinfo_t *texinfo = (const texinfo_t *)pWorldEnt()->lumps[LUMP_TEXINFO].data; const int num_texinfo = pWorldEnt()->lumps[LUMP_TEXINFO].index; int i; for (i = 0; i < num_texinfo; i++) { if (texinfo[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; 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)); for (i = 0; i < num_texinfo; i++) { fprintf(texinfofile, "%u\n", texinfo[i].flags); } fclose(texinfofile); } /* * 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 = (texinfo_t *)pWorldEnt()->lumps[LUMP_TEXINFO].data; const int num_texinfo = pWorldEnt()->lumps[LUMP_TEXINFO].index; int i; for (i = 0; i < num_texinfo; i++, texinfo++) texinfo->flags &= TEX_SPECIAL; } /* ================== FinishBSPFile ================== */ void FinishBSPFile(void) { struct lumpdata *planes = &pWorldEnt()->lumps[LUMP_PLANES]; dplane_t *newdata; options.fVerbose = true; Message(msgProgress, "WriteBSPFile"); // TODO: Fix this somewhere else? newdata = (dplane_t *)AllocMem(BSP_PLANE, map.cTotal[LUMP_PLANES], true); memcpy(newdata, planes->data, map.cTotal[LUMP_PLANES] * MemSize[BSP_PLANE]); FreeMem(planes->data, BSP_PLANE, planes->count); planes->data = newdata; planes->count = map.cTotal[LUMP_PLANES]; // Shrink texinfo lump { struct lumpdata *texinfo; void *pTemp; texinfo = &pWorldEnt()->lumps[LUMP_TEXINFO]; pTemp = texinfo->data; texinfo->data = AllocMem(BSP_TEXINFO, texinfo->index, true); memcpy(texinfo->data, pTemp, texinfo->index * MemSize[BSP_TEXINFO]); FreeMem(pTemp, BSP_TEXINFO, texinfo->count); texinfo->count = texinfo->index; } WriteExtendedTexinfoFlags(); CleanBSPTexinfoFlags(); WriteBSPFile(); PrintBSPFileSizes(); options.fVerbose = options.fAllverbose; }