diff --git a/common/bspfile.cc b/common/bspfile.cc index 5c0050da..779d88f3 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -97,7 +97,6 @@ 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); } @@ -348,7 +347,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") { hull_extents = 40; } + gamedef_h2_t() : gamedef_q1_like_t("DATA1") { } const std::initializer_list &get_hull_sizes() const { @@ -403,7 +402,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; hull_extents = 36; } + gamedef_hl_t() : gamedef_q1_like_t("VALVE") { has_rgb_lightmap = true; } const std::initializer_list &get_hull_sizes() const { diff --git a/include/common/aabb.hh b/include/common/aabb.hh index e664f580..a6b341e4 100644 --- a/include/common/aabb.hh +++ b/include/common/aabb.hh @@ -29,7 +29,6 @@ class aabb { public: using value_type = qvec; - using value_value_type = typename value_type::value_type; class intersection_t { @@ -184,20 +183,6 @@ 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 5e5ac2b7..23a6d4d7 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -1766,12 +1766,7 @@ struct gamedef_t size_t max_entity_key = 32; size_t max_entity_value = 128; - // 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) - { - } + 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 93acc2c7..26a93d0a 100644 --- a/include/qbsp/brush.hh +++ b/include/qbsp/brush.hh @@ -24,17 +24,16 @@ #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 82e2b0a2..e95e5bd8 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -71,7 +71,6 @@ public: int numfaces = 0; brushformat_t format = brushformat_t::NORMAL; int contents = 0; - vec_t extents = 0; const mapface_t &face(int i) const; }; @@ -159,6 +158,8 @@ 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 c7e21486..e89de399 100644 --- a/include/qbsp/winding.hh +++ b/include/qbsp/winding.hh @@ -24,3 +24,5 @@ #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 9f3d6967..17580955 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -91,6 +91,10 @@ 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) @@ -305,7 +309,7 @@ static std::vector CreateBrushFaces(const mapentity_t *src, hullbrush_t continue; } - w = winding_t::from_plane(mapface.plane, hullbrush->srcbrush->extents); + w = BaseWindingForPlane(mapface.plane); for (auto &mapface2 : hullbrush->faces) { if (&mapface == &mapface2) @@ -702,8 +706,6 @@ 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 28a4e15b..6a00420d 100644 --- a/qbsp/map.cc +++ b/qbsp/map.cc @@ -40,67 +40,6 @@ #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 @@ -1634,13 +1573,6 @@ 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; } @@ -2229,6 +2161,94 @@ 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 @@ -2256,7 +2276,7 @@ void WriteBspBrushMap(const std::filesystem::path &name, const std::vectorextents); + winding_t w = BaseWindingForPlane(plane); fmt::print(f, "( {} ) ", w[0]); fmt::print(f, "( {} ) ", w[1]); diff --git a/qbsp/portals.cc b/qbsp/portals.cc index f39b92a8..edc02bcc 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -364,7 +364,6 @@ 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(); @@ -390,7 +389,7 @@ static void MakeHeadnodePortals(const mapentity_t *entity, node_t *node) } p->planenum = FindPlane(pl, &side); - p->winding = winding_t::from_plane(pl, extents); + p->winding = BaseWindingForPlane(pl); if (side) AddPortalToNodes(p, &outside_node, node); else @@ -528,7 +527,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 = winding_t::from_plane(plane, node->bounds.extents() + options.target_game->hull_extents); + std::optional winding = BaseWindingForPlane(plane); 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 fa7b337b..50d1a804 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -472,6 +472,11 @@ 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 @@ -985,6 +990,11 @@ 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 b9ef0ddd..79c5712e 100644 --- a/qbsp/test_qbsp.cc +++ b/qbsp/test_qbsp.cc @@ -29,6 +29,8 @@ static mapentity_t LoadMap(const char *map) // FIXME: adds the brush to the global map... Q_assert(ParseEntity(parser, &worldspawn)); + CalculateWorldExtent(); + return worldspawn; }