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

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