From fe31e1c1cedbe9ca08c105d5695287ea57e12e0e Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Sun, 5 Jun 2022 23:24:02 -0600 Subject: [PATCH 1/7] bsputil: add --decompile-ignore-brushes option for visualizing leafs in q2 maps --- bsputil/bsputil.cc | 4 +++- bsputil/decompile.cpp | 2 +- bsputil/decompile.h | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bsputil/bsputil.cc b/bsputil/bsputil.cc index 2b7f2998..ac4ab030 100644 --- a/bsputil/bsputil.cc +++ b/bsputil/bsputil.cc @@ -617,8 +617,9 @@ int main(int argc, char **argv) WriteBSPFile(source, &bspdata); return 0; - } else if (!strcmp(argv[i], "--decompile") || !strcmp(argv[i], "--decompile-geomonly")) { + } else if (!strcmp(argv[i], "--decompile") || !strcmp(argv[i], "--decompile-geomonly") || !strcmp(argv[i], "--decompile-ignore-brushes")) { const bool geomOnly = !strcmp(argv[i], "--decompile-geomonly"); + const bool ignoreBrushes = !strcmp(argv[i], "--decompile-ignore-brushes"); source.replace_extension(""); source.replace_filename(source.stem().string() + "-decompile"); @@ -632,6 +633,7 @@ int main(int argc, char **argv) decomp_options options; options.geometryOnly = geomOnly; + options.ignoreBrushes = ignoreBrushes; DecompileBSP(&bsp, options, f); diff --git a/bsputil/decompile.cpp b/bsputil/decompile.cpp index 0088913e..049b4128 100644 --- a/bsputil/decompile.cpp +++ b/bsputil/decompile.cpp @@ -1053,7 +1053,7 @@ static void DecompileEntity( // If we have brush info, we'll use that directly // TODO: support BSPX brushes too - if (bsp->loadversion->game->id == GAME_QUAKE_II) { + if (bsp->loadversion->game->id == GAME_QUAKE_II && !options.ignoreBrushes) { std::unordered_map brushes; auto handle_leaf = [&brushes, bsp, model](const mleaf_t *leaf) { diff --git a/bsputil/decompile.h b/bsputil/decompile.h index a2bec388..777df663 100644 --- a/bsputil/decompile.h +++ b/bsputil/decompile.h @@ -14,6 +14,11 @@ struct decomp_options * For debugging (there's not much that can go wrong). */ bool geometryOnly = false; + /** + * If true, don't use brushes in Q2 .bsp's and instead decompile the leafs. + * Intended for visualizing leafs. + */ + bool ignoreBrushes = false; }; void DecompileBSP(const mbsp_t *bsp, const decomp_options &options, std::ofstream &file); From c2b39a650243d521a6093702e18a731070af370e Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Sun, 5 Jun 2022 23:42:44 -0600 Subject: [PATCH 2/7] decompile: fix texture name crash with missing texinfo --- bsputil/decompile.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bsputil/decompile.cpp b/bsputil/decompile.cpp index 049b4128..2e31a67a 100644 --- a/bsputil/decompile.cpp +++ b/bsputil/decompile.cpp @@ -860,7 +860,7 @@ static compiled_brush_t DecompileLeafTask( side.valve = finalSide.plane.normal; DefaultSkipSide(side, bsp); } else { - const char *name; + const char *name = nullptr; const gtexinfo_t *ti; auto faces = finalSide.faces; @@ -871,10 +871,12 @@ static compiled_brush_t DecompileLeafTask( ti = Face_Texinfo(bsp, face); } else if (finalSide.plane.source) { ti = BSP_GetTexinfo(bsp, finalSide.plane.source->texinfo); - name = ti->texture.data(); + if (ti) { + name = ti->texture.data(); + } } - if (!name && !*name) { + if (!name || !name[0]) { DefaultSkipSide(side, bsp); } else { OverrideTextureForContents(side, bsp, name, brush.contents); From a12427b47dd05410d375daf67a57465959696b42 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Sun, 5 Jun 2022 23:55:02 -0600 Subject: [PATCH 3/7] decompile: fix --decompile-ignore-brushes --- bsputil/decompile.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/bsputil/decompile.cpp b/bsputil/decompile.cpp index 2e31a67a..178f3941 100644 --- a/bsputil/decompile.cpp +++ b/bsputil/decompile.cpp @@ -800,7 +800,7 @@ static compiled_brush_t DecompileLeafTaskGeometryOnly( } static compiled_brush_t DecompileLeafTask( - const mbsp_t *bsp, leaf_decompile_task &task, std::optional &brush_offset) + const mbsp_t *bsp, const decomp_options &options, leaf_decompile_task &task, std::optional &brush_offset) { compiled_brush_t brush; brush.source = task.brush; @@ -808,11 +808,12 @@ static compiled_brush_t DecompileLeafTask( brush.contents = {task.brush ? task.brush->contents : task.leaf->contents}; std::vector finalBrushes; - if (bsp->loadversion->game->id == GAME_QUAKE_II) { + if (bsp->loadversion->game->id == GAME_QUAKE_II && !options.ignoreBrushes) { // Q2 doesn't need this - we assume each brush in the brush lump corresponds to exactly one .map file brush // and so each side of the brush can only have 1 texture at this point. finalBrushes = {BuildInitialBrush_Q2(bsp, task, task.allPlanes)}; } else { + // Q1 (or Q2, with options.ignoreBrushes) RemoveRedundantPlanes(task.allPlanes); if (task.allPlanes.empty()) { @@ -832,7 +833,13 @@ static compiled_brush_t DecompileLeafTask( // Next, for each plane in reducedPlanes, if there are 2+ faces on the plane with non-equal // texinfo, we need to clip the brush perpendicular to the face until there are no longer // 2+ faces on a plane with non-equal texinfo. - finalBrushes = SplitDifferentTexturedPartsOfBrush(bsp, initialBrush); + if (!options.ignoreBrushes) { + finalBrushes = SplitDifferentTexturedPartsOfBrush(bsp, initialBrush); + } else { + // we don't really care about accurate textures with options.ignoreBrushes, just + // want to reconstuct the leafs + finalBrushes = {initialBrush}; + } } for (const decomp_brush_t &finalBrush : finalBrushes) { @@ -974,7 +981,7 @@ static compiled_brush_t DecompileBrushTask( if (options.geometryOnly) { return DecompileLeafTaskGeometryOnly(bsp, task, brush_offset); } else { - return DecompileLeafTask(bsp, task, brush_offset); + return DecompileLeafTask(bsp, options, task, brush_offset); } } @@ -1122,7 +1129,7 @@ static void DecompileEntity( if (options.geometryOnly) { compiledBrushes[i] = DecompileLeafTaskGeometryOnly(bsp, tasks[i], brush_offset); } else { - compiledBrushes[i] = DecompileLeafTask(bsp, tasks[i], brush_offset); + compiledBrushes[i] = DecompileLeafTask(bsp, options, tasks[i], brush_offset); } }); } From d348a4cb6b09772e761ee68758fa31b33d993ea5 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 7 Jun 2022 20:30:10 -0400 Subject: [PATCH 4/7] fix nodes getting negative planes from certain splits # Conflicts: # include/qbsp/brush.hh # qbsp/solidbsp.cc --- include/qbsp/brush.hh | 1 + qbsp/brush.cc | 19 +++++++++++++++++++ qbsp/solidbsp.cc | 5 +++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index 16881c64..b0d675d3 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -52,3 +52,4 @@ std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *mapbr void FreeBrushes(mapentity_t *ent); int FindPlane(const qplane3d &plane, int *side); +int FindPositivePlane(int planenum); diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 7c4b8996..a085484f 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -209,6 +209,10 @@ static int NewPlane(const qplane3d &plane, int *side) } PlaneHash_Add(added_plane, index); + + // add the back side, too + FindPlane(-plane, nullptr); + return index; } @@ -234,6 +238,21 @@ int FindPlane(const qplane3d &plane, int *side) return NewPlane(plane, side); } +/* + * FindPositivePlane + * - Only used for nodes; always finds a positive matching plane. + */ +int FindPositivePlane(int planenum) +{ + const auto &plane = map.planes[planenum]; + + // already positive, or it's PLANE_ANY_x which doesn't matter + if (plane.type >= PLANE_ANYX || (plane.normal[0] + plane.normal[1] + plane.normal[2]) > 0) + return planenum; + + return FindPlane(-plane, nullptr); +} + /* ============================================================================= diff --git a/qbsp/solidbsp.cc b/qbsp/solidbsp.cc index 84197030..f18c28d3 100644 --- a/qbsp/solidbsp.cc +++ b/qbsp/solidbsp.cc @@ -741,10 +741,11 @@ static void PartitionSurfaces(std::vector &surfaces, node_t *node) node->facelist = LinkNodeFaces(*split); node->children[0] = new node_t{}; node->children[1] = new node_t{}; - node->planenum = split->planenum; + node->planenum = FindPositivePlane(split->planenum); + node->detail_separator = split->detail_separator; - const qbsp_plane_t &splitplane = map.planes[split->planenum]; + const qbsp_plane_t &splitplane = map.planes.at(node->planenum); DivideNodeBounds(node, splitplane); From 633aaf3954c27b11b72f9cdad3f842044058237a Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 7 Jun 2022 19:27:16 -0600 Subject: [PATCH 5/7] testqbsp: mark lavaclip test as [!mayfail] --- qbsp/test_qbsp.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index 45ef6ca3..38b4fcee 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -1122,7 +1122,7 @@ TEST_CASE("base1leak", "[testmaps_q2]") /** * e1u1/brlava brush intersecting e1u1/clip **/ -TEST_CASE("lavaclip", "[testmaps_q2]") { +TEST_CASE("lavaclip", "[testmaps_q2][!mayfail]") { // this is probably only fixable on the brushbsp branch const mbsp_t bsp = LoadTestmap("qbsp_q2_lavaclip.map", {"-q2bsp"}); CHECK(GAME_QUAKE_II == bsp.loadversion->game->id); From 0326374d68c411e47f7be2bd45f063837bf29dfc Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 7 Jun 2022 17:05:03 -0600 Subject: [PATCH 6/7] testqbsp: bmodel collision issue where world and bmodel are interfering --- qbsp/test_qbsp.cc | 13 ++++++++++ testmaps/qbsp_q2_bmodel_collision.map | 34 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 testmaps/qbsp_q2_bmodel_collision.map diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index 38b4fcee..a26ba935 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -1154,6 +1154,19 @@ TEST_CASE("lavaclip", "[testmaps_q2][!mayfail]") { // this is probably only fixa CHECK(texinfo->flags.native == (Q2_SURF_LIGHT | Q2_SURF_WARP)); } +/** + * Weird mystery issue with a func_wall with broken collision + */ +TEST_CASE("qbsp_q2_bmodel_collision", "[testmaps_q2]") { + const mbsp_t bsp = LoadTestmap("qbsp_q2_bmodel_collision.map", {"-q2bsp"}); + + CHECK(GAME_QUAKE_II == bsp.loadversion->game->id); + + const qvec3d in_bmodel {-544, -312, -258}; + REQUIRE(2 == bsp.dmodels.size()); + CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[1], in_bmodel)->contents); +} + TEST_CASE("winding", "[benchmark]") { ankerl::nanobench::Bench bench; diff --git a/testmaps/qbsp_q2_bmodel_collision.map b/testmaps/qbsp_q2_bmodel_collision.map new file mode 100644 index 00000000..c7166991 --- /dev/null +++ b/testmaps/qbsp_q2_bmodel_collision.map @@ -0,0 +1,34 @@ +// Game: Quake 2 +// Format: Quake2 +// entity 0 +{ +"classname" "worldspawn" +"_tb_textures" "textures/e1u1" +// brush 0 +{ +( -578 -310 -250 ) ( -578 -284 -250 ) ( -570 -284 -214 ) e1u1/+0comp10_1 0 0 0 1 1 +( -570 -299 -215 ) ( -518 -299 -215 ) ( -508 -302 -250 ) e1u1/+0comp10_1 0 0 0 1 1 +( -518 -299 -215 ) ( -570 -299 -215 ) ( -570 -292 -214 ) e1u1/+0comp10_1 0 0 0 1 1 +( -508 -310 -250 ) ( -508 -284 -250 ) ( -578 -284 -250 ) e1u1/+0comp10_1 0 0 0 1 1 +( -516 -292 -214 ) ( -568 -292 -214 ) ( -568 -292 -262 ) e1u1/+0comp10_1 0 0 0 1 1 +( -508 -284 -250 ) ( -508 -310 -250 ) ( -518 -310 -214 ) e1u1/+0comp10_1 0 0 0 1 1 +} +} +// entity 1 +{ +"classname" "info_player_start" +"origin" "-544 -320 -208" +} +// entity 2 +{ +"classname" "func_wall" +// brush 0 +{ +( -568 -270 -212 ) ( -568 -326 -212 ) ( -568 -326 -260 ) e1u1/+0comp10_1 0 0 0 1 1 +( -560 -326 -212 ) ( -504 -326 -212 ) ( -504 -326 -260 ) e1u1/+0comp10_1 0 0 0 1 1 +( -568 -302 -252 ) ( -504 -302 -252 ) ( -504 -326 -256 ) e1u1/+0comp10_1 0 0 0 1 1 +( -560 -326 -304 ) ( -504 -326 -304 ) ( -504 -270 -304 ) e1u1/+0comp10_1 0 0 0 1 1 +( -504 -302 -252 ) ( -568 -302 -252 ) ( -568 -302 -260 ) e1u1/+0comp10_1 0 0 0 1 1 +( -520 -326 -212 ) ( -520 -270 -212 ) ( -520 -270 -260 ) e1u1/+0comp10_1 0 0 0 1 1 +} +} From 4941a86cbe9adeea4fde7a33f450403bc34283dc Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 7 Jun 2022 19:49:39 -0600 Subject: [PATCH 7/7] qbsp: rest of fix for qbsp_q2_bmodel_collision, only needed on type-cleanup branch --- include/qbsp/brush.hh | 1 + qbsp/brush.cc | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index b0d675d3..63f0af07 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -53,3 +53,4 @@ void FreeBrushes(mapentity_t *ent); int FindPlane(const qplane3d &plane, int *side); int FindPositivePlane(int planenum); +int FindPositivePlane(const qplane3d &plane, int *side); diff --git a/qbsp/brush.cc b/qbsp/brush.cc index a085484f..9b9d84b1 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -253,6 +253,22 @@ int FindPositivePlane(int planenum) return FindPlane(-plane, nullptr); } +int FindPositivePlane(const qplane3d &plane, int *side) +{ + int planenum = FindPlane(plane, side); + int positive_plane = FindPositivePlane(planenum); + + if (planenum == positive_plane) { + return planenum; + } + + // planenum itself isn't positive, so flip the planeside and return the positive version + if (side) { + *side = !*side; + } + return positive_plane; +} + /* ============================================================================= @@ -394,7 +410,7 @@ static std::vector CreateBrushFaces(const mapentity_t *src, hullbrush_t plane.dist = qv::dot(plane.normal, point); f.texinfo = hullnum > 0 ? 0 : mapface.texinfo; - f.planenum = FindPlane(plane, &f.planeside); + f.planenum = FindPositivePlane(plane, &f.planeside); f.src_entity = const_cast(src); // FIXME: get rid of consts on src in the callers? CheckFace(&f, mapface);