diff --git a/include/qbsp/csg4.hh b/include/qbsp/csg4.hh index 580a15b5..5f7bba6d 100644 --- a/include/qbsp/csg4.hh +++ b/include/qbsp/csg4.hh @@ -21,10 +21,13 @@ #pragma once +#include + #include #include #include +#include struct face_t; @@ -33,3 +36,4 @@ face_t *NewFaceFromFace(const face_t *in); face_t *MirrorFace(const face_t *face); std::tuple SplitFace(face_t *in, const qplane3d &split); void UpdateFaceSphere(face_t *in); +std::vector ChopBrushes(const std::vector &input); diff --git a/include/qbsp/solidbsp.hh b/include/qbsp/solidbsp.hh index 9eaa3d48..e47a2af5 100644 --- a/include/qbsp/solidbsp.hh +++ b/include/qbsp/solidbsp.hh @@ -21,8 +21,13 @@ #pragma once +#include + +#include + #include #include +#include extern std::atomic splitnodes; @@ -32,4 +37,5 @@ class mapentity_t; void DetailToSolid(node_t *node); void PruneNodes(node_t *node); +twosided> SplitBrush(const brush_t &brush, const qplane3d &split); node_t *SolidBSP(mapentity_t *entity, bool midsplit); diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 160bc021..bde60b3d 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -474,3 +475,126 @@ std::list CSGFace(face_t *srcface, const mapentity_t *srcentity, const return result; } + +/* +================== +SubtractBrush + +Returns the fragments from a - b +================== +*/ +std::vector SubtractBrush(const brush_t& a, const brush_t& b) +{ + // first, check if `a` is fully in front of _any_ of b's planes + for (const auto &side : b.faces) { + auto [front, back] = SplitBrush(a, Face_Plane(&side)); + if (front && !back) { + // `a` is fully in front of this side of b, so they don't actually intersect + return {a}; + } + } + + std::vector frontlist; + std::vector unclassified{a}; + + for (const auto &side : b.faces) { + std::vector new_unclassified; + + for (const auto &fragment : unclassified) { + auto [front, back] = SplitBrush(fragment, Face_Plane(&side)); + if (front) { + frontlist.push_back(*front); + } + if (back) { + new_unclassified.push_back(*back); + } + } + + unclassified = std::move(new_unclassified); + } + + return frontlist; +} + +/* +================== +BrushGE + +Returns a >= b as far as brush clipping +================== +*/ +static bool BrushGE(const brush_t& a, const brush_t& b) +{ + int32_t a_pri = a.contents.priority(options.target_game); + int32_t b_pri = b.contents.priority(options.target_game); + + return a_pri >= b_pri; +} + +/* +================== +ChopBrushes + +Clips off any overlapping portions of brushes +================== +*/ +std::vector ChopBrushes(const std::vector& input) +{ + logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); + + // output vector for the parallel_for + std::vector> brush_fragments; + brush_fragments.resize(input.size()); + + /* + * For each brush, clip away the parts that are inside other brushes. + * Solid brushes override non-solid brushes. + * brush => the brush to be clipped + * clipbrush => the brush we are clipping against + * + * The output of this is a face list for each brush called "outside" + */ + tbb::parallel_for(static_cast(0), input.size(), [input, &brush_fragments](const size_t i) { + const auto &brush = input[i]; + // the fragments `brush` is chopped into + std::vector brush_result{brush}; + + for (auto &clipbrush : input) { + if (&brush == &clipbrush) { + continue; + } + if (brush.bounds.disjoint(clipbrush.bounds)) { + continue; + } + + if (BrushGE(clipbrush, brush)) { + std::vector new_result; + + // clipbrush is stronger. clip all existing fragments to clipbrush + for (const auto ¤t_fragment : brush_result) { + for (const auto &new_fragment : SubtractBrush(current_fragment, clipbrush)) { + new_result.push_back(new_fragment); + } + } + + brush_result = std::move(new_result); + } + } + + // save the result + brush_fragments[i] = brush_result; + }); + + // Non parallel part: + std::vector result; + for (auto &fragment_list : brush_fragments) { + for (auto &fragment : fragment_list) { + result.push_back(std::move(fragment)); + } + } + + logging::print(logging::flag::STAT, " {:8} brushes\n", input.size()); + logging::print(logging::flag::STAT, " {:8} chopped brushes\n", result.size()); + + return result; +} diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index de397a26..3650fdad 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -653,6 +654,8 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) logging::print(logging::flag::PROGRESS, "---- Brush_LoadEntity ----\n"); auto stats = Brush_LoadEntity(entity, hullnum); + entity->brushes = ChopBrushes(entity->brushes); + // we're discarding the brush if (discarded_trigger) { entity->brushes.clear(); diff --git a/qbsp/solidbsp.cc b/qbsp/solidbsp.cc index db7fd80d..2ca2515f 100644 --- a/qbsp/solidbsp.cc +++ b/qbsp/solidbsp.cc @@ -616,7 +616,7 @@ unchanged https://github.com/id-Software/Quake-2-Tools/blob/master/bsp/qbsp3/brushbsp.c#L935 ================ */ -static twosided> SplitBrush(const brush_t &brush, const qplane3d &split) +twosided> SplitBrush(const brush_t &brush, const qplane3d &split) { twosided> result;