Path: blob/master/thirdparty/manifold/src/polygon.cpp
9903 views
// Copyright 2021 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include "manifold/polygon.h"1516#include <functional>17#include <map>18#include <set>1920#include "manifold/manifold.h"21#include "manifold/optional_assert.h"22#include "parallel.h"23#include "tree2d.h"24#include "utils.h"2526#ifdef MANIFOLD_DEBUG27#include <iomanip>28#endif2930namespace {31using namespace manifold;3233constexpr int TRIANGULATOR_VERBOSE_LEVEL = 2;3435constexpr double kBest = -std::numeric_limits<double>::infinity();3637// it seems that MSVC cannot optimize la::determinant(mat2(a, b))38constexpr double determinant2x2(vec2 a, vec2 b) {39return a.x * b.y - a.y * b.x;40}4142#ifdef MANIFOLD_DEBUG43struct PolyEdge {44int startVert, endVert;45};4647std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx& polys) {48std::vector<PolyEdge> halfedges;49for (const auto& poly : polys) {50for (size_t i = 1; i < poly.size(); ++i) {51halfedges.push_back({poly[i - 1].idx, poly[i].idx});52}53halfedges.push_back({poly.back().idx, poly[0].idx});54}55return halfedges;56}5758std::vector<PolyEdge> Triangles2Edges(const std::vector<ivec3>& triangles) {59std::vector<PolyEdge> halfedges;60halfedges.reserve(triangles.size() * 3);61for (const ivec3& tri : triangles) {62halfedges.push_back({tri[0], tri[1]});63halfedges.push_back({tri[1], tri[2]});64halfedges.push_back({tri[2], tri[0]});65}66return halfedges;67}6869void CheckTopology(const std::vector<PolyEdge>& halfedges) {70DEBUG_ASSERT(halfedges.size() % 2 == 0, topologyErr,71"Odd number of halfedges.");72size_t n_edges = halfedges.size() / 2;73std::vector<PolyEdge> forward(halfedges.size()), backward(halfedges.size());7475auto end = std::copy_if(halfedges.begin(), halfedges.end(), forward.begin(),76[](PolyEdge e) { return e.endVert > e.startVert; });77DEBUG_ASSERT(78static_cast<size_t>(std::distance(forward.begin(), end)) == n_edges,79topologyErr, "Half of halfedges should be forward.");80forward.resize(n_edges);8182end = std::copy_if(halfedges.begin(), halfedges.end(), backward.begin(),83[](PolyEdge e) { return e.endVert < e.startVert; });84DEBUG_ASSERT(85static_cast<size_t>(std::distance(backward.begin(), end)) == n_edges,86topologyErr, "Half of halfedges should be backward.");87backward.resize(n_edges);8889std::for_each(backward.begin(), backward.end(),90[](PolyEdge& e) { std::swap(e.startVert, e.endVert); });91auto cmp = [](const PolyEdge& a, const PolyEdge& b) {92return a.startVert < b.startVert ||93(a.startVert == b.startVert && a.endVert < b.endVert);94};95std::stable_sort(forward.begin(), forward.end(), cmp);96std::stable_sort(backward.begin(), backward.end(), cmp);97for (size_t i = 0; i < n_edges; ++i) {98DEBUG_ASSERT(forward[i].startVert == backward[i].startVert &&99forward[i].endVert == backward[i].endVert,100topologyErr, "Not manifold.");101}102}103104void CheckTopology(const std::vector<ivec3>& triangles,105const PolygonsIdx& polys) {106std::vector<PolyEdge> halfedges = Triangles2Edges(triangles);107std::vector<PolyEdge> openEdges = Polygons2Edges(polys);108for (PolyEdge e : openEdges) {109halfedges.push_back({e.endVert, e.startVert});110}111CheckTopology(halfedges);112}113114void CheckGeometry(const std::vector<ivec3>& triangles,115const PolygonsIdx& polys, double epsilon) {116std::unordered_map<int, vec2> vertPos;117for (const auto& poly : polys) {118for (size_t i = 0; i < poly.size(); ++i) {119vertPos[poly[i].idx] = poly[i].pos;120}121}122DEBUG_ASSERT(std::all_of(triangles.begin(), triangles.end(),123[&vertPos, epsilon](const ivec3& tri) {124return CCW(vertPos[tri[0]], vertPos[tri[1]],125vertPos[tri[2]], epsilon) >= 0;126}),127geometryErr, "triangulation is not entirely CCW!");128}129130void Dump(const PolygonsIdx& polys, double epsilon) {131std::cout << std::setprecision(19);132std::cout << "Polygon 0 " << epsilon << " " << polys.size() << std::endl;133for (auto poly : polys) {134std::cout << poly.size() << std::endl;135for (auto v : poly) {136std::cout << v.pos.x << " " << v.pos.y << std::endl;137}138}139std::cout << "# ... " << std::endl;140for (auto poly : polys) {141std::cout << "show(array([" << std::endl;142for (auto v : poly) {143std::cout << " [" << v.pos.x << ", " << v.pos.y << "]," << std::endl;144}145std::cout << "]))" << std::endl;146}147}148149std::atomic<int> numFailures(0);150151void PrintFailure(const std::exception& e, const PolygonsIdx& polys,152std::vector<ivec3>& triangles, double epsilon) {153// only print the first triangulation failure154if (numFailures.fetch_add(1) != 0) return;155std::cout << std::setprecision(19);156std::cout << "-----------------------------------" << std::endl;157std::cout << "Triangulation failed! Precision = " << epsilon << std::endl;158std::cout << e.what() << std::endl;159if (triangles.size() > 1000 &&160ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) {161std::cout << "Output truncated due to producing " << triangles.size()162<< " triangles." << std::endl;163return;164}165Dump(polys, epsilon);166std::cout << "produced this triangulation:" << std::endl;167for (size_t j = 0; j < triangles.size(); ++j) {168std::cout << triangles[j][0] << ", " << triangles[j][1] << ", "169<< triangles[j][2] << std::endl;170}171}172173#define PRINT(msg) \174if (ManifoldParams().verbose >= TRIANGULATOR_VERBOSE_LEVEL) \175std::cout << msg << std::endl;176#else177#define PRINT(msg)178#endif179180/**181* Tests if the input polygons are convex by searching for any reflex vertices.182* Exactly colinear edges and zero-length edges are treated conservatively as183* reflex. Does not check for overlaps.184*/185bool IsConvex(const PolygonsIdx& polys, double epsilon) {186for (const SimplePolygonIdx& poly : polys) {187const vec2 firstEdge = poly[0].pos - poly[poly.size() - 1].pos;188// Zero-length edges comes out NaN, which won't trip the early return, but189// it's okay because that zero-length edge will also get tested190// non-normalized and will trip det == 0.191vec2 lastEdge = la::normalize(firstEdge);192for (size_t v = 0; v < poly.size(); ++v) {193const vec2 edge =194v + 1 < poly.size() ? poly[v + 1].pos - poly[v].pos : firstEdge;195const double det = determinant2x2(lastEdge, edge);196if (det <= 0 || (std::abs(det) < epsilon && la::dot(lastEdge, edge) < 0))197return false;198lastEdge = la::normalize(edge);199}200}201return true;202}203204/**205* Triangulates a set of convex polygons by alternating instead of a fan, to206* avoid creating high-degree vertices.207*/208std::vector<ivec3> TriangulateConvex(const PolygonsIdx& polys) {209const size_t numTri = manifold::transform_reduce(210polys.begin(), polys.end(), 0_uz,211[](size_t a, size_t b) { return a + b; },212[](const SimplePolygonIdx& poly) { return poly.size() - 2; });213std::vector<ivec3> triangles;214triangles.reserve(numTri);215for (const SimplePolygonIdx& poly : polys) {216size_t i = 0;217size_t k = poly.size() - 1;218bool right = true;219while (i + 1 < k) {220const size_t j = right ? i + 1 : k - 1;221triangles.push_back({poly[i].idx, poly[j].idx, poly[k].idx});222if (right) {223i = j;224} else {225k = j;226}227right = !right;228}229}230return triangles;231}232233/**234* Ear-clipping triangulator based on David Eberly's approach from Geometric235* Tools, but adjusted to handle epsilon-valid polygons, and including a236* fallback that ensures a manifold triangulation even for overlapping polygons.237* This is reduced from an O(n^2) algorithm by means of our BVH Collider.238*239* The main adjustments for robustness involve clipping the sharpest ears first240* (a known technique to get higher triangle quality), and doing an exhaustive241* search to determine ear convexity exactly if the first geometric result is242* within epsilon.243*/244245class EarClip {246public:247EarClip(const PolygonsIdx& polys, double epsilon) : epsilon_(epsilon) {248ZoneScoped;249250size_t numVert = 0;251for (const SimplePolygonIdx& poly : polys) {252numVert += poly.size();253}254polygon_.reserve(numVert + 2 * polys.size());255256std::vector<VertItr> starts = Initialize(polys);257258for (VertItr v = polygon_.begin(); v != polygon_.end(); ++v) {259ClipIfDegenerate(v);260}261262for (const VertItr first : starts) {263FindStart(first);264}265}266267std::vector<ivec3> Triangulate() {268ZoneScoped;269270for (const VertItr start : holes_) {271CutKeyhole(start);272}273274for (const VertItr start : simples_) {275TriangulatePoly(start);276}277278return triangles_;279}280281double GetPrecision() const { return epsilon_; }282283private:284struct Vert;285using VertItr = std::vector<Vert>::iterator;286using VertItrC = std::vector<Vert>::const_iterator;287struct MaxX {288bool operator()(const VertItr& a, const VertItr& b) const {289return a->pos.x > b->pos.x;290}291};292struct MinCost {293bool operator()(const VertItr& a, const VertItr& b) const {294return a->cost < b->cost;295}296};297using qItr = std::set<VertItr, MinCost>::iterator;298299// The flat list where all the Verts are stored. Not used much for traversal.300std::vector<Vert> polygon_;301// The set of right-most starting points, one for each negative-area contour.302std::multiset<VertItr, MaxX> holes_;303// The set of starting points, one for each positive-area contour.304std::vector<VertItr> outers_;305// The set of starting points, one for each simple polygon.306std::vector<VertItr> simples_;307// Maps each hole (by way of starting point) to its bounding box.308std::map<VertItr, Rect> hole2BBox_;309// A priority queue of valid ears - the multiset allows them to be updated.310std::multiset<VertItr, MinCost> earsQueue_;311// The output triangulation.312std::vector<ivec3> triangles_;313// Bounding box of the entire set of polygons314Rect bBox_;315// Working epsilon: max of float error and input value.316double epsilon_;317318struct IdxCollider {319Vec<PolyVert> points;320std::vector<VertItr> itr;321};322323// A circularly-linked list representing the polygon(s) that still need to be324// triangulated. This gets smaller as ears are clipped until it degenerates to325// two points and terminates.326struct Vert {327int mesh_idx;328double cost;329qItr ear;330vec2 pos, rightDir;331VertItr left, right;332333// Shorter than half of epsilon, to be conservative so that it doesn't334// cause CW triangles that exceed epsilon due to rounding error.335bool IsShort(double epsilon) const {336const vec2 edge = right->pos - pos;337return la::dot(edge, edge) * 4 < epsilon * epsilon;338}339340// Like CCW, returns 1 if v is on the inside of the angle formed at this341// vert, -1 on the outside, and 0 if it's within epsilon of the boundary.342// Ensure v is more than epsilon from pos, as case this will not return 0.343int Interior(vec2 v, double epsilon) const {344const vec2 diff = v - pos;345if (la::dot(diff, diff) < epsilon * epsilon) {346return 0;347}348return CCW(pos, left->pos, right->pos, epsilon) +349CCW(pos, right->pos, v, epsilon) + CCW(pos, v, left->pos, epsilon);350}351352// Returns true if Vert is on the inside of the edge that goes from tail to353// tail->right. This will walk the edges if necessary until a clear answer354// is found (beyond epsilon). If toLeft is true, this Vert will walk its355// edges to the left. This should be chosen so that the edges walk in the356// same general direction - tail always walks to the right.357bool InsideEdge(VertItr tail, double epsilon, bool toLeft) const {358const double p2 = epsilon * epsilon;359VertItr nextL = left->right;360VertItr nextR = tail->right;361VertItr center = tail;362VertItr last = center;363364while (nextL != nextR && tail != nextR &&365nextL != (toLeft ? right : left)) {366const vec2 edgeL = nextL->pos - center->pos;367const double l2 = la::dot(edgeL, edgeL);368if (l2 <= p2) {369nextL = toLeft ? nextL->left : nextL->right;370continue;371}372373const vec2 edgeR = nextR->pos - center->pos;374const double r2 = la::dot(edgeR, edgeR);375if (r2 <= p2) {376nextR = nextR->right;377continue;378}379380const vec2 vecLR = nextR->pos - nextL->pos;381const double lr2 = la::dot(vecLR, vecLR);382if (lr2 <= p2) {383last = center;384center = nextL;385nextL = toLeft ? nextL->left : nextL->right;386if (nextL == nextR) break;387nextR = nextR->right;388continue;389}390391int convexity = CCW(nextL->pos, center->pos, nextR->pos, epsilon);392if (center != last) {393convexity += CCW(last->pos, center->pos, nextL->pos, epsilon) +394CCW(nextR->pos, center->pos, last->pos, epsilon);395}396if (convexity != 0) return convexity > 0;397398if (l2 < r2) {399center = nextL;400nextL = toLeft ? nextL->left : nextL->right;401} else {402center = nextR;403nextR = nextR->right;404}405last = center;406}407// The whole polygon is degenerate - consider this to be convex.408return true;409}410411// Returns true for convex or colinear ears.412bool IsConvex(double epsilon) const {413return CCW(left->pos, pos, right->pos, epsilon) >= 0;414}415416// Subtly different from !IsConvex because IsConvex will return true for417// colinear non-folded verts, while IsReflex will always check until actual418// certainty is determined.419bool IsReflex(double epsilon) const {420return !left->InsideEdge(left->right, epsilon, true);421}422423// Returns the x-value on this edge corresponding to the start.y value,424// returning NAN if the edge does not cross the value from below to above,425// right of start - all within a epsilon tolerance. If onTop != 0, this426// restricts which end is allowed to terminate within the epsilon band.427double InterpY2X(vec2 start, int onTop, double epsilon) const {428if (la::abs(pos.y - start.y) <= epsilon) {429if (right->pos.y <= start.y + epsilon || onTop == 1) {430return NAN;431} else {432return pos.x;433}434} else if (pos.y < start.y - epsilon) {435if (right->pos.y > start.y + epsilon) {436return pos.x + (start.y - pos.y) * (right->pos.x - pos.x) /437(right->pos.y - pos.y);438} else if (right->pos.y < start.y - epsilon || onTop == -1) {439return NAN;440} else {441return right->pos.x;442}443} else {444return NAN;445}446}447448// This finds the cost of this vert relative to one of the two closed sides449// of the ear. Points are valid even when they touch, so long as their edge450// goes to the outside. No need to check the other side, since all verts are451// processed in the EarCost loop.452double SignedDist(VertItr v, vec2 unit, double epsilon) const {453double d = determinant2x2(unit, v->pos - pos);454if (std::abs(d) < epsilon) {455double dR = determinant2x2(unit, v->right->pos - pos);456if (std::abs(dR) > epsilon) return dR;457double dL = determinant2x2(unit, v->left->pos - pos);458if (std::abs(dL) > epsilon) return dL;459}460return d;461}462463// Find the cost of Vert v within this ear, where openSide is the unit464// vector from Verts right to left - passed in for reuse.465double Cost(VertItr v, vec2 openSide, double epsilon) const {466double cost = std::min(SignedDist(v, rightDir, epsilon),467SignedDist(v, left->rightDir, epsilon));468469const double openCost = determinant2x2(openSide, v->pos - right->pos);470return std::min(cost, openCost);471}472473// For verts outside the ear, apply a cost based on the Delaunay condition474// to aid in prioritization and produce cleaner triangulations. This doesn't475// affect robustness, but may be adjusted to improve output.476static double DelaunayCost(vec2 diff, double scale, double epsilon) {477return -epsilon - scale * la::dot(diff, diff);478}479480// This is the expensive part of the algorithm, checking this ear against481// every Vert to ensure none are inside. The Collider brings the total482// triangulator cost down from O(n^2) to O(nlogn) for most large polygons.483//484// Think of a cost as vaguely a distance metric - 0 is right on the edge of485// being invalid. cost > epsilon is definitely invalid. Cost < -epsilon486// is definitely valid, so all improvement costs are designed to always give487// values < -epsilon so they will never affect validity. The first488// totalCost is designed to give priority to sharper angles. Any cost < (-1489// - epsilon) has satisfied the Delaunay condition.490double EarCost(double epsilon, IdxCollider& collider) const {491vec2 openSide = left->pos - right->pos;492const vec2 center = 0.5 * (left->pos + right->pos);493const double scale = 4 / la::dot(openSide, openSide);494const double radius = la::length(openSide) / 2;495openSide = la::normalize(openSide);496497double totalCost = la::dot(left->rightDir, rightDir) - 1 - epsilon;498if (CCW(pos, left->pos, right->pos, epsilon) == 0) {499// Clip folded ears first500return totalCost;501}502503Rect earBox = Rect(vec2(center.x - radius, center.y - radius),504vec2(center.x + radius, center.y + radius));505earBox.Union(pos);506earBox.min -= epsilon;507earBox.max += epsilon;508509const int lid = left->mesh_idx;510const int rid = right->mesh_idx;511QueryTwoDTree(collider.points, earBox, [&](PolyVert point) {512const VertItr test = collider.itr[point.idx];513if (!Clipped(test) && test->mesh_idx != mesh_idx &&514test->mesh_idx != lid &&515test->mesh_idx != rid) { // Skip duplicated verts516double cost = Cost(test, openSide, epsilon);517if (cost < -epsilon) {518cost = DelaunayCost(test->pos - center, scale, epsilon);519}520if (cost > totalCost) totalCost = cost;521}522});523return totalCost;524}525526void PrintVert() const {527#ifdef MANIFOLD_DEBUG528if (ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) return;529std::cout << "vert: " << mesh_idx << ", left: " << left->mesh_idx530<< ", right: " << right->mesh_idx << ", cost: " << cost531<< std::endl;532#endif533}534};535536static vec2 SafeNormalize(vec2 v) {537vec2 n = la::normalize(v);538return std::isfinite(n.x) ? n : vec2(0, 0);539}540541// This function and JoinPolygons are the only functions that affect the542// circular list data structure. This helps ensure it remains circular.543static void Link(VertItr left, VertItr right) {544left->right = right;545right->left = left;546left->rightDir = SafeNormalize(right->pos - left->pos);547}548549// When an ear vert is clipped, its neighbors get linked, so they get unlinked550// from it, but it is still linked to them.551static bool Clipped(VertItr v) { return v->right->left != v; }552553// Apply func to each un-clipped vert in a polygon and return an un-clipped554// vert.555template <typename F>556VertItrC Loop(VertItr first, F func) const {557VertItr v = first;558do {559if (Clipped(v)) {560// Update first to an un-clipped vert so we will return to it instead561// of infinite-looping.562first = v->right->left;563if (!Clipped(first)) {564v = first;565if (v->right == v->left) {566return polygon_.end();567}568func(v);569}570} else {571if (v->right == v->left) {572return polygon_.end();573}574func(v);575}576v = v->right;577} while (v != first);578return v;579}580581// Remove this vert from the circular list and output a corresponding582// triangle.583void ClipEar(VertItrC ear) {584Link(ear->left, ear->right);585if (ear->left->mesh_idx != ear->mesh_idx &&586ear->mesh_idx != ear->right->mesh_idx &&587ear->right->mesh_idx != ear->left->mesh_idx) {588// Filter out topological degenerates, which can form in bad589// triangulations of polygons with holes, due to vert duplication.590triangles_.push_back(591{ear->left->mesh_idx, ear->mesh_idx, ear->right->mesh_idx});592} else {593PRINT("Topological degenerate!");594}595}596597// If an ear will make a degenerate triangle, clip it early to avoid598// difficulty in key-holing. This function is recursive, as the process of599// clipping may cause the neighbors to degenerate.600void ClipIfDegenerate(VertItr ear) {601if (Clipped(ear)) {602return;603}604if (ear->left == ear->right) {605return;606}607if (ear->IsShort(epsilon_) ||608(CCW(ear->left->pos, ear->pos, ear->right->pos, epsilon_) == 0 &&609la::dot(ear->left->pos - ear->pos, ear->right->pos - ear->pos) > 0)) {610ClipEar(ear);611ClipIfDegenerate(ear->left);612ClipIfDegenerate(ear->right);613}614}615616// Build the circular list polygon structures.617std::vector<VertItr> Initialize(const PolygonsIdx& polys) {618std::vector<VertItr> starts;619const auto invalidItr = polygon_.begin();620for (const SimplePolygonIdx& poly : polys) {621auto vert = poly.begin();622polygon_.push_back({vert->idx,6230.0,624earsQueue_.end(),625vert->pos,626{0, 0},627invalidItr,628invalidItr});629const VertItr first = std::prev(polygon_.end());630631bBox_.Union(first->pos);632VertItr last = first;633// This is not the real rightmost start, but just an arbitrary vert for634// now to identify each polygon.635starts.push_back(first);636637for (++vert; vert != poly.end(); ++vert) {638bBox_.Union(vert->pos);639640polygon_.push_back({vert->idx,6410.0,642earsQueue_.end(),643vert->pos,644{0, 0},645invalidItr,646invalidItr});647VertItr next = std::prev(polygon_.end());648649Link(last, next);650last = next;651}652Link(last, first);653}654655if (epsilon_ < 0) epsilon_ = bBox_.Scale() * kPrecision;656657// Slightly more than enough, since each hole can cause two extra triangles.658triangles_.reserve(polygon_.size() + 2 * starts.size());659return starts;660}661662// Find the actual rightmost starts after degenerate removal. Also calculate663// the polygon bounding boxes.664void FindStart(VertItr first) {665const vec2 origin = first->pos;666667VertItr start = first;668double maxX = -std::numeric_limits<double>::infinity();669Rect bBox;670// Kahan summation671double area = 0;672double areaCompensation = 0;673674auto AddPoint = [&](VertItr v) {675bBox.Union(v->pos);676const double area1 =677determinant2x2(v->pos - origin, v->right->pos - origin);678const double t1 = area + area1;679areaCompensation += (area - t1) + area1;680area = t1;681682if (v->pos.x > maxX) {683maxX = v->pos.x;684start = v;685}686};687688if (Loop(first, AddPoint) == polygon_.end()) {689// No polygon left if all ears were degenerate and already clipped.690return;691}692693area += areaCompensation;694const vec2 size = bBox.Size();695const double minArea = epsilon_ * std::max(size.x, size.y);696697if (std::isfinite(maxX) && area < -minArea) {698holes_.insert(start);699hole2BBox_.insert({start, bBox});700} else {701simples_.push_back(start);702if (area > minArea) {703outers_.push_back(start);704}705}706}707708// All holes must be key-holed (attached to an outer polygon) before ear709// clipping can commence. Instead of relying on sorting, which may be710// incorrect due to epsilon, we check for polygon edges both ahead and711// behind to ensure all valid options are found.712void CutKeyhole(const VertItr start) {713const Rect bBox = hole2BBox_[start];714const int onTop = start->pos.y >= bBox.max.y - epsilon_ ? 1715: start->pos.y <= bBox.min.y + epsilon_ ? -1716: 0;717VertItr connector = polygon_.end();718719auto CheckEdge = [&](VertItr edge) {720const double x = edge->InterpY2X(start->pos, onTop, epsilon_);721if (std::isfinite(x) && start->InsideEdge(edge, epsilon_, true) &&722(connector == polygon_.end() ||723CCW({x, start->pos.y}, connector->pos, connector->right->pos,724epsilon_) == 1 ||725(connector->pos.y < edge->pos.y726? edge->InsideEdge(connector, epsilon_, false)727: !connector->InsideEdge(edge, epsilon_, false)))) {728connector = edge;729}730};731732for (const VertItr first : outers_) {733Loop(first, CheckEdge);734}735736if (connector == polygon_.end()) {737PRINT("hole did not find an outer contour!");738simples_.push_back(start);739return;740}741742connector = FindCloserBridge(start, connector);743744JoinPolygons(start, connector);745746#ifdef MANIFOLD_DEBUG747if (ManifoldParams().verbose >= TRIANGULATOR_VERBOSE_LEVEL) {748std::cout << "connected " << start->mesh_idx << " to "749<< connector->mesh_idx << std::endl;750}751#endif752}753754// This converts the initial guess for the keyhole location into the final one755// and returns it. It does so by finding any reflex verts inside the triangle756// containing the best connection and the initial horizontal line.757VertItr FindCloserBridge(VertItr start, VertItr edge) {758VertItr connector =759edge->pos.x < start->pos.x ? edge->right760: edge->right->pos.x < start->pos.x ? edge761: edge->right->pos.y - start->pos.y > start->pos.y - edge->pos.y762? edge763: edge->right;764if (la::abs(connector->pos.y - start->pos.y) <= epsilon_) {765return connector;766}767const double above = connector->pos.y > start->pos.y ? 1 : -1;768769auto CheckVert = [&](VertItr vert) {770const double inside =771above * CCW(start->pos, vert->pos, connector->pos, epsilon_);772if (vert->pos.x > start->pos.x - epsilon_ &&773vert->pos.y * above > start->pos.y * above - epsilon_ &&774(inside > 0 || (inside == 0 && vert->pos.x < connector->pos.x &&775vert->pos.y * above < connector->pos.y * above)) &&776vert->InsideEdge(edge, epsilon_, true) && vert->IsReflex(epsilon_)) {777connector = vert;778}779};780781for (const VertItr first : outers_) {782Loop(first, CheckVert);783}784785return connector;786}787788// Creates a keyhole between the start vert of a hole and the connector vert789// of an outer polygon. To do this, both verts are duplicated and reattached.790// This process may create degenerate ears, so these are clipped if necessary791// to keep from confusing subsequent key-holing operations.792void JoinPolygons(VertItr start, VertItr connector) {793polygon_.push_back(*start);794const VertItr newStart = std::prev(polygon_.end());795polygon_.push_back(*connector);796const VertItr newConnector = std::prev(polygon_.end());797798start->right->left = newStart;799connector->left->right = newConnector;800Link(start, connector);801Link(newConnector, newStart);802803ClipIfDegenerate(start);804ClipIfDegenerate(newStart);805ClipIfDegenerate(connector);806ClipIfDegenerate(newConnector);807}808809// Recalculate the cost of the Vert v ear, updating it in the queue by810// removing and reinserting it.811void ProcessEar(VertItr v, IdxCollider& collider) {812if (v->ear != earsQueue_.end()) {813earsQueue_.erase(v->ear);814v->ear = earsQueue_.end();815}816if (v->IsShort(epsilon_)) {817v->cost = kBest;818v->ear = earsQueue_.insert(v);819} else if (v->IsConvex(2 * epsilon_)) {820v->cost = v->EarCost(epsilon_, collider);821v->ear = earsQueue_.insert(v);822} else {823v->cost = 1; // not used, but marks reflex verts for debug824}825}826827// Create a collider of all vertices in this polygon, each expanded by828// epsilon_. Each ear uses this BVH to quickly find a subset of vertices to829// check for cost.830IdxCollider VertCollider(VertItr start) const {831ZoneScoped;832std::vector<VertItr> itr;833Vec<PolyVert> points;834Loop(start, [&itr, &points](VertItr v) {835points.push_back({v->pos, static_cast<int>(itr.size())});836itr.push_back(v);837});838839BuildTwoDTree(points);840return {std::move(points), std::move(itr)};841}842843// The main ear-clipping loop. This is called once for each simple polygon -844// all holes have already been key-holed and joined to an outer polygon.845void TriangulatePoly(VertItr start) {846ZoneScoped;847848IdxCollider vertCollider = VertCollider(start);849850if (vertCollider.itr.empty()) {851PRINT("Empty poly");852return;853}854855// A simple polygon always creates two fewer triangles than it has verts.856int numTri = -2;857earsQueue_.clear();858859auto QueueVert = [&](VertItr v) {860ProcessEar(v, vertCollider);861++numTri;862v->PrintVert();863};864865VertItrC v = Loop(start, QueueVert);866if (v == polygon_.end()) return;867Dump(v);868869while (numTri > 0) {870const qItr ear = earsQueue_.begin();871if (ear != earsQueue_.end()) {872v = *ear;873// Cost should always be negative, generally < -epsilon.874v->PrintVert();875earsQueue_.erase(ear);876} else {877PRINT("No ear found!");878}879880ClipEar(v);881--numTri;882883ProcessEar(v->left, vertCollider);884ProcessEar(v->right, vertCollider);885// This is a backup vert that is used if the queue is empty (geometrically886// invalid polygon), to ensure manifoldness.887v = v->right;888}889890DEBUG_ASSERT(v->right == v->left, logicErr, "Triangulator error!");891PRINT("Finished poly");892}893894void Dump(VertItrC start) const {895#ifdef MANIFOLD_DEBUG896if (ManifoldParams().verbose < TRIANGULATOR_VERBOSE_LEVEL) return;897VertItrC v = start;898std::cout << "show(array([" << std::setprecision(15) << std::endl;899do {900std::cout << " [" << v->pos.x << ", " << v->pos.y << "],# "901<< v->mesh_idx << ", cost: " << v->cost << std::endl;902v = v->right;903} while (v != start);904std::cout << " [" << v->pos.x << ", " << v->pos.y << "],# " << v->mesh_idx905<< std::endl;906std::cout << "]))" << std::endl;907908v = start;909std::cout << "polys.push_back({" << std::setprecision(15) << std::endl;910do {911std::cout << " {" << v->pos.x << ", " << v->pos.y << "}, //"912<< std::endl;913v = v->right;914} while (v != start);915std::cout << "});" << std::endl;916#endif917}918};919} // namespace920921namespace manifold {922923/**924* @brief Triangulates a set of ε-valid polygons. If the input is not925* ε-valid, the triangulation may overlap, but will always return a926* manifold result that matches the input edge directions.927*928* @param polys The set of polygons, wound CCW and representing multiple929* polygons and/or holes. These have 2D-projected positions as well as930* references back to the original vertices.931* @param epsilon The value of ε, bounding the uncertainty of the932* input.933* @param allowConvex If true (default), the triangulator will use a fast934* triangulation if the input is convex, falling back to ear-clipping if not.935* The triangle quality may be lower, so set to false to disable this936* optimization.937* @return std::vector<ivec3> The triangles, referencing the original938* vertex indicies.939*/940std::vector<ivec3> TriangulateIdx(const PolygonsIdx& polys, double epsilon,941bool allowConvex) {942std::vector<ivec3> triangles;943double updatedEpsilon = epsilon;944#ifdef MANIFOLD_DEBUG945try {946#endif947if (allowConvex && IsConvex(polys, epsilon)) { // fast path948triangles = TriangulateConvex(polys);949} else {950EarClip triangulator(polys, epsilon);951triangles = triangulator.Triangulate();952updatedEpsilon = triangulator.GetPrecision();953}954#ifdef MANIFOLD_DEBUG955if (ManifoldParams().intermediateChecks) {956CheckTopology(triangles, polys);957if (!ManifoldParams().processOverlaps) {958CheckGeometry(triangles, polys, 2 * updatedEpsilon);959}960}961} catch (const geometryErr& e) {962if (!ManifoldParams().suppressErrors) {963PrintFailure(e, polys, triangles, updatedEpsilon);964}965throw;966} catch (const std::exception& e) {967PrintFailure(e, polys, triangles, updatedEpsilon);968throw;969}970#endif971return triangles;972}973974/**975* @brief Triangulates a set of ε-valid polygons. If the input is not976* ε-valid, the triangulation may overlap, but will always return a977* manifold result that matches the input edge directions.978*979* @param polygons The set of polygons, wound CCW and representing multiple980* polygons and/or holes.981* @param epsilon The value of ε, bounding the uncertainty of the982* input.983* @param allowConvex If true (default), the triangulator will use a fast984* triangulation if the input is convex, falling back to ear-clipping if not.985* The triangle quality may be lower, so set to false to disable this986* optimization.987* @return std::vector<ivec3> The triangles, referencing the original988* polygon points in order.989*/990std::vector<ivec3> Triangulate(const Polygons& polygons, double epsilon,991bool allowConvex) {992int idx = 0;993PolygonsIdx polygonsIndexed;994for (const auto& poly : polygons) {995SimplePolygonIdx simpleIndexed;996for (const vec2& polyVert : poly) {997simpleIndexed.push_back({polyVert, idx++});998}999polygonsIndexed.push_back(simpleIndexed);1000}1001return TriangulateIdx(polygonsIndexed, epsilon, allowConvex);1002}10031004} // namespace manifold100510061007