/* Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 1997 Greg Lewis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #include #include #include #include #include #include #include #include #include #include static bool ShouldOmitFace(face_t *f) { if (!options.includeskip.value() && map.mtexinfos.at(f->texinfo).flags.is_skip) return true; if (map.mtexinfos.at(f->texinfo).flags.is_hint) return true; // HACK: to save a few faces, don't output the interior faces of sky brushes if (f->contents[0].is_sky(options.target_game)) { return true; } return false; } static void MergeNodeFaces (node_t *node) { node->facelist = MergeFaceList(node->facelist); } /* =============== SubdivideFace If the face is >256 in either texture direction, carve a valid sized piece off and insert the remainder in the next link =============== */ std::list SubdivideFace(face_t *f) { vec_t mins, maxs; vec_t v; int axis; qbsp_plane_t plane; face_t *front, *back; const maptexinfo_t *tex; vec_t subdiv; vec_t extent; int lmshift; /* special (non-surface cached) faces don't need subdivision */ tex = &map.mtexinfos.at(f->texinfo); if (tex->flags.is_skip || tex->flags.is_hint || !options.target_game->surf_is_subdivided(tex->flags)) { return {f}; } // subdivision is pretty much pointless other than because of lightmap block limits // one lightmap block will always be added at the end, for smooth interpolation // engines that do support scaling will support 256*256 blocks (at whatever scale). lmshift = f->lmshift[0]; if (lmshift > 4) lmshift = 4; // no bugging out with legacy lighting // legacy engines support 18*18 max blocks (at 1:16 scale). // the 18*18 limit can be relaxed in certain engines, and doing so will generally give a performance boost. subdiv = min(options.subdivide.value(), 255 << lmshift); // subdiv += 8; // floating point precision from clipping means we should err on the low side // the bsp is possibly going to be used in both engines that support scaling and those that do not. this means we // always over-estimate by 16 rather than 1< surfaces{f}; for (axis = 0; axis < 2; axis++) { // we'll transfer faces that are chopped down to size to this list std::list chopped; while (!surfaces.empty()) { f = surfaces.front(); surfaces.pop_front(); mins = VECT_MAX; maxs = -VECT_MAX; qvec3d tmp = tex->vecs.row(axis).xyz(); for (int32_t i = 0; i < f->w.size(); i++) { v = qv::dot(f->w[i], tmp); if (v < mins) mins = v; if (v > maxs) maxs = v; } extent = ceil(maxs) - floor(mins); // extent = maxs - mins; if (extent <= subdiv) { // this face is already good chopped.push_back(f); continue; } // split it plane.normal = tmp; v = qv::normalizeInPlace(plane.normal); // ericw -- reverted this, was causing https://github.com/ericwa/ericw-tools/issues/160 // if (subdiv > extent/2) /* if we're near a boundary, just split the difference, this // should balance the load slightly */ // plane.dist = (mins + subdiv/2) / v; // else // plane.dist = (mins + subdiv) / v; plane.dist = (mins + subdiv - 16) / v; std::tie(front, back) = SplitFace(f, plane); if (!front || !back) { //logging::print("didn't split\n"); // FError("Didn't split the polygon"); } if (front) { surfaces.push_back(front); } if (back) { chopped.push_front(back); } } // we've finished chopping on this axis, but we may need to chop on other axes Q_assert(surfaces.empty()); surfaces = std::move(chopped); } return surfaces; } static void SubdivideNodeFaces(node_t *node) { std::list result; // subdivide each face and push the results onto subdivided for (face_t *face : node->facelist) { result.splice(result.end(), SubdivideFace(face)); } node->facelist = result; } static void FreeNode(node_t *node) { FreeTreePortals_r(node); for (face_t *f : node->facelist) { delete f; } delete node; } void FreeNodes(node_t *node) { if (node->planenum != PLANENUM_LEAF) { FreeNodes(node->children[0]); FreeNodes(node->children[1]); } FreeNode(node); } //=========================================================================== // This is a kludge. Should be pEdgeFaces[2]. static std::map pEdgeFaces0; static std::map pEdgeFaces1; //============================================================================ struct hashvert_t { qvec3d point; size_t num; }; using vertidx_t = size_t; using edgeidx_t = size_t; static std::map, std::list> hashedges; static std::map> hashverts; inline void InitHash() { pEdgeFaces0.clear(); pEdgeFaces1.clear(); hashverts.clear(); hashedges.clear(); } inline void AddHashEdge(size_t v1, size_t v2, size_t i) { hashedges[std::make_pair(v1, v2)].push_front(i); } inline qvec3i HashVec(const qvec3d &vec) { return {floor(vec[0]), floor(vec[1]), floor(vec[2])}; } inline void AddHashVert(const hashvert_t &hv) { // insert each vert at floor(pos[axis]) and floor(pos[axis]) + 1 (for each axis) // so e.g. a vert at (0.99, 0.99, 0.99) shows up if we search at (1.01, 1.01, 1.01) // this is a bit wasteful, since it inserts 8 copies of each vert. for (int x = 0; x <= 1; x++) { for (int y = 0; y <= 1; y++) { for (int z = 0; z <= 1; z++) { const qvec3i h{floor(hv.point[0]) + x, floor(hv.point[1]) + y, floor(hv.point[2]) + z}; hashverts[h].push_front(hv); } } } } /* ============= GetVertex ============= */ inline size_t GetVertex(qvec3d vert) { for (auto &v : vert) { double rounded = Q_rint(v); if (fabs(v - rounded) < ZERO_EPSILON) v = rounded; } const auto h = HashVec(vert); auto it = hashverts.find(h); if (it != hashverts.end()) { for (hashvert_t &hv : it->second) { if (fabs(hv.point[0] - vert[0]) < POINT_EPSILON && fabs(hv.point[1] - vert[1]) < POINT_EPSILON && fabs(hv.point[2] - vert[2]) < POINT_EPSILON) { return hv.num; } } } const size_t global_vert_num = map.bsp.dvertexes.size(); AddHashVert({vert, global_vert_num}); /* emit a vertex */ map.bsp.dvertexes.emplace_back(vert); return global_vert_num; } //=========================================================================== /* ================== GetEdge Don't allow four way edges (FIXME: What is this?) Returns a global edge number, possibly negative to indicate a backwards edge. ================== */ inline size_t GetEdge(mapentity_t *entity, const qvec3d &p1, const qvec3d &p2, const face_t *face) { if (!face->contents[0].is_valid(options.target_game, false)) FError("Face with invalid contents"); size_t v1 = GetVertex(p1); size_t v2 = GetVertex(p2); // search for an existing edge from v2->v1 const std::pair edge_hash_key = std::make_pair(v2, v1); auto it = hashedges.find(edge_hash_key); if (it != hashedges.end()) { for (const int i : it->second) { if (pEdgeFaces1[i] == NULL && pEdgeFaces0[i]->contents[0].native == face->contents[0].native) { pEdgeFaces1[i] = face; return -i; } } } /* emit an edge */ size_t i = map.bsp.dedges.size(); map.bsp.dedges.emplace_back(bsp2_dedge_t{static_cast(v1), static_cast(v2)}); AddHashEdge(v1, v2, i); pEdgeFaces0[i] = face; return i; } static void FindFaceFragmentEdges(mapentity_t *entity, face_t *face, face_fragment_t *fragment) { fragment->outputnumber = std::nullopt; if (fragment->w.size() > MAXEDGES) { FError("Internal error: face->numpoints > MAXEDGES"); } fragment->edges.resize(fragment->w.size()); for (size_t i = 0; i < fragment->w.size(); i++) { const qvec3d &p1 = fragment->w[i]; const qvec3d &p2 = fragment->w[(i + 1) % fragment->w.size()]; fragment->edges[i] = GetEdge(entity, p1, p2, face); } } /* ================== FindFaceEdges ================== */ static void FindFaceEdges(mapentity_t *entity, face_t *face) { if (ShouldOmitFace(face)) return; FindFaceFragmentEdges(entity, face, face); for (auto &fragment : face->fragments) { FindFaceFragmentEdges(entity, face, &fragment); } } /* ================ MakeFaceEdges_r ================ */ static int MakeFaceEdges_r(mapentity_t *entity, node_t *node, int progress) { if (node->planenum == PLANENUM_LEAF) return progress; for (face_t *f : node->facelist) { FindFaceEdges(entity, f); } logging::percent(progress, splitnodes, entity); progress = MakeFaceEdges_r(entity, node->children[0], progress); progress = MakeFaceEdges_r(entity, node->children[1], progress); return progress; } /* ============== EmitFaceFragment ============== */ static void EmitFaceFragment(mapentity_t *entity, face_t *face, face_fragment_t *fragment) { int i; // emit a region Q_assert(!fragment->outputnumber.has_value()); fragment->outputnumber = map.bsp.dfaces.size(); mface_t &out = map.bsp.dfaces.emplace_back(); // emit lmshift map.exported_lmshifts.push_back(face->lmshift[1]); Q_assert(map.bsp.dfaces.size() == map.exported_lmshifts.size()); out.planenum = ExportMapPlane(face->planenum); out.side = face->planeside; out.texinfo = ExportMapTexinfo(face->texinfo); for (i = 0; i < MAXLIGHTMAPS; i++) out.styles[i] = 255; out.lightofs = -1; // emit surfedges out.firstedge = static_cast(map.bsp.dsurfedges.size()); std::copy(fragment->edges.cbegin(), fragment->edges.cbegin() + fragment->w.size(), std::back_inserter(map.bsp.dsurfedges)); fragment->edges.clear(); out.numedges = static_cast(map.bsp.dsurfedges.size()) - out.firstedge; } /* ============== EmitFace ============== */ static void EmitFace(mapentity_t *entity, face_t *face) { if (ShouldOmitFace(face)) return; EmitFaceFragment(entity, face, face); for (auto &fragment : face->fragments) { EmitFaceFragment(entity, face, &fragment); } } /* ============== GrowNodeRegion ============== */ static void GrowNodeRegion(mapentity_t *entity, node_t *node) { if (node->planenum == PLANENUM_LEAF) return; node->firstface = static_cast(map.bsp.dfaces.size()); for (face_t *face : node->facelist) { //Q_assert(face->planenum == node->planenum); // emit a region EmitFace(entity, face); } node->numfaces = static_cast(map.bsp.dfaces.size()) - node->firstface; GrowNodeRegion(entity, node->children[0]); GrowNodeRegion(entity, node->children[1]); } static void CountFace(mapentity_t *entity, face_t *f, size_t &facesCount, size_t &vertexesCount) { if (ShouldOmitFace(f)) return; if (f->lmshift[1] != 4) map.needslmshifts = true; facesCount++; vertexesCount += f->w.size(); } /* ============== CountData_r ============== */ static void CountData_r(mapentity_t *entity, node_t *node, size_t &facesCount, size_t &vertexesCount) { if (node->planenum == PLANENUM_LEAF) return; for (face_t *f : node->facelist) { CountFace(entity, f, facesCount, vertexesCount); } CountData_r(entity, node->children[0], facesCount, vertexesCount); CountData_r(entity, node->children[1], facesCount, vertexesCount); } /* ================ MakeFaceEdges ================ */ int MakeFaceEdges(mapentity_t *entity, node_t *headnode) { int firstface; logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); Q_assert(entity->firstoutputfacenumber == -1); entity->firstoutputfacenumber = static_cast(map.bsp.dfaces.size()); size_t facesCount = 0, vertexesCount = 0; CountData_r(entity, headnode, facesCount, vertexesCount); // Accessory data InitHash(); firstface = static_cast(map.bsp.dfaces.size()); MakeFaceEdges_r(entity, headnode, 0); logging::percent(splitnodes, splitnodes, entity == map.world_entity()); pEdgeFaces0.clear(); pEdgeFaces1.clear(); logging::print(logging::flag::PROGRESS, "---- GrowRegions ----\n"); GrowNodeRegion(entity, headnode); return firstface; } //=========================================================================== static int c_nodefaces; /* ================ AddMarksurfaces_r Adds the given face to the markfaces lists of all descendant leafs of `node`. fixme-brushbsp: all leafs in a cluster can share the same marksurfaces, right? ================ */ static void AddMarksurfaces_r(face_t *face, face_t *face_copy, node_t *node) { if (node->planenum == PLANENUM_LEAF) { node->markfaces.push_back(face); return; } const qbsp_plane_t &splitplane = map.planes.at(node->planenum); auto [frontFragment, backFragment] = SplitFace(face_copy, splitplane); if (frontFragment) { AddMarksurfaces_r(face, frontFragment, node->children[0]); } if (backFragment) { AddMarksurfaces_r(face, backFragment, node->children[1]); } } /* ================ MakeMarkFaces Populates the `markfaces` vectors of all leafs ================ */ void MakeMarkFaces(mapentity_t* entity, node_t* node) { if (node->planenum == PLANENUM_LEAF) { return; } // for the faces on this splitting node.. for (face_t *face : node->facelist) { // add this face to all descendant leafs it touches // make a copy we can clip face_t *face_copy = CopyFace(face); if (face->planeside == 0) { AddMarksurfaces_r(face, face_copy, node->children[0]); } else { AddMarksurfaces_r(face, face_copy, node->children[1]); } } // process child nodes recursively MakeMarkFaces(entity, node->children[0]); MakeMarkFaces(entity, node->children[1]); } // the fronts of `faces` are facing `node`, determine what gets clipped away // (return the post-clipping result) static std::list ClipFacesToTree_r(node_t *node, const brush_t *srcbrush, std::list faces) { if (node->planenum == PLANENUM_LEAF) { // fixme-brushbsp: move to contentflags_t? if (node->contents.is_solid(options.target_game) || node->contents.is_detail_solid(options.target_game) || node->contents.is_sky(options.target_game)) { // solids eat any faces that reached this point return {}; } // see what the game thinks about the clip if (srcbrush->contents.will_clip_same_type(options.target_game, node->contents)) { return {}; } // other content types let the faces thorugh return faces; } const qbsp_plane_t &splitplane = map.planes.at(node->planenum); std::list front, back; for (auto *face : faces) { auto [frontFragment, backFragment] = SplitFace(face, splitplane); if (frontFragment) { front.push_back(frontFragment); } if (backFragment) { back.push_back(backFragment); } } front = ClipFacesToTree_r(node->children[0], srcbrush, front); back = ClipFacesToTree_r(node->children[1], srcbrush, back); // merge lists front.splice(front.end(), back); return front; } static std::list ClipFacesToTree(node_t *node, const brush_t *srcbrush, std::list faces) { // handles the first level - faces are all supposed to be lying exactly on `node` for (auto *face : faces) { Q_assert(face->planenum == node->planenum); } std::list front, back; for (auto *face : faces) { if (face->planeside == 0) { front.push_back(face); } else { back.push_back(face); } } front = ClipFacesToTree_r(node->children[0], srcbrush, front); back = ClipFacesToTree_r(node->children[1], srcbrush, back); // merge lists front.splice(front.end(), back); return front; } static void AddFaceToTree_r(mapentity_t* entity, face_t *face, brush_t *srcbrush, node_t* node) { if (node->planenum == PLANENUM_LEAF) { //FError("couldn't find node for face"); // after outside filling, this is completely expected return; } if (face->planenum == node->planenum) { // found the correct plane - add the face to it. ++c_nodefaces; // csg it std::list faces = CSGFace(face, entity, srcbrush, node); // clip them to the descendant parts of the BSP // (otherwise we could have faces floating in the void on this node) faces = ClipFacesToTree(node, srcbrush, faces); for (face_t *part : faces) { node->facelist.push_back(part); if (srcbrush->contents.is_mirrored(options.target_game)) { node->facelist.push_back(MirrorFace(part)); } } return; } // fixme-brushbsp: we need to handle the case of the face being near enough that it gets clipped away, // but not face->planenum == node->planenum auto [frontWinding, backWinding] = face->w.clip(map.planes.at(node->planenum)); if (frontWinding) { auto *newFace = new face_t{*face}; newFace->w = *frontWinding; AddFaceToTree_r(entity, newFace, srcbrush, node->children[0]); } if (backWinding) { auto *newFace = new face_t{*face}; newFace->w = *backWinding; AddFaceToTree_r(entity, newFace, srcbrush, node->children[1]); } delete face; } /* ================ MakeVisibleFaces Given a completed BSP tree and a list of the original brushes (in `entity`), - filters the brush faces into the BSP, finding the correct nodes they end up on - clips the faces by other brushes. first iteration, we can just do an exhaustive check against all brushes ================ */ void MakeVisibleFaces(mapentity_t* entity, node_t* headnode) { c_nodefaces = 0; for (auto &brush : entity->brushes) { for (auto &face : brush->faces) { if (!face.visible) { continue; } face_t *temp = CopyFace(&face); AddFaceToTree_r(entity, temp, brush.get(), headnode); } } logging::print(logging::flag::STAT, "{} nodefaces\n", c_nodefaces); } struct makefaces_stats_t { int c_nodefaces; int c_merge; int c_subdivide; }; /* ============ FaceFromPortal pside is which side of portal (equivalently, which side of the node) we're in. Typically, we're in an empty leaf and the other side of the portal is a solid wall. ============ */ static face_t *FaceFromPortal(portal_t *p, int pside) { face_t *side = p->side; if (!side) return nullptr; // portal does not bridge different visible contents face_t *f = new face_t{}; f->texinfo = side->texinfo; f->planenum = side->planenum; f->planeside = static_cast(pside); f->portal = p; f->lmshift = side->lmshift; // don't show insides of windows // fixme-brushbsp: restore this? // if (!side->contents[1].is_mirrored(options.target_game)) // return nullptr; if (pside) { f->w = p->winding->flip(); // fixme-brushbsp: was just `f->contents` on qbsp3 f->contents[1] = p->nodes[1]->contents; } else { f->w = *p->winding; f->contents[1] = p->nodes[0]->contents; } UpdateFaceSphere(f); return f; } /* =============== MakeFaces_r If a portal will make a visible face, mark the side that originally created it solid / empty : solid solid / water : solid water / empty : water water / water : none =============== */ static void MakeFaces_r(node_t *node, makefaces_stats_t& stats) { // recurse down to leafs if (node->planenum != PLANENUM_LEAF) { MakeFaces_r(node->children[0], stats); MakeFaces_r(node->children[1], stats); // merge together all visible faces on the node if (!options.nomerge.value()) MergeNodeFaces(node); if (options.subdivide.boolValue()) SubdivideNodeFaces(node); return; } // solid leafs never have visible faces if (node->contents.is_any_solid(options.target_game)) return; // see which portals are valid // (Note, this is happening per leaf, so we can potentially generate faces // for the same portal once from one leaf, and once from the neighbouring one) int s; for (portal_t *p = node->portals; p; p = p->next[s]) { // 1 means node is on the back side of planenum s = (p->nodes[1] == node); face_t *f = FaceFromPortal(p, s); if (f) { stats.c_nodefaces++; p->face[s] = f; p->onnode->facelist.push_back(f); } } } /* ============ MakeFaces ============ */ void MakeFaces(node_t *node) { logging::print("--- {} ---\n", __func__); makefaces_stats_t stats{}; MakeFaces_r(node, stats); logging::print(logging::flag::STAT, "{} makefaces\n", stats.c_nodefaces); logging::print(logging::flag::STAT, "{} merged\n", stats.c_merge); logging::print(logging::flag::STAT, "{} subdivided\n", stats.c_subdivide); }