diff --git a/include/qbsp/faces.hh b/include/qbsp/faces.hh index 9c6ec57b..437cad46 100644 --- a/include/qbsp/faces.hh +++ b/include/qbsp/faces.hh @@ -24,8 +24,6 @@ #include struct node_t; -class mapentity_t; -struct face_t; -void MakeMarkFaces(mapentity_t* entity, node_t* headnode); +void MakeMarkFaces(node_t* headnode); void MakeFaces(node_t *node); diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 22d242a4..64f78e3a 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -127,6 +127,12 @@ struct maptexdata_t extern std::recursive_mutex map_planes_lock; +struct hashvert_t +{ + qvec3d point; + size_t num; +}; + struct mapdata_t { /* Arrays of actual items */ @@ -143,6 +149,53 @@ struct mapdata_t /* map from plane hash code to list of indicies in `planes` vector */ std::unordered_map> planehash; + // hashed vertices; generated by EmitVertices + std::map> hashverts; + + // find vector of points in hash closest to vec + inline auto find_hash_vector(const qvec3d &vec) + { + return hashverts.find({floor(vec[0]), floor(vec[1]), floor(vec[2])}); + } + + // find output index for specified already-output vector. + inline std::optional find_emitted_hash_vector(const qvec3d &vert) + { + if (auto it = find_hash_vector(vert); it != hashverts.end()) { + for (hashvert_t &hv : it->second) { + if (qv::epsilonEqual(hv.point, vert, POINT_EQUAL_EPSILON)) { + return hv.num; + } + } + } + + return std::nullopt; + } + + // add vector to hash + inline void add_hash_vector(const qvec3d &point, const size_t &num) + { + // 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 (int32_t x = 0; x <= 1; x++) { + for (int32_t y = 0; y <= 1; y++) { + for (int32_t z = 0; z <= 1; z++) { + const qvec3i h{floor(point[0]) + x, floor(point[1]) + y, floor(point[2]) + z}; + hashverts[h].push_front({ point, num }); + } + } + } + } + + // hashed edges; generated by EmitEdges + std::map, int64_t> hashedges; + + inline void add_hash_edge(size_t v1, size_t v2, int64_t i) + { + hashedges.emplace(std::make_pair(v1, v2), i); + } + /* Misc other global state for the compile process */ bool leakfile = false; /* Flag once we've written a leak (.por/.pts) file */ @@ -226,7 +279,8 @@ void Brush_LoadEntity(mapentity_t *entity, const int hullnum); std::list CSGFace(face_t *srcface, const mapentity_t* srcentity, const bspbrush_t *srcbrush, const node_t *srcnode); void TJunc(const mapentity_t *entity, node_t *headnode); -int MakeFaceEdges(mapentity_t *entity, node_t *headnode); +int MakeFaceEdges(node_t *headnode); +void EmitVertices(node_t *headnode); void ExportClipNodes(mapentity_t *entity, node_t *headnode, const int hullnum); void ExportDrawNodes(mapentity_t *entity, node_t *headnode, int firstface); diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index 06c9ba05..59e7d870 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -294,7 +294,6 @@ constexpr int32_t PLANENUM_LEAF = -1; constexpr vec_t ANGLEEPSILON = 0.000001; constexpr vec_t ZERO_EPSILON = 0.0001; constexpr vec_t DISTEPSILON = 0.0001; -constexpr vec_t POINT_EPSILON = 0.0001; constexpr vec_t EQUAL_EPSILON = 0.0001; constexpr vec_t T_EPSILON = 0.0002; constexpr vec_t CONTINUOUS_EPSILON = 0.0005; diff --git a/qbsp/faces.cc b/qbsp/faces.cc index d5c1494f..cc2d354a 100644 --- a/qbsp/faces.cc +++ b/qbsp/faces.cc @@ -46,120 +46,104 @@ static bool ShouldOmitFace(face_t *f) return false; } -static void MergeNodeFaces (node_t *node) +static void MergeNodeFaces(node_t *node) { node->facelist = MergeFaceList(std::move(node->facelist)); } -//=========================================================================== - -// This is a kludge. Should be pEdgeFaces[2]. -static std::map pEdgeFaces0; -static std::map pEdgeFaces1; - -//============================================================================ - -struct hashvert_t -{ - qvec3d point; - size_t num; -}; - -static std::map, int64_t> hashedges; -static std::map> hashverts; - -inline void InitHash() -{ - pEdgeFaces0.clear(); - pEdgeFaces1.clear(); - hashverts.clear(); - hashedges.clear(); -} - -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 +EmitVertex +NOTE: modifies input to be rounded! ============= */ -inline size_t GetVertex(qvec3d vert) +inline void EmitVertex(qvec3d &vert) { + // if we're extremely close to an integral point, + // snap us to it. for (auto &v : vert) { double rounded = Q_rint(v); - if (fabs(v - rounded) < ZERO_EPSILON) + 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(); + // already added + if (auto v = map.find_emitted_hash_vector(vert)) { + return; + } - AddHashVert({vert, global_vert_num}); + // add new vertex! + map.add_hash_vector(vert, map.bsp.dvertexes.size()); - /* emit a vertex */ map.bsp.dvertexes.emplace_back(vert); +} - return global_vert_num; +// snap windings & output final vertices +static void EmitFaceVertices(face_t *f) +{ + if (ShouldOmitFace(f)) { + return; + } + + for (auto &p : f->w) { + EmitVertex(p); + } + + for (auto &frag : f->fragments) { + for (auto &p : frag.w) { + EmitVertex(p); + } + } +} + +static void EmitVertices_R(node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) { + return; + } + + for (auto &f : node->facelist) { + EmitFaceVertices(f.get()); + } + + EmitVertices_R(node->children[0].get()); + EmitVertices_R(node->children[1].get()); +} + +void EmitVertices(node_t *headnode) +{ + EmitVertices_R(headnode); } //=========================================================================== -inline void AddHashEdge(size_t v1, size_t v2, int64_t i) -{ - hashedges.emplace(std::make_pair(v1, v2), i); -} - /* ================== GetEdge -Don't allow four way edges (FIXME: What is this?) - Returns a global edge number, possibly negative to indicate a backwards edge. ================== */ -inline int64_t GetEdge(mapentity_t *entity, const qvec3d &p1, const qvec3d &p2, const face_t *face) +inline int64_t GetEdge(const qvec3d &p1, const qvec3d &p2, const face_t *face) { if (!face->contents.is_valid(options.target_game, false)) FError("Face with invalid contents"); - size_t v1 = GetVertex(p1); - size_t v2 = GetVertex(p2); + auto v1o = map.find_emitted_hash_vector(p1); + auto v2o = map.find_emitted_hash_vector(p2); + + if (!v1o || !v2o) { + FError("invalid output vertex"); + } + + size_t v1 = *v1o; + size_t v2 = *v2o; // search for existing edges - if (auto it = hashedges.find(std::make_pair(v1, v2)); it != hashedges.end()) { + if (auto it = map.hashedges.find(std::make_pair(v1, v2)); it != map.hashedges.end()) { return it->second; - } else if (auto it = hashedges.find(std::make_pair(v2, v1)); it != hashedges.end()) { + } else if (auto it = map.hashedges.find(std::make_pair(v2, v1)); it != map.hashedges.end()) { return -it->second; } @@ -168,13 +152,12 @@ inline int64_t GetEdge(mapentity_t *entity, const qvec3d &p1, const qvec3d &p2, map.bsp.dedges.emplace_back(bsp2_dedge_t{static_cast(v1), static_cast(v2)}); - AddHashEdge(v1, v2, i); + map.add_hash_edge(v1, v2, i); - pEdgeFaces0[i] = face; return i; } -static void FindFaceFragmentEdges(mapentity_t *entity, face_t *face, face_fragment_t *fragment) +static void FindFaceFragmentEdges(face_t *face, face_fragment_t *fragment) { fragment->outputnumber = std::nullopt; @@ -187,7 +170,7 @@ static void FindFaceFragmentEdges(mapentity_t *entity, face_t *face, face_fragme 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); + fragment->edges[i] = GetEdge(p1, p2, face); } } @@ -196,15 +179,15 @@ static void FindFaceFragmentEdges(mapentity_t *entity, face_t *face, face_fragme FindFaceEdges ================== */ -static void FindFaceEdges(mapentity_t *entity, face_t *face) +static void FindFaceEdges(face_t *face) { if (ShouldOmitFace(face)) return; - FindFaceFragmentEdges(entity, face, face); + FindFaceFragmentEdges(face, face); for (auto &fragment : face->fragments) { - FindFaceFragmentEdges(entity, face, &fragment); + FindFaceFragmentEdges(face, &fragment); } } @@ -213,19 +196,17 @@ static void FindFaceEdges(mapentity_t *entity, face_t *face) MakeFaceEdges_r ================ */ -static int MakeFaceEdges_r(mapentity_t *entity, node_t *node, int progress) +static void MakeFaceEdges_r(node_t *node) { if (node->planenum == PLANENUM_LEAF) - return progress; + return; for (auto &f : node->facelist) { - FindFaceEdges(entity, f.get()); + FindFaceEdges(f.get()); } - progress = MakeFaceEdges_r(entity, node->children[0].get(), progress); - progress = MakeFaceEdges_r(entity, node->children[1].get(), progress); - - return progress; + MakeFaceEdges_r(node->children[0].get()); + MakeFaceEdges_r(node->children[1].get()); } /* @@ -233,7 +214,7 @@ static int MakeFaceEdges_r(mapentity_t *entity, node_t *node, int progress) EmitFaceFragment ============== */ -static void EmitFaceFragment(mapentity_t *entity, face_t *face, face_fragment_t *fragment) +static void EmitFaceFragment(face_t *face, face_fragment_t *fragment) { int i; @@ -268,15 +249,15 @@ static void EmitFaceFragment(mapentity_t *entity, face_t *face, face_fragment_t EmitFace ============== */ -static void EmitFace(mapentity_t *entity, face_t *face) +static void EmitFace(face_t *face) { if (ShouldOmitFace(face)) return; - EmitFaceFragment(entity, face, face); + EmitFaceFragment(face, face); for (auto &fragment : face->fragments) { - EmitFaceFragment(entity, face, &fragment); + EmitFaceFragment(face, &fragment); } } @@ -285,7 +266,7 @@ static void EmitFace(mapentity_t *entity, face_t *face) GrowNodeRegion ============== */ -static void GrowNodeRegion(mapentity_t *entity, node_t *node) +static void GrowNodeRegion(node_t *node) { if (node->planenum == PLANENUM_LEAF) return; @@ -296,43 +277,13 @@ static void GrowNodeRegion(mapentity_t *entity, node_t *node) //Q_assert(face->planenum == node->planenum); // emit a region - EmitFace(entity, face.get()); + EmitFace(face.get()); } node->numfaces = static_cast(map.bsp.dfaces.size()) - node->firstface; - GrowNodeRegion(entity, node->children[0].get()); - GrowNodeRegion(entity, node->children[1].get()); -} - -static void CountFace(mapentity_t *entity, face_t *f, size_t &facesCount, size_t &vertexesCount) -{ - if (ShouldOmitFace(f)) - return; - - if (f->lmshift != 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 (auto &f : node->facelist) { - CountFace(entity, f.get(), facesCount, vertexesCount); - } - - CountData_r(entity, node->children[0].get(), facesCount, vertexesCount); - CountData_r(entity, node->children[1].get(), facesCount, vertexesCount); + GrowNodeRegion(node->children[0].get()); + GrowNodeRegion(node->children[1].get()); } /* @@ -340,29 +291,17 @@ static void CountData_r(mapentity_t *entity, node_t *node, size_t &facesCount, s MakeFaceEdges ================ */ -int MakeFaceEdges(mapentity_t *entity, node_t *headnode) +int MakeFaceEdges(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); - - pEdgeFaces0.clear(); - pEdgeFaces1.clear(); + MakeFaceEdges_r(headnode); logging::print(logging::flag::PROGRESS, "---- GrowRegions ----\n"); - GrowNodeRegion(entity, headnode); + GrowNodeRegion(headnode); return firstface; } @@ -406,7 +345,7 @@ MakeMarkFaces Populates the `markfaces` vectors of all leafs ================ */ -void MakeMarkFaces(mapentity_t* entity, node_t* node) +void MakeMarkFaces(node_t* node) { if (node->planenum == PLANENUM_LEAF) { return; @@ -427,8 +366,8 @@ void MakeMarkFaces(mapentity_t* entity, node_t* node) } // process child nodes recursively - MakeMarkFaces(entity, node->children[0].get()); - MakeMarkFaces(entity, node->children[1].get()); + MakeMarkFaces(node->children[0].get()); + MakeMarkFaces(node->children[1].get()); } struct makefaces_stats_t { diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index 3e38f84b..6327bd09 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -487,8 +487,6 @@ ProcessEntity */ static void ProcessEntity(mapentity_t *entity, const int hullnum) { - int firstface; - /* No map brushes means non-bmodel entity. We need to handle worldspawn containing no brushes, though. */ if (!entity->nummapbrushes && entity != map.world_entity()) @@ -638,13 +636,16 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) } // needs to come after any face creation - MakeMarkFaces(entity, tree->headnode.get()); + MakeMarkFaces(tree->headnode.get()); // convert detail leafs to solid (in case we didn't make the call above) DetailToSolid(tree->headnode.get()); // fixme-brushbsp: prune nodes + // output vertices first, since TJunc needs it + EmitVertices(tree->headnode.get()); + if (!options.notjunc.value()) { TJunc(entity, tree->headnode.get()); } @@ -653,14 +654,16 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum) ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode.get()); ExportObj_Marksurfaces("pre_makefaceedges_marksurfaces", tree->headnode.get()); } + + Q_assert(entity->firstoutputfacenumber == -1); - firstface = MakeFaceEdges(entity, tree->headnode.get()); + entity->firstoutputfacenumber = MakeFaceEdges(tree->headnode.get()); if (options.target_game->id == GAME_QUAKE_II) { ExportBrushList(entity, tree->headnode.get()); } - ExportDrawNodes(entity, tree->headnode.get(), firstface); + ExportDrawNodes(entity, tree->headnode.get(), entity->firstoutputfacenumber); } FreeBrushes(entity); diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index 7646d2e8..94c44b07 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -97,7 +97,7 @@ size_t ExportMapTexinfo(size_t texinfonum) ExportClipNodes ================== */ -static size_t ExportClipNodes(mapentity_t *entity, node_t *node) +static size_t ExportClipNodes(node_t *node) { if (node->planenum == PLANENUM_LEAF) { return node->contents.native; @@ -107,8 +107,8 @@ static size_t ExportClipNodes(mapentity_t *entity, node_t *node) const size_t nodenum = map.bsp.dclipnodes.size(); map.bsp.dclipnodes.emplace_back(); - const int child0 = ExportClipNodes(entity, node->children[0].get()); - const int child1 = ExportClipNodes(entity, node->children[1].get()); + const int child0 = ExportClipNodes(node->children[0].get()); + const int child1 = ExportClipNodes(node->children[1].get()); // Careful not to modify the vector while using this clipnode pointer bsp2_dclipnode_t &clipnode = map.bsp.dclipnodes[nodenum]; @@ -134,7 +134,7 @@ accomodate new data interleaved with old. void ExportClipNodes(mapentity_t *entity, node_t *nodes, const int hullnum) { auto &model = map.bsp.dmodels.at(entity->outputmodelnumber.value()); - model.headnode[hullnum] = ExportClipNodes(entity, nodes); + model.headnode[hullnum] = ExportClipNodes(nodes); } //=========================================================================== @@ -144,7 +144,7 @@ void ExportClipNodes(mapentity_t *entity, node_t *nodes, const int hullnum) ExportLeaf ================== */ -static void ExportLeaf(mapentity_t *entity, node_t *node) +static void ExportLeaf(node_t *node) { mleaf_t &dleaf = map.bsp.dleafs.emplace_back(); @@ -201,7 +201,7 @@ static void ExportLeaf(mapentity_t *entity, node_t *node) ExportDrawNodes ================== */ -static void ExportDrawNodes(mapentity_t *entity, node_t *node) +static void ExportDrawNodes(node_t *node) { const size_t ourNodeIndex = map.bsp.dnodes.size(); bsp2_dnode_t *dnode = &map.bsp.dnodes.emplace_back(); @@ -223,12 +223,12 @@ static void ExportDrawNodes(mapentity_t *entity, node_t *node) int32_t nextLeafIndex = static_cast(map.bsp.dleafs.size()); const int32_t childnum = -(nextLeafIndex + 1); dnode->children[i] = childnum; - ExportLeaf(entity, node->children[i].get()); + ExportLeaf(node->children[i].get()); } } else { const int32_t childnum = static_cast(map.bsp.dnodes.size()); dnode->children[i] = childnum; - ExportDrawNodes(entity, node->children[i].get()); + ExportDrawNodes(node->children[i].get()); // Important: our dnode pointer may be invalid after the recursive call, if the vector got resized. // So re-set the pointer. @@ -260,9 +260,9 @@ void ExportDrawNodes(mapentity_t *entity, node_t *headnode, int firstface) const size_t mapleafsAtStart = map.bsp.dleafs.size(); if (headnode->planenum == PLANENUM_LEAF) - ExportLeaf(entity, headnode); + ExportLeaf(headnode); else - ExportDrawNodes(entity, headnode); + ExportDrawNodes(headnode); // count how many leafs were exported by the above calls dmodel.visleafs = static_cast(map.bsp.dleafs.size() - mapleafsAtStart);