From 537bc3686d27f891d3914c223e0dc7af5de8d43f Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 25 Apr 2023 18:41:21 -0600 Subject: [PATCH 1/2] qbsp: experimental -scale flag for applying uniform scale to map --- include/qbsp/qbsp.hh | 1 + qbsp/map.cc | 31 +++++++++++++++++++++++++++++++ qbsp/qbsp.cc | 2 ++ 3 files changed, 34 insertions(+) diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index 6184b9f1..f5512e18 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -227,6 +227,7 @@ public: setting_validator maxedges; setting_numeric midsplitbrushfraction; setting_string add; + setting_scalar scale; setting_bool loghulls; setting_bool logbmodels; diff --git a/qbsp/map.cc b/qbsp/map.cc index 43d2cf45..16d57582 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -2582,6 +2582,12 @@ static void ScaleMapFace(mapface_t &face, const qvec3d &scale) } face.set_texvecs(newtexvecs); + + // update winding + + for (qvec3d &p : face.winding) { + p = scaleM * p; + } } static void RotateMapFace(mapface_t &face, const qvec3d &angles) @@ -3070,6 +3076,27 @@ void ProcessMapBrushes() } } + // apply global scale + if (qbsp_options.scale.value() != 1.0) { + // scale brushes + for (auto &brush : entity.mapbrushes) { + for (auto &f : brush.faces) { + ScaleMapFace(f, qvec3d(qbsp_options.scale.value())); + } + CalculateBrushBounds(brush); + } + + // scale point entity origin + if (entity.epairs.find("origin") != entity.epairs.end()) { + qvec3d origin; + if (3 == entity.epairs.get_vector("origin", origin)) { + origin *= qbsp_options.scale.value(); + + entity.epairs.set("origin", qv::to_string(origin)); + } + } + } + // remove windings, we no longer need them for (auto &brush : entity.mapbrushes) { for (auto &f : brush.faces) { @@ -3474,6 +3501,10 @@ inline vec_t GetBrushExtents(const mapbrush_t &hullbrush) } } + if (qbsp_options.scale.value() != 1) { + extents *= qbsp_options.scale.value(); + } + return extents; } diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index a5b0b651..520eab14 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -569,6 +569,8 @@ qbsp_settings::qbsp_settings() midsplitbrushfraction{this, "midsplitbrushfraction", 0.0, &common_format_group, "switch to cheaper partitioning if a node contains this % of brushes in the map"}, add{this, "add", "", "", &common_format_group, "the given map file will be appended to the base map"}, + scale{this, "scale", 1.0, &map_development_group, + "scales the map brushes and point entity origins by a give factor"}, loghulls{this, {"loghulls"}, false, &logging_group, "print log output for collision hulls"}, logbmodels{this, {"logbmodels"}, false, &logging_group, "print log output for bmodels"} { From bc7f08abe53e259a5f82690a4d4983894c00364f Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Tue, 25 Apr 2023 23:48:09 -0600 Subject: [PATCH 2/2] test: add broken case where func_detail_wall generates splits --- .../q1_detail_wall_intersecting_detail.map | 96 +++++++++++++++++++ tests/test_qbsp.cc | 16 ++++ 2 files changed, 112 insertions(+) create mode 100644 testmaps/q1_detail_wall_intersecting_detail.map diff --git a/testmaps/q1_detail_wall_intersecting_detail.map b/testmaps/q1_detail_wall_intersecting_detail.map new file mode 100644 index 00000000..c6c89499 --- /dev/null +++ b/testmaps/q1_detail_wall_intersecting_detail.map @@ -0,0 +1,96 @@ +// Game: Quake +// Format: Valve +// entity 0 +{ +"mapversion" "220" +"classname" "worldspawn" +"wad" "deprecated/free_wad.wad;deprecated/fence.wad;deprecated/origin.wad;deprecated/hintskip.wad" +"_wateralpha" "0.5" +"_tb_def" "builtin:Quoth2.fgd" +// brush 0 +{ +( -160 -256 96 ) ( -160 -255 96 ) ( -160 -256 97 ) orangestuff8 [ 0 -1 0 0 ] [ 0 0 -1 16 ] 0 1 1 +( -160 -432 96 ) ( -160 -432 97 ) ( -159 -432 96 ) orangestuff8 [ 1 0 0 0 ] [ 0 0 -1 16 ] 0 1 1 +( -160 -256 96 ) ( -159 -256 96 ) ( -160 -255 96 ) orangestuff8 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -80 64 112 ) ( -80 65 112 ) ( -79 64 112 ) orangestuff8 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -80 176 112 ) ( -79 176 112 ) ( -80 176 113 ) orangestuff8 [ -1 0 0 0 ] [ 0 0 -1 16 ] 0 1 1 +( 288 64 112 ) ( 288 64 113 ) ( 288 65 112 ) orangestuff8 [ 0 1 0 0 ] [ 0 0 -1 16 ] 0 1 1 +} +// brush 1 +{ +( -176 -256 80 ) ( -176 -255 80 ) ( -176 -256 81 ) tsl_wall1 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 0 1 1 +( -176 -432 80 ) ( -176 -432 81 ) ( -175 -432 80 ) tsl_wall1 [ 1 0 0 0 ] [ 0 0 -1 -16 ] 0 1 1 +( -176 -256 112 ) ( -175 -256 112 ) ( -176 -255 112 ) tsl_wall1 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -160 192 368 ) ( -160 193 368 ) ( -159 192 368 ) tsl_wall1 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -160 176 96 ) ( -159 176 96 ) ( -160 176 97 ) tsl_wall1 [ 1 0 0 0 ] [ 0 0 -1 -16 ] 0 1 1 +( -160 192 96 ) ( -160 192 97 ) ( -160 193 96 ) tsl_wall1 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 0 1 1 +} +// brush 2 +{ +( -160 176 104 ) ( -160 177 104 ) ( -160 176 105 ) tsl_wall1 [ 0 1.0000000000000002 0 -32 ] [ 0 0 -1.0000000000000002 -16 ] 0 1 1 +( -160 176 104 ) ( -160 176 105 ) ( -159 176 104 ) tsl_wall1 [ -1 0 0 -16 ] [ 0 0 -1 -16 ] 0 1 1 +( -160 176 112 ) ( -159 176 112 ) ( -160 177 112 ) tsl_wall1 [ -1.0000000000000002 0 0 -16 ] [ 0 1.0000000000000002 0 -40 ] 0 1 1 +( 288 192 368 ) ( 288 193 368 ) ( 289 192 368 ) tsl_wall1 [ -1.0000000000000002 0 0 -16 ] [ 0 -1.0000000000000002 0 48 ] 0 1 1 +( 288 192 112 ) ( 289 192 112 ) ( 288 192 113 ) tsl_wall1 [ -1 0 0 -16 ] [ 0 0 -1 -16 ] 0 1 1 +( 288 192 112 ) ( 288 192 113 ) ( 288 193 112 ) tsl_wall1 [ 0 -1.0000000000000002 0 0 ] [ 0 0 -1.0000000000000002 -16 ] 0 1 1 +} +// brush 3 +{ +( -160 -448 104 ) ( -160 -447 104 ) ( -160 -448 105 ) tsl_wall1 [ 0 1.0000000000000002 0 80 ] [ 0 0 -1.0000000000000002 -16 ] 0 1 1 +( -160 -448 104 ) ( -160 -448 105 ) ( -159 -448 104 ) tsl_wall1 [ -1 0 0 -16 ] [ 0 0 -1 -16 ] 0 1 1 +( -160 -448 112 ) ( -159 -448 112 ) ( -160 -447 112 ) tsl_wall1 [ -1.0000000000000002 0 0 -16 ] [ 0 1.0000000000000002 0 72 ] 0 1 1 +( 288 -432 368 ) ( 288 -431 368 ) ( 289 -432 368 ) tsl_wall1 [ -1.0000000000000002 0 0 -16 ] [ 0 -1.0000000000000002 0 -64 ] 0 1 1 +( 288 -432 112 ) ( 289 -432 112 ) ( 288 -432 113 ) tsl_wall1 [ -1 0 0 -16 ] [ 0 0 -1 -16 ] 0 1 1 +( 288 -432 112 ) ( 288 -432 113 ) ( 288 -431 112 ) tsl_wall1 [ 0 -1.0000000000000002 0 -112 ] [ 0 0 -1.0000000000000002 -16 ] 0 1 1 +} +// brush 4 +{ +( 288 192 96 ) ( 288 193 96 ) ( 288 192 97 ) tsl_wall1 [ 0 1 0 0 ] [ 0 0 -1 -16 ] 0 1 1 +( 304 -432 80 ) ( 303 -432 80 ) ( 304 -432 81 ) tsl_wall1 [ -1 0 0 16 ] [ 0 0 -1 -16 ] 180 1 1 +( 304 -256 112 ) ( 304 -255 112 ) ( 303 -256 112 ) tsl_wall1 [ 1 0 0 -16 ] [ 0 -1 0 0 ] 180 1 1 +( 304 -256 368 ) ( 303 -256 368 ) ( 304 -255 368 ) tsl_wall1 [ 1 0 0 -16 ] [ 0 -1 0 0 ] 180 1 1 +( 304 176 80 ) ( 304 176 81 ) ( 303 176 80 ) tsl_wall1 [ -1 0 0 16 ] [ 0 0 -1 -16 ] 180 1 1 +( 304 -256 80 ) ( 304 -256 81 ) ( 304 -255 80 ) tsl_wall1 [ 0 -1 0 0 ] [ 0 0 -1 -16 ] 0 1 1 +} +// brush 5 +{ +( -160 -256 240 ) ( -160 -255 240 ) ( -160 -256 241 ) orangestuff8 [ 0 -1 0 0 ] [ 0 0 -1 -32 ] 0 1 1 +( -160 -432 240 ) ( -160 -432 241 ) ( -159 -432 240 ) orangestuff8 [ 1 0 0 0 ] [ 0 0 -1 -32 ] 0 1 1 +( -160 -256 240 ) ( -159 -256 240 ) ( -160 -255 240 ) orangestuff8 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -80 64 256 ) ( -80 65 256 ) ( -79 64 256 ) orangestuff8 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -80 176 256 ) ( -79 176 256 ) ( -80 176 257 ) orangestuff8 [ -1 0 0 0 ] [ 0 0 -1 -32 ] 0 1 1 +( 288 64 256 ) ( 288 64 257 ) ( 288 65 256 ) orangestuff8 [ 0 1 0 0 ] [ 0 0 -1 -32 ] 0 1 1 +} +} +// entity 1 +{ +"classname" "info_player_start" +"origin" "-48 -160 136" +"angle" "180" +} +// entity 2 +{ +"classname" "func_detail_wall" +// brush 0 +{ +( -160 -168 184 ) ( -160 -184 184 ) ( -160 -184 112 ) tsl_tower1 [ 0 -1 0 56 ] [ 0 0 -1 -40 ] 0 1 1 +( -144 -184 184 ) ( -136 -184 112 ) ( -160 -184 112 ) tsl_tower1 [ 1 0 0 96 ] [ 0 0 -1 -40 ] 0 1 1 +( -136 -184 112 ) ( -136 -168 112 ) ( -160 -168 112 ) tsl_tower1 [ -1 0 0 -96 ] [ 0 -1 0 56 ] 0 1 1 +( -160 -168 184 ) ( -144 -168 184 ) ( -144 -184 184 ) tsl_tower1 [ 1 0 0 96 ] [ 0 -1 0 56 ] 0 1 1 +( -160 -160 112 ) ( -136 -160 112 ) ( -144 -160 184 ) tsl_tower1 [ -1 0 0 -96 ] [ 0 0 -1 -40 ] 0 1 1 +( -144 -168 184 ) ( -136 -168 112 ) ( -136 -184 112 ) tsl_tower1 [ 0 1 0 -56 ] [ 0 0 -1 -40 ] 0 1 1 +} +} +// entity 3 +{ +"classname" "func_detail" +// brush 0 +{ +( -160 192 96 ) ( -160 193 96 ) ( -160 192 97 ) orangestuff8 [ 0 0 -1.0000000000000002 -48 ] [ 0 -1.0000000000000002 0 0 ] 0 1 1 +( -176 -432 80 ) ( -176 -432 81 ) ( -175 -432 80 ) orangestuff8 [ 1.0000000000000002 0 0 0 ] [ 0 0 1.0000000000000002 0 ] 0 1 1 +( -176 -256 112 ) ( -175 -256 112 ) ( -176 -255 112 ) orangestuff8 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -160 192 240 ) ( -160 193 240 ) ( -159 192 240 ) orangestuff8 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1 +( -160 176 96 ) ( -159 176 96 ) ( -160 176 97 ) orangestuff8 [ 1.0000000000000002 0 0 0 ] [ 0 0 -1.0000000000000002 0 ] 0 1 1 +( -152 192 96 ) ( -152 192 97 ) ( -152 193 96 ) orangestuff8 [ 0 0 1.0000000000000002 -8 ] [ 0 -1.0000000000000002 0 0 ] 0 1 1 +} +} diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index 08118c0e..706a3232 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -1159,6 +1159,22 @@ TEST_CASE("q1_detail_wall tjuncs" * doctest::test_suite("testmaps_q1")) CHECK(w.size() == 5); } +TEST_CASE("q1_detail_wall_intersecting_detail" * doctest::test_suite("testmaps_q1") * doctest::may_fail()) +{ + const auto [bsp, bspx, prt] = LoadTestmapQ1("q1_detail_wall_intersecting_detail.map"); + + const auto *left_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {-152, -192, 160}, {1, 0, 0}); + const auto *under_detail_wall_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {-152, -176, 160}, {1, 0, 0}); + const auto *right_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {-152, -152, 160}, {1, 0, 0}); + + CHECK(left_face != nullptr); + CHECK(under_detail_wall_face != nullptr); + CHECK(right_face != nullptr); + + CHECK(left_face == under_detail_wall_face); + CHECK(left_face == right_face); +} + bool PortalMatcher(const prtfile_winding_t &a, const prtfile_winding_t &b) { return a.undirectional_equal(b);