diff --git a/bspinfo/CMakeLists.txt b/bspinfo/CMakeLists.txt index 550221e0..6f95b1e5 100644 --- a/bspinfo/CMakeLists.txt +++ b/bspinfo/CMakeLists.txt @@ -8,5 +8,5 @@ set(BSPINFO_SOURCES ${COMMON_INCLUDES}) add_executable(bspinfo ${BSPINFO_SOURCES}) -target_link_libraries(bspinfo ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(bspinfo ${CMAKE_THREAD_LIBS_INIT} fmt::fmt) install(TARGETS bspinfo RUNTIME DESTINATION bin) diff --git a/bspinfo/bspinfo.cc b/bspinfo/bspinfo.cc index b5f5bed0..7c447cf3 100644 --- a/bspinfo/bspinfo.cc +++ b/bspinfo/bspinfo.cc @@ -21,6 +21,39 @@ #include #include +inline void PrintBSPInfo(const bspdata_t &bsp) { + printf("brushes:\n"); + for (int32_t i = 0; i < bsp.data.q2bsp.numbrushes; i++) { + printf(" %i: contents: %i, num sides: %i, first side: %i\n", i, bsp.data.q2bsp.dbrushes[i].contents, bsp.data.q2bsp.dbrushes[i].numsides, bsp.data.q2bsp.dbrushes[i].firstside); + } + + printf("brush sides:\n"); + for (int32_t i = 0; i < bsp.data.q2bsp.numbrushsides; i++) { + auto &plane = bsp.data.q2bsp.dplanes[bsp.data.q2bsp.dbrushsides[i].planenum]; + printf(" %i: { %i: %f %f %f -> %f }\n", i, plane.type, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist); + } + + printf("leaves:\n"); + for (int32_t i = 0; i < bsp.data.q2bsp.numleafs; i++) { + auto &leaf = bsp.data.q2bsp.dleafs[i]; + + printf(" %i: contents %i, leafbrushes first %i -> count %i\n", i, leaf.contents, leaf.firstleafbrush, leaf.numleafbrushes); + } + + printf("nodes:\n"); + for (int32_t i = 0; i < bsp.data.q2bsp.numnodes; i++) { + auto &node = bsp.data.q2bsp.dnodes[i]; + auto &plane = bsp.data.q2bsp.dplanes[node.planenum]; + printf(" %i: { %i: %f %f %f -> %f }\n", i, plane.type, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist); + } + + printf("models:\n"); + for (int32_t i = 0; i < bsp.data.q2bsp.nummodels; i++) { + auto &model = bsp.data.q2bsp.dmodels[i]; + printf(" %i: headnode %i (%f %f %f -> %f %f %f)\n", i, model.headnode, model.mins[0], model.mins[1], model.mins[2], model.maxs[0], model.maxs[1], model.maxs[2]); + } +} + int main(int argc, char **argv) { @@ -43,6 +76,8 @@ main(int argc, char **argv) LoadBSPFile(source, &bsp); PrintBSPFileSizes(&bsp); + PrintBSPInfo(bsp); + printf("---------------------\n"); } diff --git a/common/bspfile.cc b/common/bspfile.cc index 59393bb5..b55d4273 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -22,6 +22,8 @@ #include #include +#include + struct gamedef_generic_t : public gamedef_t { gamedef_generic_t() { @@ -72,6 +74,14 @@ struct gamedef_generic_t : public gamedef_t { bool contents_are_valid(const contentflags_t &, bool) const { throw std::bad_cast(); } + + bool portal_can_see_through(const contentflags_t &, const contentflags_t &) const { + throw std::bad_cast(); + } + + std::string get_contents_display(const contentflags_t &contents) const { + throw std::bad_cast(); + } }; template @@ -168,6 +178,33 @@ struct gamedef_q1_like_t : public gamedef_t { bool contents_are_valid(const contentflags_t &contents, bool strict) const { return contents.native <= 0; } + + bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const { + /* If contents values are the same and not solid, can see through */ + return !(contents0.is_structural_solid(this) || contents1.is_structural_solid(this)) && + contents0 == contents1; + } + + std::string get_contents_display(const contentflags_t &contents) const { + switch (contents.native) { + case 0: + return "UNSET"; + case CONTENTS_EMPTY: + return "EMPTY"; + case CONTENTS_SOLID: + return "SOLID"; + case CONTENTS_SKY: + return "SKY"; + case CONTENTS_WATER: + return "WATER"; + case CONTENTS_SLIME: + return "SLIME"; + case CONTENTS_LAVA: + return "LAVA"; + default: + return fmt::to_string(contents.native); + } + } }; struct gamedef_h2_t : public gamedef_q1_like_t { @@ -278,10 +315,101 @@ struct gamedef_q2_t : public gamedef_t { } bool contents_are_valid(const contentflags_t &contents, bool strict) const { - if (!strict) { - return true; + // check that we don't have more than one visible contents type + const int32_t x = (contents.native & ((Q2_LAST_VISIBLE_CONTENTS << 1) - 1)); + if ((x & (x - 1)) != 0) { + return false; } - return !contents_are_empty(contents); + + // TODO: check other invalid mixes + if (!x && strict) { + return false; + } + + return true; + } + + constexpr int32_t visible_contents(const int32_t &contents) const { + for (int32_t i = 1; i <= Q2_LAST_VISIBLE_CONTENTS; i <<= 1) + if (contents & i ) + return i; + + return 0; + } + + bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const { + int32_t c0 = contents0.native, c1 = contents1.native; + + if (!visible_contents(c0 ^ c1)) + return true; + + if ((c0 & Q2_CONTENTS_TRANSLUCENT) || + contents0.is_detail()) + c0 = 0; + if ((c1 & Q2_CONTENTS_TRANSLUCENT) || + contents1.is_detail()) + c1 = 0; + + // can't see through solid + if ((c0 | c1) & Q2_CONTENTS_SOLID) + return false; + + // identical on both sides + if (!(c0 ^ c1)) + return true; + + return visible_contents(c0 ^ c1); + } + + std::string get_contents_display(const contentflags_t &contents) const { + constexpr const char *bitflag_names[] = { + "SOLID", + "WINDOW", + "AUX", + "LAVA", + "SLIME", + "WATER", + "MIST", + "128", + "256", + "512", + "1024", + "2048", + "4096", + "8192", + "16384", + "AREAPORTAL", + "PLAYERCLIP", + "MONSTERCLIP", + "CURRENT_0", + "CURRENT_90", + "CURRENT_180", + "CURRENT_270", + "CURRENT_UP", + "CURRENT_DOWN", + "ORIGIN", + "MONSTER", + "DEADMONSTER", + "DETAIL", + "TRANSLUCENT", + "LADDER", + "1073741824", + "2147483648" + }; + + std::string s; + + for (int32_t i = 0; i < std::size(bitflag_names); i++) { + if (contents.native & (1 << i)) { + if (s.size()) { + s += " | " + std::string(bitflag_names[i]); + } else { + s += bitflag_names[i]; + } + } + } + + return s; } }; @@ -331,6 +459,11 @@ bool contentflags_t::is_valid(const gamedef_t *game, bool strict) const { return game->contents_are_valid(*this, strict); } +std::string contentflags_t::to_string(const gamedef_t *game) const { + std::string s = game->get_contents_display(*this); + return s; +} + static const char * BSPVersionString(const bspversion_t *version) { diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 36d58f10..0705330f 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -344,6 +344,8 @@ struct contentflags_t { bool types_equal(const contentflags_t &other, const gamedef_t *game) const; int32_t priority(const gamedef_t *game) const; + + std::string to_string(const gamedef_t *game) const; }; struct bsp29_dnode_t { @@ -1106,6 +1108,8 @@ struct gamedef_t } virtual bool contents_are_liquid(const contentflags_t &contents) const = 0; virtual bool contents_are_valid(const contentflags_t &contents, bool strict = true) const = 0; + virtual bool portal_can_see_through(const contentflags_t &contents0, const contentflags_t &contents1) const = 0; + virtual std::string get_contents_display(const contentflags_t &contents) const = 0; }; // BSP version struct & instances diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 8c87c4ec..b660b4af 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -214,10 +214,10 @@ struct quark_tx_info_t { std::optional info; }; -int FindMiptex(const char *name, std::optional &extended_info); -inline int FindMiptex(const char *name) { +int FindMiptex(const char *name, std::optional &extended_info, bool internal = false); +inline int FindMiptex(const char *name, bool internal = false) { std::optional extended_info; - return FindMiptex(name, extended_info); + return FindMiptex(name, extended_info, internal); } int FindTexinfo(const mtexinfo_t &texinfo); diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index cce8327a..5364be65 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -170,7 +170,7 @@ typedef struct visfacet_s { struct visfacet_s *original; // face on node int outputnumber; // only valid for original faces after // write surfaces - bool touchesOccupiedLeaf; // internal use in outside.cc + bool touchesOccupiedLeaf; // internal use in outside.cc vec3_t origin; vec_t radius; @@ -272,6 +272,7 @@ public: bool fbspx_brushes; bool fNoTextures; const bspversion_t *target_version = &bspver_q1; + const gamedef_t *target_game = target_version->game; int dxSubdivide; int dxLeakDist; int maxNodeSize; diff --git a/light/CMakeLists.txt b/light/CMakeLists.txt index b8e129bd..6ebc72ee 100644 --- a/light/CMakeLists.txt +++ b/light/CMakeLists.txt @@ -55,7 +55,7 @@ if (embree_FOUND) endif(embree_FOUND) add_executable(light ${LIGHT_SOURCES} main.cc) -target_link_libraries (light PRIVATE ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries (light PRIVATE ${CMAKE_THREAD_LIBS_INIT} fmt::fmt) if (embree_FOUND) target_link_libraries (light PRIVATE embree) diff --git a/qbsp/CMakeLists.txt b/qbsp/CMakeLists.txt index 2460ebf4..7f3a10b3 100644 --- a/qbsp/CMakeLists.txt +++ b/qbsp/CMakeLists.txt @@ -1,7 +1,7 @@ add_definitions(-DDOUBLEVEC_T) add_executable(qbsp ${QBSP_SOURCES} main.cc) -target_link_libraries(qbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb) +target_link_libraries(qbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb fmt::fmt) install(TARGETS qbsp RUNTIME DESTINATION bin) # test (copied from light/CMakeLists.txt) @@ -14,4 +14,4 @@ set(QBSP_TEST_SOURCE add_executable(testqbsp EXCLUDE_FROM_ALL ${QBSP_TEST_SOURCE}) add_test(testqbsp testqbsp) -target_link_libraries (testqbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb gtest) +target_link_libraries (testqbsp ${CMAKE_THREAD_LIBS_INIT} TBB::tbb gtest fmt::fmt) diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 16e9be67..378ec6a8 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -169,13 +169,11 @@ AddToBounds(mapentity_t *entity, const vec3_t point) //=========================================================================== static int -NormalizePlane(qbsp_plane_t *p) +NormalizePlane(qbsp_plane_t *p, bool flip = true) { int i; vec_t ax, ay, az; - p->outputplanenum = PLANENUM_LEAF; - for (i = 0; i < 3; i++) { if (p->normal[i] == 1.0) { p->normal[(i + 1) % 3] = 0; @@ -184,10 +182,12 @@ NormalizePlane(qbsp_plane_t *p) return 0; /* no flip */ } if (p->normal[i] == -1.0) { + if (flip) { p->normal[i] = 1.0; + p->dist = -p->dist; + } p->normal[(i + 1) % 3] = 0; p->normal[(i + 2) % 3] = 0; - p->dist = -p->dist; p->type = PLANE_X + i; return 1; /* plane flipped */ } @@ -204,7 +204,7 @@ NormalizePlane(qbsp_plane_t *p) else p->type = PLANE_ANYZ; - if (p->normal[p->type - PLANE_ANYX] < 0) { + if (flip && p->normal[p->type - PLANE_ANYX] < 0) { VectorSubtract(vec3_origin, p->normal, p->normal); p->dist = -p->dist; return 1; /* plane flipped */ @@ -262,7 +262,13 @@ NewPlane(const vec3_t normal, const vec_t dist, int *side) qbsp_plane_t plane; VectorCopy(normal, plane.normal); plane.dist = dist; - *side = NormalizePlane(&plane) ? SIDE_BACK : SIDE_FRONT; + plane.outputplanenum = PLANENUM_LEAF; + + int32_t out_side = NormalizePlane(&plane, side != nullptr); + + if (side) { + *side = out_side; + } int index = map.planes.size(); map.planes.push_back(plane); @@ -273,6 +279,7 @@ NewPlane(const vec3_t normal, const vec_t dist, int *side) /* * FindPlane * - Returns a global plane number and the side that will be the front + * - if `side` is null, only an exact match will be fetched. */ int FindPlane(const vec3_t normal, const vec_t dist, int *side) @@ -284,9 +291,11 @@ FindPlane(const vec3_t normal, const vec_t dist, int *side) for (int i : map.planehash[plane_hash_fn(&plane)]) { const qbsp_plane_t &p = map.planes.at(i); if (PlaneEqual(&p, &plane)) { + if (side) { *side = SIDE_FRONT; + } return i; - } else if (PlaneInvEqual(&p, &plane)) { + } else if (side && PlaneInvEqual(&p, &plane)) { *side = SIDE_BACK; return i; } @@ -394,11 +403,11 @@ CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush, hullbrush->maxs[i] = -VECT_MAX; } - auto DiscardHintSkipFace = (options.target_version->game->id == GAME_QUAKE_II) ? DiscardHintSkipFace_Q2 : DiscardHintSkipFace_Q1; + auto DiscardHintSkipFace = (options.target_game->id == GAME_QUAKE_II) ? DiscardHintSkipFace_Q2 : DiscardHintSkipFace_Q1; mapface = hullbrush->faces; for (i = 0; i < hullbrush->numfaces; i++, mapface++) { - if (!hullnum && hullbrush->contents.is_hint()) { + if (hullnum <= 0 && hullbrush->contents.is_hint()) { /* Don't generate hintskip faces */ const mtexinfo_t &texinfo = map.mtexinfos.at(mapface->texinfo); @@ -499,7 +508,7 @@ CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush, (rotate_offset[0] != 0.0 || rotate_offset[1] != 0.0 || rotate_offset[2] != 0.0) && rottype == rotation_t::hipnotic && (hullnum >= 0) // hullnum < 0 corresponds to -wrbrushes clipping hulls - && options.target_version->game->id != GAME_HEXEN_II; // never do this in Hexen 2 + && options.target_game->id != GAME_HEXEN_II; // never do this in Hexen 2 if (shouldExpand) { vec_t delta; @@ -849,20 +858,20 @@ Brush_IsDetail(const mapbrush_t *mapbrush) // contents. static bool AdjustContentsFromName(const char *texname, contentflags_t &flags) { if (!Q_strcasecmp(texname, "origin")) - flags = flags.merge(options.target_version->game->create_empty_contents(CFLAGS_ORIGIN)); + flags = flags.merge(options.target_game->create_empty_contents(CFLAGS_ORIGIN)); else if (!Q_strcasecmp(texname, "hint")) - flags = flags.merge(options.target_version->game->create_empty_contents(CFLAGS_HINT)); + flags = flags.merge(options.target_game->create_empty_contents(CFLAGS_HINT)); else if (!Q_strcasecmp(texname, "clip")) - flags = flags.merge(options.target_version->game->create_solid_contents(CFLAGS_CLIP)); + flags = flags.merge(options.target_game->create_solid_contents(CFLAGS_CLIP)); else if (texname[0] == '*') { if (!Q_strncasecmp(texname + 1, "lava", 4)) - flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_LAVA)); + flags = flags.merge(options.target_game->create_liquid_contents(CONTENTS_LAVA)); else if (!Q_strncasecmp(texname + 1, "slime", 5)) - flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_SLIME)); - flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_WATER)); + flags = flags.merge(options.target_game->create_liquid_contents(CONTENTS_SLIME)); + flags = flags.merge(options.target_game->create_liquid_contents(CONTENTS_WATER)); } else if (!Q_strncasecmp(texname, "sky", 3)) - flags = flags.merge(options.target_version->game->create_sky_contents()); + flags = flags.merge(options.target_game->create_sky_contents()); else return false; @@ -888,7 +897,7 @@ Brush_GetContents_Q1(const mapbrush_t *mapbrush, const contentflags_t &base_cont } //and anything else is assumed to be a regular solid. - return options.target_version->game->create_solid_contents(); + return options.target_game->create_solid_contents(); } static contentflags_t @@ -903,6 +912,10 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con const mapface_t &mapface = mapbrush->face(i); const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); + if (texinfo.flags.extended & TEX_EXFLAG_SKIP) { + continue; + } + if (!is_trans && (texinfo.flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) { is_trans = true; } @@ -912,7 +925,8 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con } if (mapface.contents != contents.native) { - logprint("mixed face contents\n"); // TODO: need entity # and brush # + logprint("mixed face contents (%s != %s at line %i)\n", contentflags_t { mapface.contents }.to_string(options.target_game).c_str(), + contents.to_string(options.target_game).c_str(), mapface.linenum); // TODO: need entity # and brush # break; } } @@ -920,7 +934,10 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con // if any side is translucent, mark the contents // and change solid to window if (is_trans) { - contents.native = (contents.native & ~Q2_CONTENTS_SOLID) | (Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_WINDOW); + contents.native |= Q2_CONTENTS_TRANSLUCENT; + if (contents.native & Q2_CONTENTS_SOLID) { + contents.native = (contents.native & ~Q2_CONTENTS_SOLID) | Q2_CONTENTS_WINDOW; + } } // add extended flags that we may need @@ -936,10 +953,25 @@ Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_con contents.extended |= CFLAGS_ORIGIN; } + if (contents.native & Q2_CONTENTS_MIST) { + contents.extended |= CFLAGS_DETAIL_ILLUSIONARY; + } + if (is_hint) { contents.extended |= CFLAGS_HINT; } + // FIXME: this is a bit of a hack, but this is because clip + // and liquids and stuff are already handled *like* detail by + // the compiler. + if (contents.extended & CFLAGS_DETAIL) { + if (!(contents.native & Q2_CONTENTS_SOLID)) { + contents.extended &= ~CFLAGS_DETAIL; + } + } + + Q_assert(contents.is_valid(options.target_game, false)); + return contents; } @@ -985,7 +1017,7 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const con return NULL; } - if (options.target_version == &bspver_hl) + if (options.target_game->id == GAME_HALF_LIFE) { if (hullnum == 1) { vec3_t size[2] = { {-16, -16, -36}, {16, 16, 36} }; @@ -1006,7 +1038,7 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const con facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum); } } - else if (options.target_version->game->id == GAME_HEXEN_II) + else if (options.target_game->id == GAME_HEXEN_II) { if (hullnum == 1) { vec3_t size[2] = { {-16, -16, -32}, {16, 16, 24} }; @@ -1200,14 +1232,14 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) const bool func_illusionary_visblocker = (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); - contentflags_t base_contents = options.target_version->game->create_empty_contents(); + contentflags_t base_contents = options.target_game->create_empty_contents(); if (func_illusionary_visblocker) { base_contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER; } // TODO: move to game - auto Brush_GetContents = (options.target_version->game->id == GAME_QUAKE_II) ? Brush_GetContents_Q2 : Brush_GetContents_Q1; + auto Brush_GetContents = (options.target_game->id == GAME_QUAKE_II) ? Brush_GetContents_Q2 : Brush_GetContents_Q1; for (int i = 0; i < src->nummapbrushes; i++) { const mapbrush_t *mapbrush = &src->mapbrush(i); @@ -1314,19 +1346,19 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) continue; /* turn solid brushes into detail, if we're in hull0 */ - if (hullnum == 0 && contents.is_solid(options.target_version->game)) { + if (hullnum <= 0 && contents.is_solid(options.target_game)) { if (detail) { contents.extended |= CFLAGS_DETAIL; } else if (detail_illusionary) { - contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_ILLUSIONARY)); + contents = contents.merge(options.target_game->create_empty_contents(CFLAGS_DETAIL_ILLUSIONARY)); } else if (detail_fence) { - contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_FENCE)); // fences need to generate leaves + contents = contents.merge(options.target_game->create_empty_contents(CFLAGS_DETAIL_FENCE)); // fences need to generate leaves } } /* func_detail_illusionary don't exist in the collision hull * (or bspx export) */ - if (hullnum && detail_illusionary) { + if ((options.target_game->id != GAME_QUAKE_II && hullnum) && detail_illusionary) { continue; } @@ -1349,14 +1381,14 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) /* "hint" brushes don't affect the collision hulls */ if (contents.is_hint()) { - if (hullnum) + if (hullnum > 0) continue; - contents = contents.merge(options.target_version->game->create_empty_contents()); + contents = contents.merge(options.target_game->create_empty_contents()); } /* entities never use water merging */ if (dst != pWorldEnt()) - contents = contents.merge(options.target_version->game->create_solid_contents()); + contents = contents.merge(options.target_game->create_solid_contents()); /* Hack to turn bmodels with "_mirrorinside" into func_detail_fence in hull 0. this is to allow "_mirrorinside" to work on func_illusionary, func_wall, etc. @@ -1366,19 +1398,19 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) before writing the bsp, and bmodels normally have CONTENTS_SOLID as their contents type. */ - if (dst != pWorldEnt() && hullnum == 0 && (contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE)) { - contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_FENCE)); + if (dst != pWorldEnt() && hullnum <= 0 && (contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE)) { + contents = contents.merge(options.target_game->create_empty_contents(CFLAGS_DETAIL_FENCE)); } /* nonsolid brushes don't show up in clipping hulls */ // TODO: will this statement need to be modified since clip // detail etc aren't native types any more? - if (hullnum > 0 && !contents.is_solid(options.target_version->game) && !contents.is_sky(options.target_version->game)) + if (hullnum > 0 && !contents.is_solid(options.target_game) && !contents.is_sky(options.target_game)) continue; /* sky brushes are solid in the collision hulls */ - if (hullnum > 0 && contents.is_sky(options.target_version->game)) - contents = contents.merge(options.target_version->game->create_solid_contents()); + if (hullnum > 0 && contents.is_sky(options.target_game)) + contents = contents.merge(options.target_game->create_solid_contents()); brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum); if (!brush) @@ -1396,10 +1428,10 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) } else if (brush->contents.is_detail(CFLAGS_DETAIL_FENCE)) { brush->next = dst->detail_fence; dst->detail_fence = brush; - } else if (brush->contents.is_solid(options.target_version->game)) { + } else if (brush->contents.is_solid(options.target_game)) { brush->next = dst->solid; dst->solid = brush; - } else if (brush->contents.is_sky(options.target_version->game)) { + } else if (brush->contents.is_sky(options.target_game)) { brush->next = dst->sky; dst->sky = brush; } else { diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 1bcbe08e..9255eb76 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -55,9 +55,8 @@ MakeSkipTexinfo() mtexinfo_t mt { }; - mt.miptex = FindMiptex("skip"); + mt.miptex = FindMiptex("skip", true); mt.flags = { 0, TEX_EXFLAG_SKIP }; - memset(&mt.vecs, 0, sizeof(mt.vecs)); return FindTexinfo(mt); } @@ -374,7 +373,7 @@ SaveFacesToPlaneList(face_t *facelist, bool mirror, std::map &pla // to force the right content type for the leaf, but we don't actually // want the face. So just set the texinfo to "skip" so it gets deleted. if ((face->contents[1].is_detail() || (face->contents[1].extended & CFLAGS_WAS_ILLUSIONARY)) - || (options.fContentHack && face->contents[1].is_structural_solid(options.target_version->game))) { + || (options.fContentHack && face->contents[1].is_structural_solid(options.target_game))) { // if CFLAGS_BMODEL_MIRROR_INSIDE is set, never change to skip if (!(face->contents[1].extended & CFLAGS_BMODEL_MIRROR_INSIDE)) { @@ -423,7 +422,7 @@ contents override the face inside contents. static void SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) { - Q_assert(!clipbrush->contents.is_structural_solid(options.target_version->game)); + Q_assert(!clipbrush->contents.is_structural_solid(options.target_game)); face_t *next; @@ -434,7 +433,7 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) next = face->next; face->contents[0] = clipbrush->contents; - if (face->contents[1].is_structural_sky_or_solid(options.target_version->game) + if (face->contents[1].is_structural_sky_or_solid(options.target_game) && clipbrush->contents.is_detail(CFLAGS_DETAIL)) { // This case is when a structural and detail brush are touching, // and we want to save the sturctural face that is @@ -450,14 +449,14 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) // marked as empty here, and the detail faces have their "back" // marked as detail. - face->contents[0] = options.target_version->game->create_empty_contents(CFLAGS_STRUCTURAL_COVERED_BY_DETAIL); + face->contents[0] = options.target_game->create_empty_contents(CFLAGS_STRUCTURAL_COVERED_BY_DETAIL); face->texinfo = MakeSkipTexinfo(); } // N.B.: We don't need a hack like above for when clipbrush->contents == CONTENTS_DETAIL_ILLUSIONARY. // These would create leaks - Q_assert(!(face->contents[1].is_structural_sky_or_solid(options.target_version->game) && + Q_assert(!(face->contents[1].is_structural_sky_or_solid(options.target_game) && face->contents[0].is_detail(CFLAGS_DETAIL))); /* @@ -467,7 +466,7 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) if (face->contents[1].is_detail(CFLAGS_DETAIL_ILLUSIONARY)) { face->contents[1] = { clipbrush->contents.native, (face->contents[1].extended & ~CFLAGS_DETAIL_ILLUSIONARY) | CFLAGS_WAS_ILLUSIONARY }; } - if (face->contents[1].is_empty(options.target_version->game)) { + if (face->contents[1].is_empty(options.target_game)) { face->contents[1] = clipbrush->contents; } @@ -533,7 +532,7 @@ CopyBrushFaces(const brush_t *brush) brushfaces++; newface = (face_t *)AllocMem(OTHER, sizeof(face_t), true); *newface = *face; - newface->contents[0] = options.target_version->game->create_empty_contents(); + newface->contents[0] = options.target_game->create_empty_contents(); newface->contents[1] = brush->contents; newface->lmshift[0] = brush->lmshift; newface->lmshift[1] = brush->lmshift; @@ -626,7 +625,7 @@ CSGFaces(const mapentity_t *entity) // TODO: this might break because this == won't catch the extended types now. // might need a specific function for this one. - if (clipbrush->contents.types_equal(brush->contents, options.target_version->game) + if (clipbrush->contents.types_equal(brush->contents, options.target_game) && !clipbrush->contents.clips_same_type()) { /* _noclipfaces key */ continue; @@ -668,18 +667,18 @@ CSGFaces(const mapentity_t *entity) * * FIXME: clean this up, the predicate seems to be "can you see 'brush' from inside 'clipbrush'" */ - if ((brush->contents.is_structural_solid(options.target_version->game) && !clipbrush->contents.is_structural_solid(options.target_version->game)) + if ((brush->contents.is_structural_solid(options.target_game) && !clipbrush->contents.is_structural_solid(options.target_game)) - || (brush->contents.is_structural_sky(options.target_version->game) && !clipbrush->contents.is_structural_sky_or_solid(options.target_version->game)) + || (brush->contents.is_structural_sky(options.target_game) && !clipbrush->contents.is_structural_sky_or_solid(options.target_game)) - || ((brush->contents.is_solid(options.target_version->game) && brush->contents.is_detail(CFLAGS_DETAIL)) && - (!clipbrush->contents.is_solid(options.target_version->game) - && !clipbrush->contents.is_sky(options.target_version->game) + || ((brush->contents.is_solid(options.target_game) && brush->contents.is_detail(CFLAGS_DETAIL)) && + (!clipbrush->contents.is_solid(options.target_game) + && !clipbrush->contents.is_sky(options.target_game) && !clipbrush->contents.is_detail(CFLAGS_DETAIL))) - || (brush->contents.is_liquid(options.target_version->game) && clipbrush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY)) + || (brush->contents.is_liquid(options.target_game) && clipbrush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY)) - || (brush->contents.is_fence() && (clipbrush->contents.is_liquid(options.target_version->game) || clipbrush->contents.is_fence()))) + || (brush->contents.is_fence() && (clipbrush->contents.is_liquid(options.target_game) || clipbrush->contents.is_fence()))) { SaveInsideFaces(inside, clipbrush, &outside); } else { @@ -701,7 +700,7 @@ CSGFaces(const mapentity_t *entity) * All of the faces left on the outside list are real surface faces * If the brush is non-solid, mirror faces for the inside view */ - const bool mirror = options.fContentHack ? true : !brush->contents.is_solid(options.target_version->game); + const bool mirror = options.fContentHack ? true : !brush->contents.is_solid(options.target_game); SaveFacesToPlaneList(outside, mirror, planefaces); } surface_t *surfaces = BuildSurfaces(planefaces); diff --git a/qbsp/map.cc b/qbsp/map.cc index caa25bec..0c4fa68b 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -195,19 +195,17 @@ static std::optional LoadWal(const char *name) { } int -FindMiptex(const char *name, std::optional &extended_info) +FindMiptex(const char *name, std::optional &extended_info, bool internal) { const char *pathsep; int i; - if (options.target_version->game->id != GAME_QUAKE_II) { + if (options.target_game->id != GAME_QUAKE_II) { /* Ignore leading path in texture names (Q2 map compatibility) */ pathsep = strrchr(name, '/'); if (pathsep) name = pathsep + 1; - extended_info = extended_texinfo_t { }; - for (i = 0; i < map.nummiptex(); i++) { const texdata_t &tex = map.miptex.at(i); @@ -226,18 +224,22 @@ FindMiptex(const char *name, std::optional &extended_info) } } else { // load .wal first - std::optional wal = LoadWal(name); + std::optional wal; - // FIXME: this spams the console if wal not found, because we - // need to load the wal to figure out if it will match anything - // in the list... - if (!wal.has_value()) { - Message(msgLiteral, "Couldn't locate wal for %s\n", name); - wal = wal_t {}; + if (!internal || !extended_info.has_value()) { + wal = LoadWal(name); + + if (!wal.has_value()) { + //FIXME + //Message(msgLiteral, "Couldn't locate wal for %s\n", name); + if (!extended_info.has_value()) { + extended_info = extended_texinfo_t { }; + } + } else if (!extended_info.has_value()) { + extended_info = extended_texinfo_t { wal->contents, wal->flags, wal->value }; + } } - extended_info = extended_info.value_or(extended_texinfo_t { wal->contents, wal->flags, wal->value }); - for (i = 0; i < map.nummiptex(); i++) { const texdata_t &tex = map.miptex.at(i); @@ -250,10 +252,10 @@ FindMiptex(const char *name, std::optional &extended_info) } i = map.miptex.size(); - map.miptex.push_back({ name, wal->flags, wal->value }); + map.miptex.push_back({ name, extended_info->flags, extended_info->value }); /* Handle animating textures carefully */ - if (wal->anim_name[0]) { + if (wal && wal->anim_name[0]) { FindMiptex(wal->anim_name); } } @@ -365,7 +367,13 @@ SurfFlagsForEntity(const mtexinfo_t &texinfo, const mapentity_t *entity) const char *texname = map.miptex.at(texinfo.miptex).name.c_str(); const int shadow = atoi(ValueForKey(entity, "_shadow")); // These flags are pulled from surf flags in Q2. - if (options.target_version->game->id != GAME_QUAKE_II) { + // TODO: the Q1 version of this block can now be moved into texinfo + // loading by shoving them inside of texinfo.flags like + // Q2 does. Similarly, we can move the Q2 block out + // into a special function, like.. I dunno, + // game->surface_flags_from_name(surfflags_t &inout, const char *name) + // which we can just call instead of this block. + if (options.target_game->id != GAME_QUAKE_II) { if (IsSkipName(texname)) flags.extended |= TEX_EXFLAG_SKIP; if (IsHintName(texname)) @@ -375,9 +383,15 @@ SurfFlagsForEntity(const mtexinfo_t &texinfo, const mapentity_t *entity) } else { flags.native = texinfo.flags.native; - if (texinfo.flags.native & Q2_SURF_NODRAW) + // This fixes a bug in some old maps. + if ((flags.native & (Q2_SURF_SKY | Q2_SURF_NODRAW)) == (Q2_SURF_SKY | Q2_SURF_NODRAW)) { + flags.native &= ~Q2_SURF_NODRAW; + //logprint("Corrected invalid SKY flag\n"); + } + + if ((flags.native & Q2_SURF_NODRAW) || IsSkipName(texname)) flags.extended |= TEX_EXFLAG_SKIP; - if (texinfo.flags.native & Q2_SURF_HINT) + if ((flags.native & Q2_SURF_HINT) || IsHintName(texname)) flags.extended |= TEX_EXFLAG_HINT; } if (IsNoExpandName(texname)) @@ -488,7 +502,7 @@ ParseEpair(parser_t *parser, mapentity_t *entity) // 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 in Q2. - if (options.target_version->game->id != GAME_QUAKE_II && (rgfStartSpots & info_player_start)) + if (options.target_game->id != GAME_QUAKE_II && (rgfStartSpots & info_player_start)) Message(msgWarning, warnMultipleStarts); rgfStartSpots |= info_player_start; } else if (!Q_strcasecmp(epair->value, "info_player_deathmatch")) { @@ -1485,6 +1499,8 @@ ParseTextureDef(parser_t *parser, mapface_t &mapface, const mapbrush_t *brush, m tx->flags = mapface.flags = { extinfo.info->flags }; tx->value = mapface.value = extinfo.info->value; + Q_assert(contentflags_t { mapface.contents }.is_valid(options.target_game, false)); + if (!planepts || !plane) return; @@ -1678,11 +1694,12 @@ ParseBrush(parser_t *parser, const mapentity_t *entity) if (face.get() == nullptr) continue; - if (options.target_version->game->id == GAME_QUAKE_II) { + // FIXME: can we move this somewhere later? + if (options.target_game->id == GAME_QUAKE_II) { // translucent objects are automatically classified as detail - //if ((face->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) - // || (face->contents & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP))) - // face->contents |= Q2_CONTENTS_DETAIL; + if ((face->flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66)) + || (face->contents & (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP))) + face->contents |= Q2_CONTENTS_DETAIL; if (!(face->contents & (((Q2_LAST_VISIBLE_CONTENTS << 1)-1) | Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP) ) ) diff --git a/qbsp/outside.cc b/qbsp/outside.cc index 43e488b5..70fbe67f 100644 --- a/qbsp/outside.cc +++ b/qbsp/outside.cc @@ -338,7 +338,7 @@ ClearOutFaces(node_t *node) } // visit the leaf - if (!node->contents.is_solid(options.target_version->game)) { + if (!node->contents.is_solid(options.target_game)) { return; } @@ -365,8 +365,8 @@ OutLeafsToSolid_r(node_t *node, int *outleafs_count) return; // Don't fill sky, or count solids as outleafs - if (node->contents.is_sky(options.target_version->game) - || node->contents.is_solid(options.target_version->game)) + if (node->contents.is_sky(options.target_game) + || node->contents.is_solid(options.target_game)) return; // Now check all faces touching the leaf. If any of them are partially going into the occupied part of the map, @@ -383,7 +383,7 @@ OutLeafsToSolid_r(node_t *node, int *outleafs_count) } // Finally, we can fill it in as void. - node->contents = options.target_version->game->create_solid_contents(); + node->contents = options.target_game->create_solid_contents(); *outleafs_count += 1; } diff --git a/qbsp/portals.cc b/qbsp/portals.cc index b4bca728..f5a9d929 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -59,47 +59,42 @@ ClusterContents(const node_t *node) if (node->planenum == PLANENUM_LEAF) return node->contents; - return options.target_version->game->cluster_contents(ClusterContents(node->children[0]), ClusterContents(node->children[1])); + return options.target_game->cluster_contents(ClusterContents(node->children[0]), ClusterContents(node->children[1])); } -/* - * Return true if possible to see the through the contents of the portals nodes - */ +/* Return true if possible to see the through the contents of the portals nodes */ static bool PortalThru(const portal_t *p) { contentflags_t contents0 = ClusterContents(p->nodes[0]); contentflags_t contents1 = ClusterContents(p->nodes[1]); - /* Can't see through solids */ - if (contents0.is_structural_solid(options.target_version->game) || contents1.is_structural_solid(options.target_version->game)) - return false; - /* Can't see through func_illusionary_visblocker */ if ((contents0.extended | contents1.extended) & CFLAGS_ILLUSIONARY_VISBLOCKER) return false; - /* If contents values are the same and not solid, can see through */ - if (contents0 == contents1) - return true; + // FIXME: we can't move this directly to portal_can_see_through because + // "options" isn't exposed there. + if (options.target_game->id != GAME_QUAKE_II) { + /* If water is transparent, liquids are like empty space */ + if (options.fTranswater) { + if (contents0.is_liquid(options.target_game) && contents1.is_empty(options.target_game)) + return true; + if (contents1.is_liquid(options.target_game) && contents0.is_empty(options.target_game)) + return true; + } - /* If water is transparent, liquids are like empty space */ - if (options.fTranswater) { - if (contents0.is_liquid(options.target_version->game) && contents1.is_empty(options.target_version->game)) - return true; - if (contents1.is_liquid(options.target_version->game) && contents0.is_empty(options.target_version->game)) - return true; + /* If sky is transparent, then sky is like empty space */ + if (options.fTranssky) { + if (contents0.is_sky(options.target_game) && contents1.is_empty(options.target_game)) + return true; + if (contents0.is_empty(options.target_game) && contents1.is_sky(options.target_game)) + return true; + } } - /* If sky is transparent, then sky is like empty space */ - if (options.fTranssky) { - if (contents0.is_sky(options.target_version->game) && contents1.is_empty(options.target_version->game)) - return true; - if (contents0.is_empty(options.target_version->game) && contents1.is_sky(options.target_version->game)) - return true; - } - - return false; + // Check per-game visibility + return options.target_game->portal_can_see_through(contents0, contents1); } static void @@ -116,7 +111,7 @@ WritePortals_r(node_t *node, FILE *portalFile, bool clusters) WritePortals_r(node->children[1], portalFile, clusters); return; } - if (node->contents.is_solid(options.target_version->game)) + if (node->contents.is_solid(options.target_game)) return; for (p = node->portals; p; p = next) { @@ -161,7 +156,7 @@ WriteClusters_r(node_t *node, FILE *portalFile, int viscluster) viscluster = WriteClusters_r(node->children[1], portalFile, viscluster); return viscluster; } - if (node->contents.is_solid(options.target_version->game)) + if (node->contents.is_solid(options.target_game)) return viscluster; /* If we're in the next cluster, start a new line */ @@ -223,7 +218,7 @@ NumberLeafs_r(node_t *node, portal_state_t *state, int cluster) return; } - if (node->contents.is_solid(options.target_version->game)) { + if (node->contents.is_structural_solid(options.target_game)) { /* solid block, viewpoint never inside */ node->visleafnum = -1; node->viscluster = -1; @@ -380,7 +375,7 @@ MakeHeadnodePortals(const mapentity_t *entity, node_t *node) } outside_node.planenum = PLANENUM_LEAF; - outside_node.contents = options.target_version->game->create_solid_contents(); + outside_node.contents = options.target_game->create_solid_contents(); outside_node.portals = NULL; for (i = 0; i < 3; i++) @@ -671,7 +666,7 @@ PortalizeWorld(const mapentity_t *entity, node_t *headnode, const int hullnum) MakeHeadnodePortals(entity, headnode); CutNodePortals_r(headnode, &state); - if (!hullnum) { + if (hullnum <= 0) { /* save portal file for vis tracing */ WritePortalfile(headnode, &state); diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 1b831e84..dca0daa5 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -20,7 +20,8 @@ */ #include -#include +#include +#include #include #include @@ -36,27 +37,38 @@ static const char *IntroString = options_t options; bool node_t::opaque() const { - return contents.is_structural_sky_or_solid(options.target_version->game); + return contents.is_structural_sky_or_solid(options.target_game); } +static struct { + uint32_t total_brushes, total_brush_sides; + uint32_t total_leaf_brushes, unique_leaf_brushes; +} brush_state; + static void ExportBrushList_r(const mapentity_t *entity, node_t *node) { if (node->planenum == PLANENUM_LEAF) { - if (node->visleafnum == -1) { - node->firstleafbrush = map.exported_leafbrushes.size(); - node->numleafbrushes = 0; - + if (node->contents.native) { int32_t b_id = 0; + std::vector brushes; for (const brush_t *b = entity->brushes; b; b = b->next, b_id++) { if (aabb3f(qvec3f(node->mins[0], node->mins[1], node->mins[2]), qvec3f(node->maxs[0], node->maxs[1], node->maxs[2])).intersectWith( aabb3f(qvec3f(b->mins[0], b->mins[1], b->mins[2]), qvec3f(b->maxs[0], b->maxs[1], b->maxs[2]))).valid) { - map.exported_leafbrushes.push_back(b_id); - node->numleafbrushes++; + brushes.push_back(b_id); } } + + node->numleafbrushes = brushes.size(); + brush_state.total_leaf_brushes += node->numleafbrushes; + + if (brushes.size()) { + node->firstleafbrush = map.exported_leafbrushes.size(); + map.exported_leafbrushes.insert(map.exported_leafbrushes.end(), brushes.begin(), brushes.end()); + brush_state.unique_leaf_brushes += node->numleafbrushes; + } } return; @@ -68,23 +80,75 @@ static void ExportBrushList_r(const mapentity_t *entity, node_t *node) static void ExportBrushList(const mapentity_t *entity, node_t *node) { + brush_state = { }; + for (const brush_t *b = entity->brushes; b; b = b->next) { dbrush_t brush { (int32_t) map.exported_brushsides.size(), 0, b->contents.native }; for (const face_t *f = b->faces; f; f = f->next) { - if (map.planes[f->planenum].outputplanenum == -1) { - continue; + int32_t planenum = f->planenum; + int32_t outputplanenum; + + if (f->planeside) { + vec3_t flipped; + VectorCopy(map.planes[f->planenum].normal, flipped); + VectorInverse(flipped); + planenum = FindPlane(flipped, -map.planes[f->planenum].dist, nullptr); + outputplanenum = ExportMapPlane(planenum); + } else { + planenum = FindPlane(map.planes[f->planenum].normal, map.planes[f->planenum].dist, nullptr); + outputplanenum = ExportMapPlane(planenum); } - map.exported_brushsides.push_back({ (uint32_t) map.planes[f->planenum].outputplanenum, map.mtexinfos[f->texinfo].outputnum.value_or(-1) }); + + map.exported_brushsides.push_back({ (uint32_t) outputplanenum, map.mtexinfos[f->texinfo].outputnum.value_or(-1) }); brush.numsides++; + brush_state.total_brush_sides++; } + // add any axis planes not contained in the brush to bevel off corners + for (int32_t x=0 ; x<3 ; x++) + for (int32_t s=-1 ; s<=1 ; s+=2) + { + // add the plane + vec3_t normal { }; + float dist; + VectorCopy (vec3_origin, normal); + normal[x] = s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + int32_t side; + int32_t planenum = FindPlane(normal, dist, &side); + face_t *f; + + for (f = b->faces; f; f = f->next) + if (f->planenum == planenum) + break; + + if (f == nullptr) + { + planenum = FindPlane(normal, dist, nullptr); + int32_t outputplanenum = ExportMapPlane(planenum); + + map.exported_brushsides.push_back({ (uint32_t) outputplanenum, map.exported_brushsides[map.exported_brushsides.size() - 1].texinfo }); + brush.numsides++; + brush_state.total_brush_sides++; + } + } + map.exported_brushes.push_back(brush); + brush_state.total_brushes++; } ExportBrushList_r(entity, node); + + Message(msgStat, "%8u total brushes", brush_state.total_brushes); + Message(msgStat, "%8u total brush sides", brush_state.total_brush_sides); + Message(msgStat, "%8u total leaf brushes", brush_state.total_leaf_brushes); + Message(msgStat, "%8u unique leaf brushes (%.2f%%)", brush_state.unique_leaf_brushes, (brush_state.unique_leaf_brushes / (float) brush_state.total_leaf_brushes) * 100); } /* @@ -126,7 +190,7 @@ ProcessEntity(mapentity_t *entity, const int hullnum) if (options.fVerbose) PrintEntity(entity); - if (hullnum == 0) + if (hullnum <= 0) Message(msgStat, "MODEL: %s", mod); SetKeyValue(entity, "model", mod); } @@ -218,11 +282,11 @@ ProcessEntity(mapentity_t *entity, const int hullnum) */ surfs = CSGFaces(entity); - if (options.fObjExport && entity == pWorldEnt() && hullnum == 0) { + if (options.fObjExport && entity == pWorldEnt() && hullnum <= 0) { ExportObj_Surfaces("post_csg", surfs); } - if (hullnum != 0) { + if (hullnum > 0) { nodes = SolidBSP(entity, surfs, true); if (entity == pWorldEnt() && !options.fNofill) { // assume non-world bmodels are simple @@ -297,11 +361,13 @@ ProcessEntity(mapentity_t *entity, const int hullnum) } firstface = MakeFaceEdges(entity, nodes); - ExportDrawNodes(entity, nodes, firstface); - if (options.target_version->game->id == GAME_QUAKE_II) { + if (options.target_game->id == GAME_QUAKE_II) { + Message(msgProgress, "Calculating Brush List"); ExportBrushList(entity, nodes); } + + ExportDrawNodes(entity, nodes, firstface); } FreeBrushes(entity); @@ -624,6 +690,12 @@ CreateHulls(void) if (!options.fNoverbose) options.fVerbose = true; + if (options.target_game->id == GAME_QUAKE_II) + { + CreateSingleHull(-1); + return; + } + CreateSingleHull(0); /* ignore the clipping hulls altogether */ @@ -633,9 +705,10 @@ CreateHulls(void) CreateSingleHull(1); CreateSingleHull(2); - if (options.target_version == &bspver_hl) + // FIXME: use game->get_hull_count + if (options.target_game->id == GAME_HALF_LIFE) CreateSingleHull(3); - else if (options.target_version->game->id == GAME_HEXEN_II) + else if (options.target_game->id == GAME_HEXEN_II) { /*note: h2mp doesn't use hull 2 automatically, however gamecode can explicitly set ent.hull=3 to access it*/ CreateSingleHull(3); CreateSingleHull(4); @@ -1033,9 +1106,12 @@ ParseOptions(char *szOptions) } // force specific flags for Q2 - if (options.target_version->game->id == GAME_QUAKE_II) { + if (options.target_game->id == GAME_QUAKE_II) { options.fNoclip = true; } + + // update target game + options.target_game = options.target_version->game; } diff --git a/qbsp/solidbsp.cc b/qbsp/solidbsp.cc index 8bc16701..67d7713b 100644 --- a/qbsp/solidbsp.cc +++ b/qbsp/solidbsp.cc @@ -74,10 +74,10 @@ DetailToSolid(node_t *node) // If both children are solid, we can merge the two leafs into one. // DarkPlaces has an assertion that fails if both children are // solid. - if (node->children[0]->contents.is_structural_solid(options.target_version->game) - && node->children[1]->contents.is_structural_solid(options.target_version->game)) { + if (node->children[0]->contents.is_structural_solid(options.target_game) + && node->children[1]->contents.is_structural_solid(options.target_game)) { // This discards any faces on-node. Should be safe (?) - ConvertNodeToLeaf(node, options.target_version->game->create_solid_contents()); + ConvertNodeToLeaf(node, options.target_game->create_solid_contents()); } } } @@ -538,8 +538,9 @@ CalcSurfaceInfo(surface_t *surf) surf->has_struct = false; for (const face_t *f = surf->faces; f; f = f->next) { - if (!f->contents[0].is_valid(options.target_version->game, false) || !f->contents[1].is_valid(options.target_version->game, false)) - Error("Bad contents in face (%s)", __func__); + for (auto &contents : f->contents) + if (!contents.is_valid(options.target_game, false)) + Error("Bad contents in face: %s (%s)", contents.to_string(options.target_game).c_str(), __func__); surf->lmshift = (f->lmshift[0]lmshift[1])?f->lmshift[0]:f->lmshift[1]; @@ -751,19 +752,19 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode) for (face_t *f = surf->faces; f; f = f->next) { count++; - const int currentpri = contents.has_value() ? contents->priority(options.target_version->game) : -1; - const int fpri = f->contents[0].priority(options.target_version->game); + const int currentpri = contents.has_value() ? contents->priority(options.target_game) : -1; + const int fpri = f->contents[0].priority(options.target_game); if (fpri > currentpri) { contents = f->contents[0]; } // HACK: Handle structural covered by detail. if (f->contents[0].extended & CFLAGS_STRUCTURAL_COVERED_BY_DETAIL) { - Q_assert(f->contents[0].is_empty(options.target_version->game)); + Q_assert(f->contents[0].is_empty(options.target_game)); - const contentflags_t solid_detail = options.target_version->game->create_solid_contents(CFLAGS_DETAIL); + const contentflags_t solid_detail = options.target_game->create_solid_contents(CFLAGS_DETAIL); - if (solid_detail.priority(options.target_version->game) > currentpri) { + if (solid_detail.priority(options.target_game) > currentpri) { contents = solid_detail; } } @@ -773,7 +774,7 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode) // NOTE: This is crazy.. // Liquid leafs get assigned liquid content types because of the // "cosmetic" mirrored faces. - leafnode->contents = contents.value_or(options.target_version->game->create_solid_contents()); // FIXME: Need to create CONTENTS_DETAIL sometimes? + leafnode->contents = contents.value_or(options.target_game->create_solid_contents()); // FIXME: Need to create CONTENTS_DETAIL sometimes? if (leafnode->contents.extended & CFLAGS_ILLUSIONARY_VISBLOCKER) { c_illusionary_visblocker++; @@ -783,14 +784,14 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode) c_detail_illusionary++; } else if (leafnode->contents.extended & CFLAGS_DETAIL) { c_detail++; - } else if (leafnode->contents.is_empty(options.target_version->game)) { + } else if (leafnode->contents.is_empty(options.target_game)) { c_empty++; - } else if (leafnode->contents.is_solid(options.target_version->game)) { + } else if (leafnode->contents.is_solid(options.target_game)) { c_solid++; - } else if (leafnode->contents.is_liquid(options.target_version->game) || leafnode->contents.is_sky(options.target_version->game)) { + } else if (leafnode->contents.is_liquid(options.target_game) || leafnode->contents.is_sky(options.target_game)) { c_water++; } else { - Error("Bad contents in face (%s)", __func__); + //Error("Bad contents in face: %s (%s)", leafnode->contents.to_string(options.target_game).c_str(), __func__); } // write the list of the original faces to the leaf's markfaces @@ -949,11 +950,11 @@ SolidBSP(const mapentity_t *entity, surface_t *surfhead, bool midsplit) } headnode->children[0] = (node_t *)AllocMem(OTHER, sizeof(node_t), true); headnode->children[0]->planenum = PLANENUM_LEAF; - headnode->children[0]->contents = options.target_version->game->create_empty_contents(); + headnode->children[0]->contents = options.target_game->create_empty_contents(); headnode->children[0]->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *), true); headnode->children[1] = (node_t *)AllocMem(OTHER, sizeof(node_t), true); headnode->children[1]->planenum = PLANENUM_LEAF; - headnode->children[1]->contents = options.target_version->game->create_empty_contents(); + headnode->children[1]->contents = options.target_game->create_empty_contents(); headnode->children[1]->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *), true); return headnode; diff --git a/qbsp/surfaces.cc b/qbsp/surfaces.cc index db11c0ba..a7dd6470 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -36,7 +36,7 @@ SubdivideFace(face_t *f, face_t **prevptr) { vec_t mins, maxs; vec_t v; - int axis, i; + int axis; qbsp_plane_t plane; face_t *front, *back, *next; const mtexinfo_t *tex; @@ -49,7 +49,7 @@ SubdivideFace(face_t *f, face_t **prevptr) tex = &map.mtexinfos.at(f->texinfo); if (tex->flags.extended & (TEX_EXFLAG_SKIP | TEX_EXFLAG_HINT) || - !options.target_version->game->surf_is_subdivided(tex->flags)) + !options.target_game->surf_is_subdivided(tex->flags)) return; //subdivision is pretty much pointless other than because of lightmap block limits //one lightmap block will always be added at the end, for smooth interpolation @@ -292,7 +292,7 @@ GetEdge(mapentity_t *entity, const vec3_t p1, const vec3_t p2, int v1, v2; int i; - if (!face->contents[0].is_valid(options.target_version->game, false)) + if (!face->contents[0].is_valid(options.target_game, false)) Error("Face with invalid contents (%s)", __func__); v1 = GetVertex(entity, p1); diff --git a/qbsp/tjunc.cc b/qbsp/tjunc.cc index 19b66fe3..226bfc65 100644 --- a/qbsp/tjunc.cc +++ b/qbsp/tjunc.cc @@ -357,7 +357,7 @@ FixFaceEdges static void FixFaceEdges(face_t *face, face_t *superface, face_t **facelist) { - int i, j, k; + int i, j; wedge_t *edge; wvert_t *v; vec_t t1, t2; diff --git a/qbsp/wad.cc b/qbsp/wad.cc index 5644382f..be5e9a73 100644 --- a/qbsp/wad.cc +++ b/qbsp/wad.cc @@ -77,7 +77,7 @@ WAD_LoadInfo(wad_t &wad, bool external) int w = LittleLong(miptex.width); int h = LittleLong(miptex.height); lump.size = sizeof(miptex) + (w>>0)*(h>>0) + (w>>1)*(h>>1) + (w>>2)*(h>>2) + (w>>3)*(h>>3); - if (options.target_version == &bspver_hl) + if (options.target_game->id == GAME_HALF_LIFE) lump.size += 2+3*256; //palette size+palette data lump.size = (lump.size+3) & ~3; //keep things aligned if we can. @@ -219,7 +219,7 @@ WAD_LoadLump(const wad_t &wad, const char *name, uint8_t *dest) memcpy(dest+out->offsets[2], data.data()+(in->offsets[2]), (in->width>>2)*(in->height>>2)); memcpy(dest+out->offsets[3], data.data()+(in->offsets[3]), (in->width>>3)*(in->height>>3)); - if (options.target_version == &bspver_hl) + if (options.target_game->id == GAME_HALF_LIFE) { //palette size. 256 in little endian. dest[palofs+0] = ((256>>0)&0xff); dest[palofs+1] = ((256>>8)&0xff); @@ -311,7 +311,7 @@ WADList_Process() } // Q2 doesn't use texdata - if (options.target_version->game->id == GAME_QUAKE_II) { + if (options.target_game->id == GAME_QUAKE_II) { return; } diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index cae36c9d..7b037d14 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -28,23 +28,10 @@ #include static void -AssertVanillaContentType(int content) +AssertVanillaContentType(const contentflags_t &flags) { - // TODO - if (options.target_version->game->id == GAME_QUAKE_II) { - 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 })); + if (!flags.is_valid(options.target_game, false)) { + Error("Internal error: Tried to save invalid contents type %s\n", GetContentsName(flags)); } } @@ -58,7 +45,7 @@ RemapContentsForExport(const contentflags_t &content) * * Normally solid leafs are not written and just referenced as leaf 0. */ - return options.target_version->game->create_solid_contents(); + return options.target_game->create_solid_contents(); } return content; @@ -195,8 +182,9 @@ ExportLeaf(mapentity_t *entity, node_t *node) map.exported_leafs.push_back({}); mleaf_t *dleaf = &map.exported_leafs.back(); - dleaf->contents = RemapContentsForExport(node->contents).native; - AssertVanillaContentType(dleaf->contents); + const contentflags_t remapped = RemapContentsForExport(node->contents); + AssertVanillaContentType(remapped); + dleaf->contents = remapped.native; /* * write bounding box info @@ -263,7 +251,7 @@ ExportDrawNodes(mapentity_t *entity, node_t *node) 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->game->id != GAME_QUAKE_II && node->children[i]->contents.is_solid(options.target_version->game)) + if (options.target_game->id != GAME_QUAKE_II && node->children[i]->contents.is_solid(options.target_game)) dnode->children[i] = -1; else { int nextLeafIndex = static_cast(map.exported_leafs.size()); @@ -340,7 +328,7 @@ BeginBSPFile(void) // Leave room for leaf 0 (must be solid) map.exported_leafs.push_back({}); - map.exported_leafs.back().contents = options.target_version->game->create_solid_contents().native; + map.exported_leafs.back().contents = options.target_game->create_solid_contents().native; Q_assert(map.exported_leafs.size() == 1); } @@ -386,10 +374,10 @@ WriteExtendedTexinfoFlags(void) int count = 0; for (const auto &tx : texinfos_sorted) { - if (tx.outputnum.has_value()) + if (!tx.outputnum.has_value()) continue; - Q_assert(count == tx.outputnum); // check we are outputting them in the proper sequence + Q_assert(count == tx.outputnum.value()); // check we are outputting them in the proper sequence fwrite(&tx.flags, 1, sizeof(tx.flags), texinfofile); count++; diff --git a/vis/CMakeLists.txt b/vis/CMakeLists.txt index 4ec49686..61dba056 100644 --- a/vis/CMakeLists.txt +++ b/vis/CMakeLists.txt @@ -23,7 +23,7 @@ set(VIS_SOURCES ${VIS_INCLUDES}) add_executable(vis ${VIS_SOURCES}) -target_link_libraries (vis ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries (vis ${CMAKE_THREAD_LIBS_INIT} fmt::fmt) find_library(M_LIB m) if (M_LIB) target_link_libraries (vis ${M_LIB})