You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
997 lines
40 KiB
997 lines
40 KiB
// David Eberly, Geometric Tools, Redmond WA 98052
|
|
// Copyright (c) 1998-2021
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// https://www.boost.org/LICENSE_1_0.txt
|
|
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
|
|
// Version: 4.0.2021.04.22
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Logger.h>
|
|
#include <Mathematics/EdgeKey.h>
|
|
#include <Mathematics/HashCombine.h>
|
|
#include <Mathematics/TriangleKey.h>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <queue>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace gte
|
|
{
|
|
class ETManifoldMesh
|
|
{
|
|
public:
|
|
// Edge data types.
|
|
class Edge;
|
|
typedef std::unique_ptr<Edge>(*ECreator)(int, int);
|
|
using EMap = std::unordered_map<EdgeKey<false>, std::unique_ptr<Edge>,
|
|
EdgeKey<false>, EdgeKey<false>>;
|
|
|
|
// Triangle data types.
|
|
class Triangle;
|
|
typedef std::unique_ptr<Triangle>(*TCreator)(int, int, int);
|
|
using TMap = std::unordered_map<TriangleKey<true>, std::unique_ptr<Triangle>,
|
|
TriangleKey<true>, TriangleKey<true>>;
|
|
|
|
// Edge object.
|
|
class Edge
|
|
{
|
|
public:
|
|
virtual ~Edge() = default;
|
|
|
|
Edge(int v0, int v1)
|
|
:
|
|
V{ v0, v1 }
|
|
{
|
|
T.fill(nullptr);
|
|
}
|
|
|
|
// Vertices of the edge.
|
|
std::array<int, 2> V;
|
|
|
|
// Triangles sharing the edge.
|
|
std::array<Triangle*, 2> T;
|
|
};
|
|
|
|
// Triangle object.
|
|
class Triangle
|
|
{
|
|
public:
|
|
virtual ~Triangle() = default;
|
|
|
|
Triangle(int v0, int v1, int v2)
|
|
:
|
|
V{ v0, v1, v2 }
|
|
{
|
|
E.fill(nullptr);
|
|
T.fill(nullptr);
|
|
}
|
|
|
|
// The edge <u0,u1> is directed. Determine whether the triangle
|
|
// has an edge <V[i],V[(i+1)%3]> = <u0,u1> (return +1) or an edge
|
|
// <V[i],V[(i+1)%3]> = <u1,u0> (return -1) or does not have an
|
|
// edge meeting either condition (return 0).
|
|
int WhichSideOfEdge(int u0, int u1) const
|
|
{
|
|
for (size_t i0 = 2, i1 = 0; i1 < 3; i0 = i1++)
|
|
{
|
|
if (V[i0] == u0 && V[i1] == u1)
|
|
{
|
|
return +1;
|
|
}
|
|
if (V[i0] == u1 && V[i1] == u0)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Triangle* GetAdjacentOfEdge(int u0, int u1)
|
|
{
|
|
for (size_t i0 = 2, i1 = 0; i1 < 3; i0 = i1++)
|
|
{
|
|
if ((V[i0] == u0 && V[i1] == u1) || (V[i0] == u1 && V[i1] == u0))
|
|
{
|
|
return T[i0];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool GetOppositeVertexOfEdge(int u0, int u1, int& uOpposite)
|
|
{
|
|
for (size_t i0 = 2, i1 = 0; i1 < 3; i0 = i1++)
|
|
{
|
|
if ((V[i0] == u0 && V[i1] == u1) || (V[i0] == u1 && V[i1] == u0))
|
|
{
|
|
uOpposite = V[(i1 + 1) % 3];
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Vertices, listed in counterclockwise order (V[0],V[1],V[2]).
|
|
std::array<int, 3> V;
|
|
|
|
// Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]).
|
|
std::array<Edge*, 3> E;
|
|
|
|
// Adjacent triangles. T[i] points to the adjacent triangle
|
|
// sharing edge E[i].
|
|
std::array<Triangle*, 3> T;
|
|
};
|
|
|
|
|
|
// Construction and destruction.
|
|
virtual ~ETManifoldMesh() = default;
|
|
|
|
ETManifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr)
|
|
:
|
|
mECreator(eCreator ? eCreator : CreateEdge),
|
|
mTCreator(tCreator ? tCreator : CreateTriangle),
|
|
mThrowOnNonmanifoldInsertion(true)
|
|
{
|
|
}
|
|
|
|
// Support for a deep copy of the mesh. The mEMap and mTMap objects
|
|
// have dynamically allocated memory for edges and triangles. A
|
|
// shallow copy of the pointers isn't possible with unique_ptr.
|
|
ETManifoldMesh(ETManifoldMesh const& mesh)
|
|
{
|
|
*this = mesh;
|
|
}
|
|
|
|
ETManifoldMesh& operator=(ETManifoldMesh const& mesh)
|
|
{
|
|
Clear();
|
|
|
|
mECreator = mesh.mECreator;
|
|
mTCreator = mesh.mTCreator;
|
|
mThrowOnNonmanifoldInsertion = mesh.mThrowOnNonmanifoldInsertion;
|
|
for (auto const& element : mesh.mTMap)
|
|
{
|
|
// The typecast avoids warnings about not storing the return
|
|
// value in a named variable. The return value is discarded.
|
|
(void)Insert(element.first.V[0], element.first.V[1], element.first.V[2]);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
// Member access.
|
|
inline EMap const& GetEdges() const
|
|
{
|
|
return mEMap;
|
|
}
|
|
|
|
inline TMap const& GetTriangles() const
|
|
{
|
|
return mTMap;
|
|
}
|
|
|
|
// If the insertion of a triangle fails because the mesh would become
|
|
// nonmanifold, the default behavior is to throw an exception. You
|
|
// can disable this behavior and continue gracefully without an
|
|
// exception. The return value is the previous value of the internal
|
|
// state mAssertOnNonmanifoldInsertion.
|
|
bool ThrowOnNonmanifoldInsertion(bool doException)
|
|
{
|
|
std::swap(doException, mThrowOnNonmanifoldInsertion);
|
|
return doException; // return the previous state
|
|
}
|
|
|
|
// If <v0,v1,v2> is not in the mesh, a Triangle object is created and
|
|
// returned; otherwise, <v0,v1,v2> is in the mesh and nullptr is
|
|
// returned. If the insertion leads to a nonmanifold mesh, the call
|
|
// fails with a nullptr returned.
|
|
virtual Triangle* Insert(int v0, int v1, int v2)
|
|
{
|
|
TriangleKey<true> tkey(v0, v1, v2);
|
|
if (mTMap.find(tkey) != mTMap.end())
|
|
{
|
|
// The triangle already exists. Return a null pointer as a
|
|
// signal to the caller that the insertion failed.
|
|
return nullptr;
|
|
}
|
|
|
|
// Create the new triangle. It will be added to mTMap at the end
|
|
// of the function so that if an assertion is triggered and the
|
|
// function returns early, the (bad) triangle will not be part of
|
|
// the mesh.
|
|
std::unique_ptr<Triangle> newTri = mTCreator(v0, v1, v2);
|
|
Triangle* tri = newTri.get();
|
|
|
|
// Add the edges to the mesh if they do not already exist.
|
|
for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++)
|
|
{
|
|
EdgeKey<false> ekey(tri->V[i0], tri->V[i1]);
|
|
Edge* edge;
|
|
auto eiter = mEMap.find(ekey);
|
|
if (eiter == mEMap.end())
|
|
{
|
|
// This is the first time the edge is encountered.
|
|
std::unique_ptr<Edge> newEdge = mECreator(tri->V[i0], tri->V[i1]);
|
|
edge = newEdge.get();
|
|
mEMap[ekey] = std::move(newEdge);
|
|
|
|
// Update the edge and triangle.
|
|
edge->T[0] = tri;
|
|
tri->E[i0] = edge;
|
|
}
|
|
else
|
|
{
|
|
// This is the second time the edge is encountered.
|
|
edge = eiter->second.get();
|
|
LogAssert(edge != nullptr, "Unexpected condition.");
|
|
|
|
// Update the edge.
|
|
if (edge->T[1])
|
|
{
|
|
if (mThrowOnNonmanifoldInsertion)
|
|
{
|
|
LogError("Attempt to create nonmanifold mesh.");
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
edge->T[1] = tri;
|
|
|
|
// Update the adjacent triangles.
|
|
auto adjacent = edge->T[0];
|
|
LogAssert(adjacent != nullptr, "Unexpected condition.");
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
if (adjacent->E[j] == edge)
|
|
{
|
|
adjacent->T[j] = tri;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update the triangle.
|
|
tri->E[i0] = edge;
|
|
tri->T[i0] = adjacent;
|
|
}
|
|
}
|
|
|
|
mTMap[tkey] = std::move(newTri);
|
|
return tri;
|
|
}
|
|
|
|
// If <v0,v1,v2> is in the mesh, it is removed and 'true' is
|
|
// returned; otherwise, <v0,v1,v2> is not in the mesh and 'false' is
|
|
// returned.
|
|
virtual bool Remove(int v0, int v1, int v2)
|
|
{
|
|
TriangleKey<true> tkey(v0, v1, v2);
|
|
auto titer = mTMap.find(tkey);
|
|
if (titer == mTMap.end())
|
|
{
|
|
// The triangle does not exist.
|
|
return false;
|
|
}
|
|
|
|
// Get the triangle.
|
|
Triangle* tri = titer->second.get();
|
|
|
|
// Remove the edges and update adjacent triangles if necessary.
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
// Inform the edges the triangle is being deleted.
|
|
auto edge = tri->E[i];
|
|
LogAssert(edge != nullptr, "Unexpected condition.");
|
|
|
|
if (edge->T[0] == tri)
|
|
{
|
|
// One-triangle edges always have pointer at index zero.
|
|
edge->T[0] = edge->T[1];
|
|
edge->T[1] = nullptr;
|
|
}
|
|
else if (edge->T[1] == tri)
|
|
{
|
|
edge->T[1] = nullptr;
|
|
}
|
|
else
|
|
{
|
|
LogError("Unexpected condition.");
|
|
}
|
|
|
|
// Remove the edge if you have the last reference to it.
|
|
if (!edge->T[0] && !edge->T[1])
|
|
{
|
|
EdgeKey<false> ekey(edge->V[0], edge->V[1]);
|
|
mEMap.erase(ekey);
|
|
}
|
|
|
|
// Inform adjacent triangles the triangle is being deleted.
|
|
auto adjacent = tri->T[i];
|
|
if (adjacent)
|
|
{
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
if (adjacent->T[j] == tri)
|
|
{
|
|
adjacent->T[j] = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mTMap.erase(tkey);
|
|
return true;
|
|
}
|
|
|
|
// Destroy the edges and triangles to obtain an empty mesh.
|
|
virtual void Clear()
|
|
{
|
|
mEMap.clear();
|
|
mTMap.clear();
|
|
}
|
|
|
|
// A manifold mesh is closed if each edge is shared twice. A closed
|
|
// mesh is not necessarily oriented. For example, you could have a
|
|
// mesh with spherical topology. The upper hemisphere has outer
|
|
// facing normals and the lower hemisphere has inner-facing normals.
|
|
// The discontinuity in orientation occurs on the circle shared by the
|
|
// hemispheres.
|
|
bool IsClosed() const
|
|
{
|
|
for (auto const& element : mEMap)
|
|
{
|
|
Edge* edge = element.second.get();
|
|
if (!edge->T[0] || !edge->T[1])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Test whether all triangles in the mesh are oriented consistently
|
|
// and that no two triangles are coincident. The latter means that
|
|
// you cannot have both triangles <v0,v1,v2> and <v0,v2,v1> in the
|
|
// mesh to be considered oriented.
|
|
bool IsOriented() const
|
|
{
|
|
for (auto const& element : mEMap)
|
|
{
|
|
Edge* edge = element.second.get();
|
|
if (edge->T[0] && edge->T[1])
|
|
{
|
|
// In each triangle, find the ordered edge that
|
|
// corresponds to the unordered edge element.first. Also
|
|
// find the vertex opposite that edge.
|
|
bool edgePositive[2] = { false, false };
|
|
int vOpposite[2] = { -1, -1 };
|
|
for (int j = 0; j < 2; ++j)
|
|
{
|
|
auto tri = edge->T[j];
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
if (tri->V[i] == element.first.V[0])
|
|
{
|
|
int vNext = tri->V[(i + 1) % 3];
|
|
if (vNext == element.first.V[1])
|
|
{
|
|
edgePositive[j] = true;
|
|
vOpposite[j] = tri->V[(i + 2) % 3];
|
|
}
|
|
else
|
|
{
|
|
edgePositive[j] = false;
|
|
vOpposite[j] = vNext;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// To be oriented consistently, the edges must have
|
|
// reversed ordering and the oppositive vertices cannot
|
|
// match.
|
|
if (edgePositive[0] == edgePositive[1] || vOpposite[0] == vOpposite[1])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Compute the connected components of the edge-triangle graph that
|
|
// the mesh represents. The first function returns pointers into
|
|
// 'this' object's containers, so you must consume the components
|
|
// before clearing or destroying 'this'. The second function returns
|
|
// triangle keys, which requires three times as much storage as the
|
|
// pointers but allows you to clear or destroy 'this' before consuming
|
|
// the components.
|
|
void GetComponents(std::vector<std::vector<Triangle*>>& components) const
|
|
{
|
|
// visited: 0 (unvisited), 1 (discovered), 2 (finished)
|
|
TrianglePtrIntMap visited;
|
|
for (auto const& element : mTMap)
|
|
{
|
|
visited.insert(std::make_pair(element.second.get(), 0));
|
|
}
|
|
|
|
for (auto& element : mTMap)
|
|
{
|
|
Triangle* tri = element.second.get();
|
|
if (visited[tri] == 0)
|
|
{
|
|
std::vector<Triangle*> component;
|
|
DepthFirstSearch(tri, visited, component);
|
|
components.push_back(std::move(component));
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetComponents(std::vector<std::vector<TriangleKey<true>>>& components) const
|
|
{
|
|
// visited: 0 (unvisited), 1 (discovered), 2 (finished)
|
|
TrianglePtrIntMap visited;
|
|
for (auto const& element : mTMap)
|
|
{
|
|
visited.insert(std::make_pair(element.second.get(), 0));
|
|
}
|
|
|
|
for (auto& element : mTMap)
|
|
{
|
|
Triangle* tri = element.second.get();
|
|
if (visited[tri] == 0)
|
|
{
|
|
std::vector<Triangle*> component;
|
|
DepthFirstSearch(tri, visited, component);
|
|
|
|
std::vector<TriangleKey<true>> keyComponent;
|
|
keyComponent.reserve(component.size());
|
|
for (auto const& t : component)
|
|
{
|
|
keyComponent.push_back(TriangleKey<true>(t->V[0], t->V[1], t->V[2]));
|
|
}
|
|
components.push_back(std::move(keyComponent));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a compact edge-triangle graph. The vertex indices are those
|
|
// integers passed to an Insert(...) call. These have no meaning to
|
|
// the semantics of maintaining an edge-triangle manifold mesh, so
|
|
// this class makes no assumption about them. The vertex indices do
|
|
// not necessarily start at 0 and they are not necessarily contiguous
|
|
// numbers. The triangles are represented by triples of vertex
|
|
// indices. The compact graph stores these in an array of N triples,
|
|
// say,
|
|
// T[0] = (v0,v1,v2), T[1] = (v3,v4,v5), ...
|
|
// Each triangle has up to 3 adjacent triangles. The compact graph
|
|
// stores the adjacency information in an array of N triples, say,
|
|
// and the indices of adjacent triangle are stored in an array of Nt
|
|
// A[0] = (t0,t1,t2), A[1] = (t3,t4,t5), ...
|
|
// where the ti are indices into the array of triangles. For example,
|
|
// the triangle T[0] has edges E[0] = (v0,v1), E[1] = (v1,v2) and
|
|
// E[2] = (v2,v0). The edge E[0] has adjacent triangle T[0]. If E[0]
|
|
// has another adjacent triangle, it is T[A[0][0]. If it does not have
|
|
// another adjacent triangle, then A[0][0] = -1 (represented by
|
|
// std::numeric_limits<size_t>::max()). Similar assignments are made
|
|
// for the other two edges which produces A[0][1] for E[1] and
|
|
// A[0][2] for E[2].
|
|
void CreateCompactGraph(
|
|
std::vector<std::array<size_t, 3>>& triangles,
|
|
std::vector<std::array<size_t, 3>>& adjacents) const
|
|
{
|
|
size_t const numTriangles = mTMap.size();
|
|
LogAssert(numTriangles > 0, "Invalid input.");
|
|
|
|
triangles.resize(numTriangles);
|
|
adjacents.resize(numTriangles);
|
|
|
|
TrianglePtrSizeTMap triIndexMap;
|
|
triIndexMap.insert(std::make_pair(nullptr, std::numeric_limits<size_t>::max()));
|
|
size_t index = 0;
|
|
for (auto const& element : mTMap)
|
|
{
|
|
triIndexMap.insert(std::make_pair(element.second.get(), index++));
|
|
}
|
|
|
|
index = 0;
|
|
for (auto const& element : mTMap)
|
|
{
|
|
auto const& triPtr = element.second;
|
|
auto& tri = triangles[index];
|
|
auto& adj = adjacents[index];
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = triPtr->V[j];
|
|
adj[j] = triIndexMap[triPtr->T[j]];
|
|
}
|
|
++index;
|
|
}
|
|
}
|
|
|
|
// The output of CreateCompactGraph can be used to compute the
|
|
// connected components of the graph, each component having
|
|
// triangles with the same chirality (winding order). Using only
|
|
// the mesh topology, it is not possible to ensure that the
|
|
// chirality is the same for all the components. Additional
|
|
// application-specific geometric information is required.
|
|
//
|
|
// The 'components' contain indices into the 'triangles' array
|
|
// and is partitioned into C subarrays, each representing a
|
|
// connected component. The lengths of the subarrays are
|
|
// stored in 'numComponentTriangles[]'. The number of elements
|
|
// of this array is C. It is the case that the number of triangles
|
|
// in the mesh is sum_{i=0}^{C-1} numComponentTriangles[i].
|
|
//
|
|
// On return, the 'triangles' and 'adjacents' have been modified
|
|
// and have the correct chirality.
|
|
static void GetComponentsConsistentChirality(
|
|
std::vector<std::array<size_t, 3>>& triangles,
|
|
std::vector<std::array<size_t, 3>>& adjacents,
|
|
std::vector<size_t>& components,
|
|
std::vector<size_t>& numComponentTriangles)
|
|
{
|
|
LogAssert(triangles.size() > 0 && triangles.size() == adjacents.size(), "Invalid inputs.");
|
|
|
|
// Use a breadth-first search to process the chirality of the
|
|
// triangles. Keep track of the connected components.
|
|
size_t const numTriangles = triangles.size();
|
|
std::vector<bool> visited(numTriangles, false);
|
|
components.reserve(numTriangles);
|
|
components.clear();
|
|
|
|
// The 'firstUnvisited' index is the that of the first triangle to
|
|
// process in a connected component of the mesh.
|
|
for (;;)
|
|
{
|
|
// Let n[i] be the number of elements of the i-th connected
|
|
// component. Let C be the number of components. During the
|
|
// execution of this loop, the array numComponentTriangles
|
|
// stores
|
|
// {0, n[0], n[0]+n[1], ..., n[0]+...+n[C-1]=numTriangles}
|
|
// At the end of this function, the array is modified to
|
|
// {n[0], n[1], ..., n[C-1]}
|
|
numComponentTriangles.push_back(components.size());
|
|
|
|
// Find the starting index of a connected component.
|
|
size_t firstUnvisited = numTriangles;
|
|
for (size_t i = 0; i < numTriangles; ++i)
|
|
{
|
|
if (!visited[i])
|
|
{
|
|
firstUnvisited = i;
|
|
break;
|
|
}
|
|
}
|
|
if (firstUnvisited == numTriangles)
|
|
{
|
|
// All connected components have been found.
|
|
break;
|
|
}
|
|
|
|
// Initialize the queue to start at the first unvisited
|
|
// triangle of a connected component.
|
|
std::queue<size_t> triQueue;
|
|
triQueue.push(firstUnvisited);
|
|
visited[firstUnvisited] = true;
|
|
components.push_back(firstUnvisited);
|
|
|
|
// Perform the breadth-first search.
|
|
while (!triQueue.empty())
|
|
{
|
|
size_t curIndex = triQueue.front();
|
|
triQueue.pop();
|
|
|
|
auto const& curTriangle = triangles[curIndex];
|
|
for (size_t i0 = 0; i0 < 3; ++i0)
|
|
{
|
|
size_t adjIndex = adjacents[curIndex][i0];
|
|
if (adjIndex != std::numeric_limits<size_t>::max() && !visited[adjIndex])
|
|
{
|
|
// The current-triangle has a directed edge
|
|
// <curTriangle[i0],curTriangle[i1]> and there is
|
|
// a triangle adjacent to it.
|
|
size_t i1 = (i0 + 1) % 3;
|
|
auto& adjTriangle = triangles[adjIndex];
|
|
size_t tv0 = curTriangle[i0];
|
|
size_t tv1 = curTriangle[i1];
|
|
|
|
// To have the same chirality, it is required
|
|
// that the adjacent triangle have the directed
|
|
// edge <curTriangle[i1],curTriangle[i0]>
|
|
bool sameChirality = true;
|
|
size_t j0, j1;
|
|
for (j0 = 0; j0 < 3; ++j0)
|
|
{
|
|
j1 = (j0 + 1) % 3;
|
|
if (adjTriangle[j0] == tv0)
|
|
{
|
|
if (adjTriangle[j1] == tv1)
|
|
{
|
|
// The adjacent triangle has the same
|
|
// directed edge as the current
|
|
// triangle, so the chiralities do
|
|
// not match.
|
|
sameChirality = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
LogAssert(j0 < 3, "Unexpected condition");
|
|
|
|
if (!sameChirality)
|
|
{
|
|
// Swap the vertices of the adjacent triangle
|
|
// that form the shared directed edge of the
|
|
// current triangle. This requires that the
|
|
// adjacency information for the other two
|
|
// edges of the adjacent triangle be swapped.
|
|
auto& adjAdjacent = adjacents[adjIndex];
|
|
size_t j2 = (j1 + 1) % 3;
|
|
std::swap(adjTriangle[j0], adjTriangle[j1]);
|
|
std::swap(adjAdjacent[j1], adjAdjacent[j2]);
|
|
}
|
|
|
|
// The adjacent triangle has been processed, but
|
|
// it might have neighbors that need to be
|
|
// processed. Push the adjacent triangle into the
|
|
// queue to ensure this happens. Insert the
|
|
// adjacent triangle into the active connected
|
|
// component.
|
|
triQueue.push(adjIndex);
|
|
visited[adjIndex] = true;
|
|
components.push_back(adjIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the comments at the beginning of this function.
|
|
size_t const numSizes = numComponentTriangles.size();
|
|
LogAssert(numSizes > 1, "Unexpected condition (this should not happen).");
|
|
for (size_t i0 = 0, i1 = 1; i1 < numComponentTriangles.size(); i0 = i1++)
|
|
{
|
|
numComponentTriangles[i0] = numComponentTriangles[i1] - numComponentTriangles[i0];
|
|
}
|
|
numComponentTriangles.resize(numSizes - 1);
|
|
}
|
|
|
|
// This is a simple wrapper around CreateCompactGraph(...) and
|
|
// GetComponentsConsistentChirality(...), in particular when you
|
|
// do not need to work directly with the connected components.
|
|
// The mesh is reconstructed, because the bookkeeping details of
|
|
// trying to modify the mesh in-place are horrendous. NOTE: If
|
|
// your mesh has more than 1 connected component, you should read
|
|
// the comments for GetComponentsConsistentChirality(...) about
|
|
// the potential for different chiralities between components.
|
|
virtual void MakeConsistentChirality()
|
|
{
|
|
std::vector<std::array<size_t, 3>> triangles;
|
|
std::vector<std::array<size_t, 3>> adjacents;
|
|
CreateCompactGraph(triangles, adjacents);
|
|
|
|
std::vector<size_t> components, numComponentTriangles;
|
|
GetComponentsConsistentChirality(triangles, adjacents,
|
|
components, numComponentTriangles);
|
|
|
|
// Only the 'triangles' array is needed to reconstruct the
|
|
// mesh. The other arrays are discarded.
|
|
Clear();
|
|
|
|
for (auto const& triangle : triangles)
|
|
{
|
|
int v0 = static_cast<int>(triangle[0]);
|
|
int v1 = static_cast<int>(triangle[1]);
|
|
int v2 = static_cast<int>(triangle[2]);
|
|
|
|
// The typecast avoids warnings about not storing the return
|
|
// value in a named variable. The return value is discarded.
|
|
(void)Insert(v0, v1, v2);
|
|
}
|
|
}
|
|
|
|
// Compute the boundary-edge components of the mesh. These are
|
|
// simple closed polygons. A vertex adjacency graph is computed
|
|
// internally. A vertex with exactly 2 neighbors is the common
|
|
// case that is easy to process. A vertex with 2n neighbors, where
|
|
// n > 1, is a branch point of the graph. The algorithm computes
|
|
// n pairs of edges at a branch point, each pair bounding a triangle
|
|
// strip whose triangles all share the branch point. If you select
|
|
// duplicateEndpoints to be false, a component has consecutive
|
|
// vertices (v[0], v[1], ..., v[n-1]) and the polygon has edges
|
|
// (v[0],v[1]), (v[1],v[2]), ..., (v[n-2],v[n-1]), (v[n-1],v[0]).
|
|
// If duplicateEndpoints is set to true, a component has consecutive
|
|
// vertices (v[0], v[1], ..., v[n-1], v[0]), emphasizing that the
|
|
// component is closed.
|
|
void GetBoundaryPolygons(std::vector<std::vector<int>>& components,
|
|
bool duplicateEndpoints) const
|
|
{
|
|
components.clear();
|
|
|
|
// Build the vertex adjacency graph.
|
|
std::unordered_set<int> boundaryVertices;
|
|
std::multimap<int, int> vertexGraph;
|
|
for (auto const& element : mEMap)
|
|
{
|
|
auto adj = element.second->T[1];
|
|
if (!adj)
|
|
{
|
|
int v0 = element.first.V[0], v1 = element.first.V[1];
|
|
boundaryVertices.insert(v0);
|
|
boundaryVertices.insert(v1);
|
|
vertexGraph.insert(std::make_pair(v0, v1));
|
|
vertexGraph.insert(std::make_pair(v1, v0));
|
|
}
|
|
}
|
|
|
|
// Create a set of edge pairs. For a 2-adjacency vertex v with
|
|
// adjacent vertices v0 and v1, an edge pair is (v, v0, v1) which
|
|
// represents undirected edges (v, v0) and (v, v1). A vertex with
|
|
// 2n-adjacency has n edge pairs of the form (v, v0, v1). Each
|
|
// edge pair forms the boundary of a triangle strip where each
|
|
// triangle shares v. When traversing a boundary curve for a
|
|
// connected component of triangles, if a 2n-adjacency vertex v is
|
|
// encountered, let v0 be the incoming vertex. The edge pair
|
|
// containing v and v0 is selected to generate the outgoing
|
|
// vertex v1.
|
|
int const invalidVertex = *boundaryVertices.begin() - 1;
|
|
std::multimap<int, std::array<int, 2>> edgePairs;
|
|
for (auto v : boundaryVertices)
|
|
{
|
|
// The number of adjacent vertices is positive and even.
|
|
size_t numAdjacents = vertexGraph.count(v);
|
|
if (numAdjacents == 2)
|
|
{
|
|
auto lbIter = vertexGraph.lower_bound(v);
|
|
std::array<int, 2> endpoints = { 0, 0 };
|
|
endpoints[0] = lbIter->second;
|
|
++lbIter;
|
|
endpoints[1] = lbIter->second;
|
|
edgePairs.insert(std::make_pair(v, endpoints));
|
|
}
|
|
else
|
|
{
|
|
// Create pairs of vertices that form a wedge of triangles
|
|
// at the vertex v, as a triangle strip of triangles all
|
|
// sharing v.
|
|
std::unordered_set<int> adjacents;
|
|
auto lbIter = vertexGraph.lower_bound(v);
|
|
auto ubIter = vertexGraph.upper_bound(v);
|
|
for (auto vgIter = lbIter; vgIter != ubIter; ++vgIter)
|
|
{
|
|
adjacents.insert(vgIter->second);
|
|
}
|
|
|
|
size_t const numEdgePairs = adjacents.size() / 2;
|
|
for (size_t j = 0; j < numEdgePairs; ++j)
|
|
{
|
|
// Get the first vertex of a pair of edges.
|
|
auto adjIter = adjacents.begin();
|
|
int vAdjacent = *adjIter;
|
|
adjacents.erase(adjIter);
|
|
|
|
// The wedge of triangles at v starts with a triangle
|
|
// that has the boundary edge.
|
|
EdgeKey<false> ekey(v, vAdjacent);
|
|
auto edge = mEMap.find(ekey);
|
|
LogAssert(edge != mEMap.end(), "unexpected condition");
|
|
auto tCurrent = edge->second->T[0];
|
|
LogAssert(tCurrent != nullptr, "unexpected condtion");
|
|
|
|
// Traverse the triangle strip to the other boundary
|
|
// edge that bounds the wedge.
|
|
int vOpposite = invalidVertex;
|
|
int vStart = vAdjacent;
|
|
for (;;)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
vOpposite = tCurrent->V[i];
|
|
if (vOpposite != v && vOpposite != vAdjacent)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
LogAssert(i < 3, "unexpected condition");
|
|
|
|
ekey = EdgeKey<false>(v, vOpposite);
|
|
edge = mEMap.find(ekey);
|
|
auto tNext = edge->second->T[1];
|
|
if (tNext == nullptr)
|
|
{
|
|
// Found the last triangle in the strip.
|
|
break;
|
|
}
|
|
|
|
// The edge is interior to the component. Traverse
|
|
// to the triangle adjacent to the current one.
|
|
if (tNext == tCurrent)
|
|
{
|
|
tCurrent = edge->second->T[0];
|
|
}
|
|
else
|
|
{
|
|
tCurrent = tNext;
|
|
}
|
|
vAdjacent = vOpposite;
|
|
}
|
|
|
|
// The boundary edge of the first triangle in the
|
|
// wedge is (v, vAdjacent). The boundary edge of the
|
|
// last triangle in the wedge is (v, vOpposite).
|
|
std::array<int, 2> endpoints{ vStart, vOpposite };
|
|
edgePairs.insert(std::make_pair(v, endpoints));
|
|
adjacents.erase(vOpposite);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (edgePairs.size() > 0)
|
|
{
|
|
// The EdgeKey<false> represents an unordered edge (v0,v1).
|
|
// Choose the starting vertex so that when you traverse from
|
|
// it to the other vertex, the direction is consistent with
|
|
// the chirality of the triangle containing that edge. For a
|
|
// component whose triangles have the same chirality, this
|
|
// approach ensures that the boundary polygon has the same
|
|
// chirality as the triangles it bounds. If the component
|
|
// triangles do not have the same chirality, it does not
|
|
// matter what the starting vertex is.
|
|
auto epIter = edgePairs.begin();
|
|
EdgeKey<false> boundaryEdge(epIter->first, epIter->second[0]);
|
|
auto edge = mEMap.find(boundaryEdge);
|
|
LogAssert(edge != mEMap.end(), "unexpected condtion");
|
|
auto currentTriangle = edge->second->T[0];
|
|
LogAssert(currentTriangle != nullptr, "unexpected condtion");
|
|
int vStart = invalidVertex, vNext = invalidVertex;
|
|
size_t i0, i1;
|
|
for (i0 = 0; i0 < 3; ++i0)
|
|
{
|
|
if (currentTriangle->V[i0] == boundaryEdge.V[0])
|
|
{
|
|
i1 = (i0 + 1) % 3;
|
|
if (currentTriangle->V[i1] == boundaryEdge.V[1])
|
|
{
|
|
vStart = boundaryEdge.V[0];
|
|
vNext = boundaryEdge.V[1];
|
|
}
|
|
else
|
|
{
|
|
vStart = boundaryEdge.V[1];
|
|
vNext = boundaryEdge.V[0];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
LogAssert(i0 < 3, "unexpected condition");
|
|
|
|
// Find the the edge-pair for vStart that contains vNext and
|
|
// remove it.
|
|
auto lbIter = edgePairs.lower_bound(vStart);
|
|
auto ubIter = edgePairs.upper_bound(vStart);
|
|
bool foundStart = false;
|
|
for (epIter = lbIter; epIter != ubIter; ++epIter)
|
|
{
|
|
if (epIter->second[0] == vNext || epIter->second[1] == vNext)
|
|
{
|
|
edgePairs.erase(epIter);
|
|
foundStart = true;
|
|
break;
|
|
}
|
|
}
|
|
LogAssert(foundStart, "unexpected condition");
|
|
|
|
// Compute the connected component of the boundary edges that
|
|
// contains the edge <vStart, vNext>.
|
|
std::vector<int> component;
|
|
component.push_back(vStart);
|
|
int vPrevious = vStart;
|
|
while (vNext != vStart)
|
|
{
|
|
component.push_back(vNext);
|
|
|
|
bool foundNext = false;
|
|
lbIter = edgePairs.lower_bound(vNext);
|
|
ubIter = edgePairs.upper_bound(vNext);
|
|
for (epIter = lbIter; epIter != ubIter; ++epIter)
|
|
{
|
|
if (vPrevious == epIter->second[0])
|
|
{
|
|
vPrevious = vNext;
|
|
vNext = epIter->second[1];
|
|
edgePairs.erase(epIter);
|
|
foundNext = true;
|
|
break;
|
|
}
|
|
if (vPrevious == epIter->second[1])
|
|
{
|
|
vPrevious = vNext;
|
|
vNext = epIter->second[0];
|
|
edgePairs.erase(epIter);
|
|
foundNext = true;
|
|
break;
|
|
}
|
|
}
|
|
LogAssert(foundNext, "unexpected condition");
|
|
}
|
|
|
|
if (duplicateEndpoints)
|
|
{
|
|
// Explicitly duplicate the starting vertex to
|
|
// emphasize that the component is a closed polyline.
|
|
component.push_back(vStart);
|
|
}
|
|
|
|
components.push_back(component);
|
|
}
|
|
}
|
|
|
|
protected:
|
|
using TrianglePtrIntMap = std::unordered_map<Triangle*, int>;
|
|
using TrianglePtrSizeTMap = std::unordered_map<Triangle*, size_t>;
|
|
|
|
// The edge data and default edge creation.
|
|
static std::unique_ptr<Edge> CreateEdge(int v0, int v1)
|
|
{
|
|
return std::make_unique<Edge>(v0, v1);
|
|
}
|
|
|
|
ECreator mECreator;
|
|
EMap mEMap;
|
|
|
|
// The triangle data and default triangle creation.
|
|
static std::unique_ptr<Triangle> CreateTriangle(int v0, int v1, int v2)
|
|
{
|
|
return std::make_unique<Triangle>(v0, v1, v2);
|
|
}
|
|
|
|
TCreator mTCreator;
|
|
TMap mTMap;
|
|
bool mThrowOnNonmanifoldInsertion; // default: true
|
|
|
|
// Support for computing connected components. This is a
|
|
// straightforward depth-first search of the graph but uses a
|
|
// preallocated stack rather than a recursive function that could
|
|
// possibly overflow the call stack.
|
|
void DepthFirstSearch(
|
|
Triangle* tInitial,
|
|
TrianglePtrIntMap& visited,
|
|
std::vector<Triangle*>& component) const
|
|
{
|
|
// Allocate the maximum-size stack that can occur in the
|
|
// depth-first search. The stack is empty when the index top
|
|
// is -1.
|
|
std::vector<Triangle*> tStack(mTMap.size());
|
|
int top = -1;
|
|
tStack[++top] = tInitial;
|
|
while (top >= 0)
|
|
{
|
|
Triangle* tri = tStack[top];
|
|
visited[tri] = 1;
|
|
int i;
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
Triangle* adj = tri->T[i];
|
|
if (adj && visited[adj] == 0)
|
|
{
|
|
tStack[++top] = adj;
|
|
break;
|
|
}
|
|
}
|
|
if (i == 3)
|
|
{
|
|
visited[tri] = 2;
|
|
component.push_back(tri);
|
|
--top;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|