diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 69cc4d39..6e09688a 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -127,106 +127,6 @@ std::tuple SplitFace(face_t *in, const qplane3d &split) return {new_front, new_back}; } -/* -================= -RemoveOutsideFaces - -Quick test before running ClipInside; move any faces that are completely -outside the brush to the outside list, without splitting them. This saves us -time in mergefaces later on (and sometimes a lot of memory) - -Input is a list of faces in the param `inside`. -On return, the ones touching `brush` remain in `inside`, the rest are added to `outside`. -================= -*/ -static void RemoveOutsideFaces(const brush_t &brush, std::list *inside, std::list *outside) -{ - std::list oldinside; - - // clear `inside`, transfer it to `oldinside` - std::swap(*inside, oldinside); - - for (face_t *face : oldinside) { - std::optional w = face->w; - for (auto &clipface : brush.faces) { - w = w->clip(Face_Plane(&clipface), options.epsilon.value(), false)[SIDE_BACK]; - if (!w) - break; - } - if (!w) { - /* The face is completely outside this brush */ - outside->push_front(face); - } else { - inside->push_front(face); - } - } -} - -/* -================= -ClipInside - -Clips all of the faces in the inside list, possibly moving them to the -outside list or spliting it into a piece in each list. - -Faces exactly on the plane will stay inside unless overdrawn by later brush -================= -*/ -static void ClipInside( - const face_t *clipface, bool precedence, std::list *inside, std::list *outside) -{ - std::list oldinside; - - // effectively make a copy of `inside`, and clear it - std::swap(*inside, oldinside); - - const qbsp_plane_t &splitplane = map.planes[clipface->planenum]; - - for (face_t *face : oldinside) { - /* HACK: Check for on-plane but not the same planenum - ( https://github.com/ericwa/ericw-tools/issues/174 ) - */ - bool spurious_onplane = false; - { - std::array counts = face->w.calc_sides(splitplane, nullptr, nullptr, options.epsilon.value()); - - if (counts[SIDE_ON] && !counts[SIDE_FRONT] && !counts[SIDE_BACK]) { - spurious_onplane = true; - } - } - - std::array frags; - - /* Handle exactly on-plane faces */ - if (face->planenum == clipface->planenum || spurious_onplane) { - const qplane3d faceplane = Face_Plane(face); - const qplane3d clipfaceplane = Face_Plane(clipface); - const vec_t dp = qv::dot(faceplane.normal, clipfaceplane.normal); - const bool opposite = (dp < 0); - - if (opposite || precedence) { - /* always clip off opposite facing */ - frags[clipface->planeside] = {}; - frags[!clipface->planeside] = {face}; - } else { - /* leave it on the outside */ - frags[clipface->planeside] = {face}; - frags[!clipface->planeside] = {}; - } - } else { - /* proper split */ - std::tie(frags[0], frags[1]) = SplitFace(face, splitplane); - } - - if (frags[clipface->planeside]) { - outside->push_front(frags[clipface->planeside]); - } - if (frags[!clipface->planeside]) { - inside->push_front(frags[!clipface->planeside]); - } - } -} - face_t *MirrorFace(const face_t *face) { face_t *newface = NewFaceFromFace(face); @@ -237,296 +137,3 @@ face_t *MirrorFace(const face_t *face) return newface; } - -static void FreeFaces(std::list &facelist) -{ - for (face_t *face : facelist) { - delete face; - } - facelist.clear(); -} - -//========================================================================== - -static std::vector> SingleBrush(std::unique_ptr a) -{ - std::vector> res; - res.push_back(std::move(a)); - return res; -} - -static bool ShouldClipbrushEatBrush(const brush_t &brush, const brush_t &clipbrush) -{ - if (clipbrush.contents.is_any_solid(options.target_game)) { - return true; - } - - if (clipbrush.contents.types_equal(brush.contents, options.target_game)) { - return clipbrush.contents.will_clip_same_type(options.target_game); - } - - return false; -} - -static std::list CSGFace_ClipAgainstSingleBrush(std::list input, const mapentity_t *srcentity, const brush_t *srcbrush, const brush_t *clipbrush) -{ - if (srcbrush == clipbrush) { - //logging::print(" ignoring self-clip\n"); - return input; - } - - const int srcindex = srcbrush->file_order; - const int clipindex = clipbrush->file_order; - - if (!ShouldClipbrushEatBrush(*srcbrush, *clipbrush)) { - return {input}; - } - - std::list inside {input}; - std::list outside; - RemoveOutsideFaces(*clipbrush, &inside, &outside); - - // at this point, inside = the faces of `input` which are touching `clipbrush` - // outside = the other faces of `input` - - const bool overwrite = (srcindex < clipindex); - - for (auto &clipface : clipbrush->faces) - ClipInside(&clipface, overwrite, &inside, &outside); - - // inside = parts of `brush` that are inside `clipbrush` - // outside = parts of `brush` that are outside `clipbrush` - - return outside; -} - -struct brush_ptr_less -{ - constexpr bool operator()(const brush_t *a, const brush_t *b) const - { - return a->file_order < b->file_order; - } -}; - -using brush_result_set_t = std::set; - -// fixme-brushbsp: add bounds test -static void GatherPossibleClippingBrushes_R(const node_t *node, const face_t *srcface, brush_result_set_t &result) -{ - if (node->planenum == PLANENUM_LEAF) { - for (auto *brush : node->original_brushes) { - result.insert(brush); - } - return; - } - - GatherPossibleClippingBrushes_R(node->children[0], srcface, result); - GatherPossibleClippingBrushes_R(node->children[1], srcface, result); -} - -/* -================== -GatherPossibleClippingBrushes - -Starting a search at `node`, returns brushes that possibly intersect `srcface`. -================== -*/ -static brush_result_set_t GatherPossibleClippingBrushes(const mapentity_t* srcentity, const node_t *node, const face_t *srcface) -{ - brush_result_set_t result; - - GatherPossibleClippingBrushes_R(node, srcface, result); - - return result; -} - -/* -================== -CSGFace - -Given `srcface`, which was produced from `srcbrush` and lies on `srcnode`: - - - search srcnode as well as its children for brushes which might clip - srcface. - - - clip srcface against all such brushes - -Frees srcface. -================== -*/ -std::list CSGFace(face_t *srcface, const mapentity_t *srcentity, const brush_t *srcbrush, const node_t *srcnode) -{ - const auto possible_clipbrushes = GatherPossibleClippingBrushes(srcentity, srcnode, srcface); - - //logging::print("face {} has {} possible clipbrushes\n", (void *)srcface, possible_clipbrushes.size()); - - std::list result{srcface}; - - for (const brush_t *possible_clipbrush : possible_clipbrushes) { - result = CSGFace_ClipAgainstSingleBrush(std::move(result), srcentity, srcbrush, possible_clipbrush); - } - - return result; -} - -/* -================== -SubtractBrush - -Returns the fragments from a - b -================== -*/ -static std::vector> SubtractBrush(std::unique_ptr 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) { - // is `a` fully in front of `side`? - bool fully_infront = true; - - // fixme-brushbsp: factor this out somewhere - for (const auto &a_face : a->faces) { - for (const auto &a_point : a_face.w) { - if (Face_Plane(&side).distance_to(a_point) < 0) { - fully_infront = false; - break; - } - } - if (!fully_infront) { - break; - } - } - - if (fully_infront) { - // `a` is fully in front of this side of b, so they don't actually intersect - return SingleBrush(std::move(a)); - } - } - - std::vector> frontlist; - std::vector> unclassified = SingleBrush(std::move(a)); - - for (const auto &side : b.faces) { - std::vector> new_unclassified; - - for (auto &fragment : unclassified) { - // destructively processing `unclassified` here - auto [front, back] = SplitBrush(std::move(fragment), Face_Plane(&side)); - if (front) { - frontlist.push_back(std::move(front)); - } - if (back) { - new_unclassified.push_back(std::move(back)); - } - } - - unclassified = std::move(new_unclassified); - } - - return frontlist; -} - -/* -================== -BrushGE - -Returns a >= b as far as brush clipping -================== -*/ -bool BrushGE(const brush_t& a, const brush_t& b) -{ - // same contents clip each other - if (a.contents.will_clip_same_type(options.target_game, b.contents)) { - // map file order - return a.file_order > b.file_order; - } - - // only chop if at least one of the two contents is - // opaque (solid, sky, or detail) - if (!(a.contents.chops(options.target_game) || b.contents.chops(options.target_game))) { - return false; - } - - int32_t a_pri = a.contents.priority(options.target_game); - int32_t b_pri = b.contents.priority(options.target_game); - - if (a_pri == b_pri) { - // map file order - return a.file_order > b.file_order; - } - - 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__); - - // each inner vector corresponds to a brush in `input` - // (set up this way for thread safety) - 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(), [&](const size_t i) { - const auto& brush = input[i]; - - // the fragments `brush` is chopped into - std::vector> brush_result = SingleBrush( - // start with a copy of brush - std::make_unique(*brush) - ); - - for (auto &clipbrush : input) { - if (brush == clipbrush) { - continue; - } - if (brush->bounds.disjoint_or_touching(clipbrush->bounds)) { - continue; - } - - if (BrushGE(*clipbrush, *brush)) { - std::vector> new_result; - - // clipbrush is stronger. - // rebuild existing fragments in brush_result, cliping them to clipbrush - for (auto ¤t_fragment : brush_result) { - for (auto &new_fragment : SubtractBrush(std::move(current_fragment), *clipbrush)) { - new_result.push_back(std::move(new_fragment)); - } - } - - brush_result = std::move(new_result); - } - } - - // save the result - brush_fragments[i] = std::move(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/surfaces.cc b/qbsp/surfaces.cc index 2f102567..ee50b3fe 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -579,153 +579,6 @@ void MakeMarkFaces(mapentity_t* entity, node_t* node) MakeMarkFaces(entity, node->children[1]); } -// the fronts of `faces` are facing `node`, determine what gets clipped away -// (return the post-clipping result) -static std::list ClipFacesToTree_r(node_t *node, const brush_t *srcbrush, std::list faces) -{ - if (node->planenum == PLANENUM_LEAF) { - // fixme-brushbsp: move to contentflags_t? - if (node->contents.is_solid(options.target_game) - || node->contents.is_detail_solid(options.target_game) - || node->contents.is_sky(options.target_game)) { - // solids eat any faces that reached this point - return {}; - } - - // see what the game thinks about the clip - if (srcbrush->contents.will_clip_same_type(options.target_game, node->contents)) { - return {}; - } - - // other content types let the faces thorugh - return faces; - } - - const qbsp_plane_t &splitplane = map.planes.at(node->planenum); - - std::list front, back; - - for (auto *face : faces) { - auto [frontFragment, backFragment] = SplitFace(face, splitplane); - if (frontFragment) { - front.push_back(frontFragment); - } - if (backFragment) { - back.push_back(backFragment); - } - } - - front = ClipFacesToTree_r(node->children[0], srcbrush, front); - back = ClipFacesToTree_r(node->children[1], srcbrush, back); - - // merge lists - front.splice(front.end(), back); - - return front; -} - -static std::list ClipFacesToTree(node_t *node, const brush_t *srcbrush, std::list faces) -{ - // handles the first level - faces are all supposed to be lying exactly on `node` - for (auto *face : faces) { - Q_assert(face->planenum == node->planenum); - } - - std::list front, back; - for (auto *face : faces) { - if (face->planeside == 0) { - front.push_back(face); - } else { - back.push_back(face); - } - } - - front = ClipFacesToTree_r(node->children[0], srcbrush, front); - back = ClipFacesToTree_r(node->children[1], srcbrush, back); - - // merge lists - front.splice(front.end(), back); - - return front; -} - -static void AddFaceToTree_r(mapentity_t* entity, face_t *face, brush_t *srcbrush, node_t* node) -{ - if (node->planenum == PLANENUM_LEAF) { - //FError("couldn't find node for face"); - // after outside filling, this is completely expected - return; - } - - if (face->planenum == node->planenum) { - // found the correct plane - add the face to it. - - ++c_nodefaces; - - // csg it - std::list faces = CSGFace(face, entity, srcbrush, node); - - // clip them to the descendant parts of the BSP - // (otherwise we could have faces floating in the void on this node) - faces = ClipFacesToTree(node, srcbrush, faces); - - for (face_t *part : faces) { - node->facelist.push_back(part); - - if (srcbrush->contents.is_mirrored(options.target_game)) { - node->facelist.push_back(MirrorFace(part)); - } - } - return; - } - - // fixme-brushbsp: we need to handle the case of the face being near enough that it gets clipped away, - // but not face->planenum == node->planenum - auto [frontWinding, backWinding] = face->w.clip(map.planes.at(node->planenum)); - if (frontWinding) { - auto *newFace = new face_t{*face}; - newFace->w = *frontWinding; - AddFaceToTree_r(entity, newFace, srcbrush, node->children[0]); - } - if (backWinding) { - auto *newFace = new face_t{*face}; - newFace->w = *backWinding; - AddFaceToTree_r(entity, newFace, srcbrush, node->children[1]); - } - - delete face; -} - -/* -================ -MakeVisibleFaces - -Given a completed BSP tree and a list of the original brushes (in `entity`), - -- filters the brush faces into the BSP, finding the correct nodes they end up on -- clips the faces by other brushes. - - first iteration, we can just do an exhaustive check against all brushes -================ -*/ -void MakeVisibleFaces(mapentity_t* entity, node_t* headnode) -{ - c_nodefaces = 0; - - for (auto &brush : entity->brushes) { - for (auto &face : brush->faces) { - if (!face.visible) { - continue; - } - face_t *temp = CopyFace(&face); - - AddFaceToTree_r(entity, temp, brush.get(), headnode); - } - } - - logging::print(logging::flag::STAT, "{} nodefaces\n", c_nodefaces); -} - struct makefaces_stats_t { int c_nodefaces; int c_merge;