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>
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;

View File

@ -27,6 +27,7 @@
#include <array>
#include <ostream>
#include <fmt/format.h>
#include <tuple>
#include "common/mathlib.hh"
#include "common/cmdlib.hh"
@ -499,6 +500,43 @@ template<size_t N, class T>
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>
[[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;
};
// 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
class setting_container

View File

@ -165,6 +165,31 @@ enum class filltype_t
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
{
public:
@ -227,7 +252,9 @@ public:
setting_int32 leakdist{this, "leakdist", 2, &debugging_group, "space between leakfile points"};
setting_bool forceprt1{
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{
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,
@ -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,
"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_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
{
@ -325,7 +355,7 @@ class mapentity_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::optional<size_t> outputnumber; // only valid for original faces after
// write surfaces
@ -333,7 +363,7 @@ struct face_fragment_t
struct portal_t;
struct face_t : face_fragment_t
struct face_t
{
int planenum;
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
int16_t lmshift;
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;
vec_t radius;
// only valid after tjunction code
std::vector<face_fragment_t> fragments;
portal_t *portal;
};

View File

@ -77,13 +77,11 @@ static void EmitFaceVertices(face_t *f)
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++) {
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)
@ -160,11 +158,6 @@ FindFaceEdges
*/
static void FindFaceEdges(face_t *face)
{
if (ShouldOmitFace(face))
return;
FindFaceFragmentEdges(face, face);
for (auto &fragment : face->fragments) {
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;
}
/*
==============
EmitFace
==============
*/
static void EmitFace(face_t *face)
{
if (ShouldOmitFace(face))
return;
EmitFaceFragment(face, face);
for (auto &fragment : face->fragments) {
EmitFaceFragment(face, &fragment);
}
}
/*
==============
GrowNodeRegion
@ -263,7 +239,9 @@ static void GrowNodeRegion(node_t *node)
//Q_assert(face->planenum == node->planenum);
// 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;

View File

@ -652,9 +652,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
// output vertices first, since TJunc needs it
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()) {
ExportObj_Nodes("pre_makefaceedges_plane_faces", tree->headnode.get());

View File

@ -24,11 +24,29 @@
#include <qbsp/map.hh>
#include <atomic>
std::atomic<size_t> c_degenerate;
std::atomic<size_t> c_tjunctions;
std::atomic<size_t> c_faceoverflows;
std::atomic<size_t> c_facecollapse;
std::atomic<size_t> c_norotates, c_rotates, c_retopology, c_faceretopology;
struct tjunc_stats_t
{
// # of degenerate edges reported (with two identical input vertices)
std::atomic<size_t> degenerate;
// # 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)
{
@ -60,11 +78,12 @@ TestEdge
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) {
// degenerate edge
c_degenerate++;
stats.degenerate++;
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
c_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);
stats.tjunctions++;
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;
}
@ -162,13 +182,13 @@ max edge count.
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();
// split into multiple fragments, because of vertex overload
while (superface.size() > maxedges) {
c_faceoverflows++;
stats.faceoverflows++;
// copy MAXEDGES from our current face
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
==================
*/
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;
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
// 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,
// place them in superface
for (size_t i = 0; i < f->output_vertices.size(); i++) {
auto v1 = f->output_vertices[i];
auto v2 = f->output_vertices[(i + 1) % f->output_vertices.size()];
for (size_t i = 0; i < f->original_vertices.size(); i++) {
auto v1 = f->original_vertices[i];
auto v2 = f->original_vertices[(i + 1) % f->original_vertices.size()];
qvec3d edge_start = map.bsp.dvertexes[v1];
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;
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;
}
#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
@ -257,7 +335,7 @@ It's still a convex face with wound vertices, though, so we
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;
// the copy we're working on
@ -413,90 +491,116 @@ FixFaceEdges
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);
if (superface.size() < 3) {
// entire face collapsed
f->output_vertices.clear();
c_facecollapse++;
return;
} else if (superface.size() == f->output_vertices.size()) {
// face didn't need any new vertices
// 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) {
// entire face collapsed
stats.facecollapse++;
return;
} else if (superface.size() == 3) {
// no need to adjust this either
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
// 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++) {
size_t x = 0;
for (; i < superface.size(); i++) {
size_t x = 0;
// try vertex i as the base, see if we find any zero-area triangles
for (; x < superface.size() - 2; x++) {
auto v0 = superface[i];
auto v1 = superface[(i + x + 1) % superface.size()];
auto v2 = superface[(i + x + 2) % superface.size()];
// try vertex i as the base, see if we find any zero-area triangles
for (; x < superface.size() - 2; x++) {
auto v0 = superface[i];
auto v1 = superface[(i + x + 1) % 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;
}
}
if (x == superface.size() - 2) {
// found one!
break;
if (i == superface.size()) {
// can't simply rotate to eliminate zero-area triangles, so we have
// 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
std::list<std::vector<size_t>> faces;
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
// the other techniques all failed, or we asked to not
// try them. just move the superface in directly.
if (!faces.size()) {
faces.emplace_back(std::move(superface));
}
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()) {
for (auto &face : faces) {
SplitFaceIntoFragments(face, faces);
SplitFaceIntoFragments(face, faces, stats);
}
}
// move the results into the face
f->output_vertices = std::move(faces.front());
f->fragments.resize(faces.size() - 1);
f->fragments.reserve(faces.size());
i = 0;
for (auto it = ++faces.begin(); it != faces.end(); it++, i++) {
f->fragments[i].output_vertices = std::move(*it);
for (auto &face : faces) {
f->fragments.emplace_back(face_fragment_t { std::move(face) });
}
}
@ -514,8 +618,8 @@ static void FindFaces_r(node_t *node, std::unordered_set<face_t *> &faces)
}
for (auto &f : node->facelist) {
// might have been omitted earlier, so `output_vertices` will be empty
if (f->output_vertices.size()) {
// might have been omitted, so `original_vertices` will be empty
if (f->original_vertices.size()) {
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)
{
logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__);
// break edges on tjunctions
c_degenerate = 0;
c_facecollapse = 0;
c_tjunctions = 0;
c_faceoverflows = 0;
c_norotates = 0;
c_rotates = 0;
c_retopology = 0;
c_faceretopology = 0;
tjunc_stats_t stats;
std::unordered_set<face_t *> faces;
FindFaces_r(headnode, faces);
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);
logging::print (logging::flag::STAT, "{:5} faces degenerated\n", c_facecollapse);
logging::print (logging::flag::STAT, "{:5} edges added by tjunctions\n", c_tjunctions);
logging::print (logging::flag::STAT, "{:5} faces rotated\n", c_rotates);
logging::print (logging::flag::STAT, "{:5} faces re-topologized\n", c_retopology);
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);
logging::print (logging::flag::STAT, "{:5} faces unable to be rotated or re-topologized\n", c_norotates);
if (stats.degenerate) {
logging::print (logging::flag::STAT, "{:5} edges degenerated\n", stats.degenerate);
}
if (stats.facecollapse) {
logging::print (logging::flag::STAT, "{:5} faces degenerated\n", stats.facecollapse);
}
if (stats.tjunctions) {
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());
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;
// 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) {
if (fragment.outputnumber.has_value()) {
map.bsp.dleaffaces.push_back(fragment.outputnumber.value());