diff --git a/include/common/aabb.hh b/include/common/aabb.hh index ee5a1245..996186e1 100644 --- a/include/common/aabb.hh +++ b/include/common/aabb.hh @@ -193,6 +193,11 @@ public: constexpr value_type centroid() const { return (m_mins + m_maxs) * 0.5; } + constexpr V volume() const { + auto s = size(); + return s[0] * s[1] * s[2]; + } + // stream support auto stream_data() { return std::tie(m_mins, m_maxs); } }; diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index eac457ef..d650352b 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -352,6 +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, &debugging_group, "switch to cheaper partitioning if a node contains this many brushes"}; void setParameters(int argc, const char **argv) override { diff --git a/qbsp/brushbsp.cc b/qbsp/brushbsp.cc index dc9fe196..23beb9bc 100644 --- a/qbsp/brushbsp.cc +++ b/qbsp/brushbsp.cc @@ -631,26 +631,6 @@ static bool CheckPlaneAgainstVolume(const qbsp_plane_t &plane, node_t *node) return good; } - -/* - * Calculate the split plane metric for axial planes - */ -inline vec_t SplitPlaneMetric_Axial(const qbsp_plane_t &p, const aabb3d &bounds) -{ - vec_t value = 0; - for (int i = 0; i < 3; i++) { - if (static_cast(i) == p.get_type()) { - const vec_t dist = p.get_dist() * p.get_normal()[i]; - value += (bounds.maxs()[i] - dist) * (bounds.maxs()[i] - dist); - value += (dist - bounds.mins()[i]) * (dist - bounds.mins()[i]); - } else { - value += 2 * (bounds.maxs()[i] - bounds.mins()[i]) * (bounds.maxs()[i] - bounds.mins()[i]); - } - } - - return value; -} - /* * Split a bounding box by a plane; The front and back bounds returned * are such that they completely contain the portion of the input box @@ -710,31 +690,15 @@ inline void DivideBounds(const aabb3d &in_bounds, const qbsp_plane_t &split, aab } } -/* - * Calculate the split plane metric for non-axial planes - */ -inline vec_t SplitPlaneMetric_NonAxial(const qbsp_plane_t &p, const aabb3d &bounds) +inline vec_t SplitPlaneMetric(const qbsp_plane_t &p, const aabb3d &bounds) { aabb3d f, b; - vec_t value = 0.0; DivideBounds(bounds, p, f, b); - for (int i = 0; i < 3; i++) { - value += (f.maxs()[i] - f.mins()[i]) * (f.maxs()[i] - f.mins()[i]); - value += (b.maxs()[i] - b.mins()[i]) * (b.maxs()[i] - b.mins()[i]); - } - - return value; -} - -inline vec_t SplitPlaneMetric(const qbsp_plane_t &p, const aabb3d &bounds) -{ - if (p.get_type() < plane_type_t::PLANE_ANYX) { - return SplitPlaneMetric_Axial(p, bounds); - } else { - return SplitPlaneMetric_NonAxial(p, bounds); - } + // i.e. a good split will have equal volumes on front and back. + // a bad split will have all of the volume on one side. + return fabs(f.volume() - b.volume()); } /* @@ -744,78 +708,49 @@ ChooseMidPlaneFromList The clipping hull BSP doesn't worry about avoiding splits ================== */ -static std::optional ChooseMidPlaneFromList(const std::vector> &brushes, const aabb3d &bounds, bool forced) +static std::optional ChooseMidPlaneFromList(const std::vector> &brushes, const aabb3d &bounds) { - /* pick the plane that splits the least */ vec_t bestaxialmetric = VECT_MAX; std::optional bestaxialplane; + vec_t bestanymetric = VECT_MAX; std::optional bestanyplane; - for (int pass = 0; pass < 2; pass++) { - for (auto &brush : brushes) { - if ((pass & 1) && !brush->original->contents.is_any_detail(qbsp_options.target_game)) { - continue; + for (auto &brush : brushes) { + for (auto &side : brush->sides) { + if (side.bevel) { + continue; // never use a bevel as a spliter } - if (!(pass & 1) && brush->original->contents.is_any_detail(qbsp_options.target_game)) { - continue; + if (side.onnode) { + continue; // allready a node splitter } - for (auto &side : brush->sides) { - if (side.bevel) { - continue; // never use a bevel as a spliter - } - if (!side.w) { - continue; // nothing visible, so it can't split - } - if (side.onnode) { - continue; // allready a node splitter - } - if (side.get_texinfo().flags.is_hintskip) { - continue; // skip surfaces are never chosen - } + const qbsp_plane_t &plane = side.plane; + /* calculate the split metric, smaller values are better */ + const vec_t metric = SplitPlaneMetric(plane, bounds); - const qbsp_plane_t &plane = side.plane; - /* calculate the split metric, smaller values are better */ - const vec_t metric = SplitPlaneMetric(plane, bounds); + if (metric < bestanymetric) { + bestanymetric = metric; + bestanyplane = plane; + } - if (metric < bestanymetric) { - bestanymetric = metric; - bestanyplane = plane; - } - - /* check for axis aligned surfaces */ - if (plane.get_type() < plane_type_t::PLANE_ANYX) { - if (metric < bestaxialmetric) { - bestaxialmetric = metric; - bestaxialplane = plane; - } + /* check for axis aligned surfaces */ + if (plane.get_type() < plane_type_t::PLANE_ANYX) { + if (metric < bestaxialmetric) { + bestaxialmetric = metric; + bestaxialplane = plane; } } } - - if (bestanyplane || bestaxialplane) { - break; - } } // prefer the axial split - auto bestsurface = !bestaxialplane ? bestanyplane : bestaxialplane; + auto bestsurface = bestaxialplane ? bestaxialplane : bestanyplane; if (!bestsurface) { FError("No valid planes in surface list"); } - // ericw -- (!forced) is true on the final SolidBSP phase for the world. - // !bestsurface->has_struct means all surfaces in this node are detail, so - // mark the surface as a detail separator. - // fixme-brushbsp: what to do here? -#if 0 - if (!forced && !bestsurface->has_struct) { - bestsurface->detail_separator = true; - } -#endif - return bestsurface; } @@ -859,32 +794,8 @@ static std::optional SelectSplitPlane(const std::vector qbsp_options.midsplitsurffraction.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(); - - largenode = (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; - } - } - } - - // do fast way for clipping hull - if (use_mid_split || largenode) { - if (auto mid_plane = ChooseMidPlaneFromList(brushes, node->bounds, use_mid_split)) { + if (brushes.size() >= qbsp_options.midsplitbrushes.value()) { + if (auto mid_plane = ChooseMidPlaneFromList(brushes, node->bounds)) { for (auto &b : brushes) { b->side = TestBrushToPlanenum(*b, mid_plane.value(), nullptr, nullptr, nullptr); @@ -893,7 +804,6 @@ static std::optional SelectSplitPlane(const std::vector