diff --git a/include/qbsp/map.hh b/include/qbsp/map.hh index 2ed5b0bb..25bd4919 100644 --- a/include/qbsp/map.hh +++ b/include/qbsp/map.hh @@ -239,6 +239,7 @@ constexpr int HULL_COLLISION = -1; /* Create BSP brushes from map brushes */ brush_stats_t Brush_LoadEntity(mapentity_t *entity, const int hullnum); +std::list CSGFace(face_t *srcface, const mapentity_t* srcentity, const brush_t *srcbrush, const node_t *srcnode); void TJunc(const mapentity_t *entity, node_t *headnode); int MakeFaceEdges(mapentity_t *entity, node_t *headnode); void ExportClipNodes(mapentity_t *entity, node_t *headnode, const int hullnum); diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 27955452..6b3d297c 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -149,6 +149,106 @@ std::tuple SplitFace(face_t *in, const qplane3d &split) return {new_front, new_back}; } +/* +================= +RemoveOutsideFaces + +Quick test before running ClipInside; move any faces that are completely +outside the brush to the outside list, without splitting them. This saves us +time in mergefaces later on (and sometimes a lot of memory) + +Input is a list of faces in the param `inside`. +On return, the ones touching `brush` remain in `inside`, the rest are added to `outside`. +================= +*/ +static void RemoveOutsideFaces(const brush_t &brush, std::list *inside, std::list *outside) +{ + std::list oldinside; + + // clear `inside`, transfer it to `oldinside` + std::swap(*inside, oldinside); + + for (face_t *face : oldinside) { + std::optional w = face->w; + for (auto &clipface : brush.faces) { + w = w->clip(Face_Plane(&clipface), ON_EPSILON, false)[SIDE_BACK]; + if (!w) + break; + } + if (!w) { + /* The face is completely outside this brush */ + outside->push_front(face); + } else { + inside->push_front(face); + } + } +} + +/* +================= +ClipInside + +Clips all of the faces in the inside list, possibly moving them to the +outside list or spliting it into a piece in each list. + +Faces exactly on the plane will stay inside unless overdrawn by later brush +================= +*/ +static void ClipInside( + const face_t *clipface, bool precedence, std::list *inside, std::list *outside) +{ + std::list oldinside; + + // effectively make a copy of `inside`, and clear it + std::swap(*inside, oldinside); + + const qbsp_plane_t &splitplane = map.planes[clipface->planenum]; + + for (face_t *face : oldinside) { + /* HACK: Check for on-plane but not the same planenum + ( https://github.com/ericwa/ericw-tools/issues/174 ) + */ + bool spurious_onplane = false; + { + std::array counts = face->w.calc_sides(splitplane, nullptr, nullptr, ON_EPSILON); + + if (counts[SIDE_ON] && !counts[SIDE_FRONT] && !counts[SIDE_BACK]) { + spurious_onplane = true; + } + } + + std::array frags; + + /* Handle exactly on-plane faces */ + if (face->planenum == clipface->planenum || spurious_onplane) { + const qplane3d faceplane = Face_Plane(face); + const qplane3d clipfaceplane = Face_Plane(clipface); + const vec_t dp = qv::dot(faceplane.normal, clipfaceplane.normal); + const bool opposite = (dp < 0); + + if (opposite || precedence) { + /* always clip off opposite facing */ + frags[clipface->planeside] = {}; + frags[!clipface->planeside] = {face}; + } else { + /* leave it on the outside */ + frags[clipface->planeside] = {face}; + frags[!clipface->planeside] = {}; + } + } else { + /* proper split */ + std::tie(frags[0], frags[1]) = SplitFace(face, splitplane); + } + + if (frags[clipface->planeside]) { + outside->push_front(frags[clipface->planeside]); + } + if (frags[!clipface->planeside]) { + inside->push_front(frags[!clipface->planeside]); + } + } +} + face_t *MirrorFace(const face_t *face) { face_t *newface = NewFaceFromFace(face); @@ -177,6 +277,157 @@ static std::vector> SingleBrush(std::unique_ptr