add in a "validator" setting which is just a thin wrapper to another setting type allowing for an additional validation step

simplify the face structure; now, "fragments" just mean the output windings (if you opt out of tjunction fixing, then there will only be 1 fragment with the same values as original_vertices)
move MakeTangentAndBitangentUnnormalized to qvec.hh since we will use it later
-tjunc allows for more fine-grained control now (-notjunc still exists)
-maxedges now throws if you specify a bad value (1 and 2)
remove the checks to OmitFaces - we can tell if a face was omitted because it will have an empty winding
remove static variables from tjunc
This commit is contained in:
Jonathan 2022-07-16 10:45:24 -04:00
parent da9546e260
commit acac6cb720
8 changed files with 323 additions and 179 deletions

View File

@ -113,47 +113,10 @@ struct planepoints : std::array<qvec3d, 3>
} }
}; };
template<typename T>
std::tuple<qvec3d, qvec3d> MakeTangentAndBitangentUnnormalized(const qvec<T, 3> &normal)
{
// 0, 1, or 2
const int axis = qv::indexOfLargestMagnitudeComponent(normal);
const int otherAxisA = (axis + 1) % 3;
const int otherAxisB = (axis + 2) % 3;
// setup two other vectors that are perpendicular to each other
qvec3d otherVecA{};
otherVecA[otherAxisA] = 1.0;
qvec3d otherVecB{};
otherVecB[otherAxisB] = 1.0;
qvec3d tangent = qv::cross(normal, otherVecA);
qvec3d bitangent = qv::cross(normal, otherVecB);
// We want `test` to point in the same direction as normal.
// Swap the tangent bitangent if we got the direction wrong.
qvec3d test = qv::cross(tangent, bitangent);
if (qv::dot(test, normal) < 0) {
std::swap(tangent, bitangent);
}
// debug test
if (0) {
auto n = qv::normalize(qv::cross(tangent, bitangent));
double d = qv::distance(n, normal);
assert(d < 0.0001);
}
return {tangent, bitangent};
}
template<typename T> template<typename T>
static planepoints NormalDistanceToThreePoints(const qplane3<T> &plane) static planepoints NormalDistanceToThreePoints(const qplane3<T> &plane)
{ {
std::tuple<qvec3d, qvec3d> tanBitan = MakeTangentAndBitangentUnnormalized(plane.normal); std::tuple<qvec3d, qvec3d> tanBitan = qv::MakeTangentAndBitangentUnnormalized(plane.normal);
qvec3d point0 = plane.normal * plane.dist; qvec3d point0 = plane.normal * plane.dist;

View File

@ -27,6 +27,7 @@
#include <array> #include <array>
#include <ostream> #include <ostream>
#include <fmt/format.h> #include <fmt/format.h>
#include <tuple>
#include "common/mathlib.hh" #include "common/mathlib.hh"
#include "common/cmdlib.hh" #include "common/cmdlib.hh"
@ -499,6 +500,43 @@ template<size_t N, class T>
return largestIndex; return largestIndex;
} }
template<typename T>
std::tuple<qvec<T, 3>, qvec<T, 3>> MakeTangentAndBitangentUnnormalized(const qvec<T, 3> &normal)
{
// 0, 1, or 2
const int axis = qv::indexOfLargestMagnitudeComponent(normal);
const int otherAxisA = (axis + 1) % 3;
const int otherAxisB = (axis + 2) % 3;
// setup two other vectors that are perpendicular to each other
qvec<T, 3> otherVecA{};
otherVecA[otherAxisA] = 1.0;
qvec<T, 3> otherVecB{};
otherVecB[otherAxisB] = 1.0;
auto tangent = qv::cross(normal, otherVecA);
auto bitangent = qv::cross(normal, otherVecB);
// We want `test` to point in the same direction as normal.
// Swap the tangent bitangent if we got the direction wrong.
qvec<T, 3> test = qv::cross(tangent, bitangent);
if (qv::dot(test, normal) < 0) {
std::swap(tangent, bitangent);
}
// debug test
if (0) {
auto n = qv::normalize(qv::cross(tangent, bitangent));
double d = qv::distance(n, normal);
assert(d < 0.0001);
}
return {tangent, bitangent};
}
template<size_t N, typename T> template<size_t N, typename T>
[[nodiscard]] inline T TriangleArea(const qvec<T, N> &v0, const qvec<T, N> &v1, const qvec<T, N> &v2) [[nodiscard]] inline T TriangleArea(const qvec<T, N> &v0, const qvec<T, N> &v1, const qvec<T, N> &v2)
{ {

View File

@ -657,6 +657,35 @@ public:
using setting_vec3::setting_vec3; using setting_vec3::setting_vec3;
}; };
// a simple wrapper type that allows you to provide
// extra validation to an existing type without needing
// to create a whole new type for it.
template<typename T>
class setting_validator : public T
{
protected:
std::function<bool(T &)> _validator;
public:
template<typename ...Args>
inline setting_validator(const decltype(_validator) &validator, Args&&... args) :
T(std::forward<Args>(args)...),
_validator(validator)
{
}
bool parse(const std::string &settingName, parser_base_t &parser, source source) override
{
bool result = this->T::parse(settingName, parser, source);
if (result) {
return _validator(*this);
}
return result;
}
};
// settings dictionary // settings dictionary
class setting_container class setting_container

