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