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:
parent
da9546e260
commit
acac6cb720
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
300
qbsp/tjunc.cc
300
qbsp/tjunc.cc
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue