diff --git a/include/qbsp/brushbsp.hh b/include/qbsp/brushbsp.hh index 71551324..6b84e392 100644 --- a/include/qbsp/brushbsp.hh +++ b/include/qbsp/brushbsp.hh @@ -41,4 +41,4 @@ bool WindingIsTiny(const winding_t &w, double size = EDGE_LENGTH_EPSILON); std::unique_ptr BrushFromBounds(const aabb3d &bounds); // compatibility version -std::unique_ptr BrushBSP(mapentity_t *entity, bool use_mid_split); +std::unique_ptr BrushBSP(mapentity_t *entity, std::optional forced_quick_tree); diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index 38d1dd82..0e5db61a 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -352,7 +352,7 @@ public: // since the max world size in Q3 is {-65536, -65536, -65536, 65536, 65536, 65536}. should we dynamically change this? // should we automatically turn this on if the world gets too big but leave it off for smaller worlds? setting_blocksize blocksize{this, "blocksize", { 0, 0, 0 }, &common_format_group, "from q3map2; split the world by x/y/z sized chunks, speeding up split decisions"}; - setting_int32 midsplitbrushes{this, "midsplitbrushes", 128, &common_format_group, "switch to cheaper partitioning if a node contains this many brushes"}; + setting_numeric midsplitbrushfraction{this, "midsplitbrushfraction", 0.0, &common_format_group, "switch to cheaper partitioning if a node contains this % of brushes in the map"}; void setParameters(int argc, const char **argv) override { diff --git a/qbsp/brushbsp.cc b/qbsp/brushbsp.cc index 7ed38470..2b6dead5 100644 --- a/qbsp/brushbsp.cc +++ b/qbsp/brushbsp.cc @@ -626,7 +626,7 @@ static void CheckPlaneAgainstParents(const qbsp_plane_t &plane, node_t *node) } } -static bool CheckPlaneAgainstVolume(const qbsp_plane_t &plane, node_t *node) +static bool CheckPlaneAgainstVolume(const qbsp_plane_t &plane, const node_t *node) { auto [front, back] = SplitBrush(node->volume->copy_unique(), plane); @@ -712,7 +712,7 @@ ChooseMidPlaneFromList The clipping hull BSP doesn't worry about avoiding splits ================== */ -static std::optional ChooseMidPlaneFromList(const std::vector> &brushes, const aabb3d &bounds) +static std::optional ChooseMidPlaneFromList(const std::vector> &brushes, const node_t *node) { vec_t bestaxialmetric = VECT_MAX; std::optional bestaxialplane; @@ -730,8 +730,13 @@ static std::optional ChooseMidPlaneFromList(const std::vectorbounds); if (metric < bestanymetric) { bestanymetric = metric; @@ -761,46 +766,77 @@ Using heuristics, chooses a plane to partition the brushes with. Returns nullopt if there are no valid planes to split with. ================ */ -static std::optional SelectSplitPlane(const std::vector> &brushes, node_t *node, bool use_mid_split, bspstats_t &stats) +static std::optional SelectSplitPlane(const std::vector> &brushes, node_t *node, std::optional forced_quick_tree, bspstats_t &stats) { // no brushes left to split, so we can't use any plane. if (!brushes.size()) { return std::nullopt; } - // if it is crossing a block boundary, force a split; - // this is optional q3map2 mode - for (size_t i = 0; i < 3; i++) { - if (qbsp_options.blocksize.value()[i] <= 0) { - continue; - } + // if forced_quick_tree is nullopt, we will choose fast/slow based on + // certain parameters. + if (!forced_quick_tree.has_value() || forced_quick_tree.value() == true) { + // if it is crossing a block boundary, force a split; + // this is optional q3map2 mode that is disabled by default. + if (qbsp_options.blocksize.isChanged()) { + for (size_t i = 0; i < 3; i++) { + if (qbsp_options.blocksize.value()[i] <= 0) { + continue; + } - vec_t dist = qbsp_options.blocksize.value()[i] * (floor(node->bounds.mins()[i] / qbsp_options.blocksize.value()[i]) + 1); + vec_t dist = qbsp_options.blocksize.value()[i] * (floor(node->bounds.mins()[i] / qbsp_options.blocksize.value()[i]) + 1); - if (node->bounds.maxs()[i] > dist) { - qplane3d plane{}; - plane.normal[i] = 1.0; - plane.dist = dist; - qbsp_plane_t bsp_plane = plane; - stats.c_blocksplit++; + if (node->bounds.maxs()[i] > dist) { + qplane3d plane{}; + plane.normal[i] = 1.0; + plane.dist = dist; + qbsp_plane_t bsp_plane = plane; - for (auto &b : brushes) { - b->side = TestBrushToPlanenum(*b, bsp_plane, nullptr, nullptr, nullptr); + if (!CheckPlaneAgainstVolume(bsp_plane, node)) { + continue; // would produce a tiny volume + } + + stats.c_blocksplit++; + + for (auto &b : brushes) { + b->side = TestBrushToPlanenum(*b, bsp_plane, nullptr, nullptr, nullptr); + } + + return bsp_plane; + } + } + } + + if (!forced_quick_tree.has_value()) { + + // decide if we should switch to the midsplit method + if (qbsp_options.midsplitbrushfraction.value() != 0.0) { + // new way (opt-in) + // how much of the map are we partitioning? + double fractionOfMap = brushes.size() / (double) map.brushes.size(); + forced_quick_tree = (fractionOfMap > qbsp_options.midsplitbrushfraction.value()); + } else { + // old way (ericw-tools 0.15.2+) + if (qbsp_options.maxnodesize.value() >= 64) { + const vec_t maxnodesize = qbsp_options.maxnodesize.value() - qbsp_options.epsilon.value(); + + forced_quick_tree = (node->bounds.maxs()[0] - node->bounds.mins()[0]) > maxnodesize + || (node->bounds.maxs()[1] - node->bounds.mins()[1]) > maxnodesize + || (node->bounds.maxs()[2] - node->bounds.mins()[2]) > maxnodesize; + } } + } - return bsp_plane; - } - } + if (forced_quick_tree.value()) { + if (auto mid_plane = ChooseMidPlaneFromList(brushes, node)) { + stats.c_midsplit++; - if (brushes.size() >= qbsp_options.midsplitbrushes.value() || use_mid_split) { - if (auto mid_plane = ChooseMidPlaneFromList(brushes, node->bounds)) { - stats.c_midsplit++; + for (auto &b : brushes) { + b->side = TestBrushToPlanenum(*b, mid_plane.value(), nullptr, nullptr, nullptr); + } - for (auto &b : brushes) { - b->side = TestBrushToPlanenum(*b, mid_plane.value(), nullptr, nullptr, nullptr); + return mid_plane; } - - return mid_plane; } } @@ -991,10 +1027,10 @@ BuildTree_r Called in parallel. ================== */ -static void BuildTree_r(node_t *node, std::vector> brushes, bool use_mid_split, bspstats_t &stats) +static void BuildTree_r(node_t *node, std::vector> brushes, std::optional forced_quick_tree, bspstats_t &stats) { // find the best plane to use as a splitter - auto bestplane = SelectSplitPlane(brushes, node, use_mid_split, stats); + auto bestplane = SelectSplitPlane(brushes, node, forced_quick_tree, stats); if (!bestplane) { // this is a leaf node @@ -1034,8 +1070,8 @@ static void BuildTree_r(node_t *node, std::vector> b // recursively process children tbb::task_group g; - g.run([&]() { BuildTree_r(node->children[0].get(), std::move(children[0]), use_mid_split, stats); }); - g.run([&]() { BuildTree_r(node->children[1].get(), std::move(children[1]), use_mid_split, stats); }); + g.run([&]() { BuildTree_r(node->children[0].get(), std::move(children[0]), forced_quick_tree, stats); }); + g.run([&]() { BuildTree_r(node->children[1].get(), std::move(children[1]), forced_quick_tree, stats); }); g.wait(); } @@ -1044,7 +1080,7 @@ static void BuildTree_r(node_t *node, std::vector> b BrushBSP ================== */ -static std::unique_ptr BrushBSP(mapentity_t *entity, std::vector> brushlist, bool use_mid_split) +static std::unique_ptr BrushBSP(mapentity_t *entity, std::vector> brushlist, std::optional forced_quick_tree) { auto tree = std::make_unique(); @@ -1120,7 +1156,7 @@ static std::unique_ptr BrushBSP(mapentity_t *entity, std::vectorcreate_content_stats(); - BuildTree_r(tree->headnode.get(), std::move(brushlist), use_mid_split, stats); + BuildTree_r(tree->headnode.get(), std::move(brushlist), forced_quick_tree, stats); logging::print(logging::flag::STAT, " {:8} visible nodes\n", stats.c_nodes - stats.c_nonvis); logging::print(logging::flag::STAT, " {:8} nonvis nodes\n", stats.c_nonvis); @@ -1133,7 +1169,7 @@ static std::unique_ptr BrushBSP(mapentity_t *entity, std::vector BrushBSP(mapentity_t *entity, bool use_mid_split) +std::unique_ptr BrushBSP(mapentity_t *entity, std::optional forced_quick_tree) { - return BrushBSP(entity, MakeBspBrushList(entity), use_mid_split); + return BrushBSP(entity, MakeBspBrushList(entity), forced_quick_tree); } diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index b8bd7607..efbed9f3 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -609,7 +609,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) if (qbsp_options.forcegoodtree.value()) { tree = BrushBSP(entity, false); } else { - tree = BrushBSP(entity, entity == map.world_entity()); + tree = BrushBSP(entity, entity == map.world_entity() ? std::nullopt : std::optional(false)); } // build all the portals in the bsp tree