From f7b8f85ecea01af04f8689fd6dc4ecedb72ac8cc Mon Sep 17 00:00:00 2001 From: Jonathan Date: Mon, 15 Aug 2022 06:13:30 -0400 Subject: [PATCH] use an optional to store hull number, which gets rid of special -1 collision hull number. fixed no-hull always chopping even with chop off --- common/bsputils.cc | 6 +- include/common/bspfile.hh | 3 + include/common/bsputils.hh | 2 +- include/common/qvec.hh | 2 + include/qbsp/brush.hh | 2 +- include/qbsp/map.hh | 9 +- include/qbsp/outside.hh | 4 +- include/qbsp/qbsp.hh | 2 +- qbsp/brush.cc | 48 +++++----- qbsp/map.cc | 6 +- qbsp/outside.cc | 6 +- qbsp/qbsp.cc | 181 +++++++++++++++++++------------------ qbsp/writebsp.cc | 6 +- tests/test_qbsp.cc | 2 +- 14 files changed, 146 insertions(+), 133 deletions(-) diff --git a/common/bsputils.cc b/common/bsputils.cc index a68d419f..b89cd10e 100644 --- a/common/bsputils.cc +++ b/common/bsputils.cc @@ -464,12 +464,12 @@ static int BSP_FindClipnodeAtPoint_r(const mbsp_t *bsp, const int clipnodenum, c } } -int BSP_FindContentsAtPoint(const mbsp_t *bsp, int hull, const dmodelh2_t *model, const qvec3d &point) +int BSP_FindContentsAtPoint(const mbsp_t *bsp, hull_index_t hullnum, const dmodelh2_t *model, const qvec3d &point) { - if (hull == 0) { + if (!hullnum.value_or(0)) { return BSP_FindLeafAtPoint_r(bsp, model->headnode[0], point)->contents; } - return BSP_FindClipnodeAtPoint_r(bsp, model->headnode.at(hull), point); + return BSP_FindClipnodeAtPoint_r(bsp, model->headnode.at(hullnum.value()), point); } std::vector Leaf_Markfaces(const mbsp_t *bsp, const mleaf_t *leaf) diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 686b7fa0..99e52a77 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -421,6 +421,9 @@ struct fmt::formatter> : formatter> using texvecf = texvec; +// type to store a hull index; max 256 hulls, zero is valid. +using hull_index_t = std::optional; + #include "bspfile_generic.hh" #include "bspfile_q1.hh" #include "bspfile_q2.hh" diff --git a/include/common/bsputils.hh b/include/common/bsputils.hh index cffc564a..e71d8bb1 100644 --- a/include/common/bsputils.hh +++ b/include/common/bsputils.hh @@ -71,7 +71,7 @@ const bsp2_dnode_t *BSP_FindNodeAtPoint( const mbsp_t *bsp, const dmodelh2_t *model, const qvec3d &point, const qvec3d &wanted_normal); const mleaf_t *BSP_FindLeafAtPoint(const mbsp_t *bsp, const dmodelh2_t *model, const qvec3d &point); -int BSP_FindContentsAtPoint(const mbsp_t *bsp, int hull, const dmodelh2_t *model, const qvec3d &point); +int BSP_FindContentsAtPoint(const mbsp_t *bsp, hull_index_t hullnum, const dmodelh2_t *model, const qvec3d &point); std::vector Leaf_Markfaces(const mbsp_t *bsp, const mleaf_t *leaf); std::vector Leaf_Brushes(const mbsp_t *bsp, const mleaf_t *leaf); diff --git a/include/common/qvec.hh b/include/common/qvec.hh index f2d2dcea..e34e91d4 100644 --- a/include/common/qvec.hh +++ b/include/common/qvec.hh @@ -595,12 +595,14 @@ std::tuple, qvec> MakeTangentAndBitangentUnnormalized(const qve } // debug test +#ifdef PARANOID if (0) { auto n = qv::normalize(qv::cross(tangent, bitangent)); double d = qv::distance(n, normal); assert(d < 0.0001); } +#endif return {tangent, bitangent}; } diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index 7ea6dcdd..0c2c71e8 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -93,5 +93,5 @@ struct bspbrush_t bspbrush_t clone() const; }; -std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush, const contentflags_t &contents, const int hullnum, std::optional> num_clipped); +std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush, const contentflags_t &contents, hull_index_t hullnum, std::optional> num_clipped); bool CreateBrushWindings(bspbrush_t *brush); \ No newline at end of file diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index cfe622d7..333e4488 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -139,7 +139,7 @@ struct maptexdata_t surfflags_t flags; int32_t value; std::string animation; - int32_t animation_miptex = -1; + std::optional animation_miptex = std::nullopt; }; #include @@ -342,18 +342,15 @@ void WriteEntitiesToString(); qvec3d FixRotateOrigin(mapentity_t *entity); -/** Special ID for the collision-only hull; used for wrbrushes/Q2 */ -constexpr int HULL_COLLISION = -1; - /* Create BSP brushes from map brushes */ -void Brush_LoadEntity(mapentity_t *entity, const int hullnum, bspbrush_t::container &brushes, size_t &num_clipped); +void Brush_LoadEntity(mapentity_t *entity, hull_index_t hullnum, bspbrush_t::container &brushes, size_t &num_clipped); std::list CSGFace( face_t *srcface, const mapentity_t *srcentity, const bspbrush_t *srcbrush, const node_t *srcnode); void TJunc(node_t *headnode); int MakeFaceEdges(node_t *headnode); void EmitVertices(node_t *headnode); -void ExportClipNodes(mapentity_t *entity, node_t *headnode, const int hullnum); +void ExportClipNodes(mapentity_t *entity, node_t *headnode, hull_index_t::value_type hullnum); void ExportDrawNodes(mapentity_t *entity, node_t *headnode, int firstface); struct bspxbrushes_s diff --git a/include/qbsp/outside.hh b/include/qbsp/outside.hh index e49b67e9..30b92174 100644 --- a/include/qbsp/outside.hh +++ b/include/qbsp/outside.hh @@ -28,7 +28,7 @@ class mapentity_t; struct node_t; struct tree_t; -bool FillOutside(mapentity_t *entity, tree_t *tree, const int hullnum, bspbrush_t::container &brushes); +bool FillOutside(mapentity_t *entity, tree_t *tree, hull_index_t hullnum, bspbrush_t::container &brushes); std::vector FindOccupiedClusters(node_t *headnode); -void FillBrushEntity(mapentity_t *entity, tree_t *tree, const int hullnum, bspbrush_t::container &brushes); +void FillBrushEntity(mapentity_t *entity, tree_t *tree, hull_index_t hullnum, bspbrush_t::container &brushes); diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index cda5b039..7b1a2580 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -407,7 +407,7 @@ struct maptexinfo_t int32_t miptex = 0; surfflags_t flags = {}; int32_t value = 0; // Q2-specific - int32_t next = -1; // Q2-specific + std::optional next = std::nullopt; // Q2-specific std::optional outputnum = std::nullopt; // nullopt until added to bsp constexpr auto as_tuple() const { return std::tie(vecs, miptex, flags, value, next); } diff --git a/qbsp/brush.cc b/qbsp/brush.cc index bafc954f..f88a9db5 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -284,7 +284,7 @@ Converts a mapbrush to a bsp brush =============== */ std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush, const contentflags_t &contents, - const int hullnum, std::optional> num_clipped) + hull_index_t hullnum, std::optional> num_clipped) { // create the brush bspbrush_t brush{}; @@ -295,7 +295,7 @@ std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush for (size_t i = 0; i < mapbrush->faces.size(); i++) { auto &src = mapbrush->faces[i]; - if (hullnum <= 0 && mapbrush->is_hint) { + if (!hullnum.value_or(0) && mapbrush->is_hint) { /* Don't generate hintskip faces */ const maptexinfo_t &texinfo = map.mtexinfos.at(src.texinfo); @@ -306,23 +306,22 @@ std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush } // don't add bevels for the point hull - if (hullnum <= 0 && src.bevel) { + if (!hullnum.value_or(0) && src.bevel) { continue; } auto &dst = brush.sides.emplace_back(); - - dst.texinfo = hullnum > 0 ? 0 : src.texinfo; + dst.texinfo = hullnum.value_or(0) ? 0 : src.texinfo; dst.planenum = src.planenum; dst.bevel = src.bevel; dst.source = &src; } // expand the brushes for the hull - if (hullnum > 0) { + if (hullnum.value_or(0)) { auto &hulls = qbsp_options.target_game->get_hull_sizes(); Q_assert(hullnum < hulls.size()); - auto &hull = *(hulls.begin() + hullnum); + auto &hull = *(hulls.begin() + hullnum.value()); for (auto &mapface : brush.sides) { if (mapface.get_texinfo().flags.no_expand) { @@ -365,8 +364,8 @@ std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush // The idea behind the bounds expansion was to avoid incorrect vis culling (AFAIK). const bool shouldExpand = (src->origin[0] != 0.0 || src->origin[1] != 0.0 || src->origin[2] != 0.0) && src->rotation == rotation_t::hipnotic && - (hullnum >= 0) // hullnum < 0 corresponds to -wrbrushes clipping hulls - && qbsp_options.target_game->id != GAME_HEXEN_II; // never do this in Hexen 2 + hullnum.has_value() && + qbsp_options.target_game->id != GAME_HEXEN_II; // never do this in Hexen 2 if (shouldExpand) { vec_t max = -std::numeric_limits::infinity(), min = std::numeric_limits::infinity(); @@ -389,7 +388,7 @@ std::optional LoadBrush(const mapentity_t *src, mapbrush_t *mapbrush //============================================================================= -static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, const int hullnum, content_stats_base_t &stats, bspbrush_t::container &brushes, logging::percent_clock &clock, size_t &num_clipped) +static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, hull_index_t hullnum, content_stats_base_t &stats, bspbrush_t::container &brushes, logging::percent_clock &clock, size_t &num_clipped) { // _omitbrushes 1 just discards all brushes in the entity. // could be useful for geometry guides, selective compilation, etc. @@ -477,7 +476,7 @@ static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, const int hulln /* func_detail_illusionary don't exist in the collision hull * (or bspx export) except for Q2, who needs them in there */ - if (hullnum > 0 && detail_illusionary) { + if (hullnum.value_or(0) && detail_illusionary) { continue; } @@ -486,8 +485,8 @@ static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, const int hulln * include them in the model bounds so collision detection works * correctly. */ - if (hullnum != HULL_COLLISION && contents.is_clip(qbsp_options.target_game)) { - if (hullnum == 0) { + if (hullnum.has_value() && contents.is_clip(qbsp_options.target_game)) { + if (hullnum.value() == 0) { if (auto brush = LoadBrush(src, &mapbrush, contents, hullnum, num_clipped)) { dst->bounds += brush->bounds; } @@ -500,8 +499,9 @@ static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, const int hulln /* "hint" brushes don't affect the collision hulls */ if (mapbrush.is_hint) { - if (hullnum > 0) + if (hullnum.value_or(0)) { continue; + } contents = qbsp_options.target_game->create_empty_contents(); } @@ -517,18 +517,20 @@ static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, const int hulln before writing the bsp, and bmodels normally have CONTENTS_SOLID as their contents type. */ - if (hullnum <= 0 && mirrorinside.value_or(false)) { + if (!hullnum.value_or(0) && mirrorinside.value_or(false)) { contents = qbsp_options.target_game->create_detail_fence_contents(contents); } } - /* nonsolid brushes don't show up in clipping hulls */ - if (hullnum > 0 && !contents.is_any_solid(qbsp_options.target_game) && !contents.is_sky(qbsp_options.target_game)) - continue; + if (hullnum.value_or(0)) { + /* nonsolid brushes don't show up in clipping hulls */ + if (!contents.is_any_solid(qbsp_options.target_game) && !contents.is_sky(qbsp_options.target_game)) + continue; - /* sky brushes are solid in the collision hulls */ - if (hullnum > 0 && contents.is_sky(qbsp_options.target_game)) - contents = qbsp_options.target_game->create_solid_contents(); + /* sky brushes are solid in the collision hulls */ + if (contents.is_sky(qbsp_options.target_game)) + contents = qbsp_options.target_game->create_solid_contents(); + } // apply extended flags contents.set_mirrored(mirrorinside); @@ -552,11 +554,11 @@ static void Brush_LoadEntity(mapentity_t *dst, mapentity_t *src, const int hulln ============ Brush_LoadEntity -hullnum HULL_COLLISION should contain ALL brushes. (used by BSPX_CreateBrushList()) +hullnum nullopt should contain ALL brushes; BSPX and Quake II, etc. hullnum 0 does not contain clip brushes. ============ */ -void Brush_LoadEntity(mapentity_t *entity, const int hullnum, bspbrush_t::container &brushes, size_t &num_clipped) +void Brush_LoadEntity(mapentity_t *entity, hull_index_t hullnum, bspbrush_t::container &brushes, size_t &num_clipped) { logging::funcheader(); diff --git a/qbsp/map.cc b/qbsp/map.cc index 6e094c65..69971c20 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -395,10 +395,10 @@ int FindTexinfo(const maptexinfo_t &texinfo) assert(map.mtexinfo_lookup.find(texinfo) != map.mtexinfo_lookup.end()); // create a copy of the miptex for animation chains - if (map.miptex[texinfo.miptex].animation_miptex != -1) { + if (map.miptex[texinfo.miptex].animation_miptex.has_value()) { maptexinfo_t anim_next = texinfo; - anim_next.miptex = map.miptex[texinfo.miptex].animation_miptex; + anim_next.miptex = map.miptex[texinfo.miptex].animation_miptex.value(); map.mtexinfos[num_texinfo].next = FindTexinfo(anim_next); } @@ -2869,7 +2869,7 @@ static void TestExpandBrushes(mapentity_t *src) for (auto &mapbrush : src->mapbrushes) { auto hull1brush = LoadBrush(src, &mapbrush, {CONTENTS_SOLID}, - qbsp_options.target_game->id == GAME_QUAKE_II ? HULL_COLLISION : 1, std::nullopt); + qbsp_options.target_game->id == GAME_QUAKE_II ? std::nullopt : std::optional(1), std::nullopt); if (hull1brush) { hull1brushes.emplace_back(bspbrush_t::make_ptr(std::move(*hull1brush))); diff --git a/qbsp/outside.cc b/qbsp/outside.cc index 6bc62030..e77fe76b 100644 --- a/qbsp/outside.cc +++ b/qbsp/outside.cc @@ -584,7 +584,7 @@ get incorrectly marked as "invisible"). Special cases: structural fully covered by detail still needs to be marked "visible". =========== */ -bool FillOutside(mapentity_t *entity, tree_t *tree, const int hullnum, bspbrush_t::container &brushes) +bool FillOutside(mapentity_t *entity, tree_t *tree, hull_index_t hullnum, bspbrush_t::container &brushes) { node_t *node = tree->headnode; @@ -604,7 +604,7 @@ bool FillOutside(mapentity_t *entity, tree_t *tree, const int hullnum, bspbrush_ } if (occupied_clusters.empty()) { - logging::print("WARNING: No entities in empty space -- no filling performed (hull {})\n", hullnum); + logging::print("WARNING: No entities in empty space -- no filling performed (hull {})\n", hullnum.value_or(0)); return false; } @@ -705,7 +705,7 @@ bool FillOutside(mapentity_t *entity, tree_t *tree, const int hullnum, bspbrush_ return true; } -void FillBrushEntity(mapentity_t *entity, tree_t *tree, const int hullnum, bspbrush_t::container &brushes) +void FillBrushEntity(mapentity_t *entity, tree_t *tree, hull_index_t hullnum, bspbrush_t::container &brushes) { logging::funcheader(); diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 12858191..4b4dee77 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -393,7 +393,7 @@ static void CountLeafs(node_t *headnode) ProcessEntity =============== */ -static void ProcessEntity(mapentity_t *entity, const int hullnum) +static void ProcessEntity(mapentity_t *entity, hull_index_t hullnum) { /* No map brushes means non-bmodel entity. We need to handle worldspawn containing no brushes, though. */ @@ -426,8 +426,9 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) if (qbsp_options.verbose.value()) PrintEntity(entity); - if (hullnum <= 0) + if (!hullnum.value_or(0) || qbsp_options.loghulls.value()) { logging::print(logging::flag::STAT, " MODEL: {}\n", mod); + } entity->epairs.set("model", mod); } } @@ -461,8 +462,8 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) logging::print(logging::flag::STAT, "INFO: calculating BSP for {} brushes with {} sides\n", brushes.size(), num_sides); - // always chop the other hulls - if (qbsp_options.chop.value() || hullnum != 0) { + // always chop the other hulls to reduce brush tests + if (qbsp_options.chop.value() || hullnum.value_or(0)) { ChopBrushes(brushes); } @@ -474,7 +475,9 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) } std::unique_ptr tree = nullptr; - if (hullnum > 0) { + + // simpler operation for hulls + if (hullnum.value_or(0)) { tree = BrushBSP(entity, brushes, true); if (entity == map.world_entity() && !qbsp_options.nofill.value()) { // assume non-world bmodels are simple @@ -490,88 +493,89 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) PruneNodes(tree->headnode); } } - ExportClipNodes(entity, tree->headnode, hullnum); + ExportClipNodes(entity, tree->headnode, hullnum.value()); + return; + } - // fixme-brushbsp: return here? + // full operation for collision (or main hull) + + if (qbsp_options.forcegoodtree.value()) { + tree = BrushBSP(entity, brushes, false); } else { + tree = BrushBSP(entity, brushes, entity == map.world_entity() ? std::nullopt : std::optional(false)); + } - if (qbsp_options.forcegoodtree.value()) { - tree = BrushBSP(entity, brushes, false); - } else { - tree = BrushBSP(entity, brushes, entity == map.world_entity() ? std::nullopt : std::optional(false)); - } + // build all the portals in the bsp tree + // some portals are solid polygons, and some are paths to other leafs + MakeTreePortals(tree.get()); - // build all the portals in the bsp tree - // some portals are solid polygons, and some are paths to other leafs - MakeTreePortals(tree.get()); - - if (entity == map.world_entity()) { - // flood fills from the void. - // marks brush sides which are *only* touching void; - // we can skip using them as BSP splitters on the "really good tree" - // (effectively expanding those brush sides outwards). - if (!qbsp_options.nofill.value() && FillOutside(entity, tree.get(), hullnum, brushes)) { - // make a really good tree - tree.reset(); - tree = BrushBSP(entity, brushes, false); - - // make the real portals for vis tracing - MakeTreePortals(tree.get()); - - // fill again so PruneNodes works - FillOutside(entity, tree.get(), hullnum, brushes); - } - - // Area portals - if (qbsp_options.target_game->id == GAME_QUAKE_II) { - FloodAreas(entity, tree->headnode); - EmitAreaPortals(tree->headnode); - } - } else { - FillBrushEntity(entity, tree.get(), hullnum, brushes); - - // rebuild BSP now that we've marked invisible brush sides + if (entity == map.world_entity()) { + // flood fills from the void. + // marks brush sides which are *only* touching void; + // we can skip using them as BSP splitters on the "really good tree" + // (effectively expanding those brush sides outwards). + if (!qbsp_options.nofill.value() && FillOutside(entity, tree.get(), hullnum, brushes)) { + // make a really good tree tree.reset(); tree = BrushBSP(entity, brushes, false); + + // make the real portals for vis tracing + MakeTreePortals(tree.get()); + + // fill again so PruneNodes works + FillOutside(entity, tree.get(), hullnum, brushes); } - MakeTreePortals(tree.get()); - - MarkVisibleSides(tree.get(), entity, brushes); - MakeFaces(tree->headnode); - - FreeTreePortals(tree.get()); - PruneNodes(tree->headnode); - - if (hullnum <= 0 && entity == map.world_entity() && (!map.leakfile || qbsp_options.keepprt.value())) { - WritePortalFile(tree.get()); - } - - // needs to come after any face creation - MakeMarkFaces(tree->headnode); - - CountLeafs(tree->headnode); - - // output vertices first, since TJunc needs it - EmitVertices(tree->headnode); - - TJunc(tree->headnode); - - if (qbsp_options.objexport.value() && entity == map.world_entity()) { - ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode); - ExportObj_Marksurfaces("pre_makefaceedges_marksurfaces", tree->headnode); - } - - Q_assert(!entity->firstoutputfacenumber.has_value()); - - entity->firstoutputfacenumber = MakeFaceEdges(tree->headnode); - + // Area portals if (qbsp_options.target_game->id == GAME_QUAKE_II) { - ExportBrushList(entity, tree->headnode); + FloodAreas(entity, tree->headnode); + EmitAreaPortals(tree->headnode); } + } else { + FillBrushEntity(entity, tree.get(), hullnum, brushes); - ExportDrawNodes(entity, tree->headnode, entity->firstoutputfacenumber.value()); + // rebuild BSP now that we've marked invisible brush sides + tree.reset(); + tree = BrushBSP(entity, brushes, false); } + + MakeTreePortals(tree.get()); + + MarkVisibleSides(tree.get(), entity, brushes); + MakeFaces(tree->headnode); + + FreeTreePortals(tree.get()); + PruneNodes(tree->headnode); + + // write out .prt for main hull + if (!hullnum.value_or(0) && entity == map.world_entity() && (!map.leakfile || qbsp_options.keepprt.value())) { + WritePortalFile(tree.get()); + } + + // needs to come after any face creation + MakeMarkFaces(tree->headnode); + + CountLeafs(tree->headnode); + + // output vertices first, since TJunc needs it + EmitVertices(tree->headnode); + + TJunc(tree->headnode); + + if (qbsp_options.objexport.value() && entity == map.world_entity()) { + ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode); + ExportObj_Marksurfaces("pre_makefaceedges_marksurfaces", tree->headnode); + } + + Q_assert(!entity->firstoutputfacenumber.has_value()); + + entity->firstoutputfacenumber = MakeFaceEdges(tree->headnode); + + if (qbsp_options.target_game->id == GAME_QUAKE_II) { + ExportBrushList(entity, tree->headnode); + } + + ExportDrawNodes(entity, tree->headnode, entity->firstoutputfacenumber.value()); } /* @@ -767,10 +771,12 @@ static void BSPX_CreateBrushList(void) CreateSingleHull ================= */ -static void CreateSingleHull(const int hullnum) +static void CreateSingleHull(hull_index_t hullnum) { - if (hullnum >= 0) { - logging::print("Processing hull {}...\n", hullnum); + if (hullnum.has_value()) { + logging::print("Processing hull {}...\n", hullnum.value()); + } else { + logging::print("Processing map...\n"); } // for each entity in the map file that has geometry @@ -781,7 +787,7 @@ static void CreateSingleHull(const int hullnum) if (&entity != map.world_entity()) { wants_logging = wants_logging && qbsp_options.logbmodels.value(); } - if (hullnum > 0) { + if (hullnum.value_or(0)) { wants_logging = wants_logging && qbsp_options.loghulls.value(); } @@ -818,14 +824,17 @@ static void CreateHulls(void) // game has no hulls, so we have to export brush lists and stuff. if (!hulls.size()) { - CreateSingleHull(HULL_COLLISION); + CreateSingleHull(std::nullopt); + return; + } + + // all the hulls + for (size_t i = 0; i < hulls.size(); i++) { + CreateSingleHull(i); + // only create hull 0 if fNoclip is set - } else if (qbsp_options.noclip.value()) { - CreateSingleHull(0); - // do all the hulls - } else { - for (size_t i = 0; i < hulls.size(); i++) { - CreateSingleHull(i); + if (qbsp_options.noclip.value()) { + break; } } } diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index 680bc2a4..32cbc7db 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -79,8 +79,8 @@ size_t ExportMapTexinfo(size_t texinfonum) src.outputnum = i; - if (src.next != -1) { - map.bsp.texinfo[i].nexttexinfo = ExportMapTexinfo(src.next); + if (src.next.has_value()) { + map.bsp.texinfo[i].nexttexinfo = ExportMapTexinfo(src.next.value()); } return i; @@ -127,7 +127,7 @@ First time just store away data, second time fix up reference points to accomodate new data interleaved with old. ================== */ -void ExportClipNodes(mapentity_t *entity, node_t *nodes, const int hullnum) +void ExportClipNodes(mapentity_t *entity, node_t *nodes, hull_index_t::value_type hullnum) { auto &model = map.bsp.dmodels.at(entity->outputmodelnumber.value()); model.headnode[hullnum] = ExportClipNodes(nodes); diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index e19aea3b..f309192e 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -250,7 +250,7 @@ static std::tuple> LoadTestmapQ1 #endif } -static void CheckFilled(const mbsp_t &bsp, int hullnum) +static void CheckFilled(const mbsp_t &bsp, hull_index_t hullnum) { int32_t contents = BSP_FindContentsAtPoint(&bsp, hullnum, &bsp.dmodels[0], qvec3d{8192, 8192, 8192});