diff --git a/common/bspfile.cc b/common/bspfile.cc index 779d88f3..5c0050da 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -97,6 +97,7 @@ struct gamedef_q1_like_t : public gamedef_t gamedef_q1_like_t(const char *base_dir = "ID1") : gamedef_t(base_dir) { this->id = ID; + hull_extents = 64; } bool surf_is_lightmapped(const surfflags_t &flags) const { return !(flags.native & TEX_SPECIAL); } @@ -347,7 +348,7 @@ struct gamedef_q1_like_t : public gamedef_t struct gamedef_h2_t : public gamedef_q1_like_t { - gamedef_h2_t() : gamedef_q1_like_t("DATA1") { } + gamedef_h2_t() : gamedef_q1_like_t("DATA1") { hull_extents = 40; } const std::initializer_list &get_hull_sizes() const { @@ -402,7 +403,7 @@ struct gamedef_h2_t : public gamedef_q1_like_t struct gamedef_hl_t : public gamedef_q1_like_t { - gamedef_hl_t() : gamedef_q1_like_t("VALVE") { has_rgb_lightmap = true; } + gamedef_hl_t() : gamedef_q1_like_t("VALVE") { has_rgb_lightmap = true; hull_extents = 36; } const std::initializer_list &get_hull_sizes() const { diff --git a/include/common/aabb.hh b/include/common/aabb.hh index a6b341e4..e664f580 100644 --- a/include/common/aabb.hh +++ b/include/common/aabb.hh @@ -29,6 +29,7 @@ class aabb { public: using value_type = qvec; + using value_value_type = typename value_type::value_type; class intersection_t { @@ -183,6 +184,20 @@ public: constexpr value_type centroid() const { return (m_mins + m_maxs) * 0.5; } + constexpr value_value_type extents() const + { + value_value_type extent = -std::numeric_limits::infinity(); + + for (auto &v : m_mins) { + extent = max(extent, v); + } + for (auto &v : m_maxs) { + extent = max(extent, v); + } + + return extent; + } + // stream support auto stream_data() { return std::tie(m_mins, m_maxs); } }; diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 23a6d4d7..5e5ac2b7 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -1766,7 +1766,12 @@ struct gamedef_t size_t max_entity_key = 32; size_t max_entity_value = 128; - gamedef_t(const char *default_base_dir) : default_base_dir(default_base_dir) { } + // maximum extent of hulls; used for world extent calculations + vec_t hull_extents = 0; + + gamedef_t(const char *default_base_dir) : default_base_dir(default_base_dir) + { + } virtual bool surf_is_lightmapped(const surfflags_t &flags) const = 0; virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0; diff --git a/include/qbsp/brush.hh b/include/qbsp/brush.hh index 26a93d0a..93acc2c7 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -24,16 +24,17 @@ #include #include +class mapbrush_t; + struct brush_t { + const mapbrush_t *src; aabb3d bounds; std::vector faces; contentflags_t contents; /* BSP contents */ short lmshift; /* lightmap scaling (qu/lightmap pixel), passed to the light util */ }; -class mapbrush_t; - qplane3d Face_Plane(const face_t *face); enum class rotation_t diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index e95e5bd8..82e2b0a2 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -71,6 +71,7 @@ public: int numfaces = 0; brushformat_t format = brushformat_t::NORMAL; int contents = 0; + vec_t extents = 0; const mapface_t &face(int i) const; }; @@ -158,8 +159,6 @@ struct mapdata_t extern mapdata_t map; -void CalculateWorldExtent(void); - extern mapentity_t *pWorldEnt(); bool ParseEntity(parser_t &parser, mapentity_t *entity); diff --git a/include/qbsp/winding.hh b/include/qbsp/winding.hh index e89de399..c7e21486 100644 --- a/include/qbsp/winding.hh +++ b/include/qbsp/winding.hh @@ -24,5 +24,3 @@ #include "common/polylib.hh" using winding_t = polylib::winding_base_t; - -winding_t BaseWindingForPlane(const qplane3d &p); \ No newline at end of file diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 17580955..9f3d6967 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -91,10 +91,6 @@ static void CheckFace(face_t *face, const mapface_t &sourceface) const qvec3d &p1 = face->w[i]; const qvec3d &p2 = face->w[(i + 1) % face->w.size()]; - for (auto &v : p1) - if (v > options.worldExtent || v < -options.worldExtent) - FError("line {}: coordinate out of range ({})", sourceface.linenum, v); - /* check the point is on the face plane */ vec_t dist = plane.distance_to(p1); if (dist < -ON_EPSILON || dist > ON_EPSILON) @@ -309,7 +305,7 @@ static std::vector CreateBrushFaces(const mapentity_t *src, hullbrush_t continue; } - w = BaseWindingForPlane(mapface.plane); + w = winding_t::from_plane(mapface.plane, hullbrush->srcbrush->extents); for (auto &mapface2 : hullbrush->faces) { if (&mapface == &mapface2) @@ -706,6 +702,8 @@ std::optional LoadBrush(const mapentity_t *src, const mapbrush_t *mapbr const qvec3d &rotate_offset, const rotation_t rottype, const int hullnum) { hullbrush_t hullbrush; + hullbrush.srcbrush = mapbrush; + std::vector facelist; // create the faces diff --git a/qbsp/map.cc b/qbsp/map.cc index 6a00420d..28a4e15b 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -40,6 +40,67 @@ #include +/* +================= +GetBrushExtents +================= +*/ +inline std::optional GetIntersection(const qbsp_plane_t &p1, const qbsp_plane_t &p2, const qbsp_plane_t &p3) +{ + const vec_t denom = qv::dot(p1.normal, qv::cross(p2.normal, p3.normal)); + + if (denom == 0.f) { + return std::nullopt; + } + + return (qv::cross(p2.normal, p3.normal) * p1.dist - qv::cross(p3.normal, p1.normal) * -p2.dist - qv::cross(p1.normal, p2.normal) * -p3.dist) / denom; +} + +#include "tbb/parallel_for.h" +#include + +inline vec_t CalculateBrushExtents(const mapbrush_t &hullbrush) +{ + vec_t extents = -std::numeric_limits::infinity(); + + for (int32_t i = 0; i < hullbrush.numfaces - 2; i++) { + for (int32_t j = i; j < hullbrush.numfaces - 1; j++) { + for (int32_t k = j; k < hullbrush.numfaces; k++) { + if (i == j || j == k || k == i) { + continue; + } + + auto &fi = hullbrush.face(i); + auto &fj = hullbrush.face(j); + auto &fk = hullbrush.face(k); + + bool legal = true; + auto vertex = GetIntersection(fi.plane, fj.plane, fk.plane); + + if (!vertex) { + continue; + } + + for (int32_t m = 0; m < hullbrush.numfaces; m++) { + if (hullbrush.face(m).plane.distance_to(*vertex) > NORMAL_EPSILON) { + legal = false; + break; + } + } + + if (legal) { + + for (auto &p : *vertex) { + extents = max(extents, fabs(p)); + } + } + } + } + } + + return extents + options.target_game->hull_extents; +} + #define info_player_start 1 #define info_player_deathmatch 2 #define info_player_coop 4 @@ -1573,6 +1634,13 @@ mapbrush_t ParseBrush(parser_t &parser, const mapentity_t *entity) } // ericw -- end brush primitives + // calculate extents, if required + if (!options.worldExtent) { + brush.extents = CalculateBrushExtents(brush); + } else { + brush.extents = options.worldExtent; + } + return brush; } @@ -2161,94 +2229,6 @@ void WriteEntitiesToString() } } -//==================================================================== - -inline std::optional GetIntersection(const qbsp_plane_t &p1, const qbsp_plane_t &p2, const qbsp_plane_t &p3) -{ - const vec_t denom = qv::dot(p1.normal, qv::cross(p2.normal, p3.normal)); - - if (denom == 0.f) { - return std::nullopt; - } - - return (qv::cross(p2.normal, p3.normal) * p1.dist - qv::cross(p3.normal, p1.normal) * -p2.dist - qv::cross(p1.normal, p2.normal) * -p3.dist) / denom; -} - -/* -================= -GetBrushExtents -================= -*/ -inline vec_t GetBrushExtents(const mapbrush_t &hullbrush) -{ - vec_t extents = -std::numeric_limits::infinity(); - - for (int32_t i = 0; i < hullbrush.numfaces - 2; i++) { - for (int32_t j = i; j < hullbrush.numfaces - 1; j++) { - for (int32_t k = j; k < hullbrush.numfaces; k++) { - if (i == j || j == k || k == i) { - continue; - } - - auto &fi = hullbrush.face(i); - auto &fj = hullbrush.face(j); - auto &fk = hullbrush.face(k); - - bool legal = true; - auto vertex = GetIntersection(fi.plane, fj.plane, fk.plane); - - if (!vertex) { - continue; - } - - for (int32_t m = 0; m < hullbrush.numfaces; m++) { - if (hullbrush.face(m).plane.distance_to(*vertex) > NORMAL_EPSILON) { - legal = false; - break; - } - } - - if (legal) { - - for (auto &p : *vertex) { - extents = max(extents, fabs(p)); - } - } - } - } - } - - return extents; -} - -#include "tbb/parallel_for.h" -#include - -void CalculateWorldExtent(void) -{ - LogPrint("Calculating world extents... "); - - std::atomic extents = -std::numeric_limits::infinity(); - - tbb::parallel_for(static_cast(0), static_cast(map.numbrushes()), [&](const size_t &i) { - const auto &brush = map.brushes[i]; - const vec_t brushExtents = max(extents.load(), GetBrushExtents(brush)); - vec_t currentExtents = extents; - while (currentExtents < brushExtents && !extents.compare_exchange_weak(currentExtents, brushExtents)); - }); - - vec_t hull_extents = 0; - - for (auto &hull : options.target_game->get_hull_sizes()) { - for (auto &v : hull.size()) { - hull_extents = max(hull_extents, fabs(v)); - } - } - - options.worldExtent = extents + hull_extents; - LogPrint("{} units\n", options.worldExtent); -} - /* ================== WriteBspBrushMap @@ -2276,7 +2256,7 @@ void WriteBspBrushMap(const std::filesystem::path &name, const std::vectorextents); fmt::print(f, "( {} ) ", w[0]); fmt::print(f, "( {} ) ", w[1]); diff --git a/qbsp/portals.cc b/qbsp/portals.cc index edc02bcc..f39b92a8 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -364,6 +364,7 @@ static void MakeHeadnodePortals(const mapentity_t *entity, node_t *node) // pad with some space so there will never be null volume leafs aabb3d bounds = entity->bounds.grow(SIDESPACE); + vec_t extents = bounds.extents() + options.target_game->hull_extents; outside_node.planenum = PLANENUM_LEAF; outside_node.contents = options.target_game->create_solid_contents(); @@ -389,7 +390,7 @@ static void MakeHeadnodePortals(const mapentity_t *entity, node_t *node) } p->planenum = FindPlane(pl, &side); - p->winding = BaseWindingForPlane(pl); + p->winding = winding_t::from_plane(pl, extents); if (side) AddPortalToNodes(p, &outside_node, node); else @@ -527,7 +528,7 @@ static void CutNodePortals_r(node_t *node, portal_state_t *state) new_portal = new portal_t{}; new_portal->planenum = node->planenum; - std::optional winding = BaseWindingForPlane(plane); + std::optional winding = winding_t::from_plane(plane, node->bounds.extents() + options.target_game->hull_extents); for (portal = node->portals; portal; portal = portal->next[side]) { clipplane = map.planes[portal->planenum]; if (portal->nodes[0] == node) diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 50d1a804..fa7b337b 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -472,11 +472,6 @@ static void EmitAreaPortals(node_t *headnode) LogPrint(LOG_STAT, "{:5} numareaportals\n", map.bsp.dareaportals.size()); } -winding_t BaseWindingForPlane(const qplane3d &p) -{ - return winding_t::from_plane(p, options.worldExtent); -} - /* =============== ProcessEntity @@ -990,11 +985,6 @@ static void ProcessFile(void) log_mask &= ~((1 << LOG_STAT) | (1 << LOG_PROGRESS)); } - // calculate extents, if required - if (!options.worldExtent) { - CalculateWorldExtent(); - } - // create hulls! CreateHulls(); diff --git a/qbsp/test_qbsp.cc b/qbsp/test_qbsp.cc index 79c5712e..b9ef0ddd 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -29,8 +29,6 @@ static mapentity_t LoadMap(const char *map) // FIXME: adds the brush to the global map... Q_assert(ParseEntity(parser, &worldspawn)); - CalculateWorldExtent(); - return worldspawn; }