From cbdb5c0c73056bae64d0a5130dd475e63f8a6a82 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 11 May 2022 09:46:39 -0400 Subject: [PATCH 01/10] slight bit of cleaning --- include/common/bspfile.hh | 7 +++++++ include/qbsp/qbsp.hh | 20 +++----------------- include/qbsp/winding.hh | 2 ++ qbsp/main.cc | 4 ++++ qbsp/map.cc | 2 +- qbsp/qbsp.cc | 28 ++++++++++++++++++++-------- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 31c7679b..9a77f46d 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -769,6 +769,13 @@ struct bsp2_dclipnode_t auto stream_data() { return std::tie(planenum, children); } }; +/* +* Clipnodes need to be stored as a 16-bit offset. Originally, this was a +* signed value and only the positive values up to 32767 were available. Since +* the negative range was unused apart from a few values reserved for flags, +* this has been extended to allow up to 65520 (0xfff0) clipnodes (with a +* suitably modified engine). +*/ struct bsp29_dclipnode_t { int32_t planenum; diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index 2fa44291..19a34aeb 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -133,8 +133,6 @@ extern setting_group debugging_group; class qbsp_settings : public common_settings { public: - inline qbsp_settings() { } - setting_bool hexen2{this, "hexen2", false, &game_target_group, "target Hexen II's BSP format"}; setting_bool hlbsp{this, "hlbsp", false, &game_target_group, "target Half Life's BSP format"}; setting_bool q2bsp{this, "q2bsp", false, &game_target_group, "target Quake II's BSP format"}; @@ -219,8 +217,8 @@ public: bool fVerbose = true; bool fAllverbose = false; bool fNoverbose = false; - const bspversion_t *target_version; - const gamedef_t *target_game; + const bspversion_t *target_version = nullptr; + const gamedef_t *target_game = nullptr; fs::path szMapName; fs::path szBSPName; }; @@ -228,18 +226,6 @@ public: extern settings::qbsp_settings options; -/* - * Clipnodes need to be stored as a 16-bit offset. Originally, this was a - * signed value and only the positive values up to 32767 were available. Since - * the negative range was unused apart from a few values reserved for flags, - * this has been extended to allow up to 65520 (0xfff0) clipnodes (with a - * suitably modified engine). - */ -#define MAX_BSP_CLIPNODES 0xfff0 - -// Various other geometry maximums -constexpr size_t MAXEDGES = 64; - // 0-2 are axial planes // 3-5 are non-axial planes snapped to the nearest #define PLANE_X 0 @@ -249,7 +235,7 @@ constexpr size_t MAXEDGES = 64; #define PLANE_ANYY 4 #define PLANE_ANYZ 5 -// planenum for a leaf (?) +// planenum for a leaf constexpr int32_t PLANENUM_LEAF = -1; /* diff --git a/include/qbsp/winding.hh b/include/qbsp/winding.hh index e89de399..a23a1962 100644 --- a/include/qbsp/winding.hh +++ b/include/qbsp/winding.hh @@ -23,6 +23,8 @@ #include "common/polylib.hh" +constexpr size_t MAXEDGES = 64; + using winding_t = polylib::winding_base_t; winding_t BaseWindingForPlane(const qplane3d &p); \ No newline at end of file diff --git a/qbsp/main.cc b/qbsp/main.cc index 72b652ea..9f4352aa 100644 --- a/qbsp/main.cc +++ b/qbsp/main.cc @@ -23,5 +23,9 @@ int main(int argc, const char **argv) { + settings::qbsp_settings s; + s = std::move(settings::qbsp_settings {}); + const char *cmd = "foo"; + s.run(1, &cmd); return qbsp_main(argc, argv); } diff --git a/qbsp/map.cc b/qbsp/map.cc index 04eafc69..1aac9d02 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -2081,7 +2081,7 @@ void ConvertMapFile(void) } fs::path filename = options.szBSPName; - filename.replace_filename(options.szBSPName.stem().string() + append); + filename.replace_filename(options.szBSPName.stem().string() + append).replace_extension(".map"); std::ofstream f(filename); diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 0502a7bd..d072f73b 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -96,7 +96,8 @@ void qbsp_settings::postinitialize(int argc, const char **argv) set_target_version(&bspver_hl); } - if (q2bsp.value() || q2rtx.value()) { + if (q2bsp.value() || + (q2rtx.value() && !q2bsp.isChanged() && !qbism.isChanged())) { set_target_version(&bspver_q2); } @@ -154,10 +155,6 @@ void qbsp_settings::postinitialize(int argc, const char **argv) if (!includeskip.isChanged()) { includeskip.setValueLocked(true); } - - if (!notriggermodels.isChanged()) { - notriggermodels.setValueLocked(true); - } } common_settings::postinitialize(argc, argv); @@ -606,6 +603,23 @@ winding_t BaseWindingForPlane(const qplane3d &p) return winding_t::from_plane(p, options.worldextent.value()); } +static bool IsTrigger(const mapentity_t *entity) +{ + auto &tex = entity->mapbrush(0).face(0).texname; + + if (tex.length() < 6) { + return false; + } + + size_t trigger_pos = tex.rfind("trigger"); + + if (trigger_pos == std::string::npos) { + return false; + } + + return trigger_pos == (tex.size() - strlen("trigger")); +} + /* =============== ProcessEntity @@ -629,9 +643,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) return; // for notriggermodels: if we have at least one trigger-like texture, do special trigger stuff - bool discarded_trigger = (entity != pWorldEnt() && - options.notriggermodels.value() && - entity->mapbrush(0).face(0).texname.find_last_of("trigger") == entity->mapbrush(0).face(0).texname.size() - strlen("trigger")); + bool discarded_trigger = entity != pWorldEnt() && options.notriggermodels.value() && IsTrigger(entity); // Export a blank model struct, and reserve the index (only do this once, for all hulls) if (!discarded_trigger) { From 15a136a742f818b4b68ba285b0e48932d26bf26c Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 12 May 2022 00:25:18 -0600 Subject: [PATCH 02/10] light/CMakeLists.txt: fix vcpkg builds improve tbb12.dll hack, which is only desired for the embree binary releases --- light/CMakeLists.txt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/light/CMakeLists.txt b/light/CMakeLists.txt index d025f139..691ee455 100644 --- a/light/CMakeLists.txt +++ b/light/CMakeLists.txt @@ -64,6 +64,15 @@ if (embree_FOUND) message(STATUS "Found embree license: ${EMBREE_LICENSE}") endif() + # HACK: Windows embree .dll's from https://github.com/embree/embree/releases ship with a tbb12.dll + # and we need to copy it from the embree/bin directory to our light.exe/testlight.exe dir in order for them to run + find_file(EMBREE_TBB_DLL tbb12.dll + "${EMBREE_ROOT_DIR}/bin" + NO_DEFAULT_PATH) + if (NOT EMBREE_TBB_DLL STREQUAL EMBREE_TBB_DLL-NOTFOUND) + message(STATUS "Found embree EMBREE_TBB_DLL: ${EMBREE_TBB_DLL}") + endif() + add_custom_command(TARGET light POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$" COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$") @@ -72,6 +81,10 @@ if (embree_FOUND) add_custom_command(TARGET light POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EMBREE_LICENSE}" "$/LICENSE-embree.txt") endif() + if (NOT EMBREE_TBB_DLL STREQUAL EMBREE_TBB_DLL-NOTFOUND) + add_custom_command(TARGET light POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EMBREE_TBB_DLL}" "$") + endif() # so the executable will search for dylib's in the same directory as the executable if(APPLE) @@ -123,11 +136,9 @@ if (embree_FOUND) COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$" COMMAND ${CMAKE_COMMAND} -E copy_if_different "$" "$") - # HACK: copy embree's tbb12.dll - # FIXME: this is only desired with the .zip release of embree, not e.g. vcpkg - if (WIN32) + if (NOT EMBREE_TBB_DLL STREQUAL EMBREE_TBB_DLL-NOTFOUND) add_custom_command(TARGET testlight POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "$/tbb12.dll" "$") + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EMBREE_TBB_DLL}" "$") endif() add_definitions(-DHAVE_EMBREE) From 0c27c92e41a214aeb492b51ad7aa8a540679907a Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 12 May 2022 00:37:22 -0600 Subject: [PATCH 03/10] vis: fix 32-bit shift warning "result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)" --- include/vis/leafbits.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/vis/leafbits.hh b/include/vis/leafbits.hh index 9d33ec25..0f1fe6a1 100644 --- a/include/vis/leafbits.hh +++ b/include/vis/leafbits.hh @@ -119,5 +119,5 @@ public: } }; - constexpr reference operator[](const size_t &index) { return {bits, index >> shift, 1u << (index & mask)}; } + constexpr reference operator[](const size_t &index) { return {bits, index >> shift, static_cast(1) << (index & mask)}; } }; From a02a4b1b5a1b8199e745b6f4b2517cd8969aa89f Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 12 May 2022 00:38:28 -0600 Subject: [PATCH 04/10] Update googletest --- 3rdparty/googletest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/googletest b/3rdparty/googletest index 703bd9ca..e2239ee6 160000 --- a/3rdparty/googletest +++ b/3rdparty/googletest @@ -1 +1 @@ -Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e +Subproject commit e2239ee6043f73722e7aa812a459f54a28552929 From 8f0034565dd3913e95baa2adc0856bd7aec38e5b Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 12 May 2022 23:43:32 -0600 Subject: [PATCH 05/10] qbsp: surfaces.cc: don't output inside faces of sky brushes Can't see them anyway --- qbsp/surfaces.cc | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/qbsp/surfaces.cc b/qbsp/surfaces.cc index a0cca555..d422fcd7 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -30,6 +30,21 @@ #include #include +static bool ShouldOmitFace(face_t *f) +{ + if (!options.includeskip.value() && map.mtexinfos.at(f->texinfo).flags.is_skip) + return true; + if (map.mtexinfos.at(f->texinfo).flags.is_hint) + return true; + + // HACK: to save a few faces, don't output the interior faces of sky brushes + if (f->contents[0].is_sky(options.target_game)) { + return true; + } + + return false; +} + /* =============== SubdivideFace @@ -336,9 +351,7 @@ FindFaceEdges */ static void FindFaceEdges(mapentity_t *entity, face_t *face) { - if (!options.includeskip.value() && map.mtexinfos.at(face->texinfo).flags.is_skip) - return; - if (map.mtexinfos.at(face->texinfo).flags.is_hint) + if (ShouldOmitFace(face)) return; FindFaceFragmentEdges(entity, face, face); @@ -411,9 +424,7 @@ EmitFace */ static void EmitFace(mapentity_t *entity, face_t *face) { - if (!options.includeskip.value() && map.mtexinfos.at(face->texinfo).flags.is_skip) - return; - if (map.mtexinfos.at(face->texinfo).flags.is_hint) + if (ShouldOmitFace(face)) return; EmitFaceFragment(entity, face, face); @@ -450,9 +461,7 @@ static void GrowNodeRegion(mapentity_t *entity, node_t *node) static void CountFace(mapentity_t *entity, face_t *f, size_t &facesCount, size_t &vertexesCount) { - if (!options.includeskip.value() && map.mtexinfos.at(f->texinfo).flags.is_skip) - return; - if (map.mtexinfos.at(f->texinfo).flags.is_hint) + if (ShouldOmitFace(f)) return; if (f->lmshift[1] != 4) From b6ce6ae5277fec8ccef8458fcd6b1df506633362 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Thu, 12 May 2022 23:51:05 -0600 Subject: [PATCH 06/10] testqbsp: simple_worldspawn_sky: fix expectation so sky leafs don't fill outwards --- qbsp/test_qbsp.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index 4b8d4874..66a8c07a 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -497,10 +497,16 @@ TEST(testmaps_q1, simple_worldspawn_sky) // check contents const qvec3d player_pos{-88, -64, 120}; + const double inside_sky_z = 232; EXPECT_EQ(CONTENTS_EMPTY, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos)->contents); - EXPECT_EQ(CONTENTS_SKY, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d(0,0,500))->contents); + // way above map is solid - sky should not fill outwards + // (otherwise, if you had sky with a floor further up above it, it's not clear where the leafs would be divided, or + // if the floor contents would turn to sky, etc.) + EXPECT_EQ(CONTENTS_SOLID, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d(0,0,500))->contents); + + EXPECT_EQ(CONTENTS_SKY, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], qvec3d(player_pos[0], player_pos[1], inside_sky_z))->contents); EXPECT_EQ(CONTENTS_SOLID, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d( 500, 0, 0))->contents); EXPECT_EQ(CONTENTS_SOLID, BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_pos + qvec3d(-500, 0, 0))->contents); From f9242f51d66f94f66868791ae24314fbe8ff41df Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 13 May 2022 00:27:10 -0600 Subject: [PATCH 07/10] bspinfo: .json: output readable node plane --- bspinfo/bspinfo.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bspinfo/bspinfo.cc b/bspinfo/bspinfo.cc index fece2c99..51bccd97 100644 --- a/bspinfo/bspinfo.cc +++ b/bspinfo/bspinfo.cc @@ -318,6 +318,10 @@ static void serialize_bsp(const bspdata_t &bspdata, const mbsp_t &bsp, const fs: node.push_back({"maxs", src_node.maxs}); node.push_back({"firstface", src_node.firstface}); node.push_back({"numfaces", src_node.numfaces}); + + // human-readable plane + auto& plane = bsp.dplanes.at(src_node.planenum); + node.push_back({"plane", json::array({plane.normal[0], plane.normal[1], plane.normal[2], plane.dist})}); } } From b3f35b28ad7073d5c0e86ebd0230b0e1a02880cb Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 13 May 2022 00:27:40 -0600 Subject: [PATCH 08/10] testqbsp: relax node count requirement in simple_worldspawn_sky --- qbsp/test_qbsp.cc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index 66a8c07a..a4e74441 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -16,6 +16,8 @@ #include #include +using namespace testing; + // FIXME: Clear global data (planes, etc) between each test static const mapface_t *Mapbrush_FirstFaceWithTextureName(const mapbrush_t *brush, const std::string &texname) @@ -492,7 +494,12 @@ TEST(testmaps_q1, simple_worldspawn_sky) EXPECT_EQ(5, textureToFace.at("orangestuff8").size()); // leaf/node counts - EXPECT_EQ(6, bsp.dnodes.size()); + // - we'd get 7 nodes if it's cut like a cube (solid outside), with 1 additional cut inside to divide sky / empty + // - we'd get 11 if it's cut as the sky plane (1), then two open cubes (5 nodes each) + // - can get in between values if it does some vertical cuts, then the sky plane, then other vertical cuts + // + // the 7 solution is better but the BSP heuristics won't help reach that one in this trivial test map + EXPECT_THAT(bsp.dnodes.size(), AllOf(Ge(7), Le(11))); EXPECT_EQ(3, bsp.dleafs.size()); // shared solid leaf + empty + sky // check contents From b2e78376cb709fe417bb0cb492ade68ae97be6d8 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 13 May 2022 01:17:45 -0600 Subject: [PATCH 09/10] qbsp: require nonzero intersection between leaf/brush in order to add a leafbrush removes some unnecessary leafbrushes in testmaps_q2.detail --- qbsp/qbsp.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 4beaeee7..3eb35f2f 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -207,7 +207,7 @@ static void ExportBrushList_r(const mapentity_t *entity, node_t *node, const uin std::vector brushes; for (auto &b : entity->brushes) { - if (node->bounds.intersectWith(b.bounds)) { + if (!node->bounds.disjoint_or_touching(b.bounds)) { brushes.push_back(b_id); } b_id++; From 2195a3f5a4a39b6012605a6f7e033f78e26c4570 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Fri, 13 May 2022 01:18:30 -0600 Subject: [PATCH 10/10] testqbsp: adjust testmaps_q2.detail to accept extra solid leafs generated on type-cleanup branch --- qbsp/test_qbsp.cc | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index a4e74441..5e4b50db 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -765,30 +765,31 @@ TEST(testmaps_q2, detail) { // stats EXPECT_EQ(1, bsp.dmodels.size()); + // Q2 reserves leaf 0 as an invalid leaf + // leafs: - // 6 solid leafs outside the room + // 6 solid leafs outside the room (* can be more depending on when the "divider" is cut) // 1 empty leaf filling the room above the divider // 2 empty leafs + 1 solid leaf for divider // 1 detail leaf for button // 4 empty leafs around + 1 on top of button - // total: 16 - // Q2 reserves leaf 0 as an invalid leaf, so dleafs size is 17 - EXPECT_EQ(17, bsp.dleafs.size()); std::map counts_by_contents; for (size_t i = 1; i < bsp.dleafs.size(); ++i) { ++counts_by_contents[bsp.dleafs[i].contents]; } + EXPECT_EQ(3, counts_by_contents.size()); // number of types - EXPECT_EQ((std::map{{Q2_CONTENTS_SOLID, 7}, {Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL, 1}, {0, 8}}), - counts_by_contents); + + EXPECT_EQ(1, counts_by_contents.at(Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL)); + EXPECT_EQ(8, counts_by_contents.at(0)); // empty leafs + EXPECT_THAT(counts_by_contents.at(Q2_CONTENTS_SOLID), AllOf(Ge(7), Le(9))); // clusters: - // 6 solid leafs outside the room - // 1 empty leaf filling the room above the divider - // 2 empty leafs + 1 solid leaf for divider + // 1 empty cluster filling the room above the divider + // 2 empty clusters created by divider // 1 cluster for the part of the room with the button - // total: 10 + std::set clusters; // first add the empty leafs for (size_t i = 1; i < bsp.dleafs.size(); ++i) {