// Copyright 2021 The Manifold Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "manifold/polygon.h" #include #include #include #include "./collider.h" #include "./parallel.h" #include "./utils.h" #include "manifold/optional_assert.h" namespace { using namespace manifold; static ExecutionParams params; constexpr double kBest = -std::numeric_limits::infinity(); // it seems that MSVC cannot optimize la::determinant(mat2(a, b)) constexpr double determinant2x2(vec2 a, vec2 b) { return a.x * b.y - a.y * b.x; } #ifdef MANIFOLD_DEBUG struct PolyEdge { int startVert, endVert; }; std::vector Polygons2Edges(const PolygonsIdx &polys) { std::vector halfedges; for (const auto &poly : polys) { for (size_t i = 1; i < poly.size(); ++i) { halfedges.push_back({poly[i - 1].idx, poly[i].idx}); } halfedges.push_back({poly.back().idx, poly[0].idx}); } return halfedges; } std::vector Triangles2Edges(const std::vector &triangles) { std::vector halfedges; halfedges.reserve(triangles.size() * 3); for (const ivec3 &tri : triangles) { halfedges.push_back({tri[0], tri[1]}); halfedges.push_back({tri[1], tri[2]}); halfedges.push_back({tri[2], tri[0]}); } return halfedges; } void CheckTopology(const std::vector &halfedges) { DEBUG_ASSERT(halfedges.size() % 2 == 0, topologyErr, "Odd number of halfedges."); size_t n_edges = halfedges.size() / 2; std::vector forward(halfedges.size()), backward(halfedges.size()); auto end = std::copy_if(halfedges.begin(), halfedges.end(), forward.begin(), [](PolyEdge e) { return e.endVert > e.startVert; }); DEBUG_ASSERT( static_cast(std::distance(forward.begin(), end)) == n_edges, topologyErr, "Half of halfedges should be forward."); forward.resize(n_edges); end = std::copy_if(halfedges.begin(), halfedges.end(), backward.begin(), [](PolyEdge e) { return e.endVert < e.startVert; }); DEBUG_ASSERT( static_cast(std::distance(backward.begin(), end)) == n_edges, topologyErr, "Half of halfedges should be backward."); backward.resize(n_edges); std::for_each(backward.begin(), backward.end(), [](PolyEdge &e) { std::swap(e.startVert, e.endVert); }); auto cmp = [](const PolyEdge &a, const PolyEdge &b) { return a.startVert < b.startVert || (a.startVert == b.startVert && a.endVert < b.endVert); }; std::stable_sort(forward.begin(), forward.end(), cmp); std::stable_sort(backward.begin(), backward.end(), cmp); for (size_t i = 0; i < n_edges; ++i) { DEBUG_ASSERT(forward[i].startVert == backward[i].startVert && forward[i].endVert == backward[i].endVert, topologyErr, "Not manifold."); } } void CheckTopology(const std::vector &triangles, const PolygonsIdx &polys) { std::vector halfedges = Triangles2Edges(triangles); std::vector openEdges = Polygons2Edges(polys); for (PolyEdge e : openEdges) { halfedges.push_back({e.endVert, e.startVert}); } CheckTopology(halfedges); } void CheckGeometry(const std::vector &triangles, const PolygonsIdx &polys, double epsilon) { std::unordered_map vertPos; for (const auto &poly : polys) { for (size_t i = 0; i < poly.size(); ++i) { vertPos[poly[i].idx] = poly[i].pos; } } DEBUG_ASSERT(std::all_of(triangles.begin(), triangles.end(), [&vertPos, epsilon](const ivec3 &tri) { return CCW(vertPos[tri[0]], vertPos[tri[1]], vertPos[tri[2]], epsilon) >= 0; }), geometryErr, "triangulation is not entirely CCW!"); } void Dump(const PolygonsIdx &polys, double epsilon) { std::cout << "Polygon 0 " << epsilon << " " << polys.size() << std::endl; for (auto poly : polys) { std::cout << poly.size() << std::endl; for (auto v : poly) { std::cout << v.pos.x << " " << v.pos.y << std::endl; } } std::cout << "# ... " << std::endl; for (auto poly : polys) { std::cout << "show(array([" << std::endl; for (auto v : poly) { std::cout << " [" << v.pos.x << ", " << v.pos.y << "]," << std::endl; } std::cout << "]))" << std::endl; } } void PrintFailure(const std::exception &e, const PolygonsIdx &polys, std::vector &triangles, double epsilon) { std::cout << "-----------------------------------" << std::endl; std::cout << "Triangulation failed! Precision = " << epsilon << std::endl; std::cout << e.what() << std::endl; if (triangles.size() > 1000 && !PolygonParams().verbose) { std::cout << "Output truncated due to producing " << triangles.size() << " triangles." << std::endl; return; } Dump(polys, epsilon); std::cout << "produced this triangulation:" << std::endl; for (size_t j = 0; j < triangles.size(); ++j) { std::cout << triangles[j][0] << ", " << triangles[j][1] << ", " << triangles[j][2] << std::endl; } } #define PRINT(msg) \ if (params.verbose) std::cout << msg << std::endl; #else #define PRINT(msg) #endif /** * Tests if the input polygons are convex by searching for any reflex vertices. * Exactly colinear edges and zero-length edges are treated conservatively as * reflex. Does not check for overlaps. */ bool IsConvex(const PolygonsIdx &polys, double epsilon) { for (const SimplePolygonIdx &poly : polys) { const vec2 firstEdge = poly[0].pos - poly[poly.size() - 1].pos; // Zero-length edges comes out NaN, which won't trip the early return, but // it's okay because that zero-length edge will also get tested // non-normalized and will trip det == 0. vec2 lastEdge = la::normalize(firstEdge); for (size_t v = 0; v < poly.size(); ++v) { const vec2 edge = v + 1 < poly.size() ? poly[v + 1].pos - poly[v].pos : firstEdge; const double det = determinant2x2(lastEdge, edge); if (det <= 0 || (std::abs(det) < epsilon && la::dot(lastEdge, edge) < 0)) return false; lastEdge = la::normalize(edge); } } return true; } /** * Triangulates a set of convex polygons by alternating instead of a fan, to * avoid creating high-degree vertices. */ std::vector TriangulateConvex(const PolygonsIdx &polys) { const size_t numTri = manifold::transform_reduce( polys.begin(), polys.end(), 0_uz, [](size_t a, size_t b) { return a + b; }, [](const SimplePolygonIdx &poly) { return poly.size() - 2; }); std::vector triangles; triangles.reserve(numTri); for (const SimplePolygonIdx &poly : polys) { size_t i = 0; size_t k = poly.size() - 1; bool right = true; while (i + 1 < k) { const size_t j = right ? i + 1 : k - 1; triangles.push_back({poly[i].idx, poly[j].idx, poly[k].idx}); if (right) { i = j; } else { k = j; } right = !right; } } return triangles; } /** * Ear-clipping triangulator based on David Eberly's approach from Geometric * Tools, but adjusted to handle epsilon-valid polygons, and including a * fallback that ensures a manifold triangulation even for overlapping polygons. * This is an O(n^2) algorithm, but hopefully this is not a big problem as the * number of edges in a given polygon is generally much less than the number of * triangles in a mesh, and relatively few faces even need triangulation. * * The main adjustments for robustness involve clipping the sharpest ears first * (a known technique to get higher triangle quality), and doing an exhaustive * search to determine ear convexity exactly if the first geometric result is * within epsilon. */ class EarClip { public: EarClip(const PolygonsIdx &polys, double epsilon) : epsilon_(epsilon) { ZoneScoped; size_t numVert = 0; for (const SimplePolygonIdx &poly : polys) { numVert += poly.size(); } polygon_.reserve(numVert + 2 * polys.size()); std::vector starts = Initialize(polys); for (VertItr v = polygon_.begin(); v != polygon_.end(); ++v) { ClipIfDegenerate(v); } for (const VertItr first : starts) { FindStart(first); } } std::vector Triangulate() { ZoneScoped; for (const VertItr start : holes_) { CutKeyhole(start); } for (const VertItr start : simples_) { TriangulatePoly(start); } return triangles_; } double GetPrecision() const { return epsilon_; } private: struct Vert; typedef std::vector::iterator VertItr; typedef std::vector::const_iterator VertItrC; struct MaxX { bool operator()(const VertItr &a, const VertItr &b) const { return a->pos.x > b->pos.x; } }; struct MinCost { bool operator()(const VertItr &a, const VertItr &b) const { return a->cost < b->cost; } }; typedef std::set::iterator qItr; // The flat list where all the Verts are stored. Not used much for traversal. std::vector polygon_; // The set of right-most starting points, one for each negative-area contour. std::multiset holes_; // The set of starting points, one for each positive-area contour. std::vector outers_; // The set of starting points, one for each simple polygon. std::vector simples_; // Maps each hole (by way of starting point) to its bounding box. std::map hole2BBox_; // A priority queue of valid ears - the multiset allows them to be updated. std::multiset earsQueue_; // The output triangulation. std::vector triangles_; // Bounding box of the entire set of polygons Rect bBox_; // Working epsilon: max of float error and input value. double epsilon_; struct IdxCollider { Collider collider; std::vector itr; SparseIndices ind; }; // A circularly-linked list representing the polygon(s) that still need to be // triangulated. This gets smaller as ears are clipped until it degenerates to // two points and terminates. struct Vert { int mesh_idx; double cost; qItr ear; vec2 pos, rightDir; VertItr left, right; // Shorter than half of epsilon, to be conservative so that it doesn't // cause CW triangles that exceed epsilon due to rounding error. bool IsShort(double epsilon) const { const vec2 edge = right->pos - pos; return la::dot(edge, edge) * 4 < epsilon * epsilon; } // Like CCW, returns 1 if v is on the inside of the angle formed at this // vert, -1 on the outside, and 0 if it's within epsilon of the boundary. // Ensure v is more than epsilon from pos, as case this will not return 0. int Interior(vec2 v, double epsilon) const { const vec2 diff = v - pos; if (la::dot(diff, diff) < epsilon * epsilon) { return 0; } return CCW(pos, left->pos, right->pos, epsilon) + CCW(pos, right->pos, v, epsilon) + CCW(pos, v, left->pos, epsilon); } // Returns true if Vert is on the inside of the edge that goes from tail to // tail->right. This will walk the edges if necessary until a clear answer // is found (beyond epsilon). If toLeft is true, this Vert will walk its // edges to the left. This should be chosen so that the edges walk in the // same general direction - tail always walks to the right. bool InsideEdge(VertItr tail, double epsilon, bool toLeft) const { const double p2 = epsilon * epsilon; VertItr nextL = left->right; VertItr nextR = tail->right; VertItr center = tail; VertItr last = center; while (nextL != nextR && tail != nextR && nextL != (toLeft ? right : left)) { const vec2 edgeL = nextL->pos - center->pos; const double l2 = la::dot(edgeL, edgeL); if (l2 <= p2) { nextL = toLeft ? nextL->left : nextL->right; continue; } const vec2 edgeR = nextR->pos - center->pos; const double r2 = la::dot(edgeR, edgeR); if (r2 <= p2) { nextR = nextR->right; continue; } const vec2 vecLR = nextR->pos - nextL->pos; const double lr2 = la::dot(vecLR, vecLR); if (lr2 <= p2) { last = center; center = nextL; nextL = toLeft ? nextL->left : nextL->right; if (nextL == nextR) break; nextR = nextR->right; continue; } int convexity = CCW(nextL->pos, center->pos, nextR->pos, epsilon); if (center != last) { convexity += CCW(last->pos, center->pos, nextL->pos, epsilon) + CCW(nextR->pos, center->pos, last->pos, epsilon); } if (convexity != 0) return convexity > 0; if (l2 < r2) { center = nextL; nextL = toLeft ? nextL->left : nextL->right; } else { center = nextR; nextR = nextR->right; } last = center; } // The whole polygon is degenerate - consider this to be convex. return true; } // A major key to robustness is to only clip convex ears, but this is // difficult to determine when an edge is folded back on itself. This // function walks down the kinks in a degenerate portion of a polygon until // it finds a clear geometric result. In the vast majority of cases the loop // will only need one or two iterations. bool IsConvex(double epsilon) const { const int convexity = CCW(left->pos, pos, right->pos, epsilon); if (convexity != 0) { return convexity > 0; } if (la::dot(left->pos - pos, right->pos - pos) <= 0) { return true; } return left->InsideEdge(left->right, epsilon, true); } // Subtly different from !IsConvex because IsConvex will return true for // colinear non-folded verts, while IsReflex will always check until actual // certainty is determined. bool IsReflex(double epsilon) const { return !left->InsideEdge(left->right, epsilon, true); } // Returns the x-value on this edge corresponding to the start.y value, // returning NAN if the edge does not cross the value from below to above, // right of start - all within a epsilon tolerance. If onTop != 0, this // restricts which end is allowed to terminate within the epsilon band. double InterpY2X(vec2 start, int onTop, double epsilon) const { if (la::abs(pos.y - start.y) <= epsilon) { if (right->pos.y <= start.y + epsilon || onTop == 1) { return NAN; } else { return pos.x; } } else if (pos.y < start.y - epsilon) { if (right->pos.y > start.y + epsilon) { return pos.x + (start.y - pos.y) * (right->pos.x - pos.x) / (right->pos.y - pos.y); } else if (right->pos.y < start.y - epsilon || onTop == -1) { return NAN; } else { return right->pos.x; } } else { return NAN; } } // This finds the cost of this vert relative to one of the two closed sides // of the ear. Points are valid even when they touch, so long as their edge // goes to the outside. No need to check the other side, since all verts are // processed in the EarCost loop. double SignedDist(VertItr v, vec2 unit, double epsilon) const { double d = determinant2x2(unit, v->pos - pos); if (std::abs(d) < epsilon) { double dR = determinant2x2(unit, v->right->pos - pos); if (std::abs(dR) > epsilon) return dR; double dL = determinant2x2(unit, v->left->pos - pos); if (std::abs(dL) > epsilon) return dL; } return d; } // Find the cost of Vert v within this ear, where openSide is the unit // vector from Verts right to left - passed in for reuse. double Cost(VertItr v, vec2 openSide, double epsilon) const { double cost = std::min(SignedDist(v, rightDir, epsilon), SignedDist(v, left->rightDir, epsilon)); const double openCost = determinant2x2(openSide, v->pos - right->pos); return std::min(cost, openCost); } // For verts outside the ear, apply a cost based on the Delaunay condition // to aid in prioritization and produce cleaner triangulations. This doesn't // affect robustness, but may be adjusted to improve output. static double DelaunayCost(vec2 diff, double scale, double epsilon) { return -epsilon - scale * la::dot(diff, diff); } // This is the expensive part of the algorithm, checking this ear against // every Vert to ensure none are inside. The Collider brings the total // triangulator cost down from O(n^2) to O(nlogn) for most large polygons. // // Think of a cost as vaguely a distance metric - 0 is right on the edge of // being invalid. cost > epsilon is definitely invalid. Cost < -epsilon // is definitely valid, so all improvement costs are designed to always give // values < -epsilon so they will never affect validity. The first // totalCost is designed to give priority to sharper angles. Any cost < (-1 // - epsilon) has satisfied the Delaunay condition. double EarCost(double epsilon, IdxCollider &collider) const { vec2 openSide = left->pos - right->pos; const vec2 center = 0.5 * (left->pos + right->pos); const double scale = 4 / la::dot(openSide, openSide); const double radius = la::length(openSide) / 2; openSide = la::normalize(openSide); double totalCost = la::dot(left->rightDir, rightDir) - 1 - epsilon; if (CCW(pos, left->pos, right->pos, epsilon) == 0) { // Clip folded ears first return totalCost; } Box earBox = Box{vec3(center.x - radius, center.y - radius, 0), vec3(center.x + radius, center.y + radius, 0)}; earBox.Union(vec3(pos, 0)); collider.collider.Collisions(VecView(&earBox, 1), collider.ind); const int lid = left->mesh_idx; const int rid = right->mesh_idx; totalCost = transform_reduce( countAt(0), countAt(collider.ind.size()), totalCost, [](double a, double b) { return std::max(a, b); }, [&](size_t i) { const VertItr test = collider.itr[collider.ind.Get(i, true)]; if (!Clipped(test) && test->mesh_idx != mesh_idx && test->mesh_idx != lid && test->mesh_idx != rid) { // Skip duplicated verts double cost = Cost(test, openSide, epsilon); if (cost < -epsilon) { cost = DelaunayCost(test->pos - center, scale, epsilon); } return cost; } return std::numeric_limits::lowest(); }); collider.ind.Clear(); return totalCost; } void PrintVert() const { #ifdef MANIFOLD_DEBUG if (!params.verbose) return; std::cout << "vert: " << mesh_idx << ", left: " << left->mesh_idx << ", right: " << right->mesh_idx << ", cost: " << cost << std::endl; #endif } }; static vec2 SafeNormalize(vec2 v) { vec2 n = la::normalize(v); return std::isfinite(n.x) ? n : vec2(0, 0); } // This function and JoinPolygons are the only functions that affect the // circular list data structure. This helps ensure it remains circular. static void Link(VertItr left, VertItr right) { left->right = right; right->left = left; left->rightDir = SafeNormalize(right->pos - left->pos); } // When an ear vert is clipped, its neighbors get linked, so they get unlinked // from it, but it is still linked to them. static bool Clipped(VertItr v) { return v->right->left != v; } // Apply func to each un-clipped vert in a polygon and return an un-clipped // vert. VertItrC Loop(VertItr first, std::function func) const { VertItr v = first; do { if (Clipped(v)) { // Update first to an un-clipped vert so we will return to it instead // of infinite-looping. first = v->right->left; if (!Clipped(first)) { v = first; if (v->right == v->left) { return polygon_.end(); } func(v); } } else { if (v->right == v->left) { return polygon_.end(); } func(v); } v = v->right; } while (v != first); return v; } // Remove this vert from the circular list and output a corresponding // triangle. void ClipEar(VertItrC ear) { Link(ear->left, ear->right); if (ear->left->mesh_idx != ear->mesh_idx && ear->mesh_idx != ear->right->mesh_idx && ear->right->mesh_idx != ear->left->mesh_idx) { // Filter out topological degenerates, which can form in bad // triangulations of polygons with holes, due to vert duplication. triangles_.push_back( {ear->left->mesh_idx, ear->mesh_idx, ear->right->mesh_idx}); } else { PRINT("Topological degenerate!"); } } // If an ear will make a degenerate triangle, clip it early to avoid // difficulty in key-holing. This function is recursive, as the process of // clipping may cause the neighbors to degenerate. Reflex degenerates *must // not* be clipped, unless they have a short edge. void ClipIfDegenerate(VertItr ear) { if (Clipped(ear)) { return; } if (ear->left == ear->right) { return; } if (ear->IsShort(epsilon_) || (CCW(ear->left->pos, ear->pos, ear->right->pos, epsilon_) == 0 && la::dot(ear->left->pos - ear->pos, ear->right->pos - ear->pos) > 0 && ear->IsConvex(epsilon_))) { ClipEar(ear); ClipIfDegenerate(ear->left); ClipIfDegenerate(ear->right); } } // Build the circular list polygon structures. std::vector Initialize(const PolygonsIdx &polys) { std::vector starts; for (const SimplePolygonIdx &poly : polys) { auto vert = poly.begin(); polygon_.push_back({vert->idx, 0.0, earsQueue_.end(), vert->pos}); const VertItr first = std::prev(polygon_.end()); bBox_.Union(first->pos); VertItr last = first; // This is not the real rightmost start, but just an arbitrary vert for // now to identify each polygon. starts.push_back(first); for (++vert; vert != poly.end(); ++vert) { bBox_.Union(vert->pos); polygon_.push_back({vert->idx, 0.0, earsQueue_.end(), vert->pos}); VertItr next = std::prev(polygon_.end()); Link(last, next); last = next; } Link(last, first); } if (epsilon_ < 0) epsilon_ = bBox_.Scale() * kPrecision; // Slightly more than enough, since each hole can cause two extra triangles. triangles_.reserve(polygon_.size() + 2 * starts.size()); return starts; } // Find the actual rightmost starts after degenerate removal. Also calculate // the polygon bounding boxes. void FindStart(VertItr first) { const vec2 origin = first->pos; VertItr start = first; double maxX = -std::numeric_limits::infinity(); Rect bBox; // Kahan summation double area = 0; double areaCompensation = 0; auto AddPoint = [&](VertItr v) { bBox.Union(v->pos); const double area1 = determinant2x2(v->pos - origin, v->right->pos - origin); const double t1 = area + area1; areaCompensation += (area - t1) + area1; area = t1; if (v->pos.x > maxX) { maxX = v->pos.x; start = v; } }; if (Loop(first, AddPoint) == polygon_.end()) { // No polygon left if all ears were degenerate and already clipped. return; } area += areaCompensation; const vec2 size = bBox.Size(); const double minArea = epsilon_ * std::max(size.x, size.y); if (std::isfinite(maxX) && area < -minArea) { holes_.insert(start); hole2BBox_.insert({start, bBox}); } else { simples_.push_back(start); if (area > minArea) { outers_.push_back(start); } } } // All holes must be key-holed (attached to an outer polygon) before ear // clipping can commence. Instead of relying on sorting, which may be // incorrect due to epsilon, we check for polygon edges both ahead and // behind to ensure all valid options are found. void CutKeyhole(const VertItr start) { const Rect bBox = hole2BBox_[start]; const int onTop = start->pos.y >= bBox.max.y - epsilon_ ? 1 : start->pos.y <= bBox.min.y + epsilon_ ? -1 : 0; VertItr connector = polygon_.end(); auto CheckEdge = [&](VertItr edge) { const double x = edge->InterpY2X(start->pos, onTop, epsilon_); if (std::isfinite(x) && start->InsideEdge(edge, epsilon_, true) && (connector == polygon_.end() || CCW({x, start->pos.y}, connector->pos, connector->right->pos, epsilon_) == 1 || (connector->pos.y < edge->pos.y ? edge->InsideEdge(connector, epsilon_, false) : !connector->InsideEdge(edge, epsilon_, false)))) { connector = edge; } }; for (const VertItr first : outers_) { Loop(first, CheckEdge); } if (connector == polygon_.end()) { PRINT("hole did not find an outer contour!"); simples_.push_back(start); return; } connector = FindCloserBridge(start, connector); JoinPolygons(start, connector); #ifdef MANIFOLD_DEBUG if (params.verbose) { std::cout << "connected " << start->mesh_idx << " to " << connector->mesh_idx << std::endl; } #endif } // This converts the initial guess for the keyhole location into the final one // and returns it. It does so by finding any reflex verts inside the triangle // containing the best connection and the initial horizontal line. VertItr FindCloserBridge(VertItr start, VertItr edge) { VertItr connector = edge->pos.x < start->pos.x ? edge->right : edge->right->pos.x < start->pos.x ? edge : edge->right->pos.y - start->pos.y > start->pos.y - edge->pos.y ? edge : edge->right; if (la::abs(connector->pos.y - start->pos.y) <= epsilon_) { return connector; } const double above = connector->pos.y > start->pos.y ? 1 : -1; auto CheckVert = [&](VertItr vert) { const double inside = above * CCW(start->pos, vert->pos, connector->pos, epsilon_); if (vert->pos.x > start->pos.x - epsilon_ && vert->pos.y * above > start->pos.y * above - epsilon_ && (inside > 0 || (inside == 0 && vert->pos.x < connector->pos.x)) && vert->InsideEdge(edge, epsilon_, true) && vert->IsReflex(epsilon_)) { connector = vert; } }; for (const VertItr first : outers_) { Loop(first, CheckVert); } return connector; } // Creates a keyhole between the start vert of a hole and the connector vert // of an outer polygon. To do this, both verts are duplicated and reattached. // This process may create degenerate ears, so these are clipped if necessary // to keep from confusing subsequent key-holing operations. void JoinPolygons(VertItr start, VertItr connector) { polygon_.push_back(*start); const VertItr newStart = std::prev(polygon_.end()); polygon_.push_back(*connector); const VertItr newConnector = std::prev(polygon_.end()); start->right->left = newStart; connector->left->right = newConnector; Link(start, connector); Link(newConnector, newStart); ClipIfDegenerate(start); ClipIfDegenerate(newStart); ClipIfDegenerate(connector); ClipIfDegenerate(newConnector); } // Recalculate the cost of the Vert v ear, updating it in the queue by // removing and reinserting it. void ProcessEar(VertItr v, IdxCollider &collider) { if (v->ear != earsQueue_.end()) { earsQueue_.erase(v->ear); v->ear = earsQueue_.end(); } if (v->IsShort(epsilon_)) { v->cost = kBest; v->ear = earsQueue_.insert(v); } else if (v->IsConvex(2 * epsilon_)) { v->cost = v->EarCost(epsilon_, collider); v->ear = earsQueue_.insert(v); } else { v->cost = 1; // not used, but marks reflex verts for debug } } // Create a collider of all vertices in this polygon, each expanded by // epsilon_. Each ear uses this BVH to quickly find a subset of vertices to // check for cost. IdxCollider VertCollider(VertItr start) const { Vec vertBox; Vec vertMorton; std::vector itr; const Box box(vec3(bBox_.min, 0), vec3(bBox_.max, 0)); Loop(start, [&vertBox, &vertMorton, &itr, &box, this](VertItr v) { itr.push_back(v); const vec3 pos(v->pos, 0); vertBox.push_back({pos - epsilon_, pos + epsilon_}); vertMorton.push_back(Collider::MortonCode(pos, box)); }); if (itr.empty()) { return {Collider(), itr}; } const int numVert = itr.size(); Vec vertNew2Old(numVert); sequence(vertNew2Old.begin(), vertNew2Old.end()); stable_sort(vertNew2Old.begin(), vertNew2Old.end(), [&vertMorton](const int a, const int b) { return vertMorton[a] < vertMorton[b]; }); Permute(vertMorton, vertNew2Old); Permute(vertBox, vertNew2Old); Permute(itr, vertNew2Old); return {Collider(vertBox, vertMorton), itr}; } // The main ear-clipping loop. This is called once for each simple polygon - // all holes have already been key-holed and joined to an outer polygon. void TriangulatePoly(VertItr start) { ZoneScoped; IdxCollider vertCollider = VertCollider(start); if (vertCollider.itr.empty()) { PRINT("Empty poly"); return; } // A simple polygon always creates two fewer triangles than it has verts. int numTri = -2; earsQueue_.clear(); auto QueueVert = [&](VertItr v) { ProcessEar(v, vertCollider); ++numTri; v->PrintVert(); }; VertItrC v = Loop(start, QueueVert); if (v == polygon_.end()) return; Dump(v); while (numTri > 0) { const qItr ear = earsQueue_.begin(); if (ear != earsQueue_.end()) { v = *ear; // Cost should always be negative, generally < -epsilon. v->PrintVert(); earsQueue_.erase(ear); } else { PRINT("No ear found!"); } ClipEar(v); --numTri; ProcessEar(v->left, vertCollider); ProcessEar(v->right, vertCollider); // This is a backup vert that is used if the queue is empty (geometrically // invalid polygon), to ensure manifoldness. v = v->right; } DEBUG_ASSERT(v->right == v->left, logicErr, "Triangulator error!"); PRINT("Finished poly"); } void Dump(VertItrC start) const { #ifdef MANIFOLD_DEBUG if (!params.verbose) return; VertItrC v = start; std::cout << "show(array([" << std::setprecision(15) << std::endl; do { std::cout << " [" << v->pos.x << ", " << v->pos.y << "],# " << v->mesh_idx << ", cost: " << v->cost << std::endl; v = v->right; } while (v != start); std::cout << " [" << v->pos.x << ", " << v->pos.y << "],# " << v->mesh_idx << std::endl; std::cout << "]))" << std::endl; v = start; std::cout << "polys.push_back({" << std::setprecision(15) << std::endl; do { std::cout << " {" << v->pos.x << ", " << v->pos.y << "}, //" << std::endl; v = v->right; } while (v != start); std::cout << "});" << std::endl; #endif } }; } // namespace namespace manifold { /** * @brief Triangulates a set of ε-valid polygons. If the input is not * ε-valid, the triangulation may overlap, but will always return a * manifold result that matches the input edge directions. * * @param polys The set of polygons, wound CCW and representing multiple * polygons and/or holes. These have 2D-projected positions as well as * references back to the original vertices. * @param epsilon The value of ε, bounding the uncertainty of the * input. * @return std::vector The triangles, referencing the original * vertex indicies. */ std::vector TriangulateIdx(const PolygonsIdx &polys, double epsilon) { std::vector triangles; double updatedEpsilon = epsilon; #ifdef MANIFOLD_DEBUG try { #endif if (IsConvex(polys, epsilon)) { // fast path triangles = TriangulateConvex(polys); } else { EarClip triangulator(polys, epsilon); triangles = triangulator.Triangulate(); updatedEpsilon = triangulator.GetPrecision(); } #ifdef MANIFOLD_DEBUG if (params.intermediateChecks) { CheckTopology(triangles, polys); if (!params.processOverlaps) { CheckGeometry(triangles, polys, 2 * updatedEpsilon); } } } catch (const geometryErr &e) { if (!params.suppressErrors) { PrintFailure(e, polys, triangles, updatedEpsilon); } throw; } catch (const std::exception &e) { PrintFailure(e, polys, triangles, updatedEpsilon); throw; } #endif return triangles; } /** * @brief Triangulates a set of ε-valid polygons. If the input is not * ε-valid, the triangulation may overlap, but will always return a * manifold result that matches the input edge directions. * * @param polygons The set of polygons, wound CCW and representing multiple * polygons and/or holes. * @param epsilon The value of ε, bounding the uncertainty of the * input. * @return std::vector The triangles, referencing the original * polygon points in order. */ std::vector Triangulate(const Polygons &polygons, double epsilon) { int idx = 0; PolygonsIdx polygonsIndexed; for (const auto &poly : polygons) { SimplePolygonIdx simpleIndexed; for (const vec2 &polyVert : poly) { simpleIndexed.push_back({polyVert, idx++}); } polygonsIndexed.push_back(simpleIndexed); } return TriangulateIdx(polygonsIndexed, epsilon); } ExecutionParams &PolygonParams() { return params; } } // namespace manifold