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.
381 lines
13 KiB
381 lines
13 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.2019.08.13
|
|
|
|
#pragma once
|
|
|
|
#include <Mathematics/Logger.h>
|
|
#include <Mathematics/WeakPtrCompare.h>
|
|
#include <Mathematics/EdgeKey.h>
|
|
#include <Mathematics/TriangleKey.h>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
namespace gte
|
|
{
|
|
class ETNonmanifoldMesh
|
|
{
|
|
public:
|
|
// Edge data types.
|
|
class Edge;
|
|
typedef std::shared_ptr<Edge>(*ECreator)(int, int);
|
|
typedef std::map<EdgeKey<false>, std::shared_ptr<Edge>> EMap;
|
|
|
|
// Triangle data types.
|
|
class Triangle;
|
|
typedef std::shared_ptr<Triangle>(*TCreator)(int, int, int);
|
|
typedef std::map<TriangleKey<true>, std::shared_ptr<Triangle>> 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<false>(V[0], V[1]) < EdgeKey<false>(other.V[0], other.V[1]);
|
|
}
|
|
|
|
// Vertices of the edge.
|
|
std::array<int, 2> V;
|
|
|
|
// Triangles sharing the edge.
|
|
std::set<std::weak_ptr<Triangle>, WeakPtrLT<Triangle>> 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<true>(V[0], V[1], V[2]) < TriangleKey<true>(other.V[0], other.V[1], other.V[2]);
|
|
}
|
|
|
|
// 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<std::weak_ptr<Edge>, 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 <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.
|
|
virtual std::shared_ptr<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::shared_ptr<Triangle> 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<false> ekey(tri->V[i0], tri->V[i1]);
|
|
std::shared_ptr<Edge> 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 <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.
|
|
std::shared_ptr<Triangle> 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<false> 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<std::vector<std::shared_ptr<Triangle>>>& components) const
|
|
{
|
|
// visited: 0 (unvisited), 1 (discovered), 2 (finished)
|
|
std::map<std::shared_ptr<Triangle>, 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<std::shared_ptr<Triangle>> component;
|
|
DepthFirstSearch(tri, visited, component);
|
|
components.push_back(component);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetComponents(std::vector<std::vector<TriangleKey<true>>>& components) const
|
|
{
|
|
// visited: 0 (unvisited), 1 (discovered), 2 (finished)
|
|
std::map<std::shared_ptr<Triangle>, int> visited;
|
|
for (auto const& element : mTMap)
|
|
{
|
|
visited.insert(std::make_pair(element.second, 0));
|
|
}
|
|
|
|
for (auto& element : mTMap)
|
|
{
|
|
std::shared_ptr<Triangle> tri = element.second;
|
|
if (visited[tri] == 0)
|
|
{
|
|
std::vector<std::shared_ptr<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(keyComponent);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
// The edge data and default edge creation.
|
|
static std::shared_ptr<Edge> CreateEdge(int v0, int v1)
|
|
{
|
|
return std::make_shared<Edge>(v0, v1);
|
|
}
|
|
|
|
ECreator mECreator;
|
|
EMap mEMap;
|
|
|
|
// The triangle data and default triangle creation.
|
|
static std::shared_ptr<Triangle> CreateTriangle(int v0, int v1, int v2)
|
|
{
|
|
return std::make_shared<Triangle>(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<Triangle> const& tInitial,
|
|
std::map<std::shared_ptr<Triangle>, int>& visited,
|
|
std::vector<std::shared_ptr<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<std::shared_ptr<Triangle>> tStack(mTMap.size());
|
|
int top = -1;
|
|
tStack[++top] = tInitial;
|
|
while (top >= 0)
|
|
{
|
|
std::shared_ptr<Triangle> 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;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|