// 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 #include #include #include #include #include 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, , 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 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 polygon; std::vector> child; }; // A tree of nested polygons with extra information about the polygon. // The tree can be stored as: std::vector 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 polygon; int64_t chirality; std::vector> 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 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> interiorTriangles; std::vector interiorNodeIndices; // The triangles in the polygon tree that cover each region bounded // by an inner polygon and its contained outer polygons. std::vector> exteriorTriangles; std::vector exteriorNodeIndices; // The triangles inside the polygon tree. // insideTriangles = interiorTriangle + exteriorTriangles std::vector> insideTriangles; std::vector 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> outsideTriangles; // All the triangles: // allTriangles = insideTriangles + outsideTriangles. std::vector> 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::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 = ; // std::vector> points = ; // Vector2 test = ; // std::pair result; // result = tree.GetContainingTriangle(test, points); template std::pair GetContainingTriangle(Vector2 const& test, Vector2 const* points) { size_t constexpr smax = std::numeric_limits::max(); std::pair result = std::make_pair(smax, smax); std::stack 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::max(). The function uses a // linear search of the input triangles. Some typical calls are // PolygonTreeEx tree = ; // std::vector> points = ; // Vector2 test = ; // std::pair 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 std::pair GetContainingTriangle(Vector2 const& test, std::vector> const& triangles, std::vector const& nodeIndices, Vector2 const* points) { LogAssert(triangles.size() == nodeIndices.size(), "Invalid argument."); size_t constexpr smax = std::numeric_limits::max(); std::pair 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::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 = ; // std::vector> points = ; // Vector2 test = ; // 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 size_t GetContainingTriangle(Vector2 const& test, std::vector> const& triangles, int64_t chirality, Vector2 const* points) { size_t result = std::numeric_limits::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 static bool PointInTriangle(Vector2 const& test, int64_t chirality, std::array const& triangle, Vector2 const* points) { T const zero = static_cast(0); T const sign = static_cast(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; } }; }