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.
 
 
 
 
 
 

1839 lines
69 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
// Remove includes of <Mathematics/PrimalQuery2.h> and <set> once
// Delaunay2<InputType, ComputeType> is removed.
#include <Mathematics/Logger.h>
#include <Mathematics/ArbitraryPrecision.h>
#include <Mathematics/FPInterval.h>
#include <Mathematics/HashCombine.h>
#include <Mathematics/Line.h>
#include <Mathematics/PrimalQuery2.h>
#include <Mathematics/SWInterval.h>
#include <Mathematics/Vector2.h>
#include <Mathematics/VETManifoldMesh.h>
#include <numeric>
// Delaunay triangulation of points (intrinsic dimensionality 2).
// VQ = number of vertices
// V = array of vertices
// TQ = number of triangles
// I = Array of 3-tuples of indices into V that represent the triangles
// (3*TQ total elements). Access via GetIndices(*).
// A = Array of 3-tuples of indices into I that represent the adjacent
// triangles (3*TQ total elements). Access via GetAdjacencies(*).
// The i-th triangle has vertices
// vertex[0] = V[I[3*i+0]]
// vertex[1] = V[I[3*i+1]]
// vertex[2] = V[I[3*i+2]]
// and edge index pairs
// edge[0] = <I[3*i+0],I[3*i+1]>
// edge[1] = <I[3*i+1],I[3*i+2]>
// edge[2] = <I[3*i+2],I[3*i+0]>
// The triangles adjacent to these edges have indices
// adjacent[0] = A[3*i+0] is the triangle sharing edge[0]
// adjacent[1] = A[3*i+1] is the triangle sharing edge[1]
// adjacent[2] = A[3*i+2] is the triangle sharing edge[2]
// If there is no adjacent triangle, the A[*] value is set to -1. The
// triangle adjacent to edge[j] has vertices
// adjvertex[0] = V[I[3*adjacent[j]+0]]
// adjvertex[1] = V[I[3*adjacent[j]+1]]
// adjvertex[2] = V[I[3*adjacent[j]+2]]
// The only way to ensure a correct result for the input vertices (assumed to
// be exact) is to choose ComputeType for exact rational arithmetic. You may
// use BSNumber. No divisions are performed in this computation, so you do
// not have to use BSRational.
namespace gte
{
// The variadic template declaration supports the class
// Delaunay2<InputType, ComputeType>, which is deprecated and will be
// removed in a future release. The declaration also supports the
// replacement class Delaunay2<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 Delaunay2 {};
}
namespace gte
{
// This class requires you to specify the ComputeType yourself. If it
// is BSNumber<> or BSRational<>, the worst-case choices of N for the
// chosen InputType are listed in the next table. The numerical
// computations are encapsulated in PrimalQuery2<ComputeType>::ToLine and
// PrimalQuery2<ComputeType>::ToCircumcircle, the latter query the
// dominant one in/ determining N. We recommend using only BSNumber,
// because no divisions are performed in the triangulation computations.
//
// input type | compute type | N
// -----------+--------------+------
// float | BSNumber | 35
// double | BSNumber | 263
// float | BSRational | 573
// double | BSRational | 4329
template <typename InputType, typename ComputeType>
class // [[deprecated("Use Delaunay2<InputType> instead.")]]
Delaunay2<InputType, ComputeType>
{
public:
// The class is a functor to support computing the Delaunay
// triangulation of multiple data sets using the same class object.
virtual ~Delaunay2() = default;
Delaunay2()
:
mEpsilon((InputType)0),
mDimension(0),
mLine(Vector2<InputType>::Zero(), Vector2<InputType>::Zero()),
mNumVertices(0),
mNumUniqueVertices(0),
mNumTriangles(0),
mVertices(nullptr),
mIndex{ { { 0, 1 }, { 1, 2 }, { 2, 0 } } }
{
}
// The input is the array of vertices whose Delaunay triangulation is
// required. The epsilon value is used to determine the intrinsic
// dimensionality of the vertices (d = 0, 1, or 2). When epsilon is
// positive, the determination is fuzzy--vertices approximately the
// same point, approximately on a line, or planar. The return value
// is 'true' if and only if the hull construction is successful.
bool operator()(int numVertices, Vector2<InputType> const* vertices, InputType epsilon)
{
mEpsilon = std::max(epsilon, (InputType)0);
mDimension = 0;
mLine.origin = Vector2<InputType>::Zero();
mLine.direction = Vector2<InputType>::Zero();
mNumVertices = numVertices;
mNumUniqueVertices = 0;
mNumTriangles = 0;
mVertices = vertices;
mGraph.Clear();
mIndices.clear();
mAdjacencies.clear();
mDuplicates.resize(std::max(numVertices, 3));
int i, j;
if (mNumVertices < 3)
{
// Delaunay2 should be called with at least three points.
return false;
}
IntrinsicsVector2<InputType> info(mNumVertices, vertices, mEpsilon);
if (info.dimension == 0)
{
// mDimension is 0; mGraph, mIndices, and mAdjacencies are empty
return false;
}
if (info.dimension == 1)
{
// The set is (nearly) collinear.
mDimension = 1;
mLine = Line2<InputType>(info.origin, info.direction[0]);
return false;
}
mDimension = 2;
// Compute the vertices for the queries.
mComputeVertices.resize(mNumVertices);
mQuery.Set(mNumVertices, &mComputeVertices[0]);
for (i = 0; i < mNumVertices; ++i)
{
for (j = 0; j < 2; ++j)
{
mComputeVertices[i][j] = vertices[i][j];
}
}
// Insert the (nondegenerate) triangle constructed by the call to
// GetInformation. This is necessary for the circumcircle-visibility
// algorithm to work correctly.
if (!info.extremeCCW)
{
std::swap(info.extreme[1], info.extreme[2]);
}
if (!mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2]))
{
return false;
}
// Incrementally update the triangulation. The set of processed
// points is maintained to eliminate duplicates, either in the
// original input points or in the points obtained by snap rounding.
std::set<ProcessedVertex> processed;
for (i = 0; i < 3; ++i)
{
j = info.extreme[i];
processed.insert(ProcessedVertex(vertices[j], j));
mDuplicates[j] = j;
}
for (i = 0; i < mNumVertices; ++i)
{
ProcessedVertex v(vertices[i], i);
auto iter = processed.find(v);
if (iter == processed.end())
{
if (!Update(i))
{
// A failure can occur if ComputeType is not an exact
// arithmetic type.
return false;
}
processed.insert(v);
mDuplicates[i] = i;
}
else
{
mDuplicates[i] = iter->location;
}
}
mNumUniqueVertices = static_cast<int>(processed.size());
// Assign integer values to the triangles for use by the caller
// and copy the triangle information to compact arrays mIndices
// and mAdjacencies.
UpdateIndicesAdjacencies();
return true;
}
// Dimensional information. If GetDimension() returns 1, the points
// lie on a line P+t*D (fuzzy comparison when epsilon > 0). You can
// sort these if you need a polyline output by projecting onto the
// line each vertex X = P+t*D, where t = Dot(D,X-P).
inline InputType GetEpsilon() const
{
return mEpsilon;
}
inline int GetDimension() const
{
return mDimension;
}
inline Line2<InputType> const& GetLine() const
{
return mLine;
}
// Member access.
inline int GetNumVertices() const
{
return mNumVertices;
}
inline int GetNumUniqueVertices() const
{
return mNumUniqueVertices;
}
inline int GetNumTriangles() const
{
return mNumTriangles;
}
inline Vector2<InputType> const* GetVertices() const
{
return mVertices;
}
inline PrimalQuery2<ComputeType> const& GetQuery() const
{
return mQuery;
}
inline ETManifoldMesh const& GetGraph() const
{
return mGraph;
}
inline std::vector<int> const& GetIndices() const
{
return mIndices;
}
inline std::vector<int> const& GetAdjacencies() const
{
return mAdjacencies;
}
// If 'vertices' has no duplicates, GetDuplicates()[i] = i for all i.
// If vertices[i] is the first occurrence of a vertex and if
// vertices[j] is found later, then GetDuplicates()[j] = i.
inline std::vector<int> const& GetDuplicates() const
{
return mDuplicates;
}
// Locate those triangle edges that do not share other triangles. The
// returned array has hull.size() = 2*numEdges, each pair representing
// an edge. The edges are not ordered, but the pair of vertices for
// an edge is ordered so that they conform to a counterclockwise
// traversal of the hull. The return value is 'true' if and only if
// the dimension is 2.
bool GetHull(std::vector<int>& hull) const
{
if (mDimension == 2)
{
// Count the number of edges that are not shared by two
// triangles.
int numEdges = 0;
for (auto adj : mAdjacencies)
{
if (adj == -1)
{
++numEdges;
}
}
if (numEdges > 0)
{
// Enumerate the edges.
hull.resize(2 * numEdges);
int current = 0, i = 0;
for (auto adj : mAdjacencies)
{
if (adj == -1)
{
int tri = i / 3, j = i % 3;
hull[current++] = mIndices[3 * tri + j];
hull[current++] = mIndices[3 * tri + ((j + 1) % 3)];
}
++i;
}
return true;
}
else
{
LogError("Unexpected. There must be at least one triangle.");
}
}
else
{
LogError("The dimension must be 2.");
}
}
// Copy Delaunay triangles to compact arrays mIndices and
// mAdjacencies. The array information is accessible via the
// functions GetIndices(int, std::array<int, 3>&) and
// GetAdjacencies(int, std::array<int, 3>&).
void UpdateIndicesAdjacencies()
{
// Assign integer values to the triangles.
auto const& tmap = mGraph.GetTriangles();
std::map<Triangle*, int> permute;
int i = -1;
permute[nullptr] = i++;
for (auto const& element : tmap)
{
permute[element.second.get()] = i++;
}
mNumTriangles = static_cast<int>(tmap.size());
int numindices = 3 * mNumTriangles;
if (numindices > 0)
{
mIndices.resize(numindices);
mAdjacencies.resize(numindices);
i = 0;
for (auto const& element : tmap)
{
Triangle* tri = element.second.get();
for (size_t j = 0; j < 3; ++j, ++i)
{
mIndices[i] = tri->V[j];
mAdjacencies[i] = permute[tri->T[j]];
}
}
}
}
// Get the vertex indices for triangle i. The function returns 'true'
// when the dimension is 2 and i is a valid triangle index, in which
// case the vertices are valid; otherwise, the function returns
// 'false' and the vertices are invalid.
bool GetIndices(int i, std::array<int, 3>& indices) const
{
if (mDimension == 2)
{
int numTriangles = static_cast<int>(mIndices.size() / 3);
if (0 <= i && i < numTriangles)
{
indices[0] = mIndices[3 * i];
indices[1] = mIndices[3 * i + 1];
indices[2] = mIndices[3 * i + 2];
return true;
}
}
else
{
LogError("The dimension must be 2.");
}
return false;
}
// Get the indices for triangles adjacent to triangle i. The function
// returns 'true' when the dimension is 2 and if i is a valid triangle
// index, in which case the adjacencies are valid; otherwise, the
// function returns 'false' and the adjacencies are invalid.
bool GetAdjacencies(int i, std::array<int, 3>& adjacencies) const
{
if (mDimension == 2)
{
int numTriangles = static_cast<int>(mIndices.size() / 3);
if (0 <= i && i < numTriangles)
{
adjacencies[0] = mAdjacencies[3 * i];
adjacencies[1] = mAdjacencies[3 * i + 1];
adjacencies[2] = mAdjacencies[3 * i + 2];
return true;
}
}
else
{
LogError("The dimension must be 2.");
}
return false;
}
// Support for searching the triangulation for a triangle that
// contains a point. If there is a containing triangle, the returned
// value is a triangle index i with 0 <= i < GetNumTriangles(). If
// there is not a containing triangle, -1 is returned. The
// computations are performed using exact rational arithmetic.
//
// The SearchInfo input stores information about the triangle search
// when looking for the triangle (if any) that contains p. The first
// triangle searched is 'initialTriangle'. On return 'path' stores
// those (ordered) triangle indices visited during the search. The
// last visited triangle has index 'finalTriangle and vertex indices
// 'finalV[0,1,2]', stored in counterclockwise order. The last edge
// of the search is <finalV[0],finalV[1]>. For spatially coherent
// inputs p for numerous calls to this function, you will want to
// specify 'finalTriangle' from the previous call as 'initialTriangle'
// for the next call, which should reduce search times.
static int constexpr negOne = -1;
struct SearchInfo
{
SearchInfo()
:
initialTriangle(negOne),
numPath(0),
path{},
finalTriangle(0),
finalV{ 0, 0, 0 }
{
}
int initialTriangle;
int numPath;
std::vector<int> path;
int finalTriangle;
std::array<int, 3> finalV;
};
int GetContainingTriangle(Vector2<InputType> const& p, SearchInfo& info) const
{
if (mDimension == 2)
{
Vector2<ComputeType> test{ p[0], p[1] };
int numTriangles = static_cast<int>(mIndices.size() / 3);
info.path.resize(numTriangles);
info.numPath = 0;
int triangle;
if (0 <= info.initialTriangle && info.initialTriangle < numTriangles)
{
triangle = info.initialTriangle;
}
else
{
info.initialTriangle = 0;
triangle = 0;
}
// Use triangle edges as binary separating lines.
for (int i = 0; i < numTriangles; ++i)
{
int ibase = 3 * triangle;
int const* v = &mIndices[ibase];
info.path[info.numPath++] = triangle;
info.finalTriangle = triangle;
info.finalV[0] = v[0];
info.finalV[1] = v[1];
info.finalV[2] = v[2];
if (mQuery.ToLine(test, v[0], v[1]) > 0)
{
triangle = mAdjacencies[ibase];
if (triangle == -1)
{
info.finalV[0] = v[0];
info.finalV[1] = v[1];
info.finalV[2] = v[2];
return -1;
}
continue;
}
if (mQuery.ToLine(test, v[1], v[2]) > 0)
{
triangle = mAdjacencies[ibase + 1];
if (triangle == -1)
{
info.finalV[0] = v[1];
info.finalV[1] = v[2];
info.finalV[2] = v[0];
return -1;
}
continue;
}
if (mQuery.ToLine(test, v[2], v[0]) > 0)
{
triangle = mAdjacencies[ibase + 2];
if (triangle == -1)
{
info.finalV[0] = v[2];
info.finalV[1] = v[0];
info.finalV[2] = v[1];
return -1;
}
continue;
}
return triangle;
}
}
else
{
LogError("The dimension must be 2.");
}
return -1;
}
protected:
// Support for incremental Delaunay triangulation.
typedef ETManifoldMesh::Triangle Triangle;
bool GetContainingTriangle(int i, Triangle*& tri) const
{
int numTriangles = static_cast<int>(mGraph.GetTriangles().size());
for (int t = 0; t < numTriangles; ++t)
{
int j;
for (j = 0; j < 3; ++j)
{
int v0 = tri->V[mIndex[j][0]];
int v1 = tri->V[mIndex[j][1]];
if (mQuery.ToLine(i, v0, v1) > 0)
{
// Point i sees edge <v0,v1> from outside the triangle.
auto adjTri = tri->T[j];
if (adjTri)
{
// Traverse to the triangle sharing the face.
tri = adjTri;
break;
}
else
{
// We reached a hull edge, so the point is outside
// the hull.
return false;
}
}
}
if (j == 3)
{
// The point is inside all four edges, so the point is inside
// a triangle.
return true;
}
}
LogError("Unexpected termination of loop.");
}
bool GetAndRemoveInsertionPolygon(int i, std::set<Triangle*>& candidates,
std::set<EdgeKey<true>>& boundary)
{
// Locate the triangles that make up the insertion polygon.
ETManifoldMesh polygon;
while (candidates.size() > 0)
{
Triangle* tri = *candidates.begin();
candidates.erase(candidates.begin());
for (int j = 0; j < 3; ++j)
{
auto adj = tri->T[j];
if (adj && candidates.find(adj) == candidates.end())
{
int a0 = adj->V[0];
int a1 = adj->V[1];
int a2 = adj->V[2];
if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0)
{
// Point i is in the circumcircle.
candidates.insert(adj);
}
}
}
if (!polygon.Insert(tri->V[0], tri->V[1], tri->V[2]))
{
return false;
}
if (!mGraph.Remove(tri->V[0], tri->V[1], tri->V[2]))
{
return false;
}
}
// Get the boundary edges of the insertion polygon.
for (auto const& element : polygon.GetTriangles())
{
Triangle* tri = element.second.get();
for (int j = 0; j < 3; ++j)
{
if (!tri->T[j])
{
boundary.insert(EdgeKey<true>(tri->V[mIndex[j][0]], tri->V[mIndex[j][1]]));
}
}
}
return true;
}
bool Update(int i)
{
// The return value of mGraph.Insert(...) is nullptr if there was
// a failure to insert. The Update function will return 'false'
// when the insertion fails.
auto const& tmap = mGraph.GetTriangles();
Triangle* tri = tmap.begin()->second.get();
if (GetContainingTriangle(i, tri))
{
// The point is inside the convex hull. The insertion polygon
// contains only triangles in the current triangulation; the
// hull does not change.
// Use a depth-first search for those triangles whose
// circumcircles contain point i.
std::set<Triangle*> candidates;
candidates.insert(tri);
// Get the boundary of the insertion polygon C that contains
// the triangles whose circumcircles contain point i. Polygon
// C contains the point i.
std::set<EdgeKey<true>> boundary;
if (!GetAndRemoveInsertionPolygon(i, candidates, boundary))
{
return false;
}
// The insertion polygon consists of the triangles formed by
// point i and the faces of C.
for (auto const& key : boundary)
{
int v0 = key.V[0];
int v1 = key.V[1];
if (mQuery.ToLine(i, v0, v1) < 0)
{
if (!mGraph.Insert(i, v0, v1))
{
return false;
}
}
// else: Point i is on an edge of 'tri', so the
// subdivision has degenerate triangles. Ignore these.
}
}
else
{
// The point is outside the convex hull. The insertion
// polygon is formed by point i and any triangles in the
// current triangulation whose circumcircles contain point i.
// Locate the convex hull of the triangles.
std::set<EdgeKey<true>> hull;
for (auto const& element : tmap)
{
Triangle* t = element.second.get();
for (int j = 0; j < 3; ++j)
{
if (!t->T[j])
{
hull.insert(EdgeKey<true>(t->V[mIndex[j][0]], t->V[mIndex[j][1]]));
}
}
}
// Iterate over all the hull edges and use the ones visible to
// point i to locate the insertion polygon.
auto const& emap = mGraph.GetEdges();
std::set<Triangle*> candidates;
std::set<EdgeKey<true>> visible;
for (auto const& key : hull)
{
int v0 = key.V[0];
int v1 = key.V[1];
if (mQuery.ToLine(i, v0, v1) > 0)
{
auto iter = emap.find(EdgeKey<false>(v0, v1));
if (iter != emap.end() && iter->second->T[1] == nullptr)
{
auto adj = iter->second->T[0];
if (adj && candidates.find(adj) == candidates.end())
{
int a0 = adj->V[0];
int a1 = adj->V[1];
int a2 = adj->V[2];
if (mQuery.ToCircumcircle(i, a0, a1, a2) <= 0)
{
// Point i is in the circumcircle.
candidates.insert(adj);
}
else
{
// Point i is not in the circumcircle but
// the hull edge is visible.
visible.insert(key);
}
}
}
else
{
// This should be exposed, but because the class is
// deprecated, it is not exposed to preserve current
// behavior in client applications.
// LogError("Unexpected condition (ComputeType not exact?)");
return false;
}
}
}
// Get the boundary of the insertion subpolygon C that
// contains the triangles whose circumcircles contain point i.
std::set<EdgeKey<true>> boundary;
if (!GetAndRemoveInsertionPolygon(i, candidates, boundary))
{
return false;
}
// The insertion polygon P consists of the triangles formed by
// point i and the back edges of C *and* the visible edges of
// mGraph-C.
for (auto const& key : boundary)
{
int v0 = key.V[0];
int v1 = key.V[1];
if (mQuery.ToLine(i, v0, v1) < 0)
{
// This is a back edge of the boundary.
if (!mGraph.Insert(i, v0, v1))
{
return false;
}
}
}
for (auto const& key : visible)
{
if (!mGraph.Insert(i, key.V[1], key.V[0]))
{
return false;
}
}
}
return true;
}
// The epsilon value is used for fuzzy determination of intrinsic
// dimensionality. If the dimension is 0 or 1, the constructor
// returns early. The caller is responsible for retrieving the
// dimension and taking an alternate path should the dimension be
// smaller than 2. If the dimension is 0, the caller may as well
// treat all vertices[] as a single point, say, vertices[0]. If the
// dimension is 1, the caller can query for the approximating line and
// project vertices[] onto it for further processing.
InputType mEpsilon;
int mDimension;
Line2<InputType> mLine;
// The array of vertices used for geometric queries. If you want to
// be certain of a correct result, choose ComputeType to be BSNumber.
std::vector<Vector2<ComputeType>> mComputeVertices;
PrimalQuery2<ComputeType> mQuery;
// The graph information.
int mNumVertices;
int mNumUniqueVertices;
int mNumTriangles;
Vector2<InputType> const* mVertices;
VETManifoldMesh mGraph;
std::vector<int> mIndices;
std::vector<int> mAdjacencies;
// If a vertex occurs multiple times in the 'vertices' input to the
// constructor, the first processed occurrence of that vertex has an
// index stored in this array. If there are no duplicates, then
// mDuplicates[i] = i for all i.
struct ProcessedVertex
{
ProcessedVertex() = default;
ProcessedVertex(Vector2<InputType> const& inVertex, int inLocation)
:
vertex(inVertex),
location(inLocation)
{
}
bool operator<(ProcessedVertex const& v) const
{
return vertex < v.vertex;
}
Vector2<InputType> vertex;
int location;
};
std::vector<int> mDuplicates;
// Indexing for the vertices of the triangle adjacent to a vertex.
// The edge adjacent to vertex j is <mIndex[j][0], mIndex[j][1]> and
// is listed so that the triangle interior is to your left as you walk
// around the edges.
std::array<std::array<int, 2>, 3> mIndex;
};
}
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 Delaunay2<T>
{
public:
// The class is a functor to support computing the Delaunay
// triangulation of multiple data sets using the same class object.
virtual ~Delaunay2() = default;
Delaunay2()
:
mNumVertices(0),
mVertices(nullptr),
mIRVertices{},
mGraph(),
mDuplicates{},
mNumUniqueVertices(0),
mDimension(0),
mLine(Vector2<T>::Zero(), Vector2<T>::Zero()),
mNumTriangles(0),
mIndices{},
mAdjacencies{},
mIndex{ { { 0, 1 }, { 1, 2 }, { 2, 0 } } },
mQueryPoint(Vector2<T>::Zero()),
mIRQueryPoint(Vector2<InputRational>::Zero()),
mCRPool(maxNumCRPool)
{
static_assert(std::is_floating_point<T>::value,
"The input type must be float or double.");
}
// The input is the array of vertices whose Delaunay triangulation is
// required. The return value is 'true' if and only if the intrinsic
// dimension of the points is 2. If the intrinsic dimension is 1, the
// points lie exactly on a line which is then accessible via the
// accessor GetLine(). If the intrinsic dimension is 0, the points are
// all the same point.
bool operator()(std::vector<Vector2<T>> const& vertices)
{
return operator()(vertices.size(), vertices.data());
}
bool operator()(size_t numVertices, Vector2<T> const* vertices)
{
// Initialize values in case they were set by a previous call
// to operator()(...).
LogAssert(numVertices > 0 && vertices != nullptr, "Invalid argument.");
mNumVertices = numVertices;
mVertices = vertices;
mIRVertices.clear();
mDuplicates.clear();
mLine.origin = Vector2<T>::Zero();
mLine.direction = Vector2<T>::Zero();
mNumUniqueVertices = 0;
mNumTriangles = 0;
mGraph.Clear();
mIndices.clear();
mAdjacencies.clear();
mQueryPoint = Vector2<T>::Zero();
mIRQueryPoint = Vector2<InputRational>::Zero();
// Compute the intrinsic dimension and return early if that
// dimension is 0 or 1.
IntrinsicsVector2<T> info(static_cast<int>(mNumVertices), mVertices, static_cast<T>(0));
if (info.dimension == 0)
{
// The vertices are the same point.
mDimension = 0;
mLine.origin = info.origin;
return false;
}
if (info.dimension == 1)
{
// The vertices are collinear.
mDimension = 1;
mLine.origin = info.origin;
mLine.direction = info.direction[0];
return false;
}
// The vertices necessarily will have a triangulation.
mDimension = 2;
// Convert the floating-point inputs to rational type.
mIRVertices.resize(mNumVertices);
for (size_t i = 0; i < mNumVertices; ++i)
{
mIRVertices[i][0] = mVertices[i][0];
mIRVertices[i][1] = mVertices[i][1];
}
// Assume initially the vertices are unique. If duplicates are
// found during the Delaunay update, mDuplicates[] will be
// modified accordingly.
mDuplicates.resize(mNumVertices);
std::iota(mDuplicates.begin(), mDuplicates.end(), 0);
// Insert the nondegenerate triangle constructed by the call to
// GetInformation. This is necessary for the circumcircle
// visibility algorithm to work correctly.
if (!info.extremeCCW)
{
std::swap(info.extreme[1], info.extreme[2]);
}
auto inserted = mGraph.Insert(info.extreme[0], info.extreme[1], info.extreme[2]);
LogAssert(inserted != nullptr, "The triangle should not be degenerate.");
// Incrementally update the triangulation. The set of processed
// points is maintained to eliminate duplicates.
ProcessedVertexSet processed;
for (size_t i = 0; i < 3; ++i)
{
int32_t j = info.extreme[i];
processed.insert(ProcessedVertex(mVertices[j], j));
mDuplicates[j] = j;
}
for (size_t i = 0; i < mNumVertices; ++i)
{
ProcessedVertex v(mVertices[i], i);
auto iter = processed.find(v);
if (iter == processed.end())
{
Update(i);
processed.insert(v);
mDuplicates[i] = i;
}
else
{
mDuplicates[i] = iter->location;
}
}
mNumUniqueVertices = processed.size();
// Assign integer values to the triangles for use by the caller
// and copy the triangle information to compact arrays mIndices
// and mAdjacencies.
UpdateIndicesAdjacencies();
return true;
}
// Dimensional information. If GetDimension() returns 1, the points
// lie on a line P+t*D. You can sort these if you need a polyline
// output by projecting onto the line each vertex X = P+t*D, where
// t = Dot(D,X-P).
inline size_t GetDimension() const
{
return mDimension;
}
inline Line2<T> const& GetLine() const
{
return mLine;
}
// Member access.
inline size_t GetNumVertices() const
{
return mIRVertices.size();
}
inline Vector2<T> const* GetVertices() const
{
return mVertices;
}
inline size_t GetNumUniqueVertices() const
{
return mNumUniqueVertices;
}
// If 'vertices' has no duplicates, GetDuplicates()[i] = i for all i.
// If vertices[i] is the first occurrence of a vertex and if
// vertices[j] is found later, then GetDuplicates()[j] = i.
inline std::vector<size_t> const& GetDuplicates() const
{
return mDuplicates;
}
inline size_t GetNumTriangles() const
{
return mNumTriangles;
}
inline ETManifoldMesh const& GetGraph() const
{
return mGraph;
}
inline std::vector<int32_t> const& GetIndices() const
{
return mIndices;
}
inline std::vector<int32_t> const& GetAdjacencies() const
{
return mAdjacencies;
}
// Locate those triangle edges that do not share other triangles. The
// returned array has hull.size() = 2*numEdges, each pair representing
// an edge. The edges are not ordered, but the pair of vertices for
// an edge is ordered so that they conform to a counterclockwise
// traversal of the hull. The return value is 'true' if and only if
// the dimension is 2.
bool GetHull(std::vector<size_t>& hull) const
{
if (mDimension == 2)
{
// Count the number of edges that are not shared by two
// triangles.
size_t numEdges = 0;
for (auto adj : mAdjacencies)
{
if (adj == -1)
{
++numEdges;
}
}
if (numEdges > 0)
{
// Enumerate the edges.
hull.resize(2 * numEdges);
size_t current = 0, i = 0;
for (auto adj : mAdjacencies)
{
if (adj == -1)
{
size_t tri = i / 3, j = i % 3;
hull[current++] = mIndices[3 * tri + j];
hull[current++] = mIndices[3 * tri + ((j + 1) % 3)];
}
++i;
}
return true;
}
else
{
LogError("Unexpected condition. There must be at least one triangle.");
}
}
else
{
LogError("The dimension must be 2.");
}
}
// Copy Delaunay triangles to compact arrays mIndices and
// mAdjacencies. The array information is accessible via the
// functions GetIndices(int32_t, std::array<int32_t, 3>&) and
// GetAdjacencies(int32_t, std::array<int32_t, 3>&).
void UpdateIndicesAdjacencies()
{
// Assign integer values to the triangles.
auto const& tmap = mGraph.GetTriangles();
std::unordered_map<Triangle*, int32_t> permute;
int32_t i = -1;
permute[nullptr] = i++;
for (auto const& element : tmap)
{
permute[element.second.get()] = i++;
}
mNumTriangles = tmap.size();
size_t numindices = 3 * mNumTriangles;
if (numindices > 0)
{
mIndices.resize(numindices);
mAdjacencies.resize(numindices);
i = 0;
for (auto const& element : tmap)
{
Triangle* tri = element.second.get();
for (size_t j = 0; j < 3; ++j, ++i)
{
mIndices[i] = tri->V[j];
mAdjacencies[i] = permute[tri->T[j]];
}
}
}
}
// Get the vertex indices for triangle t. The function returns 'true'
// when the dimension is 2 and t is a valid triangle index, in which
// case the vertices are valid; otherwise, the function returns
// 'false' and the vertices are invalid.
bool GetIndices(size_t t, std::array<int32_t, 3>& indices) const
{
if (mDimension == 2)
{
size_t const numTriangles = mIndices.size() / 3;
if (t < numTriangles)
{
indices[0] = mIndices[3 * t];
indices[1] = mIndices[3 * t + 1];
indices[2] = mIndices[3 * t + 2];
return true;
}
}
return false;
}
// Get the indices for triangles adjacent to triangle t. The function
// returns 'true' when the dimension is 2 and if t is a valid triangle
// index, in which case the adjacencies are valid; otherwise, the
// function returns 'false' and the adjacencies are invalid.
bool GetAdjacencies(size_t t, std::array<int32_t, 3>& adjacencies) const
{
if (mDimension == 2)
{
size_t const numTriangles = mIndices.size() / 3;
if (t < numTriangles)
{
adjacencies[0] = mAdjacencies[3 * t];
adjacencies[1] = mAdjacencies[3 * t + 1];
adjacencies[2] = mAdjacencies[3 * t + 2];
return true;
}
}
return false;
}
// Support for searching the triangulation for a triangle that
// contains a point. If there is a containing triangle, the returned
// value is a triangle index t with 0 <= t < GetNumTriangles(). If
// there is not a containing triangle, -1 is returned. The
// computations are performed using exact rational arithmetic.
//
// The SearchInfo input stores information about the triangle search
// when looking for the triangle (if any) that contains p. The first
// triangle searched is 'initialTriangle'. On return 'path' stores
// those (ordered) triangle indices visited during the search. The
// last visited triangle has index 'finalTriangle and vertex indices
// 'finalV[0,1,2]', stored in counterclockwise order. The last edge
// of the search is <finalV[0],finalV[1]>. For spatially coherent
// inputs p for numerous calls to this function, you will want to
// specify 'finalTriangle' from the previous call as 'initialTriangle'
// for the next call, which should reduce search times.
static size_t constexpr negOne = std::numeric_limits<size_t>::max();
struct SearchInfo
{
SearchInfo()
:
initialTriangle(negOne),
numPath(0),
finalTriangle(0),
finalV{ 0, 0, 0 },
path{}
{
}
size_t initialTriangle;
size_t numPath;
size_t finalTriangle;
std::array<int32_t, 3> finalV;
std::vector<size_t> path;
};
// If the point is in a triangle, the return value is the index of the
// triangle. If the point is not in a triangle, the return value is
// std::numeric_limits<size_t>::max().
size_t GetContainingTriangle(Vector2<T> const& inP, SearchInfo& info) const
{
LogAssert(mDimension == 2, "Invalid dimension for triangle search.");
mQueryPoint = inP;
mIRQueryPoint = { inP[0], inP[1] };
size_t const numTriangles = mIndices.size() / 3;
info.path.resize(numTriangles);
info.numPath = 0;
size_t triangle;
if (info.initialTriangle < numTriangles)
{
triangle = info.initialTriangle;
}
else
{
info.initialTriangle = 0;
triangle = 0;
}
// Use triangle edges as binary separating lines.
int32_t adjacent;
for (size_t i = 0; i < numTriangles; ++i)
{
size_t ibase = 3 * triangle;
int32_t const* v = &mIndices[ibase];
info.path[info.numPath++] = triangle;
info.finalTriangle = triangle;
info.finalV[0] = v[0];
info.finalV[1] = v[1];
info.finalV[2] = v[2];
if (ToLine(negOne, v[0], v[1]) > 0)
{
adjacent = mAdjacencies[ibase];
if (adjacent == -1)
{
info.finalV[0] = v[0];
info.finalV[1] = v[1];
info.finalV[2] = v[2];
return negOne;
}
triangle = static_cast<size_t>(adjacent);
continue;
}
if (ToLine(negOne, v[1], v[2]) > 0)
{
adjacent = mAdjacencies[ibase + 1];
if (adjacent == -1)
{
info.finalV[0] = v[1];
info.finalV[1] = v[2];
info.finalV[2] = v[0];
return negOne;
}
triangle = static_cast<size_t>(adjacent);
continue;
}
if (ToLine(negOne, v[2], v[0]) > 0)
{
adjacent = mAdjacencies[ibase + 2];
if (adjacent == -1)
{
info.finalV[0] = v[2];
info.finalV[1] = v[0];
info.finalV[2] = v[1];
return negOne;
}
triangle = static_cast<size_t>(adjacent);
continue;
}
return triangle;
}
LogError("Unexpected termination of loop while searching for a triangle.");
}
protected:
// The type of the read-only input vertices[] when converted for
// rational arithmetic.
static int32_t constexpr InputNumWords = std::is_same<T, float>::value ? 2 : 4;
using InputRational = BSNumber<UIntegerFP32<InputNumWords>>;
// The vector of vertices used for geometric queries. The input
// vertices are read-only, so we can represent them by the type
// InputRational.
size_t mNumVertices;
Vector2<T> const* mVertices;
std::vector<Vector2<InputRational>> mIRVertices;
VETManifoldMesh mGraph;
private:
// The compute type used for exact sign classification.
static int32_t constexpr ComputeNumWords = std::is_same<T, float>::value ? 36 : 264;
using ComputeRational = BSNumber<UIntegerFP32<ComputeNumWords>>;
// Convenient renaming.
using Triangle = ETManifoldMesh::Triangle;
struct ProcessedVertex
{
ProcessedVertex() = default;
ProcessedVertex(Vector2<T> const& inVertex, size_t inLocation)
:
vertex(inVertex),
location(inLocation)
{
}
// Support for hashing in std::unordered_set<>. The first
// operator() is the hash function. The second operator() is
// the equality comparison used for elements in the same bucket.
std::size_t operator()(ProcessedVertex const& v) const
{
return HashValue(v.vertex[0], v.vertex[1], v.location);
}
bool operator()(ProcessedVertex const& v0, ProcessedVertex const& v1) const
{
return v0.vertex == v1.vertex && v0.location == v1.location;
}
Vector2<T> vertex;
size_t location;
};
using ProcessedVertexSet = std::unordered_set<
ProcessedVertex, ProcessedVertex, ProcessedVertex>;
using DirectedEdgeKeySet = std::unordered_set<
EdgeKey<true>, EdgeKey<true>, EdgeKey<true>>;
using TrianglePtrSet = std::unordered_set<Triangle*>;
static ComputeRational const& Copy(InputRational const& source,
ComputeRational& target)
{
target.SetSign(source.GetSign());
target.SetBiasedExponent(source.GetBiasedExponent());
target.GetUInteger().CopyFrom(source.GetUInteger());
return target;
}
// 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 = (pIndex != negOne ? mVertices[pIndex] : mQueryPoint);
Vector2<T> const& inV0 = mVertices[v0Index];
Vector2<T> const& inV1 = 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 = (pIndex != negOne ? mIRVertices[pIndex] : mIRQueryPoint);
Vector2<InputRational> const& irV0 = mIRVertices[v0Index];
Vector2<InputRational> const& irV1 = 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();
}
// For a triangle with counterclockwise vertices V0, V1 and V2 and a
// query point P, ToCircumcircle returns
// +1, P outside circumcircle of triangle
// -1, P inside circumcircle of triangle
// 0, P on circumcircle of triangle
int32_t ToCircumcircle(size_t pIndex, size_t v0Index, size_t v1Index, size_t v2Index) const
{
// The expression tree has 43 nodes consisting of 8 input
// leaves and 35 compute nodes.
// Use interval arithmetic to determine the sign if possible.
auto const& inP = (pIndex != negOne ? mVertices[pIndex] : mQueryPoint);
Vector2<T> const& inV0 = mVertices[v0Index];
Vector2<T> const& inV1 = mVertices[v1Index];
Vector2<T> const& inV2 = mVertices[v2Index];
auto x0 = SWInterval<T>::Sub(inV0[0], inP[0]);
auto y0 = SWInterval<T>::Sub(inV0[1], inP[1]);
auto s00 = SWInterval<T>::Add(inV0[0], inP[0]);
auto s01 = SWInterval<T>::Add(inV0[1], inP[1]);
auto x1 = SWInterval<T>::Sub(inV1[0], inP[0]);
auto y1 = SWInterval<T>::Sub(inV1[1], inP[1]);
auto s10 = SWInterval<T>::Add(inV1[0], inP[0]);
auto s11 = SWInterval<T>::Add(inV1[1], inP[1]);
auto x2 = SWInterval<T>::Sub(inV2[0], inP[0]);
auto y2 = SWInterval<T>::Sub(inV2[1], inP[1]);
auto s20 = SWInterval<T>::Add(inV2[0], inP[0]);
auto s21 = SWInterval<T>::Add(inV2[1], inP[1]);
auto t00 = s00 * x0;
auto t01 = s01 * y0;
auto t10 = s10 * x1;
auto t11 = s11 * y1;
auto t20 = s20 * x2;
auto t21 = s21 * y2;
auto z0 = t00 + t01;
auto z1 = t10 + t11;
auto z2 = t20 + t21;
auto y0z1 = y0 * z1;
auto y0z2 = y0 * z2;
auto y1z0 = y1 * z0;
auto y1z2 = y1 * z2;
auto y2z0 = y2 * z0;
auto y2z1 = y2 * z1;
auto c0 = y1z2 - y2z1;
auto c1 = y2z0 - y0z2;
auto c2 = y0z1 - y1z0;
auto x0c0 = x0 * c0;
auto x1c1 = x1 * c1;
auto x2c2 = x2 * c2;
auto det = x0c0 + x1c1 + x2c2;
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 = (pIndex != negOne ? mIRVertices[pIndex] : mIRQueryPoint);
Vector2<InputRational> const& irV0 = mIRVertices[v0Index];
Vector2<InputRational> const& irV1 = mIRVertices[v1Index];
Vector2<InputRational> const& irV2 = mIRVertices[v2Index];
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 const& crV20 = Copy(irV2[0], mCRPool[6]);
auto const& crV21 = Copy(irV2[1], mCRPool[7]);
auto& crX0 = mCRPool[8];
auto& crY0 = mCRPool[9];
auto& crS00 = mCRPool[10];
auto& crS01 = mCRPool[11];
auto& crT00 = mCRPool[12];
auto& crT01 = mCRPool[13];
auto& crZ0 = mCRPool[14];
auto& crX1 = mCRPool[15];
auto& crY1 = mCRPool[16];
auto& crS10 = mCRPool[17];
auto& crS11 = mCRPool[18];
auto& crT10 = mCRPool[19];
auto& crT11 = mCRPool[20];
auto& crZ1 = mCRPool[21];
auto& crX2 = mCRPool[22];
auto& crY2 = mCRPool[23];
auto& crS20 = mCRPool[24];
auto& crS21 = mCRPool[25];
auto& crT20 = mCRPool[26];
auto& crT21 = mCRPool[27];
auto& crZ2 = mCRPool[28];
auto& crY0Z1 = mCRPool[29];
auto& crY0Z2 = mCRPool[30];
auto& crY1Z0 = mCRPool[31];
auto& crY1Z2 = mCRPool[32];
auto& crY2Z0 = mCRPool[33];
auto& crY2Z1 = mCRPool[34];
auto& crC0 = mCRPool[35];
auto& crC1 = mCRPool[36];
auto& crC2 = mCRPool[37];
auto& crX0C0 = mCRPool[38];
auto& crX1C1 = mCRPool[39];
auto& crX2C2 = mCRPool[40];
auto& crTerm = mCRPool[41];
auto& crDet = mCRPool[42];
// Evaluate the expression tree.
crX0 = crV00 - crP0;
crY0 = crV01 - crP1;
crS00 = crV00 + crP0;
crS01 = crV01 + crP1;
crT00 = crS00 * crX0;
crT01 = crS01 * crY0;
crZ0 = crT00 + crT01;
crX1 = crV10 - crP0;
crY1 = crV11 - crP1;
crS10 = crV10 + crP0;
crS11 = crV11 + crP1;
crT10 = crS10 * crX1;
crT11 = crS11 * crY1;
crZ1 = crT10 + crT11;
crX2 = crV20 - crP0;
crY2 = crV21 - crP1;
crS20 = crV20 + crP0;
crS21 = crV21 + crP1;
crT20 = crS20 * crX2;
crT21 = crS21 * crY2;
crZ2 = crT20 + crT21;
crY0Z1 = crY0 * crZ1;
crY0Z2 = crY0 * crZ2;
crY1Z0 = crY1 * crZ0;
crY1Z2 = crY1 * crZ2;
crY2Z0 = crY2 * crZ0;
crY2Z1 = crY2 * crZ1;
crC0 = crY1Z2 - crY2Z1;
crC1 = crY2Z0 - crY0Z2;
crC2 = crY0Z1 - crY1Z0;
crX0C0 = crX0 * crC0;
crX1C1 = crX1 * crC1;
crX2C2 = crX2 * crC2;
crTerm = crX0C0 + crX1C1;
crDet = crTerm + crX2C2;
return -crDet.GetSign();
}
bool GetContainingTriangle(size_t pIndex, Triangle*& tri) const
{
size_t const numTriangles = mGraph.GetTriangles().size();
for (size_t t = 0; t < numTriangles; ++t)
{
size_t j;
for (j = 0; j < 3; ++j)
{
size_t v0Index = static_cast<size_t>(tri->V[mIndex[j][0]]);
size_t v1Index = static_cast<size_t>(tri->V[mIndex[j][1]]);
if (ToLine(pIndex, v0Index, v1Index) > 0)
{
// Point i sees edge <v0,v1> from outside the triangle.
auto adjTri = tri->T[j];
if (adjTri)
{
// Traverse to the triangle sharing the face.
tri = adjTri;
break;
}
else
{
// We reached a hull edge, so the point is outside
// the hull.
return false;
}
}
}
if (j == 3)
{
// The point is inside all four edges, so the point is
// inside a triangle.
return true;
}
}
LogError("Unexpected termination of loop while searching for a triangle.");
}
void GetAndRemoveInsertionPolygon(size_t pIndex,
TrianglePtrSet& candidates, DirectedEdgeKeySet& boundary)
{
// Locate the triangles that make up the insertion polygon.
ETManifoldMesh polygon;
while (candidates.size() > 0)
{
Triangle* tri = *candidates.begin();
candidates.erase(candidates.begin());
for (size_t j = 0; j < 3; ++j)
{
auto adj = tri->T[j];
if (adj && candidates.find(adj) == candidates.end())
{
size_t v0Index = adj->V[0];
size_t v1Index = adj->V[1];
size_t v2Index = adj->V[2];
if (ToCircumcircle(pIndex, v0Index, v1Index, v2Index) <= 0)
{
// Point P is in the circumcircle.
candidates.insert(adj);
}
}
}
auto inserted = polygon.Insert(tri->V[0], tri->V[1], tri->V[2]);
LogAssert(inserted != nullptr, "Unexpected insertion failure.");
auto removed = mGraph.Remove(tri->V[0], tri->V[1], tri->V[2]);
LogAssert(removed, "Unexpected removal failure.");
}
// Get the boundary edges of the insertion polygon.
for (auto const& element : polygon.GetTriangles())
{
Triangle* tri = element.second.get();
for (size_t j = 0; j < 3; ++j)
{
if (!tri->T[j])
{
EdgeKey<true> ekey(tri->V[mIndex[j][0]], tri->V[mIndex[j][1]]);
boundary.insert(ekey);
}
}
}
}
void Update(size_t pIndex)
{
auto const& tmap = mGraph.GetTriangles();
Triangle* tri = tmap.begin()->second.get();
if (GetContainingTriangle(pIndex, tri))
{
// The point is inside the convex hull. The insertion polygon
// contains only triangles in the current triangulation; the
// hull does not change.
// Use a depth-first search for those triangles whose
// circumcircles contain point P.
TrianglePtrSet candidates;
candidates.insert(tri);
// Get the boundary of the insertion polygon C that contains
// the triangles whose circumcircles contain point P. Polygon
// Polygon C contains this point.
DirectedEdgeKeySet boundary;
GetAndRemoveInsertionPolygon(pIndex, candidates, boundary);
// The insertion polygon consists of the triangles formed by
// point P and the faces of C.
for (auto const& key : boundary)
{
size_t v0Index = static_cast<size_t>(key.V[0]);
size_t v1Index = static_cast<size_t>(key.V[1]);
if (ToLine(pIndex, v0Index, v1Index) < 0)
{
auto inserted = mGraph.Insert(static_cast<int32_t>(pIndex),
key.V[0], key.V[1]);
LogAssert(inserted != nullptr, "Unexpected insertion failure.");
}
}
}
else
{
// The point is outside the convex hull. The insertion
// polygon is formed by point P and any triangles in the
// current triangulation whose circumcircles contain point P.
// Locate the convex hull of the triangles.
DirectedEdgeKeySet hull;
for (auto const& element : tmap)
{
Triangle* t = element.second.get();
for (size_t j = 0; j < 3; ++j)
{
if (!t->T[j])
{
hull.insert(EdgeKey<true>(t->V[mIndex[j][0]], t->V[mIndex[j][1]]));
}
}
}
// Iterate over all the hull edges and use the ones visible to
// point P to locate the insertion polygon.
auto const& emap = mGraph.GetEdges();
TrianglePtrSet candidates;
DirectedEdgeKeySet visible;
for (auto const& key : hull)
{
size_t v0Index = static_cast<size_t>(key.V[0]);
size_t v1Index = static_cast<size_t>(key.V[1]);
if (ToLine(pIndex, v0Index, v1Index) > 0)
{
auto iter = emap.find(EdgeKey<false>(key.V[0], key.V[1]));
if (iter != emap.end() && iter->second->T[1] == nullptr)
{
auto adj = iter->second->T[0];
if (adj && candidates.find(adj) == candidates.end())
{
size_t a0Index = static_cast<size_t>(adj->V[0]);
size_t a1Index = static_cast<size_t>(adj->V[1]);
size_t a2Index = static_cast<size_t>(adj->V[2]);
if (ToCircumcircle(pIndex, a0Index, a1Index, a2Index) <= 0)
{
// Point P is in the circumcircle.
candidates.insert(adj);
}
else
{
// Point P is not in the circumcircle but
// the hull edge is visible.
visible.insert(key);
}
}
}
else
{
LogError("This condition should not occur for rational arithmetic.");
}
}
}
// Get the boundary of the insertion subpolygon C that
// contains the triangles whose circumcircles contain point P.
DirectedEdgeKeySet boundary;
GetAndRemoveInsertionPolygon(pIndex, candidates, boundary);
// The insertion polygon P consists of the triangles formed by
// point i and the back edges of C and by the visible edges of
// mGraph-C.
for (auto const& key : boundary)
{
size_t v0Index = static_cast<size_t>(key.V[0]);
size_t v1Index = static_cast<size_t>(key.V[1]);
if (ToLine(pIndex, v0Index, v1Index) < 0)
{
// This is a back edge of the boundary.
auto inserted = mGraph.Insert(static_cast<int32_t>(pIndex),
key.V[0], key.V[1]);
LogAssert(inserted != nullptr, "Unexpected insertion failure.");
}
}
for (auto const& key : visible)
{
auto inserted = mGraph.Insert(static_cast<int32_t>(pIndex),
key.V[1], key.V[0]);
LogAssert(inserted != nullptr, "Unexpected insertion failure.");
}
}
}
// If a vertex occurs multiple times in the 'vertices' input to the
// constructor, the first processed occurrence of that vertex has an
// index stored in this array. If there are no duplicates, then
// mDuplicates[i] = i for all i.
std::vector<size_t> mDuplicates;
size_t mNumUniqueVertices;
// If the intrinsic dimension of the input vertices is 0 or 1, the
// constructor returns early. The caller is responsible for retrieving
// the dimension and taking an alternate path should the dimension be
// smaller than 2. If the dimension is 0, all vertices are the same.
// If the dimension is 1, the vertices lie on a line, in which case
// the caller can project vertices[] onto the line for further
// processing.
size_t mDimension;
Line2<T> mLine;
// These are computed by UpdateIndicesAdjacencies(). They are used
// for point-containment queries in the triangle mesh.
size_t mNumTriangles;
std::vector<int32_t> mIndices;
std::vector<int32_t> mAdjacencies;
private:
// Indexing for the vertices of the triangle adjacent to a vertex.
// The edge adjacent to vertex j is <mIndex[j][0], mIndex[j][1]> and
// is listed so that the triangle interior is to your left as you walk
// around the edges.
std::array<std::array<size_t, 2>, 3> const mIndex;
// The query point for Update, GetContainingTriangle and
// GetAndRemoveInsertionPolygon when the point is not an input vertex
// to the constructor. ToLine and ToCircumcircle are passed indices
// into the vertex array. When the vertex is valid, mVertices[] and
// mCRVertices[] are used for lookups. When the vertex is 'negOne', the
// query point is used for lookups.
mutable Vector2<T> mQueryPoint;
mutable Vector2<InputRational> mIRQueryPoint;
// Sufficient storage for the expression trees related to computing
// the exact signs in ToLine(...) and ToCircumcircle(...).
static size_t constexpr maxNumCRPool = 43;
mutable std::vector<ComputeRational> mCRPool;
};
}