Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
minkowskisum.hpp
Go to the documentation of this file.
1#pragma once
2
4
136
137#include <algorithm>
138#include <cstddef>
139#include <iterator>
140#include <type_traits>
141#include <utility>
142#include <vector>
143
144namespace pgl {
145
146namespace detail {
147
180template <class Shape>
181std::vector<Convex<typename Shape::PointType>> minkowskiConvexPieces(const Shape& shape) {
182 using ShapePoint = typename Shape::PointType;
183 using PieceConvex = Convex<ShapePoint>;
184
185 std::vector<PieceConvex> pieces;
186 // Untrusted throughout: the constructor's Graham scan is what orders the
187 // vertices and prunes a piece that has collapsed, and the convex merge below
188 // relies on both.
189 const auto add = [&pieces](std::vector<ShapePoint> vertices) {
190 pieces.push_back(PieceConvex(std::move(vertices)));
191 };
192
193 if constexpr (is_polygon_v<Shape> || is_polygon_with_holes_v<Shape>) {
194 const auto addEdge = [&add](const auto& edge) { add({edge.min(), edge.max()}); };
195 if (shape.isDegenerate()) {
196 if constexpr (is_polygon_with_holes_v<Shape>) {
197 for (const auto& edge : shape.edges()) {
198 addEdge(edge);
199 }
200 } else {
201 for (const auto& edge : shape.edgesView()) {
202 addEdge(edge);
203 }
204 }
205 return pieces;
206 }
207 // Already canonical, and already `Convex`: no Graham scan to redo.
208 pieces = shape.convexPartition();
209 if constexpr (is_polygon_with_holes_v<Shape>) {
210 for (const auto& slit : regionSlits(shape)) {
211 addEdge(slit);
212 }
213 }
214 } else if constexpr (is_polyline_v<Shape> || is_monotone_chain_v<Shape>) {
215 // A chain of one vertex covers that vertex and has no edge to say so
216 // with; every other chain is exactly the union of its edges, zero-length
217 // ones included (the Graham scan prunes those to a point). An x-monotone
218 // chain decomposes the same way — it is a polyline that happens to be
219 // sorted, and nothing here needs the sorting.
220 if (shape.size() == 1) {
221 add({shape[0]});
222 }
223 for (const auto& edge : shape.edgesView()) {
224 add({edge.min(), edge.max()});
225 }
226 } else {
227 std::vector<ShapePoint> vertices;
228 for (const auto& vertex : shape.vertices()) {
229 vertices.emplace_back(vertex);
230 }
231 add(std::move(vertices));
232 }
233 return pieces;
234}
235
259template <class Shape, class OtherShape>
260decltype(auto) holeFilteredFor(const Shape& shape, const OtherShape& other) {
261 if constexpr (is_polygon_with_holes_v<Shape>) {
262 if (shape.holes().empty()) {
263 return Shape(shape);
264 }
265 using Extent =
266 std::common_type_t<typename Shape::NumberType, typename OtherShape::NumberType>;
267 const auto box = other.bbox();
268 const Extent width = Extent(box.max().x()) - Extent(box.min().x());
269 const Extent height = Extent(box.max().y()) - Extent(box.min().y());
270
271 std::vector<typename Shape::PolygonType> kept;
272 for (const auto& hole : shape.holes()) {
273 const auto holeBox = hole.bbox();
274 if (Extent(holeBox.max().x()) - Extent(holeBox.min().x()) >= width &&
275 Extent(holeBox.max().y()) - Extent(holeBox.min().y()) >= height) {
276 kept.push_back(hole);
277 }
278 }
279 return Shape(shape.outer(), std::move(kept), true);
280 } else {
281 return (shape); // nothing that could have a hole in it
282 }
283}
284
301template <class ResultPoint, class ShapeA, class ShapeB>
302PolygonSet<ResultPoint> decomposedMinkowskiSum(const ShapeA& a,
303 const ShapeB& b) {
304 const auto left = minkowskiConvexPieces(a);
305 const auto right = minkowskiConvexPieces(b);
306 using SumConvex = decltype(minkowskiConvexSum(left.front(), right.front()));
307
308 std::vector<SumConvex> sums;
309 sums.reserve(left.size() * right.size());
310 for (const auto& piece : left) {
311 for (const auto& other : right) {
312 SumConvex sum = minkowskiConvexSum(piece, other);
313 if (!sum.isDegenerate()) {
314 sums.push_back(std::move(sum));
315 }
316 }
317 }
318 // Repeats are common once either decomposition has congruent pieces in the
319 // same place — two slits sharing a direction, or a rectilinear operand whose
320 // triangles come in matching pairs — and each duplicate would otherwise pay
321 // for its whole boundary again in the arrangement.
322 std::sort(sums.begin(), sums.end());
323 sums.erase(std::unique(sums.begin(), sums.end()), sums.end());
324
326}
327
328// -----------------------------------------------------------------------------
329// What an operand is, as far as the sum is concerned. Three questions decide
330// which construction @ref regularizedMinkowskiSum runs, and all three are answered
331// at run time: a `Polygon` that happens to be convex takes the same path a
332// `Convex` does, and it is only convex on this particular call.
333
344template <class Shape>
345bool minkowskiIsConvex(const Shape& shape) {
346 if constexpr (is_polygon_v<Shape>) {
347 return shape.isConvex();
348 } else if constexpr (is_polygon_with_holes_v<Shape>) {
349 return shape.holes().empty() && shape.outer().isConvex();
350 } else if constexpr (is_polyline_v<Shape> || is_monotone_chain_v<Shape>) {
351 return false;
352 } else {
353 return true;
354 }
355}
356
364template <class Shape>
365bool minkowskiHasArea(const Shape& shape) {
366 if constexpr (is_segment_v<Shape> || is_oriented_segment_v<Shape> ||
367 is_polyline_v<Shape> || is_monotone_chain_v<Shape>) {
368 return false;
369 } else {
370 return !shape.isDegenerate();
371 }
372}
373
383template <class Shape>
384auto minkowskiAsConvex(const Shape& shape) {
385 using ShapePoint = typename Shape::PointType;
386 if constexpr (is_convex_v<Shape>) {
387 return shape;
388 } else if constexpr (is_polygon_v<Shape>) {
389 return Convex<ShapePoint>(shape.vertices());
390 } else if constexpr (is_polygon_with_holes_v<Shape>) {
391 return Convex<ShapePoint>(shape.outer().vertices());
392 } else {
393 std::vector<ShapePoint> vertices;
394 for (const auto& vertex : shape.vertices()) {
395 vertices.emplace_back(vertex);
396 }
397 return Convex<ShapePoint>(std::move(vertices));
398 }
399}
400
401// -----------------------------------------------------------------------------
402// The x-monotone chain's own sum, which needs none of the above.
403
415template <class Number>
416using chainWide_t = promoted_number_t<promoted_number_t<Number>>;
417
419template <class Number>
420constexpr int chainSignOf(const Number& value) {
421 const Number zero{};
422 return value > zero ? 1 : (value < zero ? -1 : 0);
423}
424
431template <class P, class Q>
432constexpr int chainSideSign(const P& a, const P& b, const Q& p) {
433 return signOf(orientationSign(a, b, p));
434}
435
451template <class P>
452struct ChainEnvelopeVertex {
453 P point;
454 P a, b;
455 P c, d;
456 bool isCrossing = false;
457};
458
460template <class P>
461constexpr ChainEnvelopeVertex<P> chainVertexAt(const P& point) {
462 return ChainEnvelopeVertex<P>{point, P(), P(), P(), P(), false};
463}
464
466template <class P>
467constexpr ChainEnvelopeVertex<P> chainVertexCrossing(const P& a, const P& b, const P& c,
468 const P& d) {
469 return ChainEnvelopeVertex<P>{P(), a, b, c, d, true};
470}
471
473template <class P, class Number>
474constexpr ChainEnvelopeVertex<P> chainVertexOnVertical(const P& a, const P& b, const Number& x) {
475 using Coordinate = typename P::NumberType;
476 const Coordinate cut = static_cast<Coordinate>(x);
477 return chainVertexCrossing(a, b, P(cut, Coordinate{}), P(cut, Coordinate(1)));
478}
479
493template <class P>
494constexpr int chainVertexSide(const ChainEnvelopeVertex<P>& vertex, const P& u1, const P& u2) {
495 if (!vertex.isCrossing) {
496 return chainSideSign(u1, u2, vertex.point);
497 }
498 using Wide = chainWide_t<typename P::NumberType>;
499 const auto wide = [](const auto& value) -> decltype(auto) {
500 return detail::asNumber<Wide>(value);
501 };
502
503 const Wide rx = wide(vertex.b.x()) - wide(vertex.a.x());
504 const Wide ry = wide(vertex.b.y()) - wide(vertex.a.y());
505 const Wide sx = wide(vertex.d.x()) - wide(vertex.c.x());
506 const Wide sy = wide(vertex.d.y()) - wide(vertex.c.y());
507 const Wide den = rx * sy - ry * sx;
508 assert(den != Wide{} && "an envelope only ever names a crossing of two crossing lines");
509 const Wide num =
510 (wide(vertex.c.x()) - wide(vertex.a.x())) * sy - (wide(vertex.c.y()) - wide(vertex.a.y())) * sx;
511
512 const Wide ux = wide(u2.x()) - wide(u1.x());
513 const Wide uy = wide(u2.y()) - wide(u1.y());
514 const Wide base =
515 ux * (wide(vertex.a.y()) - wide(u1.y())) - uy * (wide(vertex.a.x()) - wide(u1.x()));
516 const Wide slope = ux * ry - uy * rx;
517
518 return chainSignOf(base * den + slope * num) * chainSignOf(den);
519}
520
528template <class P, class Number>
529constexpr int chainVertexXSign(const ChainEnvelopeVertex<P>& vertex, const Number& x) {
530 if (!vertex.isCrossing) {
531 return chainSignOf(vertex.point.x() - x);
532 }
533 using Wide = chainWide_t<typename P::NumberType>;
534 const auto wide = [](const auto& value) -> decltype(auto) {
535 return detail::asNumber<Wide>(value);
536 };
537
538 const Wide rx = wide(vertex.b.x()) - wide(vertex.a.x());
539 const Wide ry = wide(vertex.b.y()) - wide(vertex.a.y());
540 const Wide sx = wide(vertex.d.x()) - wide(vertex.c.x());
541 const Wide sy = wide(vertex.d.y()) - wide(vertex.c.y());
542 const Wide den = rx * sy - ry * sx;
543 assert(den != Wide{} && "an envelope only ever names a crossing of two crossing lines");
544 const Wide num =
545 (wide(vertex.c.x()) - wide(vertex.a.x())) * sy - (wide(vertex.c.y()) - wide(vertex.a.y())) * sx;
546
547 return chainSignOf((wide(vertex.a.x()) - wide(x)) * den + rx * num) * chainSignOf(den);
548}
549
559template <class ResultPoint, class P>
560ResultPoint chainVertexPoint(const ChainEnvelopeVertex<P>& vertex) {
561 using ResultNumber = typename ResultPoint::NumberType;
562 if (!vertex.isCrossing) {
563 return ResultPoint(detail::asNumber<ResultNumber>(vertex.point.x()),
564 detail::asNumber<ResultNumber>(vertex.point.y()));
565 }
566 using Wide = chainWide_t<typename P::NumberType>;
567 const auto wide = [](const auto& value) -> decltype(auto) {
568 return detail::asNumber<Wide>(value);
569 };
570
571 const Wide rx = wide(vertex.b.x()) - wide(vertex.a.x());
572 const Wide ry = wide(vertex.b.y()) - wide(vertex.a.y());
573 const Wide sx = wide(vertex.d.x()) - wide(vertex.c.x());
574 const Wide sy = wide(vertex.d.y()) - wide(vertex.c.y());
575 const Wide den = rx * sy - ry * sx;
576 assert(den != Wide{} && "an envelope only ever names a crossing of two crossing lines");
577 const Wide num =
578 (wide(vertex.c.x()) - wide(vertex.a.x())) * sy - (wide(vertex.c.y()) - wide(vertex.a.y())) * sx;
579
580 const Wide xn = wide(vertex.a.x()) * den + rx * num;
581 const Wide yn = wide(vertex.a.y()) * den + ry * num;
582
583 if constexpr (extended_integral<Wide>) {
584 // An exact fraction over the widened integers, handed to the same
585 // conversion the boolean engine makes at the end of an arrangement.
586 return ResultPoint(static_cast<ResultNumber>(Rational<Wide>(xn, den)),
587 static_cast<ResultNumber>(Rational<Wide>(yn, den)));
588 } else {
589 // The coordinate type already divides exactly (a Rational) or inexactly
590 // by nature (a floating-point one); either way it needs no wrapper.
591 return ResultPoint(static_cast<ResultNumber>(xn / den),
592 static_cast<ResultNumber>(yn / den));
593 }
594}
595
609template <class P>
610struct ChainEnvelopeArc {
611 P a;
612 P b;
613 ChainEnvelopeVertex<P> start;
614 ChainEnvelopeVertex<P> step;
615 typename P::NumberType stepAt{};
616 bool hasStep = false;
617};
618
620template <class P>
621ChainEnvelopeArc<P> chainArcEnteredAt(const P& a, const P& b,
622 const ChainEnvelopeVertex<P>& start) {
623 ChainEnvelopeArc<P> arc;
624 arc.a = a;
625 arc.b = b;
626 arc.start = start;
627 return arc;
628}
629
631template <class P>
632ChainEnvelopeArc<P> chainArcSteppedInto(const P& a, const P& b,
633 const ChainEnvelopeVertex<P>& start,
634 const ChainEnvelopeVertex<P>& step,
635 const typename P::NumberType& x) {
636 ChainEnvelopeArc<P> arc = chainArcEnteredAt(a, b, start);
637 arc.step = step;
638 arc.stepAt = x;
639 arc.hasStep = true;
640 return arc;
641}
642
667template <class P>
668void chainMergeArc(std::vector<ChainEnvelopeArc<P>>& envelope, const std::vector<P>& arc,
669 bool keepHigher) {
670 assert(arc.size() >= 2 && "a piece with no span cannot bound an envelope");
671 const int keep = keepHigher ? 1 : -1;
672
673 const auto plain = [&arc](std::size_t index) {
674 return chainArcEnteredAt(arc[index], arc[index + 1], chainVertexAt(arc[index]));
675 };
676
677 if (envelope.empty()) {
678 for (std::size_t index = 0; index + 1 < arc.size(); ++index) {
679 envelope.push_back(plain(index));
680 }
681 return;
682 }
683
684 // Where the boundary arrives at an arc: on the previous arc's segment when a
685 // step brings it there, which is the value the span before it ends on.
686 const auto entry = [&envelope](std::size_t index) -> const ChainEnvelopeVertex<P>& {
687 return envelope[index].hasStep ? envelope[index].step : envelope[index].start;
688 };
689
690 const auto& x0 = arc.front().x();
691 std::size_t i = envelope.size() - 1;
692 while (i > 0 && chainVertexXSign(entry(i), x0) > 0) {
693 --i;
694 }
695
696 // Arcs up to and including `i` survive; whatever the sweep decides is
697 // appended after them, so it may keep reading the old envelope as it goes.
698 std::size_t kept = i + 1;
699 std::vector<ChainEnvelopeArc<P>> merged;
700 std::size_t j = 0;
701
702 // Who bounds the sum where the incoming arc starts. That start is a vertex of
703 // the arc, so it lies on it and the envelope's own segment decides.
704 const int startSide = -chainSideSign(envelope[i].a, envelope[i].b, arc.front());
705 int winner = keep * startSide >= 0 ? 1 : -1;
706 if (winner < 0) {
707 // The incoming piece starts beyond the envelope, so the boundary steps
708 // across to it. It leaves the envelope wherever the boundary had reached:
709 // partway along the arc in force, or — when that arc begins right here,
710 // which leaves it bounding nothing at all — at the point that arc was
711 // itself entered at.
712 ChainEnvelopeVertex<P> from;
713 if (chainVertexXSign(entry(i), x0) == 0) {
714 from = entry(i);
715 kept = i;
716 } else {
717 from = chainVertexOnVertical(envelope[i].a, envelope[i].b, x0);
718 }
719 merged.push_back(chainArcSteppedInto(arc[0], arc[1], chainVertexAt(arc[0]), from, x0));
720 }
721
722 while (true) {
723 const bool envelopeEndsLast = i + 1 == envelope.size();
724
725 // Which of the two spans ends first; a negative order is the envelope's.
726 const int order = envelopeEndsLast
727 ? chainSignOf(envelope[i].b.x() - arc[j + 1].x())
728 : chainVertexXSign(entry(i + 1), arc[j + 1].x());
729
730 // Which of the two is ahead where the span ends, read at whichever of the
731 // two segments that point belongs to.
732 int side;
733 if (order <= 0) {
734 side = envelopeEndsLast ? chainSideSign(arc[j], arc[j + 1], envelope[i].b)
735 : chainVertexSide(entry(i + 1), arc[j], arc[j + 1]);
736 } else {
737 side = -chainSideSign(envelope[i].a, envelope[i].b, arc[j + 1]);
738 }
739 const int ahead = keep * side;
740 if (ahead != 0 && ahead != winner) {
741 // The two segments swapped places inside the span, so they cross
742 // there. Which two segments cross is all the boundary needs to know.
743 const ChainEnvelopeVertex<P> crossing =
744 chainVertexCrossing(envelope[i].a, envelope[i].b, arc[j], arc[j + 1]);
745 merged.push_back(ahead > 0
746 ? chainArcEnteredAt(envelope[i].a, envelope[i].b, crossing)
747 : chainArcEnteredAt(arc[j], arc[j + 1], crossing));
748 winner = ahead;
749 }
750
751 if (order >= 0) {
752 // The incoming arc's segment ends here. It runs at least as far as
753 // the envelope, so running out of segments means the merge is done.
754 ++j;
755 if (j + 1 >= arc.size()) {
756 break;
757 }
758 if (winner < 0) {
759 merged.push_back(plain(j));
760 }
761 }
762 if (order <= 0) {
763 if (envelopeEndsLast) {
764 // Past the old envelope's right end the incoming arc is alone. It
765 // takes over there, over a step unless the two happen to meet.
766 if (winner > 0) {
767 const auto& stepAt = envelope[i].b.x();
768 merged.push_back(
769 side == 0
770 ? chainArcEnteredAt(arc[j], arc[j + 1], chainVertexAt(envelope[i].b))
771 : chainArcSteppedInto(
772 arc[j], arc[j + 1],
773 chainVertexOnVertical(arc[j], arc[j + 1], stepAt),
774 chainVertexAt(envelope[i].b), stepAt));
775 }
776 for (std::size_t rest = j + 1; rest + 1 < arc.size(); ++rest) {
777 merged.push_back(plain(rest));
778 }
779 break;
780 }
781 ++i;
782 if (!envelope[i].hasStep) {
783 if (winner > 0) {
784 merged.push_back(envelope[i]);
785 }
786 } else {
787 // The envelope jumps here, so whichever of the two bounds the sum
788 // can change without the segments ever meeting. Both ends of what
789 // the boundary actually walks are read afresh: the near one from
790 // whoever bounded it before the jump, the far one from whoever
791 // bounds it after.
792 const int afterSide = chainVertexSide(envelope[i].start, arc[j], arc[j + 1]);
793 const int after = afterSide == 0 ? winner : keep * afterSide;
794 const auto& stepAt = envelope[i].stepAt;
795 if (after > 0) {
796 merged.push_back(
797 winner > 0 ? envelope[i]
798 : chainArcSteppedInto(
799 envelope[i].a, envelope[i].b, envelope[i].start,
800 chainVertexOnVertical(arc[j], arc[j + 1], stepAt),
801 stepAt));
802 } else if (winner > 0) {
803 merged.push_back(chainArcSteppedInto(
804 arc[j], arc[j + 1], chainVertexOnVertical(arc[j], arc[j + 1], stepAt),
805 envelope[i].step, stepAt));
806 }
807 winner = after;
808 }
809 }
810 }
811
812 envelope.resize(kept);
813 envelope.insert(envelope.end(), std::make_move_iterator(merged.begin()),
814 std::make_move_iterator(merged.end()));
815}
816
829template <class P, class L>
830std::vector<P> chainPieceArc(const Convex<P, L>& piece, bool upper) {
831 const std::size_t n = piece.size();
832 const std::size_t top = piece.maxIndex();
833 assert(n >= 2 && "a piece with no span has no arc; an operand of no width is handled apart");
834
835 std::vector<P> arc;
836 if (upper) {
837 // Clockwise from the lexicographic minimum: the boundary above, already
838 // in increasing lexicographic order.
839 arc.reserve(n - top + 1);
840 arc.push_back(piece[0]);
841 for (std::size_t k = n; k > top; --k) {
842 arc.push_back(piece[k - 1]);
843 }
844 std::size_t drop = 0;
845 while (drop + 1 < arc.size() && arc[drop].x() == arc[drop + 1].x()) {
846 ++drop;
847 }
848 arc.erase(arc.begin(), arc.begin() + static_cast<std::ptrdiff_t>(drop));
849 } else {
850 arc.reserve(top + 1);
851 for (std::size_t k = 0; k <= top; ++k) {
852 arc.push_back(piece[k]);
853 }
854 while (arc.size() >= 2 && arc[arc.size() - 2].x() == arc.back().x()) {
855 arc.pop_back();
856 }
857 }
858 return arc;
859}
860
862template <class ResultPoint, class P>
863void chainAppendEnvelope(std::vector<ResultPoint>& walk,
864 const std::vector<ChainEnvelopeArc<P>>& envelope) {
865 using ResultNumber = typename ResultPoint::NumberType;
866 for (const ChainEnvelopeArc<P>& arc : envelope) {
867 if (arc.hasStep) {
868 walk.push_back(chainVertexPoint<ResultPoint>(arc.step));
869 }
870 walk.push_back(chainVertexPoint<ResultPoint>(arc.start));
871 }
872 walk.emplace_back(detail::asNumber<ResultNumber>(envelope.back().b.x()),
873 detail::asNumber<ResultNumber>(envelope.back().b.y()));
874}
875
877template <class ResultPoint>
878void chainDropRepeated(std::vector<ResultPoint>& walk) {
879 walk.erase(std::unique(walk.begin(), walk.end()), walk.end());
880 while (walk.size() >= 2 && walk.front() == walk.back()) {
881 walk.pop_back();
882 }
883}
884
893template <class ResultPoint>
894void chainDropCollinear(std::vector<ResultPoint>& walk) {
895 if (walk.size() < 3) {
896 return;
897 }
898 std::vector<ResultPoint> kept;
899 kept.reserve(walk.size());
900 for (std::size_t index = 0; index < walk.size(); ++index) {
901 const ResultPoint& previous = walk[(index + walk.size() - 1) % walk.size()];
902 const ResultPoint& vertex = walk[index];
903 const ResultPoint& next = walk[(index + 1) % walk.size()];
904 // Only the sign of that dot product is wanted — whether the walk carries
905 // on in the same direction or doubles back — so dotSign answers it,
906 // computing in the promoted coordinate type instead of one that a long
907 // stretch of the walk can overflow.
908 const bool straight = collinear(previous, vertex, next) &&
909 dotSign(vertex - previous, next - vertex) > 0;
910 if (!straight) {
911 kept.push_back(vertex);
912 }
913 }
914 walk.swap(kept);
915}
916
934template <class ResultPoint, class ChainType, class ConvexOperand>
935std::vector<ResultPoint> chainSumWalk(const ChainType& chain, const ConvexOperand& other) {
936 using SumPoint = minkowskiPoint_t<ChainType, ConvexOperand>;
937 using SumNumber = typename SumPoint::NumberType;
938 using ResultNumber = typename ResultPoint::NumberType;
939
940 const auto convert = [](const auto& vertex) {
941 return ResultPoint(detail::asNumber<ResultNumber>(vertex.x()),
942 detail::asNumber<ResultNumber>(vertex.y()));
943 };
944 const auto shifted = [](const auto& p, const auto& q) {
945 return SumPoint(detail::asNumber<SumNumber>(p.x()) + detail::asNumber<SumNumber>(q.x()),
946 detail::asNumber<SumNumber>(p.y()) + detail::asNumber<SumNumber>(q.y()));
947 };
948
949 const std::vector<SumPoint> operandVertices = minkowskiVertices<SumPoint>(other);
950 if (chain.empty() || operandVertices.empty()) {
951 return {}; // an empty operand absorbs
952 }
953
954 const auto byX = [](const SumPoint& p, const SumPoint& q) { return p.x() < q.x(); };
955 const auto [leftmost, rightmost] =
956 std::minmax_element(operandVertices.begin(), operandVertices.end(), byX);
957 if (leftmost->x() == rightmost->x()) {
958 // An operand of zero width — a vertical segment, or a shape that has
959 // collapsed onto one — leaves the sum's fibre over x the chain's own
960 // fibre widened by the operand's, so the boundary traces the chain out
961 // through the operand's top and back through its bottom.
962 const auto byY = [](const SumPoint& p, const SumPoint& q) { return p.y() < q.y(); };
963 const auto [lowest, highest] =
964 std::minmax_element(operandVertices.begin(), operandVertices.end(), byY);
965 std::vector<ResultPoint> swept;
966 swept.reserve(2 * chain.size());
967 for (std::size_t index = 0; index < chain.size(); ++index) {
968 swept.push_back(convert(shifted(chain[index], *lowest)));
969 }
970 for (std::size_t index = chain.size(); index > 0; --index) {
971 swept.push_back(convert(shifted(chain[index - 1], *highest)));
972 }
973 chainDropRepeated(swept);
974 chainDropCollinear(swept);
975 return swept;
976 }
977
978 std::vector<ChainEnvelopeArc<SumPoint>> lower;
979 std::vector<ChainEnvelopeArc<SumPoint>> upper;
980 const auto mergePiece = [&lower, &upper](const auto& piece) {
981 chainMergeArc(lower, chainPieceArc(piece, false), false);
982 chainMergeArc(upper, chainPieceArc(piece, true), true);
983 };
984
985 if (chain.size() == 1) {
986 // No edge to sweep along: the sum is the operand, translated.
987 std::vector<SumPoint> translated;
988 translated.reserve(operandVertices.size());
989 for (const SumPoint& vertex : operandVertices) {
990 translated.push_back(shifted(vertex, chain[0]));
991 }
992 mergePiece(Convex<SumPoint>(translated));
993 } else {
994 for (const auto& edge : chain.edgesView()) {
995 mergePiece(minkowskiConvexSum(edge, other));
996 }
997 }
998
999 std::vector<ResultPoint> walk;
1000 chainAppendEnvelope(walk, lower);
1001 const std::size_t fromRight = walk.size();
1002 chainAppendEnvelope(walk, upper);
1003 // The upper envelope was read left to right; the boundary walks it back.
1004 std::reverse(walk.begin() + static_cast<std::ptrdiff_t>(fromRight), walk.end());
1005 chainDropRepeated(walk);
1006 chainDropCollinear(walk);
1007 return walk;
1008}
1009
1023template <class ResultPoint, class ChainType, class ConvexOperand>
1024Polygon<ResultPoint> chainMinkowskiSum(const ChainType& chain, const ConvexOperand& other) {
1025 std::vector<ResultPoint> walk = chainSumWalk<ResultPoint>(chain, other);
1026 // The walk already runs counterclockwise — the lower boundary left to right,
1027 // then the upper one back — and a walk with no area has no orientation to get
1028 // wrong, so all the canonical form still wants is its lexicographically
1029 // smallest vertex first. Rotating it here rather than leaving it to the
1030 // constructor skips an exact signed area over the whole ring, which for a
1031 // rational result type is the one costly thing left in this construction: its
1032 // running denominator is the common multiple of every crossing's.
1033 std::rotate(walk.begin(), std::min_element(walk.begin(), walk.end()), walk.end());
1034 return Polygon<ResultPoint>(std::move(walk), true);
1035}
1036
1037// -----------------------------------------------------------------------------
1038// The boundary decomposition: what a convex operand buys the other one.
1039
1054template <class P>
1055std::vector<std::vector<P>> minkowskiMonotoneRuns(const std::vector<P>& walk) {
1056 std::vector<std::vector<P>> runs;
1057 const auto direction = [](const P& from, const P& to) {
1058 return from == to ? 0 : (from < to ? 1 : -1);
1059 };
1060
1061 std::size_t start = 0;
1062 while (start + 1 < walk.size()) {
1063 const int forward = direction(walk[start], walk[start + 1]);
1064 if (forward == 0) {
1065 ++start; // a repeated vertex spans no edge
1066 continue;
1067 }
1068 std::size_t end = start + 1;
1069 while (end + 1 < walk.size() && direction(walk[end], walk[end + 1]) == forward) {
1070 ++end;
1071 }
1072 std::vector<P> run(walk.begin() + static_cast<std::ptrdiff_t>(start),
1073 walk.begin() + static_cast<std::ptrdiff_t>(end) + 1);
1074 if (forward < 0) {
1075 std::reverse(run.begin(), run.end());
1076 }
1077 runs.push_back(std::move(run));
1078 start = end;
1079 }
1080 return runs;
1081}
1082
1095template <class Shape>
1096std::vector<std::vector<typename Shape::PointType>> minkowskiBoundaryRuns(const Shape& shape) {
1097 using ShapePoint = typename Shape::PointType;
1098
1099 std::vector<std::vector<ShapePoint>> walks;
1100 const auto addRing = [&walks](const auto& ring) {
1101 std::vector<ShapePoint> walk = ring.vertices();
1102 if (!walk.empty()) {
1103 walk.push_back(walk.front());
1104 }
1105 walks.push_back(std::move(walk));
1106 };
1107 if constexpr (is_polygon_with_holes_v<Shape>) {
1108 addRing(shape.outer());
1109 for (const auto& hole : shape.holes()) {
1110 addRing(hole);
1111 }
1112 } else if constexpr (is_polygon_v<Shape>) {
1113 addRing(shape);
1114 } else {
1115 // A chain is open: no closing edge, and its own vertex order is the walk.
1116 std::vector<ShapePoint> walk;
1117 walk.reserve(shape.size());
1118 for (std::size_t index = 0; index < shape.size(); ++index) {
1119 walk.push_back(shape[index]);
1120 }
1121 walks.push_back(std::move(walk));
1122 }
1123
1124 std::vector<std::vector<ShapePoint>> runs;
1125 for (const std::vector<ShapePoint>& walk : walks) {
1126 std::vector<std::vector<ShapePoint>> walkRuns = minkowskiMonotoneRuns(walk);
1127 if (walkRuns.empty() && !walk.empty()) {
1128 // No run means no two consecutive vertices differ, so the whole walk
1129 // is one point. It still sums to something — the operand translated
1130 // there — and a chain of that single vertex is what says so.
1131 walkRuns.push_back({walk.front()});
1132 }
1133 runs.insert(runs.end(), std::make_move_iterator(walkRuns.begin()),
1134 std::make_move_iterator(walkRuns.end()));
1135 }
1136 return runs;
1137}
1138
1170template <class ExactPoint, class Shape, class ConvexOperand>
1171std::vector<PolygonWithHoles<ExactPoint>> minkowskiBoundaryPieces(
1172 const Shape& shape, const ConvexOperand& other,
1173 std::vector<std::vector<typename Shape::PointType>> runs) {
1174 using ShapePoint = typename Shape::PointType;
1175 using SumPoint = minkowskiPoint_t<Shape, ConvexOperand>;
1176 using SumNumber = typename SumPoint::NumberType;
1177 using ExactNumber = typename ExactPoint::NumberType;
1178 using ExactPolygon = Polygon<ExactPoint>;
1179
1180 std::vector<PolygonWithHoles<ExactPoint>> pieces;
1181 pieces.reserve(runs.size() + 1);
1182
1183 for (std::vector<ShapePoint>& run : runs) {
1184 const MonotoneChain<ShapePoint> chain(std::move(run), true);
1185 ExactPolygon sum = chainMinkowskiSum<ExactPoint>(chain, other);
1186 if (sum.size() >= 3) {
1187 pieces.emplace_back(std::move(sum), std::vector<ExactPolygon>{}, true);
1188 }
1189 }
1190
1191 if constexpr (is_polygon_v<Shape> || is_polygon_with_holes_v<Shape>) {
1192 // The interior term. A boundary with no area beside it needs none: the
1193 // runs already cover such a shape entirely.
1194 if (!shape.isDegenerate()) {
1195 const std::vector<SumPoint> operandVertices = minkowskiVertices<SumPoint>(other);
1196 if (operandVertices.empty()) {
1197 return {}; // an empty operand absorbs
1198 }
1199 const SumPoint& q0 = operandVertices.front();
1200 // Translating a ring preserves both the lexicographic order of its
1201 // vertices and its orientation, so the canonical form survives and
1202 // the rings can be rebuilt trusted.
1203 const auto translated = [&q0](const auto& ring) {
1204 std::vector<ExactPoint> moved;
1205 moved.reserve(ring.size());
1206 for (const auto& vertex : ring.vertices()) {
1207 moved.emplace_back(static_cast<ExactNumber>(detail::asNumber<SumNumber>(vertex.x()) +
1208 q0.x()),
1209 static_cast<ExactNumber>(detail::asNumber<SumNumber>(vertex.y()) +
1210 q0.y()));
1211 }
1212 return ExactPolygon(std::move(moved), true);
1213 };
1214 if constexpr (is_polygon_with_holes_v<Shape>) {
1215 std::vector<ExactPolygon> holes;
1216 holes.reserve(shape.holes().size());
1217 for (const auto& hole : shape.holes()) {
1218 holes.push_back(translated(hole));
1219 }
1220 pieces.emplace_back(translated(shape.outer()), std::move(holes), true);
1221 } else {
1222 pieces.emplace_back(translated(shape), std::vector<ExactPolygon>{}, true);
1223 }
1224 }
1225 }
1226 return pieces;
1227}
1228
1237template <class Shape>
1238inline constexpr bool minkowskiHasWalkableBoundary =
1239 is_polygon_v<Shape> || is_polygon_with_holes_v<Shape> || is_polyline_v<Shape> ||
1240 is_monotone_chain_v<Shape>;
1241
1251template <class Shape>
1252bool minkowskiHasSimpleBoundary(const Shape& shape) {
1253 if constexpr (is_polygon_with_holes_v<Shape>) {
1254 return shape.holes().empty() || regionSlits(shape).empty();
1255 } else {
1256 return is_polygon_v<Shape> || is_polyline_v<Shape> || is_monotone_chain_v<Shape>;
1257 }
1258}
1259
1278template <class Ring>
1279std::size_t minkowskiReflexCount(const Ring& ring, bool isHole) {
1280 const std::size_t n = ring.size();
1281 if (n < 3) {
1282 return 0;
1283 }
1284 std::size_t reflex = 0;
1285 for (std::size_t i = 0; i < n; ++i) {
1286 const auto turn = orientationSign(ring[(i + n - 1) % n], ring[i], ring[(i + 1) % n]);
1287 if (isHole ? turn > 0 : turn < 0) {
1288 ++reflex;
1289 }
1290 }
1291 return reflex;
1292}
1293
1307template <class Shape>
1308std::pair<std::size_t, std::size_t> minkowskiPieceEstimate(const Shape& shape) {
1309 if constexpr (is_polygon_v<Shape> || is_polygon_with_holes_v<Shape>) {
1310 std::size_t edges = 0;
1311 std::size_t reflex = 0;
1312 std::size_t holes = 0;
1313 if constexpr (is_polygon_with_holes_v<Shape>) {
1314 edges = shape.outer().size();
1315 reflex = minkowskiReflexCount(shape.outer(), false);
1316 holes = shape.holes().size();
1317 for (const auto& hole : shape.holes()) {
1318 edges += hole.size();
1319 reflex += minkowskiReflexCount(hole, true);
1320 }
1321 } else {
1322 edges = shape.size();
1323 reflex = minkowskiReflexCount(shape, false);
1324 }
1325 const std::size_t pieces = reflex + 1 + holes;
1326 return {pieces, edges + 2 * (pieces - 1 + holes)};
1327 } else {
1328 const std::size_t edges = shape.size() > 1 ? shape.size() - 1 : 0;
1329 const std::size_t pieces = edges > 0 ? edges : 1;
1330 return {pieces, 2 * pieces};
1331 }
1332}
1333
1389template <class Shape, class ConvexOperand, class Runs>
1390bool minkowskiBoundaryPays(const Shape& shape, const ConvexOperand& other, const Runs& runs) {
1391 const std::size_t operandEdges = other.size();
1392 std::size_t edges = 0;
1393 if constexpr (is_polygon_with_holes_v<Shape>) {
1394 edges = shape.outer().size();
1395 for (const auto& hole : shape.holes()) {
1396 edges += hole.size();
1397 }
1398 } else if constexpr (is_polygon_v<Shape>) {
1399 edges = shape.size(); // a ring has one edge per vertex
1400 } else {
1401 edges = shape.size() > 1 ? shape.size() - 1 : 0;
1402 }
1403 const auto [pieces, pieceVertices] = minkowskiPieceEstimate(shape);
1404 const std::size_t convexEdges = pieceVertices + pieces * operandEdges;
1405 const std::size_t boundaryEdges = 2 * edges + runs.size() * operandEdges;
1406 return 6 * boundaryEdges < 5 * convexEdges;
1407}
1408
1409// -----------------------------------------------------------------------------
1410// The one-sided decomposition, which sums its pieces with the engine below and
1411// so has to be declared before it.
1412
1413template <class ResultPoint, class ShapeA, class ShapeB>
1414PolygonSet<ResultPoint> regularizedMinkowskiSum(const ShapeA& a,
1415 const ShapeB& b);
1416
1453template <class ExactPoint, class ShapeA, class ShapeB>
1454std::vector<PolygonWithHoles<ExactPoint>> minkowskiOneSidedPieces(const ShapeA& a,
1455 const ShapeB& b) {
1456 std::vector<PolygonWithHoles<ExactPoint>> pieces;
1457 for (const auto& piece : minkowskiConvexPieces(a)) {
1458 const PolygonSet<ExactPoint> sum = regularizedMinkowskiSum<ExactPoint>(b, piece);
1459 pieces.insert(pieces.end(), sum.begin(), sum.end());
1460 }
1461 return pieces;
1462}
1463
1477template <class Shape>
1478std::size_t minkowskiPieceCount(const Shape& shape) {
1479 if constexpr (is_polygon_with_holes_v<Shape>) {
1480 return shape.vertexCount();
1481 } else {
1482 return shape.size();
1483 }
1484}
1485
1517template <class ShapeA, class ShapeB>
1518bool minkowskiOneSidedDecomposesLeft(const ShapeA& a, const ShapeB& b) {
1519 return static_cast<long double>(minkowskiPieceCount(a)) * b.bbox().template area<long double>() <=
1520 static_cast<long double>(minkowskiPieceCount(b)) * a.bbox().template area<long double>();
1521}
1522
1570template <class ResultPoint, class ShapeA, class ShapeB>
1571PolygonSet<ResultPoint> regularizedMinkowskiSum(const ShapeA& a,
1572 const ShapeB& b) {
1573 using SumPoint = minkowskiPoint_t<ShapeA, ShapeB>;
1574 using SumNumber = typename SumPoint::NumberType;
1576
1577 // Filtering either operand leaves the other's outer boundary untouched, so
1578 // the two tests see the same boxes whichever order they run in.
1579 const auto& left = holeFilteredFor(a, b);
1580 const auto& right = holeFilteredFor(b, a);
1581
1582 const bool leftConvex = minkowskiIsConvex(left);
1583 const bool rightConvex = minkowskiIsConvex(right);
1584
1585 if (leftConvex && rightConvex) {
1586 const auto sum = minkowskiConvexSum(minkowskiAsConvex(left), minkowskiAsConvex(right));
1587 if (sum.isDegenerate()) {
1588 return {}; // nothing with area, so the regularized sum is empty
1589 }
1592 }
1593
1594 // The sum is commutative, so it is the convex operand that decides and not
1595 // which side it arrived on. Only one of the two branches can fire: a pair that
1596 // got past the test above has at most one convex operand, so the other is the
1597 // one to decompose.
1598 //
1599 // Both branches are gated on the coordinates being exact, and that is not a
1600 // performance choice. A convex piece sum never divides — every one of its
1601 // vertices is a sum of two input vertices — so the all-pairs decomposition is
1602 // exact in *any* coordinate type, floating-point included, and only the final
1603 // arrangement rounds. A run's sum does divide, to place the crossings of its
1604 // own sub-sums, and those rounded vertices then feed a second arrangement. On
1605 // integral operands stored as `double`, measured against the exact answer, that
1606 // costs a worst-case relative area error of 0.145 where the all-pairs
1607 // decomposition holds 2e-14, and it turns single regions into two or invents
1608 // holes. So the decomposition is taken only where its pieces can carry their
1609 // own crossings, which is exactly where `Exact1DNumber` is a rational.
1610 constexpr bool exactPieces = !std::is_floating_point_v<SumNumber>;
1611 if constexpr (exactPieces && minkowskiHasWalkableBoundary<std::remove_cvref_t<decltype(left)>>) {
1612 if (rightConvex && minkowskiHasArea(right) && minkowskiHasSimpleBoundary(left)) {
1613 const auto convexRight = minkowskiAsConvex(right);
1614 auto runs = minkowskiBoundaryRuns(left);
1615 if (minkowskiBoundaryPays(left, convexRight, runs)) {
1617 minkowskiBoundaryPieces<ExactPoint>(left, convexRight, std::move(runs)), true);
1618 }
1619 }
1620 }
1621 if constexpr (exactPieces && minkowskiHasWalkableBoundary<std::remove_cvref_t<decltype(right)>>) {
1622 if (leftConvex && minkowskiHasArea(left) && minkowskiHasSimpleBoundary(right)) {
1623 const auto convexLeft = minkowskiAsConvex(left);
1624 auto runs = minkowskiBoundaryRuns(right);
1625 if (minkowskiBoundaryPays(right, convexLeft, runs)) {
1627 minkowskiBoundaryPieces<ExactPoint>(right, convexLeft, std::move(runs)), true);
1628 }
1629 }
1630 }
1631
1632 // Neither operand is convex, so one of them is decomposed and the other is
1633 // not. The recursion this opens is one level deep: every sum below has a
1634 // `Convex` operand and so is answered by construction 1 or 2, or — for a
1635 // piece with no area, which only a slit produces — by construction 4.
1636 //
1637 // Both operands must have **area**, and that is what the construction turns
1638 // on rather than a size or a vertex count. Its whole advantage is that the
1639 // pieces scatter instead of piling up, and that needs a decomposition into
1640 // pieces *small* relative to the operand — which triangles of a triangulation
1641 // are and the edges of a chain are not. A 32-vertex chain over the large
1642 // coordinate range has edges as long as the chain itself, so every piece of
1643 // its sum spans the whole answer and they all cross each other; measured
1644 // against a region of the same extent that is 3x *slower* than construction 4,
1645 // whichever of the two is decomposed. The same chain against a *small* polygon
1646 // is 22x faster, so there is something here for a criterion that can tell a
1647 // chain's pieces apart by length — but extent alone does not do it, since two
1648 // large regions gain 1.75x where a large chain and a large region lose.
1649 if constexpr (exactPieces) {
1650 if (!leftConvex && !rightConvex && minkowskiHasArea(left) &&
1651 minkowskiHasArea(right)) {
1652 auto pieces = minkowskiOneSidedDecomposesLeft(left, right)
1653 ? minkowskiOneSidedPieces<ExactPoint>(left, right)
1654 : minkowskiOneSidedPieces<ExactPoint>(right, left);
1655 return regularizedUnionOf<ResultPoint>(pieces, true);
1656 }
1657 }
1658
1659 return decomposedMinkowskiSum<ResultPoint>(left, right);
1660}
1661
1687template <class ResultPoint, class ShapeA, class ShapeB>
1688PolygonWithHoles<ResultPoint> singleRegionMinkowskiSum(const ShapeA& a, const ShapeB& b) {
1689 PolygonSet<ResultPoint> sum = regularizedMinkowskiSum<ResultPoint>(a, b);
1690 if (sum.componentCount() == 0) {
1692 }
1693 return sum.component(0);
1694}
1695
1708template <class ResultPoint, class SetT, class ShapeB>
1709PolygonSet<ResultPoint> setMinkowskiSum(const SetT& set, const ShapeB& other) {
1710 using SumPoint = minkowskiPoint_t<SetT, ShapeB>;
1711 using SumNumber = typename SumPoint::NumberType;
1713
1714 std::vector<PolygonWithHoles<ExactPoint>> regions;
1715 const auto addSum = [&regions](const auto& left, const auto& right) {
1716 for (const auto& region : regularizedMinkowskiSum<ExactPoint>(left, right)) {
1717 regions.push_back(region);
1718 }
1719 };
1720 for (const auto& component : set) {
1721 if constexpr (is_polygon_set_v<ShapeB>) {
1722 for (const auto& piece : other) {
1723 addSum(component, piece);
1724 }
1725 } else {
1726 addSum(component, other);
1727 }
1728 }
1729 // A component sum is a region and may carry a slit, so the coverage
1730 // classifier is not available: this is the witness path, as it is wherever a
1731 // region is united rather than a convex piece.
1732 return regularizedUnionOf<ResultPoint>(regions);
1733}
1734
1735} // namespace detail
1736
1737// -----------------------------------------------------------------------------
1738// Out-of-line: the region-valued Minkowski sums are declared in
1739// shape/polyline.hpp, shape/polygon.hpp and shape/polygonwithholes.hpp, which
1740// precede this header in the layering, but they can only be defined once
1741// Triangulation is visible.
1742
1743// A pair with a body in it is one region — see @ref detail::singleRegionMinkowskiSum
1744// for why, and for what a degenerate operand costs.
1745
1746#define PGL_DEFINE_REGION_MINKOWSKI_SUM(RECEIVER, CONCEPT, OPERAND) \
1747 template <class PointType_, class TLabel> \
1748 template <class ResultNumber, CONCEPT OPERAND> \
1749 PolygonWithHoles<Point<ResultNumber, typename PointType_::LabelType>> \
1750 RECEIVER<PointType_, TLabel>::minkowskiSum(const OPERAND& other) const { \
1751 return detail::singleRegionMinkowskiSum< \
1752 Point<ResultNumber, typename PointType_::LabelType>>(*this, other); \
1753 }
1754
1755// A pair with no body in it, where the regularization can genuinely leave several
1756// components for operands that are in no way degenerate.
1757
1758#define PGL_DEFINE_REGION_SET_MINKOWSKI_SUM(RECEIVER, CONCEPT, OPERAND) \
1759 template <class PointType_, class TLabel> \
1760 template <class ResultNumber, CONCEPT OPERAND> \
1761 PolygonSet<Point<ResultNumber, typename PointType_::LabelType>> \
1762 RECEIVER<PointType_, TLabel>::minkowskiSum(const OPERAND& other) const { \
1763 return detail::regularizedMinkowskiSum< \
1764 Point<ResultNumber, typename PointType_::LabelType>>(*this, other); \
1765 }
1766
1772
1778
1779// A Polyline has no area, so most of its operands are the ones that have some.
1780// A Segment is the exception, and belongs here for the reason a chain does: two
1781// shapes with no area between them still sweep one out, since an edge of the
1782// chain and the segment span a parallelogram unless they are parallel.
1788
1789// The thinnest operand any of the three receivers takes. A segment is a single
1790// convex piece, so it costs one convex merge per piece of the receiver, and it
1791// is the receiver's own shape that decides whether the sweep strands a cavity.
1792// The two polygonal receivers are bodies and hold the sum in one region; the
1793// polyline is the pair with no body on either side, and stays a set.
1800
1801// Two chains, the pair with the least area of all: both operands decompose into
1802// their edges, and a segment operand is the one-edge case of it. A polyline
1803// outranks a monotone chain and owns the mixed pair; the chain's own forwarder
1804// reaches this definition for it.
1807
1808// The chain's two non-convex pairs, mirrored so that neither spelling is the
1809// privileged one, exactly as `Polygon` and `PolygonWithHoles` mirror theirs. The
1810// two calls build the same piece sums and take the same union, so they agree by
1811// construction rather than by a forwarding hop.
1814
1815// A monotone chain against a non-convex receiver, which owns the pair by rank.
1816// The chain's monotonicity buys nothing here: it is the receiver's concavity that
1817// calls for a region, and no sorting of the chain's edges takes that back.
1820
1821#undef PGL_DEFINE_REGION_MINKOWSKI_SUM
1822
1823// -----------------------------------------------------------------------------
1824// The chain-valued receiver's own overload set: declared in
1825// shape/monotonechain.hpp, and a polygon rather than a region-with-holes result
1826// for every bounded convex operand a monotone chain accepts.
1827
1828#define PGL_DEFINE_CHAIN_MINKOWSKI_SUM(CONCEPT, OPERAND) \
1829 template <class PointType_, class TLabel, class Storage> \
1830 template <class ResultNumber, CONCEPT OPERAND> \
1831 Polygon<Point<ResultNumber, typename PointType_::LabelType>> \
1832 MonotoneChain<PointType_, TLabel, Storage>::minkowskiSum(const OPERAND& other) const { \
1833 return detail::chainMinkowskiSum<Point<ResultNumber, typename PointType_::LabelType>>( \
1834 *this, other); \
1835 }
1836
1840
1841#undef PGL_DEFINE_CHAIN_MINKOWSKI_SUM
1842
1843// The two operands with no area of their own. The sweep above has nothing to say
1844// about them: a summand with no area leaves consecutive pieces of the sum merely
1845// touching rather than overlapping, so the sum can pinch shut and needs regions —
1846// the same answer, from the same engine, as a Polyline's.
1847
1848#define PGL_DEFINE_CHAIN_REGULARIZED_SUM(CONCEPT, OPERAND) \
1849 template <class PointType_, class TLabel, class Storage> \
1850 template <class ResultNumber, CONCEPT OPERAND> \
1851 PolygonSet<Point<ResultNumber, typename PointType_::LabelType>> \
1852 MonotoneChain<PointType_, TLabel, Storage>::minkowskiSum(const OPERAND& other) const { \
1853 return detail::regularizedMinkowskiSum< \
1854 Point<ResultNumber, typename PointType_::LabelType>>(*this, other); \
1855 }
1856
1859// A second chain is not convex either, so the sweep has nothing to say about it
1860// and the region-valued engine answers, as it does for two polylines.
1862
1863#undef PGL_DEFINE_CHAIN_REGULARIZED_SUM
1864
1865// -----------------------------------------------------------------------------
1866// The set receiver: one definition over the whole operand family, since the
1867// construction reads none of the operand's shape — it hands each component pair
1868// to the engine above and unites what comes back.
1869
1870template <class PointType_, class TLabel>
1871template <class ResultNumber, detail::SetMinkowskiOperandConcept OtherShape>
1873PolygonSet<PointType_, TLabel>::minkowskiSum(const OtherShape& other) const {
1874 return detail::setMinkowskiSum<Point<ResultNumber, typename PointType_::LabelType>>(*this,
1875 other);
1876}
1877
1878// The mirror spellings. A set outranks every one of these, so it owns the pair
1879// and they hand it straight over — the three receivers that carry no rank-based
1880// forwarder of their own, unlike the convex shapes and the chain.
1881
1882#define PGL_DEFINE_SET_MIRROR_MINKOWSKI_SUM(RECEIVER) \
1883 template <class PointType_, class TLabel> \
1884 template <class ResultNumber, PolygonSetConcept OtherSet> \
1885 PolygonSet<Point<ResultNumber, typename PointType_::LabelType>> \
1886 RECEIVER<PointType_, TLabel>::minkowskiSum(const OtherSet& other) const { \
1887 return other.template minkowskiSum<ResultNumber>(*this); \
1888 }
1889
1893
1894#undef PGL_DEFINE_SET_MIRROR_MINKOWSKI_SUM
1895
1896} // namespace pgl
Regularized boolean operations on closed polygonal regions.
Definition forward.hpp:315
Definition forward.hpp:320
Definition forward.hpp:308
Definition forward.hpp:316
Definition forward.hpp:317
Definition forward.hpp:321
Definition forward.hpp:313
Definition forward.hpp:307
Definition forward.hpp:314
#define PGL_DEFINE_REGION_MINKOWSKI_SUM(RECEIVER, CONCEPT, OPERAND)
Definition minkowskisum.hpp:1746
#define PGL_DEFINE_SET_MIRROR_MINKOWSKI_SUM(RECEIVER)
Definition minkowskisum.hpp:1882
#define PGL_DEFINE_REGION_SET_MINKOWSKI_SUM(RECEIVER, CONCEPT, OPERAND)
Definition minkowskisum.hpp:1758
#define PGL_DEFINE_CHAIN_MINKOWSKI_SUM(CONCEPT, OPERAND)
Definition minkowskisum.hpp:1828
#define PGL_DEFINE_CHAIN_REGULARIZED_SUM(CONCEPT, OPERAND)
Definition minkowskisum.hpp:1848
Definition arrangement.hpp:67
@ x
Definition intervaltree.hpp:24
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
Point() -> Point< int >
constexpr std::partial_ordering dotSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b)
Tells if the angle between two vectors is acute, right, or obtuse.
Definition orientation.hpp:688
PolygonSet() -> PolygonSet< Point<>, NoLabel >
Definition polygonset.hpp:1699
constexpr std::partial_ordering orientationSign(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Classifies the orientation of three points.
Definition orientation.hpp:544
Rational(T) -> Rational< T >
MonotoneChain() -> MonotoneChain< Point<>, NoLabel >
Definition monotonechain.hpp:2439
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
Shape(const std::variant< T, Ts... > &) -> Shape< detail::shape_point_type_t< T > >
PolygonWithHoles() -> PolygonWithHoles< Point<>, NoLabel >
Definition polygonwithholes.hpp:3093
Convex() -> Convex< Point<>, NoLabel >
Definition convex.hpp:3311
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
Set of closed regions with pairwise disjoint interiors.
Definition polygonset.hpp:165
constexpr auto minkowskiSum(const OtherShape &other) const
Returns the Minkowski sum of this shape and another (A ⊕ B).
Definition minkowski.hpp:807
friend struct PolygonWithHoles
Definition polygonwithholes.hpp:3314
constexpr Polygon()=default
constexpr Polyline()=default