diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index a26aa450..428a10c3 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -29,6 +29,7 @@ class mapentity_t; struct maptexinfo_t; +struct mapface_t; struct side_t { @@ -43,6 +44,7 @@ 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 + const mapface_t *source; // the mapface we were generated from bool tested; @@ -82,14 +84,7 @@ struct bspbrush_t qplane3d Face_Plane(const face_t *face); qplane3d Face_Plane(const side_t *face); -enum class rotation_t -{ - none, - hipnotic, - origin_brush -}; - -std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const contentflags_t &contents, - const int hullnum); +bspbrush_t LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const contentflags_t &contents, const int hullnum); contentflags_t Brush_GetContents(const mapbrush_t *mapbrush); +void CreateBrushWindings(bspbrush_t *brush); void FreeBrushes(mapentity_t *ent); diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 29564b26..1d4aa611 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -45,6 +45,7 @@ struct mapface_t int texinfo = 0; int linenum = 0; bool bevel = false; + winding_t winding; // winding used to calculate bevels surfflags_t flags{}; @@ -55,8 +56,6 @@ struct mapface_t // for convert std::optional raw_info; - winding_t winding; - bool set_planepts(const std::array &pts); const texvecf &get_texvecs() const; @@ -87,6 +86,13 @@ struct lumpdata void *data; }; +enum class rotation_t +{ + none, + hipnotic, + origin_brush +}; + class mapentity_t { public: @@ -100,6 +106,7 @@ public: #endif qvec3d origin{}; + rotation_t rotation; std::list mapbrushes; diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 495d8437..b24ded18 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -321,6 +321,40 @@ contentflags_t Brush_GetContents(const mapbrush_t *mapbrush) return base_contents; } +/* +================== +CreateBrushWindings + +Create all of the windings for the specified brush, and +calculate its bounds. +================== +*/ +void CreateBrushWindings(bspbrush_t *brush) +{ + std::optional w; + + for (int i = 0; i < brush->sides.size(); i++) { + side_t *side = &brush->sides[i]; + w = BaseWindingForPlane(Face_Plane(side)); + for (int j = 0; j < brush->sides.size() && w; j++) { + if (i == j) + continue; + if (brush->sides[j].bevel) + continue; + qplane3d plane = -Face_Plane(&brush->sides[j]); + w = w->clip(plane, 0, false)[SIDE_FRONT]; // CLIP_EPSILON); + } + + if (w) { + side->w = *w; + } else { + side->w.clear(); + } + } + + brush->update_bounds(); +} + /* =============== LoadBrush @@ -328,18 +362,20 @@ LoadBrush Converts a mapbrush to a bsp brush =============== */ -std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const contentflags_t &contents, +bspbrush_t LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const contentflags_t &contents, const int hullnum) { // create the brush bspbrush_t brush{}; brush.contents = contents; brush.sides.reserve(mapbrush->faces.size()); + brush.mapbrush = mapbrush; for (size_t i = 0; i < mapbrush->faces.size(); i++) { auto &src = mapbrush->faces[i]; - if (src.bevel) { + // don't add bevels for the point hull + if (hullnum <= 0 && src.bevel) { continue; } @@ -348,26 +384,73 @@ std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *ma dst.texinfo = hullnum > 0 ? 0 : src.texinfo; dst.planenum = src.planenum; dst.bevel = src.bevel; - - // TEMP - dst.w = src.winding; - - CheckFace(&dst, src); + dst.source = &src; } - // todo: expand planes, recalculate bounds & windings - brush.bounds = mapbrush->bounds; - -#if 0 + // expand the brushes for the hull if (hullnum > 0) { auto &hulls = qbsp_options.target_game->get_hull_sizes(); Q_assert(hullnum < hulls.size()); - ExpandBrush(&hullbrush, *(hulls.begin() + hullnum), facelist); - facelist = CreateBrushFaces(src, &hullbrush, hullnum); - } -#endif + auto &hull = *(hulls.begin() + hullnum); + + for (auto &mapface : brush.sides) { + if (mapface.get_texinfo().flags.no_expand) { + continue; + } + qvec3d corner{}; + for (int32_t x = 0; x < 3; x++) { + if (mapface.get_plane().get_normal()[x] > 0) { + corner[x] = hull[1][x]; + } else if (mapface.get_plane().get_normal()[x] < 0) { + corner[x] = hull[0][x]; + } + } + qplane3d plane = mapface.get_plane(); + plane.dist += qv::dot(corner, plane.normal); + mapface.planenum = map.add_or_find_plane(plane); + mapface.bevel = false; + } + } + + CreateBrushWindings(&brush); + + for (auto &face : brush.sides) { + CheckFace(&face, *face.source); + } + + // Rotatable objects must have a bounding box big enough to + // account for all its rotations + + // if -wrbrushes is in use, don't do this for the clipping hulls because it depends on having + // the actual non-hacked bbox (it doesn't write axial planes). + + // Hexen2 also doesn't want the bbox expansion, it's handled in engine (see: SV_LinkEdict) + + // Only do this for hipnotic rotation. For origin brushes in Quake, it breaks some of their + // uses (e.g. func_train). This means it's up to the mapper to expand the model bounds with + // clip brushes if they're going to rotate a model in vanilla Quake and not use hipnotic rotation. + // 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 + + if (shouldExpand) { + vec_t max = -std::numeric_limits::infinity(), min = std::numeric_limits::infinity(); + + for (auto &v : brush.bounds.mins()) { + min = ::min(min, v); + max = ::max(max, v); + } + for (auto &v : brush.bounds.maxs()) { + min = ::min(min, v); + max = ::max(max, v); + } + + vec_t delta = std::max(fabs(max), fabs(min)); + brush.bounds = {-delta, delta}; + } - brush.mapbrush = mapbrush; return brush; } @@ -483,12 +566,8 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int */ if (hullnum != HULL_COLLISION && contents.is_clip(qbsp_options.target_game)) { if (hullnum == 0) { - std::optional brush = LoadBrush(src, &mapbrush, contents, hullnum); - - if (brush) { - dst->bounds += brush->bounds; - } - + bspbrush_t brush = LoadBrush(src, &mapbrush, contents, hullnum); + dst->bounds += brush.bounds; continue; // for hull1, 2, etc., convert clip to CONTENTS_SOLID } else { @@ -533,22 +612,21 @@ static void Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int contents.set_clips_same_type(clipsametype); contents.illusionary_visblocker = func_illusionary_visblocker; - std::optional brush = LoadBrush(src, &mapbrush, contents, hullnum); - if (!brush) - continue; + bspbrush_t brush = LoadBrush(src, &mapbrush, contents, hullnum); - brush->lmshift = lmshift; + brush.lmshift = lmshift; - for (auto &face : brush->sides) + for (auto &face : brush.sides) { face.lmshift = lmshift; - - if (classname == std::string_view("func_areaportal")) { - brush->func_areaportal = const_cast(src); // FIXME: get rid of consts on src in the callers? } - qbsp_options.target_game->count_contents_in_stats(brush->contents, stats); - dst->brushes.push_back(std::make_unique(brush.value())); - dst->bounds += brush->bounds; + if (classname == std::string_view("func_areaportal")) { + brush.func_areaportal = const_cast(src); // FIXME: get rid of consts on src in the callers? + } + + qbsp_options.target_game->count_contents_in_stats(brush.contents, stats); + dst->brushes.push_back(std::make_unique(std::move(brush))); + dst->bounds += brush.bounds; } logging::percent(src->mapbrushes.size(), src->mapbrushes.size(), src == map.world_entity()); diff --git a/qbsp/brushbsp.cc b/qbsp/brushbsp.cc index d952f531..12576738 100644 --- a/qbsp/brushbsp.cc +++ b/qbsp/brushbsp.cc @@ -70,39 +70,6 @@ struct bspstats_t std::atomic c_brushesonesided; }; -/* -================== -CreateBrushWindings - -Currently only used in BrushFromBounds -================== -*/ -static void CreateBrushWindings(bspbrush_t *brush) -{ - std::optional w; - - for (int i = 0; i < brush->sides.size(); i++) { - side_t *side = &brush->sides[i]; - w = BaseWindingForPlane(Face_Plane(side)); - for (int j = 0; j < brush->sides.size() && w; j++) { - if (i == j) - continue; - if (brush->sides[j].bevel) - continue; - qplane3d plane = -Face_Plane(&brush->sides[j]); - w = w->clip(plane, 0, false)[SIDE_FRONT]; // CLIP_EPSILON); - } - - if (w) { - side->w = *w; - } else { - side->w.clear(); - } - } - - brush->update_bounds(); -} - /* ================== BrushFromBounds @@ -575,6 +542,8 @@ static twosided> SplitBrush(std::unique_ptr= 0) // hullnum < 0 corresponds to -wrbrushes clipping hulls - && qbsp_options.target_game->id != GAME_HEXEN_II; // never do this in Hexen 2 - - if (shouldExpand) { - vec_t delta = std::max(fabs(max), fabs(min)); - hullbrush->bounds = {-delta, delta}; - } -#endif } logging::print(logging::flag::STAT, " {:8} brushes\n", map.total_brushes); @@ -2715,6 +2691,8 @@ void CalculateWorldExtent(void) } qbsp_options.worldextent.setValue((extents + hull_extents) * 2, settings::source::GAME_TARGET); + + logging::print("INFO: world extents calculated to {} units\n", qbsp_options.worldextent.value()); } /* @@ -2774,12 +2752,10 @@ static void TestExpandBrushes(const mapentity_t *src) std::vector> hull1brushes; for (auto &mapbrush : src->mapbrushes) { - std::optional hull1brush = LoadBrush(src, &mapbrush, {CONTENTS_SOLID}, + bspbrush_t hull1brush = LoadBrush(src, &mapbrush, {CONTENTS_SOLID}, qbsp_options.target_game->id == GAME_QUAKE_II ? HULL_COLLISION : 1); - if (hull1brush) { - hull1brushes.emplace_back(std::make_unique(std::move(*hull1brush))); - } + hull1brushes.emplace_back(std::make_unique(std::move(hull1brush))); } WriteBspBrushMap("expanded.map", hull1brushes); diff --git a/qbsp/outside.cc b/qbsp/outside.cc index e56bd981..9ab50edc 100644 --- a/qbsp/outside.cc +++ b/qbsp/outside.cc @@ -413,7 +413,7 @@ static void MarkVisibleBrushSides_R(node_t *node) // leaf that are coplanar for (auto *brush : neighbour_leaf->original_brushes) { for (auto &side : brush->sides) { - //if (qv::epsilonEqual(side.plane, portal->plane)) { + // fixme-brushbsp: should this be get_plane() ? if (qv::epsilonEqual(side.get_positive_plane(), portal->plane)) { // we've found a brush side in an original brush in the neighbouring // leaf, on a portal to this (non-opaque) leaf, so mark it as visible. diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index 4ff10464..f9184313 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -41,6 +41,9 @@ static mapentity_t LoadMap(const char *map) parser_t parser(map); + // FIXME: ??? + mapentity_t &entity = ::map.entities.emplace_back(); + mapentity_t worldspawn; // FIXME: adds the brush to the global map... @@ -399,10 +402,8 @@ TEST_CASE("duplicatePlanes", "[qbsp]") CHECK(0 == worldspawn.brushes.size()); CHECK(6 == worldspawn.mapbrushes.front().faces.size()); - std::optional brush = - LoadBrush(&worldspawn, &worldspawn.mapbrushes.front(), {CONTENTS_SOLID}, 0); - REQUIRE(std::nullopt != brush); - CHECK(6 == brush->sides.size()); + bspbrush_t brush = LoadBrush(&worldspawn, &worldspawn.mapbrushes.front(), {CONTENTS_SOLID}, 0); + CHECK(6 == brush.sides.size()); } /**