372 lines
9.5 KiB
C++
372 lines
9.5 KiB
C++
/*
|
|
Copyright (C) 1996-1997 Id Software, Inc.
|
|
Copyright (C) 1997 Greg Lewis
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
See file, 'COPYING', for details.
|
|
*/
|
|
// tjunc.c
|
|
|
|
#include <qbsp/qbsp.hh>
|
|
#include <qbsp/map.hh>
|
|
|
|
size_t c_degenerate;
|
|
size_t c_tjunctions;
|
|
size_t c_faceoverflows;
|
|
size_t c_facecollapse;
|
|
#if 0
|
|
size_t c_badstartverts;
|
|
#endif
|
|
size_t c_norotates, c_rotates;
|
|
|
|
/*
|
|
==========
|
|
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)
|
|
{
|
|
if (p1 == p2) {
|
|
// degenerate edge
|
|
c_degenerate++;
|
|
return;
|
|
}
|
|
|
|
for (size_t k = startvert; k < edge_verts.size(); k++) {
|
|
size_t j = edge_verts[k];
|
|
|
|
if (j == p1 || j == p2) {
|
|
continue;
|
|
}
|
|
|
|
const qvec3d &p = map.bsp.dvertexes[j];
|
|
qvec3d delta = p - edge_start;
|
|
vec_t dist = qv::dot(delta, edge_dir);
|
|
|
|
// check if off an end
|
|
if (dist <= start || dist >= end) {
|
|
continue;
|
|
}
|
|
|
|
qvec3d exact = edge_start + (edge_dir * dist);
|
|
qvec3d off = p - exact;
|
|
vec_t error = qv::length(off);
|
|
|
|
// brushbsp-fixme: this was 0.5 in Q2, check?
|
|
if (fabs(error) > DEFAULT_ON_EPSILON) {
|
|
continue; // not on the edge
|
|
}
|
|
|
|
// break the edge
|
|
c_tjunctions++;
|
|
TestEdge (start, dist, p1, j, k + 1, edge_verts, edge_start, edge_dir, superface);
|
|
TestEdge (dist, end, j, p2, k + 1, edge_verts, edge_start, edge_dir, superface);
|
|
return;
|
|
}
|
|
|
|
// the edge p1 to p2 is now free of tjunctions
|
|
superface.push_back(p1);
|
|
}
|
|
|
|
/*
|
|
==========
|
|
FindEdgeVerts_BruteForce
|
|
|
|
Force a dumb check of everything
|
|
==========
|
|
*/
|
|
static void FindEdgeVerts_BruteForce(const node_t *, const node_t *, const qvec3d &, const qvec3d &, std::vector<size_t> &verts)
|
|
{
|
|
verts.resize(map.bsp.dvertexes.size());
|
|
|
|
for (size_t i = 0; i < verts.size(); i++) {
|
|
verts[i] = i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==========
|
|
FindEdgeVerts_FaceBounds_R
|
|
|
|
Recursive function for matching nodes that intersect the aabb
|
|
for vertex checking.
|
|
==========
|
|
*/
|
|
static void FindEdgeVerts_FaceBounds_R(const node_t *node, const aabb3d &aabb, std::vector<size_t> &verts)
|
|
{
|
|
if (node->planenum == PLANENUM_LEAF) {
|
|
return;
|
|
} else if (node->bounds.disjoint(aabb, 0.0)) {
|
|
return;
|
|
}
|
|
|
|
for (auto &face : node->facelist) {
|
|
for (auto &v : face->output_vertices) {
|
|
if (aabb.containsPoint(map.bsp.dvertexes[v])) {
|
|
verts.push_back(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
FindEdgeVerts_FaceBounds_R(node->children[0].get(), aabb, verts);
|
|
FindEdgeVerts_FaceBounds_R(node->children[1].get(), aabb, verts);
|
|
}
|
|
|
|
/*
|
|
==========
|
|
FindEdgeVerts_FaceBounds
|
|
|
|
Use a loose AABB around the line and only capture vertices that intersect it.
|
|
==========
|
|
*/
|
|
static void FindEdgeVerts_FaceBounds(const node_t *headnode, const node_t *node, const qvec3d &p1, const qvec3d &p2, std::vector<size_t> &verts)
|
|
{
|
|
// magic number, average of "usual" points per edge
|
|
verts.reserve(8);
|
|
|
|
FindEdgeVerts_FaceBounds_R(headnode, (aabb3d{} + p1 + p2).grow(qvec3d(1.0, 1.0, 1.0)), verts);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FaceFromSuperverts
|
|
|
|
The faces vertexes have been added to the superverts[] array,
|
|
and there may be more there than can be held in a face (MAXEDGES).
|
|
|
|
If less, the faces vertexnums[] will be filled in, otherwise
|
|
face will reference a tree of split[] faces until all of the
|
|
vertexnums can be added.
|
|
|
|
superverts[base] will become face->vertexnums[0], and the others
|
|
will be circularly filled in.
|
|
==================
|
|
*/
|
|
inline void FaceFromSuperverts(node_t *node, face_t *f, size_t base, const std::vector<size_t> &superface)
|
|
{
|
|
size_t remaining = superface.size();
|
|
|
|
// split into multiple fragments, because of vertex overload
|
|
while (remaining > MAXEDGES) {
|
|
c_faceoverflows++;
|
|
|
|
auto &newf = f->fragments.emplace_back();
|
|
|
|
newf.output_vertices.resize(MAXEDGES);
|
|
|
|
for (size_t i = 0; i < MAXEDGES; i++) {
|
|
newf.output_vertices[i] = superface[(i + base) % superface.size()];
|
|
}
|
|
|
|
remaining -= (MAXEDGES - 2);
|
|
base = (base + MAXEDGES - 1) % superface.size();
|
|
}
|
|
|
|
// copy the vertexes back to the face
|
|
f->output_vertices.resize(remaining);
|
|
|
|
for (size_t i = 0; i < remaining; i++) {
|
|
f->output_vertices[i] = superface[(i + base) % superface.size()];
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FixFaceEdges
|
|
==================
|
|
*/
|
|
static void FixFaceEdges(node_t *headnode, node_t *node, face_t *f)
|
|
{
|
|
#if 0
|
|
std::vector<size_t> count, start;
|
|
#endif
|
|
std::vector<size_t> superface;
|
|
superface.reserve(64);
|
|
|
|
std::vector<size_t> edge_verts;
|
|
|
|
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()];
|
|
|
|
qvec3d edge_start = map.bsp.dvertexes[v1];
|
|
qvec3d e2 = map.bsp.dvertexes[v2];
|
|
|
|
FindEdgeVerts_FaceBounds(headnode, node, edge_start, e2, edge_verts);
|
|
|
|
vec_t len;
|
|
qvec3d edge_dir = qv::normalize(e2 - edge_start, len);
|
|
|
|
#if 0
|
|
start.push_back(superface.size());
|
|
#endif
|
|
|
|
TestEdge(0, len, v1, v2, 0, edge_verts, edge_start, edge_dir, superface);
|
|
|
|
#if 0
|
|
count.push_back(superface.size() - start[i]);
|
|
#endif
|
|
}
|
|
|
|
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
|
|
return;
|
|
}
|
|
|
|
// brute force rotating the start point until we find a valid winding
|
|
// that doesn't have any zero-area triangles
|
|
size_t i = 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()];
|
|
|
|
qvec3d d1 = map.bsp.dvertexes[v2] - map.bsp.dvertexes[v0];
|
|
qvec3d d2 = map.bsp.dvertexes[v1] - map.bsp.dvertexes[v0];
|
|
vec_t total = 0.5 * qv::length(qv::cross(d1, d2));
|
|
|
|
if (!total) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// found one!
|
|
if (x == superface.size() - 2) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == superface.size()) {
|
|
// can't simply rotate to eliminate zero-area triangles, so we have
|
|
// to do a bit of re-topology.
|
|
c_norotates++;
|
|
} else if (i != 0) {
|
|
// have to rotate the superface to get it to work properly.
|
|
// use the face's output vertices as scratch space.
|
|
// brushbsp-todo: we can avoid this if we work directly on
|
|
// f->output_vertices in FaceFromSuperverts.
|
|
c_rotates++;
|
|
|
|
f->output_vertices.resize(superface.size());
|
|
// copy base -> end
|
|
auto out = std::copy(superface.begin() + i, superface.end(), f->output_vertices.begin());
|
|
// copy end -> base
|
|
std::copy(superface.begin(), superface.begin() + i, out);
|
|
// copy back to superface
|
|
superface = f->output_vertices;
|
|
}
|
|
|
|
#if 0
|
|
// we want to pick a vertex that doesn't have tjunctions
|
|
// on either side, which can cause artifacts on trifans,
|
|
// especially underwater
|
|
size_t i = 0;
|
|
|
|
for (; i < f->output_vertices.size(); i++) {
|
|
if (count[i] == 1 && count[(i + f->output_vertices.size() - 1) % f->output_vertices.size()] == 1) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
size_t base;
|
|
|
|
if (i == f->output_vertices.size()) {
|
|
c_badstartverts++;
|
|
base = 0;
|
|
} else {
|
|
// rotate the vertex order
|
|
base = start[i];
|
|
}
|
|
#endif
|
|
|
|
if (superface.size() > MAXEDGES) {
|
|
// split face into multiple faces if we're too large
|
|
FaceFromSuperverts(node, f, 0, superface);
|
|
} else {
|
|
// we aren't too large, so just move the superface right in
|
|
// brushbsp-todo: we can avoid this if we work directly on
|
|
// f->output_vertices in FaceFromSuperverts.
|
|
f->output_vertices = std::move(superface);
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
FixEdges_r
|
|
==================
|
|
*/
|
|
static void FixEdges_r(node_t *headnode, node_t *node)
|
|
{
|
|
if (node->planenum == PLANENUM_LEAF) {
|
|
return;
|
|
}
|
|
|
|
for (auto &f : node->facelist) {
|
|
// might have been omitted earlier, so `output_vertices` will be empty
|
|
if (f->output_vertices.size()) {
|
|
FixFaceEdges(headnode, node, f.get());
|
|
}
|
|
}
|
|
|
|
FixEdges_r(headnode, node->children[0].get());
|
|
FixEdges_r(headnode, node->children[1].get());
|
|
}
|
|
|
|
/*
|
|
===========
|
|
tjunc
|
|
===========
|
|
*/
|
|
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;
|
|
#if 0
|
|
c_badstartverts = 0;
|
|
#endif
|
|
c_norotates = 0;
|
|
c_rotates = 0;
|
|
|
|
FixEdges_r (headnode, headnode);
|
|
|
|
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 added by tjunctions\n", c_faceoverflows);
|
|
#if 0
|
|
logging::print (logging::flag::STAT, "{:5} bad start verts\n", c_badstartverts);
|
|
#endif
|
|
logging::print (logging::flag::STAT, "{:5} faces unable to be rotated\n", c_norotates);
|
|
logging::print (logging::flag::STAT, "{:5} faces rotated\n", c_rotates);
|
|
}
|