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.
 
 
 
 
 
 

287 lines
12 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: 5.3.2020.11.06
#pragma once
#include <Mathematics/Vector2.h>
#include <cstdint>
#include <limits>
#include <memory>
#include <stack>
#include <vector>
namespace gte
{
// These classes are used by class TriangulateEC (triangulation based on
// ear clipping) and class TriangulateCDT (triangulation based on
// Constrained Delaunay triangulation). The PolygonTree class used to be
// the nested class Tree in those classes, but it has been factored out to
// allow applications to use either triangulator without having to
// duplicate the trees.
//
// NOTE: The polygon member does not duplicate endpoints. For example,
// if P[] are the point locations and the polygon is a triangle with
// counterclockwise ordering, <P[i0],P[i1],P[i2]>, then
// polygon = {i0,i1,i2}. The implication is that there are 3 directed
// edges: {P[i0],P[i1]}, {P[i1],P[i2]} and {P[i2],P[i0].
//
// Eventually, the PolygonTreeEx struct will replace PolygonTree because
// 1. The algorithms can be rewritten not to depend on the alternating
// winding order between parent and child.
// 2. The triangulation is explicitly stored in the tree nodes and can
// support point-in-polygon tree queries (In the tree? Which polygon
// contains the point?).
// 3. The polygon trees can be built not to use std::shared_ptr, making
// the trees more compact by using std::vector<PolygonTree> vpt. The
// ordering of the tree nodes must be that implied by a breadth-first
// search.
// A tree of nested polygons. The root node corresponds to an outer
// polygon. The children of the root correspond to inner polygons,
// which polygons strictly contained in the outer polygon. Each inner
// polygon may itself contain an outer polygon which in turn can
// contain inner polygons, thus leading to a hierarchy of polygons.
// The outer polygons have vertices listed in counterclockwise order.
// The inner polygons have vertices listed in clockwise order.
class PolygonTree
{
public:
PolygonTree()
:
polygon{},
child{}
{
}
std::vector<int> polygon;
std::vector<std::shared_ptr<PolygonTree>> child;
};
// A tree of nested polygons with extra information about the polygon.
// The tree can be stored as: std::vector<PolygonTree> tree(numNodes).
// The point locations are specified separately to the triangulators.
//
// The chirality (winding ordering of the polygon) is set to +1 for a
// counterclockwise-ordered polygon or -1 for a clockwise-oriented
// polygon.
//
// The triangulation is computed by the triangulators and explicitly
// stored per tree node.
//
// The element node[0] is the root of the tree with node[0].parent = -1.
// If node[0] has C children, then node[0].minChild = 1 and
// node[0].supChild = 1 + C. Generally, node[i] is a node with parent
// node[p], where p = node[i].parent, and children node[c], where
// node[i].minChild <= c < node[i].supChild. If node[i].minChild >=
// node[i].supChild, the node has no children.
class PolygonTreeEx
{
public:
class Node
{
public:
Node()
:
polygon{},
chirality(0),
triangulation{},
self(0),
parent(0),
minChild(0),
supChild(0)
{
}
std::vector<int> polygon;
int64_t chirality;
std::vector<std::array<int, 3>> triangulation;
size_t self, parent, minChild, supChild;
};
// The nodes of the polygon tree, organized based on a breadth-first
// traversal of the tree.
std::vector<Node> nodes;
// These members support TriangulateCDT. The *NodeIndices members
// store the indices into 'nodes[]' for the triangles in the
// *Triangles members. For example, the triangle interiorTriangles[t]
// comes from nodes[interiorNodes[t]].
// The triangles in the polygon tree that cover each region bounded
// by an outer polygon and its contained inner polygons. This set
// is the equivalent of the output of TriangulateEC that uses ear
// clipping.
std::vector<std::array<int, 3>> interiorTriangles;
std::vector<size_t> interiorNodeIndices;
// The triangles in the polygon tree that cover each region bounded
// by an inner polygon and its contained outer polygons.
std::vector<std::array<int, 3>> exteriorTriangles;
std::vector<size_t> exteriorNodeIndices;
// The triangles inside the polygon tree.
// insideTriangles = interiorTriangle + exteriorTriangles
std::vector<std::array<int, 3>> insideTriangles;
std::vector<size_t> insideNodeIndices;
// The triangles inside the convex hull of the Delaunay triangles but
// outside the polygon tree. These triangles are not associated with
// any 'nodes[]' element.
std::vector<std::array<int, 3>> outsideTriangles;
// All the triangles:
// allTriangles = insideTriangles + outsideTriangles.
std::vector<std::array<int, 3>> allTriangles;
public:
// Point-containment queries.
// Search the polygon tree for the triangle that contains 'test'. If
// there is such a triangle, the returned pair (nIndex,tIndex) states
// that the triangle is nodes[nIndex].triangulation[tIndex]. If there
// is no such triangle, the returned pair is (smax,smax) where
// smax = std::numeric_limits<size_t>::max(). The function is
// naturally recursive, but simulated recursion is used to avoid a
// large program stack by instead using the heap. A typical call is
// PolygonTreeEx tree = <some tree>;
// std::vector<Vector2<T>> points = <some vector of points>;
// Vector2<T> test = <some point>;
// std::pair<size_t, size_t> result;
// result = tree.GetContainingTriangle(test, points);
template <typename T>
std::pair<size_t, size_t> GetContainingTriangle(Vector2<T> const& test,
Vector2<T> const* points)
{
size_t constexpr smax = std::numeric_limits<size_t>::max();
std::pair<size_t, size_t> result = std::make_pair(smax, smax);
std::stack<size_t> stack;
stack.push(0);
while (stack.size() > 0 && result.first == smax)
{
auto nIndex = stack.top();
stack.pop();
auto const& node = nodes[nIndex];
for (size_t c = node.minChild; c < node.supChild; ++c)
{
stack.push(c);
}
for (size_t tIndex = 0; tIndex < node.triangulation.size(); ++tIndex)
{
if (PointInTriangle(test, node.chirality, node.triangulation[tIndex], points))
{
result = std::make_pair(nIndex, tIndex);
break;
}
}
}
return result;
}
// Search the triangles for the triangle that contains 'test'. If
// there is such a triangle, the returned pair (nIndex,tIndex) states
// that the triangle is nodes[nIndex].triangulation[tIndex]. If there
// is no such triangle, the returned pair is (smax,smax) where
// smax = std::numeric_limits<size_t>::max(). The function uses a
// linear search of the input triangles. Some typical calls are
// PolygonTreeEx tree = <some tree>;
// std::vector<Vector2<T>> points = <some vector of points>;
// Vector2<T> test = <some point>;
// std::pair<size_t, size_t> result;
// result = tree.GetContainingTriangle(test, tree.insideTriangles,
// tree.insideNodeIndices, points);
// result = tree.GetContainingTriangle(test, tree.interiorTriangles,
// tree.interiorNodeIndices, points);
// result = tree.GetContainingTriangle(test, tree.exteriorTriangles,
// tree.exteriorIndices, points);
template <typename T>
std::pair<size_t, size_t> GetContainingTriangle(Vector2<T> const& test,
std::vector<std::array<int, 3>> const& triangles,
std::vector<size_t> const& nodeIndices,
Vector2<T> const* points)
{
LogAssert(triangles.size() == nodeIndices.size(), "Invalid argument.");
size_t constexpr smax = std::numeric_limits<size_t>::max();
std::pair<size_t, size_t> result = std::make_pair(smax, smax);
for (size_t tIndex = 0; tIndex < triangles.size(); ++tIndex)
{
size_t const nIndex = nodeIndices[tIndex];
auto const& node = nodes[nIndex];
if (PointInTriangle(test, node.chirality, triangles[tIndex], points))
{
result = std::make_pair(nIndex, tIndex);
break;
}
}
return result;
}
// Search the triangles for the triangle that contains 'test'. If
// there is such a triangle, the returned t-value is in the range
// 0 <= t < triangles.size(); otherwise, smax is returned where
// smax = std::numeric_limits<size_t>::max(). The function uses a
// linear search of the input triangles. No information is available
// about the 'nodes[]' element corresponding to the containing
// triangle of the test point. Typical calls are
// PolygonTreeEx tree = <some tree>;
// std::vector<Vector2<T>> points = <some vector of points>;
// Vector2<T> test = <some point>;
// size_t resultInt, resultExt, resultOut;
// resultInt = PolygonTreeEx::GetContainingTriangle(test,
// tree.interiorTriangles, +1, points);
// resultExt = PolygonTreeEx::GetContainingTriangle(test,
// tree.exteriorTriangles, -1, points);
// resultOut = PolygonTreeEx::GetContainingTriangle(test,
// tree.outsideTriangles, +1, points);
template <typename T>
size_t GetContainingTriangle(Vector2<T> const& test,
std::vector<std::array<int, 3>> const& triangles, int64_t chirality,
Vector2<T> const* points)
{
size_t result = std::numeric_limits<size_t>::max();
for (size_t tIndex = 0; tIndex < triangles.size(); ++tIndex)
{
if (PointInTriangle(test, chirality, triangles[tIndex], points))
{
result = tIndex;
break;
}
}
return result;
}
private:
// Determine whether 'test' is inside the triangle whose vertices
// are points[triangle[0]], points[triangle[1]], points[triangle[2].
// If the points are counterclockwise ordered, set 'chirality' to +1.
// If the points are clockwise ordered, set 'chirality' to -1.
template <typename T>
static bool PointInTriangle(Vector2<T> const& test, int64_t chirality,
std::array<int, 3> const& triangle, Vector2<T> const* points)
{
T const zero = static_cast<T>(0);
T const sign = static_cast<T>(chirality);
for (int i1 = 0, i0 = 2; i1 < 3; i0 = i1++)
{
T nx = points[triangle[i1]][1] - points[triangle[i0]][1];
T ny = points[triangle[i0]][0] - points[triangle[i1]][0];
T dx = test[0] - points[triangle[i0]][0];
T dy = test[1] - points[triangle[i0]][1];
T sdot = sign * (nx * dx + ny * dy);
if (sdot > zero)
{
return false;
}
}
return true;
}
};
}