From 40bba01b21ce882e16be3fd5f9568976b4f90659 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen Date: Sat, 2 Jul 2022 13:33:18 -0600 Subject: [PATCH] qbsp: brushbsp.cc import from qbsp3 --- common/bsputils.cc | 11 + include/common/bspfile.hh | 7 +- include/common/bsputils.hh | 2 + include/qbsp/brush.hh | 14 +- include/qbsp/brushbsp.hh | 9 +- include/qbsp/map.hh | 8 + include/qbsp/portals.hh | 3 +- include/qbsp/qbsp.hh | 10 +- qbsp/brush.cc | 23 + qbsp/brushbsp.cc | 1192 ++++++++++++++++++------------------ qbsp/exportobj.cc | 8 +- qbsp/faces.cc | 35 +- qbsp/map.cc | 3 + qbsp/merge.cc | 5 +- qbsp/outside.cc | 43 +- qbsp/portals.cc | 57 +- qbsp/prtfile.cc | 26 +- qbsp/qbsp.cc | 41 +- qbsp/test_qbsp.cc | 49 +- qbsp/tjunc.cc | 12 +- qbsp/tree.cc | 10 +- qbsp/writebsp.cc | 9 +- 22 files changed, 867 insertions(+), 710 deletions(-) diff --git a/common/bsputils.cc b/common/bsputils.cc index 747eea3e..ff0218ab 100644 --- a/common/bsputils.cc +++ b/common/bsputils.cc @@ -514,6 +514,17 @@ std::vector GLM_FacePoints(const mbsp_t *bsp, const mface_t *face) return points; } +polylib::winding_t Face_Winding(const mbsp_t *bsp, const mface_t *face) +{ + polylib::winding_t w{}; + + for (int j = 0; j < face->numedges; j++) { + w.push_back(Face_PointAtIndex(bsp, face, j)); + } + + return w; +} + qvec3f Face_Centroid(const mbsp_t *bsp, const mface_t *face) { auto points = GLM_FacePoints(bsp, face); diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 909b9c26..a6e84776 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -157,9 +157,12 @@ struct surfflags_t // native flags value; what's written to the BSP basically int32_t native; - // an invisible surface + // an invisible surface (Q1 "skip" texture, Q2 SURF_NODRAW) bool is_skip; + // completely ignore, allowing non-closed brushes (Q2 SURF_SKIP) + bool is_hintskip; + // hint surface bool is_hint; @@ -205,7 +208,7 @@ struct surfflags_t private: constexpr auto as_tuple() const { - return std::tie(native, is_skip, is_hint, no_dirt, no_shadow, no_bounce, no_minlight, no_expand, light_ignore, + return std::tie(native, is_skip, is_hintskip, is_hint, no_dirt, no_shadow, no_bounce, no_minlight, no_expand, light_ignore, phong_angle, phong_angle_concave, minlight, minlight_color, light_alpha); } diff --git a/include/common/bsputils.hh b/include/common/bsputils.hh index 14931626..8523eafe 100644 --- a/include/common/bsputils.hh +++ b/include/common/bsputils.hh @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -78,6 +79,7 @@ const qvec3f &Face_PointAtIndex(const mbsp_t *bsp, const mface_t *f); const qvec3f &Vertex_GetPos(const mbsp_t *bsp, int num); qvec3d Face_Normal(const mbsp_t *bsp, const mface_t *f); std::vector GLM_FacePoints(const mbsp_t *bsp, const mface_t *face); +polylib::winding_t Face_Winding(const mbsp_t *bsp, const mface_t *face); qvec3f Face_Centroid(const mbsp_t *bsp, const mface_t *face); void Face_DebugPrint(const mbsp_t *bsp, const mface_t *face); diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index 2536d089..86ebacb4 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -27,6 +27,7 @@ #include class mapentity_t; +struct maptexinfo_t; struct side_t { @@ -42,16 +43,25 @@ struct side_t // non-visible means we can discard the brush side // (avoiding generating a BSP spit, so expanding it outwards) bool bevel; // don't ever use for bsp splitting + + bool tested; + + const maptexinfo_t& get_texinfo() const; }; +class mapbrush_t; + struct bspbrush_t { /** * The brushes in the mapentity_t::brushes vector are considered originals. Brush fragments created during * the BrushBSP will have this pointing back to the original brush in mapentity_t::brushes. + * + * fixme-brushbsp: this is supposed to be a mapbrush_t */ bspbrush_t *original; uint32_t file_order; aabb3d bounds; + int side, testside; // side of node during construction std::vector sides; contentflags_t contents; /* BSP contents */ short lmshift; /* lightmap scaling (qu/lightmap pixel), passed to the light util */ @@ -59,9 +69,9 @@ struct bspbrush_t { mapentity_t *func_areaportal; void update_bounds(); -}; -class mapbrush_t; + std::unique_ptr copy_unique() const; +}; qplane3d Face_Plane(const face_t *face); qplane3d Face_Plane(const side_t *face); diff --git a/include/qbsp/brushbsp.hh b/include/qbsp/brushbsp.hh index c00689fa..e91e55fc 100644 --- a/include/qbsp/brushbsp.hh +++ b/include/qbsp/brushbsp.hh @@ -29,16 +29,15 @@ #include #include -extern std::atomic splitnodes; - struct bspbrush_t; struct node_t; struct face_t; class mapentity_t; struct tree_t; -void DetailToSolid(node_t *node); -void PruneNodes(node_t *node); bool WindingIsTiny(const winding_t &w, double size = 0.2); -twosided> SplitBrush(std::unique_ptr brush, const qplane3d &split); +std::unique_ptr BrushFromBounds(const aabb3d &bounds); +tree_t *BrushBSP(std::vector> brushlist); + +// compatibility version tree_t *BrushBSP(mapentity_t *entity, bool midsplit); diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index aebcbdb1..22d242a4 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -32,6 +32,7 @@ #include #include #include +#include struct bspbrush_t; @@ -124,6 +125,8 @@ struct maptexdata_t #include +extern std::recursive_mutex map_planes_lock; + struct mapdata_t { /* Arrays of actual items */ @@ -168,6 +171,11 @@ struct mapdata_t const std::string &texinfoTextureName(int texinfo) const { return miptexTextureName(mtexinfos.at(texinfo).miptex); } + inline qbsp_plane_t get_plane(int pnum) { + const auto lock = std::lock_guard(map_planes_lock); + return planes.at(pnum); + } + int skip_texinfo; mapentity_t *world_entity(); diff --git a/include/qbsp/portals.hh b/include/qbsp/portals.hh index d4518d36..14b7be60 100644 --- a/include/qbsp/portals.hh +++ b/include/qbsp/portals.hh @@ -25,6 +25,7 @@ #include #include +#include struct side_t; @@ -43,7 +44,7 @@ struct portal_t struct tree_t { - node_t *headnode; + std::unique_ptr headnode; node_t outside_node = {}; // portals outside the world face this aabb3d bounds; }; diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index b3417789..39625c4a 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -209,6 +210,8 @@ public: this, "oldrottex", false, &debugging_group, "use old rotate_ brush texturing aligned at (0 0 0)"}; setting_scalar epsilon{ this, "epsilon", 0.0001, 0.0, 1.0, &debugging_group, "customize epsilon value for point-on-plane checks"}; + setting_scalar microvolume{ + this, "microvolume", 1.0, 0.0, 1000.0, &debugging_group, "microbrush volume"}; setting_bool contenthack{this, "contenthack", false, &debugging_group, "hack to fix leaks through solids. causes missing faces in some cases so disabled by default"}; setting_bool leaktest{this, "leaktest", false, &map_development_group, "make compilation fail if the map leaks"}; @@ -349,18 +352,23 @@ struct face_t : face_fragment_t // there is a node_t structure for every node and leaf in the bsp tree struct bspbrush_t; +struct side_t; struct node_t { + // both leafs and nodes aabb3d bounds; // bounding volume, not just points inside node_t *parent; + // this is also a bounding volume like `bounds` + std::unique_ptr volume; // one for each leaf/node // information for decision nodes int planenum; // -1 = leaf node int firstface; // decision node only int numfaces; // decision node only - node_t *children[2]; // children[0] = front side, children[1] = back side of plane. only valid for decision nodes + twosided> children; // children[0] = front side, children[1] = back side of plane. only valid for decision nodes std::list facelist; // decision nodes only, list for both sides + side_t *side; // the side that created the node // information for leafs contentflags_t contents; // leaf nodes (0 for decision nodes) diff --git a/qbsp/brush.cc b/qbsp/brush.cc index c931f331..345369e1 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -28,6 +28,18 @@ #include #include +const maptexinfo_t& side_t::get_texinfo() const +{ + return map.mtexinfos[this->texinfo]; +} + +std::unique_ptr bspbrush_t::copy_unique() const +{ + bspbrush_t *copy = new bspbrush_t{*this}; + + return std::unique_ptr(copy); +} + /* * Beveled clipping hull can generate many extra faces */ @@ -56,6 +68,7 @@ Face_Plane */ qplane3d Face_Plane(const face_t *face) { + const auto lock = std::lock_guard(map_planes_lock); const qplane3d &result = map.planes.at(face->planenum); if (face->planeside) { @@ -67,6 +80,7 @@ qplane3d Face_Plane(const face_t *face) qplane3d Face_Plane(const side_t *face) { + const auto lock = std::lock_guard(map_planes_lock); const qplane3d &result = map.planes.at(face->planenum); if (face->planeside) { @@ -85,6 +99,7 @@ Note: this will not catch 0 area polygons */ static void CheckFace(side_t *face, const mapface_t &sourceface) { + const auto lock = std::lock_guard(map_planes_lock); const qbsp_plane_t &plane = map.planes.at(face->planenum); if (face->w.size() < 3) { @@ -209,6 +224,8 @@ inline int plane_hash_fn(const qplane3d &p) static void PlaneHash_Add(const qplane3d &p, int index) { + const auto lock = std::lock_guard(map_planes_lock); + const int hash = plane_hash_fn(p); map.planehash[hash].push_back(index); } @@ -219,6 +236,8 @@ static void PlaneHash_Add(const qplane3d &p, int index) */ static int NewPlane(const qplane3d &plane, planeside_t *side) { + const auto lock = std::lock_guard(map_planes_lock); + vec_t len = qv::length(plane.normal); if (len < 1 - options.epsilon.value() || len > 1 + options.epsilon.value()) @@ -248,6 +267,8 @@ static int NewPlane(const qplane3d &plane, planeside_t *side) */ int FindPlane(const qplane3d &plane, planeside_t *side) { + const auto lock = std::lock_guard(map_planes_lock); + for (int i : map.planehash[plane_hash_fn(plane)]) { const qbsp_plane_t &p = map.planes.at(i); if (qv::epsilonEqual(p, plane)) { @@ -269,6 +290,8 @@ int FindPlane(const qplane3d &plane, planeside_t *side) */ int FindPositivePlane(int planenum) { + const auto lock = std::lock_guard(map_planes_lock); + const auto &plane = map.planes[planenum]; // already positive, or it's PLANE_ANY_x which doesn't matter diff --git a/qbsp/brushbsp.cc b/qbsp/brushbsp.cc index bbef81c9..5f0c8b3e 100644 --- a/qbsp/brushbsp.cc +++ b/qbsp/brushbsp.cc @@ -35,410 +35,37 @@ #include "tbb/task_group.h" -std::atomic splitnodes; +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +constexpr double PLANESIDE_EPSILON = 0.001; +//0.1 -static std::any leafstats; -static bool usemidsplit; +constexpr int PSIDE_FRONT = 1; +constexpr int PSIDE_BACK = 2; +constexpr int PSIDE_BOTH = (PSIDE_FRONT|PSIDE_BACK); +// this gets OR'ed in in the return value of QuickTestBrushToPlanenum if one of the brush sides is on the input plane +constexpr int PSIDE_FACING = 4; -/** - * Total number of brushes in the map - */ -static int mapbrushes; +struct bspstats_t { + std::any leafstats; + // total number of nodes, includes c_nonvis + std::atomic c_nodes; + // number of nodes created by splitting on a side_t which had !visible + std::atomic c_nonvis; + // total number of leafs + std::atomic c_leafs; +}; -/* -================== -FaceSide - -For BSP hueristic -================== -*/ -static int FaceSide__(const winding_t &w, const qbsp_plane_t &split) -{ - bool have_front, have_back; - int i; - - have_front = have_back = false; - - if (split.type < plane_type_t::PLANE_ANYX) { - /* shortcut for axial planes */ - const vec_t *p = &w[0][static_cast(split.type)]; - for (i = 0; i < w.size(); i++, p += 3) { - if (*p > split.dist + options.epsilon.value()) { - if (have_back) - return SIDE_ON; - have_front = true; - } else if (*p < split.dist - options.epsilon.value()) { - if (have_front) - return SIDE_ON; - have_back = true; - } - } - } else { - /* sloping planes take longer */ - for (i = 0; i < w.size(); i++) { - const vec_t dot = split.distance_to(w[i]); - if (dot > options.epsilon.value()) { - if (have_back) - return SIDE_ON; - have_front = true; - } else if (dot < -options.epsilon.value()) { - if (have_front) - return SIDE_ON; - have_back = true; - } - } - } - - if (!have_front) - return SIDE_BACK; - if (!have_back) - return SIDE_FRONT; - - return SIDE_ON; -} - -inline int FaceSide(const face_t *in, const qbsp_plane_t &split) -{ - vec_t dist = split.distance_to(in->origin); - - if (dist > in->radius) - return SIDE_FRONT; - else if (dist < -in->radius) - return SIDE_BACK; - else - return FaceSide__(in->w, split); -} - -inline int FaceSide(const side_t *in, const qbsp_plane_t &split) -{ - return FaceSide__(in->w, split); -} - -/* - * 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 - * on that side of the plane. Therefore, if the split plane is - * non-axial, then the returned bounds will overlap. - */ -static void DivideBounds(const aabb3d &in_bounds, const qbsp_plane_t &split, aabb3d &front_bounds, aabb3d &back_bounds) -{ - int a, b, c, i, j; - vec_t dist1, dist2, mid, split_mins, split_maxs; - qvec3d corner; - - front_bounds = back_bounds = in_bounds; - - if (split.type < plane_type_t::PLANE_ANYX) { - front_bounds[0][static_cast(split.type)] = back_bounds[1][static_cast(split.type)] = split.dist; - return; - } - - /* Make proper sloping cuts... */ - for (a = 0; a < 3; ++a) { - /* Check for parallel case... no intersection */ - if (fabs(split.normal[a]) < NORMAL_EPSILON) - continue; - - b = (a + 1) % 3; - c = (a + 2) % 3; - - split_mins = in_bounds.maxs()[a]; - split_maxs = in_bounds.mins()[a]; - for (i = 0; i < 2; ++i) { - corner[b] = in_bounds[i][b]; - for (j = 0; j < 2; ++j) { - corner[c] = in_bounds[j][c]; - - corner[a] = in_bounds[0][a]; - dist1 = split.distance_to(corner); - - corner[a] = in_bounds[1][a]; - dist2 = split.distance_to(corner); - - mid = in_bounds[1][a] - in_bounds[0][a]; - mid *= (dist1 / (dist1 - dist2)); - mid += in_bounds[0][a]; - - split_mins = max(min(mid, split_mins), in_bounds.mins()[a]); - split_maxs = min(max(mid, split_maxs), in_bounds.maxs()[a]); - } - } - if (split.normal[a] > 0) { - front_bounds[0][a] = split_mins; - back_bounds[1][a] = split_maxs; - } else { - back_bounds[0][a] = split_mins; - front_bounds[1][a] = split_maxs; - } - } -} - -/* - * 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.type) { - const vec_t dist = p.dist * p.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; -} - -/* - * Calculate the split plane metric for non-axial planes - */ -inline vec_t SplitPlaneMetric_NonAxial(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.type < plane_type_t::PLANE_ANYX) - return SplitPlaneMetric_Axial(p, bounds); - else - return SplitPlaneMetric_NonAxial(p, bounds); -} - -/* -================== -ChooseMidPlaneFromList - -The clipping hull BSP doesn't worry about avoiding splits -================== -*/ -static const side_t *ChooseMidPlaneFromList(const std::vector> &brushes, const aabb3d &bounds) -{ - /* pick the plane that splits the least */ - vec_t bestaxialmetric = VECT_MAX; - side_t *bestaxialsurface = nullptr; - vec_t bestanymetric = VECT_MAX; - side_t *bestanysurface = nullptr; - - for (int pass = 0; pass < 2; pass++) { - for (auto &brush : brushes) { - if (brush->contents.is_any_detail(options.target_game) != (pass == 1)) { - continue; - } - - for (auto &face : brush->sides) { - if (face.onnode) - continue; - if (!face.visible) { - // never use as a bsp splitter, efffectively filling the brush outwards - continue; - } - - const qbsp_plane_t &plane = map.planes.at(face.planenum); - bool axial = false; - - /* check for axis aligned surfaces */ - if (plane.type < plane_type_t::PLANE_ANYX) { - axial = true; - } - - /* calculate the split metric, smaller values are better */ - const vec_t metric = SplitPlaneMetric(plane, bounds); - - if (metric < bestanymetric) { - bestanymetric = metric; - bestanysurface = &face; - } - - if (axial) { - if (metric < bestaxialmetric) { - bestaxialmetric = metric; - bestaxialsurface = &face; - } - } - } - } - - if (bestanysurface != nullptr || bestaxialsurface != nullptr) { - break; - } - } - - // prefer the axial split - auto bestsurface = (bestaxialsurface == nullptr) ? bestanysurface : bestaxialsurface; - return bestsurface; -} - -/* -================== -ChoosePlaneFromList - -The real BSP heuristic - -fixme-brushbsp: prefer splits that include a lot of brush sides? -================== -*/ -static const side_t *ChoosePlaneFromList(const std::vector> &brushes, const aabb3d &bounds) -{ - /* pick the plane that splits the least */ - int minsplits = INT_MAX - 1; - vec_t bestdistribution = VECT_MAX; - side_t *bestsurface = nullptr; - - /* passes: - * 0: structural visible - * 1: structural non-visible - * 2: detail visible - * 3: detail non-visible - * */ - for (int pass = 0; pass < 4; pass++) { - const bool pass_wants_detail = (pass >= 2); - const bool pass_wants_visible = (pass == 0 || pass == 2); - - for (auto &brush : brushes) { - if (brush->contents.is_any_detail(options.target_game) != pass_wants_detail) { - continue; - } - - for (auto &face : brush->sides) { - if (face.onnode) { - continue; - } - if (face.visible != pass_wants_visible) { - continue; - } - - const bool hintsplit = map.mtexinfos.at(face.texinfo).flags.is_hint; - - const qbsp_plane_t &plane = map.planes.at(face.planenum); - int splits = 0; - - // now check all of the other faces in `brushes` and count how many - // would get split if we used `face` as the splitting plane - for (auto &brush2 : brushes) { - for (auto &face2 : brush2->sides) { - if (face2.planenum == face.planenum || face2.onnode) - continue; - if (!face2.visible) - continue; // don't penalize for splitting non-visible - - const surfflags_t &flags = map.mtexinfos.at(face2.texinfo).flags; - /* Don't penalize for splitting skip faces */ - if (flags.is_skip) - continue; - if (FaceSide(&face2, plane) == SIDE_ON) { - /* Never split a hint face except with a hint */ - if (!hintsplit && flags.is_hint) { - splits = INT_MAX; - break; - } - splits++; - if (splits >= minsplits) - break; - } - } - if (splits > minsplits) - break; - } - if (splits > minsplits) - continue; - - /* - * if equal numbers axial planes win, otherwise decide on spatial - * subdivision - */ - if (splits < minsplits || (splits == minsplits && plane.type < plane_type_t::PLANE_ANYX)) { - if (plane.type < plane_type_t::PLANE_ANYX) { - const vec_t distribution = SplitPlaneMetric(plane, bounds); - if (distribution > bestdistribution && splits == minsplits) - continue; - bestdistribution = distribution; - } - /* currently the best! */ - minsplits = splits; - bestsurface = &face; - } - } - } - - /* If we found a candidate on first pass, don't do a second pass */ - if (bestsurface != nullptr) { - return bestsurface; - } - } - - return bestsurface; -} - -/* -================== -SelectPartition - -Selects a surface from a linked list of surfaces to split the group on -returns NULL if the surface list can not be divided any more (a leaf) - -Called in parallel. -================== -*/ -static const side_t *SelectPartition(const std::vector> &brushes) -{ - // calculate a bounding box of the entire surfaceset - aabb3d bounds; - - for (auto &brush : brushes) { - bounds += brush->bounds; - } - - // how much of the map are we partitioning? - double fractionOfMap = brushes.size() / (double)mapbrushes; - - bool largenode = false; - - // decide if we should switch to the midsplit method - if (options.midsplitsurffraction.value() != 0.0) { - // new way (opt-in) - largenode = (fractionOfMap > options.midsplitsurffraction.value()); - } else { - // old way (ericw-tools 0.15.2+) - if (options.maxnodesize.value() >= 64) { - const vec_t maxnodesize = options.maxnodesize.value() - options.epsilon.value(); - - largenode = (bounds.maxs()[0] - bounds.mins()[0]) > maxnodesize || - (bounds.maxs()[1] - bounds.mins()[1]) > maxnodesize || - (bounds.maxs()[2] - bounds.mins()[2]) > maxnodesize; - } - } - -#if 0 - if (usemidsplit || largenode) // do fast way for clipping hull - return ChooseMidPlaneFromList(brushes, bounds); -#endif - - // do slow way to save poly splits for drawing hull - return ChoosePlaneFromList(brushes, bounds); -} - -//============================================================================ +static twosided> SplitBrush(std::unique_ptr brush, int planenum); /* ================== CreateBrushWindings +Currently only used in BrushFromBounds ================== */ -void CreateBrushWindings(bspbrush_t *brush) +static void CreateBrushWindings(bspbrush_t *brush) { std::optional w; @@ -464,6 +91,248 @@ void CreateBrushWindings(bspbrush_t *brush) brush->update_bounds(); } +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +std::unique_ptr BrushFromBounds(const aabb3d &bounds) +{ + auto b = std::unique_ptr(new bspbrush_t{}); + + b->sides.resize(6); + for (int i = 0; i < 3; i++) + { + { + qplane3d plane{}; + plane.normal[i] = 1; + plane.dist = bounds.maxs()[i]; + + side_t &side = b->sides[i]; + side.planenum = FindPlane(plane, &side.planeside); + } + + { + qplane3d plane{}; + plane.normal[i] = -1; + plane.dist = -bounds.mins()[i]; + + side_t &side = b->sides[3 + i]; + side.planenum = FindPlane(plane, &side.planeside); + } + } + + CreateBrushWindings(b.get()); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +static vec_t BrushVolume(const bspbrush_t &brush) +{ + // grab the first valid point as the corner + + bool found = false; + qvec3d corner; + for (auto &face : brush.sides) { + if (face.w.size() > 0) { + corner = face.w[0]; + found = true; + } + } + if (!found) { + return 0; + } + + // make tetrahedrons to all other faces + + vec_t volume = 0; + for (auto &side : brush.sides) { + if (!side.w.size()) { + continue; + } + auto plane = Face_Plane(&side); + vec_t d = -(qv::dot(corner, plane.normal) - plane.dist); + vec_t area = side.w.area(); + volume += d * area; + } + + volume /= 3; + return volume; +} + +//======================================================== + +/* +============== +BoxOnPlaneSide + +Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +============== +*/ +static int BoxOnPlaneSide(const aabb3d& bounds, const qbsp_plane_t &plane) +{ + // axial planes are easy + if (static_cast(plane.type) < 3) + { + int side = 0; + if (bounds.maxs()[static_cast(plane.type)] > plane.dist + PLANESIDE_EPSILON) + side |= PSIDE_FRONT; + if (bounds.mins()[static_cast(plane.type)] < plane.dist - PLANESIDE_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + std::array corners; + for (int i = 0; i < 3; i++) + { + if (plane.normal[i] < 0) + { + corners[0][i] = bounds.mins()[i]; + corners[1][i] = bounds.maxs()[i]; + } + else + { + corners[1][i] = bounds.mins()[i]; + corners[0][i] = bounds.maxs()[i]; + } + } + + double dist1 = qv::dot(plane.normal, corners[0]) - plane.dist; + double dist2 = qv::dot(plane.normal, corners[1]) - plane.dist; + int side = 0; + if (dist1 >= PLANESIDE_EPSILON) + side = PSIDE_FRONT; + if (dist2 < PLANESIDE_EPSILON) + side |= PSIDE_BACK; + + return side; +} + +/* +============ +QuickTestBrushToPlanenum + +Returns PSIDE_BACK, PSIDE_FRONT, PSIDE_BOTH depending on how the brush is split by planenum +============ +*/ +static int QuickTestBrushToPlanenum(const bspbrush_t &brush, int planenum, int *numsplits) +{ + *numsplits = 0; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (auto& side : brush.sides) { + int num = side.planenum; + if (num == planenum && side.planeside == SIDE_FRONT) + return PSIDE_BACK|PSIDE_FACING; + if (num == planenum && side.planeside == SIDE_BACK) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + const auto lock = std::lock_guard(map_planes_lock); + auto plane = map.planes[planenum]; + int s = BoxOnPlaneSide(brush.bounds, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} + +/* +============ +TestBrushToPlanenum + +============ +*/ +static int TestBrushToPlanenum(const bspbrush_t &brush, int planenum, int *numsplits, bool *hintsplit, int *epsilonbrush) +{ + *numsplits = 0; + *hintsplit = false; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (auto &side : brush.sides) { + int num = side.planenum; + if (num == planenum && side.planeside == SIDE_FRONT) { + return PSIDE_BACK | PSIDE_FACING; + } + if (num == planenum && side.planeside == SIDE_BACK) { + return PSIDE_FRONT | PSIDE_FACING; + } + } + + // box on plane side + qbsp_plane_t plane; + { + const auto lock = std::lock_guard(map_planes_lock); + plane = map.planes[planenum]; + } + int s = BoxOnPlaneSide(brush.bounds, plane); + + if (s != PSIDE_BOTH) + return s; + + // if both sides, count the visible faces split + vec_t d_front = 0; + vec_t d_back = 0; + + for (const side_t &side : brush.sides) + { + if (side.onnode) + continue; // on node, don't worry about splits + if (!side.visible) + continue; // we don't care about non-visible + auto &w = side.w; + if (!w) + continue; + int front = 0; + int back = 0; + for (auto &point : w) + { + const double d = qv::dot(point, plane.normal) - plane.dist; + if (d > d_front) + d_front = d; + if (d < d_back) + d_back = d; + + if (d > 0.1) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.1) // PLANESIDE_EPSILON) + back = 1; + } + if (front && back) { + if (!(side.get_texinfo().flags.is_hintskip)) { + (*numsplits)++; + if (side.get_texinfo().flags.is_hint) { + *hintsplit = true; + } + } + } + } + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + + return s; +} + +//======================================================== + /* ================ WindingIsTiny @@ -510,6 +379,186 @@ bool WindingIsHuge(const winding_t &w) return false; } +//============================================================================ + +/* +================== +LeafNode + +Creates a leaf node. + +Called in parallel. +================== +*/ +static void LeafNode(node_t *leafnode, std::vector> brushes, bspstats_t &stats) +{ + leafnode->facelist.clear(); + leafnode->planenum = PLANENUM_LEAF; + + leafnode->contents = options.target_game->create_empty_contents(); + for (auto &brush : brushes) { + leafnode->contents = options.target_game->combine_contents(leafnode->contents, brush->contents); + } + for (auto &brush : brushes) { + Q_assert(brush->original != nullptr); + leafnode->original_brushes.push_back(brush->original); + } + + options.target_game->count_contents_in_stats(leafnode->contents, stats.leafstats); +} + +//============================================================ + +static void CheckPlaneAgainstParents(int pnum, node_t *node) +{ + for (node_t *p = node->parent; p; p = p->parent) { + if (p->planenum == pnum) { + Error("Tried parent"); + } + } +} + +static bool CheckPlaneAgainstVolume(int pnum, node_t *node) +{ + auto [front, back] = SplitBrush(node->volume->copy_unique(), pnum); + + bool good = (front && back); + + return good; +} + +/* +================ +SelectSplitSide + +Using a hueristic, choses one of the sides out of the brushlist +to partition the brushes with. +Returns NULL if there are no valid planes to split with.. +================ +*/ +side_t *SelectSplitSide(const std::vector>& brushes, node_t *node) +{ + side_t* bestside = nullptr; + int bestvalue = -99999; + int bestsplits = 0; + + // the search order goes: visible-structural, visible-detail, + // nonvisible-structural, nonvisible-detail. + // If any valid plane is available in a pass, no further + // passes will be tried. + constexpr int numpasses = 4; + for (int pass = 0 ; pass < numpasses ; pass++) { + for (auto &brush : brushes) { + if ( (pass & 1) && !brush->original->contents.is_any_detail(options.target_game) ) + continue; + if ( !(pass & 1) && brush->original->contents.is_any_detail(options.target_game) ) + continue; + 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.tested) + continue; // we allready have metrics for this plane + if (side.get_texinfo().flags.is_hintskip) + continue; // skip surfaces are never chosen + if ( side.visible ^ (pass<2) ) + continue; // only check visible faces on first pass + + int pnum = FindPositivePlane(side.planenum); // always use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + int front = 0; + int back = 0; + int both = 0; + int facing = 0; + int splits = 0; + int epsilonbrush = 0; + bool hintsplit = false; + + for (auto &test : brushes) + { + int bsplits; + int s = TestBrushToPlanenum(*test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; + if (bsplits && (s&PSIDE_FACING) ) + Error ("PSIDE_FACING with splits"); + + test->testside = s; + // if the brush shares this face, don't bother + // testing that facenum as a splitter again + if (s & PSIDE_FACING) + { + facing++; + for (auto &testside : test->sides) { + if (testside.planenum == pnum) { + testside.tested = true; + } + } + } + if (s & PSIDE_FRONT) + front++; + if (s & PSIDE_BACK) + back++; + if (s == PSIDE_BOTH) + both++; + } + + // give a value estimate for using this plane + + int value = 5*facing - 5*splits - abs(front-back); + // value = -5*splits; + // value = 5*facing - 5*splits; + if (static_cast(map.get_plane(pnum).type) < 3) + value+=5; // axial is better + value -= epsilonbrush*1000; // avoid! + + // never split a hint side except with another hint + if (hintsplit && !(side.get_texinfo().flags.is_hint) ) + value = -9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) { + bestvalue = value; + bestside = &side; + bestsplits = splits; + for (auto &test : brushes) { + test->side = test->testside; + } + } + } + } + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) { + if (pass > 0) + node->detail_separator = true; // not needed for vis + break; + } + } + + // + // clear all the tested flags we set + // + for (auto &brush : brushes) { + for (auto &side : brush->sides) { + side.tested = false; + } + } + + return bestside; +} + /* ================== BrushMostlyOnSide @@ -536,42 +585,6 @@ planeside_t BrushMostlyOnSide(const bspbrush_t &brush, const qplane3d &plane) return side; } -/* -================== -BrushVolume - -================== -*/ -vec_t BrushVolume(const bspbrush_t &brush) -{ - // grab the first valid point as the corner - - bool found = false; - qvec3d corner; - for (auto &face : brush.sides) { - if (face.w.size() > 0) { - corner = face.w[0]; - found = true; - } - } - if (!found) { - return 0; - } - - // make tetrahedrons to all other faces - - vec_t volume = 0; - for (auto &face : brush.sides) { - auto plane = Face_Plane(&face); - vec_t d = -(qv::dot(corner, plane.normal) - plane.dist); - vec_t area = face.w.area(); - volume += d * area; - } - - volume /= 3; - return volume; -} - /* ================ SplitBrush @@ -582,8 +595,14 @@ input. https://github.com/id-Software/Quake-2-Tools/blob/master/bsp/qbsp3/brushbsp.c#L935 ================ */ -twosided> SplitBrush(std::unique_ptr brush, const qplane3d &split) +static twosided> SplitBrush(std::unique_ptr brush, int planenum) { + qplane3d split; + { + const auto lock = std::lock_guard(map_planes_lock); + split = map.planes.at(planenum); + } + twosided> result; // check all points @@ -717,7 +736,9 @@ twosided> SplitBrush(std::unique_ptr bru // (the face that is touching the plane) should have a normal opposite the plane's normal cs.planenum = FindPlane(brushOnFront ? -split : split, &cs.planeside); cs.texinfo = map.skip_texinfo; - + cs.visible = false; + cs.tested = false; + cs.onnode = true; // fixme-brushbsp: configure any other settings on the face? cs.w = brushOnFront ? midwinding.flip() : midwinding; @@ -741,204 +762,209 @@ twosided> SplitBrush(std::unique_ptr bru return result; } -//============================================================================ - /* -================== -DivideNodeBounds -================== +================ +SplitBrushList +================ */ -inline void DivideNodeBounds(node_t *node, const qbsp_plane_t &split) +static std::array>, 2> SplitBrushList(std::vector> brushes, const node_t *node) { - DivideBounds(node->bounds, split, node->children[0]->bounds, node->children[1]->bounds); -} + std::array>, 2> result; -static bool AllDetail(const std::vector> &brushes) -{ - for (auto &brush : brushes) { - if (!brush->contents.is_any_detail(options.target_game)) { - return false; + for (auto& brush : brushes) { + int sides = brush->side; + + if (sides == PSIDE_BOTH) { + // split into two brushes + auto [front, back] = SplitBrush(brush->copy_unique(), node->planenum); + if (front) + { + result[0].push_back(std::move(front)); + } + if (back) + { + result[1].push_back(std::move(back)); + } + continue; } - } - return true; -} -/* -================== -CreateLeaf - -Determines the contents of the leaf and creates the final list of -original faces that have some fragment inside this leaf. - -Called in parallel. -================== -*/ -static void CreateLeaf(std::vector> brushes, node_t *leafnode) -{ - leafnode->facelist.clear(); - leafnode->planenum = PLANENUM_LEAF; - - leafnode->contents = options.target_game->create_empty_contents(); - for (auto &brush : brushes) { - leafnode->contents = options.target_game->combine_contents(leafnode->contents, brush->contents); - } - for (auto &brush : brushes) { - Q_assert(brush->original != nullptr); - leafnode->original_brushes.push_back(brush->original); - } - - options.target_game->count_contents_in_stats(leafnode->contents, leafstats); -} - -/* -================== -PartitionBrushes - -Called in parallel. -================== -*/ -static void PartitionBrushes(std::vector> brushes, node_t *node) -{ - auto *split = const_cast(SelectPartition(brushes)); - - if (split == nullptr) { // this is a leaf node - node->planenum = PLANENUM_LEAF; - - CreateLeaf(std::move(brushes), node); - return; - } - - splitnodes++; - - node->children[0] = new node_t{}; - node->children[0]->parent = node; - node->children[1] = new node_t{}; - node->children[1]->parent = node; - node->planenum = FindPositivePlane(split->planenum); - - node->detail_separator = AllDetail(brushes); - - const qbsp_plane_t &splitplane = map.planes.at(node->planenum); - - DivideNodeBounds(node, splitplane); - - // multiple surfaces, so split all the polysurfaces into front and back lists - std::vector> frontlist, backlist; - - for (auto &brush : brushes) { - // NOTE: we're destroying `brushes` here with the std::move() - auto frags = SplitBrush(std::move(brush), splitplane); - - // mark faces which were used as a splitter - for (auto &brushMaybe : frags) { - if (brushMaybe) { - for (auto &face : brushMaybe->sides) { - if (face.planenum == split->planenum) { - face.onnode = true; - } + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if (sides & PSIDE_FACING) { + for (auto &side : brush->sides) { + if (side.planenum == node->planenum) { + side.onnode = true; } } } - if (frags.front) { - if (frags.front->sides.empty()) { - FError("Surface with no faces"); - } - frontlist.emplace_back(std::move(frags.front)); + if (sides & PSIDE_FRONT) { + result[0].push_back(std::move(brush)); + continue; } - if (frags.back) { - if (frags.back->sides.empty()) { - FError("Surface with no faces"); - } - backlist.emplace_back(std::move(frags.back)); + if (sides & PSIDE_BACK) { + result[1].push_back(std::move(brush)); + continue; } } + return result; +} + +/* +================== +BuildTree_r + +Called in parallel. +================== +*/ +static void BuildTree_r(node_t *node, std::vector> brushes, bspstats_t& stats) +{ + // find the best plane to use as a splitter + auto *bestside = const_cast(SelectSplitSide(brushes, node)); + if (!bestside) { + // this is a leaf node + node->side = nullptr; + node->planenum = PLANENUM_LEAF; + + stats.c_leafs++; + LeafNode(node, std::move(brushes), stats); + + return; + } + + // this is a splitplane node + stats.c_nodes++; + if (!bestside->visible) { + stats.c_nonvis++; + } + + node->side = bestside; + node->planenum = FindPositivePlane(bestside->planenum); // always use front facing + + auto children = SplitBrushList(std::move(brushes), node); + + // allocate children before recursing + for (int i = 0; i < 2; i++) + { + auto* newnode = new node_t{}; + newnode->parent = node; + node->children[i] = std::unique_ptr(newnode); + } + + auto children_volumes = SplitBrush(node->volume->copy_unique(), node->planenum); + node->children[0]->volume = std::move(children_volumes[0]); + node->children[1]->volume = std::move(children_volumes[1]); + + // recursively process children tbb::task_group g; - g.run([&]() { PartitionBrushes(std::move(frontlist), node->children[0]); }); - g.run([&]() { PartitionBrushes(std::move(backlist), node->children[1]); }); + g.run([&]() { BuildTree_r(node->children[0].get(), std::move(children[0]), stats); }); + g.run([&]() { BuildTree_r(node->children[1].get(), std::move(children[1]), stats); }); g.wait(); } /* ================== -SolidBSP +BrushBSP ================== */ -tree_t *BrushBSP(mapentity_t *entity, bool midsplit) +static tree_t *BrushBSP(mapentity_t *entity, std::vector> brushlist) { - tree_t *tree = new tree_t{}; + auto *tree = new tree_t{}; - if (entity->brushes.empty()) { + logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); + + size_t c_faces = 0; + size_t c_nonvisfaces = 0; + size_t c_brushes = 0; + for (const auto &b : brushlist) + { + c_brushes++; + + double volume = BrushVolume(*b); + if (volume < options.microvolume.value()) + { + logging::print("WARNING: microbrush"); + // fixme-brushbsp: add entitynum, brushnum in mapbrush_t +// printf ("WARNING: entity %i, brush %i: microbrush\n", +// b->original->entitynum, b->original->brushnum); + } + + for (side_t &side : b->sides) + { + if (side.bevel) + continue; + if (!side.w) + continue; + if (side.onnode) + continue; + if (side.visible) + c_faces++; + else + c_nonvisfaces++; + } + + tree->bounds += b->bounds; + } + + if (brushlist.empty()) { /* * We allow an entity to be constructed with no visible brushes * (i.e. all clip brushes), but need to construct a simple empty * collision hull for the engine. Probably could be done a little * smarter, but this works. */ - node_t *headnode = new node_t{}; + auto headnode = std::unique_ptr(new node_t{}); headnode->bounds = entity->bounds; - headnode->children[0] = new node_t{}; + headnode->children[0] = std::unique_ptr(new node_t{}); headnode->children[0]->planenum = PLANENUM_LEAF; headnode->children[0]->contents = options.target_game->create_empty_contents(); - headnode->children[0]->parent = headnode; - headnode->children[1] = new node_t{}; + headnode->children[0]->parent = headnode.get(); + headnode->children[1] = std::unique_ptr(new node_t{}); headnode->children[1]->planenum = PLANENUM_LEAF; headnode->children[1]->contents = options.target_game->create_empty_contents(); - headnode->children[1]->parent = headnode; + headnode->children[1]->parent = headnode.get(); - tree->headnode = headnode; tree->bounds = headnode->bounds; + tree->headnode = std::move(headnode); return tree; } - logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); + logging::print("{:5} brushes\n", c_brushes); + logging::print("{:5} visible faces\n", c_faces); + logging::print("{:5} nonvisible faces\n", c_nonvisfaces); - // Count visible/nonvisible brush sides (this is the side effect of FillOutside) - int visible_brush_sides = 0; - int invisible_brush_sides = 0; - for (const auto &brush : entity->brushes) { - for (auto &side : brush->sides) { - if (side.visible) { - ++visible_brush_sides; - } else { - ++invisible_brush_sides; - } - } - } - logging::print(logging::flag::STAT, " {:8} visible brush sides\n", visible_brush_sides); - logging::print(logging::flag::STAT, " {:8} invisible brush sides\n", invisible_brush_sides); + auto node = std::unique_ptr(new node_t{}); - node_t *headnode = new node_t{}; - usemidsplit = midsplit; + node->volume = BrushFromBounds(tree->bounds.grow(SIDESPACE)); - // calculate a bounding box for the entire model - headnode->bounds = entity->bounds; + tree->headnode = std::move(node); - // recursively partition everything - splitnodes = 0; - leafstats = options.target_game->create_content_stats(); - // count map surfaces; this is used when deciding to switch between midsplit and the expensive partitioning - mapbrushes = entity->brushes.size(); + bspstats_t stats{}; + stats.leafstats = options.target_game->create_content_stats(); + BuildTree_r(tree->headnode.get(), std::move(brushlist), stats); - // set the original pointers - std::vector> brushcopies; - for (const auto &original : entity->brushes) { - auto copy = std::make_unique(*original); - copy->original = original.get(); - brushcopies.push_back(std::move(copy)); - } - PartitionBrushes(std::move(brushcopies), headnode); - - //logging::percent(csgmergefaces, csgmergefaces, entity == map.pWorldEnt()); - - logging::print(logging::flag::STAT, " {:8} split nodes\n", splitnodes.load()); - options.target_game->print_content_stats(leafstats, "leaves"); - - tree->bounds = headnode->bounds; - tree->headnode = headnode; + logging::print("{:5} visible nodes\n", stats.c_nodes - stats.c_nonvis); + logging::print("{:5} nonvis nodes\n", stats.c_nonvis); + logging::print("{:5} leafs\n", stats.c_leafs); + + return tree; +} + +tree_t *BrushBSP(mapentity_t *entity, bool midsplit) +{ + // set the original pointers + std::vector> brushcopies; + for (const auto &original : entity->brushes) { + auto copy = original->copy_unique(); + copy->original = original.get(); + brushcopies.push_back(std::move(copy)); + } + + tree_t *tree = BrushBSP(entity, std::move(brushcopies)); return tree; } diff --git a/qbsp/exportobj.cc b/qbsp/exportobj.cc index 55447d49..834174fa 100644 --- a/qbsp/exportobj.cc +++ b/qbsp/exportobj.cc @@ -135,8 +135,8 @@ static void ExportObj_Nodes_r(const node_t *node, std::vector *d dest->push_back(face); } - ExportObj_Nodes_r(node->children[0], dest); - ExportObj_Nodes_r(node->children[1], dest); + ExportObj_Nodes_r(node->children[0].get(), dest); + ExportObj_Nodes_r(node->children[1].get(), dest); } void ExportObj_Nodes(const std::string &filesuffix, const node_t *nodes) @@ -149,8 +149,8 @@ void ExportObj_Nodes(const std::string &filesuffix, const node_t *nodes) static void ExportObj_Marksurfaces_r(const node_t *node, std::unordered_set *dest) { if (node->planenum != PLANENUM_LEAF) { - ExportObj_Marksurfaces_r(node->children[0], dest); - ExportObj_Marksurfaces_r(node->children[1], dest); + ExportObj_Marksurfaces_r(node->children[0].get(), dest); + ExportObj_Marksurfaces_r(node->children[1].get(), dest); return; } diff --git a/qbsp/faces.cc b/qbsp/faces.cc index a87cb864..90728dc0 100644 --- a/qbsp/faces.cc +++ b/qbsp/faces.cc @@ -185,8 +185,8 @@ static void FreeNode(node_t *node) void FreeNodes(node_t *node) { if (node->planenum != PLANENUM_LEAF) { - FreeNodes(node->children[0]); - FreeNodes(node->children[1]); + FreeNodes(node->children[0].get()); + FreeNodes(node->children[1].get()); } FreeNode(node); } @@ -369,9 +369,8 @@ static int MakeFaceEdges_r(mapentity_t *entity, node_t *node, int progress) FindFaceEdges(entity, f); } - logging::percent(progress, splitnodes, entity); - progress = MakeFaceEdges_r(entity, node->children[0], progress); - progress = MakeFaceEdges_r(entity, node->children[1], progress); + progress = MakeFaceEdges_r(entity, node->children[0].get(), progress); + progress = MakeFaceEdges_r(entity, node->children[1].get(), progress); return progress; } @@ -449,8 +448,8 @@ static void GrowNodeRegion(mapentity_t *entity, node_t *node) node->numfaces = static_cast(map.bsp.dfaces.size()) - node->firstface; - GrowNodeRegion(entity, node->children[0]); - GrowNodeRegion(entity, node->children[1]); + GrowNodeRegion(entity, node->children[0].get()); + GrowNodeRegion(entity, node->children[1].get()); } static void CountFace(mapentity_t *entity, face_t *f, size_t &facesCount, size_t &vertexesCount) @@ -479,8 +478,8 @@ static void CountData_r(mapentity_t *entity, node_t *node, size_t &facesCount, s CountFace(entity, f, facesCount, vertexesCount); } - CountData_r(entity, node->children[0], facesCount, vertexesCount); - CountData_r(entity, node->children[1], facesCount, vertexesCount); + CountData_r(entity, node->children[0].get(), facesCount, vertexesCount); + CountData_r(entity, node->children[1].get(), facesCount, vertexesCount); } /* @@ -505,7 +504,6 @@ int MakeFaceEdges(mapentity_t *entity, node_t *headnode) firstface = static_cast(map.bsp.dfaces.size()); MakeFaceEdges_r(entity, headnode, 0); - logging::percent(splitnodes, splitnodes, entity == map.world_entity()); pEdgeFaces0.clear(); pEdgeFaces1.clear(); @@ -536,14 +534,15 @@ static void AddMarksurfaces_r(face_t *face, face_t *face_copy, node_t *node) return; } + const auto lock = std::lock_guard(map_planes_lock); const qbsp_plane_t &splitplane = map.planes.at(node->planenum); auto [frontFragment, backFragment] = SplitFace(face_copy, splitplane); if (frontFragment) { - AddMarksurfaces_r(face, frontFragment, node->children[0]); + AddMarksurfaces_r(face, frontFragment, node->children[0].get()); } if (backFragment) { - AddMarksurfaces_r(face, backFragment, node->children[1]); + AddMarksurfaces_r(face, backFragment, node->children[1].get()); } } @@ -568,15 +567,15 @@ void MakeMarkFaces(mapentity_t* entity, node_t* node) face_t *face_copy = CopyFace(face); if (face->planeside == 0) { - AddMarksurfaces_r(face, face_copy, node->children[0]); + AddMarksurfaces_r(face, face_copy, node->children[0].get()); } else { - AddMarksurfaces_r(face, face_copy, node->children[1]); + AddMarksurfaces_r(face, face_copy, node->children[1].get()); } } // process child nodes recursively - MakeMarkFaces(entity, node->children[0]); - MakeMarkFaces(entity, node->children[1]); + MakeMarkFaces(entity, node->children[0].get()); + MakeMarkFaces(entity, node->children[1].get()); } struct makefaces_stats_t { @@ -663,8 +662,8 @@ static void MakeFaces_r(node_t *node, makefaces_stats_t& stats) // recurse down to leafs if (node->planenum != PLANENUM_LEAF) { - MakeFaces_r(node->children[0], stats); - MakeFaces_r(node->children[1], stats); + MakeFaces_r(node->children[0].get(), stats); + MakeFaces_r(node->children[1].get(), stats); // merge together all visible faces on the node if (!options.nomerge.value()) diff --git a/qbsp/map.cc b/qbsp/map.cc index 39119699..fed2cb13 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -42,6 +42,7 @@ #include mapdata_t map; +std::recursive_mutex map_planes_lock; const std::optional &mapdata_t::load_image_meta(const std::string_view &name) { @@ -2372,6 +2373,8 @@ from q3map */ void WriteBspBrushMap(const fs::path &name, const std::vector> &list) { + const auto lock = std::lock_guard(map_planes_lock); + logging::print("writing {}\n", name); std::ofstream f(name); diff --git a/qbsp/merge.cc b/qbsp/merge.cc index 82341b56..a195579b 100644 --- a/qbsp/merge.cc +++ b/qbsp/merge.cc @@ -99,6 +99,7 @@ static face_t *TryMerge(face_t *f1, face_t *f2) // check slope of connected lines // if the slopes are colinear, the point can be removed + const auto lock = std::lock_guard(map_planes_lock); const auto &plane = map.planes.at(f1->planenum); planenormal = plane.normal; if (f1->planeside) @@ -211,8 +212,8 @@ static void CollectNodes_R(node_t *node, std::vector &allnodes) return; } - CollectNodes_R(node->children[0], allnodes); - CollectNodes_R(node->children[1], allnodes); + CollectNodes_R(node->children[0].get(), allnodes); + CollectNodes_R(node->children[1].get(), allnodes); } /* diff --git a/qbsp/outside.cc b/qbsp/outside.cc index aab2bac7..96cd775e 100644 --- a/qbsp/outside.cc +++ b/qbsp/outside.cc @@ -58,20 +58,21 @@ static node_t *PointInLeaf(node_t *node, const qvec3d &point) return node; } + const auto lock = std::lock_guard(map_planes_lock); const auto &plane = map.planes.at(node->planenum); vec_t dist = plane.distance_to(point); if (dist > 0) { // point is on the front of the node plane - return PointInLeaf(node->children[0], point); + return PointInLeaf(node->children[0].get(), point); } else if (dist < 0) { // point is on the back of the node plane - return PointInLeaf(node->children[1], point); + return PointInLeaf(node->children[1].get(), point); } else { // point is exactly on the node plane - node_t *front = PointInLeaf(node->children[0], point); - node_t *back = PointInLeaf(node->children[1], point); + node_t *front = PointInLeaf(node->children[0].get(), point); + node_t *back = PointInLeaf(node->children[1].get(), point); // prefer the opaque one if (LeafSealsMap(front)) { @@ -89,8 +90,8 @@ static void ClearOccupied_r(node_t *node) node->occupant = nullptr; if (node->planenum != PLANENUM_LEAF) { - ClearOccupied_r(node->children[0]); - ClearOccupied_r(node->children[1]); + ClearOccupied_r(node->children[0].get()); + ClearOccupied_r(node->children[1].get()); } } @@ -117,8 +118,8 @@ static void MarkClusterOutsideDistance_R(node_t* node, int outside_distance) node->outside_distance = outside_distance; if (node->planenum != PLANENUM_LEAF) { - MarkClusterOutsideDistance_R(node->children[0], outside_distance); - MarkClusterOutsideDistance_R(node->children[1], outside_distance); + MarkClusterOutsideDistance_R(node->children[0].get(), outside_distance); + MarkClusterOutsideDistance_R(node->children[1].get(), outside_distance); } } @@ -328,8 +329,8 @@ static void FindOccupiedClusters_R(node_t *node, std::vector& result) } if (node->planenum != PLANENUM_LEAF) { - FindOccupiedClusters_R(node->children[0], result); - FindOccupiedClusters_R(node->children[1], result); + FindOccupiedClusters_R(node->children[0].get(), result); + FindOccupiedClusters_R(node->children[1].get(), result); } } @@ -362,8 +363,8 @@ static void MarkAllBrushSidesVisible_R(node_t *node) { // descend to leafs if (node->planenum != PLANENUM_LEAF) { - MarkAllBrushSidesVisible_R(node->children[0]); - MarkAllBrushSidesVisible_R(node->children[1]); + MarkAllBrushSidesVisible_R(node->children[0].get()); + MarkAllBrushSidesVisible_R(node->children[1].get()); return; } @@ -385,8 +386,8 @@ static void MarkVisibleBrushSides_R(node_t *node) { // descent to leafs if (!(node->planenum == PLANENUM_LEAF)) { - MarkVisibleBrushSides_R(node->children[0]); - MarkVisibleBrushSides_R(node->children[1]); + MarkVisibleBrushSides_R(node->children[0].get()); + MarkVisibleBrushSides_R(node->children[1].get()); return; } @@ -429,8 +430,8 @@ static void MarkVisibleBrushSides_R(node_t *node) static void OutLeafsToSolid_r(node_t *node, int *outleafs_count, settings::filltype_t filltype) { if (node->planenum != PLANENUM_LEAF) { - OutLeafsToSolid_r(node->children[0], outleafs_count, filltype); - OutLeafsToSolid_r(node->children[1], outleafs_count, filltype); + OutLeafsToSolid_r(node->children[0].get(), outleafs_count, filltype); + OutLeafsToSolid_r(node->children[1].get(), outleafs_count, filltype); return; } @@ -467,8 +468,8 @@ static int OutLeafsToSolid(node_t *node, settings::filltype_t filltype) static void SetOccupied_R(node_t *node, int dist) { if (node->planenum != PLANENUM_LEAF) { - SetOccupied_R(node->children[0], dist); - SetOccupied_R(node->children[1], dist); + SetOccupied_R(node->children[0].get(), dist); + SetOccupied_R(node->children[1].get(), dist); } node->occupied = dist; @@ -596,7 +597,7 @@ Special cases: structural fully covered by detail still needs to be marked "visi */ bool FillOutside(mapentity_t *entity, tree_t *tree, const int hullnum) { - node_t *node = tree->headnode; + node_t *node = tree->headnode.get(); logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); @@ -716,9 +717,9 @@ void FillBrushEntity(mapentity_t* entity, tree_t *tree, const int hullnum) logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); // Clear the outside filling state on all nodes - ClearOccupied_r(tree->headnode); + ClearOccupied_r(tree->headnode.get()); MarkBrushSidesInvisible(entity); - MarkVisibleBrushSides_R(tree->headnode); + MarkVisibleBrushSides_R(tree->headnode.get()); } diff --git a/qbsp/portals.cc b/qbsp/portals.cc index 509790b8..b8a3e5b7 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -39,7 +39,7 @@ contentflags_t ClusterContents(const node_t *node) return node->contents; return options.target_game->cluster_contents( - ClusterContents(node->children[0]), ClusterContents(node->children[1])); + ClusterContents(node->children[0].get()), ClusterContents(node->children[1].get())); } /* @@ -193,9 +193,9 @@ void MakeHeadnodePortals(tree_t *tree) p->winding = BaseWindingForPlane(pl); if (side) - AddPortalToNodes(p, &tree->outside_node, tree->headnode); + AddPortalToNodes(p, &tree->outside_node, tree->headnode.get()); else - AddPortalToNodes(p, tree->headnode, &tree->outside_node); + AddPortalToNodes(p, tree->headnode.get(), &tree->outside_node); } // clip the basewindings by all the other planes @@ -222,6 +222,7 @@ Creates a winding from the given node plane, clipped by all parent nodes. std::optional BaseWindingForNode(node_t *node) { + const auto lock = std::lock_guard(map_planes_lock); auto plane = map.planes.at(node->planenum); std::optional w = BaseWindingForPlane(plane); @@ -231,7 +232,7 @@ std::optional BaseWindingForNode(node_t *node) { plane = map.planes.at(np->planenum); - const planeside_t keep = (np->children[0] == node) ? + const planeside_t keep = (np->children[0].get() == node) ? SIDE_FRONT : SIDE_BACK; w = w->clip(plane, BASE_WINDING_EPSILON, false)[keep]; @@ -254,6 +255,8 @@ portals in the node. */ void MakeNodePortal(node_t *node, portalstats_t &stats) { + const auto lock = std::lock_guard(map_planes_lock); + auto w = BaseWindingForNode(node); // clip the portal by all the other portals in the node @@ -291,7 +294,7 @@ void MakeNodePortal(node_t *node, portalstats_t &stats) new_portal->planenum = node->planenum; new_portal->onnode = node; new_portal->winding = w; - AddPortalToNodes(new_portal, node->children[0], node->children[1]); + AddPortalToNodes(new_portal, node->children[0].get(), node->children[1].get()); } /* @@ -304,9 +307,11 @@ children have portals instead of node. */ void SplitNodePortals(node_t *node, portalstats_t &stats) { + const auto lock = std::lock_guard(map_planes_lock); + const auto plane = map.planes.at(node->planenum); - node_t *f = node->children[0]; - node_t *b = node->children[1]; + node_t *f = node->children[0].get(); + node_t *b = node->children[1].get(); portal_t *next_portal = nullptr; for (portal_t *p = node->portals; p ; p = next_portal) @@ -433,8 +438,8 @@ void MakeTreePortals_r(node_t *node, portalstats_t &stats) MakeNodePortal(node, stats); SplitNodePortals(node, stats); - MakeTreePortals_r(node->children[0], stats); - MakeTreePortals_r(node->children[1], stats); + MakeTreePortals_r(node->children[0].get(), stats); + MakeTreePortals_r(node->children[1].get(), stats); } /* @@ -444,14 +449,14 @@ MakeTreePortals */ void MakeTreePortals(tree_t *tree) { - FreeTreePortals_r(tree->headnode); + FreeTreePortals_r(tree->headnode.get()); - AssertNoPortals(tree->headnode); + AssertNoPortals(tree->headnode.get()); portalstats_t stats{}; MakeHeadnodePortals(tree); - MakeTreePortals_r(tree->headnode, stats); + MakeTreePortals_r(tree->headnode.get(), stats); } void AssertNoPortals(node_t *node) @@ -459,8 +464,8 @@ void AssertNoPortals(node_t *node) Q_assert(!node->portals); if (node->planenum != PLANENUM_LEAF) { - AssertNoPortals(node->children[0]); - AssertNoPortals(node->children[1]); + AssertNoPortals(node->children[0].get()); + AssertNoPortals(node->children[1].get()); } } @@ -475,8 +480,8 @@ void FreeTreePortals_r(node_t *node) portal_t *p, *nextp; if (node->planenum != PLANENUM_LEAF) { - FreeTreePortals_r(node->children[0]); - FreeTreePortals_r(node->children[1]); + FreeTreePortals_r(node->children[0].get()); + FreeTreePortals_r(node->children[1].get()); } for (p = node->portals; p; p = nextp) { @@ -504,8 +509,8 @@ static void ApplyArea_r(node_t *node) node->area = map.c_areas; if (node->planenum != PLANENUM_LEAF) { - ApplyArea_r(node->children[0]); - ApplyArea_r(node->children[1]); + ApplyArea_r(node->children[0].get()); + ApplyArea_r(node->children[1].get()); } } @@ -513,10 +518,10 @@ static mapentity_t *AreanodeEntityForLeaf(node_t *node) { // if detail cluster, search the children recursively if (node->planenum != PLANENUM_LEAF) { - if (auto *child0result = AreanodeEntityForLeaf(node->children[0]); child0result) { + if (auto *child0result = AreanodeEntityForLeaf(node->children[0].get()); child0result) { return child0result; } - return AreanodeEntityForLeaf(node->children[1]); + return AreanodeEntityForLeaf(node->children[1].get()); } for (auto &brush : node->original_brushes) { @@ -629,8 +634,8 @@ area set, flood fill out from there static void SetAreaPortalAreas_r(node_t *node) { if (node->planenum != PLANENUM_LEAF) { - SetAreaPortalAreas_r(node->children[0]); - SetAreaPortalAreas_r(node->children[1]); + SetAreaPortalAreas_r(node->children[0].get()); + SetAreaPortalAreas_r(node->children[1].get()); return; } @@ -735,6 +740,8 @@ Finds a brush side to use for texturing the given portal */ static void FindPortalSide(portal_t *p) { + const auto lock = std::lock_guard(map_planes_lock); + // decide which content change is strongest // solid > lava > water, etc contentflags_t viscontents = options.target_game->visible_contents(p->nodes[0]->contents, p->nodes[1]->contents); @@ -799,8 +806,8 @@ static void MarkVisibleSides_r(node_t *node) { if (node->planenum != PLANENUM_LEAF) { - MarkVisibleSides_r(node->children[0]); - MarkVisibleSides_r(node->children[1]); + MarkVisibleSides_r(node->children[0].get()); + MarkVisibleSides_r(node->children[1].get()); return; } @@ -840,5 +847,5 @@ void MarkVisibleSides(tree_t *tree, mapentity_t* entity) } // set visible flags on the sides that are used by portals - MarkVisibleSides_r (tree->headnode); + MarkVisibleSides_r(tree->headnode.get()); } diff --git a/qbsp/prtfile.cc b/qbsp/prtfile.cc index ac181159..5aef2c4f 100644 --- a/qbsp/prtfile.cc +++ b/qbsp/prtfile.cc @@ -48,14 +48,16 @@ static void WriteFloat(std::ofstream &portalFile, vec_t v) static void WritePortals_r(node_t *node, std::ofstream &portalFile, bool clusters) { + const auto lock = std::lock_guard(map_planes_lock); + const portal_t *p, *next; std::optional w; int i, front, back; qplane3d plane2; if (node->planenum != PLANENUM_LEAF && !node->detail_separator) { - WritePortals_r(node->children[0], portalFile, clusters); - WritePortals_r(node->children[1], portalFile, clusters); + WritePortals_r(node->children[0].get(), portalFile, clusters); + WritePortals_r(node->children[1].get(), portalFile, clusters); return; } if (node->contents.is_solid(options.target_game)) @@ -101,8 +103,8 @@ static void WritePortals_r(node_t *node, std::ofstream &portalFile, bool cluster static int WriteClusters_r(node_t *node, std::ofstream &portalFile, int viscluster) { if (node->planenum != PLANENUM_LEAF) { - viscluster = WriteClusters_r(node->children[0], portalFile, viscluster); - viscluster = WriteClusters_r(node->children[1], portalFile, viscluster); + viscluster = WriteClusters_r(node->children[0].get(), portalFile, viscluster); + viscluster = WriteClusters_r(node->children[1].get(), portalFile, viscluster); return viscluster; } if (node->contents.is_solid(options.target_game)) @@ -167,8 +169,8 @@ static void NumberLeafs_r(node_t *node, portal_state_t *state, int cluster) node->viscluster = cluster; CountPortals(node, state); } - NumberLeafs_r(node->children[0], state, cluster); - NumberLeafs_r(node->children[1], state, cluster); + NumberLeafs_r(node->children[0].get(), state, cluster); + NumberLeafs_r(node->children[1].get(), state, cluster); return; } @@ -264,8 +266,8 @@ void CreateVisPortals_r(node_t *node, portalstats_t &stats) MakeNodePortal(node, stats); SplitNodePortals(node, stats); - CreateVisPortals_r(node->children[0], stats); - CreateVisPortals_r(node->children[1], stats); + CreateVisPortals_r(node->children[0].get(), stats); + CreateVisPortals_r(node->children[1].get(), stats); } /* @@ -279,16 +281,16 @@ void WritePortalFile(tree_t *tree) portal_state_t state{}; - FreeTreePortals_r(tree->headnode); + FreeTreePortals_r(tree->headnode.get()); - AssertNoPortals(tree->headnode); + AssertNoPortals(tree->headnode.get()); MakeHeadnodePortals(tree); portalstats_t stats{}; - CreateVisPortals_r(tree->headnode, stats); + CreateVisPortals_r(tree->headnode.get(), stats); /* save portal file for vis tracing */ - WritePortalfile(tree->headnode, &state); + WritePortalfile(tree->headnode.get(), &state); logging::print(logging::flag::STAT, " {:8} vis leafs\n", state.num_visleafs); logging::print(logging::flag::STAT, " {:8} vis clusters\n", state.num_visclusters); diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 7306c2ee..5fd6d50d 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -40,6 +40,7 @@ #include #include #include +#include #include @@ -283,8 +284,8 @@ static void ExportBrushList_r(const mapentity_t *entity, node_t *node) return; } - ExportBrushList_r(entity, node->children[0]); - ExportBrushList_r(entity, node->children[1]); + ExportBrushList_r(entity, node->children[0].get()); + ExportBrushList_r(entity, node->children[1].get()); } /* @@ -297,6 +298,8 @@ against axial bounding boxes */ static std::vector> AddBrushBevels(const bspbrush_t &b) { + const auto lock = std::lock_guard(map_planes_lock); + // add already-present planes std::vector> planes; @@ -579,11 +582,11 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) // fill again so PruneNodes works MakeTreePortals(tree); FillOutside(entity, tree, hullnum); - PruneNodes(tree->headnode); - DetailToSolid(tree->headnode); + PruneNodes(tree->headnode.get()); + DetailToSolid(tree->headnode.get()); } } - ExportClipNodes(entity, tree->headnode, hullnum); + ExportClipNodes(entity, tree->headnode.get(), hullnum); // fixme-brushbsp: return here? } else { @@ -628,8 +631,8 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) // Area portals if (options.target_game->id == GAME_QUAKE_II) { - FloodAreas(entity, tree->headnode); - EmitAreaPortals(tree->headnode); + FloodAreas(entity, tree->headnode.get()); + EmitAreaPortals(tree->headnode.get()); } } else { FillBrushEntity(entity, tree, hullnum); @@ -641,39 +644,39 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) MakeTreePortals(tree); MarkVisibleSides(tree, entity); - MakeFaces(tree->headnode); + MakeFaces(tree->headnode.get()); - FreeTreePortals_r(tree->headnode); - PruneNodes(tree->headnode); + FreeTreePortals_r(tree->headnode.get()); + PruneNodes(tree->headnode.get()); if (hullnum <= 0 && entity == map.world_entity() && (!map.leakfile || options.keepprt.value())) { WritePortalFile(tree); } // needs to come after any face creation - MakeMarkFaces(entity, tree->headnode); + MakeMarkFaces(entity, tree->headnode.get()); // convert detail leafs to solid (in case we didn't make the call above) - DetailToSolid(tree->headnode); + DetailToSolid(tree->headnode.get()); // fixme-brushbsp: prune nodes if (!options.notjunc.value()) { - TJunc(entity, tree->headnode); + TJunc(entity, tree->headnode.get()); } if (options.objexport.value() && entity == map.world_entity()) { - ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode); - ExportObj_Marksurfaces("pre_makefaceedges_marksurfaces", tree->headnode); + ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode.get()); + ExportObj_Marksurfaces("pre_makefaceedges_marksurfaces", tree->headnode.get()); } - firstface = MakeFaceEdges(entity, tree->headnode); + firstface = MakeFaceEdges(entity, tree->headnode.get()); if (options.target_game->id == GAME_QUAKE_II) { - ExportBrushList(entity, tree->headnode); + ExportBrushList(entity, tree->headnode.get()); } - ExportDrawNodes(entity, tree->headnode, firstface); + ExportDrawNodes(entity, tree->headnode.get(), firstface); } FreeBrushes(entity); @@ -763,6 +766,8 @@ hull sizes static void BSPX_Brushes_AddModel( struct bspxbrushes_s *ctx, int modelnum, std::vector> &brushes) { + const auto lock = std::lock_guard(map_planes_lock); + bspxbrushes_permodel permodel{1, modelnum}; for (auto &b : brushes) { diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index 03e1edd0..02456039 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -870,7 +871,19 @@ TEST_CASE("merge", "[testmaps_q1]") const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_merge.map"); REQUIRE_FALSE(prt.has_value()); - REQUIRE(6 == bsp.dfaces.size()); + REQUIRE(bsp.dfaces.size() >= 6); + + // BrushBSP does a split through the middle first to keep the BSP balanced, which prevents + // two of the side face from being merged + REQUIRE(bsp.dfaces.size() <= 8); + + const auto exp_bounds = aabb3d{{48,0,96}, {224,96,96}}; + + auto* top_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {48,0,96}, {0,0,1}); + const auto top_winding = Face_Winding(&bsp, top_face); + + CHECK(top_winding.bounds().mins() == exp_bounds.mins()); + CHECK(top_winding.bounds().maxs() == exp_bounds.maxs()); } TEST_CASE("tjunc_many_sided_face", "[testmaps_q1]") @@ -1677,3 +1690,37 @@ TEST_CASE("winding", "[benchmark][.releaseonly]") { ankerl::nanobench::doNotOptimizeAway(temp); }); } + +TEST_CASE("BrushFromBounds") { + map.reset(); + options.reset(); + options.worldextent.setValue(1024); + + auto brush = BrushFromBounds({{2,2,2}, {32, 32, 32}}); + + CHECK(brush->sides.size() == 6); + + const auto top_winding = winding_t{{2, 2, 32}, {2, 32, 32}, {32, 32, 32}, {32, 2, 32}}; + const auto bottom_winding = winding_t{{32, 2, 2},{32, 32, 2}, {2, 32, 2}, {2, 2, 2}}; + + int found = 0; + + for (auto &side : brush->sides) { + CHECK(side.w); + + if (side.w.directional_equal(top_winding)) { + found++; + auto plane = Face_Plane(&side); + CHECK(plane.normal == qvec3d{0,0,1}); + CHECK(plane.dist == 32); + } + + if (side.w.directional_equal(bottom_winding)) { + found++; + auto plane = Face_Plane(&side); + CHECK(plane.normal == qvec3d{0,0,-1}); + CHECK(plane.dist == -2); + } + } + CHECK(found == 2); +} diff --git a/qbsp/tjunc.cc b/qbsp/tjunc.cc index 05ac55dd..973293a9 100644 --- a/qbsp/tjunc.cc +++ b/qbsp/tjunc.cc @@ -370,8 +370,8 @@ static void tjunc_count_r(node_t *node) cWVerts += f->w.size(); } - tjunc_count_r(node->children[0]); - tjunc_count_r(node->children[1]); + tjunc_count_r(node->children[0].get()); + tjunc_count_r(node->children[1].get()); } static void tjunc_find_r(node_t *node) @@ -383,8 +383,8 @@ static void tjunc_find_r(node_t *node) AddFaceEdges(f); } - tjunc_find_r(node->children[0]); - tjunc_find_r(node->children[1]); + tjunc_find_r(node->children[0].get()); + tjunc_find_r(node->children[1].get()); } static void tjunc_fix_r(node_t *node) @@ -396,8 +396,8 @@ static void tjunc_fix_r(node_t *node) FixFaceEdges(face); } - tjunc_fix_r(node->children[0]); - tjunc_fix_r(node->children[1]); + tjunc_fix_r(node->children[0].get()); + tjunc_fix_r(node->children[1].get()); } /* diff --git a/qbsp/tree.cc b/qbsp/tree.cc index bb33e93e..ffa27eb5 100644 --- a/qbsp/tree.cc +++ b/qbsp/tree.cc @@ -23,6 +23,7 @@ #include #include +#include //============================================================================ @@ -37,7 +38,6 @@ static void ConvertNodeToLeaf(node_t *node, const contentflags_t &contents) node->planenum = PLANENUM_LEAF; for (int i = 0; i < 2; ++i) { - delete node->children[i]; node->children[i] = nullptr; } for (auto *face : node->facelist) { @@ -68,8 +68,8 @@ void DetailToSolid(node_t *node) */ return; } else { - DetailToSolid(node->children[0]); - DetailToSolid(node->children[1]); + DetailToSolid(node->children[0].get()); + DetailToSolid(node->children[1].get()); // If both children are solid, we can merge the two leafs into one. // DarkPlaces has an assertion that fails if both children are @@ -89,8 +89,8 @@ static void PruneNodes_R(node_t *node, int &count_pruned) return; } - PruneNodes_R(node->children[0], count_pruned); - PruneNodes_R(node->children[1], count_pruned); + PruneNodes_R(node->children[0].get(), count_pruned); + PruneNodes_R(node->children[1].get(), count_pruned); if (node->children[0]->planenum == PLANENUM_LEAF && node->children[0]->contents.is_solid(options.target_game) && node->children[1]->planenum == PLANENUM_LEAF && node->children[1]->contents.is_solid(options.target_game)) { diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index d14c0f19..9d6d4ce6 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -37,6 +37,7 @@ using nlohmann::json; */ size_t ExportMapPlane(size_t planenum) { + const auto lock = std::lock_guard(map_planes_lock); qbsp_plane_t &plane = map.planes.at(planenum); if (plane.outputplanenum.has_value()) @@ -106,8 +107,8 @@ static size_t ExportClipNodes(mapentity_t *entity, node_t *node) const size_t nodenum = map.bsp.dclipnodes.size(); map.bsp.dclipnodes.emplace_back(); - const int child0 = ExportClipNodes(entity, node->children[0]); - const int child1 = ExportClipNodes(entity, node->children[1]); + const int child0 = ExportClipNodes(entity, node->children[0].get()); + const int child1 = ExportClipNodes(entity, node->children[1].get()); // Careful not to modify the vector while using this clipnode pointer bsp2_dclipnode_t &clipnode = map.bsp.dclipnodes[nodenum]; @@ -222,12 +223,12 @@ static void ExportDrawNodes(mapentity_t *entity, node_t *node) int32_t nextLeafIndex = static_cast(map.bsp.dleafs.size()); const int32_t childnum = -(nextLeafIndex + 1); dnode->children[i] = childnum; - ExportLeaf(entity, node->children[i]); + ExportLeaf(entity, node->children[i].get()); } } else { const int32_t childnum = static_cast(map.bsp.dnodes.size()); dnode->children[i] = childnum; - ExportDrawNodes(entity, node->children[i]); + ExportDrawNodes(entity, node->children[i].get()); // Important: our dnode pointer may be invalid after the recursive call, if the vector got resized. // So re-set the pointer.