// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // 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.2019.08.13 #pragma once #include #include #include #include #include #include namespace WwiseGTE { class ETNonmanifoldMesh { public: // Edge data types. class Edge; typedef std::shared_ptr(*ECreator)(int, int); typedef std::map, std::shared_ptr> EMap; // Triangle data types. class Triangle; typedef std::shared_ptr(*TCreator)(int, int, int); typedef std::map, std::shared_ptr> TMap; // Edge object. class Edge { public: virtual ~Edge() = default; Edge(int v0, int v1) : V{ v0, v1 } { } bool operator<(Edge const& other) const { return EdgeKey(V[0], V[1]) < EdgeKey(other.V[0], other.V[1]); } // Vertices of the edge. std::array V; // Triangles sharing the edge. std::set, WeakPtrLT> T; }; // Triangle object. class Triangle { public: virtual ~Triangle() = default; Triangle(int v0, int v1, int v2) : V{ v0, v1, v2 } { } bool operator<(Triangle const& other) const { return TriangleKey(V[0], V[1], V[2]) < TriangleKey(other.V[0], other.V[1], other.V[2]); } // Vertices listed in counterclockwise order (V[0],V[1],V[2]). std::array V; // Adjacent edges. E[i] points to edge (V[i],V[(i+1)%3]). std::array, 3> E; }; // Construction and destruction. virtual ~ETNonmanifoldMesh() = default; ETNonmanifoldMesh(ECreator eCreator = nullptr, TCreator tCreator = nullptr) : mECreator(eCreator ? eCreator : CreateEdge), mTCreator(tCreator ? tCreator : CreateTriangle) { } // 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 to this memory is problematic. // Allowing sharing, say, via std::shared_ptr, is an option but not // really the intent of copying the mesh graph. ETNonmanifoldMesh(ETNonmanifoldMesh const& mesh) { *this = mesh; } ETNonmanifoldMesh& operator=(ETNonmanifoldMesh const& mesh) { Clear(); mECreator = mesh.mECreator; mTCreator = mesh.mTCreator; for (auto const& element : mesh.mTMap) { 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 is not in the mesh, a Triangle object is created and // returned; otherwise, is in the mesh and nullptr is // returned. virtual std::shared_ptr Insert(int v0, int v1, int v2) { TriangleKey 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::shared_ptr tri = mTCreator(v0, v1, v2); // Add the edges to the mesh if they do not already exist. for (int i0 = 2, i1 = 0; i1 < 3; i0 = i1++) { EdgeKey ekey(tri->V[i0], tri->V[i1]); std::shared_ptr edge; auto eiter = mEMap.find(ekey); if (eiter == mEMap.end()) { // This is the first time the edge is encountered. edge = mECreator(tri->V[i0], tri->V[i1]); mEMap[ekey] = edge; } else { // The edge was previously encountered and created. edge = eiter->second; LogAssert(edge != nullptr, "Unexpected condition."); } // Associate the edge with the triangle. tri->E[i0] = edge; // Update the adjacent set of triangles for the edge. edge->T.insert(tri); } mTMap[tkey] = tri; return tri; } // If is in the mesh, it is removed and 'true' is returned; // otherwise, is not in the mesh and 'false' is returned. virtual bool Remove(int v0, int v1, int v2) { TriangleKey tkey(v0, v1, v2); auto titer = mTMap.find(tkey); if (titer == mTMap.end()) { // The triangle does not exist. return false; } // Get the triangle. std::shared_ptr tri = titer->second; // 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].lock(); LogAssert(edge != nullptr, "Unexpected condition."); // Remove the triangle from the edge's set of adjacent // triangles. size_t numRemoved = edge->T.erase(tri); LogAssert(numRemoved > 0, "Unexpected condition."); // Remove the edge if you have the last reference to it. if (edge->T.size() == 0) { EdgeKey ekey(edge->V[0], edge->V[1]); mEMap.erase(ekey); } } // Remove the triangle from the graph. 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 has the property that an edge is shared by at most // two triangles sharing. bool IsManifold() const { for (auto const& element : mEMap) { if (element.second->T.size() > 2) { return false; } } return true; } // 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) { if (element.second->T.size() != 2) { 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>>& components) const { // visited: 0 (unvisited), 1 (discovered), 2 (finished) std::map, int> visited; for (auto const& element : mTMap) { visited.insert(std::make_pair(element.second, 0)); } for (auto& element : mTMap) { auto tri = element.second; if (visited[tri] == 0) { std::vector> component; DepthFirstSearch(tri, visited, component); components.push_back(component); } } } void GetComponents(std::vector>>& components) const { // visited: 0 (unvisited), 1 (discovered), 2 (finished) std::map, int> visited; for (auto const& element : mTMap) { visited.insert(std::make_pair(element.second, 0)); } for (auto& element : mTMap) { std::shared_ptr tri = element.second; if (visited[tri] == 0) { std::vector> component; DepthFirstSearch(tri, visited, component); std::vector> keyComponent; keyComponent.reserve(component.size()); for (auto const& t : component) { keyComponent.push_back(TriangleKey(t->V[0], t->V[1], t->V[2])); } components.push_back(keyComponent); } } } protected: // The edge data and default edge creation. static std::shared_ptr CreateEdge(int v0, int v1) { return std::make_shared(v0, v1); } ECreator mECreator; EMap mEMap; // The triangle data and default triangle creation. static std::shared_ptr CreateTriangle(int v0, int v1, int v2) { return std::make_shared(v0, v1, v2); } TCreator mTCreator; TMap mTMap; // 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(std::shared_ptr const& tInitial, std::map, int>& visited, std::vector>& 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> tStack(mTMap.size()); int top = -1; tStack[++top] = tInitial; while (top >= 0) { std::shared_ptr tri = tStack[top]; visited[tri] = 1; int i; for (i = 0; i < 3; ++i) { auto edge = tri->E[i].lock(); LogAssert(edge != nullptr, "Unexpected condition."); bool foundUnvisited = false; for (auto const& adjw : edge->T) { auto adj = adjw.lock(); LogAssert(adj != nullptr, "Unexpected condition."); if (visited[adj] == 0) { tStack[++top] = adj; foundUnvisited = true; break; } } if (foundUnvisited) { break; } } if (i == 3) { visited[tri] = 2; component.push_back(tri); --top; } } } }; }