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.
 
 

1134 lines
50 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/Delaunay2.h>
// Compute the Delaunay triangulation of the input point and then insert
// edges that are constrained to be in the triangulation. For each such
// edge, a retriangulation of the triangle strip containing the edge is
// required. NOTE: If two constrained edges overlap at a point that is
// an interior point of each edge, the second insertion will interfere
// with the retriangulation of the first edge. Although the code here
// will do what is requested, a pair of such edges usually indicates the
// upstream process that generated the edges is not doing what it should.
namespace gte
{
// The variadic template declaration supports the class
// ConstrainedDelaunay2<InputType, ComputeType>, which is deprecated and
// will be removed in a future release. The declaration also supports the
// replacement class ConstrainedDelaunay2<InputType>. The new class uses
// a blend of interval arithmetic and rational arithmetic. It also uses
// unordered sets (hash tables). The replacement performs much better than
// the deprecated class.
template <typename T, typename...>
class ConstrainedDelaunay2 {};
}
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 size to be a
// multiple of 8 bytes.)
template <typename InputType, typename ComputeType>
class // [[deprecated("Use ConstrainedDelaunay2<InputType> instead.")]]
ConstrainedDelaunay2<InputType, ComputeType> : public Delaunay2<InputType, ComputeType>
{
public:
// The class is a functor to support computing the constrained
// Delaunay triangulation of multiple data sets using the same class
// object.
virtual ~ConstrainedDelaunay2() = default;
ConstrainedDelaunay2()
:
Delaunay2<InputType, ComputeType>()
{
}
// This operator computes the Delaunay triangulation only. Read the
// Delaunay2 constructor comments about 'vertices' and 'epsilon'. For
// ComputeType chosen to be a rational type, pass zero for epsilon.
bool operator()(int numVertices, Vector2<InputType> const* vertices, InputType epsilon)
{
return Delaunay2<InputType, ComputeType>::operator()(numVertices, vertices, epsilon);
}
// The 'edge' is the constrained edge to be inserted into the
// triangulation. If that edge is already in the triangulation, the
// function returns without any retriangulation and 'partitionedEdge'
// contains the input 'edge'. If 'edge' is coincident with 1 or more
// edges already in the triangulation, 'edge' is partitioned into
// subedges which are then inserted. It is also possible that 'edge'
// does not overlap already existing edges in the triangulation but
// has interior points that are vertices in the triangulation; in
// this case, 'edge' is partitioned and the subedges are inserted.
// In either case, 'partitionEdge' is an ordered list of indices
// into the triangulation vertices that are on the edge. It is
// guaranteed that partitionedEdge.front() = edge[0] and
// partitionedEdge.back() = edge[1].
void Insert(std::array<int, 2> edge, std::vector<int>& partitionedEdge)
{
LogAssert(
edge[0] != edge[1] &&
0 <= edge[0] && edge[0] < this->mNumVertices &&
0 <= edge[1] && edge[1] < this->mNumVertices,
"Invalid edge.");
// The partitionedEdge vector stores the endpoints of the incoming
// edge if that edge does not contain interior points that are
// vertices of the Delaunay triangulation. If the edge contains
// one or more vertices in its interior, the edge is partitioned
// into subedges, each subedge having vertex endpoints but no
// interior point is a vertex. The partition is stored in the
// partitionedEdge vector.
std::vector<std::array<int, 2>> partition;
// When using exact arithmetic, a while(!edgeConsumed) loop
// suffices. When using floating-point arithmetic (which you
// should not do for CDT), guard against an infinite loop.
bool edgeConsumed = false;
size_t const numTriangles = this->mGraph.GetTriangles().size();
size_t t;
for (t = 0; t < numTriangles && !edgeConsumed; ++t)
{
EdgeKey<false> ekey(edge[0], edge[1]);
if (this->mGraph.GetEdges().find(ekey) != this->mGraph.GetEdges().end())
{
// The edge already exists in the triangulation.
mInsertedEdges.insert(ekey);
partition.push_back(edge);
break;
}
// Get the link edges for the vertex edge[0]. These edges are
// opposite the link vertex.
std::vector<std::array<int, 2>> linkEdges;
GetLinkEdges(edge[0], linkEdges);
// Determine which link triangle contains the to-be-inserted
// edge.
for (auto const& linkEdge : linkEdges)
{
// Compute on which side of the to-be-inserted edge the
// link vertices live. The triangles are not degenerate,
// so it is not possible for sign0 = sign1 = 0.
int v0 = linkEdge[0];
int v1 = linkEdge[1];
int sign0 = this->mQuery.ToLine(v0, edge[0], edge[1]);
int sign1 = this->mQuery.ToLine(v1, edge[0], edge[1]);
if (sign0 >= 0 && sign1 <= 0)
{
if (sign0 > 0)
{
if (sign1 < 0)
{
// The triangle <edge[0], v0, v1> strictly
// contains the to-be-inserted edge. Gather
// the triangles in the triangle strip
// containing the edge.
edgeConsumed = ProcessTriangleStrip(edge, v0, v1, partition);
}
else // sign1 == 0 && sign0 > 0
{
// The to-be-inserted edge is coincident with
// the triangle edge <edge[0], v1>, and it is
// guaranteed that the vertex at v1 is an
// interior point of <edge[0],edge[1]> because
// we previously tested whether edge[] is in
// the triangulation.
edgeConsumed = ProcessCoincidentEdge(edge, v1, partition);
}
}
else // sign0 == 0 && sign1 < 0
{
// The to-be-inserted edge is coincident with
// the triangle edge <edge[0], v0>, and it is
// guaranteed that the vertex at v0 is an
// interior point of <edge[0],edge[1]> because
// we previously tested whether edge[] is in
// the triangulation.
edgeConsumed = ProcessCoincidentEdge(edge, v0, partition);
}
break;
}
}
}
// If the following assertion is triggered, ComputeType was chosen
// to be 'float' or 'double'. Floating-point rounding errors led to
// misclassification of signs. The linkEdges-loop exited without
// ever calling the ProcessTriangleStrip or ProcessCoincidentEdge
// functions.
LogAssert(partition.size() > 0, CDTMessage());
partitionedEdge.resize(partition.size() + 1);
for (size_t i = 0; i < partition.size(); ++i)
{
partitionedEdge[i] = partition[i][0];
}
partitionedEdge.back() = partition.back()[1];
}
// All edges inserted via the Insert(...) call are stored for use
// by the caller. If any edge passed to Insert(...) is partitioned
// into subedges, the subedges are stored but not the original edge.
std::set<EdgeKey<false>> const& GetInsertedEdges() const
{
return mInsertedEdges;
}
// The interface functions to the base class Delaunay2 are valid, so
// access to any Delaunay information is allowed. Perhaps the most
// important member function is GetGraph() that returns a reference
// to the ETManifoldMesh that represents the constrained Delaunay
// triangulation. NOTE: If you want access to the compact arrays
// via GetIndices(t, indices[]) or GetAdjacencies(t, adjacents[]),
// you must first call UpdateIndicesAdjacencies() to ensure that the
// compact arrays are synchonized with the Delaunay graph.
private:
using Vertex = VETManifoldMesh::Vertex;
using Edge = VETManifoldMesh::Edge;
using Triangle = VETManifoldMesh::Triangle;
// For a vertex at index v, return the edges of the adjacent triangles,
// each triangle having v as a vertex and the returned edge is
// opposite v.
void GetLinkEdges(int v, std::vector<std::array<int, 2>>& linkEdges)
{
auto const& vmap = this->mGraph.GetVertices();
auto viter = vmap.find(v);
LogAssert(viter != vmap.end(), "Failed to find vertex in graph.");
auto vertex = viter->second.get();
LogAssert(vertex != nullptr, "Unexpected condition.");
for (auto const& linkTri : vertex->TAdjacent)
{
size_t j;
for (j = 0; j < 3; ++j)
{
if (linkTri->V[j] == vertex->V)
{
linkEdges.push_back({
linkTri->V[(j + 1) % 3], linkTri->V[(j + 2) % 3] });
break;
}
}
LogAssert(j < 3, "Unexpected condition.");
}
}
// The return value is 'true' if the edge did not have to be
// subdivided because it has an interior point that is a vertex.
// The return value is 'false' if it does have such a point, in
// which case edge[0] is updated to the index of that vertex. The
// caller must process the new edge.
bool ProcessTriangleStrip(std::array<int, 2>& edge, int v0, int v1,
std::vector<std::array<int, 2>>& partitionedEdge)
{
bool edgeConsumed = true;
std::array<int, 2> localEdge = edge;
// Locate and store the triangles in the triangle strip containing
// the edge.
std::vector<TriangleKey<true>> tristrip;
tristrip.emplace_back(localEdge[0], v0, v1);
auto const& tmap = this->mGraph.GetTriangles();
auto titer = tmap.find(TriangleKey<true>(localEdge[0], v0, v1));
LogAssert(titer != tmap.end(), CDTMessage());
auto tri = titer->second.get();
LogAssert(tri, CDTMessage());
// Keep track of the right and left polylines that bound the
// triangle strip. These polylines can have coincident edges.
// In particular, this happens when the current triangle in the
// strip shares an edge with a previous triangle in the strip
// and the previous triangle is not the immediate predecessor
// to the current triangle.
std::vector<int> rightPolygon, leftPolygon;
rightPolygon.push_back(localEdge[0]);
rightPolygon.push_back(v0);
leftPolygon.push_back(localEdge[0]);
leftPolygon.push_back(v1);
// When using exact arithmetic, a for(;;) loop suffices. When
// using floating-point arithmetic (which you should really not
// do for CDT), guard against an infinite loop.
size_t const numTriangles = tmap.size();
size_t t;
for (t = 0; t < numTriangles; ++t)
{
// The current triangle is tri and has edge <v0,v1>. Get
// the triangle adj that is adjacent to tri via this edge.
auto adj = tri->GetAdjacentOfEdge(v0, v1);
LogAssert(adj, CDTMessage());
tristrip.emplace_back(adj->V[0], adj->V[1], adj->V[2]);
// Get the vertex of adj that is opposite edge <v0,v1>.
int vOpposite = 0;
bool found = adj->GetOppositeVertexOfEdge(v0, v1, vOpposite);
LogAssert(found, CDTMessage());
if (vOpposite == localEdge[1])
{
// The triangle strip containing the edge is complete.
break;
}
// The next triangle in the strip depends on whether the
// opposite vertex is left-of the edge, right-of the edge
// or on the edge.
int querySign = this->mQuery.ToLine(vOpposite, localEdge[0], localEdge[1]);
if (querySign > 0)
{
tri = adj;
v0 = vOpposite;
rightPolygon.push_back(v0);
}
else if (querySign < 0)
{
tri = adj;
v1 = vOpposite;
leftPolygon.push_back(v1);
}
else
{
// The to-be-inserted edge contains an interior point that
// is also a vertex in the triangulation. The edge must be
// subdivided. The first subedge is in a triangle strip
// that is processed by code below that is outside the
// loop. The second subedge must be processed by the
// caller.
localEdge[1] = vOpposite;
edge[0] = vOpposite;
edgeConsumed = false;
break;
}
}
LogAssert(t < numTriangles, CDTMessage());
// Insert the final endpoint of the to-be-inserted edge.
rightPolygon.push_back(localEdge[1]);
leftPolygon.push_back(localEdge[1]);
// The retriangulation depends on counterclockwise ordering of
// the boundary right and left polygons. The right polygon is
// already counterclockwise ordered. The left polygon is
// clockwise ordered, so reverse it.
std::reverse(leftPolygon.begin(), leftPolygon.end());
// Update the inserted edges.
mInsertedEdges.insert(EdgeKey<false>(localEdge[0], localEdge[1]));
partitionedEdge.push_back(localEdge);
// Remove the triangle strip from the full triangulation. This
// must occur before the retriangulation which inserts new
// triangles into the full triangulation.
for (auto const& tkey : tristrip)
{
this->mGraph.Remove(tkey.V[0], tkey.V[1], tkey.V[2]);
}
// Retriangulate the tristrip region.
Retriangulate(leftPolygon);
Retriangulate(rightPolygon);
return edgeConsumed;
}
// Process a to-be-inserted edge that is coincident with an already
// existing triangulation edge.
bool ProcessCoincidentEdge(std::array<int, 2>& edge, int v,
std::vector<std::array<int, 2>>& partitionedEdge)
{
mInsertedEdges.insert(EdgeKey<false>(edge[0], v));
partitionedEdge.push_back({ edge[0], v });
edge[0] = v;
bool edgeConsumed = (v == edge[1]);
return edgeConsumed;
}
// Retriangulate the polygon via a bisection-like method that finds
// vertices closest to the current polygon base edge. The function
// is naturally recursive, but simulated recursion is used to avoid
// a large program stack by instead using the heap.
void Retriangulate(std::vector<int> const& polygon)
{
std::vector<std::array<size_t, 2>> stack(polygon.size());
int top = -1;
stack[++top] = { 0, polygon.size() - 1 };
while (top != -1)
{
std::array<size_t, 2> i = stack[top--];
if (i[1] > i[0] + 1)
{
// Get the vertex indices for the specified i-values.
int v0 = polygon[i[0]];
int v1 = polygon[i[1]];
// Select isplit in the index range [i[0]+1,i[1]-1] so
// that the vertex at index polygon[isplit] attains the
// minimum distance to the edge with vertices at the
// indices polygon[i[0]] and polygon[i[1]].
size_t isplit = SelectSplit(polygon, i[0], i[1]);
int vsplit = polygon[isplit];
// Insert the triangle into the Delaunay graph.
this->mGraph.Insert(v0, vsplit, v1);
stack[++top] = { i[0], isplit };
stack[++top] = { isplit, i[1] };
}
}
}
// Determine the polygon vertex with index strictly between i0 and i1
// that minimizes the pseudosquared distance from that vertex to the
// line segment whose endpoints are at indices i0 and i1.
size_t SelectSplit(std::vector<int> const& polygon, size_t i0, size_t i1)
{
size_t i2;
if (i1 == i0 + 2)
{
// This is the only candidate.
i2 = i0 + 1;
}
else // i1 - i0 > 2
{
// Select the index i2 in [i0+1,i1-1] for which the distance
// from the vertex v2 at i2 to the edge <v0,v1> is minimized.
// To allow exact arithmetic, use a pseudosquared distance
// that avoids divisions and square roots.
i2 = i0 + 1;
int v0 = polygon[i0];
int v1 = polygon[i1];
int v2 = polygon[i2];
// Precompute some common values that are used in all calls
// to ComputePSD.
Vector2<ComputeType> const& ctv0 = this->mComputeVertices[v0];
Vector2<ComputeType> const& ctv1 = this->mComputeVertices[v1];
Vector2<ComputeType> V1mV0 = ctv1 - ctv0;
ComputeType sqrlen10 = Dot(V1mV0, V1mV0);
// Locate the minimum pseudosquared distance.
ComputeType minpsd = ComputePSD(v0, v1, v2, V1mV0, sqrlen10);
for (size_t i = i2 + 1; i < i1; ++i)
{
v2 = polygon[i];
ComputeType psd = ComputePSD(v0, v1, v2, V1mV0, sqrlen10);
if (psd < minpsd)
{
minpsd = psd;
i2 = i;
}
}
}
return i2;
}
// Compute a pseudosquared distance from the vertex at v2 to the edge
// <v0,v1>. The result is exact for rational arithmetic and does not
// involve division. This allows ComputeType to be BSNumber<UInteger>
// rather than BSRational<UInteger>, which leads to better
// performance.
ComputeType ComputePSD(int v0, int v1, int v2,
Vector2<ComputeType> const& V1mV0, ComputeType const& sqrlen10)
{
ComputeType const zero = static_cast<ComputeType>(0);
Vector2<ComputeType> const& ctv0 = this->mComputeVertices[v0];
Vector2<ComputeType> const& ctv1 = this->mComputeVertices[v1];
Vector2<ComputeType> const& ctv2 = this->mComputeVertices[v2];
Vector2<ComputeType> V2mV0 = ctv2 - ctv0;
ComputeType dot1020 = Dot(V1mV0, V2mV0);
ComputeType psd;
if (dot1020 <= zero)
{
ComputeType sqrlen20 = Dot(V2mV0, V2mV0);
psd = sqrlen10 * sqrlen20;
}
else
{
Vector2<ComputeType> V2mV1 = ctv2 - ctv1;
ComputeType dot1021 = Dot(V1mV0, V2mV1);
if (dot1021 >= zero)
{
ComputeType sqrlen21 = Dot(V2mV1, V2mV1);
psd = sqrlen10 * sqrlen21;
}
else
{
ComputeType sqrlen20 = Dot(V2mV0, V2mV0);
psd = sqrlen10 * sqrlen20 - dot1020 * dot1020;
}
}
return psd;
}
// All edges inserted via the Insert(...) call are stored for use
// by the caller. If any edge passed to Insert(...) is partitioned
// into subedges, the subedges are inserted into this member.
std::set<EdgeKey<false>> mInsertedEdges;
private:
// All LogAssert statements other than the first one in the Insert
// call possibly can occur when ComputeType is chosen to be 'float'
// or 'double' rather than an arbitrary-precision type. This function
// encapsulates a message that is included in the logging and in the
// thrown exception explaining that floating-point rounding errors
// are most likely the problem and that you should consider using
// arbitrary precision for the ComputeType.
template <typename Dummy = ComputeType>
static typename std::enable_if<!is_arbitrary_precision<Dummy>::value, std::string>::type
CDTMessage()
{
return R"(
ComputeType is a floating-point type. The assertions can be triggered because
of rounding errors. Repeat the call to operator() using ComputeType the type
BSNumber<UIntegerAP32>. If no assertion is triggered, the problem was most
likely due to rounding errors. If an assertion is triggered, please file a
bug report and provide the input dataset to the operator()(...) function.
)";
}
template <typename Dummy = ComputeType>
static typename std::enable_if<is_arbitrary_precision<Dummy>::value, std::string>::type
CDTMessage()
{
return R"(
The failed assertion is unexpected when using arbitrary-precision arithmetic.
Please file a bug report and provide the input dataset to the operator()(...)
function.
)";
}
};
}
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 ConstrainedDelaunay2<T> : public Delaunay2<T>
{
public:
// The class is a functor to support computing the constrained
// Delaunay triangulation of multiple data sets using the same class
// object.
virtual ~ConstrainedDelaunay2() = default;
ConstrainedDelaunay2()
:
Delaunay2<T>(),
mInsertedEdges{},
mCRPool(maxNumCRPool)
{
}
// This operator computes the Delaunay triangulation only. Edges are
// inserted later.
bool operator()(std::vector<Vector2<T>> const& vertices)
{
return Delaunay2<T>::operator()(vertices);
}
bool operator()(size_t numVertices, Vector2<T> const* vertices)
{
return Delaunay2<T>::operator()(numVertices, vertices);
}
// The 'edge' is the constrained edge to be inserted into the
// triangulation. If that edge is already in the triangulation, the
// function returns without any retriangulation and 'partitionedEdge'
// contains the input 'edge'. If 'edge' is coincident with 1 or more
// edges already in the triangulation, 'edge' is partitioned into
// subedges which are then inserted. It is also possible that 'edge'
// does not overlap already existing edges in the triangulation but
// has interior points that are vertices in the triangulation; in
// this case, 'edge' is partitioned and the subedges are inserted.
// In either case, 'partitionEdge' is an ordered list of indices
// into the triangulation vertices that are on the edge. It is
// guaranteed that partitionedEdge.front() = edge[0] and
// partitionedEdge.back() = edge[1].
void Insert(std::array<int32_t, 2> edge, std::vector<int32_t>& partitionedEdge)
{
LogAssert(
edge[0] != edge[1] &&
0 <= edge[0] && edge[0] < static_cast<int32_t>(this->GetNumVertices()) &&
0 <= edge[1] && edge[1] < static_cast<int32_t>(this->GetNumVertices()),
"Invalid edge.");
// The partitionedEdge vector stores the endpoints of the incoming
// edge if that edge does not contain interior points that are
// vertices of the Delaunay triangulation. If the edge contains
// one or more vertices in its interior, the edge is partitioned
// into subedges, each subedge having vertex endpoints but no
// interior point is a vertex. The partition is stored in the
// partitionedEdge vector.
std::vector<std::array<int32_t, 2>> partition;
// When using exact arithmetic, a while(!edgeConsumed) loop
// suffices. Just in case the code has a bug, guard against an
// infinite loop.
bool edgeConsumed = false;
size_t const numTriangles = this->mGraph.GetTriangles().size();
size_t t;
for (t = 0; t < numTriangles && !edgeConsumed; ++t)
{
EdgeKey<false> ekey(edge[0], edge[1]);
if (this->mGraph.GetEdges().find(ekey) != this->mGraph.GetEdges().end())
{
// The edge already exists in the triangulation.
mInsertedEdges.insert(ekey);
partition.push_back(edge);
break;
}
// Get the link edges for the vertex edge[0]. These edges are
// opposite the link vertex.
std::vector<std::array<int32_t, 2>> linkEdges;
GetLinkEdges(edge[0], linkEdges);
// Determine which link triangle contains the to-be-inserted
// edge.
for (auto const& linkEdge : linkEdges)
{
// Compute on which side of the to-be-inserted edge the
// link vertices live. The triangles are not degenerate,
// so it is not possible for sign0 = sign1 = 0.
size_t e0Index = static_cast<size_t>(edge[0]);
size_t e1Index = static_cast<size_t>(edge[1]);
size_t v0Index = static_cast<size_t>(linkEdge[0]);
size_t v1Index = static_cast<size_t>(linkEdge[1]);
int32_t sign0 = ToLine(v0Index, e0Index, e1Index);
int32_t sign1 = ToLine(v1Index, e0Index, e1Index);
if (sign0 >= 0 && sign1 <= 0)
{
if (sign0 > 0)
{
if (sign1 < 0)
{
// The triangle <edge[0], v0, v1> strictly
// contains the to-be-inserted edge. Gather
// the triangles in the triangle strip
// containing the edge.
edgeConsumed = ProcessTriangleStrip(edge, v0Index, v1Index, partition);
}
else // sign1 == 0 && sign0 > 0
{
// The to-be-inserted edge is coincident with
// the triangle edge <edge[0], v1>, and it is
// guaranteed that the vertex at v1 is an
// interior point of <edge[0],edge[1]> because
// we previously tested whether edge[] is in
// the triangulation.
edgeConsumed = ProcessCoincidentEdge(edge, v1Index, partition);
}
}
else // sign0 == 0 && sign1 < 0
{
// The to-be-inserted edge is coincident with
// the triangle edge <edge[0], v0>, and it is
// guaranteed that the vertex at v0 is an
// interior point of <edge[0],edge[1]> because
// we previously tested whether edge[] is in
// the triangulation.
edgeConsumed = ProcessCoincidentEdge(edge, v0Index, partition);
}
break;
}
}
}
// If the following assertion is triggered, ComputeType was chosen
// to be 'float' or 'double'. Floating-point rounding errors led to
// misclassification of signs. The linkEdges-loop exited without
// ever calling the ProcessTriangleStrip or ProcessCoincidentEdge
// functions.
LogAssert(partition.size() > 0, CDTMessage());
partitionedEdge.resize(partition.size() + 1);
for (size_t i = 0; i < partition.size(); ++i)
{
partitionedEdge[i] = partition[i][0];
}
partitionedEdge.back() = partition.back()[1];
}
// All edges inserted via the Insert(...) call are stored for use
// by the caller. If any edge passed to Insert(...) is partitioned
// into subedges, the subedges are stored but not the original edge.
using EdgeKeySet = std::unordered_set<EdgeKey<false>,
EdgeKey<false>, EdgeKey<false>>;
EdgeKeySet const& GetInsertedEdges() const
{
return mInsertedEdges;
}
// The interface functions to the base class Delaunay2 are valid, so
// access to any Delaunay information is allowed. Perhaps the most
// important member function is GetGraph() that returns a reference
// to the ETManifoldMesh that represents the constrained Delaunay
// triangulation. NOTE: If you want access to the compact arrays
// via GetIndices(t, indices[]) or GetAdjacencies(t, adjacents[]),
// you must first call UpdateIndicesAdjacencies() to ensure that the
// compact arrays are synchonized with the Delaunay graph.
private:
// The type of the read-only input vertices[] when converted for
// rational arithmetic.
using InputRational = typename Delaunay2<T>::InputRational;
// The compute type used for exact sign classification.
static int32_t constexpr ComputeNumWords = std::is_same<T, float>::value ? 70 : 526;
using ComputeRational = BSNumber<UIntegerFP32<ComputeNumWords>>;
// Convenient renaming.
using Vertex = VETManifoldMesh::Vertex;
using Edge = VETManifoldMesh::Edge;
using Triangle = VETManifoldMesh::Triangle;
// For a vertex at index v, return the edges of the adjacent triangles,
// each triangle having v as a vertex and the returned edge is
// opposite v.
void GetLinkEdges(int32_t v, std::vector<std::array<int32_t, 2>>& linkEdges)
{
auto const& vmap = this->mGraph.GetVertices();
auto viter = vmap.find(v);
LogAssert(viter != vmap.end(), "Failed to find vertex in graph.");
auto vertex = viter->second.get();
LogAssert(vertex != nullptr, "Unexpected condition.");
for (auto const& linkTri : vertex->TAdjacent)
{
size_t j;
for (j = 0; j < 3; ++j)
{
if (linkTri->V[j] == vertex->V)
{
linkEdges.push_back({
linkTri->V[(j + 1) % 3], linkTri->V[(j + 2) % 3] });
break;
}
}
LogAssert(j < 3, "Unexpected condition.");
}
}
// Given a line with origin V0 and direction <V0,V1> and a query
// point P, ToLine returns
// +1, P on right of line
// -1, P on left of line
// 0, P on the line
int32_t ToLine(size_t pIndex, size_t v0Index, size_t v1Index) const
{
// The expression tree has 13 nodes consisting of 6 input
// leaves and 7 compute nodes.
// Use interval arithmetic to determine the sign if possible.
auto const& inP = this->mVertices[pIndex];
Vector2<T> const& inV0 = this->mVertices[v0Index];
Vector2<T> const& inV1 = this->mVertices[v1Index];
auto x0 = SWInterval<T>::Sub(inP[0], inV0[0]);
auto y0 = SWInterval<T>::Sub(inP[1], inV0[1]);
auto x1 = SWInterval<T>::Sub(inV1[0], inV0[0]);
auto y1 = SWInterval<T>::Sub(inV1[1], inV0[1]);
auto x0y1 = x0 * y1;
auto x1y0 = x1 * y0;
auto det = x0y1 - x1y0;
T constexpr zero = 0;
if (det[0] > zero)
{
return +1;
}
else if (det[1] < zero)
{
return -1;
}
// The exact sign of the determinant is not known, so compute
// the determinant using rational arithmetic.
// Name the nodes of the expression tree.
auto const& irP = this->mIRVertices[pIndex];
auto const& irV0 = this->mIRVertices[v0Index];
auto const& irV1 = this->mIRVertices[v1Index];
auto const& crP0 = Copy(irP[0], mCRPool[0]);
auto const& crP1 = Copy(irP[1], mCRPool[1]);
auto const& crV00 = Copy(irV0[0], mCRPool[2]);
auto const& crV01 = Copy(irV0[1], mCRPool[3]);
auto const& crV10 = Copy(irV1[0], mCRPool[4]);
auto const& crV11 = Copy(irV1[1], mCRPool[5]);
auto& crX0 = mCRPool[6];
auto& crY0 = mCRPool[7];
auto& crX1 = mCRPool[8];
auto& crY1 = mCRPool[9];
auto& crX0Y1 = mCRPool[10];
auto& crX1Y0 = mCRPool[11];
auto& crDet = mCRPool[12];
// Evaluate the expression tree.
crX0 = crP0 - crV00;
crY0 = crP1 - crV01;
crX1 = crV10 - crV00;
crY1 = crV11 - crV01;
crX0Y1 = crX0 * crY1;
crX1Y0 = crX1 * crY0;
crDet = crX0Y1 - crX1Y0;
return crDet.GetSign();
}
// The return value is 'true' if the edge did not have to be
// subdivided because it has an interior point that is a vertex.
// The return value is 'false' if it does have such a point, in
// which case edge[0] is updated to the index of that vertex. The
// caller must process the new edge.
bool ProcessTriangleStrip(std::array<int32_t, 2>& edge, size_t v0Index,
size_t v1Index, std::vector<std::array<int32_t, 2>>& partitionedEdge)
{
int32_t v0 = static_cast<int32_t>(v0Index);
int32_t v1 = static_cast<int32_t>(v1Index);
bool edgeConsumed = true;
std::array<int32_t, 2> localEdge = edge;
// Locate and store the triangles in the triangle strip containing
// the edge.
ETManifoldMesh tristrip;
tristrip.Insert(localEdge[0], v0, v1);
auto const& tmap = this->mGraph.GetTriangles();
auto titer = tmap.find(TriangleKey<true>(localEdge[0], v0, v1));
LogAssert(titer != tmap.end(), CDTMessage());
auto tri = titer->second.get();
LogAssert(tri, CDTMessage());
// Keep track of the right and left polylines that bound the
// triangle strip. These polylines can have coincident edges.
// In particular, this happens when the current triangle in the
// strip shares an edge with a previous triangle in the strip
// and the previous triangle is not the immediate predecessor
// to the current triangle.
std::vector<int32_t> rightPolygon, leftPolygon;
rightPolygon.push_back(localEdge[0]);
rightPolygon.push_back(v0);
leftPolygon.push_back(localEdge[0]);
leftPolygon.push_back(v1);
// When using exact arithmetic, a for(;;) loop suffices. When
// using floating-point arithmetic (which you should really not
// do for CDT), guard against an infinite loop.
size_t const numTriangles = tmap.size();
size_t t;
for (t = 0; t < numTriangles; ++t)
{
// The current triangle is tri and has edge <v0,v1>. Get
// the triangle adj that is adjacent to tri via this edge.
auto adj = tri->GetAdjacentOfEdge(v0, v1);
LogAssert(adj, CDTMessage());
tristrip.Insert(adj->V[0], adj->V[1], adj->V[2]);
// Get the vertex of adj that is opposite edge <v0,v1>.
int32_t vOpposite = 0;
bool found = adj->GetOppositeVertexOfEdge(v0, v1, vOpposite);
LogAssert(found, CDTMessage());
if (vOpposite == localEdge[1])
{
// The triangle strip containing the edge is complete.
break;
}
// The next triangle in the strip depends on whether the
// opposite vertex is left-of the edge, right-of the edge
// or on the edge.
int32_t querySign = ToLine(static_cast<size_t>(vOpposite),
static_cast<size_t>(localEdge[0]), static_cast<size_t>(localEdge[1]));
if (querySign > 0)
{
tri = adj;
v0 = vOpposite;
rightPolygon.push_back(v0);
}
else if (querySign < 0)
{
tri = adj;
v1 = vOpposite;
leftPolygon.push_back(v1);
}
else
{
// The to-be-inserted edge contains an interior point that
// is also a vertex in the triangulation. The edge must be
// subdivided. The first subedge is in a triangle strip
// that is processed by code below that is outside the
// loop. The second subedge must be processed by the
// caller.
localEdge[1] = vOpposite;
edge[0] = vOpposite;
edgeConsumed = false;
break;
}
}
LogAssert(t < numTriangles, CDTMessage());
// Insert the final endpoint of the to-be-inserted edge.
rightPolygon.push_back(localEdge[1]);
leftPolygon.push_back(localEdge[1]);
// The retriangulation depends on counterclockwise ordering of
// the boundary right and left polygons. The right polygon is
// already counterclockwise ordered. The left polygon is
// clockwise ordered, so reverse it.
std::reverse(leftPolygon.begin(), leftPolygon.end());
// Update the inserted edges.
mInsertedEdges.insert(EdgeKey<false>(localEdge[0], localEdge[1]));
partitionedEdge.push_back(localEdge);
// Remove the triangle strip from the full triangulation. This
// must occur before the retriangulation which inserts new
// triangles into the full triangulation.
for (auto const& element : tristrip.GetTriangles())
{
auto const& tkey = element.first;
this->mGraph.Remove(tkey.V[0], tkey.V[1], tkey.V[2]);
}
// Retriangulate the tristrip region.
Retriangulate(leftPolygon);
Retriangulate(rightPolygon);
return edgeConsumed;
}
// Process a to-be-inserted edge that is coincident with an already
// existing triangulation edge.
bool ProcessCoincidentEdge(std::array<int32_t, 2>& edge, size_t vIndex,
std::vector<std::array<int32_t, 2>>& partitionedEdge)
{
int32_t v = static_cast<int32_t>(vIndex);
mInsertedEdges.insert(EdgeKey<false>(edge[0], v));
partitionedEdge.push_back({ edge[0], v });
edge[0] = v;
bool edgeConsumed = (v == edge[1]);
return edgeConsumed;
}
// Retriangulate the polygon via a bisection-like method that finds
// vertices closest to the current polygon base edge. The function
// is naturally recursive, but simulated recursion is used to avoid
// a large program stack by instead using the heap.
void Retriangulate(std::vector<int32_t> const& polygon)
{
std::vector<std::array<size_t, 2>> stack(polygon.size());
size_t top = std::numeric_limits<size_t>::max();
stack[++top] = { 0, polygon.size() - 1 };
while (top != std::numeric_limits<size_t>::max())
{
std::array<size_t, 2> i = stack[top--];
if (i[1] > i[0] + 1)
{
// Get the vertex indices for the specified i-values.
int32_t v0 = polygon[i[0]];
int32_t v1 = polygon[i[1]];
// Select isplit in the index range [i[0]+1,i[1]-1] so
// that the vertex at index polygon[isplit] attains the
// minimum distance to the edge with vertices at the
// indices polygon[i[0]] and polygon[i[1]].
size_t isplit = SelectSplit(polygon, i[0], i[1]);
int32_t vsplit = polygon[isplit];
// Insert the triangle into the Delaunay graph.
this->mGraph.Insert(v0, vsplit, v1);
stack[++top] = { i[0], isplit };
stack[++top] = { isplit, i[1] };
}
}
}
static ComputeRational const& Copy(InputRational const& source,
ComputeRational& target)
{
target.SetSign(source.GetSign());
target.SetBiasedExponent(source.GetBiasedExponent());
target.GetUInteger().CopyFrom(source.GetUInteger());
return target;
}
// Determine the polygon vertex with index strictly between i0 and i1
// that minimizes the pseudosquared distance from that vertex to the
// line segment whose endpoints are at indices i0 and i1.
size_t SelectSplit(std::vector<int32_t> const& polygon, size_t i0, size_t i1)
{
size_t i2;
if (i1 == i0 + 2)
{
// This is the only candidate.
i2 = i0 + 1;
}
else // i1 - i0 > 2
{
// Select the index i2 in [i0+1,i1-1] for which the distance
// from the vertex v2 at i2 to the edge <v0,v1> is minimized.
// To allow exact arithmetic, use a pseudosquared distance
// that avoids divisions and square roots.
i2 = i0 + 1;
size_t v0 = static_cast<size_t>(polygon[i0]);
size_t v1 = static_cast<size_t>(polygon[i1]);
size_t v2 = static_cast<size_t>(polygon[i2]);
// Precompute some common values that are used in all calls
// to ComputePSD.
auto const& irV0 = this->mIRVertices[v0];
auto const& irV1 = this->mIRVertices[v1];
auto const& irV2 = this->mIRVertices[v2];
auto const& crV0x = Copy(irV0[0], mCRPool[0]);
auto const& crV0y = Copy(irV0[1], mCRPool[1]);
auto const& crV1x = Copy(irV1[0], mCRPool[2]);
auto const& crV1y = Copy(irV1[1], mCRPool[3]);
auto const& crV2x = Copy(irV2[0], mCRPool[4]);
auto const& crV2y = Copy(irV2[1], mCRPool[5]);
auto& crV1mV0x = mCRPool[6];
auto& crV1mV0y = mCRPool[7];
auto& crSqrLen10 = mCRPool[8];
auto& crPSD = mCRPool[9];
auto& crMinPSD = mCRPool[10];
crV1mV0x = crV1x - crV0x;
crV1mV0y = crV1y - crV0y;
crSqrLen10 = crV1mV0x * crV1mV0x + crV1mV0y * crV1mV0y;
// Locate the minimum pseudosquared distance.
ComputePSD(crV0x, crV0y, crV1x, crV1y, crV2x, crV2y,
crV1mV0x, crV1mV0y, crSqrLen10, crMinPSD);
for (size_t i = i2 + 1; i < i1; ++i)
{
v2 = polygon[i];
auto const& irNextV2 = this->mIRVertices[v2];
this->Copy(irNextV2[0], mCRPool[4]);
this->Copy(irNextV2[1], mCRPool[5]);
ComputePSD(crV0x, crV0y, crV1x, crV1y, crV2x, crV2y,
crV1mV0x, crV1mV0y, crSqrLen10, crPSD);
if (crPSD < crMinPSD)
{
crMinPSD = crPSD;
i2 = i;
}
}
}
return i2;
}
// Compute a pseudosquared distance from the vertex at v2 to the edge
// <v0,v1>. The result is exact for rational arithmetic and does not
// involve division. This allows ComputeType to be BSNumber<UInteger>
// rather than BSRational<UInteger>, which leads to better
// performance.
void ComputePSD(
ComputeRational const& crV0x,
ComputeRational const& crV0y,
ComputeRational const& crV1x,
ComputeRational const& crV1y,
ComputeRational const& crV2x,
ComputeRational const& crV2y,
ComputeRational const& crV1mV0x,
ComputeRational const& crV1mV0y,
ComputeRational const& crSqrLen10,
ComputeRational& crPSD)
{
auto& crV2mV0x = mCRPool[11];
auto& crV2mV0y = mCRPool[12];
auto& crV2mV1x = mCRPool[13];
auto& crV2mV1y = mCRPool[14];
auto& crDot1020 = mCRPool[15];
auto& crSqrLen20 = mCRPool[16];
auto& crDot1021 = mCRPool[17];
auto& crSqrLen21 = mCRPool[18];
crV2mV0x = crV2x - crV0x;
crV2mV0y = crV2y - crV0y;
crDot1020 = crV1mV0x * crV2mV0x + crV1mV0y * crV2mV0y;
if (crDot1020.GetSign() <= 0)
{
crSqrLen20 = crV2mV0x * crV2mV0x + crV2mV0y * crV2mV0y;
crPSD = crSqrLen10 * crSqrLen20;
}
else
{
crV2mV1x = crV2x - crV1x;
crV2mV1y = crV2y - crV1y;
crDot1021 = crV1mV0x * crV2mV1x + crV1mV0y * crV2mV1y;
if (crDot1021.GetSign() >= 0)
{
crSqrLen21 = crV2mV1x * crV2mV1x + crV2mV1y * crV2mV1y;
crPSD = crSqrLen10 * crSqrLen21;
}
else
{
crSqrLen20 = crV2mV0x * crV2mV0x + crV2mV0y * crV2mV0y;
crPSD = crSqrLen10 * crSqrLen20 - crDot1020 * crDot1020;
}
}
}
static std::string CDTMessage()
{
return R"(
The failed assertion is unexpected when using arbitrary-precision arithmetic.
Please file a bug report and provide the input dataset to the operator()(...)
function.
)";
}
// All edges inserted via the Insert(...) call are stored for use
// by the caller. If any edge passed to Insert(...) is partitioned
// into subedges, the subedges are inserted into this member.
EdgeKeySet mInsertedEdges;
// Sufficient storage for the expression trees related to computing
// the exact pseudosquared distances in SelectSplit and ComputePSD.
static size_t constexpr maxNumCRPool = 19;
mutable std::vector<ComputeRational> mCRPool;
};
}