Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
booleans.hpp
Go to the documentation of this file.
1#pragma once
2
4
47
48#include <algorithm>
49#include <array>
50#include <cassert>
51#include <cstdint>
52#include <cstddef>
53#include <limits>
54#include <map>
55#include <numeric>
56#include <ranges>
57#include <type_traits>
58#include <variant>
59#include <vector>
60
61namespace pgl {
62
63namespace detail {
64
74template <class ExactPoint>
75void dropCollinearRingVertices(std::vector<ExactPoint>& ring) {
76 bool changed = true;
77 while (changed && ring.size() > 3) {
78 changed = false;
79 for (std::size_t i = 0; i < ring.size() && ring.size() > 3;) {
80 const ExactPoint& previous = ring[(i + ring.size() - 1) % ring.size()];
81 const ExactPoint& next = ring[(i + 1) % ring.size()];
82 if (collinear(previous, ring[i], next)) {
83 ring.erase(ring.begin() + static_cast<std::ptrdiff_t>(i));
84 changed = true;
85 } else {
86 ++i;
87 }
88 }
89 }
90}
91
92// ringOrientation and splitWalkIntoRings, which the extraction below also
93// uses, live beside the arrangement in algorithm/arrangement.hpp: they are
94// what turns any cell complex's boundary walks into rings.
95
117template <class ResultPoint, class ExactPoint>
118PolygonSet<ResultPoint> regularizedCellsFromKeep(
119 const Arrangement<ExactPoint>& arrangement, const std::vector<char>& keep) {
120 using HalfedgeId = typename Arrangement<ExactPoint>::HalfedgeId;
121 using ExactNumber = typename ExactPoint::NumberType;
122 using ExactPolygon = Polygon<ExactPoint>;
123 using ResultPolygon = Polygon<ResultPoint>;
124 constexpr std::size_t none = std::numeric_limits<std::size_t>::max();
125
126 std::vector<PolygonWithHoles<ResultPoint>> result;
127 const auto isKept = [&](HalfedgeId h) { return keep[arrangement.face(h).index()] != 0; };
128
129 // Kept faces sharing an edge are one piece of the result. Faces meeting at a
130 // single vertex are not: the result pinches shut there, and the two sides
131 // have to come back as two regions, since neither a polygon nor a region may
132 // have a self-touching outer ring.
133 std::vector<std::size_t> parent(arrangement.faceCount());
134 for (std::size_t i = 0; i < parent.size(); ++i) {
135 parent[i] = i;
136 }
137 const auto findRoot = [&parent](std::size_t x) {
138 while (parent[x] != x) {
139 parent[x] = parent[parent[x]];
140 x = parent[x];
141 }
142 return x;
143 };
144 std::vector<HalfedgeId> boundary;
145 for (std::uint32_t i = 0; i < arrangement.halfedgeCount(); ++i) {
146 const HalfedgeId h(i);
147 if (!isKept(h)) {
148 continue;
149 }
150 if (isKept(arrangement.twin(h))) {
151 parent[findRoot(arrangement.face(h).index())] =
152 findRoot(arrangement.face(arrangement.twin(h)).index());
153 } else {
154 boundary.push_back(h);
155 }
156 }
157
158 // Walking the boundary: from a halfedge with kept material on its left and
159 // none across it, follow the face's own cycle, stepping over any edge whose
160 // far side is kept too — that is the rotation around the shared vertex the
161 // DCEL stores, and it stops at the far side of the same fan of kept faces.
162 // An edge kept on both sides is interior to the result and is skipped, which
163 // is what regularizes a slit away.
164 const auto nextBoundary = [&](HalfedgeId h) {
165 HalfedgeId ahead = arrangement.next(h);
166 while (isKept(arrangement.twin(ahead))) {
167 ahead = arrangement.next(arrangement.twin(ahead));
168 }
169 return ahead;
170 };
171
172 std::map<std::size_t, std::vector<std::vector<ExactPoint>>> ringsOfPiece;
173 std::vector<char> walked(arrangement.halfedgeCount(), 0);
174 for (const HalfedgeId start : boundary) {
175 if (walked[start.index()] != 0) {
176 continue;
177 }
178 std::vector<ExactPoint> walk;
179 HalfedgeId h = start;
180 do {
181 walked[h.index()] = 1;
182 walk.push_back(arrangement[arrangement.source(h)]);
183 h = nextBoundary(h);
184 } while (h != start);
185 splitWalkIntoRings(walk, ringsOfPiece[findRoot(arrangement.face(start).index())]);
186 }
187
188 // Converting an already canonical ring into the caller's coordinates keeps
189 // it canonical whenever the conversion is the identity, and only then: an
190 // integral result type truncates, which can reorder the vertices or flip the
191 // ring, so that case is renormalized as usual.
192 const auto convert = [](const std::vector<ExactPoint>& ring) {
193 std::vector<ResultPoint> converted;
194 converted.reserve(ring.size());
195 for (const ExactPoint& vertex : ring) {
196 converted.emplace_back(vertex);
197 }
198 constexpr bool exact = std::is_same_v<ResultPoint, ExactPoint>;
199 return ResultPolygon(std::move(converted), /*trusted=*/exact);
200 };
201
202 for (auto& entry : ringsOfPiece) {
203 std::vector<ExactPolygon> outers;
204 std::vector<ExactPolygon> holes;
205 for (std::vector<ExactPoint>& ring : entry.second) {
206 dropCollinearRingVertices(ring);
207 const int orientation = ringOrientation(ring);
208 if (orientation == 0) {
209 continue; // a ring bounding no area is not part of any region
210 }
211 // The walks come out of a planar subdivision, so every ring here is
212 // simple and its orientation is one predicate away. Putting it into
213 // canonical form by hand then keeps @ref Polygon from measuring it
214 // all over again, which over rationals costs more than everything
215 // else in this function put together.
216 if (orientation < 0) {
217 std::reverse(ring.begin(), ring.end());
218 }
219 std::rotate(ring.begin(), std::min_element(ring.begin(), ring.end()), ring.end());
220 (orientation > 0 ? outers : holes).emplace_back(std::move(ring), /*trusted=*/true);
221 }
222 if (outers.empty()) {
223 continue;
224 }
225
226 // An edge-connected piece has a single outer ring unless it pinches shut
227 // at a vertex, where the walk above cuts it into one ring per side. Each
228 // hole then goes to the smallest outer ring holding it, decided by a
229 // witness strictly inside the hole (the rings meet at most along their
230 // boundaries, so the closed containment is unambiguous).
231 std::vector<std::vector<ResultPolygon>> holesOfOuter(outers.size());
232 for (const ExactPolygon& hole : holes) {
233 std::size_t owner = 0;
234 if (outers.size() > 1) {
235 const ExactPoint witness = hole.template pointInside<ExactNumber>();
236 std::size_t best = none;
237 for (std::size_t i = 0; i < outers.size(); ++i) {
238 if (outers[i].contains(witness) &&
239 (best == none || outers[i].twiceArea() < outers[best].twiceArea())) {
240 best = i;
241 }
242 }
243 owner = best == none ? 0 : best;
244 }
245 holesOfOuter[owner].push_back(convert(hole.vertices()));
246 }
247 // The same bargain `convert` strikes for a ring, struck once more for
248 // the region: its holes are canonical polygons already, and not one of
249 // them bounds no area — a ring whose orientation vanished was dropped
250 // above — so all the region's own normalization would find to do is
251 // sort them, after measuring every one of them exactly to learn what is
252 // already known. Over rationals that measurement is the single most
253 // expensive thing this function does. A result type that is not the
254 // exact one may collapse a ring on the way, so it still normalizes.
255 constexpr bool exact = std::is_same_v<ResultPoint, ExactPoint>;
256 for (std::size_t i = 0; i < outers.size(); ++i) {
257 if constexpr (exact) {
258 std::sort(holesOfOuter[i].begin(), holesOfOuter[i].end());
259 }
260 result.emplace_back(convert(outers[i].vertices()), std::move(holesOfOuter[i]),
261 /*trusted=*/exact);
262 }
263 }
264 // The pieces have pairwise disjoint interiors and share no stretch of edge —
265 // an edge kept on both sides is interior to the result and never reaches the
266 // boundary walk — so they are a canonical PolygonSet once sorted, and the
267 // set adopts them without measuring a single area again.
268 std::sort(result.begin(), result.end());
269 return PolygonSet<ResultPoint>(std::move(result), /*trusted=*/true);
270}
271
284template <class ExactPoint>
285Arrangement<ExactPoint> framedArrangement(const std::vector<Segment<ExactPoint>>& cuts) {
286 using ExactNumber = typename ExactPoint::NumberType;
287 using ExactSegment = Segment<ExactPoint>;
288
289 assert(!cuts.empty());
290 ExactNumber loX = cuts.front().min().x();
291 ExactNumber loY = cuts.front().min().y();
292 ExactNumber hiX = loX;
293 ExactNumber hiY = loY;
294 for (const ExactSegment& cut : cuts) {
295 for (const ExactPoint& point : {cut.min(), cut.max()}) {
296 loX = std::min(loX, point.x());
297 loY = std::min(loY, point.y());
298 hiX = std::max(hiX, point.x());
299 hiY = std::max(hiY, point.y());
300 }
301 }
302 const ExactNumber margin(1);
303 const std::array<ExactPoint, 4> corners{
304 ExactPoint(loX - margin, loY - margin), ExactPoint(hiX + margin, loY - margin),
305 ExactPoint(hiX + margin, hiY + margin), ExactPoint(loX - margin, hiY + margin)};
306 std::vector<ExactSegment> segments = cuts;
307 for (std::size_t i = 0; i < corners.size(); ++i) {
308 segments.emplace_back(corners[i], corners[(i + 1) % corners.size()]);
309 }
310 return Arrangement<ExactPoint>(segments);
311}
312
317template <class ResultPoint, class ExactPoint, class KeepWitness>
318PolygonSet<ResultPoint> regularizedCells(
319 const std::vector<Segment<ExactPoint>>& cuts, KeepWitness keepWitness) {
320 using ExactNumber = typename ExactPoint::NumberType;
321
322 if (cuts.empty()) {
323 return {}; // no operand has an edge, so none has area
324 }
325
326 const Arrangement<ExactPoint> arrangement = framedArrangement(cuts);
327 using FaceId = typename Arrangement<ExactPoint>::FaceId;
328 std::vector<char> keep(arrangement.faceCount(), 0);
329 for (std::uint32_t i = 0; i < arrangement.faceCount(); ++i) {
330 const FaceId f(i);
331 if (!arrangement.isUnbounded(f)) {
332 keep[f.index()] = static_cast<char>(
333 keepWitness(arrangement.template witness<ExactNumber>(f)));
334 }
335 }
336 return regularizedCellsFromKeep<ResultPoint>(arrangement, keep);
337}
338
364template <class ResultPoint, class ShapeA, class ShapeB, class KeepCell>
365PolygonSet<ResultPoint> regularizedBoolean(const ShapeA& a, const ShapeB& b, KeepCell keepCell) {
366 using ExactNumber = Exact1DNumber<typename ShapeA::NumberType, typename ShapeB::NumberType>;
367 using ExactPoint = Point<ExactNumber>;
368
369 const bool aHasArea = !a.isDegenerate();
370 const bool bHasArea = !b.isDegenerate();
371
372 std::vector<Segment<ExactPoint>> cuts;
373 appendCutSegments<ExactPoint>(a, cuts);
374 appendCutSegments<ExactPoint>(b, cuts);
375 return regularizedCells<ResultPoint>(
376 cuts, [&a, &b, aHasArea, bHasArea, &keepCell](const ExactPoint& witness) {
377 return keepCell(aHasArea && a.contains(witness), bHasArea && b.contains(witness));
378 });
379}
380
399template <class ResultPoint, class ShapeType>
400PolygonSet<ResultPoint> regularizedUnionByCoverage(
401 const std::vector<ShapeType>& distinct) {
402 using ShapeNumber = typename ShapeType::NumberType;
403 using ExactNumber = Exact1DNumber<ShapeNumber, ShapeNumber>;
404 using ExactPoint = Point<ExactNumber>;
405
406 // Start outside every piece in the unbounded face, then propagate those
407 // parity bits across the arrangement's face adjacency graph.
408 const Arrangement<ExactPoint> arrangement(distinct, detail::simpleBoundaries);
409 using HalfedgeId = typename Arrangement<ExactPoint>::HalfedgeId;
410 using FaceId = typename Arrangement<ExactPoint>::FaceId;
411 const std::size_t faceCount = arrangement.faceCount();
412 const std::size_t pieceCount = distinct.size();
413 constexpr std::size_t wordBits = 64;
414 const std::size_t words = (pieceCount + wordBits - 1) / wordBits;
415
416 // The halfedges bounding each face, as one pair of arrays rather than a
417 // vector per face: the face count runs to hundreds of thousands here, and
418 // that many separate allocations costs more than the traversal.
419 std::vector<std::uint32_t> faceEdgeBegin(faceCount + 1, 0);
420 for (std::uint32_t i = 0; i < arrangement.halfedgeCount(); ++i) {
421 const HalfedgeId h(i);
422 ++faceEdgeBegin[arrangement.face(h).index() + 1];
423 }
424 for (std::size_t i = 0; i < faceCount; ++i) {
425 faceEdgeBegin[i + 1] += faceEdgeBegin[i];
426 }
427 std::vector<std::uint32_t> faceEdge(arrangement.halfedgeCount(), 0);
428 {
429 std::vector<std::uint32_t> cursor(faceEdgeBegin.begin(), faceEdgeBegin.end() - 1);
430 for (std::uint32_t i = 0; i < arrangement.halfedgeCount(); ++i) {
431 const HalfedgeId h(i);
432 faceEdge[cursor[arrangement.face(h).index()]++] = h.index();
433 }
434 }
435
436 // One shared membership word set, not one per face. Walking a spanning
437 // tree of the face adjacency graph depth first, a face's membership is
438 // its parent's with the crossed edge's origins toggled, so the descent
439 // toggles them and the return toggles them back. The flip is its own
440 // inverse in the coverage count too — a bit that was clear counts one
441 // more piece, a bit that was set one fewer — so both restore exactly.
442 // Storing membership per face instead would be faces x pieces bits,
443 // nearly half a gigabyte on the largest shape-pair cell.
444 std::vector<std::uint64_t> membership(words, 0);
445 std::size_t covered = 0;
446 const auto crossEdge = [&](std::uint32_t halfedge) {
447 for (const std::uint32_t origin : arrangement.originsOf(HalfedgeId(halfedge))) {
448 const std::size_t word = origin / wordBits;
449 const std::uint64_t mask = std::uint64_t{1} << (origin % wordBits);
450 if ((membership[word] & mask) == 0) {
451 ++covered;
452 } else {
453 --covered;
454 }
455 membership[word] ^= mask;
456 }
457 };
458
459 struct Frame {
460 std::uint32_t face;
461 std::uint32_t cursor; // next index into faceEdge
462 std::uint32_t entered; // halfedge crossed to get here, or noEdge at the root
463 };
464 constexpr std::uint32_t noEdge = ~std::uint32_t{0};
465
466 std::vector<std::size_t> coverage(faceCount, 0);
467 std::vector<char> seen(faceCount, 0);
468 std::vector<Frame> stack;
469 // Face 0 is the unbounded one, which lies outside every piece.
470 stack.push_back(Frame{0, faceEdgeBegin[0], noEdge});
471 seen[0] = 1;
472
473 while (!stack.empty()) {
474 Frame& top = stack.back();
475 if (top.cursor == faceEdgeBegin[top.face + 1]) {
476 if (top.entered != noEdge) {
477 crossEdge(top.entered); // undo, restoring the parent's state
478 }
479 stack.pop_back();
480 continue;
481 }
482 const std::uint32_t h = faceEdge[top.cursor++];
483 const std::uint32_t next = arrangement.face(arrangement.twin(HalfedgeId(h))).index();
484 if (seen[next] != 0) {
485 continue;
486 }
487 seen[next] = 1;
488 crossEdge(h);
489 coverage[next] = covered;
490 // `top` may dangle after this, so nothing above may be used again.
491 stack.push_back(Frame{next, faceEdgeBegin[next], h});
492 }
493
494 assert(covered == 0); // every descent undone
495 assert(std::ranges::all_of(seen, [](char value) { return value != 0; }));
496 std::vector<char> keep(faceCount, 0);
497 for (std::uint32_t i = 0; i < arrangement.faceCount(); ++i) {
498 const FaceId f(i);
499 keep[f.index()] = static_cast<char>(coverage[f.index()] != 0);
500 }
501 return regularizedCellsFromKeep<ResultPoint>(arrangement, keep);
502}
503
504// The boundary of a union is a subset of the operands' boundaries. For a set
505// of lattice triangles, find that subset directly: another convex triangle
506// covers one interval of an edge, obtained by clipping the edge's supporting
507// line against its three half-planes. Subtracting those intervals leaves only
508// the exposed pieces. All the rejection and clipping arithmetic stays in
509// int128; rationals are constructed only for actual interval ends on the output
510// boundary. The final, usually small arrangement turns those pieces into the
511// canonical PolygonSet and handles touching/collinear degeneracies centrally.
512//
513// The subtraction is what keeps the scan from being quadratic in earnest. An
514// edge carries what is still uncovered of it, as a short sorted list of
515// disjoint intervals, and a triangle that covers nothing left of it costs one
516// clip and nothing else; the moment the list empties the edge is done and the
517// remaining triangles are never looked at. Only an edge with a piece on the
518// output boundary is ever tested against all of them, so the work is
519// n x (output complexity) rather than n² whenever the union covers itself —
520// which is the case a union of many overlapping pieces actually is. Testing the
521// largest triangles first is the same bet: a big one covers a long stretch of
522// whatever it meets, so the list empties in fewer tests.
523template <class ResultPoint, class TriangleType>
524std::optional<PolygonSet<ResultPoint>> regularizedUnionOfIntegralTriangles(
525 const std::vector<TriangleType>& triangles) {
526 using ShapeNumber = typename TriangleType::NumberType;
527 using ExactNumber = Exact1DNumber<ShapeNumber, ShapeNumber>;
528 using ExactPoint = Point<ExactNumber>;
529 using ExactSegment = Segment<ExactPoint>;
530 using Wide = int128;
531
532 struct IPoint {
533 std::int64_t x;
534 std::int64_t y;
535 };
536 struct ITriangle {
537 std::array<IPoint, 3> vertex;
538 std::int64_t minX, minY, maxX, maxY;
539 Wide twiceArea;
540 };
541 constexpr std::int64_t safeCoordinate = 1000000000;
542 const auto narrow = [](const ShapeNumber& value) -> std::optional<std::int64_t> {
543 if constexpr (is_Rational_v<ShapeNumber>) {
544 if (!value.isInteger()) {
545 return std::nullopt;
546 }
547 using Integer = rational_int_t<ShapeNumber>;
548 const Integer integer = static_cast<Integer>(value);
549 if (!detail::representableAs<std::int64_t>(integer)) {
550 return std::nullopt;
551 }
552 return detail::narrowTo<std::int64_t>(integer);
553 } else if constexpr (detail::extended_integral<ShapeNumber> ||
554 std::same_as<ShapeNumber, BigInt>) {
555 if (!detail::representableAs<std::int64_t>(value)) {
556 return std::nullopt;
557 }
558 return detail::narrowTo<std::int64_t>(value);
559 } else {
560 return std::nullopt;
561 }
562 };
563
564 const auto cross = [](Wide ax, Wide ay, Wide bx, Wide by) {
565 return ax * by - ay * bx;
566 };
567
568 std::vector<ITriangle> integral;
569 integral.reserve(triangles.size());
570 for (const TriangleType& triangle : triangles) {
571 ITriangle converted{};
572 const auto vertices = triangle.vertices();
573 for (std::size_t i = 0; i < 3; ++i) {
574 const auto x = narrow(vertices[i].x());
575 const auto y = narrow(vertices[i].y());
576 if (!x || !y) {
577 return std::nullopt;
578 }
579 if (*x < -safeCoordinate || safeCoordinate < *x ||
580 *y < -safeCoordinate || safeCoordinate < *y) {
581 return std::nullopt;
582 }
583 converted.vertex[i] = {*x, *y};
584 }
585 converted.minX = converted.maxX = converted.vertex[0].x;
586 converted.minY = converted.maxY = converted.vertex[0].y;
587 for (const IPoint& point : converted.vertex) {
588 converted.minX = std::min(converted.minX, point.x);
589 converted.minY = std::min(converted.minY, point.y);
590 converted.maxX = std::max(converted.maxX, point.x);
591 converted.maxY = std::max(converted.maxY, point.y);
592 }
593 const IPoint& a = converted.vertex[0];
594 const IPoint& b = converted.vertex[1];
595 const IPoint& c = converted.vertex[2];
596 converted.twiceArea =
597 cross(Wide(b.x) - a.x, Wide(b.y) - a.y, Wide(c.x) - a.x, Wide(c.y) - a.y);
598 if (converted.twiceArea < 0) {
599 converted.twiceArea = -converted.twiceArea;
600 }
601 integral.push_back(converted);
602 }
603
604 // The order the coverers are tested in, largest first.
605 std::vector<std::uint32_t> order(integral.size());
606 std::iota(order.begin(), order.end(), std::uint32_t{0});
607 std::sort(order.begin(), order.end(), [&integral](std::uint32_t left, std::uint32_t right) {
608 return integral[left].twiceArea > integral[right].twiceArea;
609 });
610
611 struct Fraction {
612 Wide numerator;
613 Wide denominator;
614 };
615 const auto fraction = [](Wide numerator, Wide denominator) {
616 if (denominator < 0) {
617 numerator = -numerator;
618 denominator = -denominator;
619 }
620 return Fraction{numerator, denominator};
621 };
622 const auto less = [](const Fraction& left, const Fraction& right) {
623 return left.numerator * right.denominator <
624 right.numerator * left.denominator;
625 };
626 const auto maximum = [&less](const Fraction& left, const Fraction& right) {
627 return less(left, right) ? right : left;
628 };
629 const auto minimum = [&less](const Fraction& left, const Fraction& right) {
630 return less(left, right) ? left : right;
631 };
632 const auto exact = [](const Fraction& value) {
633 return ExactNumber(BigInt(value.numerator), BigInt(value.denominator));
634 };
635 constexpr Fraction zero{0, 1};
636 constexpr Fraction one{1, 1};
637 std::vector<ExactSegment> exposed;
638 // What is still uncovered of the edge being scanned: sorted, disjoint, and
639 // usually one interval or none.
640 std::vector<std::pair<Fraction, Fraction>> uncovered;
641
642 for (std::size_t owner = 0; owner < integral.size(); ++owner) {
643 const ITriangle& mine = integral[owner];
644 for (std::size_t edge = 0; edge < 3; ++edge) {
645 const IPoint from = mine.vertex[edge];
646 const IPoint to = mine.vertex[(edge + 1) % 3];
647 const Wide dx = Wide(to.x) - from.x;
648 const Wide dy = Wide(to.y) - from.y;
649 const std::int64_t edgeMinX = std::min(from.x, to.x);
650 const std::int64_t edgeMinY = std::min(from.y, to.y);
651 const std::int64_t edgeMaxX = std::max(from.x, to.x);
652 const std::int64_t edgeMaxY = std::max(from.y, to.y);
653 uncovered.assign(1, {zero, one});
654
655 for (const std::uint32_t other : order) {
656 const ITriangle& theirs = integral[other];
657 if (other == owner || edgeMaxX < theirs.minX || theirs.maxX < edgeMinX ||
658 edgeMaxY < theirs.minY || theirs.maxY < edgeMinY) {
659 continue;
660 }
661 Fraction low = zero;
662 Fraction high = one;
663 bool feasible = true;
664 bool coincident = false;
665 bool interiorOnRight = false;
666 for (std::size_t wall = 0; wall < 3; ++wall) {
667 const IPoint a = theirs.vertex[wall];
668 const IPoint b = theirs.vertex[(wall + 1) % 3];
669 const Wide wx = Wide(b.x) - a.x;
670 const Wide wy = Wide(b.y) - a.y;
671 const Wide c = cross(wx, wy, Wide(from.x) - a.x,
672 Wide(from.y) - a.y);
673 const Wide slope = cross(wx, wy, dx, dy);
674 if (slope == 0) {
675 if (c < 0) {
676 feasible = false;
677 break;
678 }
679 if (c == 0) {
680 coincident = true;
681 interiorOnRight = wx * dx + wy * dy < 0;
682 }
683 continue;
684 }
685 const Fraction at = fraction(-c, slope);
686 if (slope > 0) {
687 low = maximum(low, at);
688 } else {
689 high = minimum(high, at);
690 }
691 if (!less(low, high)) {
692 feasible = false;
693 break;
694 }
695 }
696 if (!feasible || !less(low, high) || (coincident && !interiorOnRight)) {
697 continue;
698 }
699
700 // Subtract [low, high]. Only the intervals it meets are
701 // touched: those from the first one ending past `low` to the
702 // last one starting before `high`. They collapse to at most a
703 // head remnant and a tail remnant, so the list grows by one
704 // only where a single interval is split in two.
705 std::size_t first = 0;
706 while (first < uncovered.size() && !less(low, uncovered[first].second)) {
707 ++first;
708 }
709 std::size_t last = first;
710 while (last < uncovered.size() && less(uncovered[last].first, high)) {
711 ++last;
712 }
713 if (first == last) {
714 continue;
715 }
716 const Fraction head = uncovered[first].first;
717 const Fraction tail = uncovered[last - 1].second;
718 const bool keepHead = less(head, low);
719 const bool keepTail = less(high, tail);
720 if (keepHead && keepTail && last - first == 1) {
721 uncovered[first] = {head, low};
722 uncovered.insert(uncovered.begin() + static_cast<std::ptrdiff_t>(first) + 1,
723 {high, tail});
724 } else {
725 std::size_t write = first;
726 if (keepHead) {
727 uncovered[write++] = {head, low};
728 }
729 if (keepTail) {
730 uncovered[write++] = {high, tail};
731 }
732 uncovered.erase(uncovered.begin() + static_cast<std::ptrdiff_t>(write),
733 uncovered.begin() + static_cast<std::ptrdiff_t>(last));
734 }
735 if (uncovered.empty()) {
736 break;
737 }
738 }
739
740 const auto pointAt = [&](const Fraction& parameter) {
741 const ExactNumber t = exact(parameter);
742 return ExactPoint(ExactNumber(from.x) + ExactNumber(dx) * t,
743 ExactNumber(from.y) + ExactNumber(dy) * t);
744 };
745 for (const auto& piece : uncovered) {
746 exposed.emplace_back(pointAt(piece.first), pointAt(piece.second));
747 }
748 }
749 }
750
751 return regularizedCells<ResultPoint>(exposed, [&triangles](const ExactPoint& witness) {
752 return std::ranges::any_of(
753 triangles, [&witness](const TriangleType& triangle) { return triangle.contains(witness); });
754 });
755}
756
757} // namespace detail
758
779template <class ResultPoint, class ShapeRange>
781 bool simpleBoundaries = false) {
782 using ShapeType = std::ranges::range_value_t<ShapeRange>;
783
784 // What survives to be unioned. A shape with no interior contributes nothing
785 // to `closure(union of interiors)`, so dropping it cannot change the result
786 // on either path below — and the parity argument on the covered one *needs*
787 // it gone: such a boundary is traversed twice, but the two traversals
788 // coincide and the arrangement merges them into a single edge carrying that
789 // origin once, so crossing it would toggle an odd number of times and report
790 // the far side as inside. A repeat contributes nothing either, and would be
791 // tested again for every face it covers. Both go before the sort, which then
792 // has less to order.
793 std::vector<ShapeType> distinct(std::ranges::begin(shapes), std::ranges::end(shapes));
794 std::erase_if(distinct, [](const ShapeType& shape) { return shape.isDegenerate(); });
795 std::sort(distinct.begin(), distinct.end());
796 distinct.erase(std::unique(distinct.begin(), distinct.end()), distinct.end());
797
798 if (distinct.empty()) {
799 return {};
800 }
801
802 if constexpr (detail::is_polygon_set_v<ShapeType>) {
803 // A set is already a union of regions with disjoint interiors, and it is
804 // those regions the paths below classify — a set is not itself something
805 // an @ref Arrangement can be built from. Separating them also weakens
806 // what `simpleBoundaries` has to promise: a stretch of boundary two
807 // components share is two origins once they are apart, which is the two
808 // crossings parity needs to see.
809 std::vector<typename ShapeType::ComponentType> components;
810 for (const ShapeType& set : distinct) {
811 components.insert(components.end(), set.components().begin(), set.components().end());
812 }
813 return regularizedUnionOf<ResultPoint>(components, simpleBoundaries);
814 } else if constexpr (detail::is_convex_v<ShapeType> || detail::is_triangle_v<ShapeType> ||
815 detail::is_rectangle_v<ShapeType>) {
816 if constexpr (detail::is_triangle_v<ShapeType> &&
817 !std::floating_point<typename ShapeType::NumberType>) {
818 if (distinct.size() >= 16) {
819 if (auto result =
820 detail::regularizedUnionOfIntegralTriangles<ResultPoint>(distinct)) {
821 return std::move(*result);
822 }
823 }
824 }
825 return detail::regularizedUnionByCoverage<ResultPoint>(distinct);
826 } else {
827 using ShapeNumber = typename ShapeType::NumberType;
829
830 if (simpleBoundaries) {
831 return detail::regularizedUnionByCoverage<ResultPoint>(distinct);
832 }
833 std::vector<Segment<ExactPoint>> cuts;
834 for (const ShapeType& shape : distinct) {
835 detail::appendCutSegments<ExactPoint>(shape, cuts);
836 }
837 return detail::regularizedCells<ResultPoint>(cuts, [&distinct](const ExactPoint& witness) {
838 return std::ranges::any_of(
839 distinct, [&witness](const ShapeType& shape) { return shape.contains(witness); });
840 });
841 }
842}
843
844namespace detail {
845
855template <class OtherShape>
856constexpr decltype(auto) booleanOperand(const OtherShape& other) {
857 if constexpr (is_rectangle_v<OtherShape> || is_triangle_v<OtherShape> ||
858 is_convex_v<OtherShape>) {
859 return other.asPolygon();
860 } else {
861 return (other);
862 }
863}
864
866template <class ResultPoint, class ShapeA, class ShapeB>
867PolygonSet<ResultPoint> regularizedDifference(const ShapeA& a, const ShapeB& b) {
868 return regularizedBoolean<ResultPoint>(a, b, [](bool inA, bool inB) { return inA && !inB; });
869}
870
872template <class ResultPoint, class ShapeA, class ShapeB>
873PolygonSet<ResultPoint> regularizedUnion(const ShapeA& a, const ShapeB& b) {
874 return regularizedBoolean<ResultPoint>(a, b, [](bool inA, bool inB) { return inA || inB; });
875}
876
894template <class ResultPoint, class ShapeA, class ShapeB>
895PolygonSet<ResultPoint> regularizedIntersection(const ShapeA& a, const ShapeB& b) {
896 if (!a.bbox().interiorsIntersect(b.bbox())) {
897 return {};
898 }
899 return regularizedBoolean<ResultPoint>(a, b, [](bool inA, bool inB) { return inA && inB; });
900}
901
936template <class ResultPoint, class ShapeA, class ShapeB>
937std::vector<std::variant<ResultPoint, Polyline<ResultPoint>, PolygonWithHoles<ResultPoint>>>
938literalIntersection(const ShapeA& a, const ShapeB& b) {
939 using ExactNumber = Exact1DNumber<typename ShapeA::NumberType, typename ShapeB::NumberType>;
940 using ExactPoint = Point<ExactNumber>;
941 using ResultPolyline = Polyline<ResultPoint>;
942 using ResultRegion = PolygonWithHoles<ResultPoint>;
943 using Piece = std::variant<ResultPoint, ResultPolyline, ResultRegion>;
944
945 std::vector<Piece> pieces;
946 // Boxes that miss each other settle the whole answer, this time including
947 // the boundaries: the boxes are closed, so a shared point of the operands
948 // would be a shared point of them.
949 if (a.empty() || b.empty() || !a.bbox().intersects(b.bbox())) {
950 return pieces;
951 }
952
953 std::vector<Segment<ExactPoint>> cuts;
954 appendCutSegments<ExactPoint>(a, cuts);
955 const std::size_t cutsOfA = cuts.size();
956 appendCutSegments<ExactPoint>(b, cuts);
957 // An operand whose every edge has zero length has collapsed onto a single
958 // point — its own bounding box — which the arrangement below would never see
959 // as a vertex, there being no edge to carry it.
960 if (cutsOfA == 0) {
961 const ExactPoint vertex(a.bbox().min());
962 if (b.contains(vertex)) {
963 pieces.emplace_back(ResultPoint(vertex));
964 }
965 return pieces;
966 }
967 if (cuts.size() == cutsOfA) {
968 const ExactPoint vertex(b.bbox().min());
969 if (a.contains(vertex)) {
970 pieces.emplace_back(ResultPoint(vertex));
971 }
972 return pieces;
973 }
974
975 const Arrangement<ExactPoint> arrangement = framedArrangement(cuts);
976 using FaceId = typename Arrangement<ExactPoint>::FaceId;
977 using HalfedgeId = typename Arrangement<ExactPoint>::HalfedgeId;
978 using VertexId = typename Arrangement<ExactPoint>::VertexId;
979
980 std::vector<char> keep(arrangement.faceCount(), 0);
981 for (std::uint32_t i = 0; i < arrangement.faceCount(); ++i) {
982 const FaceId f(i);
983 if (!arrangement.isUnbounded(f)) {
984 const ExactPoint witness = arrangement.template witness<ExactNumber>(f);
985 keep[f.index()] = static_cast<char>(a.contains(witness) && b.contains(witness));
986 }
987 }
988
989 // The strands, as an undirected graph on the arrangement's own vertices.
990 std::vector<std::uint32_t> strandDegree(arrangement.vertexCount(), 0);
991 std::vector<std::vector<std::uint32_t>> adjacency(arrangement.vertexCount());
992 for (std::uint32_t i = 0; i < arrangement.edgeCount(); ++i) {
993 const HalfedgeId h(2 * i);
994 if (keep[arrangement.face(h).index()] != 0 ||
995 keep[arrangement.face(arrangement.twin(h)).index()] != 0) {
996 continue;
997 }
998 const ExactPoint witness = arrangement.template witness<ExactNumber>(h);
999 if (!a.contains(witness) || !b.contains(witness)) {
1000 continue;
1001 }
1002 const std::uint32_t source = arrangement.source(h).index();
1003 const std::uint32_t target = arrangement.target(h).index();
1004 adjacency[source].push_back(target);
1005 adjacency[target].push_back(source);
1006 ++strandDegree[source];
1007 ++strandDegree[target];
1008 }
1009
1010 // The isolated contact points. A vertex beside a kept face is in that
1011 // region piece already, and the rotational fan the DCEL carries answers that
1012 // without building a vector per vertex.
1013 for (std::uint32_t i = 0; i < arrangement.vertexCount(); ++i) {
1014 if (strandDegree[i] != 0) {
1015 continue;
1016 }
1017 const VertexId v(i);
1018 const HalfedgeId start = arrangement.outgoing(v);
1019 assert(start.valid()); // every vertex here is an endpoint of some cut
1020 bool besideKept = false;
1021 HalfedgeId h = start;
1022 do {
1023 besideKept = keep[arrangement.face(h).index()] != 0;
1024 h = arrangement.next(arrangement.twin(h));
1025 } while (h != start && !besideKept);
1026 if (besideKept) {
1027 continue;
1028 }
1029 const ExactPoint& position = arrangement[v];
1030 if (a.contains(position) && b.contains(position)) {
1031 pieces.emplace_back(ResultPoint(position));
1032 }
1033 }
1034
1035 // Peel the open strands from their loose ends, then take whatever is left —
1036 // every vertex of which now carries at least two strand edges — as closed
1037 // walks. Peeling cannot strand a new loose end: a walk runs on through every
1038 // vertex it leaves with one edge, and stops only at a junction or at nothing.
1039 const auto takeEdge = [&adjacency](std::uint32_t from, std::uint32_t to) {
1040 auto& out = adjacency[from];
1041 out.erase(std::find(out.begin(), out.end(), to));
1042 auto& back = adjacency[to];
1043 back.erase(std::find(back.begin(), back.end(), from));
1044 };
1045 const auto walkFrom = [&](std::uint32_t start, bool stopAtJunctions) {
1046 std::vector<std::uint32_t> walk{start};
1047 std::uint32_t current = start;
1048 while (!adjacency[current].empty() &&
1049 (!stopAtJunctions || adjacency[current].size() == 1)) {
1050 const std::uint32_t next = adjacency[current].front();
1051 takeEdge(current, next);
1052 walk.push_back(next);
1053 current = next;
1054 }
1055 return walk;
1056 };
1057 std::vector<std::vector<std::uint32_t>> strands;
1058 for (std::uint32_t i = 0; i < adjacency.size(); ++i) {
1059 if (adjacency[i].size() == 1) {
1060 strands.push_back(walkFrom(i, /*stopAtJunctions=*/true));
1061 }
1062 }
1063 for (std::uint32_t i = 0; i < adjacency.size(); ++i) {
1064 while (!adjacency[i].empty()) {
1065 strands.push_back(walkFrom(i, /*stopAtJunctions=*/false));
1066 }
1067 }
1068
1069 // A vertex the arrangement put in the middle of a straight stretch says
1070 // nothing about the strand, exactly as in a ring of the region pieces. Only
1071 // one of strand degree two may go: a junction is shared with another strand.
1072 const auto removable = [&](std::uint32_t before, std::uint32_t at, std::uint32_t after) {
1073 return strandDegree[at] == 2 &&
1074 collinear(arrangement[VertexId(before)], arrangement[VertexId(at)],
1075 arrangement[VertexId(after)]);
1076 };
1077 for (std::vector<std::uint32_t>& walk : strands) {
1078 if (walk.size() > 2 && walk.front() == walk.back()) {
1079 // A closed strand has no distinguished first vertex, so put one that
1080 // survives the simplification there before simplifying.
1081 walk.pop_back();
1082 std::size_t corner = 0;
1083 while (corner < walk.size() &&
1084 removable(walk[(corner + walk.size() - 1) % walk.size()], walk[corner],
1085 walk[(corner + 1) % walk.size()])) {
1086 ++corner;
1087 }
1088 std::rotate(walk.begin(),
1089 walk.begin() + static_cast<std::ptrdiff_t>(corner % walk.size()),
1090 walk.end());
1091 walk.push_back(walk.front());
1092 }
1093 std::vector<ResultPoint> vertices;
1094 vertices.reserve(walk.size());
1095 vertices.emplace_back(arrangement[VertexId(walk.front())]);
1096 for (std::size_t i = 1; i + 1 < walk.size(); ++i) {
1097 if (!removable(walk[i - 1], walk[i], walk[i + 1])) {
1098 vertices.emplace_back(arrangement[VertexId(walk[i])]);
1099 }
1100 }
1101 vertices.emplace_back(arrangement[VertexId(walk.back())]);
1102 pieces.emplace_back(ResultPolyline(std::move(vertices)));
1103 }
1104
1105 for (const ResultRegion& region : regularizedCellsFromKeep<ResultPoint>(arrangement, keep)) {
1106 pieces.emplace_back(region);
1107 }
1108 return pieces;
1109}
1110
1115template <class ResultPoint, class ShapeA, class ShapeB>
1116PolygonSet<ResultPoint> regularizedSymmetricDifference(const ShapeA& a, const ShapeB& b) {
1117 return regularizedBoolean<ResultPoint>(a, b, [](bool inA, bool inB) { return inA != inB; });
1118}
1119
1120} // namespace detail
1121
1122// Out-of-line: the boolean operations are declared in shape/polygon.hpp and
1123// shape/polygonwithholes.hpp, which precede this header in the layering, but
1124// they can only be defined once Triangulation is visible.
1125
1126template <class PointType_, class TLabel>
1127template <class ResultNumber>
1131 // Nothing with area, so closure(A°) is empty and has no pieces at all.
1132 if (isDegenerate()) {
1133 return {};
1134 }
1135 // Already the closure of its own interior: rebuilding it through the
1136 // arrangement could only cost time and shed collinear vertices.
1137 if (isRegular()) {
1139 }
1140 // One arrangement over this one boundary. Every cell of it is inside the
1141 // region or outside it, and a slit — having no cell of its own — survives
1142 // in neither.
1143 const std::array<PolygonWithHoles, 1> self{*this};
1145}
1146
1147template <class PointType_, class TLabel>
1148template <class ResultNumber>
1152 // The components meet at finitely many points at most, so no slit runs
1153 // between two of them and each regularizes on its own. The pieces of
1154 // different components keep the disjoint interiors their components had.
1155 std::vector<PolygonWithHoles<ResultPoint>> pieces;
1156 pieces.reserve(components_.size());
1157 for (const auto& component : components_) {
1158 for (const auto& piece : component.template regularized<ResultNumber>()) {
1159 pieces.push_back(piece);
1160 }
1161 }
1162 return PolygonSet<ResultPoint>(std::move(pieces));
1163}
1164
1165template <class PointType_, class TLabel>
1166template <class ResultNumber, PolygonConcept OtherPolygon>
1168Polygon<PointType_, TLabel>::difference(const OtherPolygon& other) const {
1169 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1170 other);
1171}
1172
1173template <class PointType_, class TLabel>
1174template <class ResultNumber, ConvexConcept OtherConvex>
1176Polygon<PointType_, TLabel>::difference(const OtherConvex& other) const {
1177 return this->template difference<ResultNumber>(other.asPolygon());
1178}
1179
1180template <class PointType_, class TLabel>
1181template <class ResultNumber, TriangleConcept OtherTriangle>
1183Polygon<PointType_, TLabel>::difference(const OtherTriangle& other) const {
1184 return this->template difference<ResultNumber>(other.asConvex());
1185}
1186
1187template <class PointType_, class TLabel>
1188template <class ResultNumber, RectangleConcept OtherRectangle>
1190Polygon<PointType_, TLabel>::difference(const OtherRectangle& other) const {
1191 return this->template difference<ResultNumber>(other.asConvex());
1192}
1193
1194template <class PointType_, class TLabel>
1195template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1197Polygon<PointType_, TLabel>::difference(const OtherRegion& other) const {
1198 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1199 other);
1200}
1201
1202template <class PointType_, class TLabel>
1203template <class ResultNumber, PolygonSetConcept OtherSet>
1205Polygon<PointType_, TLabel>::difference(const OtherSet& other) const {
1206 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1207 other);
1208}
1209
1210template <class PointType_, class TLabel>
1211template <class ResultNumber, PolygonConcept OtherPolygon>
1214 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1215 other);
1216}
1217
1218template <class PointType_, class TLabel>
1219template <class ResultNumber, ConvexConcept OtherConvex>
1222 return this->template difference<ResultNumber>(other.asPolygon());
1223}
1224
1225template <class PointType_, class TLabel>
1226template <class ResultNumber, TriangleConcept OtherTriangle>
1228PolygonWithHoles<PointType_, TLabel>::difference(const OtherTriangle& other) const {
1229 return this->template difference<ResultNumber>(other.asConvex());
1230}
1231
1232template <class PointType_, class TLabel>
1233template <class ResultNumber, RectangleConcept OtherRectangle>
1235PolygonWithHoles<PointType_, TLabel>::difference(const OtherRectangle& other) const {
1236 return this->template difference<ResultNumber>(other.asConvex());
1237}
1238
1239template <class PointType_, class TLabel>
1240template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1243 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1244 other);
1245}
1246
1247template <class PointType_, class TLabel>
1248template <class ResultNumber, PolygonSetConcept OtherSet>
1251 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1252 other);
1253}
1254
1255// The three bounded convex regions take a difference against every region
1256// through their polygon spelling. Unlike the symmetric three, a difference has
1257// no higher-ranked operand to forward to — `A ∖ B` is not `B ∖ A` — so each of
1258// them states it against all six, and `asPolygon` is what hands the pair to the
1259// engine. The conversion costs nothing, the vertices already being in canonical
1260// polygon order, and it is the same one `detail::booleanOperand` makes for a
1261// set's operands.
1262
1263template <class PointType_, class TLabel>
1264template <class ResultNumber, PolygonalRegionConcept OtherRegion>
1266Rectangle<PointType_, TLabel>::difference(const OtherRegion& other) const {
1267 return asPolygon().template difference<ResultNumber>(other);
1268}
1269
1270template <class PointType_, class TLabel>
1271template <class ResultNumber, PolygonalRegionConcept OtherRegion>
1273Triangle<PointType_, TLabel>::difference(const OtherRegion& other) const {
1274 return asPolygon().template difference<ResultNumber>(other);
1275}
1276
1277template <class PointType_, class TLabel>
1278template <class ResultNumber, PolygonalRegionConcept OtherRegion>
1280Convex<PointType_, TLabel>::difference(const OtherRegion& other) const {
1281 return asPolygon().template difference<ResultNumber>(other);
1282}
1283
1284// ---------------------------------------------------------------------------
1285// The unbounded subtrahends.
1286//
1287// A union or a symmetric difference with an unbounded operand is unbounded, and
1288// no set of regions can hold it. A *difference* against one is not: `A ∖ B` is
1289// contained in `A`, so it is bounded whenever the receiver is, whatever `B`
1290// covers — which is why these overloads exist where the symmetric ones cannot.
1291// The latitude is one-sided, and visibly so: it is the receiver that has to be
1292// bounded, so `halfplane.difference(polygon)` is nowhere to be found.
1293//
1294// Being bounded is also what makes them computable. Only the part of `B` near
1295// `A` can matter, so `B` is clipped to a box strictly containing `A` — which
1296// leaves `A ∖ B` untouched, `A` being inside the box — and what comes back is a
1297// convex polygon the engine already takes. The two shortcuts below are the cases
1298// where the clip leaves nothing with area: a subtrahend with empty interior
1299// removes nothing, since `A° ∖ B` is dense in `A°` when `B` is nowhere dense, so
1300// the answer is `closure(A°)`, which is exactly @ref PolygonWithHoles::regularized.
1301//
1302// Everything else forwards: the convex three through their polygon spelling as
1303// they do for a bounded operand, and a polygon through the region spelling of
1304// itself, so the clip is written once.
1305
1306template <class PointType_, class TLabel>
1307template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1309PolygonWithHoles<PointType_, TLabel>::difference(const OtherIntersection& other) const {
1310 using ExactNumber = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
1311 // Nothing with area to keep, and no bounding rectangle to clip against.
1312 if (isDegenerate()) {
1313 return {};
1314 }
1315 if (other.isDegenerate()) {
1316 return this->template regularized<ResultNumber>();
1317 }
1318 const auto clipped = detail::regionClippedToBox(other, bbox());
1319 if (clipped.isDegenerate()) {
1320 return this->template regularized<ResultNumber>();
1321 }
1322 return this->template difference<ResultNumber>(clipped.template asConvex<ExactNumber>());
1323}
1324
1325template <class PointType_, class TLabel>
1326template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1328PolygonWithHoles<PointType_, TLabel>::difference(const OtherHalfplane& other) const {
1329 return this->template difference<ResultNumber>(other.asHalfplaneIntersection());
1330}
1331
1332template <class PointType_, class TLabel>
1333template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1335PolygonSet<PointType_, TLabel>::difference(const OtherIntersection& other) const {
1336 using ExactNumber = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
1337 if (isDegenerate()) {
1338 return {};
1339 }
1340 if (other.isDegenerate()) {
1341 return this->template regularized<ResultNumber>();
1342 }
1343 const auto clipped = detail::regionClippedToBox(other, bbox());
1344 if (clipped.isDegenerate()) {
1345 return this->template regularized<ResultNumber>();
1346 }
1347 return this->template difference<ResultNumber>(clipped.template asConvex<ExactNumber>());
1348}
1349
1350template <class PointType_, class TLabel>
1351template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1353PolygonSet<PointType_, TLabel>::difference(const OtherHalfplane& other) const {
1354 return this->template difference<ResultNumber>(other.asHalfplaneIntersection());
1355}
1356
1357template <class PointType_, class TLabel>
1358template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1360Polygon<PointType_, TLabel>::difference(const OtherIntersection& other) const {
1361 return asPolygonWithHoles().template difference<ResultNumber>(other);
1362}
1363
1364template <class PointType_, class TLabel>
1365template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1367Polygon<PointType_, TLabel>::difference(const OtherHalfplane& other) const {
1368 return asPolygonWithHoles().template difference<ResultNumber>(other);
1369}
1370
1371template <class PointType_, class TLabel>
1372template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1374Rectangle<PointType_, TLabel>::difference(const OtherIntersection& other) const {
1375 return asPolygon().template difference<ResultNumber>(other);
1376}
1377
1378template <class PointType_, class TLabel>
1379template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1381Rectangle<PointType_, TLabel>::difference(const OtherHalfplane& other) const {
1382 return asPolygon().template difference<ResultNumber>(other);
1383}
1384
1385template <class PointType_, class TLabel>
1386template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1388Triangle<PointType_, TLabel>::difference(const OtherIntersection& other) const {
1389 return asPolygon().template difference<ResultNumber>(other);
1390}
1391
1392template <class PointType_, class TLabel>
1393template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1395Triangle<PointType_, TLabel>::difference(const OtherHalfplane& other) const {
1396 return asPolygon().template difference<ResultNumber>(other);
1397}
1398
1399template <class PointType_, class TLabel>
1400template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1402Convex<PointType_, TLabel>::difference(const OtherIntersection& other) const {
1403 return asPolygon().template difference<ResultNumber>(other);
1404}
1405
1406template <class PointType_, class TLabel>
1407template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1409Convex<PointType_, TLabel>::difference(const OtherHalfplane& other) const {
1410 return asPolygon().template difference<ResultNumber>(other);
1411}
1412
1413// The three bounded convex regions unite among themselves through the polygon
1414// engine, each pair on the higher-ranked of its two operands. The union of two
1415// convex shapes is not convex in general, so there is nothing a convex operand
1416// could contribute that its outline does not; going through `asPolygon` is the
1417// same conversion `detail::booleanOperand` makes for a set's operands, and it
1418// costs nothing, the vertices already being in canonical polygon order.
1419
1420template <class PointType_, class TLabel>
1421template <class ResultNumber, RectangleConcept OtherRectangle>
1423Rectangle<PointType_, TLabel>::regularizedUnion(const OtherRectangle& other) const {
1424 return asPolygon().template regularizedUnion<ResultNumber>(other);
1425}
1426
1427template <class PointType_, class TLabel>
1428template <class ResultNumber, TriangleConcept OtherTriangle>
1430Triangle<PointType_, TLabel>::regularizedUnion(const OtherTriangle& other) const {
1431 return asPolygon().template regularizedUnion<ResultNumber>(other);
1432}
1433
1434template <class PointType_, class TLabel>
1435template <class ResultNumber, RectangleConcept OtherRectangle>
1437Triangle<PointType_, TLabel>::regularizedUnion(const OtherRectangle& other) const {
1438 return asPolygon().template regularizedUnion<ResultNumber>(other);
1439}
1440
1441template <class PointType_, class TLabel>
1442template <class ResultNumber, ConvexConcept OtherConvex>
1444Convex<PointType_, TLabel>::regularizedUnion(const OtherConvex& other) const {
1445 return asPolygon().template regularizedUnion<ResultNumber>(other);
1446}
1447
1448template <class PointType_, class TLabel>
1449template <class ResultNumber, TriangleConcept OtherTriangle>
1451Convex<PointType_, TLabel>::regularizedUnion(const OtherTriangle& other) const {
1452 return asPolygon().template regularizedUnion<ResultNumber>(other);
1453}
1454
1455template <class PointType_, class TLabel>
1456template <class ResultNumber, RectangleConcept OtherRectangle>
1458Convex<PointType_, TLabel>::regularizedUnion(const OtherRectangle& other) const {
1459 return asPolygon().template regularizedUnion<ResultNumber>(other);
1460}
1461
1462template <class PointType_, class TLabel>
1463template <class ResultNumber, PolygonConcept OtherPolygon>
1465Polygon<PointType_, TLabel>::regularizedUnion(const OtherPolygon& other) const {
1466 using ExactNumber = detail::Exact1DNumber<NumberType, typename OtherPolygon::NumberType>;
1467 using ExactPoint = Point<ExactNumber>;
1468 const std::array<Polygon<ExactPoint>, 2> operands{Polygon<ExactPoint>(*this),
1469 Polygon<ExactPoint>(other)};
1471 operands, true);
1472}
1473
1474template <class PointType_, class TLabel>
1475template <class ResultNumber, ConvexConcept OtherConvex>
1477Polygon<PointType_, TLabel>::regularizedUnion(const OtherConvex& other) const {
1478 return this->template regularizedUnion<ResultNumber>(other.asPolygon());
1479}
1480
1481template <class PointType_, class TLabel>
1482template <class ResultNumber, TriangleConcept OtherTriangle>
1484Polygon<PointType_, TLabel>::regularizedUnion(const OtherTriangle& other) const {
1485 return this->template regularizedUnion<ResultNumber>(other.asConvex());
1486}
1487
1488template <class PointType_, class TLabel>
1489template <class ResultNumber, RectangleConcept OtherRectangle>
1491Polygon<PointType_, TLabel>::regularizedUnion(const OtherRectangle& other) const {
1492 return this->template regularizedUnion<ResultNumber>(other.asConvex());
1493}
1494
1495template <class PointType_, class TLabel>
1496template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1498Polygon<PointType_, TLabel>::regularizedUnion(const OtherRegion& other) const {
1499 return detail::regularizedUnion<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1500}
1501
1502template <class PointType_, class TLabel>
1503template <class ResultNumber, PolygonConcept OtherPolygon>
1506 return detail::regularizedUnion<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1507}
1508
1509template <class PointType_, class TLabel>
1510template <class ResultNumber, ConvexConcept OtherConvex>
1513 return this->template regularizedUnion<ResultNumber>(other.asPolygon());
1514}
1515
1516template <class PointType_, class TLabel>
1517template <class ResultNumber, TriangleConcept OtherTriangle>
1520 return this->template regularizedUnion<ResultNumber>(other.asConvex());
1521}
1522
1523template <class PointType_, class TLabel>
1524template <class ResultNumber, RectangleConcept OtherRectangle>
1527 return this->template regularizedUnion<ResultNumber>(other.asConvex());
1528}
1529
1530template <class PointType_, class TLabel>
1531template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1534 return detail::regularizedUnion<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1535}
1536
1537// A symmetric difference is symmetric, so the three convex regions own exactly
1538// the same pairs among themselves that they own for the union — each on the
1539// higher-ranked of its two operands — and reach everything above them through
1540// the same rank forwarder. The polygon spelling is the engine's operand here too.
1541
1542template <class PointType_, class TLabel>
1543template <class ResultNumber, RectangleConcept OtherRectangle>
1545Rectangle<PointType_, TLabel>::symmetricDifference(const OtherRectangle& other) const {
1546 return asPolygon().template symmetricDifference<ResultNumber>(other);
1547}
1548
1549template <class PointType_, class TLabel>
1550template <class ResultNumber, TriangleConcept OtherTriangle>
1552Triangle<PointType_, TLabel>::symmetricDifference(const OtherTriangle& other) const {
1553 return asPolygon().template symmetricDifference<ResultNumber>(other);
1554}
1555
1556template <class PointType_, class TLabel>
1557template <class ResultNumber, RectangleConcept OtherRectangle>
1559Triangle<PointType_, TLabel>::symmetricDifference(const OtherRectangle& other) const {
1560 return asPolygon().template symmetricDifference<ResultNumber>(other);
1561}
1562
1563template <class PointType_, class TLabel>
1564template <class ResultNumber, ConvexConcept OtherConvex>
1567 return asPolygon().template symmetricDifference<ResultNumber>(other);
1568}
1569
1570template <class PointType_, class TLabel>
1571template <class ResultNumber, TriangleConcept OtherTriangle>
1573Convex<PointType_, TLabel>::symmetricDifference(const OtherTriangle& other) const {
1574 return asPolygon().template symmetricDifference<ResultNumber>(other);
1575}
1576
1577template <class PointType_, class TLabel>
1578template <class ResultNumber, RectangleConcept OtherRectangle>
1580Convex<PointType_, TLabel>::symmetricDifference(const OtherRectangle& other) const {
1581 return asPolygon().template symmetricDifference<ResultNumber>(other);
1582}
1583
1584template <class PointType_, class TLabel>
1585template <class ResultNumber, PolygonConcept OtherPolygon>
1588 return detail::regularizedSymmetricDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1589}
1590
1591template <class PointType_, class TLabel>
1592template <class ResultNumber, ConvexConcept OtherConvex>
1595 return this->template symmetricDifference<ResultNumber>(other.asPolygon());
1596}
1597
1598template <class PointType_, class TLabel>
1599template <class ResultNumber, TriangleConcept OtherTriangle>
1601Polygon<PointType_, TLabel>::symmetricDifference(const OtherTriangle& other) const {
1602 return this->template symmetricDifference<ResultNumber>(other.asConvex());
1603}
1604
1605template <class PointType_, class TLabel>
1606template <class ResultNumber, RectangleConcept OtherRectangle>
1608Polygon<PointType_, TLabel>::symmetricDifference(const OtherRectangle& other) const {
1609 return this->template symmetricDifference<ResultNumber>(other.asConvex());
1610}
1611
1612template <class PointType_, class TLabel>
1613template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1616 return detail::regularizedSymmetricDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1617}
1618
1619template <class PointType_, class TLabel>
1620template <class ResultNumber, PolygonConcept OtherPolygon>
1623 return detail::regularizedSymmetricDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1624}
1625
1626template <class PointType_, class TLabel>
1627template <class ResultNumber, ConvexConcept OtherConvex>
1630 return this->template symmetricDifference<ResultNumber>(other.asPolygon());
1631}
1632
1633template <class PointType_, class TLabel>
1634template <class ResultNumber, TriangleConcept OtherTriangle>
1637 return this->template symmetricDifference<ResultNumber>(other.asConvex());
1638}
1639
1640template <class PointType_, class TLabel>
1641template <class ResultNumber, RectangleConcept OtherRectangle>
1644 return this->template symmetricDifference<ResultNumber>(other.asConvex());
1645}
1646
1647template <class PointType_, class TLabel>
1648template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1651 return detail::regularizedSymmetricDifference<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1652}
1653
1654template <class PointType_, class TLabel>
1655template <class ResultNumber, PolygonConcept OtherPolygon>
1658 return detail::regularizedIntersection<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1659}
1660
1661template <class PointType_, class TLabel>
1662template <class ResultNumber, ConvexConcept OtherConvex>
1665 return this->template regularizedIntersection<ResultNumber>(other.asPolygon());
1666}
1667
1668template <class PointType_, class TLabel>
1669template <class ResultNumber, TriangleConcept OtherTriangle>
1672 return this->template regularizedIntersection<ResultNumber>(other.asConvex());
1673}
1674
1675template <class PointType_, class TLabel>
1676template <class ResultNumber, RectangleConcept OtherRectangle>
1679 return this->template regularizedIntersection<ResultNumber>(other.asConvex());
1680}
1681
1682template <class PointType_, class TLabel>
1683template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1686 return detail::regularizedIntersection<Point<ResultNumber, typename PointType_::LabelType>>(*this, other);
1687}
1688
1689template <class PointType_, class TLabel>
1690template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1693 using ExactNumber = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
1694 // Neither a region without area nor a half-plane intersection without
1695 // interior can contribute to closure(A° ∩ B°).
1696 if (isDegenerate() || other.isDegenerate()) {
1697 return {};
1698 }
1699 // The clip only has to preserve A ∩ B, and A lies strictly inside the box.
1700 const auto clipped = detail::regionClippedToBox(other, bbox());
1701 if (clipped.isDegenerate()) {
1702 return {};
1703 }
1704 return this->template regularizedIntersection<ResultNumber>(clipped.template asConvex<ExactNumber>());
1705}
1706
1707template <class PointType_, class TLabel>
1708template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1711 return this->template regularizedIntersection<ResultNumber>(other.asHalfplaneIntersection());
1712}
1713
1714
1715// The literal intersection: the one operation here that keeps what
1716// regularization drops. It takes the same operand grid as
1717// regularizedIntersection and makes the same reductions onto the engine's own
1718// shapes — a bounded convex operand goes in as its polygon, an unbounded one is
1719// clipped against this region first — and differs only in the engine it calls.
1720
1721template <class PointType_, class TLabel>
1722template <class ResultNumber, PolygonConcept OtherPolygon>
1723std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1727 return detail::literalIntersection<Point<ResultNumber, typename PointType_::LabelType>>(
1728 *this, other);
1729}
1730
1731template <class PointType_, class TLabel>
1732template <class ResultNumber, ConvexConcept OtherConvex>
1733std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1737 return this->template intersection<ResultNumber>(other.asPolygon());
1738}
1739
1740template <class PointType_, class TLabel>
1741template <class ResultNumber, TriangleConcept OtherTriangle>
1742std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1746 return this->template intersection<ResultNumber>(other.asConvex());
1747}
1748
1749template <class PointType_, class TLabel>
1750template <class ResultNumber, RectangleConcept OtherRectangle>
1751std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1754PolygonWithHoles<PointType_, TLabel>::intersection(const OtherRectangle& other) const {
1755 return this->template intersection<ResultNumber>(other.asConvex());
1756}
1757
1758template <class PointType_, class TLabel>
1759template <class ResultNumber, PolygonWithHolesConcept OtherRegion>
1760std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1764 return detail::literalIntersection<Point<ResultNumber, typename PointType_::LabelType>>(
1765 *this, other);
1766}
1767
1768template <class PointType_, class TLabel>
1769template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1770std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1773PolygonWithHoles<PointType_, TLabel>::intersection(const OtherIntersection& other) const {
1774 using ExactNumber = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
1775 if (empty() || other.empty()) {
1776 return {};
1777 }
1778 // The clip only has to preserve A ∩ B, and A lies strictly inside the box.
1779 // A clip that comes back without interior is kept and handed to the engine
1780 // as the segment or point it collapsed to: unlike the regularized answer,
1781 // a contact of that dimension is a piece of this one.
1782 const auto clipped = detail::regionClippedToBox(other, bbox());
1783 return detail::literalIntersection<Point<ResultNumber, typename PointType_::LabelType>>(
1784 *this, clipped.template asConvex<ExactNumber>());
1785}
1786
1787template <class PointType_, class TLabel>
1788template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1789std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1792PolygonWithHoles<PointType_, TLabel>::intersection(const OtherHalfplane& other) const {
1793 return this->template intersection<ResultNumber>(other.asHalfplaneIntersection());
1794}
1795
1796
1797// ---------------------------------------------------------------------------
1798// The set of regions, where the four operations close.
1799//
1800// One definition per operation over every operand the engine takes, another set
1801// included. There is nothing per-operand about them: the engine asks a shape for
1802// its cut segments and for one containment test per cell, which a set answers
1803// like anything else. Note in particular that a set operand goes in whole rather
1804// than one component at a time — folding would build one arrangement per step.
1805
1806template <class PointType_, class TLabel>
1807template <class ResultNumber, detail::SetBooleanOperandConcept OtherShape>
1809PolygonSet<PointType_, TLabel>::difference(const OtherShape& other) const {
1810 return detail::regularizedDifference<Point<ResultNumber, typename PointType_::LabelType>>(
1811 *this, detail::booleanOperand(other));
1812}
1813
1814template <class PointType_, class TLabel>
1815template <class ResultNumber, detail::SetBooleanOperandConcept OtherShape>
1818 return detail::regularizedUnion<Point<ResultNumber, typename PointType_::LabelType>>(
1819 *this, detail::booleanOperand(other));
1820}
1821
1822template <class PointType_, class TLabel>
1823template <class ResultNumber, detail::SetBooleanOperandConcept OtherShape>
1826 return detail::regularizedIntersection<Point<ResultNumber, typename PointType_::LabelType>>(
1827 *this, detail::booleanOperand(other));
1828}
1829
1830template <class PointType_, class TLabel>
1831template <class ResultNumber, detail::SetBooleanOperandConcept OtherShape>
1834 return detail::regularizedSymmetricDifference<
1835 Point<ResultNumber, typename PointType_::LabelType>>(*this, detail::booleanOperand(other));
1836}
1837
1838template <class PointType_, class TLabel>
1839template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1841PolygonSet<PointType_, TLabel>::regularizedIntersection(const OtherIntersection& other) const {
1842 using ExactNumber = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
1843 // Neither a set without area nor a half-plane intersection without interior
1844 // can contribute to closure(A° ∩ B°).
1845 if (isDegenerate() || other.isDegenerate()) {
1846 return {};
1847 }
1848 // The clip only has to preserve A ∩ B, and A lies strictly inside the box.
1849 const auto clipped = detail::regionClippedToBox(other, bbox());
1850 if (clipped.isDegenerate()) {
1851 return {};
1852 }
1853 return this->template regularizedIntersection<ResultNumber>(clipped.template asConvex<ExactNumber>());
1854}
1855
1856template <class PointType_, class TLabel>
1857template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1860 return this->template regularizedIntersection<ResultNumber>(other.asHalfplaneIntersection());
1861}
1862
1863template <class PointType_, class TLabel>
1864template <class ResultNumber, detail::SetBooleanOperandConcept OtherShape>
1865std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1868PolygonSet<PointType_, TLabel>::intersection(const OtherShape& other) const {
1869 return detail::literalIntersection<Point<ResultNumber, typename PointType_::LabelType>>(
1870 *this, detail::booleanOperand(other));
1871}
1872
1873template <class PointType_, class TLabel>
1874template <class ResultNumber, HalfplaneIntersectionConcept OtherIntersection>
1875std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1878PolygonSet<PointType_, TLabel>::intersection(const OtherIntersection& other) const {
1879 using ExactNumber = detail::region_exact_number_t<typename OtherIntersection::NumberType>;
1880 if (empty() || other.empty()) {
1881 return {};
1882 }
1883 // The clip only has to preserve A ∩ B, and A lies strictly inside the box.
1884 // A clip without interior still carries pieces of this answer; see
1885 // PolygonWithHoles::intersection(const OtherIntersection&) const.
1886 const auto clipped = detail::regionClippedToBox(other, bbox());
1887 return detail::literalIntersection<Point<ResultNumber, typename PointType_::LabelType>>(
1888 *this, clipped.template asConvex<ExactNumber>());
1889}
1890
1891template <class PointType_, class TLabel>
1892template <class ResultNumber, HalfplaneConcept OtherHalfplane>
1893std::vector<std::variant<Point<ResultNumber, typename PointType_::LabelType>,
1896PolygonSet<PointType_, TLabel>::intersection(const OtherHalfplane& other) const {
1897 return this->template intersection<ResultNumber>(other.asHalfplaneIntersection());
1898}
1899
1900} // namespace pgl
Planar subdivision induced by a set of one-dimensional shapes.
detail::Handle< FaceTag > FaceId
Handle of a face of this arrangement specialization.
Definition arrangement.hpp:185
detail::Handle< VertexTag > VertexId
Handle of a vertex of this arrangement specialization.
Definition arrangement.hpp:181
detail::Handle< HalfedgeTag > HalfedgeId
Handle of a halfedge of this arrangement specialization.
Definition arrangement.hpp:183
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
constexpr bool is_Rational_v
Definition rational.hpp:37
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Point() -> Point< int >
boost::multiprecision::number< boost::multiprecision::cpp_int_backend< 127, 127, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void > > int128
Signed 128-bit integer.
Definition numeric.hpp:64
PolygonSet() -> PolygonSet< Point<>, NoLabel >
Definition polygonset.hpp:1699
typename rational_int< T >::type rational_int_t
Definition rational.hpp:61
constexpr bool collinear(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Tests whether three points are collinear.
Definition orientation.hpp:651
PolygonWithHoles() -> PolygonWithHoles< Point<>, NoLabel >
Definition polygonwithholes.hpp:3093
Segment() -> Segment< Point<>, NoLabel >
Polyline() -> Polyline< Point<>, NoLabel >
Definition polyline.hpp:2369
Polygon() -> Polygon< Point<>, NoLabel >
Definition polygon.hpp:3200
PolygonSet< ResultPoint > regularizedUnionOf(const ShapeRange &shapes, bool simpleBoundaries=false)
The regularized union of arbitrarily many shapes, as a set of regions.
Definition booleans.hpp:780
auto difference(const Shape< OtherPoint > &other) const
Returns the regularized set difference of the two shapes (A ∖ B), re-dispatching through the wrapper'...
Definition convex.hpp:2538
constexpr Polygon< PointType > asPolygon() const
Definition convex.hpp:636
auto symmetricDifference(const Shape< OtherPoint > &other) const
Returns the regularized symmetric difference of the two shapes (A △ B), re-dispatching through the wr...
Definition convex.hpp:2555
auto regularizedUnion(const Shape< OtherPoint > &other) const
Returns the regularized union of the two shapes (A ∪ B), re-dispatching through the wrapper's own reg...
Definition convex.hpp:2519
Two-dimensional point with optional label payload.
Definition point.hpp:129
Set of closed regions with pairwise disjoint interiors.
Definition polygonset.hpp:165
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > symmetricDifference(const OtherShape &other) const
Returns the regularized symmetric difference of the two shapes (A △ B).
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > difference(const OtherShape &other) const
Returns the regularized set difference of the two shapes (A ∖ B).
std::vector< std::variant< Point< ResultNumber, typename PointType::LabelType >, Polyline< Point< ResultNumber, typename PointType::LabelType > >, PolygonWithHoles< Point< ResultNumber, typename PointType::LabelType > > > > intersection(const OtherShape &other) const
Returns the intersection of the two shapes (A ∩ B), empty when they are disjoint.
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularizedUnion(const OtherShape &other) const
Returns the regularized union of the two shapes (A ∪ B).
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularizedIntersection(const OtherShape &other) const
Returns the regularized intersection of the two shapes (A ∩ B).
constexpr bool empty() const
Definition polygonset.hpp:485
constexpr const ComponentType & component(std::size_t index) const
Definition polygonset.hpp:271
constexpr bool isDegenerate() const
Definition polygonset.hpp:499
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularized() const
Returns the set without its slits (closure(A°)).
constexpr const Rectangle< PointType > & bbox() const
Closed region bounded by one outer simple polygon minus disjoint polygonal holes.
Definition polygonwithholes.hpp:89
constexpr const Rectangle< PointType > & bbox() const
Definition polygonwithholes.hpp:1571
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularizedUnion(const OtherPolygon &other) const
Returns the regularized union of the two shapes (A ∪ B).
constexpr bool isDegenerate() const
Definition polygonwithholes.hpp:441
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularizedIntersection(const OtherPolygon &other) const
Returns the regularized intersection of the two shapes (A ∩ B).
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > symmetricDifference(const OtherPolygon &other) const
Returns the regularized symmetric difference of the two shapes (A △ B).
friend struct PolygonWithHoles
Definition polygonwithholes.hpp:3314
constexpr std::optional< Point< ResultNumber, typename PointType::LabelType > > intersection(const OtherPoint &other) const
Returns the intersection of the two shapes (A ∩ B), empty when they are disjoint.
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularized() const
Returns the region without its slits (closure(A°)), as a set of regions.
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > difference(const OtherPolygon &other) const
Returns the regularized set difference of the two shapes (A ∖ B).
constexpr bool empty() const
Definition polygonwithholes.hpp:430
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > difference(const OtherPolygon &other) const
Returns the regularized set difference of the two shapes (A ∖ B).
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > regularizedUnion(const OtherPolygon &other) const
Returns the regularized union of the two shapes (A ∪ B).
PolygonSet< Point< ResultNumber, typename PointType::LabelType > > symmetricDifference(const OtherPolygon &other) const
Returns the regularized symmetric difference of the two shapes (A △ B).
constexpr PolygonWithHoles< PointType > asPolygonWithHoles() const
Definition polygon.hpp:835
Open polygonal chain stored in traversal order; may self-intersect.
Definition polyline.hpp:69
auto symmetricDifference(const Shape< OtherPoint > &other) const
Returns the regularized symmetric difference of the two shapes (A △ B), re-dispatching through the wr...
Definition rectangle.hpp:1673
auto regularizedUnion(const Shape< OtherPoint > &other) const
Returns the regularized union of the two shapes (A ∪ B), re-dispatching through the wrapper's own reg...
Definition rectangle.hpp:1637
auto difference(const Shape< OtherPoint > &other) const
Returns the regularized set difference of the two shapes (A ∖ B), re-dispatching through the wrapper'...
Definition rectangle.hpp:1656
constexpr Polygon< PointType > asPolygon() const
Definition rectangle.hpp:737
auto regularizedUnion(const Shape< OtherPoint > &other) const
Returns the regularized union of the two shapes (A ∪ B), re-dispatching through the wrapper's own reg...
Definition triangle.hpp:1515
auto symmetricDifference(const Shape< OtherPoint > &other) const
Returns the regularized symmetric difference of the two shapes (A △ B), re-dispatching through the wr...
Definition triangle.hpp:1551
constexpr Polygon< PointType > asPolygon() const
Definition triangle.hpp:533
auto difference(const Shape< OtherPoint > &other) const
Returns the regularized set difference of the two shapes (A ∖ B), re-dispatching through the wrapper'...
Definition triangle.hpp:1534