diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index d72b6fbd..a26aa450 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -62,13 +62,13 @@ struct bspbrush_t * fixme-brushbsp: this is supposed to be a mapbrush_t */ bspbrush_t *original; + const mapbrush_t *mapbrush; 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 */ - std::optional outputnumber; /* only set for original brushes */ mapentity_t *func_areaportal; qvec3d sphere_origin; diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 0420e37f..4851bea7 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -78,6 +78,7 @@ public: brushformat_t format = brushformat_t::NORMAL; int contents = 0; aabb3d bounds {}; + std::optional outputnumber; /* only set for original brushes */ }; struct lumpdata @@ -90,6 +91,15 @@ struct lumpdata class mapentity_t { public: +#ifdef _MSC_VER + // FIXME: this is to allow MSVC to compile + mapentity_t() = default; + mapentity_t(mapentity_t &&) noexcept = default; + mapentity_t(const mapentity_t &) = delete; + mapentity_t &operator=(const mapentity_t &) = delete; + mapentity_t &operator=(mapentity_t &&) noexcept = default; +#endif + qvec3d origin{}; std::list mapbrushes; @@ -333,6 +343,7 @@ bool IsWorldBrushEntity(const mapentity_t *entity); bool IsNonRemoveWorldBrushEntity(const mapentity_t *entity); void LoadMapFile(void); void ConvertMapFile(void); +void ProcessMapBrushes(); struct quark_tx_info_t { diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 78ee0e20..495d8437 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -53,27 +53,6 @@ std::unique_ptr bspbrush_t::copy_unique() const return std::make_unique(*this); } -/* - * Beveled clipping hull can generate many extra faces - */ -constexpr size_t MAX_FACES = 128; -constexpr size_t MAX_HULL_POINTS = 512; -constexpr size_t MAX_HULL_EDGES = 1024; - -struct hullbrush_t -{ - const mapbrush_t *srcbrush; - contentflags_t contents; - aabb3d bounds; - - std::vector faces; - std::vector points; - std::vector corners; - std::vector> edges; - - int linenum; -}; - /* ================= Face_Plane @@ -105,7 +84,7 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) } else if (face->w.size() == 1) { logging::print("WARNING: line {}: too few points (1): ({})\n", sourceface.linenum, face->w[0]); } else { - logging::print("WARNING: line {}: too few points ({})", sourceface.linenum, face->w.size()); + logging::print("WARNING: line {}: too few points ({})\n", sourceface.linenum, face->w.size()); } face->w.clear(); @@ -122,7 +101,7 @@ static void CheckFace(side_t *face, const mapface_t &sourceface) for (auto &v : p1) { if (fabs(v) > qbsp_options.worldextent.value()) { // this is fatal because a point should never lay outside the world - FError("line {}: coordinate out of range ({})", sourceface.linenum, v); + FError("line {}: coordinate out of range ({})\n", sourceface.linenum, v); } } @@ -218,15 +197,6 @@ qvec3d FixRotateOrigin(mapentity_t *entity) return offset; } -static bool Brush_IsHint(const hullbrush_t &brush) -{ - for (auto &f : brush.faces) - if (f.flags.is_hint) - return true; - - return false; -} - static bool MapBrush_IsHint(const mapbrush_t &brush) { for (auto &f : brush.faces) { @@ -237,84 +207,6 @@ static bool MapBrush_IsHint(const mapbrush_t &brush) return false; } -/* -================= -CreateBrushFaces -================= -*/ -static std::vector CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush, const int hullnum) -{ - vec_t r; - std::optional w; - qplane3d plane; - std::vector facelist; - qvec3d point; - vec_t max, min; - - min = VECT_MAX; - max = -VECT_MAX; - - hullbrush->bounds = {}; - - for (auto &mapface : hullbrush->faces) { - if (hullnum <= 0 && Brush_IsHint(*hullbrush)) { - /* Don't generate hintskip faces */ - const maptexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); - - if (qbsp_options.target_game->texinfo_is_hintskip(texinfo.flags, map.miptexTextureName(texinfo.miptex))) - continue; - } - - w = BaseWindingForPlane(mapface.get_plane()); - - for (auto &mapface2 : hullbrush->faces) { - if (&mapface == &mapface2) - continue; - if (!w) - break; - - // flip the plane, because we want to keep the back side - plane = -mapface2.get_plane(); - - w = w->clip(plane, qbsp_options.epsilon.value(), false)[SIDE_FRONT]; - } - - if (!w) { - continue; // overconstrained plane - } - - // this face is a keeper - side_t &f = facelist.emplace_back(); - - f.w.resize(w->size()); - - for (size_t j = 0; j < w->size(); j++) { - for (size_t k = 0; k < 3; k++) { - point[k] = w->at(j)[k]; - r = Q_rint(point[k]); - if (fabs(point[k] - r) < ZERO_EPSILON) - f.w[j][k] = r; - else - f.w[j][k] = point[k]; - - if (f.w[j][k] < min) - min = f.w[j][k]; - if (f.w[j][k] > max) - max = f.w[j][k]; - } - - hullbrush->bounds += f.w[j]; - } - - f.texinfo = hullnum > 0 ? 0 : mapface.texinfo; - f.planenum = mapface.planenum; - - CheckFace(&f, mapface); - } - - return facelist; -} - /* ===================== FreeBrushes @@ -325,189 +217,28 @@ void FreeBrushes(mapentity_t *ent) ent->brushes.clear(); } -/* -============================================================================== +#if 0 + if (hullnum <= 0 && Brush_IsHint(*hullbrush)) { + /* Don't generate hintskip faces */ + const maptexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); -BEVELED CLIPPING HULL GENERATION - -This is done by brute force, and could easily get a lot faster if anyone cares. -============================================================================== -*/ - -/* -============ -AddBrushPlane -============= -*/ -static void AddBrushPlane(hullbrush_t *hullbrush, const qbsp_plane_t &plane) -{ - vec_t len = qv::length(plane.get_normal()); - - if (len < 1.0 - NORMAL_EPSILON || len > 1.0 + NORMAL_EPSILON) - FError("invalid normal (vector length {:.4})", len); - - for (auto &mapface : hullbrush->faces) { - if (qv::epsilonEqual(mapface.get_plane(), plane, EQUAL_EPSILON, qbsp_options.epsilon.value())) { - return; + if (qbsp_options.target_game->texinfo_is_hintskip(texinfo.flags, map.miptexTextureName(texinfo.miptex))) + continue; } - } - - if (hullbrush->faces.size() == MAX_FACES) { - FError( - "brush->faces >= MAX_FACES ({}), source brush on line {}", MAX_FACES, hullbrush->srcbrush->faces[0].linenum); - } - - mapface_t &mapface = hullbrush->faces.emplace_back(); - mapface.planenum = map.add_or_find_plane(plane); - mapface.texinfo = 0; -} - -/* -============ -TestAddPlane - -Adds the given plane to the brush description if all of the original brush -vertexes can be put on the front side -============= -*/ -static void TestAddPlane(hullbrush_t *hullbrush, qbsp_plane_t &plane) -{ - vec_t d; - int points_front, points_back; - - /* see if the plane has already been added */ - for (auto &mapface : hullbrush->faces) { - if (qv::epsilonEqual(plane, mapface.get_plane())) - return; - if (qv::epsilonEqual(-plane, mapface.get_plane())) - return; - } - - /* check all the corner points */ - points_front = 0; - points_back = 0; - - for (auto &corner : hullbrush->corners) { - d = plane.distance_to(corner); - if (d < -qbsp_options.epsilon.value()) { - if (points_front) - return; - points_back = 1; - } else if (d > qbsp_options.epsilon.value()) { - if (points_back) - return; - points_front = 1; - } - } - - // the plane is a seperator - if (points_front) { - plane = -plane; - } - - AddBrushPlane(hullbrush, plane); -} - -/* -============ -AddHullPoint - -Doesn't add if duplicated -============= -*/ -static int AddHullPoint(hullbrush_t *hullbrush, const qvec3d &p, const aabb3d &hull_size) -{ - for (auto &pt : hullbrush->points) - if (qv::epsilonEqual(p, pt, EQUAL_EPSILON)) - return &pt - hullbrush->points.data(); - - if (hullbrush->points.size() == MAX_HULL_POINTS) - FError("hullbrush->numpoints == MAX_HULL_POINTS ({}), " - "source brush on line {}", - MAX_HULL_POINTS, hullbrush->srcbrush->faces[0].linenum); - - int i = hullbrush->points.size(); - hullbrush->points.emplace_back(p); - - for (size_t x = 0; x < 2; x++) - for (size_t y = 0; y < 2; y++) - for (size_t z = 0; z < 2; z++) { - hullbrush->corners.emplace_back(p[0] + hull_size[x][0], p[1] + hull_size[y][1], p[2] + hull_size[z][2]); - } - - return i; -} - -/* -============ -AddHullEdge - -Creates all of the hull planes around the given edge, if not done allready -============= -*/ -static void AddHullEdge(hullbrush_t *hullbrush, const qvec3d &p1, const qvec3d &p2, const aabb3d &hull_size) -{ - int pt1, pt2; - int a, b, c, d, e; - qbsp_plane_t plane; - vec_t length; - - pt1 = AddHullPoint(hullbrush, p1, hull_size); - pt2 = AddHullPoint(hullbrush, p2, hull_size); - - for (auto &edge : hullbrush->edges) - if ((edge == std::make_tuple(pt1, pt2)) || (edge == std::make_tuple(pt2, pt1))) - return; - - if (hullbrush->edges.size() == MAX_HULL_EDGES) - FError("hullbrush->numedges == MAX_HULL_EDGES ({}), " - "source brush on line {}", - MAX_HULL_EDGES, hullbrush->srcbrush->faces[0].linenum); - - hullbrush->edges.emplace_back(pt1, pt2); - - qvec3d edgevec = qv::normalize(p1 - p2); - - for (a = 0; a < 3; a++) { - b = (a + 1) % 3; - c = (a + 2) % 3; - - qvec3d planevec{}; - planevec[a] = 1; - plane.set_normal(qv::normalize(qv::cross(planevec, edgevec), length)); - - /* If this edge is almost parallel to the hull edge, skip it. */ - if (length < ANGLEEPSILON) { - continue; - } - - for (d = 0; d <= 1; d++) { - for (e = 0; e <= 1; e++) { - qvec3d planeorg = p1; - planeorg[b] += hull_size[d][b]; - planeorg[c] += hull_size[e][c]; - plane.get_dist() = qv::dot(planeorg, plane.get_normal()); - TestAddPlane(hullbrush, plane); - } - } - } -} +#endif +#if 0 /* ============ ExpandBrush ============= */ -static void ExpandBrush(hullbrush_t *hullbrush, const aabb3d &hull_size, std::vector &facelist) +static void ExpandBrush(bspbrush_t &hullbrush, const aabb3d &hull_size) { int x, s; qbsp_plane_t plane; int cBevEdge = 0; - hullbrush->points.clear(); - hullbrush->corners.clear(); - hullbrush->edges.clear(); - // create all the hull points for (auto &f : facelist) for (size_t i = 0; i < f.w.size(); i++) { @@ -550,6 +281,7 @@ static void ExpandBrush(hullbrush_t *hullbrush, const aabb3d &hull_size, std::ve for (size_t i = 0; i < f.w.size(); i++) AddHullEdge(hullbrush, f.w[i], f.w[(i + 1) % f.w.size()], hull_size); } +#endif //============================================================================ @@ -597,44 +329,45 @@ Converts a mapbrush to a bsp brush =============== */ std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, const contentflags_t &contents, - const qvec3d &rotate_offset, const rotation_t rottype, const int hullnum) + const int hullnum) { - hullbrush_t hullbrush; - std::vector facelist; + // create the brush + bspbrush_t brush{}; + brush.contents = contents; + brush.sides.reserve(mapbrush->faces.size()); - // create the faces + for (size_t i = 0; i < mapbrush->faces.size(); i++) { + auto &src = mapbrush->faces[i]; - hullbrush.linenum = mapbrush->faces[0].linenum; - if (mapbrush->faces.size() > MAX_FACES) - FError("brush->faces >= MAX_FACES ({}), source brush on line {}", MAX_FACES, hullbrush.linenum); + if (src.bevel) { + continue; + } - hullbrush.contents = contents; - hullbrush.srcbrush = mapbrush; - hullbrush.faces.reserve(mapbrush->faces.size()); - for (auto &face : mapbrush->faces) { - hullbrush.faces.emplace_back(face); + auto &dst = brush.sides.emplace_back(); + + dst.texinfo = hullnum > 0 ? 0 : src.texinfo; + dst.planenum = src.planenum; + dst.bevel = src.bevel; + + // TEMP + dst.w = src.winding; + + CheckFace(&dst, src); } - facelist = CreateBrushFaces(src, &hullbrush, hullnum); - - if (facelist.empty()) { - logging::print("WARNING: Couldn't create brush faces\n"); - logging::print("^ brush at line {} of .map file\n", hullbrush.linenum); - return std::nullopt; - } + // todo: expand planes, recalculate bounds & windings + brush.bounds = mapbrush->bounds; +#if 0 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 - // create the brush - bspbrush_t brush{}; - brush.contents = contents; - brush.sides = std::move(facelist); - brush.bounds = hullbrush.bounds; + brush.mapbrush = mapbrush; return brush; } diff --git a/qbsp/map.cc b/qbsp/map.cc index 1838b6b6..1b1e8c3c 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -1634,50 +1634,6 @@ static std::optional ParseBrushFace(parser_t &parser, const mapbrush_ return face; } - -/* -================ -CalculateBrushBounds -================ -*/ -inline void CalculateBrushBounds(mapbrush_t &ob) -{ - ob.bounds = {}; - - for (size_t i = 0; i < ob.faces.size(); i++) { - const auto &plane = ob.faces[i].get_plane(); - std::optional w = BaseWindingForPlane(plane); - - for (size_t j = 0; j < ob.faces.size() && w; j++) { - if (i == j) { - continue; - } - if (ob.faces[j].bevel) { - continue; - } - const auto &plane = map.get_plane(ob.faces[j].planenum ^ 1); - w = w->clip(plane, 0)[SIDE_FRONT]; //CLIP_EPSILON); - } - - if (w) { - ob.faces[i].winding = w.value(); - //side->visible = true; - for (auto &p : w.value()) { - ob.bounds += p; - } - } - } - - for (size_t i = 0; i < 3; i++) { - if (ob.bounds.mins()[0] < -qbsp_options.worldextent.value() || ob.bounds.maxs()[0] > qbsp_options.worldextent.value()) { - logging::print("WARNING: entity xxx, brush yyy: bounds out of range\n"); - } - if (ob.bounds.mins()[0] > qbsp_options.worldextent.value() || ob.bounds.maxs()[0] < -qbsp_options.worldextent.value()) { - logging::print("WARNING: entity xxx, brush yyy: no visible sides on brush\n"); - } - } -} - /* ================= AddBrushBevels @@ -2202,6 +2158,179 @@ bool IsNonRemoveWorldBrushEntity(const mapentity_t *entity) return false; } +/* +================ +CalculateBrushBounds +================ +*/ +inline void CalculateBrushBounds(mapbrush_t &ob) +{ + ob.bounds = {}; + + for (size_t i = 0; i < ob.faces.size(); i++) { + const auto &plane = ob.faces[i].get_plane(); + std::optional w = BaseWindingForPlane(plane); + + for (size_t j = 0; j < ob.faces.size() && w; j++) { + if (i == j) { + continue; + } + if (ob.faces[j].bevel) { + continue; + } + const auto &plane = map.get_plane(ob.faces[j].planenum ^ 1); + w = w->clip(plane, 0)[SIDE_FRONT]; //CLIP_EPSILON); + } + + if (w) { + ob.faces[i].winding = w.value(); + //side->visible = true; + for (auto &p : w.value()) { + ob.bounds += p; + } + } + } + + for (size_t i = 0; i < 3; i++) { + if (ob.bounds.mins()[0] < -qbsp_options.worldextent.value() || ob.bounds.maxs()[0] > qbsp_options.worldextent.value()) { + logging::print("WARNING: entity xxx, brush yyy: bounds out of range\n"); + } + if (ob.bounds.mins()[0] > qbsp_options.worldextent.value() || ob.bounds.maxs()[0] < -qbsp_options.worldextent.value()) { + logging::print("WARNING: entity xxx, brush yyy: no visible sides on brush\n"); + } + } +} + +void ProcessMapBrushes() +{ + logging::funcheader(); + + // calculate extents, if required + if (!qbsp_options.worldextent.value()) { + CalculateWorldExtent(); + } + + map.total_brushes = 0; + + size_t num_faces = 0, num_bevels = 0, num_removed = 0, num_offset = 0; + + // calculate brush extents and brush bevels + for (auto &entity : map.entities) { + + /* Origin brush support */ + rotation_t rottype = rotation_t::none; + + for (auto it = entity.mapbrushes.begin(); it != entity.mapbrushes.end(); ) { + auto &brush = *it; + + // calculate brush bounds + CalculateBrushBounds(brush); + + const contentflags_t contents = Brush_GetContents(&brush); + + // origin brushes are removed, and the origin of the entity is overwritten + // with its centroid. + if (contents.is_origin(qbsp_options.target_game)) { + if (&entity == map.world_entity()) { + logging::print("WARNING: Ignoring origin brush in worldspawn\n"); + } else if (entity.epairs.has("origin")) { + logging::print("WARNING: Entity at line {} has multiple origin brushes\n", entity.mapbrushes.front().faces[0].linenum); + } else { + entity.origin = brush.bounds.centroid(); + entity.epairs.set("origin", qv::to_string(entity.origin)); + } + + num_removed++; + it = entity.mapbrushes.erase(it); + rottype = rotation_t::origin_brush; + continue; + } + + size_t old_num_faces = brush.faces.size(); + num_faces += old_num_faces; + + // add the brush bevels + AddBrushBevels(entity, brush); + + num_bevels += brush.faces.size() - old_num_faces; + it++; + } + + map.total_brushes += entity.mapbrushes.size(); + + /* Hipnotic rotation */ + if (rottype == rotation_t::none) { + if (!Q_strncasecmp(entity.epairs.get("classname"), "rotate_", 7)) { + entity.origin = FixRotateOrigin(&entity); + rottype = rotation_t::hipnotic; + } + } + + // offset brush bounds + if (rottype != rotation_t::none) { + for (auto &brush : entity.mapbrushes) { + brush.bounds = brush.bounds.translate(-entity.origin); + + for (auto &f : brush.faces) { + // account for texture offset, from txqbsp-xt + if (!qbsp_options.oldrottex.value()) { + maptexinfo_t texInfoNew = map.mtexinfos.at(f.texinfo); + texInfoNew.outputnum = std::nullopt; + + texInfoNew.vecs.at(0, 3) += qv::dot(entity.origin, texInfoNew.vecs.row(0).xyz()); + texInfoNew.vecs.at(1, 3) += qv::dot(entity.origin, texInfoNew.vecs.row(1).xyz()); + + f.texinfo = FindTexinfo(texInfoNew); + } + + qplane3d plane = f.get_plane(); + plane.dist -= qv::dot(plane.normal, entity.origin); + f.planenum = map.add_or_find_plane(plane); + } + + // re-calculate brush bounds + CalculateBrushBounds(brush); + + num_offset++; + } + } +#if 0 + // 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 = (rotate_offset[0] != 0.0 || rotate_offset[1] != 0.0 || rotate_offset[2] != 0.0) && + rottype == 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 delta = std::max(fabs(max), fabs(min)); + hullbrush->bounds = {-delta, delta}; + } +#endif + } + + logging::print(logging::flag::STAT, " {:8} brushes\n", map.total_brushes); + logging::print(logging::flag::STAT, " {:8} faces\n", num_faces); + logging::print(logging::flag::STAT, " {:8} bevel faces\n", num_bevels); + if (num_removed) { + logging::print(logging::flag::STAT, " {:8} utility brushes removed\n", num_removed); + } + if (num_offset) { + logging::print(logging::flag::STAT, " {:8} brushes translated from origins\n", num_offset); + } + logging::print(logging::flag::STAT, "\n"); +} + void LoadMapFile(void) { logging::funcheader(); @@ -2260,118 +2389,6 @@ void LoadMapFile(void) map.entities.pop_back(); } - // calculate extents, if required - if (!qbsp_options.worldextent.value()) { - CalculateWorldExtent(); - } - - map.total_brushes = 0; - - size_t num_faces = 0, num_bevels = 0; - - // calculate brush extents and brush bevels - for (auto &entity : map.entities) { - - for (auto it = entity.mapbrushes.begin(); it != entity.mapbrushes.end(); ) { - auto &brush = *it; - - // calculate brush bounds - CalculateBrushBounds(brush); - - const contentflags_t contents = Brush_GetContents(&brush); - - // origin brushes are removed, and the origin of the entity is overwritten - // with its centroid. - if (contents.is_origin(qbsp_options.target_game)) { - if (&entity == map.world_entity()) { - logging::print("WARNING: Ignoring origin brush in worldspawn\n"); - } else if (entity.epairs.has("origin")) { - logging::print("WARNING: Entity at line {} has multiple origin brushes\n", entity.mapbrushes.front().faces[0].linenum); - } else { - entity.origin = brush.bounds.centroid(); - entity.epairs.set("origin", qv::to_string(entity.origin)); - } - - it = entity.mapbrushes.erase(it); - continue; - } - - size_t old_num_faces = brush.faces.size(); - num_faces += old_num_faces; - - // add the brush bevels - AddBrushBevels(entity, brush); - - num_bevels += brush.faces.size() - old_num_faces; - it++; - } - - map.total_brushes += entity.mapbrushes.size(); - -#if 0 - qvec3d rotate_offset{}; - - /* Origin brush support */ - rotation_t rottype = rotation_t::none; - - for (auto &mapbrush : src->mapbrushes) { - const contentflags_t contents = Brush_GetContents(&mapbrush); - - if (contents.is_origin(qbsp_options.target_game)) { - if (dst == map.world_entity()) { - logging::print("WARNING: Ignoring origin brush in worldspawn\n"); - continue; - } - - std::optional brush = LoadBrush(src, &mapbrush, contents, {}, rotation_t::none, 0); - - if (brush) { - rotate_offset = brush->bounds.centroid(); - - dst->epairs.set("origin", qv::to_string(rotate_offset)); - - rottype = rotation_t::origin_brush; - } - } - } - - /* Hipnotic rotation */ - if (rottype == rotation_t::none) { - if (!Q_strncasecmp(classname, "rotate_", 7)) { - rotate_offset = FixRotateOrigin(dst); - rottype = rotation_t::hipnotic; - } - } - - - // 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 = (rotate_offset[0] != 0.0 || rotate_offset[1] != 0.0 || rotate_offset[2] != 0.0) && - rottype == 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 delta = std::max(fabs(max), fabs(min)); - hullbrush->bounds = {-delta, delta}; - } -#endif - } - - logging::print(logging::flag::STAT, " {:8} brushes\n", map.total_brushes); - logging::print(logging::flag::STAT, " {:8} faces\n", num_faces); - logging::print(logging::flag::STAT, " {:8} bevel faces\n", num_bevels); - logging::print(logging::flag::STAT, " {:8} entities\n", map.entities.size()); logging::print(logging::flag::STAT, " {:8} unique texnames\n", map.miptex.size()); logging::print(logging::flag::STAT, " {:8} texinfo\n", map.mtexinfos.size()); diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index cabef367..609a1886 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -305,7 +305,7 @@ static void ExportBrushList_r(const mapentity_t *entity, node_t *node) brush_state.total_leaf_brushes += node->numleafbrushes; node->firstleafbrush = map.bsp.dleafbrushes.size(); for (auto &b : node->original_brushes) { - map.bsp.dleafbrushes.push_back(b->outputnumber.value()); + map.bsp.dleafbrushes.push_back(b->mapbrush->outputnumber.value()); } } } @@ -324,14 +324,14 @@ static void ExportBrushList(mapentity_t *entity, node_t *node) brush_state = {}; for (auto &b : entity->brushes) { - b->outputnumber = {static_cast(map.bsp.dbrushes.size())}; + const_cast(b->mapbrush)->outputnumber = {static_cast(map.bsp.dbrushes.size())}; dbrush_t &brush = map.bsp.dbrushes.emplace_back( dbrush_t{static_cast(map.bsp.dbrushsides.size()), 0, b->contents.native}); for (auto &side : b->sides) { map.bsp.dbrushsides.push_back( - {(uint32_t) side.planenum, (int32_t)ExportMapTexinfo(side.texinfo)}); + {(uint32_t) ExportMapPlane(side.get_plane()), (int32_t)ExportMapTexinfo(side.texinfo)}); brush.numsides++; brush_state.total_brush_sides++; } @@ -928,6 +928,9 @@ void ProcessFile() return; } + // handle load time operation on the .map + ProcessMapBrushes(); + // initialize secondary textures LoadSecondaryTextures(); diff --git a/qbsp/tjunc.cc b/qbsp/tjunc.cc index d107bd8a..def95ca1 100644 --- a/qbsp/tjunc.cc +++ b/qbsp/tjunc.cc @@ -607,7 +607,6 @@ static std::list> RetopologizeFace(const face_t *f, const st for (; end != wrap; end = (end + 1) % input.size()) { auto v0 = input[seed]; - auto v1 = input[(end - 1) < 0 ? (input.size() - 1) : (end - 1)]; auto v2 = input[end]; // if the next point lays on the edge of v0-v2, this next diff --git a/tests/test_qbsp.cc b/tests/test_qbsp.cc index 73fec06f..4ff10464 100644 --- a/tests/test_qbsp.cc +++ b/tests/test_qbsp.cc @@ -43,8 +43,6 @@ static mapentity_t LoadMap(const char *map) mapentity_t worldspawn; - mapentity_t &entity = ::map.entities.emplace_back(); - // FIXME: adds the brush to the global map... Q_assert(ParseEntity(parser, &worldspawn)); @@ -55,6 +53,7 @@ static mapentity_t LoadMap(const char *map) #include +#if 0 static std::tuple> LoadTestmapRef(const std::filesystem::path &name) { const char *destdir = test_quake2_maps_dir; @@ -166,7 +165,7 @@ static std::tuple> LoadTestmapRe std::move(bspdata.bspx.entries), std::move(prtfile)); } - +#endif static std::tuple> LoadTestmap(const std::filesystem::path &name, std::vector extra_args = {}) { @@ -275,6 +274,7 @@ static void CheckFilled(const mbsp_t &bsp) } } +#if 0 static mbsp_t LoadBsp(const std::filesystem::path &path_in) { std::filesystem::path path = path_in; @@ -286,6 +286,7 @@ static mbsp_t LoadBsp(const std::filesystem::path &path_in) return std::get(bspdata.bsp); } +#endif static std::map> MakeTextureToFaceMap(const mbsp_t &bsp) { @@ -1175,7 +1176,7 @@ TEST_CASE("qbsp_func_detail various types", "[testmaps_q1]") { const qvec3d in_func_detail_illusionary{56, -216, 120}; const qvec3d in_func_detail_illusionary_mirrorinside{56, -296, 120}; - const double floor_z = 96; + //const double floor_z = 96; // detail clips away world faces, others don't CHECK(nullptr == BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], in_func_detail - qvec3d(0,0,24), {0, 0, 1}));