View File

@ -165,6 +165,31 @@ enum class filltype_t
INSIDE INSIDE
}; };
enum class tjunclevel_t
{
NONE, // don't attempt to adjust faces at all - pass them through unchanged
ROTATE, // allow faces' vertices to be rotated to prevent zero-area triangles
RETOPOLOGIZE, // if a face still has zero-area triangles, allow it to be re-topologized
// by splitting it into multiple fans
DELAUNAY // attempt a delaunay triangulation first, only falling back to the prior two steps if it fails.
};
struct setting_tjunc : public setting_enum<tjunclevel_t>
{
public:
using setting_enum<tjunclevel_t>::setting_enum;
bool parse(const std::string &settingName, parser_base_t &parser, source source) override
{
if (settingName == "notjunc") {
this->setValue(tjunclevel_t::NONE, source);
return true;
}
return this->setting_enum<tjunclevel_t>::parse(settingName, parser, source);
}
};
class qbsp_settings : public common_settings class qbsp_settings : public common_settings
{ {
public: public:
@ -227,7 +252,9 @@ public:
setting_int32 leakdist{this, "leakdist", 2, &debugging_group, "space between leakfile points"}; setting_int32 leakdist{this, "leakdist", 2, &debugging_group, "space between leakfile points"};
setting_bool forceprt1{ setting_bool forceprt1{
this, "forceprt1", false, &debugging_group, "force a PRT1 output file even if PRT2 is required for vis"}; this, "forceprt1", false, &debugging_group, "force a PRT1 output file even if PRT2 is required for vis"};
setting_bool notjunc{this, "notjunc", false, &debugging_group, "don't fix T-junctions"}; setting_tjunc tjunc{this, { "tjunc", "notjunc" }, tjunclevel_t::DELAUNAY,
{ { "none", tjunclevel_t::NONE }, { "rotate", tjunclevel_t::ROTATE }, { "retopologize", tjunclevel_t::RETOPOLOGIZE }, { "delaunay", tjunclevel_t::DELAUNAY } },
&debugging_group, "T-junction fix level"};
setting_bool objexport{ setting_bool objexport{
this, "objexport", false, &debugging_group, "export the map file as .OBJ models during various CSG phases"}; this, "objexport", false, &debugging_group, "export the map file as .OBJ models during various CSG phases"};
setting_bool wrbrushes{this, {"wrbrushes", "bspx"}, false, &common_format_group, setting_bool wrbrushes{this, {"wrbrushes", "bspx"}, false, &common_format_group,
@ -253,7 +280,10 @@ public:
setting_enum<filltype_t> filltype{this, "filltype", filltype_t::AUTO, { { "auto", filltype_t::AUTO }, { "inside", filltype_t::INSIDE }, { "outside", filltype_t::OUTSIDE } }, &common_format_group, setting_enum<filltype_t> filltype{this, "filltype", filltype_t::AUTO, { { "auto", filltype_t::AUTO }, { "inside", filltype_t::INSIDE }, { "outside", filltype_t::OUTSIDE } }, &common_format_group,
"whether to fill the map from the outside in (lenient), from the inside out (aggressive), or to automatically decide based on the hull being used."}; "whether to fill the map from the outside in (lenient), from the inside out (aggressive), or to automatically decide based on the hull being used."};
setting_invertible_bool allow_upgrade{this, "allowupgrade", true, &common_format_group, "allow formats to \"upgrade\" to compatible extended formats when a limit is exceeded (ie Quake BSP to BSP2)"}; setting_invertible_bool allow_upgrade{this, "allowupgrade", true, &common_format_group, "allow formats to \"upgrade\" to compatible extended formats when a limit is exceeded (ie Quake BSP to BSP2)"};
setting_int32 maxedges{this, "maxedges", 64, &map_development_group, "the max number of edges/vertices on a single face before it is split into another face"}; setting_validator<setting_int32> maxedges{
[](setting_int32 &setting) {
return setting.value() == 0 || setting.value() >= 3;
}, this, "maxedges", 64, &map_development_group, "the max number of edges/vertices on a single face before it is split into another face"};
void setParameters(int argc, const char **argv) override void setParameters(int argc, const char **argv) override
{ {
@ -325,7 +355,7 @@ class mapentity_t;
struct face_fragment_t struct face_fragment_t
{ {
std::vector<size_t> output_vertices, original_vertices; // filled in by EmitVertices & TJunc std::vector<size_t> output_vertices; // filled in by TJunc
std::vector<int64_t> edges; // only filled in MakeFaceEdges std::vector<int64_t> edges; // only filled in MakeFaceEdges
std::optional<size_t> outputnumber; // only valid for original faces after std::optional<size_t> outputnumber; // only valid for original faces after
// write surfaces // write surfaces
@ -333,7 +363,7 @@ struct face_fragment_t
struct portal_t; struct portal_t;
struct face_t : face_fragment_t struct face_t
{ {
int planenum; int planenum;
planeside_t planeside; // which side is the front of the face planeside_t planeside; // which side is the front of the face
@ -341,13 +371,12 @@ struct face_t : face_fragment_t
contentflags_t contents; // contents on the front of the face contentflags_t contents; // contents on the front of the face
int16_t lmshift; int16_t lmshift;
winding_t w; winding_t w;
std::vector<size_t> original_vertices; // the vertices of this face before fragmentation; filled in by EmitVertices
std::vector<face_fragment_t> fragments; // the vertices of this face post-fragmentation; filled in by TJunc
qvec3d origin; qvec3d origin;
vec_t radius; vec_t radius;
// only valid after tjunction code
std::vector<face_fragment_t> fragments;
portal_t *portal; portal_t *portal;
}; };

View File

@ -77,13 +77,11 @@ static void EmitFaceVertices(face_t *f)
return; return;
} }
f->output_vertices.resize(f->w.size()); f->original_vertices.resize(f->w.size());
for (size_t i = 0; i < f->w.size(); i++) { for (size_t i = 0; i < f->w.size(); i++) {
EmitVertex(f->w[i], f->output_vertices[i]); EmitVertex(f->w[i], f->original_vertices[i]);
} }
f->original_vertices = f->output_vertices;
} }
static void EmitVertices_R(node_t *node) static void EmitVertices_R(node_t *node)
@ -160,11 +158,6 @@ FindFaceEdges
*/ */
static void FindFaceEdges(face_t *face) static void FindFaceEdges(face_t *face)
{ {
if (ShouldOmitFace(face))
return;
FindFaceFragmentEdges(face, face);
for (auto &fragment : face->fragments) { for (auto &fragment : face->fragments) {
FindFaceFragmentEdges(face, &fragment); FindFaceFragmentEdges(face, &fragment);
} }
@ -230,23 +223,6 @@ static void EmitFaceFragment(face_t *face, face_fragment_t *fragment)
out.numedges = static_cast<int32_t>(map.bsp.dsurfedges.size()) - out.firstedge; out.numedges = static_cast<int32_t>(map.bsp.dsurfedges.size()) - out.firstedge;
} }
/*
==============
EmitFace
==============
*/
static void EmitFace(face_t *face)
{
if (ShouldOmitFace(face))
return;
EmitFaceFragment(face, face);
for (auto &fragment : face->fragments) {
EmitFaceFragment(face, &fragment);
}
}
/* /*
============== ==============
GrowNodeRegion GrowNodeRegion
@ -263,7 +239,9 @@ static void GrowNodeRegion(node_t *node)
//Q_assert(face->planenum == node->planenum); //Q_assert(face->planenum == node->planenum);
// emit a region // emit a region
EmitFace(face.get()); for (auto &fragment : face->fragments) {
EmitFaceFragment(face.get(), &fragment);
}
} }
node->numfaces = static_cast<int>(map.bsp.dfaces.size()) - node->firstface; node->numfaces = static_cast<int>(map.bsp.dfaces.size()) - node->firstface;

View File

@ -652,9 +652,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
// output vertices first, since TJunc needs it // output vertices first, since TJunc needs it
EmitVertices(tree->headnode.get()); EmitVertices(tree->headnode.get());
if (!qbsp_options.notjunc.value()) { TJunc(tree->headnode.get());
TJunc(tree->headnode.get());
}
if (qbsp_options.objexport.value() && entity == map.world_entity()) { if (qbsp_options.objexport.value() && entity == map.world_entity()) {
ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode.get()); ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode.get());

View File

@ -24,11 +24,29 @@
#include <qbsp/map.hh> #include <qbsp/map.hh>
#include <atomic> #include <atomic>
std::atomic<size_t> c_degenerate; struct tjunc_stats_t
std::atomic<size_t> c_tjunctions; {
std::atomic<size_t> c_faceoverflows; // # of degenerate edges reported (with two identical input vertices)
std::atomic<size_t> c_facecollapse; std::atomic<size_t> degenerate;
std::atomic<size_t> c_norotates, c_rotates, c_retopology, c_faceretopology; // # of new edges created to close a tjunction
// (also technically the # of points detected that lay on other faces' edges)
std::atomic<size_t> tjunctions;
// # of faces that were created as a result of splitting faces that are too large
// to be contained on a single face
std::atomic<size_t> faceoverflows;
// # of faces that were degenerate and were just collapsed altogether.
std::atomic<size_t> facecollapse;
// # of faces that were able to be fixed just by rotating the start point.
std::atomic<size_t> rotates;
// # of faces that weren't able to be fixed with start point rotation
std::atomic<size_t> norotates;
// # of faces that could be successfully retopologized
std::atomic<size_t> retopology;
// # of faces generated by retopologization
std::atomic<size_t> faceretopology;
// # of faces that were successfully topologized by delaunay triangulation
std::atomic<size_t> delaunay;
};
inline std::optional<vec_t> PointOnEdge(const qvec3d &p, const qvec3d &edge_start, const qvec3d &edge_dir, float start = 0, float end = 1) inline std::optional<vec_t> PointOnEdge(const qvec3d &p, const qvec3d &edge_start, const qvec3d &edge_dir, float start = 0, float end = 1)
{ {
@ -60,11 +78,12 @@ TestEdge
Can be recursively reentered Can be recursively reentered
========== ==========
*/ */
inline void TestEdge(vec_t start, vec_t end, size_t p1, size_t p2, size_t startvert, const std::vector<size_t> &edge_verts, const qvec3d &edge_start, const qvec3d &edge_dir, std::vector<size_t> &superface) inline void TestEdge(vec_t start, vec_t end, size_t p1, size_t p2, size_t startvert, const std::vector<size_t> &edge_verts,
const qvec3d &edge_start, const qvec3d &edge_dir, std::vector<size_t> &superface, tjunc_stats_t &stats)
{ {
if (p1 == p2) { if (p1 == p2) {
// degenerate edge // degenerate edge
c_degenerate++; stats.degenerate++;
return; return;
} }
@ -82,9 +101,10 @@ inline void TestEdge(vec_t start, vec_t end, size_t p1, size_t p2, size_t startv
} }
// break the edge // break the edge
c_tjunctions++; stats.tjunctions++;
TestEdge (start, dist.value(), p1, j, k + 1, edge_verts, edge_start, edge_dir, superface);
TestEdge (dist.value(), end, j, p2, k + 1, edge_verts, edge_start, edge_dir, superface); TestEdge (start, dist.value(), p1, j, k + 1, edge_verts, edge_start, edge_dir, superface, stats);
TestEdge (dist.value(), end, j, p2, k + 1, edge_verts, edge_start, edge_dir, superface, stats);
return; return;
} }
@ -162,13 +182,13 @@ max edge count.
Modifies `superface`. Adds the results to the end of `output`. Modifies `superface`. Adds the results to the end of `output`.
================== ==================
*/ */
inline void SplitFaceIntoFragments(std::vector<size_t> &superface, std::list<std::vector<size_t>> &output) inline void SplitFaceIntoFragments(std::vector<size_t> &superface, std::list<std::vector<size_t>> &output, tjunc_stats_t &stats)
{ {
const int32_t &maxedges = qbsp_options.maxedges.value(); const int32_t &maxedges = qbsp_options.maxedges.value();
// split into multiple fragments, because of vertex overload // split into multiple fragments, because of vertex overload
while (superface.size() > maxedges) { while (superface.size() > maxedges) {
c_faceoverflows++; stats.faceoverflows++;
// copy MAXEDGES from our current face // copy MAXEDGES from our current face
std::vector<size_t> &newf = output.emplace_back(maxedges); std::vector<size_t> &newf = output.emplace_back(maxedges);
@ -217,11 +237,11 @@ Generate a superface (the input face `f` but with all of the
verts in the world added that lay on the line) and return it verts in the world added that lay on the line) and return it
================== ==================
*/ */
static std::vector<size_t> CreateSuperFace(node_t *headnode, face_t *f) static std::vector<size_t> CreateSuperFace(node_t *headnode, face_t *f, tjunc_stats_t &stats)
{ {
std::vector<size_t> superface; std::vector<size_t> superface;
superface.reserve(f->output_vertices.size() * 2); superface.reserve(f->original_vertices.size() * 2);
// stores all of the verts in the world that are close to // stores all of the verts in the world that are close to
// being on a given edge // being on a given edge
@ -229,9 +249,9 @@ static std::vector<size_t> CreateSuperFace(node_t *headnode, face_t *f)
// find all of the extra vertices that lay on edges, // find all of the extra vertices that lay on edges,
// place them in superface // place them in superface
for (size_t i = 0; i < f->output_vertices.size(); i++) { for (size_t i = 0; i < f->original_vertices.size(); i++) {
auto v1 = f->output_vertices[i]; auto v1 = f->original_vertices[i];
auto v2 = f->output_vertices[(i + 1) % f->output_vertices.size()]; auto v2 = f->original_vertices[(i + 1) % f->original_vertices.size()];
qvec3d edge_start = map.bsp.dvertexes[v1]; qvec3d edge_start = map.bsp.dvertexes[v1];
qvec3d e2 = map.bsp.dvertexes[v2]; qvec3d e2 = map.bsp.dvertexes[v2];
@ -242,12 +262,70 @@ static std::vector<size_t> CreateSuperFace(node_t *headnode, face_t *f)
vec_t len; vec_t len;
qvec3d edge_dir = qv::normalize(e2 - edge_start, len); qvec3d edge_dir = qv::normalize(e2 - edge_start, len);
TestEdge(0, len, v1, v2, 0, edge_verts, edge_start, edge_dir, superface); TestEdge(0, len, v1, v2, 0, edge_verts, edge_start, edge_dir, superface, stats);
} }
return superface; return superface;
} }
#include <common/bsputils.hh>
static std::list<std::vector<size_t>> DelaunayFace(const face_t *f, const std::vector<size_t> &vertices)
{
// commented until DelaBella updates to fix cocircular points
#if 0
auto p = map.planes[f->planenum];
if (f->planeside) {
p = -p;
}
auto [ u, v ] = qv::MakeTangentAndBitangentUnnormalized(p.normal);
qv::normalizeInPlace(u);
qv::normalizeInPlace(v);
std::vector<qvec2d> points_2d(vertices.size());
for (size_t i = 0; i < vertices.size(); i++) {
points_2d[i] = { qv::dot(map.bsp.dvertexes[vertices[i]], u), qv::dot(map.bsp.dvertexes[vertices[i]], v) };
}
IDelaBella* idb = IDelaBella::Create();
idb->SetErrLog([](auto stream, auto fmt, ...) {
logging::print("{}", fmt);
return 0;
}, nullptr);
std::list<std::vector<size_t>> tris_compiled;
int verts = idb->Triangulate(points_2d.size(), &points_2d[0][0], &points_2d[0][1], sizeof(qvec2d));
// if positive, all ok
if (verts > 0)
{
int tris = verts / 3;
const DelaBella_Triangle* dela = idb->GetFirstDelaunayTriangle();
for (int i = 0; i<tris; i++)
{
// do something with dela triangle
// ...
tris_compiled.emplace_back(std::vector<size_t> { vertices[dela->v[0]->i], vertices[dela->v[1]->i], vertices[dela->v[2]->i] });
dela = dela->next;
}
c_delaunay++;
}
idb->Destroy();
// ...
return tris_compiled;
#endif
return {};
}
/* /*
================== ==================
RetopologizeFace RetopologizeFace
@ -257,7 +335,7 @@ It's still a convex face with wound vertices, though, so we
can split it into several triangle fans. can split it into several triangle fans.
================== ==================
*/ */
static std::list<std::vector<size_t>> RetopologizeFace(const std::vector<size_t> &vertices) static std::list<std::vector<size_t>> RetopologizeFace(const face_t *f, const std::vector<size_t> &vertices)
{ {
std::list<std::vector<size_t>> result; std::list<std::vector<size_t>> result;
// the copy we're working on // the copy we're working on
@ -413,90 +491,116 @@ FixFaceEdges
If the face has any T-junctions, fix them here. If the face has any T-junctions, fix them here.
================== ==================
*/ */
static void FixFaceEdges(node_t *headnode, face_t *f) static void FixFaceEdges(node_t *headnode, face_t *f, tjunc_stats_t &stats)
{ {
std::vector<size_t> superface = CreateSuperFace(headnode, f); // we were asked not to bother fixing any of the faces.
if (qbsp_options.tjunc.value() == settings::tjunclevel_t::NONE) {
f->fragments.emplace_back(face_fragment_t { f->original_vertices });
return;
}
std::vector<size_t> superface = CreateSuperFace(headnode, f, stats);
if (superface.size() < 3) { if (superface.size() < 3) {
// entire face collapsed // entire face collapsed
f->output_vertices.clear(); stats.facecollapse++;
c_facecollapse++;
return; return;
} else if (superface.size() == f->output_vertices.size()) { } else if (superface.size() == 3) {
// face didn't need any new vertices // no need to adjust this either
return; return;
} }
// faces with 4 or more vertices can be done better.
// temporary storage for result faces; stored as a list
// since a resize may steal references out from underneath us
// as the functions do their work.
std::list<std::vector<size_t>> faces;
// do delaunay first; it will generate optimal results for everything.
if (qbsp_options.tjunc.value() >= settings::tjunclevel_t::DELAUNAY) {
try
{
faces = DelaunayFace(f, superface);
}
catch (std::exception)
{
}
}
// brute force rotating the start point until we find a valid winding // brute force rotating the start point until we find a valid winding
// that doesn't have any T-junctions // that doesn't have any T-junctions
size_t i = 0; if (!faces.size() && qbsp_options.tjunc.value() >= settings::tjunclevel_t::ROTATE) {
size_t i = 0;
for (; i < superface.size(); i++) { for (; i < superface.size(); i++) {
size_t x = 0; size_t x = 0;
// try vertex i as the base, see if we find any zero-area triangles // try vertex i as the base, see if we find any zero-area triangles
for (; x < superface.size() - 2; x++) { for (; x < superface.size() - 2; x++) {
auto v0 = superface[i]; auto v0 = superface[i];
auto v1 = superface[(i + x + 1) % superface.size()]; auto v1 = superface[(i + x + 1) % superface.size()];
auto v2 = superface[(i + x + 2) % superface.size()]; auto v2 = superface[(i + x + 2) % superface.size()];
if (!TriangleIsValid(v0, v1, v2, superface, 0.01)) { if (!TriangleIsValid(v0, v1, v2, superface, 0.01)) {
break;
}
}
if (x == superface.size() - 2) {
// found one!
break; break;
} }
} }
if (x == superface.size() - 2) { if (i == superface.size()) {
// found one! // can't simply rotate to eliminate zero-area triangles, so we have
break; // to do a bit of re-topology.
if (qbsp_options.tjunc.value() >= settings::tjunclevel_t::RETOPOLOGIZE) {
if (auto retopology = RetopologizeFace(f, superface); retopology.size() > 1) {
stats.retopology++;
stats.faceretopology += retopology.size() - 1;
faces = std::move(retopology);
}
}
if (!faces.size()) {
// unable to re-topologize, so just stick with the superface.
// it's got zero-area triangles that fill in the gaps.
stats.norotates++;
}
} else if (i != 0) {
// was able to rotate the superface to eliminate zero-area triangles.
stats.rotates++;
auto &output = faces.emplace_back(superface.size());
// copy base -> end
auto out = std::copy(superface.begin() + i, superface.end(), output.begin());
// copy end -> base
std::copy(superface.begin(), superface.begin() + i, out);
} }
} }
// temporary storage for result faces // the other techniques all failed, or we asked to not
std::list<std::vector<size_t>> faces; // try them. just move the superface in directly.
if (!faces.size()) {
if (i == superface.size()) {
// can't simply rotate to eliminate zero-area triangles, so we have
// to do a bit of re-topology.
if (auto retopology = RetopologizeFace(superface); retopology.size() > 1) {
c_retopology++;
c_faceretopology += retopology.size() - 1;
faces = std::move(retopology);
} else {
// unable to re-topologize, so just stick with the superface.
// it's got zero-area triangles that fill in the gaps.
c_norotates++;
faces.emplace_back(std::move(superface));
}
} else if (i != 0) {
// was able to rotate the superface to eliminate zero-area triangles.
c_rotates++;
auto &output = faces.emplace_back(superface.size());
// copy base -> end
auto out = std::copy(superface.begin() + i, superface.end(), output.begin());
// copy end -> base
std::copy(superface.begin(), superface.begin() + i, out);
} else {
// no need to change topology
faces.emplace_back(std::move(superface)); faces.emplace_back(std::move(superface));
} }
Q_assert(faces.size()); Q_assert(faces.size());
// split giant superfaces into subfaces // split giant superfaces into subfaces if we have an edge limit.
if (qbsp_options.maxedges.value()) { if (qbsp_options.maxedges.value()) {
for (auto &face : faces) { for (auto &face : faces) {
SplitFaceIntoFragments(face, faces); SplitFaceIntoFragments(face, faces, stats);
} }
} }
// move the results into the face // move the results into the face
f->output_vertices = std::move(faces.front()); f->fragments.reserve(faces.size());
f->fragments.resize(faces.size() - 1);
i = 0; for (auto &face : faces) {
for (auto it = ++faces.begin(); it != faces.end(); it++, i++) { f->fragments.emplace_back(face_fragment_t { std::move(face) });
f->fragments[i].output_vertices = std::move(*it);
} }
} }
@ -514,8 +618,8 @@ static void FindFaces_r(node_t *node, std::unordered_set<face_t *> &faces)
} }
for (auto &f : node->facelist) { for (auto &f : node->facelist) {
// might have been omitted earlier, so `output_vertices` will be empty // might have been omitted, so `original_vertices` will be empty
if (f->output_vertices.size()) { if (f->original_vertices.size()) {
faces.insert(f.get()); faces.insert(f.get());
} }
} }
@ -526,37 +630,47 @@ static void FindFaces_r(node_t *node, std::unordered_set<face_t *> &faces)
/* /*
=========== ===========
tjunc TJunc fixing entry point
=========== ===========
*/ */
void TJunc(node_t *headnode) void TJunc(node_t *headnode)
{ {
logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__); logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__);
// break edges on tjunctions tjunc_stats_t stats;
c_degenerate = 0;
c_facecollapse = 0;
c_tjunctions = 0;
c_faceoverflows = 0;
c_norotates = 0;
c_rotates = 0;
c_retopology = 0;
c_faceretopology = 0;
std::unordered_set<face_t *> faces; std::unordered_set<face_t *> faces;
FindFaces_r(headnode, faces); FindFaces_r(headnode, faces);
logging::parallel_for_each(faces, [&](auto &face) { logging::parallel_for_each(faces, [&](auto &face) {
FixFaceEdges(headnode, face); FixFaceEdges(headnode, face, stats);
}); });
logging::print (logging::flag::STAT, "{:5} edges degenerated\n", c_degenerate); if (stats.degenerate) {
logging::print (logging::flag::STAT, "{:5} faces degenerated\n", c_facecollapse); logging::print (logging::flag::STAT, "{:5} edges degenerated\n", stats.degenerate);
logging::print (logging::flag::STAT, "{:5} edges added by tjunctions\n", c_tjunctions); }
logging::print (logging::flag::STAT, "{:5} faces rotated\n", c_rotates); if (stats.facecollapse) {
logging::print (logging::flag::STAT, "{:5} faces re-topologized\n", c_retopology); logging::print (logging::flag::STAT, "{:5} faces degenerated\n", stats.facecollapse);
logging::print (logging::flag::STAT, "{:5} faces added by re-topology\n", c_faceretopology); }
logging::print (logging::flag::STAT, "{:5} faces added by splitting large faces\n", c_faceoverflows); if (stats.tjunctions) {
logging::print (logging::flag::STAT, "{:5} faces unable to be rotated or re-topologized\n", c_norotates); logging::print (logging::flag::STAT, "{:5} edges added by tjunctions\n", stats.tjunctions);
}
if (stats.delaunay) {
logging::print (logging::flag::STAT, "{:5} faces delaunay triangulated\n", stats.delaunay);
}
if (stats.rotates) {
logging::print (logging::flag::STAT, "{:5} faces rotated\n", stats.rotates);
}
if (stats.norotates) {
logging::print (logging::flag::STAT, "{:5} faces unable to be rotated or re-topologized\n", stats.norotates);
}
if (stats.retopology) {
logging::print (logging::flag::STAT, "{:5} faces re-topologized\n", stats.retopology);
}
if (stats.faceretopology) {
logging::print (logging::flag::STAT, "{:5} faces added by re-topology\n", stats.faceretopology);
}
if (stats.faceoverflows) {
logging::print (logging::flag::STAT, "{:5} faces added by splitting large faces\n", stats.faceoverflows);
}
} }

View File

@ -173,16 +173,11 @@ static void ExportLeaf(node_t *node)
dleaf.firstmarksurface = static_cast<int>(map.bsp.dleaffaces.size()); dleaf.firstmarksurface = static_cast<int>(map.bsp.dleaffaces.size());
for (auto &face : node->markfaces) { for (auto &face : node->markfaces) {
if (!qbsp_options.includeskip.value() && map.mtexinfos.at(face->texinfo).flags.is_skip) if (!qbsp_options.includeskip.value() && map.mtexinfos.at(face->texinfo).flags.is_skip) {
continue; continue;
// FIXME: this can happen when compiling some Q2 maps
// as Q1.
if (face->outputnumber.has_value()) {
/* emit a marksurface */
map.bsp.dleaffaces.push_back(face->outputnumber.value());
} }
/* grab tjunction split faces */ /* grab final output faces */
for (auto &fragment : face->fragments) { for (auto &fragment : face->fragments) {
if (fragment.outputnumber.has_value()) { if (fragment.outputnumber.has_value()) {
map.bsp.dleaffaces.push_back(fragment.outputnumber.value()); map.bsp.dleaffaces.push_back(fragment.outputnumber.value());