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.
1105 lines
43 KiB
1105 lines
43 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/PolygonTree.h>
|
|
#include <Mathematics/ConstrainedDelaunay2.h>
|
|
#include <numeric>
|
|
|
|
// The fundamental problem is to compute the triangulation of a polygon tree.
|
|
// The outer polygons have counterclockwise ordered vertices. The inner
|
|
// polygons have clockwise ordered vertices. The algorithm uses Constrained
|
|
// Delaunay Triangulation and the implementation allows polygons to share
|
|
// vertices and edges.
|
|
//
|
|
// The polygons are not required to be simple in the sense that a vertex can
|
|
// be shared by an even number of edges, where the number is larger than 2.
|
|
// The input points can have duplicates, which the triangulator handles
|
|
// correctly. The algorithm supports coincident vertex-edge and coincident
|
|
// edge-edge configurations. See the document
|
|
// https://www.geometrictools.com/Documentation/TriangulationByCDT.pdf
|
|
// for examples.
|
|
//
|
|
// If two edges intersect at edge-interior points, the current implementation
|
|
// cannot handle this. A pair of such edges cannot simultaneously be inserted
|
|
// into the constrained triangulation without affecting each other's local
|
|
// re-triangulation. A pending work item is to add validation code and fix
|
|
// an incoming malformed polygon tree during the triangulation.
|
|
//
|
|
// The input points are a vertex pool. The input tree is a PolygonTree object,
|
|
// defined in PolygonTree.h. Any outer polygon has vertices points[outer[0]]
|
|
// through points[outer[outer.size()-1]] listed in counterclockwise order. Any
|
|
// inner polygon has vertices points[inner[0]] through
|
|
// points[inner[inner.size()-1]] listed in clockwise order. The output tree
|
|
// contains the triangulation of the polygon tree on a per-node basis. If
|
|
// coincident vertex-edge or coincident edge-edge configurations exist in
|
|
// the polygon tree, the corresponding output polygons differ from the input
|
|
// polygons in that they have more vertices due to edge splits. The triangle
|
|
// chirality (winding order) is the same as the containing polygon.
|
|
//
|
|
// TriangulateCDT uses the ComputeType only for the ConstrainedDelaunay2
|
|
// object. To avoid the heap management costs of BSNumber<UIntegerAP32> when
|
|
// the input datasets are large, use BSNumber<UIntegerFP32<N>>. The worst-case
|
|
// choices of N for ComputeType are listed in the next table.
|
|
//
|
|
// input type | compute type | N
|
|
// -----------+--------------+------
|
|
// float | BSNumber | 70
|
|
// double | BSNumber | 525
|
|
// float | BSRational | 573
|
|
// double | BSRational | 4329
|
|
|
|
namespace gte
|
|
{
|
|
// The variadic template declaration supports the class
|
|
// TriangulateCDT<InputType, ComputeType>, which is deprecated and will
|
|
// be removed in a future release. The declaration also supports the
|
|
// replacement class TriangulateCDT<InputType>.
|
|
template <typename T, typename...>
|
|
class TriangulateCDT {};
|
|
}
|
|
|
|
namespace gte
|
|
{
|
|
// The code has LogAssert statements that throw exceptions when triggered.
|
|
// For a correct algorithm using exact arithmetic, these should not occur.
|
|
// When using floating-point arithmetic, it is possible that rounding errors
|
|
// lead to a malformed triangulation. It is strongly recommended that you
|
|
// choose ComputeType to be a rational type. No divisions are required,
|
|
// either in Delaunay2 or ConstrainedDelaunay2, so you may choose the type
|
|
// to be BSNumber<UInteger> rather than BSRational<UInteger>. However, if
|
|
// you choose ComputeType to be 'float' or 'double', you should call the
|
|
// Insert(...) function in a try-catch block and take appropriate action.
|
|
//
|
|
// The worst-case choices of N for ComputeType of type BSNumber or BSRational
|
|
// with integer storage UIntegerFP32<N> are listed in the next table. The
|
|
// expression requiring the most bits is in the last else-block of ComputePSD.
|
|
// We recommend using only BSNumber, because no divisions are performed in the
|
|
// insertion computations.
|
|
//
|
|
// input type | compute type | ComputePSD | Delaunay | N
|
|
// -----------+--------------+------------+----------+------
|
|
// float | BSNumber | 70 | 35 | 70
|
|
// double | BSNumber | 525 | 263 | 525
|
|
// float | BSRational | 555 | 573 | 573
|
|
// double | BSRational | 4197 | 4329 | 4329
|
|
//
|
|
// The recommended ComputeType is BSNumber<UIntegerFP32<70>> for the
|
|
// InputType 'float' and BSNumber<UIntegerFP32<526>> for the InputType
|
|
// 'double' (525 rounded up to 526 for the bits array to be a multiple
|
|
// of 8 bytes.)
|
|
|
|
template <typename InputType, typename ComputeType>
|
|
class // [[deprecated("Use TriangulateCDT<InputType> instead.")]]
|
|
TriangulateCDT<InputType, ComputeType>
|
|
{
|
|
public:
|
|
void operator()(
|
|
std::vector<Vector2<InputType>> const& inputPoints,
|
|
std::shared_ptr<PolygonTree> const& inputTree,
|
|
PolygonTreeEx& outputTree)
|
|
{
|
|
operator()(static_cast<int>(inputPoints.size()), inputPoints.data(),
|
|
inputTree, outputTree);
|
|
}
|
|
|
|
void operator()(int numInputPoints, Vector2<InputType> const* inputPoints,
|
|
std::shared_ptr<PolygonTree> const& inputTree,
|
|
PolygonTreeEx& outputTree)
|
|
{
|
|
LogAssert(numInputPoints >= 3 && inputPoints != nullptr && inputTree,
|
|
"Invalid argument.");
|
|
|
|
CopyAndCompactify(inputTree, outputTree);
|
|
Triangulate(numInputPoints, inputPoints, outputTree);
|
|
}
|
|
|
|
private:
|
|
void CopyAndCompactify(std::shared_ptr<PolygonTree> const& input,
|
|
PolygonTreeEx& output)
|
|
{
|
|
output.nodes.clear();
|
|
output.interiorTriangles.clear();
|
|
output.interiorNodeIndices.clear();
|
|
output.exteriorTriangles.clear();
|
|
output.exteriorNodeIndices.clear();
|
|
output.insideTriangles.clear();
|
|
output.insideNodeIndices.clear();
|
|
output.outsideTriangles.clear();
|
|
output.allTriangles.clear();
|
|
|
|
// Count the number of nodes in the tree.
|
|
size_t numNodes = 1; // the root node
|
|
std::queue<std::shared_ptr<PolygonTree>> queue;
|
|
queue.push(input);
|
|
while (queue.size() > 0)
|
|
{
|
|
std::shared_ptr<PolygonTree> node = queue.front();
|
|
queue.pop();
|
|
numNodes += node->child.size();
|
|
for (auto const& child : node->child)
|
|
{
|
|
queue.push(child);
|
|
}
|
|
}
|
|
|
|
// Create the PolygonTreeEx nodes.
|
|
output.nodes.resize(numNodes);
|
|
for (size_t i = 0; i < numNodes; ++i)
|
|
{
|
|
output.nodes[i].self = i;
|
|
}
|
|
output.nodes[0].chirality = +1;
|
|
output.nodes[0].parent = std::numeric_limits<size_t>::max();
|
|
|
|
size_t current = 0, last = 0, minChild = 1;
|
|
queue.push(input);
|
|
while (queue.size() > 0)
|
|
{
|
|
std::shared_ptr<PolygonTree> node = queue.front();
|
|
queue.pop();
|
|
auto& exnode = output.nodes[current++];
|
|
exnode.polygon = node->polygon;
|
|
exnode.minChild = minChild;
|
|
exnode.supChild = minChild + node->child.size();
|
|
minChild = exnode.supChild;
|
|
for (auto const& child : node->child)
|
|
{
|
|
auto& exchild = output.nodes[++last];
|
|
exchild.chirality = -exnode.chirality;
|
|
exchild.parent = exnode.self;
|
|
queue.push(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Triangulate(int numInputPoints, Vector2<InputType> const* inputPoints,
|
|
PolygonTreeEx& tree)
|
|
{
|
|
// The constrained Delaunay triangulator will be given the unique
|
|
// points referenced by the polygons in the tree. The tree
|
|
// 'polygon' indices are relative to inputPoints[], but they are
|
|
// temporarily mapped to indices relative to 'points'. Once the
|
|
// triangulation is complete, the indices are restored to those
|
|
// relative to inputPoints[].
|
|
std::vector<Vector2<InputType>> points;
|
|
std::vector<int> remapping;
|
|
RemapPolygonTree(numInputPoints, inputPoints, tree, points, remapping);
|
|
LogAssert(points.size() >= 3, "Invalid polygon tree.");
|
|
|
|
ETManifoldMesh graph;
|
|
std::set<EdgeKey<false>> edges;
|
|
ConstrainedTriangulate(tree, points, graph, edges);
|
|
ClassifyTriangles(tree, graph, edges);
|
|
|
|
RestorePolygonTree(tree, remapping);
|
|
}
|
|
|
|
// On return, 'points' are the unique inputPoints[] values referenced by
|
|
// the tree. The tree 'polygon' members are modified to be indices
|
|
// into 'points' rather than inputPoints[]. The 'remapping' allows us to
|
|
// restore the tree 'polygon' members to be indices into inputPoints[]
|
|
// after the triangulation is computed. The 'edges' stores all the
|
|
// polygon edges that must be in the triangulation.
|
|
void RemapPolygonTree(
|
|
int numInputPoints,
|
|
Vector2<InputType> const* inputPoints,
|
|
PolygonTreeEx& tree,
|
|
std::vector<Vector2<InputType>>& points,
|
|
std::vector<int>& remapping)
|
|
{
|
|
std::map<Vector2<InputType>, int> pointMap;
|
|
points.reserve(numInputPoints);
|
|
int currentIndex = 0;
|
|
|
|
// The remapping is initially the identity, remapping[j] = j.
|
|
remapping.resize(numInputPoints);
|
|
std::iota(remapping.begin(), remapping.end(), 0);
|
|
|
|
std::queue<size_t> queue;
|
|
queue.push(0);
|
|
while (queue.size() > 0)
|
|
{
|
|
PolygonTreeEx::Node& node = tree.nodes[queue.front()];
|
|
queue.pop();
|
|
size_t const numIndices = node.polygon.size();
|
|
for (size_t i = 0; i < numIndices; ++i)
|
|
{
|
|
auto const& point = inputPoints[node.polygon[i]];
|
|
auto iter = pointMap.find(point);
|
|
if (iter == pointMap.end())
|
|
{
|
|
// The point is encountered the first time.
|
|
pointMap.insert(std::make_pair(point, currentIndex));
|
|
remapping[currentIndex] = node.polygon[i];
|
|
node.polygon[i] = currentIndex;
|
|
points.push_back(point);
|
|
++currentIndex;
|
|
}
|
|
else
|
|
{
|
|
// The point is a duplicate. On the remapping, the
|
|
// polygon[] value is set to the index for the
|
|
// first occurrence of the duplicate.
|
|
remapping[iter->second] = node.polygon[i];
|
|
node.polygon[i] = iter->second;
|
|
}
|
|
|
|
}
|
|
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
queue.push(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RestorePolygonTree(PolygonTreeEx& tree, std::vector<int> const& remapping)
|
|
{
|
|
std::queue<size_t> queue;
|
|
queue.push(0);
|
|
while (queue.size() > 0)
|
|
{
|
|
auto& node = tree.nodes[queue.front()];
|
|
queue.pop();
|
|
|
|
for (auto& index : node.polygon)
|
|
{
|
|
index = remapping[index];
|
|
}
|
|
for (auto& tri : node.triangulation)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
queue.push(c);
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.interiorTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.exteriorTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.insideTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.outsideTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.allTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConstrainedTriangulate(
|
|
PolygonTreeEx& tree,
|
|
std::vector<Vector2<InputType>> const& points,
|
|
ETManifoldMesh& graph,
|
|
std::set<EdgeKey<false>>& edges)
|
|
{
|
|
// Use constrained Delaunay triangulation.
|
|
ConstrainedDelaunay2<InputType, ComputeType> cdt;
|
|
int const numPoints = static_cast<int>(points.size());
|
|
cdt(numPoints, points.data(), static_cast<InputType>(0));
|
|
std::vector<int> outEdge;
|
|
|
|
std::queue<size_t> queue;
|
|
queue.push(0);
|
|
while (queue.size() > 0)
|
|
{
|
|
auto& node = tree.nodes[queue.front()];
|
|
queue.pop();
|
|
|
|
std::vector<int> replacement;
|
|
size_t numIndices = node.polygon.size();
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
// Insert the polygon edge into the constrained Delaunay
|
|
// triangulation.
|
|
std::array<int, 2> edge = { node.polygon[i0], node.polygon[i1] };
|
|
outEdge.clear();
|
|
(void)cdt.Insert(edge, outEdge);
|
|
if (outEdge.size() > 2)
|
|
{
|
|
// The polygon edge intersects additional vertices in
|
|
// the triangulation. The outEdge[] edge values are
|
|
// { edge[0], other_vertices, edge[1] } which are
|
|
// ordered along the line segment.
|
|
replacement.insert(replacement.end(), outEdge.begin() + 1, outEdge.end());
|
|
}
|
|
else
|
|
{
|
|
replacement.push_back(node.polygon[i1]);
|
|
}
|
|
}
|
|
if (replacement.size() > node.polygon.size())
|
|
{
|
|
node.polygon = std::move(replacement);
|
|
}
|
|
|
|
numIndices = node.polygon.size();
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
edges.insert(EdgeKey<false>(node.polygon[i0], node.polygon[i1]));
|
|
}
|
|
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
queue.push(c);
|
|
}
|
|
}
|
|
|
|
// Copy the graph to the compact arrays mIndices and
|
|
// mAdjacencies for use by the caller.
|
|
cdt.UpdateIndicesAdjacencies();
|
|
|
|
// Duplicate the graph, which will be modified during triangle
|
|
// extration. The original is kept intact for use by the caller.
|
|
graph = cdt.GetGraph();
|
|
|
|
// Store the triangles in allTriangles for potential use by the
|
|
// caller.
|
|
int const numTriangles = cdt.GetNumTriangles();
|
|
int const* indices = cdt.GetIndices().data();
|
|
tree.allTriangles.resize(numTriangles);
|
|
for (int t = 0; t < numTriangles; ++t)
|
|
{
|
|
int v0 = *indices++;
|
|
int v1 = *indices++;
|
|
int v2 = *indices++;
|
|
graph.Insert(v0, v1, v2);
|
|
tree.allTriangles[t] = { v0, v1, v2 };
|
|
}
|
|
}
|
|
|
|
void ClassifyTriangles(PolygonTreeEx& tree, ETManifoldMesh& graph,
|
|
std::set<EdgeKey<false>>& edges)
|
|
{
|
|
ClassifyDFS(tree, 0, graph, edges);
|
|
LogAssert(edges.size() == 0, "The edges should be empty for a correct implementation.");
|
|
GetOutsideTriangles(tree, graph);
|
|
GetInsideTriangles(tree);
|
|
}
|
|
|
|
void ClassifyDFS(PolygonTreeEx& tree, size_t index, ETManifoldMesh& graph,
|
|
std::set<EdgeKey<false>>& edges)
|
|
{
|
|
auto& node = tree.nodes[index];
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
ClassifyDFS(tree, c, graph, edges);
|
|
}
|
|
|
|
auto const& emap = graph.GetEdges();
|
|
std::set<TriangleKey<true>> region;
|
|
size_t const numIndices = node.polygon.size();
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
int v0 = node.polygon[i0];
|
|
int v1 = node.polygon[i1];
|
|
EdgeKey<false> ekey(v0, v1);
|
|
auto eiter = emap.find(ekey);
|
|
LogAssert(eiter != emap.end(), "Unexpected condition.");
|
|
auto const& edge = eiter->second;
|
|
LogAssert(edge, "Unexpected condition.");
|
|
auto tri0 = edge->T[0];
|
|
LogAssert(tri0, "Unexpected condition.");
|
|
if (tri0->WhichSideOfEdge(v0, v1) == node.chirality)
|
|
{
|
|
region.insert(TriangleKey<true>(tri0->V[0], tri0->V[1], tri0->V[2]));
|
|
}
|
|
else
|
|
{
|
|
auto tri1 = edge->T[1];
|
|
if (tri1)
|
|
{
|
|
region.insert(TriangleKey<true>(tri1->V[0], tri1->V[1], tri1->V[2]));
|
|
}
|
|
}
|
|
}
|
|
|
|
FillRegion(graph, edges, region);
|
|
ExtractTriangles(graph, region, node);
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
edges.erase(EdgeKey<false>(node.polygon[i0], node.polygon[i1]));
|
|
}
|
|
}
|
|
|
|
// On input, the set has the initial seeds for the desired region. A
|
|
// breadth-first search is performed to find the connected component
|
|
// of the seeds. The component is bounded by an outer polygon and the
|
|
// inner polygons of its children.
|
|
void FillRegion(ETManifoldMesh& graph, std::set<EdgeKey<false>> const& edges,
|
|
std::set<TriangleKey<true>>& region)
|
|
{
|
|
std::queue<TriangleKey<true>> regionQueue;
|
|
for (auto const& tkey : region)
|
|
{
|
|
regionQueue.push(tkey);
|
|
}
|
|
|
|
auto const& tmap = graph.GetTriangles();
|
|
while (regionQueue.size() > 0)
|
|
{
|
|
auto const& tkey = regionQueue.front();
|
|
regionQueue.pop();
|
|
auto titer = tmap.find(tkey);
|
|
LogAssert(titer != tmap.end(), "Unexpected condition.");
|
|
auto const& tri = titer->second;
|
|
LogAssert(tri, "Unexpected condition.");
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
auto edge = tri->E[j];
|
|
if (edge)
|
|
{
|
|
auto siter = edges.find(EdgeKey<false>(edge->V[0], edge->V[1]));
|
|
if (siter == edges.end())
|
|
{
|
|
// The edge is not constrained, so it allows the
|
|
// search to continue.
|
|
auto adj = tri->T[j];
|
|
if (adj)
|
|
{
|
|
TriangleKey<true> akey(adj->V[0], adj->V[1], adj->V[2]);
|
|
auto riter = region.find(akey);
|
|
if (riter == region.end())
|
|
{
|
|
// The adjacent triangle has not yet been
|
|
// visited, so place it in the queue to
|
|
// continue the search.
|
|
region.insert(akey);
|
|
regionQueue.push(akey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store the region triangles in a triangulation object and remove
|
|
// those triangles from the graph in preparation for processing the
|
|
// next layer of triangles.
|
|
void ExtractTriangles(ETManifoldMesh& graph,
|
|
std::set<TriangleKey<true>> const& region,
|
|
PolygonTreeEx::Node& node)
|
|
{
|
|
node.triangulation.reserve(region.size());
|
|
if (node.chirality > 0)
|
|
{
|
|
for (auto const& tri : region)
|
|
{
|
|
node.triangulation.push_back({ tri.V[0], tri.V[1], tri.V[2] });
|
|
graph.Remove(tri.V[0], tri.V[1], tri.V[2]);
|
|
}
|
|
}
|
|
else // node.chirality < 0
|
|
{
|
|
for (auto const& tri : region)
|
|
{
|
|
node.triangulation.push_back({ tri.V[0], tri.V[2], tri.V[1] });
|
|
graph.Remove(tri.V[0], tri.V[1], tri.V[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetOutsideTriangles(PolygonTreeEx& tree, ETManifoldMesh& graph)
|
|
{
|
|
auto const& tmap = graph.GetTriangles();
|
|
tree.outsideTriangles.resize(tmap.size());
|
|
size_t t = 0;
|
|
for (auto const& tri : tmap)
|
|
{
|
|
auto const& tkey = tri.first;
|
|
tree.outsideTriangles[t++] = { tkey.V[0], tkey.V[1], tkey.V[2] };
|
|
}
|
|
graph.Clear();
|
|
}
|
|
|
|
// Get the triangles in the polygon tree, classifying each as
|
|
// interior (in region bounded by outer polygon and its contained
|
|
// inner polygons) or exterior (in region bounded by inner polygon
|
|
// and its contained outer polygons). The inside triangles are the
|
|
// union of the interior and exterior triangles.
|
|
void GetInsideTriangles(PolygonTreeEx& tree)
|
|
{
|
|
size_t const numTriangles = tree.allTriangles.size();
|
|
size_t const numOutside = tree.outsideTriangles.size();
|
|
size_t const numInside = numTriangles - numOutside;
|
|
tree.interiorTriangles.reserve(numTriangles);
|
|
tree.interiorNodeIndices.reserve(numTriangles);
|
|
tree.exteriorTriangles.reserve(numTriangles);
|
|
tree.exteriorNodeIndices.reserve(numTriangles);
|
|
tree.insideTriangles.reserve(numInside);
|
|
tree.insideNodeIndices.reserve(numInside);
|
|
|
|
for (size_t nIndex = 0; nIndex < tree.nodes.size(); ++nIndex)
|
|
{
|
|
auto const& node = tree.nodes[nIndex];
|
|
for (auto& tri : node.triangulation)
|
|
{
|
|
if (node.chirality > 0)
|
|
{
|
|
tree.interiorTriangles.push_back(tri);
|
|
tree.interiorNodeIndices.push_back(nIndex);
|
|
}
|
|
else
|
|
{
|
|
tree.exteriorTriangles.push_back(tri);
|
|
tree.exteriorNodeIndices.push_back(nIndex);
|
|
}
|
|
|
|
tree.insideTriangles.push_back(tri);
|
|
tree.insideNodeIndices.push_back(nIndex);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace gte
|
|
{
|
|
// The input type must be 'float' or 'double'. The user no longer has
|
|
// the responsibility to specify the compute type.
|
|
|
|
template <typename T>
|
|
class TriangulateCDT<T>
|
|
{
|
|
public:
|
|
void operator()(
|
|
std::vector<Vector2<T>> const& inputPoints,
|
|
std::shared_ptr<PolygonTree> const& inputTree,
|
|
PolygonTreeEx& outputTree)
|
|
{
|
|
LogAssert(inputPoints.size() >= 3 && inputTree, "Invalid argument.");
|
|
|
|
CopyAndCompactify(inputTree, outputTree);
|
|
Triangulate(inputPoints.size(), inputPoints.data(), outputTree);
|
|
}
|
|
|
|
void operator()(
|
|
size_t numInputPoints,
|
|
Vector2<T> const* inputPoints,
|
|
std::shared_ptr<PolygonTree> const& inputTree,
|
|
PolygonTreeEx& outputTree)
|
|
{
|
|
LogAssert(numInputPoints >= 3 && inputPoints != nullptr && inputTree, "Invalid argument.");
|
|
|
|
CopyAndCompactify(inputTree, outputTree);
|
|
Triangulate(numInputPoints, inputPoints, outputTree);
|
|
}
|
|
|
|
private:
|
|
void CopyAndCompactify(std::shared_ptr<PolygonTree> const& input,
|
|
PolygonTreeEx& output)
|
|
{
|
|
output.nodes.clear();
|
|
output.interiorTriangles.clear();
|
|
output.interiorNodeIndices.clear();
|
|
output.exteriorTriangles.clear();
|
|
output.exteriorNodeIndices.clear();
|
|
output.insideTriangles.clear();
|
|
output.insideNodeIndices.clear();
|
|
output.outsideTriangles.clear();
|
|
output.allTriangles.clear();
|
|
|
|
// Count the number of nodes in the tree.
|
|
size_t numNodes = 1; // the root node
|
|
std::queue<std::shared_ptr<PolygonTree>> queue;
|
|
queue.push(input);
|
|
while (queue.size() > 0)
|
|
{
|
|
auto const& node = queue.front();
|
|
queue.pop();
|
|
numNodes += node->child.size();
|
|
for (auto const& child : node->child)
|
|
{
|
|
queue.push(child);
|
|
}
|
|
}
|
|
|
|
// Create the PolygonTreeEx nodes.
|
|
output.nodes.resize(numNodes);
|
|
for (size_t i = 0; i < numNodes; ++i)
|
|
{
|
|
output.nodes[i].self = i;
|
|
}
|
|
output.nodes[0].chirality = +1;
|
|
output.nodes[0].parent = std::numeric_limits<size_t>::max();
|
|
|
|
size_t current = 0, last = 0, minChild = 1;
|
|
queue.push(input);
|
|
while (queue.size() > 0)
|
|
{
|
|
auto const& node = queue.front();
|
|
queue.pop();
|
|
auto& exnode = output.nodes[current++];
|
|
exnode.polygon = node->polygon;
|
|
exnode.minChild = minChild;
|
|
exnode.supChild = minChild + node->child.size();
|
|
minChild = exnode.supChild;
|
|
for (auto const& child : node->child)
|
|
{
|
|
auto& exchild = output.nodes[++last];
|
|
exchild.chirality = -exnode.chirality;
|
|
exchild.parent = exnode.self;
|
|
queue.push(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Triangulate(size_t numInputPoints, Vector2<T> const* inputPoints,
|
|
PolygonTreeEx& tree)
|
|
{
|
|
// The constrained Delaunay triangulator will be given the unique
|
|
// points referenced by the polygons in the tree. The tree
|
|
// 'polygon' indices are relative to inputPoints[], but they are
|
|
// temporarily mapped to indices relative to 'points'. Once the
|
|
// triangulation is complete, the indices are restored to those
|
|
// relative to inputPoints[].
|
|
std::vector<Vector2<T>> points;
|
|
std::vector<int32_t> remapping;
|
|
RemapPolygonTree(numInputPoints, inputPoints, tree, points, remapping);
|
|
LogAssert(points.size() >= 3, "Invalid polygon tree.");
|
|
|
|
ETManifoldMesh graph;
|
|
std::set<EdgeKey<false>> edges;
|
|
ConstrainedTriangulate(tree, points, graph, edges);
|
|
ClassifyTriangles(tree, graph, edges);
|
|
|
|
RestorePolygonTree(tree, remapping);
|
|
}
|
|
|
|
// On return, 'points' are the unique inputPoints[] values referenced by
|
|
// the tree. The tree 'polygon' members are modified to be indices
|
|
// into 'points' rather than inputPoints[]. The 'remapping' allows us to
|
|
// restore the tree 'polygon' members to be indices into inputPoints[]
|
|
// after the triangulation is computed. The 'edges' stores all the
|
|
// polygon edges that must be in the triangulation.
|
|
void RemapPolygonTree(
|
|
size_t numInputPoints,
|
|
Vector2<T> const* inputPoints,
|
|
PolygonTreeEx& tree,
|
|
std::vector<Vector2<T>>& points,
|
|
std::vector<int32_t>& remapping)
|
|
{
|
|
std::map<Vector2<T>, int32_t> pointMap;
|
|
points.reserve(numInputPoints);
|
|
int32_t currentIndex = 0;
|
|
|
|
// The remapping is initially the identity, remapping[j] = j.
|
|
remapping.resize(numInputPoints);
|
|
std::iota(remapping.begin(), remapping.end(), 0);
|
|
|
|
std::queue<size_t> queue;
|
|
queue.push(0);
|
|
while (queue.size() > 0)
|
|
{
|
|
PolygonTreeEx::Node& node = tree.nodes[queue.front()];
|
|
queue.pop();
|
|
size_t const numIndices = node.polygon.size();
|
|
for (size_t i = 0; i < numIndices; ++i)
|
|
{
|
|
auto const& point = inputPoints[node.polygon[i]];
|
|
auto iter = pointMap.find(point);
|
|
if (iter == pointMap.end())
|
|
{
|
|
// The point is encountered the first time.
|
|
pointMap.insert(std::make_pair(point, currentIndex));
|
|
remapping[currentIndex] = node.polygon[i];
|
|
node.polygon[i] = currentIndex;
|
|
points.push_back(point);
|
|
++currentIndex;
|
|
}
|
|
else
|
|
{
|
|
// The point is a duplicate. On the remapping, the
|
|
// polygon[] value is set to the index for the
|
|
// first occurrence of the duplicate.
|
|
remapping[iter->second] = node.polygon[i];
|
|
node.polygon[i] = iter->second;
|
|
}
|
|
|
|
}
|
|
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
queue.push(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RestorePolygonTree(PolygonTreeEx& tree, std::vector<int32_t> const& remapping)
|
|
{
|
|
std::queue<size_t> queue;
|
|
queue.push(0);
|
|
while (queue.size() > 0)
|
|
{
|
|
auto& node = tree.nodes[queue.front()];
|
|
queue.pop();
|
|
|
|
for (auto& index : node.polygon)
|
|
{
|
|
index = remapping[index];
|
|
}
|
|
for (auto& tri : node.triangulation)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
queue.push(c);
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.interiorTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.exteriorTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.insideTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.outsideTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
|
|
for (auto& tri : tree.allTriangles)
|
|
{
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
tri[j] = remapping[tri[j]];
|
|
}
|
|
}
|
|
}
|
|
|
|
void ConstrainedTriangulate(
|
|
PolygonTreeEx& tree,
|
|
std::vector<Vector2<T>> const& points,
|
|
ETManifoldMesh& graph,
|
|
std::set<EdgeKey<false>>& edges)
|
|
{
|
|
// Use constrained Delaunay triangulation.
|
|
ConstrainedDelaunay2<T> cdt;
|
|
cdt(points);
|
|
|
|
size_t counter = 0;
|
|
|
|
std::vector<int32_t> outEdge;
|
|
std::queue<size_t> queue;
|
|
queue.push(0);
|
|
while (queue.size() > 0)
|
|
{
|
|
auto& node = tree.nodes[queue.front()];
|
|
queue.pop();
|
|
|
|
std::vector<int32_t> replacement;
|
|
size_t numIndices = node.polygon.size();
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
// Insert the polygon edge into the constrained Delaunay
|
|
// triangulation.
|
|
std::array<int32_t, 2> edge = { node.polygon[i0], node.polygon[i1] };
|
|
outEdge.clear();
|
|
(void)cdt.Insert(edge, outEdge);
|
|
++counter;
|
|
if (outEdge.size() > 2)
|
|
{
|
|
// The polygon edge intersects additional vertices in
|
|
// the triangulation. The outEdge[] edge values are
|
|
// { edge[0], other_vertices, edge[1] } which are
|
|
// ordered along the line segment.
|
|
replacement.insert(replacement.end(), outEdge.begin() + 1, outEdge.end());
|
|
}
|
|
else
|
|
{
|
|
replacement.push_back(node.polygon[i1]);
|
|
}
|
|
}
|
|
if (replacement.size() > node.polygon.size())
|
|
{
|
|
node.polygon = std::move(replacement);
|
|
}
|
|
|
|
numIndices = node.polygon.size();
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
edges.insert(EdgeKey<false>(node.polygon[i0], node.polygon[i1]));
|
|
}
|
|
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
queue.push(c);
|
|
}
|
|
}
|
|
|
|
// Copy the graph to the compact arrays mIndices and
|
|
// mAdjacencies for use by the caller.
|
|
cdt.UpdateIndicesAdjacencies();
|
|
|
|
// Duplicate the graph, which will be modified during triangle
|
|
// extration. The original is kept intact for use by the caller.
|
|
graph = cdt.GetGraph();
|
|
|
|
// Store the triangles in allTriangles for potential use by the
|
|
// caller.
|
|
size_t const numTriangles = cdt.GetNumTriangles();
|
|
int32_t const* indices = cdt.GetIndices().data();
|
|
tree.allTriangles.resize(numTriangles);
|
|
for (size_t t = 0; t < numTriangles; ++t)
|
|
{
|
|
int32_t v0 = *indices++;
|
|
int32_t v1 = *indices++;
|
|
int32_t v2 = *indices++;
|
|
graph.Insert(v0, v1, v2);
|
|
tree.allTriangles[t] = { v0, v1, v2 };
|
|
}
|
|
}
|
|
|
|
void ClassifyTriangles(PolygonTreeEx& tree, ETManifoldMesh& graph,
|
|
std::set<EdgeKey<false>>& edges)
|
|
{
|
|
ClassifyDFS(tree, 0, graph, edges);
|
|
LogAssert(edges.size() == 0, "The edges should be empty for a correct implementation.");
|
|
GetOutsideTriangles(tree, graph);
|
|
GetInsideTriangles(tree);
|
|
}
|
|
|
|
void ClassifyDFS(PolygonTreeEx& tree, size_t index, ETManifoldMesh& graph,
|
|
std::set<EdgeKey<false>>& edges)
|
|
{
|
|
auto& node = tree.nodes[index];
|
|
for (size_t c = node.minChild; c < node.supChild; ++c)
|
|
{
|
|
ClassifyDFS(tree, c, graph, edges);
|
|
}
|
|
|
|
auto const& emap = graph.GetEdges();
|
|
std::set<TriangleKey<true>> region;
|
|
size_t const numIndices = node.polygon.size();
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
int32_t v0 = node.polygon[i0];
|
|
int32_t v1 = node.polygon[i1];
|
|
EdgeKey<false> ekey(v0, v1);
|
|
auto eiter = emap.find(ekey);
|
|
LogAssert(eiter != emap.end(), "Unexpected condition.");
|
|
auto const& edge = eiter->second;
|
|
LogAssert(edge, "Unexpected condition.");
|
|
auto tri0 = edge->T[0];
|
|
LogAssert(tri0, "Unexpected condition.");
|
|
if (tri0->WhichSideOfEdge(v0, v1) == node.chirality)
|
|
{
|
|
region.insert(TriangleKey<true>(tri0->V[0], tri0->V[1], tri0->V[2]));
|
|
}
|
|
else
|
|
{
|
|
auto tri1 = edge->T[1];
|
|
if (tri1)
|
|
{
|
|
region.insert(TriangleKey<true>(tri1->V[0], tri1->V[1], tri1->V[2]));
|
|
}
|
|
}
|
|
}
|
|
|
|
FillRegion(graph, edges, region);
|
|
ExtractTriangles(graph, region, node);
|
|
for (size_t i0 = numIndices - 1, i1 = 0; i1 < numIndices; i0 = i1++)
|
|
{
|
|
edges.erase(EdgeKey<false>(node.polygon[i0], node.polygon[i1]));
|
|
}
|
|
}
|
|
|
|
// On input, the set has the initial seeds for the desired region. A
|
|
// breadth-first search is performed to find the connected component
|
|
// of the seeds. The component is bounded by an outer polygon and the
|
|
// inner polygons of its children.
|
|
void FillRegion(ETManifoldMesh& graph, std::set<EdgeKey<false>> const& edges,
|
|
std::set<TriangleKey<true>>& region)
|
|
{
|
|
std::queue<TriangleKey<true>> regionQueue;
|
|
for (auto const& tkey : region)
|
|
{
|
|
regionQueue.push(tkey);
|
|
}
|
|
|
|
auto const& tmap = graph.GetTriangles();
|
|
while (regionQueue.size() > 0)
|
|
{
|
|
auto const& tkey = regionQueue.front();
|
|
regionQueue.pop();
|
|
auto titer = tmap.find(tkey);
|
|
LogAssert(titer != tmap.end(), "Unexpected condition.");
|
|
auto const& tri = titer->second;
|
|
LogAssert(tri, "Unexpected condition.");
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
auto edge = tri->E[j];
|
|
if (edge)
|
|
{
|
|
auto siter = edges.find(EdgeKey<false>(edge->V[0], edge->V[1]));
|
|
if (siter == edges.end())
|
|
{
|
|
// The edge is not constrained, so it allows the
|
|
// search to continue.
|
|
auto adj = tri->T[j];
|
|
if (adj)
|
|
{
|
|
TriangleKey<true> akey(adj->V[0], adj->V[1], adj->V[2]);
|
|
auto riter = region.find(akey);
|
|
if (riter == region.end())
|
|
{
|
|
// The adjacent triangle has not yet been
|
|
// visited, so place it in the queue to
|
|
// continue the search.
|
|
region.insert(akey);
|
|
regionQueue.push(akey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store the region triangles in a triangulation object and remove
|
|
// those triangles from the graph in preparation for processing the
|
|
// next layer of triangles.
|
|
void ExtractTriangles(ETManifoldMesh& graph,
|
|
std::set<TriangleKey<true>> const& region,
|
|
PolygonTreeEx::Node& node)
|
|
{
|
|
node.triangulation.reserve(region.size());
|
|
if (node.chirality > 0)
|
|
{
|
|
for (auto const& tri : region)
|
|
{
|
|
node.triangulation.push_back({ tri.V[0], tri.V[1], tri.V[2] });
|
|
graph.Remove(tri.V[0], tri.V[1], tri.V[2]);
|
|
}
|
|
}
|
|
else // node.chirality < 0
|
|
{
|
|
for (auto const& tri : region)
|
|
{
|
|
node.triangulation.push_back({ tri.V[0], tri.V[2], tri.V[1] });
|
|
graph.Remove(tri.V[0], tri.V[1], tri.V[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GetOutsideTriangles(PolygonTreeEx& tree, ETManifoldMesh& graph)
|
|
{
|
|
auto const& tmap = graph.GetTriangles();
|
|
tree.outsideTriangles.resize(tmap.size());
|
|
size_t t = 0;
|
|
for (auto const& tri : tmap)
|
|
{
|
|
auto const& tkey = tri.first;
|
|
tree.outsideTriangles[t++] = { tkey.V[0], tkey.V[1], tkey.V[2] };
|
|
}
|
|
graph.Clear();
|
|
}
|
|
|
|
// Get the triangles in the polygon tree, classifying each as
|
|
// interior (in region bounded by outer polygon and its contained
|
|
// inner polygons) or exterior (in region bounded by inner polygon
|
|
// and its contained outer polygons). The inside triangles are the
|
|
// union of the interior and exterior triangles.
|
|
void GetInsideTriangles(PolygonTreeEx& tree)
|
|
{
|
|
size_t const numTriangles = tree.allTriangles.size();
|
|
size_t const numOutside = tree.outsideTriangles.size();
|
|
size_t const numInside = numTriangles - numOutside;
|
|
tree.interiorTriangles.reserve(numTriangles);
|
|
tree.interiorNodeIndices.reserve(numTriangles);
|
|
tree.exteriorTriangles.reserve(numTriangles);
|
|
tree.exteriorNodeIndices.reserve(numTriangles);
|
|
tree.insideTriangles.reserve(numInside);
|
|
tree.insideNodeIndices.reserve(numInside);
|
|
|
|
for (size_t nIndex = 0; nIndex < tree.nodes.size(); ++nIndex)
|
|
{
|
|
auto const& node = tree.nodes[nIndex];
|
|
for (auto& tri : node.triangulation)
|
|
{
|
|
if (node.chirality > 0)
|
|
{
|
|
tree.interiorTriangles.push_back(tri);
|
|
tree.interiorNodeIndices.push_back(nIndex);
|
|
}
|
|
else
|
|
{
|
|
tree.exteriorTriangles.push_back(tri);
|
|
tree.exteriorNodeIndices.push_back(nIndex);
|
|
}
|
|
|
|
tree.insideTriangles.push_back(tri);
|
|
tree.insideNodeIndices.push_back(nIndex);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|