From b01fddf7f13ed713b87ad64d1dc653eef4b76a3d Mon Sep 17 00:00:00 2001 From: Jonathan Date: Sat, 4 Sep 2021 18:49:01 -0400 Subject: [PATCH] More Q2 support --- bsputil/bsputil.cc | 4 +- common/bspfile.cc | 44 ++++++++++--------- include/common/bspfile.hh | 89 ++++++++++++++++++++------------------- include/qbsp/qbsp.hh | 3 -- qbsp/map.cc | 5 ++- qbsp/qbsp.cc | 42 ++++++++++-------- qbsp/writebsp.cc | 74 +++++++++++++++++++++++--------- 7 files changed, 156 insertions(+), 105 deletions(-) diff --git a/bsputil/bsputil.cc b/bsputil/bsputil.cc index 4ce08b44..d4637ef0 100644 --- a/bsputil/bsputil.cc +++ b/bsputil/bsputil.cc @@ -135,7 +135,7 @@ PrintModelInfo(const mbsp_t *bsp) /* * Quick hack to check verticies of faces lie on the correct plane */ -#define ON_EPSILON 0.01 +#define PLANE_ON_EPSILON 0.01 static void CheckBSPFacesPlanar(const mbsp_t *bsp) @@ -157,7 +157,7 @@ CheckBSPFacesPlanar(const mbsp_t *bsp) const float *point = bsp->dvertexes[vertnum].point; const float dist = DotProduct(plane.normal, point) - plane.dist; - if (dist < -ON_EPSILON || dist > ON_EPSILON) + if (dist < -PLANE_ON_EPSILON || dist > PLANE_ON_EPSILON) printf("WARNING: face %d, point %d off plane by %f\n", (int)(face - bsp->dfaces), j, dist); } diff --git a/common/bspfile.cc b/common/bspfile.cc index 3b5efa8f..6cb1c1d1 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -22,17 +22,17 @@ #include /* hexen2, quake2 */ -const bspversion_t bspver_generic { NO_VERSION, NO_VERSION, "mbsp", "generic BSP", false, false }; -const bspversion_t bspver_q1 { BSPVERSION, NO_VERSION, "bsp29", "Quake BSP", false, false }; -const bspversion_t bspver_bsp2 { BSP2VERSION, NO_VERSION, "bsp2", "Quake BSP2", false, false }; -const bspversion_t bspver_bsp2rmq { BSP2RMQVERSION, NO_VERSION, "bsp2rmq", "Quake BSP2-RMQ", false, false }; +const bspversion_t bspver_generic { NO_VERSION, NO_VERSION, "mbsp", "generic BSP", false, false }; +const bspversion_t bspver_q1 { BSPVERSION, NO_VERSION, "bsp29", "Quake BSP", false, false }; +const bspversion_t bspver_bsp2 { BSP2VERSION, NO_VERSION, "bsp2", "Quake BSP2", false, false }; +const bspversion_t bspver_bsp2rmq { BSP2RMQVERSION, NO_VERSION, "bsp2rmq", "Quake BSP2-RMQ", false, false }; /* Hexen II doesn't use a separate version, but we can still use a separate tag/name for it */ -const bspversion_t bspver_h2 { BSPVERSION, NO_VERSION, "hexen2", "Hexen II BSP", true, false }; -const bspversion_t bspver_h2bsp2 { BSP2VERSION, NO_VERSION, "hexen2bsp2", "Hexen II BSP2", true, false }; -const bspversion_t bspver_h2bsp2rmq { BSP2RMQVERSION, NO_VERSION, "hexen2bsp2rmq", "Hexen II BSP2-RMQ", true, false }; -const bspversion_t bspver_hl { BSPHLVERSION, NO_VERSION, "hl", "Half-Life BSP", false, false }; -const bspversion_t bspver_q2 { Q2_BSPIDENT, Q2_BSPVERSION, "q2bsp", "Quake II BSP", false, true }; -const bspversion_t bspver_qbism { Q2_QBISMIDENT, Q2_BSPVERSION, "qbism", "Quake II Qbism BSP", false, true }; +const bspversion_t bspver_h2 { BSPVERSION, NO_VERSION, "hexen2", "Hexen II BSP", true, false }; +const bspversion_t bspver_h2bsp2 { BSP2VERSION, NO_VERSION, "hexen2bsp2", "Hexen II BSP2", true, false }; +const bspversion_t bspver_h2bsp2rmq { BSP2RMQVERSION, NO_VERSION, "hexen2bsp2rmq", "Hexen II BSP2-RMQ", true, false }; +const bspversion_t bspver_hl { BSPHLVERSION, NO_VERSION, "hl", "Half-Life BSP", false, false }; +const bspversion_t bspver_q2 { Q2_BSPIDENT, Q2_BSPVERSION, "q2bsp", "Quake II BSP", false, true }; +const bspversion_t bspver_qbism { Q2_QBISMIDENT, Q2_BSPVERSION, "qbism", "Quake II Qbism BSP", false, true }; static const char * BSPVersionString(const bspversion_t *version) @@ -579,15 +579,17 @@ void Q2_SwapBSPFile (q2bsp_t *bsp, qboolean todisk) // // visibility // - if (todisk) - j = bsp->dvis->numclusters; - else - j = LittleLong(bsp->dvis->numclusters); - bsp->dvis->numclusters = LittleLong (bsp->dvis->numclusters); - for (i=0 ; idvis->bitofs[i][0] = LittleLong (bsp->dvis->bitofs[i][0]); - bsp->dvis->bitofs[i][1] = LittleLong (bsp->dvis->bitofs[i][1]); + if (bsp->dvis) { + if (todisk) + j = bsp->dvis->numclusters; + else + j = LittleLong(bsp->dvis->numclusters); + bsp->dvis->numclusters = LittleLong (bsp->dvis->numclusters); + for (i=0 ; idvis->bitofs[i][0] = LittleLong (bsp->dvis->bitofs[i][0]); + bsp->dvis->bitofs[i][1] = LittleLong (bsp->dvis->bitofs[i][1]); + } } } @@ -1176,6 +1178,10 @@ static std::vector CalcPHS(int32_t portalclusters, const uint8_t *visda static dvis_t * MBSPtoQ2_CopyVisData(const uint8_t *visdata, int *visdatasize, int numleafs, const mleaf_t *leafs) { + if (!*visdatasize) { + return nullptr; + } + int32_t num_clusters = 0; for (int32_t i = 0; i < numleafs; i++) { diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 3fd3fed5..0d692e67 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -51,20 +51,6 @@ #define MAX_ENT_KEY 32 #define MAX_ENT_VALUE 1024 -struct bspversion_t -{ - /* identifier value, the first int32_t in the header */ - int32_t ident; - /* version value, if supported; use NO_VERSION if a version is not required */ - int32_t version; - /* short name used for command line args, etc */ - const char *short_name; - /* full display name for printing */ - const char *name; - bool hexen2; - bool quake2; -}; - #define NO_VERSION -1 #define BSPVERSION 29 @@ -75,31 +61,6 @@ struct bspversion_t #define Q2_BSPVERSION 38 #define Q2_QBISMIDENT (('P'<<24)+('S'<<16)+('B'<<8)+'Q') -extern const bspversion_t bspver_generic; -extern const bspversion_t bspver_q1; -extern const bspversion_t bspver_h2; -extern const bspversion_t bspver_h2bsp2; -extern const bspversion_t bspver_h2bsp2rmq; -extern const bspversion_t bspver_bsp2; -extern const bspversion_t bspver_bsp2rmq; -extern const bspversion_t bspver_hl; -extern const bspversion_t bspver_q2; -extern const bspversion_t bspver_qbism; - -/* table of supported versions */ -constexpr const bspversion_t *const bspversions[] = { - &bspver_generic, - &bspver_q1, - &bspver_h2, - &bspver_h2bsp2, - &bspver_h2bsp2rmq, - &bspver_bsp2, - &bspver_bsp2rmq, - &bspver_hl, - &bspver_q2, - &bspver_qbism -}; - typedef struct { int32_t fileofs; int32_t filelen; @@ -247,15 +208,15 @@ typedef struct { #define CONTENTS_SKY -6 #define CONTENTS_MIN CONTENTS_SKY -#define CONTENTS_CLIP -7 /* compiler internal use only */ -#define CONTENTS_HINT -8 /* compiler internal use only */ +#define CONTENTS_HINT -7 /* compiler internal use only */ +#define CONTENTS_CLIP -8 /* compiler internal use only */ #define CONTENTS_ORIGIN -9 /* compiler internal use only */ #define CONTENTS_DETAIL -10 /* compiler internal use only */ #define CONTENTS_DETAIL_ILLUSIONARY -11 /* compiler internal use only */ #define CONTENTS_DETAIL_FENCE -12 /* compiler internal use only */ #define CONTENTS_ILLUSIONARY_VISBLOCKER -13 -#define CONTENTS_FENCE -15 /* compiler internal use only */ -#define CONTENTS_LADDER -16 /* reserved for engine use */ +//#define CONTENTS_FENCE -15 /* compiler internal use only */ +//#define CONTENTS_LADDER -16 /* reserved for engine use */ // Q2 contents (from qfiles.h) @@ -886,6 +847,8 @@ struct q2bsp_qbism_t { uint8_t dpop[256]; }; +struct bspversion_t; + struct mbsp_t { const bspversion_t *loadversion; @@ -982,6 +945,46 @@ typedef struct { bspxentry_t *bspxentries; } bspdata_t; +// BSP version struct & instances +struct bspversion_t +{ + /* identifier value, the first int32_t in the header */ + int32_t ident; + /* version value, if supported; use NO_VERSION if a version is not required */ + int32_t version; + /* short name used for command line args, etc */ + const char *short_name; + /* full display name for printing */ + const char *name; + bool hexen2; + bool quake2; +}; + +extern const bspversion_t bspver_generic; +extern const bspversion_t bspver_q1; +extern const bspversion_t bspver_h2; +extern const bspversion_t bspver_h2bsp2; +extern const bspversion_t bspver_h2bsp2rmq; +extern const bspversion_t bspver_bsp2; +extern const bspversion_t bspver_bsp2rmq; +extern const bspversion_t bspver_hl; +extern const bspversion_t bspver_q2; +extern const bspversion_t bspver_qbism; + +/* table of supported versions */ +constexpr const bspversion_t *const bspversions[] = { + &bspver_generic, + &bspver_q1, + &bspver_h2, + &bspver_h2bsp2, + &bspver_h2bsp2rmq, + &bspver_bsp2, + &bspver_bsp2rmq, + &bspver_hl, + &bspver_q2, + &bspver_qbism +}; + void LoadBSPFile(char *filename, bspdata_t *bspdata); //returns the filename as contained inside a bsp void WriteBSPFile(const char *filename, bspdata_t *bspdata); void PrintBSPFileSizes(const bspdata_t *bspdata); diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index 2e38b398..ba23f805 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -367,8 +367,5 @@ extern options_t options; #include int qbsp_main(int argc, const char **argv); -void ProcessEntity(mapentity_t *entity, const int hullnum); -void CreateSingleHull(const int hullnum); -void CreateHulls(void); #endif diff --git a/qbsp/map.cc b/qbsp/map.cc index fe53ff52..60523328 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -418,7 +418,10 @@ ParseEpair(parser_t *parser, mapentity_t *entity) GetVectorForKey(entity, epair->key, entity->origin); } else if (!Q_strcasecmp(epair->key, "classname")) { if (!Q_strcasecmp(epair->value, "info_player_start")) { - if (rgfStartSpots & info_player_start) + // Quake II uses multiple starts for level transitions/backtracking. + // TODO: instead, this should check targetnames. There should only be + // one info_player_start per targetname. + if (!options.target_version->quake2 && (rgfStartSpots & info_player_start)) Message(msgWarning, warnMultipleStarts); rgfStartSpots |= info_player_start; } else if (!Q_strcasecmp(epair->value, "info_player_deathmatch")) { diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 1ac89c8a..18c5e86a 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -39,7 +39,7 @@ options_t options; ProcessEntity =============== */ -void +static void ProcessEntity(mapentity_t *entity, const int hullnum) { int i, firstface; @@ -406,23 +406,21 @@ void BSPX_Brushes_AddModel(struct bspxbrushes_s *ctx, int modelnum, brush_t *bru perbrush.maxs[2] = LittleFloat(b->maxs[2]); switch(b->contents) { + //contents should match the engine. case CONTENTS_EMPTY: //really an error, but whatever case CONTENTS_SOLID: //these are okay case CONTENTS_WATER: case CONTENTS_SLIME: case CONTENTS_LAVA: case CONTENTS_SKY: - perbrush.contents = b->contents; - break; - //contents should match the engine. case CONTENTS_CLIP: - perbrush.contents = -8; + perbrush.contents = b->contents; break; // case CONTENTS_LADDER: // perbrush.contents = -16; // break; default: - Message(msgWarning, "Uknown contents: %i. Translating to solid.", b->contents); + Message(msgWarning, "Unknown contents: %i. Translating to solid.", b->contents); perbrush.contents = CONTENTS_SOLID; break; } @@ -537,7 +535,7 @@ CreateSingleHull ================= */ -void +static void CreateSingleHull(const int hullnum) { int i; @@ -560,7 +558,7 @@ CreateHulls ================= */ -void +static void CreateHulls(void) { /* create the hulls sequentially */ @@ -600,14 +598,19 @@ EnsureTexturesLoaded() wadlist_tried_loading = true; - wadlist = NULL; - wadstring = ValueForKey(pWorldEnt(), "_wad"); - if (!wadstring[0]) - wadstring = ValueForKey(pWorldEnt(), "wad"); - if (!wadstring[0]) - Message(msgWarning, warnNoWadKey); - else - wadlist = WADList_Init(wadstring); + // Quake II doesn't use wads, .wal's are loaded from pak/loose files + if (!options.target_version->quake2) { + wadlist = NULL; + wadstring = ValueForKey(pWorldEnt(), "_wad"); + if (!wadstring[0]) + wadstring = ValueForKey(pWorldEnt(), "wad"); + if (!wadstring[0]) + Message(msgWarning, warnNoWadKey); + else + wadlist = WADList_Init(wadstring); + } else { + wadstring = ""; + } if (!wadlist) { if (wadstring[0]) @@ -959,7 +962,7 @@ ParseOptions(char *szOptions) szTok = GetTok(szTok + strlen(szTok) + 1, szEnd); } - // combine format flags + // if we wanted hexen2, update it now if (hexen2) { if (options.target_version == &bspver_bsp2) { options.target_version = &bspver_h2bsp2; @@ -969,6 +972,11 @@ ParseOptions(char *szOptions) options.target_version = &bspver_h2; } } + + // force specific flags for Q2 + if (options.target_version->quake2) { + options.fNoclip = true; + } } diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index 2eff1a6f..6260cc1c 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -30,6 +30,10 @@ static void AssertVanillaContentType(int content) { + if (options.target_version->quake2) { + return; + } + switch (content) { case CONTENTS_EMPTY: case CONTENTS_SOLID: @@ -44,7 +48,7 @@ AssertVanillaContentType(int content) } static int -RemapContentsForExport(int content) +RemapContentsForExport_(int content) { if (content == CONTENTS_DETAIL_FENCE) { /* @@ -61,6 +65,34 @@ RemapContentsForExport(int content) 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 */ @@ -194,14 +226,11 @@ ExportLeaf(mapentity_t *entity, node_t *node) /* * 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]; + 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 @@ -223,6 +252,8 @@ ExportLeaf(mapentity_t *entity, node_t *node) static_cast(map.exported_marksurfaces.size()) - dleaf->firstmarksurface; // FIXME-Q2: fill in other things + dleaf->area = 0; + dleaf->cluster = node->viscluster; } /* @@ -241,13 +272,10 @@ ExportDrawNodes(mapentity_t *entity, node_t *node) dnode = &map.exported_nodes_bsp29[ourNodeIndex]; - // 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]; + 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; @@ -256,7 +284,9 @@ ExportDrawNodes(mapentity_t *entity, node_t *node) // recursively output the other nodes for (i = 0; i < 2; i++) { if (node->children[i]->planenum == -1) { - if (node->children[i]->contents == CONTENTS_SOLID) + // 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(map.exported_leafs_bsp29.size()); @@ -304,9 +334,9 @@ ExportDrawNodes(mapentity_t *entity, node_t *headnode, int firstface) { if (headnode->contents < 0) - ExportLeaf(entity, headnode); - else - ExportDrawNodes(entity, headnode); + ExportLeaf(entity, headnode); + else + ExportDrawNodes(entity, headnode); } // count how many leafs were exported by the above calls @@ -335,7 +365,7 @@ BeginBSPFile(void) // Leave room for leaf 0 (must be solid) map.exported_leafs_bsp29.push_back({}); - map.exported_leafs_bsp29.back().contents = CONTENTS_SOLID; // FIXME-Q2: use Q2_CONTENTS_SOLID + map.exported_leafs_bsp29.back().contents = RemapContentsForExport(CONTENTS_SOLID); Q_assert(map.exported_leafs_bsp29.size() == 1); } @@ -438,6 +468,10 @@ WriteBSPFile() 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);