Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
arrangement.hpp
Go to the documentation of this file.
1#pragma once
2
4
45
46#include <algorithm>
47#include <bit>
48#include <cassert>
49#include <cstddef>
50#include <cstdint>
51#include <functional>
52#include <map>
53#include <memory>
54#include <numeric>
55#include <optional>
56#include <random>
57#include <ranges>
58#include <set>
59#include <span>
60#include <stdexcept>
61#include <type_traits>
62#include <unordered_map>
63#include <utility>
64#include <variant>
65#include <vector>
66
67namespace pgl {
68
69namespace detail {
70
71struct SimpleBoundariesTag {};
72inline constexpr SimpleBoundariesTag simpleBoundaries;
73
95template <class ExactPoint>
96int ringOrientation(const std::vector<ExactPoint>& ring) {
97 if (ring.size() < 3) {
98 return 0;
99 }
100 const std::size_t leftmost =
101 static_cast<std::size_t>(std::min_element(ring.begin(), ring.end()) - ring.begin());
102 const ExactPoint& corner = ring[leftmost];
103 const ExactPoint& ahead = ring[(leftmost + 1) % ring.size()];
104 const ExactPoint& behind = ring[(leftmost + ring.size() - 1) % ring.size()];
105 const auto turn = orientationSign(corner, ahead, behind);
106 if (turn > 0) {
107 return 1;
108 }
109 return turn < 0 ? -1 : 0;
110}
111
122template <class ExactPoint>
123void splitWalkIntoRings(const std::vector<ExactPoint>& walk,
124 std::vector<std::vector<ExactPoint>>& out) {
125 std::vector<ExactPoint> pending;
126 std::map<ExactPoint, std::size_t> position;
127 for (const ExactPoint& vertex : walk) {
128 const auto seen = position.find(vertex);
129 if (seen != position.end()) {
130 const std::size_t from = seen->second;
131 out.emplace_back(pending.begin() + static_cast<std::ptrdiff_t>(from), pending.end());
132 for (std::size_t i = from; i < pending.size(); ++i) {
133 position.erase(pending[i]);
134 }
135 pending.resize(from);
136 }
137 position.emplace(vertex, pending.size());
138 pending.push_back(vertex);
139 }
140 if (!pending.empty()) {
141 out.push_back(std::move(pending));
142 }
143}
144
145} // namespace detail
146
170template <class PointType_, class TLabel>
172 // Tags that keep the three handle families distinct types, so a vertex
173 // handle can never be passed where a halfedge or face one is meant.
174 struct VertexTag;
175 struct HalfedgeTag;
176 struct FaceTag;
177 class TrapezoidPointLocation;
178
179public:
181 using VertexId = detail::Handle<VertexTag>;
183 using HalfedgeId = detail::Handle<HalfedgeTag>;
185 using FaceId = detail::Handle<FaceTag>;
187 using CellId = std::variant<VertexId, HalfedgeId, FaceId>;
188
190 using PointType = PointType_;
192 using NumberType = typename PointType::NumberType;
194 using LabelType = TLabel;
206 using HalfedgeType = std::variant<OrientedSegmentType, OrientedLineType, RayType>;
208 using EdgeType = std::variant<SegmentType, LineType, RayType>;
210 using IntersectionId = std::variant<HalfedgeId, VertexId>;
211
212 static_assert(detail::is_point_v<PointType>, "Arrangement requires pgl::Point vertices");
213
216 buildFaces();
217 }
218
261 template <std::ranges::input_range ShapeRange>
262 explicit Arrangement(const ShapeRange& shapes) {
263 std::vector<InputSegment> segments;
264 std::vector<InputCurve> curves;
265 std::vector<PointType> isolated;
266 collect(shapes, segments, curves, isolated);
267 build(segments, curves, isolated, false);
268 }
269
270 // Internal fast path for boolean operands whose boundary rings are known
271 // simple and non-overlapping within each input shape.
272 template <std::ranges::input_range ShapeRange>
273 Arrangement(const ShapeRange& shapes, detail::SimpleBoundariesTag) {
274 std::vector<InputSegment> segments;
275 std::vector<InputCurve> curves;
276 std::vector<PointType> isolated;
277 collect(shapes, segments, curves, isolated);
278 build(segments, curves, isolated, true);
279 }
280
299 template <std::ranges::input_range ShapeRange, std::ranges::input_range PointRange>
300 Arrangement(const ShapeRange& shapes, const PointRange& points) {
301 std::vector<InputSegment> segments;
302 std::vector<InputCurve> curves;
303 std::vector<PointType> isolated;
304 collect(shapes, segments, curves, isolated);
305 for (const auto& point : points) {
306 isolated.emplace_back(point);
307 }
308 build(segments, curves, isolated, false);
309 }
310
311 // -------------------------------------------------------------------------
312 // Cells
313
315 [[nodiscard]] std::size_t vertexCount() const {
316 return points_.size();
317 }
318
320 [[nodiscard]] std::size_t halfedgeCount() const {
321 return origin_.size();
322 }
323
325 [[nodiscard]] std::size_t edgeCount() const {
326 return origin_.size() / 2;
327 }
328
330 [[nodiscard]] std::size_t faceCount() const {
331 return outerCycle_.size();
332 }
333
341 [[nodiscard]] const std::vector<PointType>& vertices() const {
342 return points_;
343 }
344
346 [[nodiscard]] std::vector<SegmentType> boundedEdges() const {
347 std::vector<SegmentType> result;
348 result.reserve(edgeCount());
349 for (std::size_t i = 0; i < edgeGeometry_.size(); ++i) {
350 const EdgeGeometry& geometry = edgeGeometry_[i];
351 if (geometry.kind != EdgeKind::segment) {
352 continue;
353 }
354 SegmentType edge(geometry.a, geometry.b);
355 if constexpr (detail::has_label_v<TLabel>) {
356 edge.label() = edgeLabel_[i];
357 }
358 result.push_back(std::move(edge));
359 }
360 return result;
361 }
362
369 [[nodiscard]] std::vector<EdgeType> edges() const {
370 std::vector<EdgeType> result;
371 result.reserve(edgeCount());
372 for (std::size_t i = 0; i < edgeGeometry_.size(); ++i) {
373 const EdgeGeometry& geometry = edgeGeometry_[i];
374 if (geometry.kind == EdgeKind::segment) {
375 SegmentType edge(geometry.a, geometry.b);
376 if constexpr (detail::has_label_v<TLabel>) {
377 edge.label() = edgeLabel_[i];
378 }
379 result.emplace_back(std::move(edge));
380 } else if (geometry.kind == EdgeKind::line) {
381 LineType edge(geometry.a, geometry.b);
382 if constexpr (detail::has_label_v<TLabel>) {
383 edge.label() = edgeLabel_[i];
384 }
385 result.emplace_back(std::move(edge));
386 } else {
387 RayType edge(geometry.a, geometry.b);
388 if constexpr (detail::has_label_v<TLabel>) {
389 edge.label() = edgeLabel_[i];
390 }
391 result.emplace_back(std::move(edge));
392 }
393 }
394 return result;
395 }
396
403 [[nodiscard]] const PointType& operator[](VertexId v) const {
404 if (!v.valid() || v.index() >= points_.size()) {
405 throw std::logic_error("the fictitious arrangement vertex has no finite position");
406 }
407 return points_[v.index()];
408 }
409
419 [[nodiscard]] HalfedgeType operator[](HalfedgeId h) const {
420 assert(h.valid() && h.index() < origin_.size());
421 const EdgeGeometry& geometry = edgeGeometry_[h.index() / 2];
422 const TLabel& label = edgeLabel_[h.index() / 2];
423 if (geometry.kind == EdgeKind::segment) {
424 OrientedSegmentType segment(points_[origin_[h.index()]],
425 points_[origin_[h.index() ^ 1]]);
426 if constexpr (detail::has_label_v<TLabel>) {
427 segment.label() = label;
428 }
429 return segment;
430 }
431 if (geometry.kind == EdgeKind::line) {
432 OrientedLineType line(h.index() % 2 == 0 ? geometry.a : geometry.b,
433 h.index() % 2 == 0 ? geometry.b : geometry.a);
434 if constexpr (detail::has_label_v<TLabel>) {
435 line.label() = label;
436 }
437 return line;
438 }
439 RayType ray(geometry.a, geometry.b);
440 if constexpr (detail::has_label_v<TLabel>) {
441 ray.label() = label;
442 }
443 return ray;
444 }
445
446 // -------------------------------------------------------------------------
447 // Incidence
448
456 [[nodiscard]] HalfedgeId twin(HalfedgeId h) const {
457 assert(h.valid() && h.index() < origin_.size());
458 return HalfedgeId(h.index() ^ 1);
459 }
460
466 [[nodiscard]] HalfedgeId next(HalfedgeId h) const {
467 assert(h.valid() && h.index() < next_.size());
468 return HalfedgeId(next_[h.index()]);
469 }
470
476 [[nodiscard]] VertexId source(HalfedgeId h) const {
477 assert(h.valid() && h.index() < origin_.size());
478 return VertexId(origin_[h.index()]);
479 }
480
486 [[nodiscard]] VertexId target(HalfedgeId h) const {
487 return source(twin(h));
488 }
489
495 [[nodiscard]] FaceId face(HalfedgeId h) const {
496 assert(h.valid() && h.index() < face_.size());
497 return FaceId(face_[h.index()]);
498 }
499
511 [[nodiscard]] HalfedgeId outgoing(VertexId v) const {
512 assert(v.valid() && v.index() < outgoing_.size());
513 return outgoing_[v.index()];
514 }
515
526 [[nodiscard]] std::size_t degree(VertexId v) const {
527 assert(v.valid() && v.index() < outgoing_.size());
528 const HalfedgeId start = outgoing_[v.index()];
529 if (!start.valid()) {
530 return 0;
531 }
532 std::size_t count = 0;
533 HalfedgeId h = start;
534 do {
535 ++count;
536 h = next(twin(h));
537 } while (h != start);
538 return count;
539 }
540
550 [[nodiscard]] std::vector<HalfedgeId> outgoingHalfedges(VertexId v) const {
551 assert(v.valid() && v.index() < outgoing_.size());
552 std::vector<HalfedgeId> halfedges;
553 const HalfedgeId start = outgoing_[v.index()];
554 if (!start.valid()) {
555 return halfedges;
556 }
557 HalfedgeId h = start;
558 do {
559 halfedges.push_back(h);
560 h = next(twin(h));
561 } while (h != start);
562 return halfedges;
563 }
564
588 [[nodiscard]] Graph<VertexId> asGraph() const {
589 Graph<VertexId> result;
590 for (std::uint32_t v = 0; v < topologicalVertexCount(); ++v) {
591 result.addVertex(VertexId(v));
592 }
593 for (std::size_t h = 0; h < origin_.size(); h += 2) {
594 result.addEdge(VertexId(origin_[h]), VertexId(origin_[h + 1]));
595 }
596 return result;
597 }
598
599 // -------------------------------------------------------------------------
600 // The uniform cell interface
601
609 template <class ResultNumber = NumberType>
610 [[nodiscard]] Point<ResultNumber> witness(VertexId v) const {
611 return Point<ResultNumber>((*this)[v]);
612 }
613
622 template <class ResultNumber = division_result_t<NumberType>>
623 [[nodiscard]] Point<ResultNumber> witness(HalfedgeId h) const {
624 assert(h.valid() && h.index() < origin_.size());
625 const EdgeGeometry& geometry = edgeGeometry_[h.index() / 2];
626 const PointType& a = geometry.a;
627 const PointType& b = geometry.b;
628 const ResultNumber two = static_cast<ResultNumber>(NumberType(2));
629 return Point<ResultNumber>(
630 (detail::asNumber<ResultNumber>(a.x()) + detail::asNumber<ResultNumber>(b.x())) / two,
631 (detail::asNumber<ResultNumber>(a.y()) + detail::asNumber<ResultNumber>(b.y())) / two);
632 }
633
662 template <class ResultNumber = division_result_t<NumberType>>
663 [[nodiscard]] Point<ResultNumber> witness(FaceId f) const {
664 assert(f.valid() && !isUnbounded(f));
665 if (hasSimpleBoundary(f)) {
666 return ringWitness<ResultNumber>(outerCycle_[f.index()]);
667 }
668 return sweptWitness<ResultNumber>(f);
669 }
670
671 // -------------------------------------------------------------------------
672 // Faces
673
686 [[nodiscard]] bool hasSimpleBoundary(FaceId f) const {
687 assert(f.valid() && f.index() < outerCycle_.size());
688 if (!outerCycle_[f.index()].valid() || !innerCycles(f).empty()) {
689 return false;
690 }
691 const std::uint32_t start = outerCycle_[f.index()].index();
692 std::uint32_t h = start;
693 do {
694 if (face_[h ^ 1] == f.index()) {
695 return false;
696 }
697 h = next_[h];
698 } while (h != start);
699 return true;
700 }
701
702private:
703 // The witness of a simple ring, by the argument Polygon::pointInside uses:
704 // the ring's leftmost vertex is convex, so either the triangle it makes with
705 // its two neighbours is an ear — and its interior point will do — or the
706 // nearest vertex inside that triangle cuts a diagonal whose midpoint is
707 // interior. Reading the cycle in place keeps this allocation-free.
708 template <class ResultNumber>
709 [[nodiscard]] Point<ResultNumber> ringWitness(HalfedgeId start) const {
710 std::uint32_t leftmost = start.index();
711 std::uint32_t beforeLeftmost = start.index();
712 std::uint32_t previous = start.index();
713 for (std::uint32_t h = next_[start.index()]; h != start.index();
714 previous = h, h = next_[h]) {
715 if (points_[origin_[h]] < points_[origin_[leftmost]]) {
716 leftmost = h;
717 beforeLeftmost = previous;
718 }
719 }
720 if (leftmost == start.index()) {
721 beforeLeftmost = previous; // the cycle's last halfedge closes onto it
722 }
723 const PointType& corner = points_[origin_[leftmost]];
724 const PointType& ahead = points_[origin_[next_[leftmost]]];
725 const PointType& behind = points_[origin_[beforeLeftmost]];
726 const Triangle<PointType> ear(corner, ahead, behind);
727 assert(!ear.isDegenerate());
728
729 // The leftmost vertex inside the ear, skipping the ear's own corners.
730 const PointType* diagonal = nullptr;
731 for (std::uint32_t h = next_[next_[leftmost]]; h != leftmost; h = next_[h]) {
732 const PointType& vertex = points_[origin_[h]];
733 if (ear.interiorContains(vertex) &&
734 (diagonal == nullptr || vertex < *diagonal)) {
735 diagonal = &vertex;
736 }
737 }
738 if (diagonal != nullptr) {
739 return (Point<ResultNumber>(corner) + Point<ResultNumber>(*diagonal)) /
740 static_cast<ResultNumber>(NumberType(2));
741 }
742 return ear.template pointInside<ResultNumber>();
743 }
744
745 // The witness of a face whose boundary is anything else.
746 template <class ResultNumber>
747 [[nodiscard]] Point<ResultNumber> sweptWitness(FaceId f) const {
748 const HalfedgeId seed = outerCycle_[f.index()];
749 const PointType& a = points_[origin_[seed.index()]];
750 const PointType& b = points_[origin_[seed.index() ^ 1]];
751
752 // Twice the midpoint of the seed halfedge, and the normal pointing into
753 // the face — the face is the one to the left of `seed`, and rotating a
754 // direction a quarter turn counterclockwise points left. Keeping the
755 // midpoint doubled leaves every quantity below a polynomial in the input
756 // coordinates, so the only division is the final one.
757 //
758 // Those polynomials reach degree four, since ordering two hits along the
759 // ray cross-multiplies two quadratic parameters, so they are formed in
760 // @ref WideNumber. That holds every one of them for a coordinate type
761 // whose promotion grows — and for a fixed-width one, coordinates up to
762 // the fourth root of the widened range, which for `int` is about `2^28`.
763 const auto wide = [](const NumberType& value) {
764 return static_cast<WideNumber>(value);
765 };
766 const WideNumber midX = wide(a.x()) + wide(b.x());
767 const WideNumber midY = wide(a.y()) + wide(b.y());
768 const WideNumber normalX = wide(a.y()) - wide(b.y());
769 const WideNumber normalY = wide(b.x()) - wide(a.x());
770 const WideNumber two(2);
771
772 // The first point where the inward ray meets the boundary, as the exact
773 // fraction hitNumerator / hitDenominator with a positive denominator.
774 WideNumber hitNumerator(0);
775 WideNumber hitDenominator(0);
776 const auto offer = [&](WideNumber numerator, WideNumber denominator) {
777 if (denominator < WideNumber(0)) {
778 numerator = -numerator;
779 denominator = -denominator;
780 }
781 if (numerator <= WideNumber(0)) {
782 return; // behind the ray's start, or at it
783 }
784 if (hitDenominator == WideNumber(0) ||
785 numerator * hitDenominator < hitNumerator * denominator) {
786 hitNumerator = numerator;
787 hitDenominator = denominator;
788 }
789 };
790
791 forEachBoundaryHalfedge(f, [&](HalfedgeId h) {
792 if (h.index() / 2 == seed.index() / 2) {
793 return; // the ray leaves its own edge, and meets it nowhere else
794 }
795 const PointType& p = points_[origin_[h.index()]];
796 const PointType& q = points_[origin_[h.index() ^ 1]];
797 const WideNumber edgeX = wide(q.x()) - wide(p.x());
798 const WideNumber edgeY = wide(q.y()) - wide(p.y());
799 // Twice the vector from the ray's start to the edge's first endpoint.
800 const WideNumber toEdgeX = two * wide(p.x()) - midX;
801 const WideNumber toEdgeY = two * wide(p.y()) - midY;
802 const WideNumber denominator = normalX * edgeY - normalY * edgeX;
803 const WideNumber alongEdge = toEdgeX * normalY - toEdgeY * normalX;
804 if (denominator != WideNumber(0)) {
805 // A proper crossing: the ray meets the edge's line at parameter
806 // (toEdge x edge) / 2·denominator, inside the edge when the
807 // parameter along the edge stays within [0, 1].
808 WideNumber along = alongEdge;
809 WideNumber scale = two * denominator;
810 if (scale < WideNumber(0)) {
811 along = -along;
812 scale = -scale;
813 }
814 if (along < WideNumber(0) || along > scale) {
815 return;
816 }
817 offer(toEdgeX * edgeY - toEdgeY * edgeX, two * denominator);
818 } else if (alongEdge == WideNumber(0)) {
819 // The edge lies along the ray: it blocks it at whichever of its
820 // endpoints comes first.
821 const WideNumber squaredNormal = normalX * normalX + normalY * normalY;
822 for (const PointType& endpoint : {p, q}) {
823 offer((two * wide(endpoint.x()) - midX) * normalX +
824 (two * wide(endpoint.y()) - midY) * normalY,
825 two * squaredNormal);
826 }
827 }
828 });
829
830 // A bounded face confines the ray, so it is always stopped.
831 assert(hitDenominator != WideNumber(0));
832 const ResultNumber scale = static_cast<ResultNumber>(two * hitDenominator);
833 return Point<ResultNumber>(
834 static_cast<ResultNumber>(midX * hitDenominator + hitNumerator * normalX) / scale,
835 static_cast<ResultNumber>(midY * hitDenominator + hitNumerator * normalY) / scale);
836 }
837
838public:
839 // -------------------------------------------------------------------------
840 // Faces, continued
841
849 [[nodiscard]] bool isUnbounded() const {
850 return infinity_.valid();
851 }
852
863 [[nodiscard]] bool isUnbounded(HalfedgeId h) const {
864 assert(h.valid() && h.index() < origin_.size());
865 return infinity_.valid() &&
866 (source(h) == infinity_ || target(h) == infinity_);
867 }
868
874 [[nodiscard]] bool isUnbounded(FaceId f) const {
875 assert(f.valid() && f.index() < outerCycle_.size());
876 return unboundedFace_[f.index()];
877 }
878
880 [[nodiscard]] bool isFictitious(VertexId v) const {
881 assert(v.valid() && v.index() < topologicalVertexCount());
882 return infinity_.valid() && v == infinity_;
883 }
884
891 [[nodiscard]] HalfedgeId outerCycle(FaceId f) const {
892 assert(f.valid() && f.index() < outerCycle_.size());
893 return outerCycle_[f.index()];
894 }
895
906 [[nodiscard]] std::span<const HalfedgeId> innerCycles(FaceId f) const {
907 assert(f.valid() && f.index() + 1 < innerOffset_.size());
908 const std::size_t from = innerOffset_[f.index()];
909 const std::size_t to = innerOffset_[f.index() + 1];
910 return std::span<const HalfedgeId>(innerCycle_.data() + from, to - from);
911 }
912
924 [[nodiscard]] std::vector<HalfedgeId> boundaryOf(FaceId f) const {
925 assert(f.valid() && f.index() < outerCycle_.size());
926 std::vector<HalfedgeId> boundary;
927 const auto walkCycle = [&](HalfedgeId start) {
928 HalfedgeId h = start;
929 do {
930 boundary.push_back(h);
931 h = next(h);
932 } while (h != start);
933 };
934 if (outerCycle_[f.index()].valid()) {
935 walkCycle(outerCycle_[f.index()]);
936 }
937 for (HalfedgeId inner : innerCycles(f)) {
938 walkCycle(inner);
939 }
940 return boundary;
941 }
942
951 [[nodiscard]] std::vector<HalfedgeId> outerBoundaryOf(FaceId f) const {
952 assert(f.valid() && f.index() < outerCycle_.size());
953 std::vector<HalfedgeId> boundary;
954 const HalfedgeId start = outerCycle_[f.index()];
955 if (!start.valid()) {
956 return boundary;
957 }
958 HalfedgeId h = start;
959 do {
960 boundary.push_back(h);
961 h = next(h);
962 } while (h != start);
963 return boundary;
964 }
965
975 [[nodiscard]] std::vector<std::vector<HalfedgeId>> innerBoundariesOf(FaceId f) const {
976 assert(f.valid() && f.index() < outerCycle_.size());
977 std::vector<std::vector<HalfedgeId>> boundaries;
978 boundaries.reserve(innerCycles(f).size());
979 for (HalfedgeId start : innerCycles(f)) {
980 std::vector<HalfedgeId>& boundary = boundaries.emplace_back();
981 HalfedgeId h = start;
982 do {
983 boundary.push_back(h);
984 h = next(h);
985 } while (h != start);
986 }
987 return boundaries;
988 }
989
1004 template <class ResultNumber = NumberType>
1006 if (!f.valid() || f.index() >= outerCycle_.size() || isUnbounded(f)) {
1007 throw std::logic_error(
1008 "Arrangement::polygonWithHoles is only defined for bounded faces");
1009 }
1010 using ExactPolygon = Polygon<PointType>;
1011
1012 std::vector<ExactPolygon> rings;
1013 collectRings(cycleRing(outerCycle_[f.index()]), rings);
1014 assert(!rings.empty());
1015 // An outer cycle that pinches shut comes apart into several rings, and
1016 // the one holding the rest — the largest, since they are nested — is the
1017 // outer boundary.
1018 const auto largest =
1019 std::max_element(rings.begin(), rings.end(),
1020 [](const ExactPolygon& left, const ExactPolygon& right) {
1021 return left.twiceArea() < right.twiceArea();
1022 });
1023 if (largest != rings.begin()) {
1024 std::iter_swap(rings.begin(), largest);
1025 }
1026 std::vector<ExactPolygon> holes(rings.begin() + 1, rings.end());
1027 for (HalfedgeId inner : innerCycles(f)) {
1028 collectRings(cycleRing(inner), holes);
1029 }
1030 const PolygonWithHoles<PointType> exact(std::move(rings.front()), std::move(holes));
1032 }
1033
1055 template <class ResultNumber = NumberType>
1058 if (!f.valid() || f.index() >= outerCycle_.size()) {
1059 throw std::logic_error(
1060 "Arrangement::halfplaneIntersection requires a valid face");
1061 }
1062
1063 using ResultPoint = Point<ResultNumber>;
1064 using ResultHalfplane = Halfplane<ResultPoint>;
1065 std::vector<ResultHalfplane> halfplanes;
1066 const auto appendCycle = [&](HalfedgeId start) {
1067 HalfedgeId h = start;
1068 do {
1069 if (face(twin(h)) != f) {
1070 const EdgeGeometry& geometry = edgeGeometry_[h.index() / 2];
1071 const bool forward = h.index() % 2 == 0;
1072 halfplanes.emplace_back(
1073 ResultPoint(forward ? geometry.a : geometry.b),
1074 ResultPoint(forward ? geometry.b : geometry.a));
1075 }
1076 h = next(h);
1077 } while (h != start);
1078 };
1079
1080 if (outerCycle_[f.index()].valid()) {
1081 appendCycle(outerCycle_[f.index()]);
1082 } else {
1083 for (HalfedgeId start : innerCycles(f)) {
1084 bool reachesInfinity = false;
1085 HalfedgeId h = start;
1086 do {
1087 reachesInfinity = reachesInfinity || isUnbounded(h);
1088 h = next(h);
1089 } while (h != start);
1090 if (reachesInfinity) {
1091 appendCycle(start);
1092 }
1093 }
1094 }
1095 return HalfplaneIntersection<ResultPoint>(std::move(halfplanes));
1096 }
1097
1098 // -------------------------------------------------------------------------
1099 // Labels and history
1100
1111 [[nodiscard]] const TLabel& label(HalfedgeId h) const {
1112 assert(h.valid() && h.index() < origin_.size());
1113 return edgeLabel_[h.index() / 2];
1114 }
1115
1117 [[nodiscard]] TLabel& label(HalfedgeId h) {
1118 assert(h.valid() && h.index() < origin_.size());
1119 return edgeLabel_[h.index() / 2];
1120 }
1121
1131 [[nodiscard]] const TLabel& label(FaceId f) const {
1132 assert(f.valid() && f.index() < faceLabel_.size());
1133 return faceLabel_[f.index()];
1134 }
1135
1137 [[nodiscard]] TLabel& label(FaceId f) {
1138 assert(f.valid() && f.index() < faceLabel_.size());
1139 return faceLabel_[f.index()];
1140 }
1141
1151 [[nodiscard]] std::span<const std::uint32_t> originsOf(HalfedgeId h) const {
1152 assert(h.valid() && h.index() < origin_.size());
1153 const std::size_t edge = h.index() / 2;
1154 const std::size_t from = originOffset_[edge];
1155 const std::size_t to = originOffset_[edge + 1];
1156 return std::span<const std::uint32_t>(originIndex_.data() + from, to - from);
1157 }
1158
1169 [[nodiscard]] std::vector<std::uint32_t> originsOf(VertexId v) const {
1170 assert(v.valid() && v.index() < outgoing_.size());
1171 std::vector<std::uint32_t> origins;
1172 const HalfedgeId start = outgoing_[v.index()];
1173 if (!start.valid()) {
1174 return origins;
1175 }
1176 HalfedgeId h = start;
1177 do {
1178 const std::span<const std::uint32_t> edgeOrigins = originsOf(h);
1179 origins.insert(origins.end(), edgeOrigins.begin(), edgeOrigins.end());
1180 h = next(twin(h));
1181 } while (h != start);
1182 std::sort(origins.begin(), origins.end());
1183 origins.erase(std::unique(origins.begin(), origins.end()), origins.end());
1184 return origins;
1185 }
1186
1187 // -------------------------------------------------------------------------
1188 // Location
1189
1203 void buildPointLocation();
1204
1211 template <class UniformRandomBitGenerator>
1212 void buildPointLocation(UniformRandomBitGenerator&& generator);
1213
1215 void clearPointLocation() noexcept {
1216 pointLocation_.reset();
1217 }
1218
1220 [[nodiscard]] bool hasPointLocation() const noexcept {
1221 return static_cast<bool>(pointLocation_);
1222 }
1223
1238 [[nodiscard]] FaceId locateFace(const PointType& p) const;
1239
1247 [[nodiscard]] CellId locateCell(const PointType& p) const;
1248
1249 // -------------------------------------------------------------------------
1250 // Intersection traversal
1251
1269 template <class Q, class Fn>
1272 bool visitIntersecting(const Q& r, Fn fn) const;
1273
1275 template <class Q>
1278 [[nodiscard]] std::vector<IntersectionId> reportIntersecting(const Q& r) const {
1279 std::vector<IntersectionId> result;
1280 visitIntersecting(r, [&](const IntersectionId& id) { result.push_back(id); });
1281 return result;
1282 }
1283
1285 template <class Q>
1288 [[nodiscard]] std::optional<IntersectionId> firstIntersecting(const Q& r) const {
1289 std::optional<IntersectionId> result;
1290 visitIntersecting(r, [&](const IntersectionId& id) {
1291 result = id;
1292 return true;
1293 });
1294 return result;
1295 }
1296
1298 template <class Q>
1301 [[nodiscard]] bool emptyIntersecting(const Q& r) const {
1302 return !visitIntersecting(r, [](const IntersectionId&) { return true; });
1303 }
1304
1305private:
1306 // Two promotions above the coordinates, which is what it takes to hold a
1307 // product of three coordinate differences exactly — one degree more than the
1308 // sign predicates promote for. It is the width the geometry that goes beyond
1309 // a sign works in: the crossing abscissae @ref halfedgeLeftOf orders, the
1310 // ordinate @ref pointAt cuts a carrier at, and the ray parameters
1311 // @ref sweptWitness compares, that last one quartic and so the one place a
1312 // fixed-width coordinate type is bounded further. A type that grows to hold
1313 // its values, or an approximate one, is its own promotion and pays nothing.
1314 using WideNumber =
1315 detail::promoted_number_t<detail::promoted_number_t<NumberType>>;
1316
1317 // Point location without the index: the face is the one left of the nearest
1318 // edge to the west, and a point with nothing to its west lies in the face
1319 // the boundary at infinity opens onto.
1320 [[nodiscard]] FaceId locateFaceLinear(const PointType& p) const {
1321 const HalfedgeId h = halfedgeLeftOf(p);
1322 if (h.valid()) {
1323 return face(h);
1324 }
1325 if (infinity_.valid()) {
1326 return face(infinityBoundaryAtWest(p));
1327 }
1328 return FaceId(0);
1329 }
1330
1331 // The same without the index, but naming the cell the point lies on rather
1332 // than the face around it: a vertex wins over an edge, and an edge over the
1333 // face, since a point on the boundary belongs to the lower-dimensional cell.
1334 [[nodiscard]] CellId locateCellLinear(const PointType& p) const {
1335 for (std::uint32_t v = 0; v < points_.size(); ++v) {
1336 if (points_[v] == p) {
1337 return VertexId(v);
1338 }
1339 }
1340 for (std::uint32_t edge = 0; edge < edgeGeometry_.size(); ++edge) {
1341 const EdgeGeometry& geometry = edgeGeometry_[edge];
1342 if (orientationSign(geometry.a, geometry.b, p) != 0) {
1343 continue;
1344 }
1345 bool contains = geometry.kind == EdgeKind::line;
1346 if (geometry.kind == EdgeKind::segment) {
1347 contains = p.x() >= std::min(geometry.a.x(), geometry.b.x()) &&
1348 p.x() <= std::max(geometry.a.x(), geometry.b.x()) &&
1349 p.y() >= std::min(geometry.a.y(), geometry.b.y()) &&
1350 p.y() <= std::max(geometry.a.y(), geometry.b.y());
1351 } else if (geometry.kind == EdgeKind::ray) {
1352 // Ahead of the source along the ray, which on the carrier is the
1353 // whole of it. The predicate promotes the product of the two
1354 // differences, which a raw multiplication of coordinates would
1355 // not.
1356 contains = dotSign(geometry.a, p, geometry.a, geometry.b) >= 0;
1357 }
1358 if (contains) {
1359 return HalfedgeId(2 * edge);
1360 }
1361 }
1362 return locateFaceLinear(p);
1363 }
1364
1365 // Vertices as the halfedge structure counts them, which is the geometric
1366 // vertices plus the single point at infinity when the arrangement has one.
1367 [[nodiscard]] std::size_t topologicalVertexCount() const {
1368 return points_.size() + (infinity_.valid() ? 1 : 0);
1369 }
1370
1371 // How far an edge runs: a segment is bounded at both ends, a ray only at
1372 // the first, and a line at neither.
1373 enum class EdgeKind : std::uint8_t { segment, ray, line };
1374
1375 // The defining coordinates of an edge, kept beside the topology so a
1376 // predicate never has to walk the halfedge structure to rebuild them. For a
1377 // ray, `a` is the source and `b` only fixes the direction; for a line the
1378 // two merely span it.
1379 struct EdgeGeometry {
1380 EdgeKind kind;
1381 PointType a;
1382 PointType b;
1383 };
1384
1385 // One straight piece of a query, reported in order along the piece. The
1386 // seen vectors carry the cells a chain has already reported across its
1387 // pieces, and are null for a query that is a single piece.
1388 template <class Q, class Fn>
1389 bool visitStraightIntersecting(const Q& piece, Fn& fn,
1390 std::vector<bool>* seenVertices,
1391 std::vector<bool>* seenEdges) const;
1392
1393 // A query that degenerated to a single point, which meets at most one cell.
1394 template <PointConcept Q, class Fn>
1395 bool visitPointIntersecting(const Q& point, Fn& fn,
1396 std::vector<bool>* seenVertices,
1397 std::vector<bool>* seenEdges) const;
1398
1399 // An input segment, with the position of the shape it came from.
1400 struct InputSegment {
1401 Segment<PointType> segment;
1402 std::uint32_t origin;
1403 [[no_unique_address]] TLabel label;
1404 };
1405
1406 // One piece of an input segment after splitting, before twin pieces of
1407 // overlapping input are merged into a single edge.
1408 struct Piece {
1409 PointType a;
1410 PointType b;
1411 std::uint32_t origin;
1412 [[no_unique_address]] TLabel label;
1413 };
1414
1415 // A segment, ray, or line before collinear inputs are overlaid.
1416 struct InputCurve {
1417 EdgeKind kind;
1418 PointType a;
1419 PointType b;
1420 std::uint32_t origin;
1421 [[no_unique_address]] TLabel label;
1422 };
1423
1424 // -------------------------------------------------------------------------
1425 // Input normalization
1426
1427 // Splits a shape range into the segments it contributes and the points it
1428 // reduces to, keeping each shape's position in the range as its origin.
1429 template <class ShapeRange>
1430 static void collect(const ShapeRange& shapes, std::vector<InputSegment>& segments,
1431 std::vector<InputCurve>& curves, std::vector<PointType>& isolated) {
1432 std::uint32_t index = 0;
1433 for (const auto& shape : shapes) {
1434 append(shape, index, segments, curves, isolated);
1435 ++index;
1436 }
1437 }
1438
1439 // The shapes the arrangement can be built from directly or through Shape.
1440 template <class InputShape>
1441 static constexpr bool isSupported =
1442 detail::is_empty_shape_v<InputShape> || detail::is_point_v<InputShape> ||
1443 detail::is_segment_v<InputShape> || detail::is_oriented_segment_v<InputShape> ||
1444 detail::is_line_v<InputShape> || detail::is_oriented_line_v<InputShape> ||
1445 detail::is_ray_v<InputShape> ||
1446 detail::is_polyline_v<InputShape> || detail::is_monotone_chain_v<InputShape> ||
1447 detail::is_triangle_v<InputShape> || detail::is_rectangle_v<InputShape> ||
1448 detail::is_convex_v<InputShape> || detail::is_polygon_v<InputShape> ||
1449 detail::is_polygon_with_holes_v<InputShape>;
1450
1451 // Appends the cut segments — and the isolated points — of one input shape.
1452 template <class InputShape>
1453 static void append(const InputShape& shape, std::uint32_t index,
1454 std::vector<InputSegment>& segments, std::vector<InputCurve>& curves,
1455 std::vector<PointType>& isolated) {
1456 static_assert(isSupported<InputShape> || detail::is_shape_v<InputShape>,
1457 "Arrangement accepts points, segment-bounded shapes, lines, and rays");
1458 const auto addSegment = [&](const auto& edge) {
1459 const PointType a(edge[0]);
1460 const PointType b(edge[1]);
1461 if (a == b) {
1462 isolated.push_back(a);
1463 return;
1464 }
1465 InputSegment input{Segment<PointType>(a, b), index, TLabel{}};
1466 if constexpr (detail::has_label_v<TLabel>) {
1467 input.label = detail::copyLabel<TLabel>(shape);
1468 }
1469 segments.push_back(std::move(input));
1470 InputCurve curve{EdgeKind::segment, a < b ? a : b, a < b ? b : a, index, TLabel{}};
1471 if constexpr (detail::has_label_v<TLabel>) {
1472 curve.label = detail::copyLabel<TLabel>(shape);
1473 }
1474 curves.push_back(std::move(curve));
1475 };
1476
1477 if constexpr (detail::is_shape_v<InputShape>) {
1478 std::visit([&](const auto& alternative) {
1479 if constexpr (isSupported<std::remove_cvref_t<decltype(alternative)>>) {
1480 append(alternative, index, segments, curves, isolated);
1481 } else {
1482 throw std::invalid_argument(
1483 "Arrangement accepts only points, segment-bounded shapes, lines, and rays");
1484 }
1485 }, shape.variant());
1486 } else if constexpr (detail::is_empty_shape_v<InputShape>) {
1487 (void)shape;
1488 } else if constexpr (detail::is_point_v<InputShape>) {
1489 isolated.emplace_back(shape);
1490 } else if constexpr (detail::is_segment_v<InputShape> ||
1491 detail::is_oriented_segment_v<InputShape>) {
1492 addSegment(shape);
1493 } else if constexpr (detail::is_line_v<InputShape> ||
1494 detail::is_oriented_line_v<InputShape> ||
1495 detail::is_ray_v<InputShape>) {
1496 const PointType a(shape[0]);
1497 const PointType b(shape[1]);
1498 if (a == b) {
1499 isolated.push_back(a);
1500 return;
1501 }
1502 constexpr EdgeKind kind = detail::is_ray_v<InputShape> ? EdgeKind::ray : EdgeKind::line;
1503 InputCurve curve{kind, a, b, index, TLabel{}};
1504 if constexpr (detail::has_label_v<TLabel>) {
1505 curve.label = detail::copyLabel<TLabel>(shape);
1506 }
1507 curves.push_back(std::move(curve));
1508 } else if constexpr (detail::is_polygon_with_holes_v<InputShape>) {
1509 for (const auto& edge : shape.edges()) {
1510 addSegment(edge);
1511 }
1512 } else if constexpr (detail::is_polyline_v<InputShape> ||
1513 detail::is_monotone_chain_v<InputShape>) {
1514 if (shape.size() == 1) {
1515 isolated.emplace_back(shape[0]);
1516 } else {
1517 for (const auto& edge : shape.edgesView()) {
1518 addSegment(edge);
1519 }
1520 }
1521 } else if constexpr (requires { shape.edgesView(); }) {
1522 for (const auto& edge : shape.edgesView()) {
1523 addSegment(edge);
1524 }
1525 } else {
1526 for (const auto& edge : shape.edges()) {
1527 addSegment(edge);
1528 }
1529 }
1530 }
1531
1532 // -------------------------------------------------------------------------
1533 // Construction
1534
1535 void build(std::vector<InputSegment>& segments, std::vector<InputCurve>& curves,
1536 std::vector<PointType>& isolated, bool simpleBoundaries) {
1537 const bool hasUnbounded = std::ranges::any_of(
1538 curves, [](const InputCurve& curve) { return curve.kind != EdgeKind::segment; });
1539 if (hasUnbounded) {
1540 buildUnbounded(curves, isolated);
1541 return;
1542 }
1543 std::vector<Piece> pieces = split(segments, isolated, simpleBoundaries);
1544 internVertices(pieces, isolated);
1545 simplifyStoredCoordinates();
1546 syncVertexApproximations();
1547 wireHalfedges();
1548 buildFaces();
1549 }
1550
1551 // A stretch of one carrier that some input curve covers, as a parameter
1552 // range along that carrier. An absent bound runs to infinity, and `origin`
1553 // names the input shape the stretch came from.
1554 struct CarrierInterval {
1555 std::optional<NumberType> low;
1556 std::optional<NumberType> high;
1557 std::uint32_t origin;
1558 [[no_unique_address]] TLabel label;
1559 };
1560
1561 // A supporting line, with every input stretch laid over it. Collinear input
1562 // shares one carrier, so overlap is settled by merging intervals instead of
1563 // by intersecting curves. `usesX` picks the coordinate that parameterizes
1564 // it, which is the abscissa unless the line is vertical. `cuts` holds the
1565 // parameters where the carrier must be split.
1566 struct Carrier {
1567 PointType a;
1568 PointType b;
1569 bool usesX;
1570 std::vector<CarrierInterval> intervals;
1571 std::vector<NumberType> cuts;
1572 };
1573
1574 // A piece of a carrier between consecutive cuts, so no other curve crosses
1575 // its interior. These are the pieces the halfedge structure is wired from,
1576 // and `origins` records every input shape covering the piece.
1577 struct AtomicCurve {
1578 std::uint32_t carrier;
1579 std::optional<NumberType> low;
1580 std::optional<NumberType> high;
1581 std::vector<std::uint32_t> origins;
1582 [[no_unique_address]] TLabel label;
1583 };
1584
1585 // Where a point on a carrier falls along it. Vertical carriers are
1586 // parameterized by ordinate, everything else by abscissa, so the
1587 // parameter is always strictly monotone along the carrier.
1588 static NumberType parameterOf(const Carrier& carrier, const PointType& point) {
1589 return carrier.usesX ? point.x() : point.y();
1590 }
1591
1592 // Whether an interval already spans the whole of another one, absent bounds
1593 // reaching infinity. This is what makes a repeated or contained input
1594 // stretch add nothing.
1595 static bool covers(const CarrierInterval& interval,
1596 const std::optional<NumberType>& low,
1597 const std::optional<NumberType>& high) {
1598 if (!low.has_value()) {
1599 if (interval.low.has_value()) {
1600 return false;
1601 }
1602 } else if (interval.low.has_value() && *low < *interval.low) {
1603 return false;
1604 }
1605 if (!high.has_value()) {
1606 if (interval.high.has_value()) {
1607 return false;
1608 }
1609 } else if (interval.high.has_value() && *interval.high < *high) {
1610 return false;
1611 }
1612 return true;
1613 }
1614
1615 // Whether an interval holds a single parameter, ends included.
1616 static bool covers(const CarrierInterval& interval, const NumberType& value) {
1617 return (!interval.low.has_value() || !(value < *interval.low)) &&
1618 (!interval.high.has_value() || !(*interval.high < value));
1619 }
1620
1621 // The point at a parameter along a carrier.
1622 static PointType pointAt(const Carrier& carrier, const NumberType& parameter) {
1623 PointType point = [&] {
1624 if (carrier.usesX) {
1625 // The ordinate reached at that abscissa, as a displacement from
1626 // the carrier's own: the product of two coordinate differences
1627 // outgrows a narrow coordinate type long before the quotient
1628 // does, and the quotient is what an arrangement whose vertices
1629 // are representable has to land on.
1630 const auto wide = [](const NumberType& value) {
1631 return static_cast<WideNumber>(value);
1632 };
1633 const WideNumber dx = wide(carrier.b.x()) - wide(carrier.a.x());
1634 const WideNumber dy = wide(carrier.b.y()) - wide(carrier.a.y());
1635 const WideNumber rise =
1636 (wide(parameter) - wide(carrier.a.x())) * dy / dx;
1637 return PointType(parameter,
1638 carrier.a.y() + detail::asNumber<NumberType>(rise));
1639 }
1640 return PointType(carrier.a.x(), parameter);
1641 }();
1642 // A point cut out of a carrier becomes an interned vertex, so it is
1643 // hashed and compared the moment it is built and read for the rest of the
1644 // arrangement's life. Reducing the chain of arithmetic above once, here,
1645 // is what puts all of that on the normalized fast path.
1646 if constexpr (pgl::is_Rational_v<NumberType>) {
1647 point.x().simplify();
1648 point.y().simplify();
1649 }
1650 return point;
1651 }
1652
1653 // Whether a curve is collinear with a carrier, and so belongs on it rather
1654 // than on one of its own.
1655 static bool sameCarrier(const Carrier& carrier, const InputCurve& curve) {
1656 return orientationSign(carrier.a, carrier.b, curve.a) == 0 &&
1657 orientationSign(carrier.a, carrier.b, curve.b) == 0;
1658 }
1659
1660 // General normalization for input containing a ray or a line. Collinear
1661 // inputs are overlaid as intervals on one exact carrier; intersections of
1662 // distinct carriers then become additional interval endpoints.
1663 void buildUnbounded(const std::vector<InputCurve>& curves,
1664 const std::vector<PointType>& isolated) {
1665 std::vector<Carrier> carriers;
1666 for (const InputCurve& curve : curves) {
1667 auto found = std::find_if(carriers.begin(), carriers.end(),
1668 [&](const Carrier& carrier) {
1669 return sameCarrier(carrier, curve);
1670 });
1671 if (found == carriers.end()) {
1672 PointType a = curve.a;
1673 PointType b = curve.b;
1674 if (b < a) {
1675 std::swap(a, b);
1676 }
1677 carriers.push_back(Carrier{a, b, !(a.x() == b.x()), {}, {}});
1678 found = std::prev(carriers.end());
1679 }
1680 Carrier& carrier = *found;
1681 const NumberType ta = parameterOf(carrier, curve.a);
1682 const NumberType tb = parameterOf(carrier, curve.b);
1683 CarrierInterval interval{{}, {}, curve.origin, curve.label};
1684 if (curve.kind == EdgeKind::segment) {
1685 interval.low = std::min(ta, tb);
1686 interval.high = std::max(ta, tb);
1687 } else if (curve.kind == EdgeKind::ray) {
1688 if (ta < tb) {
1689 interval.low = ta;
1690 } else {
1691 interval.high = ta;
1692 }
1693 }
1694 if (interval.low.has_value()) {
1695 carrier.cuts.push_back(*interval.low);
1696 }
1697 if (interval.high.has_value()) {
1698 carrier.cuts.push_back(*interval.high);
1699 }
1700 carrier.intervals.push_back(std::move(interval));
1701 }
1702
1703 for (Carrier& carrier : carriers) {
1704 for (const PointType& point : isolated) {
1705 if (orientationSign(carrier.a, carrier.b, point) != 0) {
1706 continue;
1707 }
1708 const NumberType value = parameterOf(carrier, point);
1709 if (std::ranges::any_of(carrier.intervals,
1710 [&](const CarrierInterval& interval) {
1711 return covers(interval, value);
1712 })) {
1713 carrier.cuts.push_back(value);
1714 }
1715 }
1716 }
1717
1718 for (std::size_t i = 0; i < carriers.size(); ++i) {
1719 for (std::size_t j = i + 1; j < carriers.size(); ++j) {
1720 const Line<PointType> first(carriers[i].a, carriers[i].b);
1721 const Line<PointType> second(carriers[j].a, carriers[j].b);
1722 const auto intersection = first.template intersection<NumberType>(second);
1723 if (!intersection || !std::holds_alternative<PointType>(*intersection)) {
1724 continue;
1725 }
1726 const PointType& point = std::get<PointType>(*intersection);
1727 const NumberType ti = parameterOf(carriers[i], point);
1728 const NumberType tj = parameterOf(carriers[j], point);
1729 const bool onFirst = std::ranges::any_of(
1730 carriers[i].intervals,
1731 [&](const CarrierInterval& interval) { return covers(interval, ti); });
1732 const bool onSecond = std::ranges::any_of(
1733 carriers[j].intervals,
1734 [&](const CarrierInterval& interval) { return covers(interval, tj); });
1735 if (onFirst && onSecond) {
1736 carriers[i].cuts.push_back(ti);
1737 carriers[j].cuts.push_back(tj);
1738 }
1739 }
1740 }
1741
1742 std::vector<AtomicCurve> atoms;
1743 for (std::uint32_t c = 0; c < carriers.size(); ++c) {
1744 Carrier& carrier = carriers[c];
1745 // A cut coming from a carrier crossing is an unreduced fraction, and
1746 // it is read by the sort and unique here, by the interval tests in
1747 // emit, and again by pointAt once it reaches an atom. One gcd apiece
1748 // covers all of it; the same reasoning as split's cut lists.
1749 if constexpr (pgl::is_Rational_v<NumberType>) {
1750 for (NumberType& cut : carrier.cuts) {
1751 cut.simplify();
1752 }
1753 }
1754 std::sort(carrier.cuts.begin(), carrier.cuts.end());
1755 carrier.cuts.erase(std::unique(carrier.cuts.begin(), carrier.cuts.end()),
1756 carrier.cuts.end());
1757
1758 const auto emit = [&](std::optional<NumberType> low,
1759 std::optional<NumberType> high) {
1760 const CarrierInterval* first = nullptr;
1761 std::vector<std::uint32_t> origins;
1762 for (const CarrierInterval& interval : carrier.intervals) {
1763 if (!covers(interval, low, high)) {
1764 continue;
1765 }
1766 if (first == nullptr || interval.origin < first->origin) {
1767 first = &interval;
1768 }
1769 origins.push_back(interval.origin);
1770 }
1771 if (first == nullptr) {
1772 return;
1773 }
1774 std::sort(origins.begin(), origins.end());
1775 origins.erase(std::unique(origins.begin(), origins.end()), origins.end());
1776 atoms.push_back(AtomicCurve{c, std::move(low), std::move(high),
1777 std::move(origins), first->label});
1778 };
1779
1780 if (carrier.cuts.empty()) {
1781 emit({}, {});
1782 continue;
1783 }
1784 emit({}, carrier.cuts.front());
1785 for (std::size_t i = 0; i + 1 < carrier.cuts.size(); ++i) {
1786 if (!(carrier.cuts[i] == carrier.cuts[i + 1])) {
1787 emit(carrier.cuts[i], carrier.cuts[i + 1]);
1788 }
1789 }
1790 emit(carrier.cuts.back(), {});
1791 }
1792
1793 std::unordered_map<PointType, std::uint32_t> vertexOf;
1794 const auto idOf = [&](const PointType& point) {
1795 const auto found = vertexOf.find(point);
1796 if (found != vertexOf.end()) {
1797 return found->second;
1798 }
1799 const auto id = static_cast<std::uint32_t>(points_.size());
1800 points_.push_back(point);
1801 vertexOf.emplace(point, id);
1802 return id;
1803 };
1804
1805 for (const AtomicCurve& atom : atoms) {
1806 if (atom.low.has_value()) {
1807 idOf(pointAt(carriers[atom.carrier], *atom.low));
1808 }
1809 if (atom.high.has_value()) {
1810 idOf(pointAt(carriers[atom.carrier], *atom.high));
1811 }
1812 }
1813 for (const PointType& point : isolated) {
1814 idOf(point);
1815 }
1816 infinity_ = VertexId(static_cast<std::uint32_t>(points_.size()));
1817
1818 originOffset_.push_back(0);
1819 for (const AtomicCurve& atom : atoms) {
1820 const Carrier& carrier = carriers[atom.carrier];
1821 if (atom.low.has_value() && atom.high.has_value()) {
1822 const PointType a = pointAt(carrier, *atom.low);
1823 const PointType b = pointAt(carrier, *atom.high);
1824 origin_.push_back(idOf(a));
1825 origin_.push_back(idOf(b));
1826 edgeGeometry_.push_back({EdgeKind::segment, a, b});
1827 } else if (atom.low.has_value() || atom.high.has_value()) {
1828 const bool increasing = atom.low.has_value();
1829 const PointType sourcePoint = pointAt(
1830 carrier, increasing ? *atom.low : *atom.high);
1831 const NumberType dx = carrier.b.x() - carrier.a.x();
1832 const NumberType dy = carrier.b.y() - carrier.a.y();
1833 const PointType directionPoint(
1834 increasing ? sourcePoint.x() + dx : sourcePoint.x() - dx,
1835 increasing ? sourcePoint.y() + dy : sourcePoint.y() - dy);
1836 origin_.push_back(idOf(sourcePoint));
1837 origin_.push_back(infinity_.index());
1838 edgeGeometry_.push_back({EdgeKind::ray, sourcePoint, directionPoint});
1839 } else {
1840 origin_.push_back(infinity_.index());
1841 origin_.push_back(infinity_.index());
1842 edgeGeometry_.push_back({EdgeKind::line, carrier.a, carrier.b});
1843 }
1844 edgeLabel_.push_back(atom.label);
1845 originIndex_.insert(originIndex_.end(), atom.origins.begin(), atom.origins.end());
1846 originOffset_.push_back(static_cast<std::uint32_t>(originIndex_.size()));
1847 }
1848
1849 simplifyStoredCoordinates();
1850 syncVertexApproximations();
1851 next_.assign(origin_.size(), 0);
1852 face_.assign(origin_.size(), 0);
1853 outgoing_.assign(topologicalVertexCount(), HalfedgeId());
1854 wireHalfedgesUnbounded();
1855 buildFacesUnbounded();
1856 }
1857
1892 static std::vector<Piece> split(std::vector<InputSegment>& segments,
1893 const std::vector<PointType>& isolated,
1894 bool simpleBoundaries) {
1895 using IntegralPoint = Point<std::int64_t>;
1896 using IntegralSegment = Segment<IntegralPoint>;
1897 constexpr bool mayNeedIntegralNarrowing =
1898 is_Rational_v<NumberType> || std::same_as<NumberType, BigInt>;
1899
1900 // Equal geometry adjacent, and within a group the contributing shapes in
1901 // the order internVertices expects to see them.
1902 std::sort(segments.begin(), segments.end(),
1903 [](const InputSegment& left, const InputSegment& right) {
1904 if (!(left.segment == right.segment)) {
1905 return left.segment < right.segment;
1906 }
1907 return left.origin < right.origin;
1908 });
1909 // One entry per group plus a trailing sentinel, so group g occupies
1910 // segments[group[g] .. group[g + 1]).
1911 std::vector<std::size_t> group;
1912 for (std::size_t i = 0; i < segments.size(); ++i) {
1913 if (i == 0 || !(segments[i].segment == segments[i - 1].segment)) {
1914 group.push_back(i);
1915 }
1916 }
1917 group.push_back(segments.size());
1918 const std::size_t count = group.size() - 1;
1919
1920 // A segment's endpoints are in lexicographic order, so min().x() is the
1921 // left end of its x-projection and max().x() the right one, with nothing
1922 // to compute. The y-extent is not ordered, hence the explicit minmax.
1923 std::vector<NumberType> right, low, high;
1924 right.reserve(count);
1925 low.reserve(count);
1926 high.reserve(count);
1927 for (std::size_t i = 0; i < count; ++i) {
1928 const Segment<PointType>& current = segments[group[i]].segment;
1929 right.push_back(current.max().x());
1930 const auto [lo, hi] = std::minmax(current.min().y(), current.max().y());
1931 low.push_back(lo);
1932 high.push_back(hi);
1933 }
1934
1935 std::vector<std::uint32_t> order(count);
1936 for (std::size_t i = 0; i < count; ++i) {
1937 order[i] = static_cast<std::uint32_t>(i);
1938 }
1939 std::sort(order.begin(), order.end(), [&](std::uint32_t a, std::uint32_t b) {
1940 return segments[group[a]].segment.min().x() < segments[group[b]].segment.min().x();
1941 });
1942
1943 std::vector<std::vector<PointType>> cuts(count);
1944 std::vector<std::optional<IntegralSegment>> integral;
1945 if constexpr (mayNeedIntegralNarrowing) {
1946 integral.resize(count);
1947 }
1948 for (std::size_t i = 0; i < count; ++i) {
1949 const Segment<PointType>& segment = segments[group[i]].segment;
1950 cuts[i].push_back(segment.min());
1951 cuts[i].push_back(segment.max());
1952
1953 // A rational arrangement is commonly fed lattice segments — in
1954 // particular, every convex-piece Minkowski sum keeps integral
1955 // vertices until this overlay creates its crossings. Remember that
1956 // narrower spelling so the many rejected segment pairs use native
1957 // int64 coordinates and int128 determinants instead of promoting
1958 // Rational<BigInt> predicates all the way to BigInt. The exact
1959 // intersection is still constructed in NumberType below.
1960 const auto asIntegral = [](const PointType& point)
1961 -> std::optional<IntegralPoint> {
1962 const auto store = [](const auto& x, const auto& y)
1963 -> std::optional<IntegralPoint> {
1964 if (!detail::representableAs<std::int64_t>(x) ||
1965 !detail::representableAs<std::int64_t>(y)) {
1966 return std::nullopt;
1967 }
1968 return IntegralPoint(detail::narrowTo<std::int64_t>(x),
1969 detail::narrowTo<std::int64_t>(y));
1970 };
1971
1972 if constexpr (is_Rational_v<NumberType>) {
1973 if (!point.x().isInteger() || !point.y().isInteger()) {
1974 return std::nullopt;
1975 }
1976 using Integer = rational_int_t<NumberType>;
1977 return store(Integer(static_cast<Integer>(point.x())),
1978 Integer(static_cast<Integer>(point.y())));
1979 } else if constexpr (detail::extended_integral<NumberType> ||
1980 std::same_as<NumberType, BigInt>) {
1981 return store(point.x(), point.y());
1982 } else {
1983 return std::nullopt;
1984 }
1985 };
1986 if constexpr (mayNeedIntegralNarrowing) {
1987 const auto a = asIntegral(segment.min());
1988 const auto b = asIntegral(segment.max());
1989 if (a && b) {
1990 integral[i].emplace(*a, *b);
1991 }
1992 }
1993 }
1994
1995 const auto meet = [&](std::size_t a, std::size_t b) {
1996 const auto add = [&](const auto& piece) {
1997 if (!piece) {
1998 return;
1999 }
2000 if (const auto* point = std::get_if<0>(&*piece)) {
2001 cuts[a].emplace_back(*point);
2002 cuts[b].emplace_back(*point);
2003 } else {
2004 const auto& overlap = std::get<1>(*piece);
2005 for (const auto& end : {overlap.min(), overlap.max()}) {
2006 cuts[a].emplace_back(end);
2007 cuts[b].emplace_back(end);
2008 }
2009 }
2010 };
2011
2012 if constexpr (mayNeedIntegralNarrowing) {
2013 if (integral[a] && integral[b]) {
2014 add(integral[a]->template intersection<NumberType>(*integral[b]));
2015 return;
2016 }
2017 }
2018 add(segments[group[a]].segment.template intersection<NumberType>(
2019 segments[group[b]].segment));
2020 };
2021
2022 // For two large simple boundaries the arrangement normally has only a
2023 // small number of red-blue crossings, while the edges of each jagged
2024 // ring have long, mutually overlapping x-projections. The box sweep
2025 // below then examines quadratically many pairs it can reject only with
2026 // an exact predicate. Bentley--Ottmann follows the actual crossings and
2027 // makes that case O((n + k) log n). Keep the box sweep for many-shape and
2028 // dense overlays: its compact vectors and native-integer rejection are
2029 // substantially cheaper when k itself is large.
2030 const std::uint32_t originCount = segments.empty()
2031 ? 0
2032 : 1 + std::ranges::max(segments, {}, &InputSegment::origin).origin;
2033 const bool useIntersectionSweep = simpleBoundaries && originCount == 2 && count >= 256 &&
2034 !std::floating_point<NumberType>;
2035 if (useIntersectionSweep) {
2036 if constexpr (!std::floating_point<NumberType>) {
2037 const bool allIntegral = mayNeedIntegralNarrowing &&
2038 std::ranges::all_of(
2039 integral, [](const auto& segment) { return segment.has_value(); });
2040 if (allIntegral) {
2041 std::vector<IntegralSegment> unique;
2042 unique.reserve(count);
2043 for (const auto& segment : integral) {
2044 unique.push_back(*segment);
2045 }
2046 detail::BentleyOttmann<NumberType, IntegralSegment> sweep;
2047 for (const auto& pair : sweep.findIntersections(unique)) {
2048 const auto indexOf = [&](const IntegralSegment& segment) {
2049 return static_cast<std::size_t>(
2050 std::lower_bound(unique.begin(), unique.end(), segment) -
2051 unique.begin());
2052 };
2053 const std::size_t a = indexOf(pair[0]);
2054 const std::size_t b = indexOf(pair[1]);
2055 if (a != b) {
2056 meet(a, b);
2057 }
2058 }
2059 } else {
2060 std::vector<Segment<PointType>> unique;
2061 unique.reserve(count);
2062 for (std::size_t i = 0; i < count; ++i) {
2063 unique.push_back(segments[group[i]].segment);
2064 }
2065 detail::BentleyOttmann<NumberType, Segment<PointType>> sweep;
2066 for (const auto& pair : sweep.findIntersections(unique)) {
2067 const auto indexOf = [&](const Segment<PointType>& segment) {
2068 return static_cast<std::size_t>(
2069 std::lower_bound(unique.begin(), unique.end(), segment) -
2070 unique.begin());
2071 };
2072 const std::size_t a = indexOf(pair[0]);
2073 const std::size_t b = indexOf(pair[1]);
2074 if (a != b) {
2075 meet(a, b);
2076 }
2077 }
2078 }
2079 }
2080 } else {
2081 // The active list is compacted by the same pass that tests it, so a
2082 // group is dropped exactly once and expiry costs nothing beyond the
2083 // comparison the test needed anyway.
2084 std::vector<std::uint32_t> active;
2085 for (const std::uint32_t current : order) {
2086 const NumberType& left = segments[group[current]].segment.min().x();
2087 std::size_t write = 0;
2088 for (std::size_t read = 0; read < active.size(); ++read) {
2089 const std::uint32_t other = active[read];
2090 bool expired;
2091 bool missesInY;
2092 if constexpr (mayNeedIntegralNarrowing) {
2093 if (integral[current] && integral[other]) {
2094 const IntegralSegment& currentSegment = *integral[current];
2095 const IntegralSegment& otherSegment = *integral[other];
2096 expired = otherSegment.max().x() < currentSegment.min().x();
2097 const auto [currentLow, currentHigh] =
2098 std::minmax(currentSegment.min().y(), currentSegment.max().y());
2099 const auto [otherLow, otherHigh] =
2100 std::minmax(otherSegment.min().y(), otherSegment.max().y());
2101 missesInY = otherHigh < currentLow || currentHigh < otherLow;
2102 } else {
2103 expired = right[other] < left;
2104 missesInY = high[other] < low[current] || high[current] < low[other];
2105 }
2106 } else {
2107 expired = right[other] < left;
2108 missesInY = high[other] < low[current] || high[current] < low[other];
2109 }
2110 if (expired) {
2111 continue; // its projection closed before this one opened
2112 }
2113 active[write++] = other;
2114 if (missesInY) {
2115 continue; // boxes overlap in x but miss in y
2116 }
2117 meet(other, current);
2118 }
2119 active.resize(write);
2120 active.push_back(current);
2121 }
2122 }
2123
2124 std::vector<Piece> pieces;
2125 for (std::size_t i = 0; i < count; ++i) {
2126 for (const PointType& point : isolated) {
2127 if (segments[group[i]].segment.contains(point)) {
2128 cuts[i].push_back(point);
2129 }
2130 }
2131 // A crossing arrives from intersection() as a fraction whose
2132 // normalization this type defers, and every read of one reduces it
2133 // again and keeps nothing. Each cut is about to be read many times
2134 // over — by the sort and unique just below, by the piece copies they
2135 // feed, and by everything internVertices and the wiring passes do
2136 // with those. Reducing once per distinct cut, before any of that,
2137 // collapses all of it to a single gcd apiece.
2138 if constexpr (pgl::is_Rational_v<NumberType>) {
2139 for (PointType& cut : cuts[i]) {
2140 cut.x().simplify();
2141 cut.y().simplify();
2142 }
2143 }
2144 // Every cut lies on the segment, so the lexicographic point order is
2145 // the linear order along it.
2146 std::sort(cuts[i].begin(), cuts[i].end());
2147 cuts[i].erase(std::unique(cuts[i].begin(), cuts[i].end()), cuts[i].end());
2148 for (std::size_t k = 0; k + 1 < cuts[i].size(); ++k) {
2149 for (std::size_t s = group[i]; s < group[i + 1]; ++s) {
2150 pieces.push_back(Piece{cuts[i][k], cuts[i][k + 1], segments[s].origin,
2151 segments[s].label});
2152 }
2153 }
2154 }
2155 return pieces;
2156 }
2157
2158 // Turns the split pieces into vertices and edges: equal pieces — the
2159 // overlapping and duplicated input of the same stretch — become one edge
2160 // remembering every input shape that produced it.
2161 void internVertices(std::vector<Piece>& pieces, const std::vector<PointType>& isolated) {
2162 // The piece endpoints were reduced by @ref split, before the cuts were
2163 // copied into pieces, so the sort and the hash lookups below already read
2164 // them on the normalized fast path.
2165 std::sort(pieces.begin(), pieces.end(), [](const Piece& left, const Piece& right) {
2166 if (!(left.a == right.a)) {
2167 return left.a < right.a;
2168 }
2169 if (!(left.b == right.b)) {
2170 return left.b < right.b;
2171 }
2172 return left.origin < right.origin;
2173 });
2174
2175 std::unordered_map<PointType, std::uint32_t> vertexOf;
2176 const auto idOf = [&](const PointType& point) {
2177 const auto found = vertexOf.find(point);
2178 if (found != vertexOf.end()) {
2179 return found->second;
2180 }
2181 const auto id = static_cast<std::uint32_t>(points_.size());
2182 points_.push_back(point);
2183 vertexOf.emplace(point, id);
2184 return id;
2185 };
2186
2187 originOffset_.push_back(0);
2188 for (std::size_t i = 0; i < pieces.size();) {
2189 origin_.push_back(idOf(pieces[i].a));
2190 origin_.push_back(idOf(pieces[i].b));
2191 edgeGeometry_.push_back({EdgeKind::segment, pieces[i].a, pieces[i].b});
2192 edgeLabel_.push_back(pieces[i].label);
2193 // The pieces of one stretch are adjacent and ordered by their input
2194 // position, so the same shape is caught by looking one back only.
2195 std::size_t j = i;
2196 while (j < pieces.size() && pieces[j].a == pieces[i].a && pieces[j].b == pieces[i].b) {
2197 if (j == i || pieces[j].origin != pieces[j - 1].origin) {
2198 originIndex_.push_back(pieces[j].origin);
2199 }
2200 ++j;
2201 }
2202 originOffset_.push_back(static_cast<std::uint32_t>(originIndex_.size()));
2203 i = j;
2204 }
2205
2206 for (const PointType& point : isolated) {
2207 idOf(point);
2208 }
2209 next_.assign(origin_.size(), 0);
2210 face_.assign(origin_.size(), 0);
2211 outgoing_.assign(points_.size(), HalfedgeId());
2212 }
2213
2214 // Reduces every rational coordinate the arrangement keeps — the interned
2215 // vertex positions and the defining geometry of each edge — to lowest terms,
2216 // once, before anything reads them.
2217 //
2218 // Two kinds of coordinate arrive here unreduced. A constructed one (a
2219 // crossing, a point cut out of a carrier) is a fraction whose normalization
2220 // this type defers, and an input one may have been handed to us unreduced by
2221 // the caller. Either way a stored coordinate is read over and over — by the
2222 // wiring and face passes immediately below, by point location, and by every
2223 // query the caller later makes — and each of those reads recomputes the same
2224 // gcd and throws it away. This spends one gcd per coordinate and keeps it, so
2225 // all of that lands on the already-normalized fast path. On a value already
2226 // in lowest terms it is a flag test.
2227 //
2228 // This is the belt to @ref split's braces: it covers the coordinates that
2229 // never passed through a cut list — isolated input points, the source and
2230 // direction of a ray, the two points defining a line — and costs nothing on
2231 // the ones that did.
2232 void simplifyStoredCoordinates() {
2233 if constexpr (pgl::is_Rational_v<NumberType>) {
2234 const auto reduce = [](PointType& point) {
2235 point.x().simplify();
2236 point.y().simplify();
2237 };
2238 for (PointType& point : points_) {
2239 reduce(point);
2240 }
2241 for (EdgeGeometry& geometry : edgeGeometry_) {
2242 reduce(geometry.a);
2243 reduce(geometry.b);
2244 }
2245 }
2246 }
2247
2248 // The coordinate type the vertex approximations are kept for: every sign
2249 // predicate that reads points_ back is an orientation over three of them.
2250 using VertexCoordinate =
2251 detail::orientation_coordinate_t<NumberType, NumberType, NumberType>;
2252
2253 // Converts every interned vertex once, for those predicates to read back.
2254 //
2255 // They run inside comparators — the rotational sort around a vertex, the
2256 // status order of the sweep — so a vertex takes part in a logarithmic number
2257 // of them and converting per predicate re-derives the same few doubles over
2258 // and over. Must run after @ref simplifyStoredCoordinates: an unreduced
2259 // fraction can overflow double in both parts, leaving a quotient the filter
2260 // can only abstain on. Stays empty, and unindexed, where
2261 // @ref detail::filtersSign says exact arithmetic is cheap enough already.
2262 void syncVertexApproximations() {
2263 if constexpr (detail::filtersSign<VertexCoordinate>) {
2264 vertexApproximations_.clear();
2265 vertexApproximations_.reserve(points_.size());
2266 for (const PointType& point : points_) {
2267 vertexApproximations_.push_back(detail::approximatePoint(point));
2268 }
2269 }
2270 }
2271
2272 // An interned vertex paired with the approximation kept for it. The two
2273 // arrays are parallel, and a vertex added without one would be read as some
2274 // other vertex's approximation — a sign the filter then proves wrong rather
2275 // than abstains on — so the pairing is asserted rather than trusted.
2276 [[nodiscard]] auto filteredVertex(std::uint32_t index) const {
2277 assert(!detail::filtersSign<VertexCoordinate> ||
2278 vertexApproximations_.size() == points_.size());
2279 return detail::filtered<VertexCoordinate>(points_[index], vertexApproximations_, index);
2280 }
2281
2282 // Sorts the halfedges leaving each vertex counterclockwise and links them:
2283 // arriving at a vertex along one edge, the boundary of the face on the left
2284 // leaves along the next edge clockwise, which is the previous one in
2285 // counterclockwise order.
2286 void wireHalfedges() {
2287 std::vector<std::vector<std::uint32_t>> fan(points_.size());
2288 for (std::uint32_t h = 0; h < origin_.size(); ++h) {
2289 fan[origin_[h]].push_back(h);
2290 }
2291 for (std::uint32_t v = 0; v < fan.size(); ++v) {
2292 std::vector<std::uint32_t>& around = fan[v];
2293 if (around.empty()) {
2294 continue;
2295 }
2296 const PointType& center = points_[v];
2297 // Half 0 holds the directions with an angle in [0, pi), half 1 the
2298 // rest, so the comparison never needs an angle, only a sign.
2299 const auto half = [&](std::uint32_t h) {
2300 const PointType& to = points_[origin_[h ^ 1]];
2301 if (to.y() > center.y()) {
2302 return 0;
2303 }
2304 if (to.y() < center.y()) {
2305 return 1;
2306 }
2307 return to.x() > center.x() ? 0 : 1;
2308 };
2309 const auto filteredCenter = filteredVertex(v);
2310 std::sort(around.begin(), around.end(), [&](std::uint32_t left, std::uint32_t right) {
2311 const int leftHalf = half(left);
2312 const int rightHalf = half(right);
2313 if (leftHalf != rightHalf) {
2314 return leftHalf < rightHalf;
2315 }
2316 return detail::orientationSignOf(filteredCenter,
2317 filteredVertex(origin_[left ^ 1]),
2318 filteredVertex(origin_[right ^ 1]))
2319 .value() > 0;
2320 });
2321 const std::size_t degree = around.size();
2322 for (std::size_t i = 0; i < degree; ++i) {
2323 next_[around[i] ^ 1] = around[(i + degree - 1) % degree];
2324 }
2325 outgoing_[v] = HalfedgeId(around.front());
2326 }
2327 }
2328
2329 // The direction a halfedge leaves its origin in, with the point that fixes
2330 // where its carrier sits. Ordering these is what puts the halfedges around
2331 // a vertex in rotational order.
2332 struct FanDirection {
2333 NumberType dx;
2334 NumberType dy;
2335 PointType anchor;
2336 std::uint32_t halfedge;
2337 };
2338
2339 // The direction of a halfedge, taken from the interned endpoints when the
2340 // edge is a segment so a split edge leaves along its own piece rather than
2341 // along the whole original. A line's two halfedges escape opposite ways.
2342 [[nodiscard]] FanDirection fanDirection(std::uint32_t h) const {
2343 const EdgeGeometry& geometry = edgeGeometry_[h / 2];
2344 NumberType dx = geometry.b.x() - geometry.a.x();
2345 NumberType dy = geometry.b.y() - geometry.a.y();
2346 if (geometry.kind == EdgeKind::segment) {
2347 const PointType& from = points_[origin_[h]];
2348 const PointType& to = points_[origin_[h ^ 1]];
2349 dx = to.x() - from.x();
2350 dy = to.y() - from.y();
2351 } else if (geometry.kind == EdgeKind::line && h % 2 == 0) {
2352 dx = -dx;
2353 dy = -dy;
2354 }
2355 return {dx, dy, geometry.a, h};
2356 }
2357
2358 // Which half-turn a direction falls in, so the angular comparison below can
2359 // use a cross product without ever spanning more than half a turn. Due east
2360 // counts as the upper half and due west as the lower, which puts the cut
2361 // just below the positive x axis.
2362 static int directionHalf(const FanDirection& direction) {
2363 if (direction.dy > NumberType(0)) {
2364 return 0;
2365 }
2366 if (direction.dy < NumberType(0)) {
2367 return 1;
2368 }
2369 return direction.dx > NumberType(0) ? 0 : 1;
2370 }
2371
2372 // Counterclockwise order of two directions around a shared origin, starting
2373 // from the cut `directionHalf` sets. Exact throughout: the angle itself is
2374 // never formed, only compared.
2375 static bool fanLess(const FanDirection& left, const FanDirection& right) {
2376 const int leftHalf = directionHalf(left);
2377 const int rightHalf = directionHalf(right);
2378 if (leftHalf != rightHalf) {
2379 return leftHalf < rightHalf;
2380 }
2381 using Vector = Point<NumberType>;
2382 const Vector leftDirection(left.dx, left.dy);
2383 const auto cross = crossSign(leftDirection, Vector(right.dx, right.dy));
2384 if (cross != 0) {
2385 return cross > 0;
2386 }
2387 // Parallel ends with the same escape direction meet only at infinity.
2388 // Their transverse order is the order of their carriers there; it
2389 // reverses automatically at the opposite end of a line.
2390 const auto offset = crossSign(
2391 leftDirection, Vector(right.anchor.x() - left.anchor.x(),
2392 right.anchor.y() - left.anchor.y()));
2393 if (offset != 0) {
2394 return offset > 0;
2395 }
2396 return left.halfedge < right.halfedge;
2397 }
2398
2399 // The same order seen from the point at infinity, where the plane is
2400 // traversed the other way round and every end is entered rather than left.
2401 static bool infinityFanLess(const FanDirection& left, const FanDirection& right) {
2402 return fanLess(right, left);
2403 }
2404
2405 // Links the halfedges into face cycles for an arrangement carrying rays or
2406 // lines. Sorting each vertex's fan rotationally makes the next halfedge of
2407 // a cycle the one just clockwise of the twin, which is what walks a face
2408 // boundary; the vertex at infinity uses the reversed order.
2409 void wireHalfedgesUnbounded() {
2410 std::vector<std::vector<std::uint32_t>> fan(topologicalVertexCount());
2411 for (std::uint32_t h = 0; h < origin_.size(); ++h) {
2412 fan[origin_[h]].push_back(h);
2413 }
2414 for (std::uint32_t v = 0; v < fan.size(); ++v) {
2415 std::vector<std::uint32_t>& around = fan[v];
2416 if (around.empty()) {
2417 continue;
2418 }
2419 const bool atInfinity = infinity_.valid() && v == infinity_.index();
2420 std::sort(around.begin(), around.end(), [&](std::uint32_t left, std::uint32_t right) {
2421 return atInfinity
2422 ? infinityFanLess(fanDirection(left), fanDirection(right))
2423 : fanLess(fanDirection(left), fanDirection(right));
2424 });
2425 const std::size_t degree = around.size();
2426 for (std::size_t i = 0; i < degree; ++i) {
2427 next_[around[i] ^ 1] = around[(i + degree - 1) % degree];
2428 }
2429 outgoing_[v] = HalfedgeId(around.front());
2430 if (infinity_.valid() && v == infinity_.index()) {
2431 infinityFan_ = around;
2432 }
2433 }
2434 }
2435
2436 // The unbounded face a point with no edge to its west lies in, found by
2437 // placing a due-west ray from the point into the fan at infinity.
2438 [[nodiscard]] HalfedgeId infinityBoundaryAtWest(const PointType& point) const {
2439 assert(!infinityFan_.empty());
2440 // At an end exactly collinear with the query ray, choose the sector on
2441 // its +y side. This is the same symbolic perturbation halfedgeLeftOf
2442 // uses at finite vertices.
2443 const FanDirection query{NumberType(-1), NumberType(0), point, 0};
2444 const auto after = std::upper_bound(
2445 infinityFan_.begin(), infinityFan_.end(), query,
2446 [&](const FanDirection& value, std::uint32_t halfedge) {
2447 return infinityFanLess(value, fanDirection(halfedge));
2448 });
2449 const std::uint32_t nextDirection =
2450 after == infinityFan_.end() ? infinityFan_.front() : *after;
2451 return HalfedgeId(nextDirection ^ 1);
2452 }
2453
2454 // Turns the halfedge cycles into faces for an arrangement carrying rays or
2455 // lines. A cycle passing through infinity bounds an unbounded face, and the
2456 // rest nest as holes the way the bounded builder handles them.
2457 void buildFacesUnbounded() {
2458 constexpr std::uint32_t none = ~std::uint32_t{};
2459 const std::uint32_t halfedges = static_cast<std::uint32_t>(origin_.size());
2460 std::vector<std::uint32_t> cycleOf(halfedges, none);
2461 std::vector<std::uint32_t> representative;
2462 std::vector<bool> reachesInfinity;
2463 for (std::uint32_t h = 0; h < halfedges; ++h) {
2464 if (cycleOf[h] != none) {
2465 continue;
2466 }
2467 const auto id = static_cast<std::uint32_t>(representative.size());
2468 representative.push_back(h);
2469 bool unbounded = false;
2470 std::uint32_t walk = h;
2471 do {
2472 cycleOf[walk] = id;
2473 unbounded = unbounded || origin_[walk] == infinity_.index();
2474 walk = next_[walk];
2475 } while (walk != h);
2476 reachesInfinity.push_back(unbounded);
2477 }
2478
2479 const auto cycles = static_cast<std::uint32_t>(representative.size());
2480 std::vector<std::uint32_t> parent(cycles);
2481 for (std::uint32_t i = 0; i < cycles; ++i) {
2482 parent[i] = i;
2483 }
2484 const auto root = [&parent](std::uint32_t x) {
2485 while (parent[x] != x) {
2486 parent[x] = parent[parent[x]];
2487 x = parent[x];
2488 }
2489 return x;
2490 };
2491
2492 std::vector<bool> isOuter(cycles, false);
2493 for (std::uint32_t id = 0; id < cycles; ++id) {
2494 if (reachesInfinity[id]) {
2495 continue;
2496 }
2497 const std::uint32_t leftmost = leftmostVertexOf(representative[id]);
2498 if (turnsLeftEverywhereAt(representative[id], leftmost)) {
2499 isOuter[id] = true;
2500 continue;
2501 }
2502 const HalfedgeId left = halfedgeLeftOf(points_[leftmost]);
2503 const HalfedgeId other = left.valid() ? left : infinityBoundaryAtWest(points_[leftmost]);
2504 parent[root(id)] = root(cycleOf[other.index()]);
2505 }
2506
2507 std::vector<std::uint32_t> infinityCycles;
2508 infinityCycles.reserve(cycles);
2509 for (std::uint32_t id = 0; id < cycles; ++id) {
2510 if (reachesInfinity[id]) {
2511 infinityCycles.push_back(id);
2512 }
2513 }
2514 const PointType zero(NumberType(0), NumberType(0));
2515 const std::uint32_t westCycle = cycleOf[infinityBoundaryAtWest(zero).index()];
2516 const auto west = std::find(infinityCycles.begin(), infinityCycles.end(), westCycle);
2517 if (west != infinityCycles.end()) {
2518 std::iter_swap(infinityCycles.begin(), west);
2519 }
2520
2521 std::vector<std::uint32_t> faceOfComponent(cycles, none);
2522 outerCycle_.clear();
2523 unboundedFace_.clear();
2524 for (const std::uint32_t id : infinityCycles) {
2525 const std::uint32_t component = root(id);
2526 if (faceOfComponent[component] != none) {
2527 continue;
2528 }
2529 faceOfComponent[component] = static_cast<std::uint32_t>(outerCycle_.size());
2530 outerCycle_.push_back(HalfedgeId());
2531 unboundedFace_.push_back(true);
2532 }
2533 for (std::uint32_t id = 0; id < cycles; ++id) {
2534 if (!isOuter[id]) {
2535 continue;
2536 }
2537 const std::uint32_t component = root(id);
2538 assert(faceOfComponent[component] == none);
2539 faceOfComponent[component] = static_cast<std::uint32_t>(outerCycle_.size());
2540 outerCycle_.push_back(HalfedgeId(representative[id]));
2541 unboundedFace_.push_back(false);
2542 }
2543
2544 std::vector<std::vector<HalfedgeId>> inner(outerCycle_.size());
2545 for (std::uint32_t id = 0; id < cycles; ++id) {
2546 const std::uint32_t f = faceOfComponent[root(id)];
2547 assert(f != none);
2548 if (!isOuter[id]) {
2549 inner[f].push_back(HalfedgeId(representative[id]));
2550 }
2551 std::uint32_t walk = representative[id];
2552 do {
2553 face_[walk] = f;
2554 walk = next_[walk];
2555 } while (walk != representative[id]);
2556 }
2557
2558 innerCycle_.clear();
2559 innerOffset_.assign(1, 0);
2560 for (const std::vector<HalfedgeId>& cyclesOfFace : inner) {
2561 innerCycle_.insert(innerCycle_.end(), cyclesOfFace.begin(), cyclesOfFace.end());
2562 innerOffset_.push_back(static_cast<std::uint32_t>(innerCycle_.size()));
2563 }
2564 faceLabel_.assign(outerCycle_.size(), TLabel{});
2565 }
2566
2567 // Traces the `next` cycles, tells the outer boundary cycles (counterclockwise)
2568 // from the inner ones (clockwise, or degenerate), and gathers the cycles that
2569 // bound the same face.
2570 void buildFaces() {
2571 constexpr std::uint32_t none = ~std::uint32_t{};
2572 const std::uint32_t halfedges = static_cast<std::uint32_t>(origin_.size());
2573
2574 std::vector<std::uint32_t> cycleOf(halfedges, none);
2575 std::vector<std::uint32_t> representative;
2576 for (std::uint32_t h = 0; h < halfedges; ++h) {
2577 if (cycleOf[h] != none) {
2578 continue;
2579 }
2580 const auto id = static_cast<std::uint32_t>(representative.size());
2581 representative.push_back(h);
2582 std::uint32_t walk = h;
2583 do {
2584 cycleOf[walk] = id;
2585 walk = next_[walk];
2586 } while (walk != h);
2587 }
2588
2589 // Cycle `n` stands for the outside of everything: an inner cycle with no
2590 // edge to its left bounds the unbounded face.
2591 const auto cycles = static_cast<std::uint32_t>(representative.size());
2592 std::vector<std::uint32_t> parent(cycles + 1);
2593 for (std::uint32_t i = 0; i < parent.size(); ++i) {
2594 parent[i] = i;
2595 }
2596 const auto root = [&parent](std::uint32_t x) {
2597 while (parent[x] != x) {
2598 parent[x] = parent[parent[x]];
2599 x = parent[x];
2600 }
2601 return x;
2602 };
2603
2604 // Which cycles are outer boundaries, and, for the inner ones, the vertex
2605 // each of them has to ask what holds it from. The questions are collected
2606 // rather than asked one by one: one sweep answers the whole batch, where
2607 // a horizontal ray per cycle would scan every edge again for each.
2608 std::vector<bool> isOuter(cycles, false);
2609 std::vector<std::uint32_t> asking;
2610 std::vector<std::uint32_t> askedFrom;
2611 for (std::uint32_t id = 0; id < cycles; ++id) {
2612 const std::uint32_t leftmost = leftmostVertexOf(representative[id]);
2613 if (turnsLeftEverywhereAt(representative[id], leftmost)) {
2614 isOuter[id] = true;
2615 continue;
2616 }
2617 asking.push_back(id);
2618 askedFrom.push_back(leftmost);
2619 }
2620 // The leftmost vertex of an inner cycle has the face this cycle bounds
2621 // immediately to its left, so whatever edge is there bounds the same
2622 // face. Merging them all afterwards builds the same partition merging
2623 // them one at a time would.
2624 const std::vector<HalfedgeId> toTheLeft = halfedgesLeftOf(askedFrom);
2625 for (std::size_t i = 0; i < asking.size(); ++i) {
2626 const std::uint32_t other =
2627 toTheLeft[i].valid() ? cycleOf[toTheLeft[i].index()] : cycles;
2628 parent[root(asking[i])] = root(other);
2629 }
2630
2631 // The unbounded face comes first, then one face per outer cycle.
2632 std::vector<std::uint32_t> faceOfComponent(cycles + 1, none);
2633 faceOfComponent[root(cycles)] = 0;
2634 outerCycle_.assign(1, HalfedgeId());
2635 for (std::uint32_t id = 0; id < cycles; ++id) {
2636 if (!isOuter[id]) {
2637 continue;
2638 }
2639 // A component holds at most one counterclockwise cycle: it is the
2640 // face's outer boundary, and the rest of the component is its holes.
2641 assert(faceOfComponent[root(id)] == none);
2642 faceOfComponent[root(id)] = static_cast<std::uint32_t>(outerCycle_.size());
2643 outerCycle_.push_back(HalfedgeId(representative[id]));
2644 }
2645
2646 std::vector<std::vector<HalfedgeId>> inner(outerCycle_.size());
2647 for (std::uint32_t id = 0; id < cycles; ++id) {
2648 const std::uint32_t f = faceOfComponent[root(id)];
2649 assert(f != none);
2650 if (!isOuter[id]) {
2651 inner[f].push_back(HalfedgeId(representative[id]));
2652 }
2653 std::uint32_t walk = representative[id];
2654 do {
2655 face_[walk] = f;
2656 walk = next_[walk];
2657 } while (walk != representative[id]);
2658 }
2659
2660 innerOffset_.assign(1, 0);
2661 for (const std::vector<HalfedgeId>& cyclesOfFace : inner) {
2662 innerCycle_.insert(innerCycle_.end(), cyclesOfFace.begin(), cyclesOfFace.end());
2663 innerOffset_.push_back(static_cast<std::uint32_t>(innerCycle_.size()));
2664 }
2665 faceLabel_.assign(outerCycle_.size(), TLabel{});
2666 unboundedFace_.assign(outerCycle_.size(), false);
2667 unboundedFace_.front() = true;
2668 }
2669
2670 // -------------------------------------------------------------------------
2671 // Geometry helpers
2672
2692 [[nodiscard]] HalfedgeId halfedgeLeftOf(const PointType& p) const {
2693 HalfedgeId best;
2694 WideNumber bestNumerator(0);
2695 WideNumber bestUpX(0);
2696 WideNumber bestUpY(1);
2697 const auto wide = [](const NumberType& value) {
2698 return static_cast<WideNumber>(value);
2699 };
2700 for (std::uint32_t h = 0; h < origin_.size(); h += 2) {
2701 const EdgeGeometry& geometry = edgeGeometry_[h / 2];
2702 const PointType& a = geometry.a;
2703 const PointType& b = geometry.b;
2704 if (a.y() == b.y()) {
2705 continue; // a horizontal edge crosses no horizontal line
2706 }
2707 // Whether the edge runs upwards is a comparison, not a subtraction,
2708 // and it is all the height tests below need.
2709 const bool upwards = a.y() < b.y();
2710 if (geometry.kind == EdgeKind::segment) {
2711 const NumberType& lowY = upwards ? a.y() : b.y();
2712 const NumberType& highY = upwards ? b.y() : a.y();
2713 if (p.y() < lowY || !(p.y() < highY)) {
2714 continue;
2715 }
2716 } else if (geometry.kind == EdgeKind::ray) {
2717 if (upwards ? p.y() < a.y() : !(p.y() < a.y())) {
2718 continue;
2719 }
2720 }
2721 // The edge directed upwards, so that the query point lies strictly
2722 // right of it exactly when the crossing is strictly left of the
2723 // query point — which is the orientation predicate, and needs no
2724 // arithmetic here at all.
2725 const PointType& low = upwards ? a : b;
2726 const PointType& high = upwards ? b : a;
2727 if (!(orientationSign(low, high, p) < 0)) {
2728 continue; // to the right of the query point, or through it
2729 }
2730 // How far right of the query point the edge crosses its horizontal
2731 // line, as the fraction numerator / upY — the upward direction's own
2732 // ordinate is the positive denominator. No division, so exact
2733 // coordinates stay exact.
2734 const WideNumber upX = wide(high.x()) - wide(low.x());
2735 const WideNumber upY = wide(high.y()) - wide(low.y());
2736 const WideNumber numerator =
2737 (wide(low.x()) - wide(p.x())) * upY - (wide(low.y()) - wide(p.y())) * upX;
2738 if (best.valid()) {
2739 const WideNumber here = numerator * bestUpY;
2740 const WideNumber there = bestNumerator * upY;
2741 if (here < there) {
2742 continue;
2743 }
2744 if (here == there) {
2745 // The same crossing point: the edge leaving it clockwise of
2746 // the incumbent is the one whose left side holds the query.
2747 if (!(upX * bestUpY - upY * bestUpX > WideNumber(0))) {
2748 continue;
2749 }
2750 }
2751 }
2752 // The halfedge running downwards has the crossing's right-hand side,
2753 // where the query point lies, on its left.
2754 best = HalfedgeId(upwards ? h + 1 : h);
2755 bestNumerator = numerator;
2756 bestUpX = upX;
2757 bestUpY = upY;
2758 }
2759 return best;
2760 }
2761
2791 struct SweepOrder {
2792 using is_transparent = void;
2793 const Arrangement* arrangement;
2794 const std::vector<std::uint32_t>* position;
2795
2796 bool operator()(std::uint32_t left, std::uint32_t right) const {
2797 const std::vector<std::uint32_t>& origin = arrangement->origin_;
2798 const std::uint32_t leftLow = origin[left ^ 1];
2799 const std::uint32_t rightLow = origin[right ^ 1];
2800 const auto vertex = [this](std::uint32_t index) {
2801 return arrangement->filteredVertex(index);
2802 };
2803 if (leftLow == rightLow) {
2804 // One vertex, both leaving it upwards: the one leaning further
2805 // right crosses the line further right.
2806 return detail::orientationSignOf(vertex(leftLow), vertex(origin[left]),
2807 vertex(origin[right]))
2808 .value() < 0;
2809 }
2810 if ((*position)[leftLow] > (*position)[rightLow]) {
2811 return detail::orientationSignOf(vertex(rightLow), vertex(origin[right]),
2812 vertex(leftLow))
2813 .value() > 0;
2814 }
2815 return detail::orientationSignOf(vertex(leftLow), vertex(origin[left]),
2816 vertex(rightLow))
2817 .value() < 0;
2818 }
2819
2820 // The edge is strictly left of the point when the point is strictly to
2821 // the right of it. A point *on* the edge is not, which is what keeps the
2822 // edges through a query vertex from answering it.
2823 bool operator()(std::uint32_t left, const PointType& p) const {
2824 return detail::orientationSignOf(
2825 arrangement->filteredVertex(arrangement->origin_[left ^ 1]),
2826 arrangement->filteredVertex(arrangement->origin_[left]),
2827 detail::filtered<VertexCoordinate>(p))
2828 .value() < 0;
2829 }
2830
2831 bool operator()(const PointType& p, std::uint32_t right) const {
2832 return detail::orientationSignOf(
2833 arrangement->filteredVertex(arrangement->origin_[right ^ 1]),
2834 arrangement->filteredVertex(arrangement->origin_[right]),
2835 detail::filtered<VertexCoordinate>(p))
2836 .value() > 0;
2837 }
2838 };
2839
2863 [[nodiscard]] std::vector<HalfedgeId> halfedgesLeftOf(
2864 const std::vector<std::uint32_t>& queries) const {
2865 const std::uint64_t edges = origin_.size() / 2;
2866 const std::uint64_t asked = queries.size();
2867 // log2 of the number of edges, near enough, and without a cast to double.
2868 const std::uint64_t depth = std::bit_width(edges);
2869 // What one sweep comparison costs in straddle tests, as a ratio so the
2870 // calibrated number stays an integer. Measured at about a half on exact
2871 // rational coordinates: the two predicates cost much the same, and the
2872 // sweep stays well inside the `(E + Q) log E` its bound allows, its
2873 // status holding only the edges the line currently crosses.
2874 constexpr std::uint64_t perComparisonNum = 1;
2875 constexpr std::uint64_t perComparisonDen = 2;
2876 if (!infinity_.valid() &&
2877 perComparisonDen * asked * edges >
2878 perComparisonNum * (2 * edges + asked) * depth) {
2879 return sweepHalfedgesLeftOf(queries);
2880 }
2881 std::vector<HalfedgeId> answer;
2882 answer.reserve(queries.size());
2883 for (const std::uint32_t query : queries) {
2884 answer.push_back(halfedgeLeftOf(points_[query]));
2885 }
2886 return answer;
2887 }
2888
2912 [[nodiscard]] std::vector<HalfedgeId> sweepHalfedgesLeftOf(
2913 const std::vector<std::uint32_t>& queries) const {
2914 // Vertices by height, then abscissa. A vertex is its position here, so
2915 // the events sort on an integer and never on a coordinate.
2916 std::vector<std::uint32_t> byHeight(points_.size());
2917 for (std::uint32_t v = 0; v < byHeight.size(); ++v) {
2918 byHeight[v] = v;
2919 }
2920 std::sort(byHeight.begin(), byHeight.end(), [this](std::uint32_t a, std::uint32_t b) {
2921 if (!(points_[a].y() == points_[b].y())) {
2922 return points_[a].y() < points_[b].y();
2923 }
2924 return points_[a].x() < points_[b].x();
2925 });
2926 std::vector<std::uint32_t> position(points_.size());
2927 for (std::uint32_t i = 0; i < byHeight.size(); ++i) {
2928 position[byHeight[i]] = i;
2929 }
2930
2931 // What happens at a vertex, in the order it has to happen.
2932 enum Phase : std::uint8_t { leaves = 0, joins = 1, asks = 2 };
2933 struct Event {
2934 std::uint32_t at; // the vertex, as its position in `byHeight`
2935 std::uint32_t subject; // a downward halfedge, or a query's position
2936 Phase phase;
2937 };
2938 std::vector<Event> events;
2939 events.reserve(origin_.size() + queries.size());
2940 for (std::uint32_t h = 0; h < origin_.size(); h += 2) {
2941 if (points_[origin_[h]].y() == points_[origin_[h + 1]].y()) {
2942 continue; // horizontal: it crosses no horizontal line
2943 }
2944 const std::uint32_t downward =
2945 points_[origin_[h]].y() > points_[origin_[h + 1]].y() ? h : h + 1;
2946 events.push_back({position[origin_[downward]], downward, leaves});
2947 events.push_back({position[origin_[downward ^ 1]], downward, joins});
2948 }
2949 for (std::uint32_t q = 0; q < queries.size(); ++q) {
2950 events.push_back({position[queries[q]], q, asks});
2951 }
2952 std::sort(events.begin(), events.end(), [](const Event& left, const Event& right) {
2953 if (left.at != right.at) {
2954 return left.at < right.at;
2955 }
2956 return left.phase < right.phase;
2957 });
2958
2959 std::vector<HalfedgeId> answer(queries.size());
2960 using Status = std::set<std::uint32_t, SweepOrder>;
2961 Status line(SweepOrder{this, &position});
2962 // Erasing by key would compare an edge that ends on the line against the
2963 // edges still crossing it, and an edge that has reached its top no longer
2964 // has a side of it to be on. Every edge remembers where it sits instead.
2965 std::vector<typename Status::iterator> seat(origin_.size() / 2);
2966 for (const Event& event : events) {
2967 if (event.phase == leaves) {
2968 line.erase(seat[event.subject / 2]);
2969 } else if (event.phase == joins) {
2970 const auto placed = line.insert(event.subject);
2971 assert(placed.second);
2972 seat[event.subject / 2] = placed.first;
2973 } else {
2974 const auto above = line.lower_bound(points_[queries[event.subject]]);
2975 if (above != line.begin()) {
2976 answer[event.subject] = HalfedgeId(*std::prev(above));
2977 }
2978 }
2979 }
2980 return answer;
2981 }
2982
2983 // Calls `fn` on every halfedge bounding the face, outer cycle first.
2984 template <class Function>
2985 void forEachBoundaryHalfedge(FaceId f, const Function& fn) const {
2986 const auto walkCycle = [&](HalfedgeId start) {
2987 std::uint32_t h = start.index();
2988 do {
2989 fn(HalfedgeId(h));
2990 h = next_[h];
2991 } while (h != start.index());
2992 };
2993 if (outerCycle_[f.index()].valid()) {
2994 walkCycle(outerCycle_[f.index()]);
2995 }
2996 for (HalfedgeId inner : innerCycles(f)) {
2997 walkCycle(inner);
2998 }
2999 }
3000
3001 // The lexicographically smallest vertex a boundary cycle visits.
3002 [[nodiscard]] std::uint32_t leftmostVertexOf(std::uint32_t start) const {
3003 std::uint32_t leftmost = origin_[start];
3004 for (std::uint32_t h = next_[start]; h != start; h = next_[h]) {
3005 if (points_[origin_[h]] < points_[leftmost]) {
3006 leftmost = origin_[h];
3007 }
3008 }
3009 return leftmost;
3010 }
3011
3039 [[nodiscard]] bool turnsLeftEverywhereAt(std::uint32_t start, std::uint32_t vertex) const {
3040 std::uint32_t h = start;
3041 do {
3042 // (h, ahead) is the pair of halfedges meeting at the vertex between
3043 // them, so their origins are the previous and the next vertex.
3044 const std::uint32_t ahead = next_[h];
3045 if (origin_[ahead] == vertex &&
3046 !(detail::orientationSignOf(filteredVertex(vertex),
3047 filteredVertex(origin_[ahead ^ 1]),
3048 filteredVertex(origin_[h]))
3049 .value() > 0)) {
3050 return false;
3051 }
3052 h = ahead;
3053 } while (h != start);
3054 return true;
3055 }
3056
3057 // The vertices of a boundary cycle, in order.
3058 [[nodiscard]] std::vector<PointType> cycleRing(HalfedgeId start) const {
3059 std::vector<PointType> ring;
3060 std::uint32_t h = start.index();
3061 do {
3062 ring.push_back(points_[origin_[h]]);
3063 h = next_[h];
3064 } while (h != start.index());
3065 return ring;
3066 }
3067
3068 // Turns one boundary cycle into the polygons that describe the same area:
3069 // the spikes a dangling edge leaves behind are dropped, a cycle pinching
3070 // shut at a vertex is cut there, and every ring comes out counterclockwise
3071 // and in canonical form.
3072 static void collectRings(std::vector<PointType> walk, std::vector<Polygon<PointType>>& out) {
3073 pruneSpikes(walk);
3074 if (walk.size() < 3) {
3075 return;
3076 }
3077 std::vector<std::vector<PointType>> rings;
3078 detail::splitWalkIntoRings(walk, rings);
3079 for (std::vector<PointType>& ring : rings) {
3080 pruneSpikes(ring);
3081 if (ring.size() < 3) {
3082 continue;
3083 }
3084 const int orientation = detail::ringOrientation(ring);
3085 if (orientation == 0) {
3086 continue;
3087 }
3088 if (orientation < 0) {
3089 std::reverse(ring.begin(), ring.end());
3090 }
3091 // Counterclockwise and rotated onto its smallest vertex is exactly
3092 // the canonical form, so the polygon needs no normalization.
3093 std::rotate(ring.begin(), std::min_element(ring.begin(), ring.end()), ring.end());
3094 out.emplace_back(std::move(ring), /*trusted=*/true);
3095 }
3096 }
3097
3098 // Drops the stretches a boundary walk covers twice, once each way: they
3099 // bound no area, and no ring may repeat a vertex around them.
3100 static void pruneSpikes(std::vector<PointType>& ring) {
3101 bool changed = true;
3102 while (changed && ring.size() >= 3) {
3103 changed = false;
3104 for (std::size_t i = 0; i < ring.size() && ring.size() >= 3; ++i) {
3105 const std::size_t size = ring.size();
3106 if (!(ring[(i + size - 1) % size] == ring[(i + 1) % size])) {
3107 continue;
3108 }
3109 const auto at = static_cast<std::ptrdiff_t>(i);
3110 if (i + 1 < size) {
3111 ring.erase(ring.begin() + at, ring.begin() + at + 2);
3112 } else {
3113 ring.erase(ring.begin() + at);
3114 ring.erase(ring.begin());
3115 }
3116 changed = true;
3117 break;
3118 }
3119 }
3120 if (ring.size() < 3) {
3121 ring.clear();
3122 }
3123 }
3124
3144 class TrapezoidPointLocation {
3145 using WorkNumber = division_result_t<NumberType>;
3146 using WorkPoint = Point<WorkNumber>;
3147 using WorkLine = OrientedLine<WorkPoint>;
3148 using IntegralPoint = Point<std::int64_t>;
3149 using IntegralLine = OrientedLine<IntegralPoint>;
3150 using StoredLine = std::variant<WorkLine, IntegralLine>;
3151 using QueryInteger = rational_int_t<WorkNumber>;
3152 using WideIntegralPoint = Point<QueryInteger>;
3153 static constexpr std::uint32_t none = ~std::uint32_t{};
3154
3155 // An abscissa of the sheared frame that a wall stands on. A vertex wall
3156 // carries the vertex, whose abscissa is `x + eps*y`; the two infinite
3157 // eps levels are the ends a vertical unbounded edge escapes to, past
3158 // every vertex on that abscissa yet within an infinitesimal of it.
3159 struct Abscissa {
3160 PointType point;
3161 std::int8_t epsInfinity = 0;
3162 };
3163
3164 // Sheared order of two wall abscissae, formed without ever building a
3165 // sheared coordinate: the plane's own abscissa decides, and the
3166 // ordinate only separates what shares it.
3167 static bool abscissaLess(const Abscissa& left, const Abscissa& right) {
3168 if (left.point.x() != right.point.x()) {
3169 return left.point.x() < right.point.x();
3170 }
3171 if (left.epsInfinity != right.epsInfinity) {
3172 return left.epsInfinity < right.epsInfinity;
3173 }
3174 return left.epsInfinity == 0 && left.point.y() < right.point.y();
3175 }
3176
3177 // An abscissa of the sheared frame that is not a wall: the pair stands
3178 // for `a + eps*b`. Sampling produces these and nothing stores them.
3179 struct SampleAbscissa {
3180 WorkNumber a{};
3181 WorkNumber b{};
3182 };
3183
3184 // A point of the sheared plane, `value + eps*epsilon`. A curve meets an
3185 // abscissa strictly inside an infinitesimally thin slab at such a point
3186 // and at no ordinary one.
3187 struct DualPoint {
3188 WorkPoint value;
3189 WorkPoint epsilon;
3190 };
3191
3192 // A vertical bound of a trapezoid. Walls are held in increasing
3193 // abscissa, so their index orders every finite bound outright.
3194 struct Bound {
3195 // -1 and +1 are the two infinities; zero carries a wall.
3196 std::int8_t infinity = 0;
3197 std::uint32_t wall = none;
3198
3199 static Bound negativeInfinity() { return Bound{-1, none}; }
3200 static Bound positiveInfinity() { return Bound{1, none}; }
3201
3202 bool operator==(const Bound& other) const {
3203 return infinity == other.infinity && wall == other.wall;
3204 }
3205 };
3206
3207 // Live trapezoids immediately to the right of one transformed vertex.
3208 // Entries are append-only; an old entry is ignored once its trapezoid
3209 // becomes inactive. This is the local adjacency needed to walk a new
3210 // curve across the map without searching the history DAG again.
3211 struct Wall {
3212 std::vector<std::uint32_t> starts;
3213 };
3214
3215 struct Curve {
3216 // Original coordinates, oriented left to right after the shear.
3217 StoredLine queryLine;
3218 Bound left;
3219 Bound right;
3220 HalfedgeId leftToRight;
3221 WorkNumber originalDx{};
3222 WorkNumber originalDy{};
3223 };
3224
3225 struct Query {
3226 const PointType* point = nullptr;
3227 std::optional<IntegralPoint> integralPoint;
3228 std::optional<WideIntegralPoint> wideIntegralPoint;
3229 };
3230
3231 struct Trapezoid {
3232 Bound left;
3233 Bound right;
3234 std::uint32_t bottom = none;
3235 std::uint32_t top = none;
3236 std::uint32_t leaf = none;
3237 FaceId face;
3238 bool active = true;
3239 };
3240
3241 enum class NodeKind : std::uint8_t { leaf, x, curve };
3242
3243 struct Node {
3244 NodeKind kind = NodeKind::leaf;
3245 std::uint32_t value = 0; // trapezoid, curve, or wall
3246 std::uint32_t low = none;
3247 std::uint32_t high = none;
3248 };
3249
3250 public:
3251 template <class UniformRandomBitGenerator>
3252 TrapezoidPointLocation(const Arrangement& arrangement,
3253 UniformRandomBitGenerator&& generator) {
3254 makeWalls(arrangement);
3255 makeVertexIndex(arrangement);
3256 makeCurves(arrangement);
3257
3258 const std::uint32_t initial = newTrapezoid(
3259 Bound::negativeInfinity(), Bound::positiveInfinity(), none, none);
3260 root_ = trapezoids_[initial].leaf;
3261
3262 std::vector<std::uint32_t> order(curves_.size());
3263 for (std::uint32_t i = 0; i < order.size(); ++i) {
3264 order[i] = i;
3265 }
3266 std::shuffle(order.begin(), order.end(),
3267 std::forward<UniformRandomBitGenerator>(generator));
3268 for (const std::uint32_t curve : order) {
3269 insertCurve(curve);
3270 }
3271 labelTrapezoids(arrangement);
3272 std::vector<WorkLine>().swap(constructionLines_);
3273 }
3274
3275 [[nodiscard]] FaceId locateFace(const Arrangement& arrangement,
3276 const PointType& point) const {
3277 const Query query = makeQuery(point);
3278 std::uint32_t node = root_;
3279 while (nodes_[node].kind != NodeKind::leaf) {
3280 const Node& decision = nodes_[node];
3281 if (decision.kind == NodeKind::x) {
3282 // A query standing on the wall goes left, which is the
3283 // existing -x perturbation.
3284 node = againstWall(point, decision.value) <= 0 ? decision.low
3285 : decision.high;
3286 continue;
3287 }
3288 const Curve& curve = curves_[decision.value];
3289 const auto orientation = sideOf(curve, query);
3290 int side = orientation > 0 ? 1 : (orientation < 0 ? -1 : 0);
3291 if (side == 0) {
3292 // Sign of cross(d, (-eps,+eps^2)): dy is primary and
3293 // dx resolves the horizontal case.
3294 side = curve.originalDy > WorkNumber(0)
3295 ? 1
3296 : (curve.originalDy < WorkNumber(0)
3297 ? -1
3298 : (curve.originalDx > WorkNumber(0) ? 1 : -1));
3299 }
3300 node = side < 0 ? decision.low : decision.high;
3301 }
3302 const FaceId result = trapezoids_[nodes_[node].value].face;
3303 assert(result.valid() && result.index() < arrangement.faceCount());
3304 return result;
3305 }
3306
3307 [[nodiscard]] CellId locateCell(const Arrangement& arrangement,
3308 const PointType& point) const {
3309 const auto vertex = std::lower_bound(
3310 vertices_.begin(), vertices_.end(), point,
3311 [&](VertexId v, const PointType& p) { return arrangement.points_[v.index()] < p; });
3312 if (vertex != vertices_.end() && arrangement.points_[vertex->index()] == point) {
3313 return *vertex;
3314 }
3315
3316 const Query query = makeQuery(point);
3317 std::uint32_t node = root_;
3318 while (nodes_[node].kind != NodeKind::leaf) {
3319 const Node& decision = nodes_[node];
3320 if (decision.kind == NodeKind::x) {
3321 node = againstWall(point, decision.value) < 0 ? decision.low
3322 : decision.high;
3323 continue;
3324 }
3325 const Curve& curve = curves_[decision.value];
3326 const auto side = sideOf(curve, query);
3327 if (side == 0) {
3328 return HalfedgeId(2 * (curve.leftToRight.index() / 2));
3329 }
3330 node = side < 0 ? decision.low : decision.high;
3331 }
3332 return trapezoids_[nodes_[node].value].face;
3333 }
3334
3335 [[nodiscard]] VertexId indexedVertex(std::size_t index) const {
3336 return vertices_[index];
3337 }
3338
3339 // Vertices whose abscissa lies within the closed range, in increasing
3340 // order. An absent bound is unbounded on that side. The index is sorted
3341 // lexicographically on the original coordinates, so a query confined to
3342 // a narrow band of abscissae -- a vertical ray above all -- reaches only
3343 // the vertices that band can hold.
3344 template <class Number, class Fn>
3345 void visitCandidateVertices(const Arrangement& arrangement,
3346 const std::optional<Number>& low,
3347 const std::optional<Number>& high,
3348 Fn&& fn) const {
3349 auto first = vertices_.begin();
3350 auto last = vertices_.end();
3351 if (low) {
3352 first = std::lower_bound(
3353 first, last, *low, [&](VertexId v, const Number& x) {
3354 return arrangement.points_[v.index()].x() < x;
3355 });
3356 }
3357 if (high) {
3358 last = std::upper_bound(
3359 first, last, *high, [&](const Number& x, VertexId v) {
3360 return x < arrangement.points_[v.index()].x();
3361 });
3362 }
3363 for (auto it = first; it != last; ++it) {
3364 fn(*it);
3365 }
3366 }
3367
3368 [[nodiscard]] HalfedgeId indexedHalfedge(std::size_t index) const {
3369 return HalfedgeId(2 * (curves_[index].leftToRight.index() / 2));
3370 }
3371
3372 // Walks the search DAG carrying the query clipped to the region each
3373 // node stands for, held as an interval of the query's parameter. Both
3374 // kinds of decision cut that interval with a straight line -- an x node
3375 // with the wall it splits on, a curve node with the curve's supporting
3376 // line -- so a child is entered only where the query really reaches it,
3377 // and the region a node stands for stays exactly the intersection of
3378 // the cuts along the path. A curve is offered where its supporting line
3379 // still crosses the clipped query, which is a superset of the
3380 // intersecting arrangement edges: the caller deduplicates the offers
3381 // and applies the exact predicate.
3382 //
3383 // Nodes are taken earliest first, by the smallest parameter the node
3384 // can still hold, so once a node comes out no candidate found later can
3385 // precede it. That bound goes to `fn` as the frontier, which lets the
3386 // caller settle its answer for everything behind the frontier and stop
3387 // the walk there instead of running the query out to its far end. `fn`
3388 // is called with an invalid halfedge to carry the frontier alone, and
3389 // stops the walk by returning `true`. An absent frontier is the start
3390 // of an unbounded query, where nothing is settled yet.
3391 template <class Parameter, class Q, class Fn>
3392 bool visitCandidateHalfedges(const Q& query, Fn&& fn) const {
3393 const Parameter zero(0);
3394
3395 const Parameter x0(query[0].x());
3396 const Parameter y0(query[0].y());
3397 const Parameter dx = Parameter(query[1].x()) - x0;
3398 const Parameter dy = Parameter(query[1].y()) - y0;
3399
3400 // The parameters still in play, each end absent where the query
3401 // runs off to infinity.
3402 struct Span {
3403 std::uint32_t node;
3404 std::optional<Parameter> low;
3405 std::optional<Parameter> high;
3406 };
3407
3408 // Keeps only the parameters where `value + t * rate` holds the
3409 // requested sign, and answers whether any parameter is left.
3410 const auto narrow = [&zero](Span& span, const Parameter& value,
3411 const Parameter& rate, bool nonNegative) {
3412 if (rate == zero) {
3413 return nonNegative ? !(value < zero) : !(value > zero);
3414 }
3415 const Parameter root = -value / rate;
3416 if ((rate > zero) == nonNegative) {
3417 if (!span.low || *span.low < root) {
3418 span.low = root;
3419 }
3420 } else {
3421 if (!span.high || root < *span.high) {
3422 span.high = root;
3423 }
3424 }
3425 return !(span.low && span.high && *span.high < *span.low);
3426 };
3427
3428 // The same, for a quantity of the sheared frame held as the pair
3429 // `(value, epsValue)` standing for `value + eps*epsValue` and
3430 // changing at `(rate, epsRate)`. The plane's own level decides
3431 // unless it vanishes identically, and only then does the eps level
3432 // speak. Where the plane's level merely reaches zero, keeping that
3433 // one parameter on both sides costs a candidate and spares the eps
3434 // level a comparison.
3435 const auto narrowSheared = [&](Span& span, const Parameter& value,
3436 const Parameter& rate,
3437 const Parameter& epsValue,
3438 const Parameter& epsRate, bool nonNegative) {
3439 if (rate != zero) {
3440 return narrow(span, value, rate, nonNegative);
3441 }
3442 if (value != zero) {
3443 return (value > zero) == nonNegative;
3444 }
3445 return narrow(span, epsValue, epsRate, nonNegative);
3446 };
3447
3448 // The side of a curve's supporting line, as `value + t * rate`.
3449 const auto sideAlongQuery = [&](const Curve& curve) {
3450 return std::visit(
3451 [&](const auto& line) {
3452 const Parameter sourceX(line.source().x());
3453 const Parameter sourceY(line.source().y());
3454 const Parameter edgeX = Parameter(line.target().x()) - sourceX;
3455 const Parameter edgeY = Parameter(line.target().y()) - sourceY;
3456 return std::pair<Parameter, Parameter>(
3457 edgeX * (y0 - sourceY) - edgeY * (x0 - sourceX),
3458 edgeX * dy - edgeY * dx);
3459 },
3460 curve.queryLine);
3461 };
3462
3463 // Orders the heap so the span holding the earliest parameter, an
3464 // absent bound being earliest of all, comes out first.
3465 const auto laterFirst = [](const Span& left, const Span& right) {
3466 if (!left.low) {
3467 return false;
3468 }
3469 if (!right.low) {
3470 return true;
3471 }
3472 return *right.low < *left.low;
3473 };
3474
3475 Span start{root_, std::nullopt, std::nullopt};
3476 if constexpr (!OrientedLineConcept<Q>) {
3477 start.low = zero;
3478 if constexpr (!RayConcept<Q>) {
3479 start.high = Parameter(1);
3480 }
3481 }
3482
3483 std::vector<Span> pending;
3484 pending.push_back(std::move(start));
3485 const auto offer = [&](Span span) {
3486 pending.push_back(std::move(span));
3487 std::push_heap(pending.begin(), pending.end(), laterFirst);
3488 };
3489
3490 while (!pending.empty()) {
3491 std::pop_heap(pending.begin(), pending.end(), laterFirst);
3492 const Span span = std::move(pending.back());
3493 pending.pop_back();
3494
3495 const Parameter* frontier = span.low ? &*span.low : nullptr;
3496 if (fn(HalfedgeId(), frontier)) {
3497 return true;
3498 }
3499
3500 const Node& node = nodes_[span.node];
3501 if (node.kind == NodeKind::leaf) {
3502 continue;
3503 }
3504
3505 if (node.kind == NodeKind::x) {
3506 // A query sitting exactly on the wall enters both sides.
3507 const Abscissa& wall = wallXs_[node.value];
3508 const Parameter splitX(wall.point.x());
3509 Span low = span;
3510 Span high = span;
3511 bool below = false;
3512 bool above = false;
3513 if (wall.epsInfinity != 0) {
3514 // The wall stands infinitesimally beside an abscissa of
3515 // the plane, so the plane's own level settles the side
3516 // wherever it settles anything at all.
3517 below = narrow(low, splitX - x0, -dx, true);
3518 above = narrow(high, x0 - splitX, dx, true);
3519 } else {
3520 const Parameter splitY(wall.point.y());
3521 below = narrowSheared(low, splitX - x0, -dx, splitY - y0, -dy,
3522 true);
3523 above = narrowSheared(high, x0 - splitX, dx, y0 - splitY, dy,
3524 true);
3525 }
3526 if (below) {
3527 low.node = node.low;
3528 offer(std::move(low));
3529 }
3530 if (above) {
3531 high.node = node.high;
3532 offer(std::move(high));
3533 }
3534 continue;
3535 }
3536
3537 const Curve& curve = curves_[node.value];
3538 const auto [value, rate] = sideAlongQuery(curve);
3539 Span low = span;
3540 const bool below = narrow(low, value, rate, false);
3541 Span high = span;
3542 const bool above = narrow(high, value, rate, true);
3543 if (below && above) {
3544 // The clipped query reaches both sides, so it meets the
3545 // supporting line inside the region this node stands for.
3546 const HalfedgeId h(2 * (curve.leftToRight.index() / 2));
3547 if (fn(h, frontier)) {
3548 return true;
3549 }
3550 }
3551 if (below) {
3552 low.node = node.low;
3553 offer(std::move(low));
3554 }
3555 if (above) {
3556 high.node = node.high;
3557 offer(std::move(high));
3558 }
3559 }
3560 return false;
3561 }
3562
3563 private:
3564 static bool less(const Bound& left, const Bound& right) {
3565 if (left.infinity != right.infinity) {
3566 return left.infinity < right.infinity;
3567 }
3568 return left.infinity == 0 && left.wall < right.wall;
3569 }
3570
3571 static Bound maximum(const Bound& left, const Bound& right) {
3572 return less(left, right) ? right : left;
3573 }
3574
3575 static Bound minimum(const Bound& left, const Bound& right) {
3576 return less(left, right) ? left : right;
3577 }
3578
3579 [[nodiscard]] Bound wallBound(const PointType& point,
3580 std::int8_t epsInfinity) const {
3581 const Abscissa key{point, epsInfinity};
3582 const auto found =
3583 std::lower_bound(wallXs_.begin(), wallXs_.end(), key, abscissaLess);
3584 assert(found != wallXs_.end() && !abscissaLess(key, *found));
3585 return Bound{0, static_cast<std::uint32_t>(found - wallXs_.begin())};
3586 }
3587
3588 // Where an ordinary query point falls against a wall. Its abscissa is
3589 // the point read lexicographically, so nothing is computed here.
3590 [[nodiscard]] int againstWall(const PointType& point, std::uint32_t wall) const {
3591 const Abscissa& abscissa = wallXs_[wall];
3592 if (point.x() != abscissa.point.x()) {
3593 return point.x() < abscissa.point.x() ? -1 : 1;
3594 }
3595 if (abscissa.epsInfinity != 0) {
3596 return abscissa.epsInfinity > 0 ? -1 : 1;
3597 }
3598 if (point.y() != abscissa.point.y()) {
3599 return point.y() < abscissa.point.y() ? -1 : 1;
3600 }
3601 return 0;
3602 }
3603
3604 // The same for a point of the sheared plane: the plane's own abscissa
3605 // first, then the eps level the shear introduces, then the eps^2 level
3606 // a sampled point can reach and a wall never does.
3607 [[nodiscard]] int againstWall(const DualPoint& point, std::uint32_t wall) const {
3608 const Abscissa& abscissa = wallXs_[wall];
3609 const WorkNumber x(abscissa.point.x());
3610 if (point.value.x() != x) {
3611 return point.value.x() < x ? -1 : 1;
3612 }
3613 if (abscissa.epsInfinity != 0) {
3614 return abscissa.epsInfinity > 0 ? -1 : 1;
3615 }
3616 const WorkNumber y(abscissa.point.y());
3617 const WorkNumber first = point.value.y() + point.epsilon.x();
3618 if (first != y) {
3619 return first < y ? -1 : 1;
3620 }
3621 if (point.epsilon.y() != WorkNumber(0)) {
3622 return point.epsilon.y() < WorkNumber(0) ? -1 : 1;
3623 }
3624 return 0;
3625 }
3626
3627 void makeVertexIndex(const Arrangement& arrangement) {
3628 vertices_.reserve(arrangement.points_.size());
3629 for (std::uint32_t v = 0; v < arrangement.points_.size(); ++v) {
3630 vertices_.push_back(VertexId(v));
3631 }
3632 std::sort(vertices_.begin(), vertices_.end(), [&](VertexId left, VertexId right) {
3633 return arrangement.points_[left.index()] < arrangement.points_[right.index()];
3634 });
3635 }
3636
3637 void makeWalls(const Arrangement& arrangement) {
3638 wallXs_.reserve(arrangement.points_.size());
3639 for (const PointType& point : arrangement.points_) {
3640 wallXs_.push_back(Abscissa{point, 0});
3641 }
3642 // A vertical unbounded edge occupies a single abscissa of the plane
3643 // and escapes to an end infinitesimally beside it, past every
3644 // vertex there. Those ends bound trapezoids, so they are walls too.
3645 for (const EdgeGeometry& geometry : arrangement.edgeGeometry_) {
3646 if (geometry.kind == EdgeKind::segment ||
3647 geometry.a.x() != geometry.b.x()) {
3648 continue;
3649 }
3650 const bool upward = geometry.a.y() < geometry.b.y();
3651 if (geometry.kind == EdgeKind::line || upward) {
3652 wallXs_.push_back(Abscissa{geometry.a, 1});
3653 }
3654 if (geometry.kind == EdgeKind::line || !upward) {
3655 wallXs_.push_back(Abscissa{geometry.a, -1});
3656 }
3657 }
3658 std::sort(wallXs_.begin(), wallXs_.end(), abscissaLess);
3659 wallXs_.erase(std::unique(wallXs_.begin(), wallXs_.end(),
3660 [](const Abscissa& left, const Abscissa& right) {
3661 return !abscissaLess(left, right) &&
3662 !abscissaLess(right, left);
3663 }),
3664 wallXs_.end());
3665 walls_.resize(wallXs_.size());
3666 }
3667
3668 void makeCurves(const Arrangement& arrangement) {
3669 curves_.reserve(arrangement.edgeGeometry_.size());
3670 constructionLines_.reserve(arrangement.edgeGeometry_.size());
3671 for (std::uint32_t edge = 0; edge < arrangement.edgeGeometry_.size(); ++edge) {
3672 const EdgeGeometry& geometry = arrangement.edgeGeometry_[edge];
3673 WorkNumber originalDx = WorkNumber(geometry.b.x()) - WorkNumber(geometry.a.x());
3674 WorkNumber originalDy = WorkNumber(geometry.b.y()) - WorkNumber(geometry.a.y());
3675 // Lexicographic order is the abscissa order of the sheared
3676 // frame, so no coordinate has to be formed to read it off.
3677 const bool forward = geometry.a < geometry.b;
3678 if (!forward) {
3679 originalDx = -originalDx;
3680 originalDy = -originalDy;
3681 }
3682
3683 Bound left = Bound::negativeInfinity();
3684 Bound right = Bound::positiveInfinity();
3685 if (geometry.kind == EdgeKind::segment) {
3686 left = wallBound(forward ? geometry.a : geometry.b, 0);
3687 right = wallBound(forward ? geometry.b : geometry.a, 0);
3688 } else {
3689 // A vertical edge spans no abscissa of the plane at all, so
3690 // an unbounded one escapes to the infinitesimal side of the
3691 // abscissa it stands on rather than to infinity.
3692 const bool vertical = geometry.a.x() == geometry.b.x();
3693 if (geometry.kind == EdgeKind::ray) {
3694 (forward ? left : right) = wallBound(geometry.a, 0);
3695 if (vertical) {
3696 (forward ? right : left) =
3697 wallBound(geometry.a, forward ? 1 : -1);
3698 }
3699 } else if (vertical) {
3700 left = wallBound(geometry.a, -1);
3701 right = wallBound(geometry.a, 1);
3702 }
3703 }
3704
3705 WorkPoint originalA(WorkNumber(geometry.a.x()), WorkNumber(geometry.a.y()));
3706 WorkPoint originalB(WorkNumber(geometry.b.x()), WorkNumber(geometry.b.y()));
3707 if (!forward) {
3708 std::swap(originalA, originalB);
3709 }
3710 // The shear has determinant one, so orientation is identical in
3711 // both frames and the original coordinates serve throughout.
3712 StoredLine queryLine(std::in_place_type<WorkLine>, originalA, originalB);
3713 if constexpr (is_Rational_v<WorkNumber>) {
3714 if (auto integral = WorkLine(originalA, originalB)
3715 .template integralLine<std::int64_t>()) {
3716 queryLine = std::move(*integral);
3717 }
3718 }
3719 curves_.push_back(Curve{std::move(queryLine), left, right,
3720 HalfedgeId(2 * edge + (forward ? 0 : 1)),
3721 originalDx, originalDy});
3722 constructionLines_.emplace_back(originalA, originalB);
3723 }
3724 }
3725
3726 // Which side of a curve a point of the sheared plane falls on: the
3727 // ordinary orientation decides, and where that vanishes the point's own
3728 // infinitesimal offset does.
3729 [[nodiscard]] std::partial_ordering constructionSideOf(
3730 std::uint32_t curve, const DualPoint& point) const {
3731 const std::partial_ordering side = std::visit(
3732 [&](const auto& line) {
3733 return orientationSign(line.source(), line.target(), point.value);
3734 },
3735 curves_[curve].queryLine);
3736 if (side != 0) {
3737 return side;
3738 }
3739 const WorkNumber cross = curves_[curve].originalDx * point.epsilon.y() -
3740 curves_[curve].originalDy * point.epsilon.x();
3741 return cross <=> WorkNumber(0);
3742 }
3743
3744 [[nodiscard]] static std::partial_ordering sideOf(const Curve& curve,
3745 const Query& query) {
3746 return std::visit(
3747 [&](const auto& line) {
3748 using Line = std::remove_cvref_t<decltype(line)>;
3749 if constexpr (std::same_as<Line, IntegralLine>) {
3750 if (query.integralPoint) {
3751 return orientationSign(line.source(), line.target(),
3752 *query.integralPoint);
3753 }
3754 if (query.wideIntegralPoint) {
3755 return orientationSign(line.source(), line.target(),
3756 *query.wideIntegralPoint);
3757 }
3758 }
3759 return orientationSign(line.source(), line.target(), *query.point);
3760 },
3761 curve.queryLine);
3762 }
3763
3764 [[nodiscard]] Query makeQuery(const PointType& point) const {
3765 Query query{&point, std::nullopt, std::nullopt};
3766 classifyQuery(query, point);
3767 return query;
3768 }
3769
3770 void classifyQuery(Query& query, const PointType& original) const {
3771 const auto storeIntegral = [&](QueryInteger x, QueryInteger y) {
3772 if (detail::representableAs<std::int64_t>(x) &&
3773 detail::representableAs<std::int64_t>(y)) {
3774 query.integralPoint.emplace(detail::narrowTo<std::int64_t>(x),
3775 detail::narrowTo<std::int64_t>(y));
3776 } else {
3777 query.wideIntegralPoint.emplace(std::move(x), std::move(y));
3778 }
3779 };
3780
3781 // Curve nodes operate in the original coordinates, and so do x
3782 // nodes now that the shear is symbolic; an integral query point
3783 // only ever needs its own coordinates.
3784 if constexpr (is_Rational_v<NumberType>) {
3785 if (original.x().isInteger() && original.y().isInteger()) {
3786 storeIntegral(
3787 QueryInteger(static_cast<rational_int_t<NumberType>>(
3788 original.x())),
3789 QueryInteger(static_cast<rational_int_t<NumberType>>(
3790 original.y())));
3791 }
3792 } else if constexpr (detail::extended_integral<NumberType> ||
3793 std::same_as<NumberType, BigInt>) {
3794 storeIntegral(QueryInteger(original.x()), QueryInteger(original.y()));
3795 }
3796 }
3797
3798 // An abscissa strictly between two bounds. Where the bounds share the
3799 // abscissa of the plane -- vertices stacked above one another -- what
3800 // separates them lives at the eps level, and the sample goes there.
3801 [[nodiscard]] SampleAbscissa sample(const Bound& left, const Bound& right) const {
3802 assert(less(left, right));
3803 const Abscissa* low = left.infinity == 0 ? &wallXs_[left.wall] : nullptr;
3804 const Abscissa* high = right.infinity == 0 ? &wallXs_[right.wall] : nullptr;
3805 if (low == nullptr && high == nullptr) {
3806 return SampleAbscissa{WorkNumber(0), WorkNumber(0)};
3807 }
3808 if (low == nullptr) {
3809 return SampleAbscissa{WorkNumber(high->point.x()) - WorkNumber(1),
3810 WorkNumber(0)};
3811 }
3812 if (high == nullptr) {
3813 return SampleAbscissa{WorkNumber(low->point.x()) + WorkNumber(1),
3814 WorkNumber(0)};
3815 }
3816 const WorkNumber lowX(low->point.x());
3817 const WorkNumber highX(high->point.x());
3818 if (lowX != highX) {
3819 return SampleAbscissa{(lowX + highX) / WorkNumber(2), WorkNumber(0)};
3820 }
3821 // Both bounds stand on one abscissa of the plane, so the eps level
3822 // is what is left to separate, its infinities included.
3823 if (low->epsInfinity < 0) {
3824 return SampleAbscissa{lowX, high->epsInfinity > 0
3825 ? WorkNumber(0)
3826 : WorkNumber(high->point.y()) -
3827 WorkNumber(1)};
3828 }
3829 if (high->epsInfinity > 0) {
3830 return SampleAbscissa{lowX, WorkNumber(low->point.y()) + WorkNumber(1)};
3831 }
3832 return SampleAbscissa{lowX, (WorkNumber(low->point.y()) +
3833 WorkNumber(high->point.y())) /
3834 WorkNumber(2)};
3835 }
3836
3837 // The point of a curve whose sheared abscissa is `x`. Inside a slab of
3838 // infinitesimal width the curve reaches the abscissa only infinitesimally
3839 // away from the plane's own points, which is what the eps part carries;
3840 // a vertical curve is the one that lives there outright.
3841 [[nodiscard]] DualPoint pointAt(std::uint32_t curve,
3842 const SampleAbscissa& x) const {
3843 const WorkPoint& source = constructionLines_[curve].source();
3844 const WorkNumber& dx = curves_[curve].originalDx;
3845 const WorkNumber& dy = curves_[curve].originalDy;
3846 const WorkPoint zero(WorkNumber(0), WorkNumber(0));
3847 if (dx == WorkNumber(0)) {
3848 // The eps level of the abscissa is the ordinate outright.
3849 assert(x.a == source.x());
3850 return DualPoint{WorkPoint(source.x(), x.b), zero};
3851 }
3852 const WorkNumber along = (x.a - source.x()) / dx;
3853 const WorkNumber y = source.y() + along * dy;
3854 // Sliding along the curve moves the abscissa by the slide itself,
3855 // so covering the eps level takes the ordinate still missing.
3856 const WorkNumber slide = x.b - y;
3857 if (slide == WorkNumber(0)) {
3858 return DualPoint{WorkPoint(x.a, y), zero};
3859 }
3860 return DualPoint{WorkPoint(x.a, y), WorkPoint(slide, slide * dy / dx)};
3861 }
3862
3863 [[nodiscard]] bool crosses(std::uint32_t curveId,
3864 const Trapezoid& trapezoid) const {
3865 const Curve& curve = curves_[curveId];
3866 const Bound left = maximum(curve.left, trapezoid.left);
3867 const Bound right = minimum(curve.right, trapezoid.right);
3868 if (!less(left, right)) {
3869 return false;
3870 }
3871 const DualPoint point = pointAt(curveId, sample(left, right));
3872 if (trapezoid.bottom != none &&
3873 constructionSideOf(trapezoid.bottom, point) <= 0) {
3874 return false;
3875 }
3876 if (trapezoid.top != none &&
3877 constructionSideOf(trapezoid.top, point) >= 0) {
3878 return false;
3879 }
3880 return true;
3881 }
3882
3883 [[nodiscard]] std::uint32_t newLeaf(std::uint32_t trapezoid) {
3884 const std::uint32_t id = static_cast<std::uint32_t>(nodes_.size());
3885 nodes_.push_back(Node{NodeKind::leaf, trapezoid, none, none});
3886 return id;
3887 }
3888
3889 [[nodiscard]] std::uint32_t newTrapezoid(Bound left, Bound right,
3890 std::uint32_t bottom,
3891 std::uint32_t top) {
3892 if (trapezoids_.size() >= none || nodes_.size() >= none) {
3893 throw std::length_error("Arrangement point-location index exceeds 32-bit capacity");
3894 }
3895 const std::uint32_t id = static_cast<std::uint32_t>(trapezoids_.size());
3896 trapezoids_.push_back(Trapezoid{std::move(left), std::move(right), bottom, top,
3897 none, FaceId(), true});
3898 if (trapezoids_.back().left.infinity == 0) {
3899 walls_[trapezoids_.back().left.wall].starts.push_back(id);
3900 }
3901 trapezoids_.back().leaf = newLeaf(id);
3902 return id;
3903 }
3904
3905 [[nodiscard]] std::uint32_t newNode(Node node) {
3906 if (nodes_.size() >= none) {
3907 throw std::length_error("Arrangement point-location index exceeds 32-bit capacity");
3908 }
3909 const std::uint32_t id = static_cast<std::uint32_t>(nodes_.size());
3910 nodes_.push_back(std::move(node));
3911 return id;
3912 }
3913
3914 [[nodiscard]] Node curveNode(std::uint32_t curve, std::uint32_t below,
3915 std::uint32_t above) const {
3916 return Node{NodeKind::curve, curve, below, above};
3917 }
3918
3919 [[nodiscard]] Node xNode(const Bound& bound, std::uint32_t left,
3920 std::uint32_t right) const {
3921 assert(bound.infinity == 0);
3922 return Node{NodeKind::x, bound.wall, left, right};
3923 }
3924
3925 // Chooses an abscissa just to the right of a curve's left end, but
3926 // before the next arrangement wall. No vertical wall of the trapezoidal
3927 // map can occur inside that open slab.
3928 [[nodiscard]] SampleAbscissa probeRightOf(const Bound& left,
3929 const Bound& right) const {
3930 const std::size_t following =
3931 left.infinity < 0 ? 0 : static_cast<std::size_t>(left.wall) + 1;
3932 Bound next = Bound::positiveInfinity();
3933 if (following < wallXs_.size()) {
3934 next = Bound{0, static_cast<std::uint32_t>(following)};
3935 }
3936 return sample(left, minimum(next, right));
3937 }
3938
3939 [[nodiscard]] std::uint32_t locateTrapezoid(const DualPoint& point) const {
3940 std::uint32_t nodeId = root_;
3941 while (nodes_[nodeId].kind != NodeKind::leaf) {
3942 const Node& node = nodes_[nodeId];
3943 if (node.kind == NodeKind::x) {
3944 const int side = againstWall(point, node.value);
3945 assert(side != 0);
3946 nodeId = side < 0 ? node.low : node.high;
3947 continue;
3948 }
3949 const auto side = constructionSideOf(node.value, point);
3950 assert(side != 0);
3951 nodeId = side < 0 ? node.low : node.high;
3952 }
3953 const std::uint32_t trapezoid = nodes_[nodeId].value;
3954 assert(trapezoids_[trapezoid].active);
3955 return trapezoid;
3956 }
3957
3958 // One DAG query finds the first trapezoid. Every subsequent one is
3959 // selected from the live trapezoids beginning at the current right
3960 // wall, so insertion work follows the curve instead of revisiting its
3961 // complete search history.
3962 [[nodiscard]] std::vector<std::uint32_t> crossedByWalk(
3963 std::uint32_t curveId) const {
3964 const Curve& curve = curves_[curveId];
3965 const SampleAbscissa probe = probeRightOf(curve.left, curve.right);
3966 std::uint32_t current = locateTrapezoid(pointAt(curveId, probe));
3967 std::vector<std::uint32_t> crossed;
3968
3969 for (;;) {
3970 const Trapezoid& trapezoid = trapezoids_[current];
3971 assert(trapezoid.active && crosses(curveId, trapezoid));
3972 crossed.push_back(current);
3973
3974 if (!less(trapezoid.right, curve.right)) {
3975 break;
3976 }
3977 assert(trapezoid.right.infinity == 0);
3978
3979 std::uint32_t next = none;
3980 for (const std::uint32_t candidate :
3981 walls_[trapezoid.right.wall].starts) {
3982 const Trapezoid& adjacent = trapezoids_[candidate];
3983 if (adjacent.active && adjacent.left == trapezoid.right &&
3984 crosses(curveId, adjacent)) {
3985 assert(next == none);
3986 next = candidate;
3987 }
3988 }
3989 if (next == none) {
3990 throw std::logic_error(
3991 "arrangement edge lost its adjacent trapezoid");
3992 }
3993 current = next;
3994 }
3995 return crossed;
3996 }
3997
3998 void insertCurve(std::uint32_t curveId) {
3999 const Curve& curve = curves_[curveId];
4000 const std::vector<std::uint32_t> crossed = crossedByWalk(curveId);
4001 if (crossed.empty()) {
4002 throw std::logic_error("arrangement edge did not cross its trapezoidal map");
4003 }
4004
4005 std::uint32_t previousAbove = none;
4006 std::uint32_t previousBelow = none;
4007 for (const std::uint32_t oldId : crossed) {
4008 const Trapezoid old = trapezoids_[oldId];
4009 const Bound left = maximum(curve.left, old.left);
4010 const Bound right = minimum(curve.right, old.right);
4011
4012 std::uint32_t below;
4013 if (previousBelow != none &&
4014 trapezoids_[previousBelow].bottom == old.bottom &&
4015 trapezoids_[previousBelow].right == left) {
4016 below = previousBelow;
4017 trapezoids_[below].right = right;
4018 } else {
4019 below = newTrapezoid(left, right, old.bottom, curveId);
4020 }
4021
4022 std::uint32_t above;
4023 if (previousAbove != none && trapezoids_[previousAbove].top == old.top &&
4024 trapezoids_[previousAbove].right == left) {
4025 above = previousAbove;
4026 trapezoids_[above].right = right;
4027 } else {
4028 above = newTrapezoid(left, right, curveId, old.top);
4029 }
4030
4031 const bool hasLeftCap = less(old.left, left);
4032 const bool hasRightCap = less(right, old.right);
4033 std::uint32_t leftCap = none;
4034 std::uint32_t rightCap = none;
4035 if (hasLeftCap) {
4036 leftCap = newTrapezoid(old.left, left, old.bottom, old.top);
4037 }
4038 if (hasRightCap) {
4039 rightCap = newTrapezoid(right, old.right, old.bottom, old.top);
4040 }
4041
4042 Node replacement = curveNode(curveId, trapezoids_[below].leaf,
4043 trapezoids_[above].leaf);
4044 if (hasRightCap) {
4045 const std::uint32_t split = newNode(std::move(replacement));
4046 replacement = xNode(right, split, trapezoids_[rightCap].leaf);
4047 }
4048 if (hasLeftCap) {
4049 const std::uint32_t split = newNode(std::move(replacement));
4050 replacement = xNode(left, trapezoids_[leftCap].leaf, split);
4051 }
4052 nodes_[old.leaf] = std::move(replacement);
4053 trapezoids_[oldId].active = false;
4054 previousBelow = below;
4055 previousAbove = above;
4056 }
4057 }
4058
4059 // The face above a vertical strip that no edge crosses, read off the
4060 // fan at infinity, where the strip escapes. The strip's own abscissa is
4061 // part of the query: an end rising straight up is parallel to it, and
4062 // the fan separates parallel ends by where they stand.
4063 //
4064 // A strip standing on a single abscissa can tie with such an end, and
4065 // then either neighbouring sector answers: an end escaping straight up
4066 // from an abscissa the map leaves free is a ray or a line that the free
4067 // stretch itself passes through, so the two sides of it are one face.
4068 [[nodiscard]] FaceId faceAboveStrip(const Arrangement& arrangement,
4069 const WorkNumber& abscissa) const {
4070 const auto beforeStrip = [&](std::uint32_t halfedge) {
4071 const FanDirection end = arrangement.fanDirection(halfedge);
4072 if (directionHalf(end) != 0) {
4073 return false;
4074 }
4075 if (end.dx != NumberType(0)) {
4076 return end.dx > NumberType(0);
4077 }
4078 return abscissa < WorkNumber(end.anchor.x());
4079 };
4080 // The fan at infinity runs against the plain fan, so the ends
4081 // before the strip are exactly its prefix.
4082 const auto after = std::partition_point(
4083 arrangement.infinityFan_.begin(), arrangement.infinityFan_.end(),
4084 [&](std::uint32_t halfedge) { return !beforeStrip(halfedge); });
4085 const std::uint32_t nextDirection =
4086 after == arrangement.infinityFan_.end() ? arrangement.infinityFan_.front() : *after;
4087 return arrangement.face(HalfedgeId(nextDirection ^ 1));
4088 }
4089
4090 // The face of a trapezoid with no curve above and none below. Nothing
4091 // meets the vertical line through it, so the whole line above it is
4092 // free of edges but for the vertices standing on it, and the face is
4093 // the one that line runs into at the top.
4094 //
4095 // Where the shear was a number that face was always the outside of the
4096 // map. It is still an unbounded face here, but no longer always the
4097 // same one: an infinitesimal shear leaves a vertical line spanning one
4098 // abscissa, so the trapezoids on either side of one are free of curves
4099 // and yet lie in the two faces it separates.
4100 [[nodiscard]] FaceId emptyFace(const Arrangement& arrangement,
4101 const Trapezoid& trapezoid) const {
4102 if (!arrangement.infinity_.valid()) {
4103 return FaceId(0);
4104 }
4105 return faceAboveStrip(arrangement, sample(trapezoid.left, trapezoid.right).a);
4106 }
4107
4108 void labelTrapezoids(const Arrangement& arrangement) {
4109 for (Trapezoid& trapezoid : trapezoids_) {
4110 if (!trapezoid.active) {
4111 continue;
4112 }
4113 if (trapezoid.bottom != none) {
4114 trapezoid.face = arrangement.face(curves_[trapezoid.bottom].leftToRight);
4115 if (trapezoid.top != none) {
4116 assert(trapezoid.face == arrangement.face(
4117 arrangement.twin(curves_[trapezoid.top].leftToRight)));
4118 }
4119 } else if (trapezoid.top != none) {
4120 trapezoid.face = arrangement.face(
4121 arrangement.twin(curves_[trapezoid.top].leftToRight));
4122 } else {
4123 trapezoid.face = emptyFace(arrangement, trapezoid);
4124 }
4125 }
4126 }
4127
4128 std::uint32_t root_ = none;
4129 std::vector<VertexId> vertices_;
4130 std::vector<Abscissa> wallXs_; // increasing
4131 std::vector<Wall> walls_; // parallel to wallXs_
4132 std::vector<Curve> curves_;
4133 // Used only while constructing the RIC and released by the constructor:
4134 // the curves again, in the one representation sampling can divide in.
4135 std::vector<WorkLine> constructionLines_;
4136 std::vector<Trapezoid> trapezoids_;
4137 std::vector<Node> nodes_;
4138 };
4139
4140 std::vector<PointType> points_;
4141 // Approximations of points_, parallel to it, or empty where the filter would
4142 // not pay for itself. See @ref syncVertexApproximations.
4143 std::vector<detail::ApproximatePoint> vertexApproximations_;
4144 VertexId infinity_; // symbolic vertex, absent for bounded input
4145 std::vector<HalfedgeId> outgoing_; // one per vertex; invalid when isolated
4146 std::vector<std::uint32_t> origin_; // one per halfedge
4147 std::vector<std::uint32_t> next_; // one per halfedge
4148 std::vector<std::uint32_t> face_; // one per halfedge
4149 std::vector<TLabel> edgeLabel_; // one per edge
4150 std::vector<EdgeGeometry> edgeGeometry_; // finite defining geometry, one per edge
4151 std::vector<std::uint32_t> originOffset_; // one per edge, plus a closing entry
4152 std::vector<std::uint32_t> originIndex_; // input positions, grouped by edge
4153 std::vector<HalfedgeId> outerCycle_; // one per face; invalid for unbounded faces
4154 std::vector<std::uint32_t> innerOffset_; // one per face, plus a closing entry
4155 std::vector<HalfedgeId> innerCycle_; // inner cycles, grouped by face
4156 std::vector<TLabel> faceLabel_; // one per face
4157 std::vector<bool> unboundedFace_; // one per face
4158 std::vector<std::uint32_t> infinityFan_; // outgoing ends in counterclockwise order
4159 std::shared_ptr<const TrapezoidPointLocation> pointLocation_;
4160};
4161
4162template <class PointType, class TLabel>
4163template <class Q, class Fn>
4164bool Arrangement<PointType, TLabel>::visitStraightIntersecting(
4165 const Q& piece, Fn& fn, std::vector<bool>* seenVertices,
4166 std::vector<bool>* seenEdges) const {
4167 using CommonNumber = std::common_type_t<NumberType, typename Q::NumberType>;
4168 using Parameter = division_result_t<CommonNumber>;
4169
4170 // A cell the piece meets, at the parameter where it first meets it. An edge
4171 // the piece runs along from infinity has no such parameter, and sorts ahead
4172 // of everything instead.
4173 struct Event {
4174 bool negativeInfinity = false;
4175 Parameter parameter{};
4176 IntersectionId id;
4177 };
4178
4179 const Parameter ax(piece[0].x());
4180 const Parameter ay(piece[0].y());
4181 const Parameter dx = Parameter(piece[1].x()) - ax;
4182 const Parameter dy = Parameter(piece[1].y()) - ay;
4183 const Parameter squaredLength = dx * dx + dy * dy;
4184
4185 // Where a point falls along the piece, as the parameter that is 0 at its
4186 // first endpoint and 1 at its second. A degenerate piece puts everything
4187 // at 0, which is the only parameter it has.
4188 const auto parameterOf = [&](const auto& point) {
4189 if (squaredLength == Parameter(0)) {
4190 return Parameter(0);
4191 }
4192 return ((Parameter(point.x()) - ax) * dx +
4193 (Parameter(point.y()) - ay) * dy) /
4194 squaredLength;
4195 };
4196
4197 // The parameter at which the piece first meets an edge. Crossing edges meet
4198 // at one parameter; a collinear edge is met along a stretch, and the earliest
4199 // of it is what counts. A query bounded at its start never reports earlier
4200 // than that start.
4201 const auto firstOnEdge = [&](const EdgeGeometry& geometry) {
4202 Event event;
4203 if (squaredLength == Parameter(0)) {
4204 return event;
4205 }
4206
4207 const Parameter ex = Parameter(geometry.b.x()) - Parameter(geometry.a.x());
4208 const Parameter ey = Parameter(geometry.b.y()) - Parameter(geometry.a.y());
4209 Parameter denominator = dx * ey - dy * ex;
4210 if (denominator != Parameter(0)) {
4211 Parameter numerator =
4212 (Parameter(geometry.a.x()) - ax) * ey -
4213 (Parameter(geometry.a.y()) - ay) * ex;
4214 event.parameter = numerator / denominator;
4215 return event;
4216 }
4217
4218 if (geometry.kind == EdgeKind::line) {
4219 event.negativeInfinity = true;
4220 } else if (geometry.kind == EdgeKind::ray && ex * dx + ey * dy < Parameter(0)) {
4221 event.negativeInfinity = true;
4222 } else {
4223 event.parameter = parameterOf(geometry.a);
4224 if (geometry.kind == EdgeKind::segment) {
4225 event.parameter = std::min(event.parameter, parameterOf(geometry.b));
4226 }
4227 }
4228
4229 if constexpr (!OrientedLineConcept<Q>) {
4230 if (event.negativeInfinity || event.parameter < Parameter(0)) {
4231 event.negativeInfinity = false;
4232 event.parameter = Parameter(0);
4233 }
4234 }
4235 return event;
4236 };
4237
4238 // Order along the piece, breaking a tie towards the vertex, since a cell the
4239 // piece meets at the same parameter as an edge is the edge's endpoint and
4240 // stands for the contact. Handle order settles the rest, so the traversal is
4241 // reproducible.
4242 const auto eventLess = [](const Event& left, const Event& right) {
4243 if (left.negativeInfinity != right.negativeInfinity) {
4244 return left.negativeInfinity;
4245 }
4246 if (!left.negativeInfinity && left.parameter != right.parameter) {
4247 return left.parameter < right.parameter;
4248 }
4249 const bool leftVertex = std::holds_alternative<VertexId>(left.id);
4250 const bool rightVertex = std::holds_alternative<VertexId>(right.id);
4251 if (leftVertex != rightVertex) {
4252 return leftVertex;
4253 }
4254 return std::visit([](const auto& id) { return id.index(); }, left.id) <
4255 std::visit([](const auto& id) { return id.index(); }, right.id);
4256 };
4257
4258 // The abscissae the piece can reach, open on a side the piece runs off to.
4259 std::optional<Parameter> lowX;
4260 std::optional<Parameter> highX;
4261 if (Parameter(piece[0].x()) == Parameter(piece[1].x())) {
4262 lowX = Parameter(piece[0].x());
4263 highX = lowX;
4264 } else if constexpr (RayConcept<Q>) {
4265 (Parameter(piece[1].x()) > Parameter(piece[0].x()) ? lowX : highX) =
4266 Parameter(piece[0].x());
4267 } else if constexpr (!OrientedLineConcept<Q>) {
4268 lowX = std::min(Parameter(piece[0].x()), Parameter(piece[1].x()));
4269 highX = std::max(Parameter(piece[0].x()), Parameter(piece[1].x()));
4270 }
4271
4272 // Events wait here until the search has passed them, earliest on top.
4273 const auto laterFirst = [&eventLess](const Event& left, const Event& right) {
4274 return eventLess(right, left);
4275 };
4276 std::vector<Event> queued;
4277 const auto queue = [&](Event event) {
4278 queued.push_back(std::move(event));
4279 std::push_heap(queued.begin(), queued.end(), laterFirst);
4280 };
4281
4282 // Queues a vertex the piece passes through, unless an earlier piece of the
4283 // same chain already reported it.
4284 const auto offerVertex = [&](VertexId v) {
4285 if (seenVertices != nullptr && (*seenVertices)[v.index()]) {
4286 return;
4287 }
4288 const PointType& point = points_[v.index()];
4289 if (piece.intersects(point)) {
4290 queue(Event{false, parameterOf(point), v});
4291 }
4292 };
4293
4294 // The walk can reach one edge from several nodes. The offers it has already
4295 // made live in a table sized to the offers themselves rather than to the
4296 // arrangement, so a query never pays for the edges it does not look at.
4297 static constexpr std::uint32_t noEdge = ~std::uint32_t{};
4298 std::vector<std::uint32_t> offered(16, noEdge);
4299 std::size_t offeredCount = 0;
4300 // Open addressing with linear probing; the table always keeps a free slot,
4301 // so the scan terminates.
4302 const auto slotOf = [](const std::vector<std::uint32_t>& table, std::uint32_t edge) {
4303 std::size_t slot = (edge * 2654435761U) & (table.size() - 1);
4304 while (table[slot] != noEdge && table[slot] != edge) {
4305 slot = (slot + 1) & (table.size() - 1);
4306 }
4307 return slot;
4308 };
4309 const auto offeredBefore = [&](std::uint32_t edge) {
4310 if (2 * (offeredCount + 1) > offered.size()) {
4311 std::vector<std::uint32_t> grown(2 * offered.size(), noEdge);
4312 for (const std::uint32_t held : offered) {
4313 if (held != noEdge) {
4314 grown[slotOf(grown, held)] = held;
4315 }
4316 }
4317 offered.swap(grown);
4318 }
4319 const std::size_t slot = slotOf(offered, edge);
4320 if (offered[slot] == edge) {
4321 return true;
4322 }
4323 offered[slot] = edge;
4324 ++offeredCount;
4325 return false;
4326 };
4327
4328 // Queues an edge the piece crosses, given a candidate the search turned up.
4329 // Candidates are a superset, so the exact predicate decides here.
4330 const auto offerEdge = [&](HalfedgeId h) {
4331 const std::size_t edge = h.index() / 2;
4332 if (offeredBefore(static_cast<std::uint32_t>(edge))) {
4333 return;
4334 }
4335 if (seenEdges != nullptr && (*seenEdges)[edge]) {
4336 return;
4337 }
4338 const EdgeGeometry& geometry = edgeGeometry_[edge];
4339
4340 // Where this piece meets a finite endpoint, the vertex there is the
4341 // whole of the contact and represents it. Another piece meeting the
4342 // same edge away from its endpoints still reports the edge.
4343 if (piece.intersects(geometry.a) ||
4344 (geometry.kind == EdgeKind::segment && piece.intersects(geometry.b))) {
4345 return;
4346 }
4347 const bool intersects = std::visit(
4348 [&](const auto& value) { return piece.intersects(value); }, (*this)[h]);
4349 if (!intersects) {
4350 return;
4351 }
4352 Event event = firstOnEdge(geometry);
4353 event.id = h;
4354 queue(std::move(event));
4355 };
4356
4357 // Records a cell as reported and answers whether it still needs reporting,
4358 // which is how a chain avoids naming the same cell from two of its pieces.
4359 const auto markSeen = [&](const IntersectionId& id) {
4360 if (const auto* v = std::get_if<VertexId>(&id)) {
4361 if (seenVertices != nullptr) {
4362 if ((*seenVertices)[v->index()]) {
4363 return false;
4364 }
4365 (*seenVertices)[v->index()] = true;
4366 }
4367 } else if (seenEdges != nullptr) {
4368 const std::size_t edge = std::get<HalfedgeId>(id).index() / 2;
4369 if ((*seenEdges)[edge]) {
4370 return false;
4371 }
4372 (*seenEdges)[edge] = true;
4373 }
4374 return true;
4375 };
4376
4377 // Hands over the events the search can no longer precede: those strictly
4378 // ahead of the frontier it has reached, or all of them once it is over.
4379 // Events tying with the frontier wait, since a candidate still to come may
4380 // share their parameter and order before them.
4381 const auto release = [&](const Parameter* frontier, bool exhausted) {
4382 while (!queued.empty()) {
4383 if (!exhausted) {
4384 const Event& next = queued.front();
4385 if (frontier == nullptr ||
4386 (!next.negativeInfinity && !(next.parameter < *frontier))) {
4387 break;
4388 }
4389 }
4390 std::pop_heap(queued.begin(), queued.end(), laterFirst);
4391 Event event = std::move(queued.back());
4392 queued.pop_back();
4393 if (markSeen(event.id) && detail::invokeVisitor(fn, event.id)) {
4394 return true;
4395 }
4396 }
4397 return false;
4398 };
4399
4400 if (pointLocation_) {
4401 pointLocation_->visitCandidateVertices(*this, lowX, highX, offerVertex);
4402 // The walk reports earliest first, so a visitor that stops on its first
4403 // answer stops the search with it instead of running the query out.
4404 if (pointLocation_->template visitCandidateHalfedges<Parameter>(
4405 piece, [&](HalfedgeId h, const Parameter* frontier) {
4406 if (h.valid()) {
4407 offerEdge(h);
4408 }
4409 return release(frontier, /*exhausted=*/false);
4410 })) {
4411 return true;
4412 }
4413 } else {
4414 for (std::size_t i = 0; i < points_.size(); ++i) {
4415 offerVertex(VertexId(static_cast<std::uint32_t>(i)));
4416 }
4417 for (std::size_t edge = 0; edge < edgeGeometry_.size(); ++edge) {
4418 offerEdge(HalfedgeId(static_cast<std::uint32_t>(2 * edge)));
4419 }
4420 }
4421 return release(nullptr, /*exhausted=*/true);
4422}
4423
4424template <class PointType, class TLabel>
4425template <PointConcept Q, class Fn>
4426bool Arrangement<PointType, TLabel>::visitPointIntersecting(
4427 const Q& point, Fn& fn, std::vector<bool>* seenVertices,
4428 std::vector<bool>* seenEdges) const {
4429 for (std::size_t i = 0; i < points_.size(); ++i) {
4430 const VertexId v = pointLocation_ ? pointLocation_->indexedVertex(i)
4431 : VertexId(static_cast<std::uint32_t>(i));
4432 if ((seenVertices == nullptr || !(*seenVertices)[v.index()]) &&
4433 points_[v.index()].intersects(point)) {
4434 if (seenVertices != nullptr) {
4435 (*seenVertices)[v.index()] = true;
4436 }
4437 return detail::invokeVisitor(fn, IntersectionId(v));
4438 }
4439 }
4440
4441 for (std::size_t i = 0; i < edgeGeometry_.size(); ++i) {
4442 const HalfedgeId h = pointLocation_
4443 ? pointLocation_->indexedHalfedge(i)
4444 : HalfedgeId(static_cast<std::uint32_t>(2 * i));
4445 const std::size_t edge = h.index() / 2;
4446 if (seenEdges != nullptr && (*seenEdges)[edge]) {
4447 continue;
4448 }
4449 const EdgeGeometry& geometry = edgeGeometry_[edge];
4450 if (point.intersects(geometry.a) ||
4451 (geometry.kind == EdgeKind::segment && point.intersects(geometry.b))) {
4452 continue;
4453 }
4454 if (!std::visit([&](const auto& value) { return point.intersects(value); },
4455 (*this)[h])) {
4456 continue;
4457 }
4458 if (seenEdges != nullptr) {
4459 (*seenEdges)[edge] = true;
4460 }
4461 if (detail::invokeVisitor(fn, IntersectionId(h))) {
4462 return true;
4463 }
4464 }
4465 return false;
4466}
4467
4468template <class PointType, class TLabel>
4469template <class Q, class Fn>
4470 requires (OrientedSegmentConcept<Q> || OrientedLineConcept<Q> ||
4471 RayConcept<Q> || MonotoneChainConcept<Q> || PolylineConcept<Q>)
4474 if (r.empty()) {
4475 return false;
4476 }
4477 std::vector<bool> seenVertices(points_.size(), false);
4478 std::vector<bool> seenEdges(edgeGeometry_.size(), false);
4479 if (r.size() == 1) {
4480 return visitPointIntersecting(r[0], fn, &seenVertices, &seenEdges);
4481 }
4482 for (const auto& edge : r.orientedEdgesView()) {
4483 if (visitStraightIntersecting(edge, fn, &seenVertices, &seenEdges)) {
4484 return true;
4485 }
4486 }
4487 return false;
4488 } else {
4489 return visitStraightIntersecting(r, fn, nullptr, nullptr);
4490 }
4491}
4492
4493template <class PointType, class TLabel>
4495 // Stable across runs: callers wanting another insertion order can use the
4496 // generator-taking overload.
4497 std::mt19937 generator(0x50474cU); // "PGL"
4498 buildPointLocation(generator);
4499}
4500
4501template <class PointType, class TLabel>
4502template <class UniformRandomBitGenerator>
4504 UniformRandomBitGenerator&& generator) {
4505 if (!pointLocation_) {
4506 pointLocation_ = std::make_shared<const TrapezoidPointLocation>(
4507 *this, std::forward<UniformRandomBitGenerator>(generator));
4508 }
4509}
4510
4511template <class PointType, class TLabel>
4514 return pointLocation_ ? pointLocation_->locateFace(*this, point) : locateFaceLinear(point);
4515}
4516
4517template <class PointType, class TLabel>
4520 return pointLocation_ ? pointLocation_->locateCell(*this, point) : locateCellLinear(point);
4521}
4522
4523template <TriangleConcept TriangleType, SegmentConcept SegmentType>
4527 using Result = Arrangement<PointType, TriId>;
4528
4529 // Do not pass SegmentType directly: Result uses TriId for both edge and
4530 // face labels, whereas SegmentType may carry an unrelated edge label.
4531 std::vector<Segment<PointType>> meshEdges;
4532 meshEdges.reserve(numEdges());
4533 visitEdges([&](const SegmentType& edge) { meshEdges.emplace_back(edge[0], edge[1]); });
4534
4535 Result result(meshEdges);
4536 for (std::size_t i = 0; i < result.faceCount(); ++i) {
4537 const typename Result::FaceId face(static_cast<std::uint32_t>(i));
4538 if (result.isUnbounded(face)) {
4539 continue;
4540 }
4541 const TriId triangle = locateId(result.witness(face));
4542 if (triangle.valid()) {
4543 result.label(face) = triangle;
4544 }
4545 }
4546 return result;
4547}
4548
4549template <TriangleConcept TriangleType, SegmentConcept SegmentType>
4550template <class ResultNumber>
4553 assert(!empty() && "Triangulation::voronoiDiagram requires a nonempty triangulation");
4554
4555 using ResultPoint = Point<ResultNumber>;
4557
4558 // One exact circumcenter per current real triangle. This deliberately uses
4559 // the internal convex-hull triangulation, including any triangles hidden by
4560 // a carved domain: the method's precondition says that this whole current
4561 // connectivity is the Delaunay triangulation of the stored sites.
4562 std::vector<ResultPoint> centers(static_cast<std::size_t>(firstGhost_));
4563 for (TriIndex t = 0; t < firstGhost_; ++t) {
4564 centers[static_cast<std::size_t>(t)] = ResultPoint(
4565 triangleValue(t).circumcircle().template center<ResultNumber>());
4566 }
4567
4568 std::vector<Shape<ResultPoint>> dualEdges;
4569 dualEdges.reserve(segToEdge_.size());
4570 for (TriIndex t = 0; t < firstGhost_; ++t) {
4571 const Tri& triangle = triangles_[static_cast<std::size_t>(t)];
4572 for (int side = 0; side < 3; ++side) {
4573 const TriIndex neighbor = triangle.nbr[static_cast<std::size_t>(side)];
4574 if (!isGhost(neighbor)) {
4575 // Each interior primal edge is visited from both incident
4576 // triangles. Emit its dual only from the lower triangle id.
4577 if (neighbor < t) {
4578 continue;
4579 }
4580 const ResultPoint& a = centers[static_cast<std::size_t>(t)];
4581 const ResultPoint& b = centers[static_cast<std::size_t>(neighbor)];
4582 if (a != b) {
4583 dualEdges.emplace_back(Segment<ResultPoint>(a, b));
4584 }
4585 continue;
4586 }
4587
4588 // A real triangle is counterclockwise, so its directed side
4589 // a -> b has the hull interior on its left. Rotating b-a clockwise
4590 // therefore points out of the hull and orients the dual ray.
4591 const PointType& a = vertices_[static_cast<std::size_t>(
4592 triangle.v[static_cast<std::size_t>((side + 1) % 3)])];
4593 const PointType& b = vertices_[static_cast<std::size_t>(
4594 triangle.v[static_cast<std::size_t>((side + 2) % 3)])];
4595 const ResultNumber dx = detail::asNumber<ResultNumber>(b.x()) -
4596 detail::asNumber<ResultNumber>(a.x());
4597 const ResultNumber dy = detail::asNumber<ResultNumber>(b.y()) -
4598 detail::asNumber<ResultNumber>(a.y());
4599 const ResultPoint& center = centers[static_cast<std::size_t>(t)];
4600 dualEdges.emplace_back(
4601 Ray<ResultPoint>(center, center + ResultPoint(dy, -dx)));
4602 }
4603 }
4604
4605 Diagram diagram(dualEdges);
4606
4607 // Site points are strictly inside their own cells. Build the logarithmic
4608 // point-location index only for this attribution pass, then release it so
4609 // the returned Arrangement follows the usual opt-in indexing contract.
4610 diagram.buildPointLocation();
4611 for (VertexIndex vertex = 1; vertex < static_cast<VertexIndex>(vertices_.size()); ++vertex) {
4612 const auto face = diagram.locateFace(ResultPoint(vertices_[static_cast<std::size_t>(vertex)]));
4613 diagram.label(face) = vertices_[static_cast<std::size_t>(vertex)];
4614 }
4615 diagram.clearPointLocation();
4616 return diagram;
4617}
4618
4619// No deduction guide, deliberately: the vertex type cannot be read off the
4620// input, because the input's own type is usually the wrong answer. Two integral
4621// segments cross at a rational point, so a guide reading the type off the input
4622// would silently pick a vertex type that cannot hold the vertices it is about to
4623// compute. What `Arrangement(shapes)` deduces instead is the default vertex
4624// type, which is exact whatever the input is; a caller wanting another one, or
4625// wanting the edges to carry the input's labels, names it.
4626
4627} // namespace pgl
The planar subdivision induced by a set of one-dimensional shapes.
Definition arrangement.hpp:171
const PointType & operator[](VertexId v) const
Returns the position of a vertex.
Definition arrangement.hpp:403
HalfedgeId outerCycle(FaceId f) const
Returns a halfedge of the face's outer boundary cycle, which runs counterclockwise,...
Definition arrangement.hpp:891
OrientedLine< PointType, TLabel > OrientedLineType
Oriented-line alternative returned for a halfedge.
Definition arrangement.hpp:202
std::span< const HalfedgeId > innerCycles(FaceId f) const
Returns one halfedge of each of the face's inner boundary cycles, each of which runs clockwise.
Definition arrangement.hpp:906
HalfedgeType operator[](HalfedgeId h) const
Returns the geometry of a halfedge, carrying the edge label.
Definition arrangement.hpp:419
std::size_t edgeCount() const
Returns the number of edges, i.e. half the number of halfedges.
Definition arrangement.hpp:325
TLabel LabelType
Label type carried by edges and faces.
Definition arrangement.hpp:194
Ray< PointType, TLabel > RayType
Ray alternative returned for a halfedge.
Definition arrangement.hpp:204
OrientedSegment< PointType, TLabel > OrientedSegmentType
Type returned for a halfedge.
Definition arrangement.hpp:200
bool hasPointLocation() const noexcept
Tells whether locateFace currently uses the point-location index.
Definition arrangement.hpp:1220
Segment< PointType, TLabel > SegmentType
Segment alternative returned for a bounded edge.
Definition arrangement.hpp:196
std::vector< SegmentType > boundedEdges() const
Returns the bounded edges, omitting every ray and line.
Definition arrangement.hpp:346
PolygonWithHoles< Point< ResultNumber > > polygonWithHoles(FaceId f) const
Returns the closure of a bounded face as a region.
Definition arrangement.hpp:1005
CellId locateCell(const PointType &p) const
Returns the vertex, edge, or face containing a point.
Definition arrangement.hpp:4519
std::variant< HalfedgeId, VertexId > IntersectionId
Vertex or edge met by a directed intersection query.
Definition arrangement.hpp:210
const TLabel & label(FaceId f) const
Returns the label of a face.
Definition arrangement.hpp:1131
const std::vector< PointType > & vertices() const
Returns the position of every finite vertex, in VertexId index order.
Definition arrangement.hpp:341
typename PointType::NumberType NumberType
Coordinate type of the vertices.
Definition arrangement.hpp:192
detail::Handle< FaceTag > FaceId
Handle of a face of this arrangement specialization.
Definition arrangement.hpp:185
bool hasSimpleBoundary(FaceId f) const
Tells whether a face's boundary is a single simple ring: no hole, and no edge with the face on both s...
Definition arrangement.hpp:686
std::vector< IntersectionId > reportIntersecting(const Q &r) const
Returns every vertex and edge met by a directed curve, in order.
Definition arrangement.hpp:1278
detail::Handle< VertexTag > VertexId
Handle of a vertex of this arrangement specialization.
Definition arrangement.hpp:181
Arrangement()
Creates the empty arrangement: no cell but the unbounded face.
Definition arrangement.hpp:215
VertexId source(HalfedgeId h) const
Returns the vertex a halfedge leaves.
Definition arrangement.hpp:476
Arrangement(const ShapeRange &shapes, const PointRange &points)
Builds the arrangement of a range of shapes together with a range of points.
Definition arrangement.hpp:300
std::optional< IntersectionId > firstIntersecting(const Q &r) const
Returns the first vertex or edge met by a directed curve.
Definition arrangement.hpp:1288
bool isUnbounded() const
Tells whether the arrangement contains an unbounded edge.
Definition arrangement.hpp:849
std::size_t faceCount() const
Returns the number of faces, including every unbounded face.
Definition arrangement.hpp:330
const TLabel & label(HalfedgeId h) const
Returns the label an edge inherited from the input shape that produced it.
Definition arrangement.hpp:1111
Arrangement(const ShapeRange &shapes, detail::SimpleBoundariesTag)
Definition arrangement.hpp:273
Graph< VertexId > asGraph() const
Returns the vertex-edge incidence structure as a Graph.
Definition arrangement.hpp:588
bool isFictitious(VertexId v) const
Tells whether a vertex is the symbolic point at infinity.
Definition arrangement.hpp:880
Point< ResultNumber > witness(FaceId f) const
Returns a point strictly inside a bounded face.
Definition arrangement.hpp:663
std::vector< EdgeType > edges() const
Returns every edge as a segment, line, or ray.
Definition arrangement.hpp:369
TLabel & label(HalfedgeId h)
Returns the mutable label of an edge.
Definition arrangement.hpp:1117
std::size_t halfedgeCount() const
Returns the number of halfedges: always even, twins being adjacent.
Definition arrangement.hpp:320
std::vector< std::vector< HalfedgeId > > innerBoundariesOf(FaceId f) const
Returns the halfedges of every clockwise inner boundary cycle.
Definition arrangement.hpp:975
void clearPointLocation() noexcept
Releases this arrangement's reference to its point-location index.
Definition arrangement.hpp:1215
TLabel & label(FaceId f)
Returns the mutable label of a face.
Definition arrangement.hpp:1137
bool isUnbounded(HalfedgeId h) const
Tells whether a halfedge is adjacent to the symbolic vertex at infinity.
Definition arrangement.hpp:863
detail::Handle< HalfedgeTag > HalfedgeId
Handle of a halfedge of this arrangement specialization.
Definition arrangement.hpp:183
std::span< const std::uint32_t > originsOf(HalfedgeId h) const
Returns the positions, in the range the arrangement was built from, of every input shape that produce...
Definition arrangement.hpp:1151
FaceId locateFace(const PointType &p) const
Returns the face containing a point.
Definition arrangement.hpp:4513
std::vector< std::uint32_t > originsOf(VertexId v) const
Returns the positions, in the range the arrangement was built from, of every input shape passing thro...
Definition arrangement.hpp:1169
std::variant< SegmentType, LineType, RayType > EdgeType
Geometry of an unoriented edge.
Definition arrangement.hpp:208
Arrangement(const ShapeRange &shapes)
Builds the arrangement of a range of shapes.
Definition arrangement.hpp:262
std::size_t vertexCount() const
Returns the number of finite vertices.
Definition arrangement.hpp:315
bool isUnbounded(FaceId f) const
Tells whether a face is unbounded.
Definition arrangement.hpp:874
std::variant< OrientedSegmentType, OrientedLineType, RayType > HalfedgeType
Geometry of an oriented halfedge.
Definition arrangement.hpp:206
bool emptyIntersecting(const Q &r) const
Returns whether a directed curve meets no vertex or edge.
Definition arrangement.hpp:1301
HalfplaneIntersection< Point< ResultNumber > > halfplaneIntersection(FaceId f) const
Returns the outer boundary constraints of a face as a half-plane intersection, ignoring holes.
Definition arrangement.hpp:1057
VertexId target(HalfedgeId h) const
Returns the vertex a halfedge arrives at.
Definition arrangement.hpp:486
std::variant< VertexId, HalfedgeId, FaceId > CellId
Handle of the arrangement cell containing a finite query point.
Definition arrangement.hpp:187
FaceId face(HalfedgeId h) const
Returns the face to the left of a halfedge.
Definition arrangement.hpp:495
std::vector< HalfedgeId > boundaryOf(FaceId f) const
Returns every halfedge bounding a face, outer cycle first.
Definition arrangement.hpp:924
std::size_t degree(VertexId v) const
Returns the number of halfedges leaving a vertex.
Definition arrangement.hpp:526
HalfedgeId outgoing(VertexId v) const
Returns one halfedge leaving a vertex, or the invalid handle when the vertex is isolated.
Definition arrangement.hpp:511
HalfedgeId next(HalfedgeId h) const
Returns the next halfedge along the boundary of the face on the left.
Definition arrangement.hpp:466
std::vector< HalfedgeId > outgoingHalfedges(VertexId v) const
Returns every halfedge leaving a vertex, in clockwise order.
Definition arrangement.hpp:550
PointType_ PointType
Vertex type.
Definition arrangement.hpp:190
Point< ResultNumber > witness(HalfedgeId h) const
Returns the midpoint of an edge, which lies in its relative interior.
Definition arrangement.hpp:623
Line< PointType, TLabel > LineType
Line alternative returned for an edge.
Definition arrangement.hpp:198
HalfedgeId twin(HalfedgeId h) const
Returns the halfedge running along the same edge the other way.
Definition arrangement.hpp:456
bool visitIntersecting(const Q &r, Fn fn) const
Visits the vertices and edges met by a directed curve, in order.
Definition arrangement.hpp:4472
Point< ResultNumber > witness(VertexId v) const
Returns the vertex itself, as the witness of a zero-dimensional cell.
Definition arrangement.hpp:610
void buildPointLocation()
Builds the randomized trapezoidal point-location index.
Definition arrangement.hpp:4494
std::vector< HalfedgeId > outerBoundaryOf(FaceId f) const
Returns the halfedges of a face's counterclockwise outer boundary.
Definition arrangement.hpp:951
Undirected simple graph stored as adjacency sets.
Definition graph.hpp:38
void addVertex(const Vertex &vertex)
Adds a vertex if it is not already present.
Definition graph.hpp:213
void addEdge(const Vertex &u, const Vertex &v)
Adds an undirected edge and its endpoints.
Definition graph.hpp:225
Definition forward.hpp:320
Definition forward.hpp:310
Definition forward.hpp:308
Definition forward.hpp:321
Definition forward.hpp:311
bool operator==(const pgl::int128 &a, double b)
Definition numeric.hpp:156
Definition arrangement.hpp:67
HalfplaneIntersection() -> HalfplaneIntersection< Point<>, NoLabel >
Definition halfplaneintersection.hpp:2308
@ 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
Line() -> Line< Point<>, NoLabel >
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
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
constexpr std::partial_ordering crossSign(const Point< UNumber, ULabel > &u, const Point< VNumber, VLabel > &v)
Classifies the turn from one vector to another.
Definition orientation.hpp:583
typename rational_int< T >::type rational_int_t
Definition rational.hpp:61
Segment() -> Segment< Point<>, NoLabel >
Triangle() -> Triangle< Point<>, NoLabel >
Definition triangle.hpp:2029
Intersection of closed half-planes; convex but possibly unbounded or empty.
Definition halfplaneintersection.hpp:244
Closed half-plane defined by an oriented boundary line.
Definition halfplane.hpp:51
Unoriented infinite line.
Definition line.hpp:52
Directed infinite line with left/right side semantics plus optional line label.
Definition orientedline.hpp:53
constexpr A & label() const
Returns the line label.
Definition orientedline.hpp:305
Directed segment preserving source-to-target order plus optional segment label.
Definition orientedsegment.hpp:44
constexpr A & label() const
Returns the segment label.
Definition orientedsegment.hpp:302
Two-dimensional point with optional label payload.
Definition point.hpp:129
constexpr std::ptrdiff_t index(const NumberType &value) const
Returns the smallest index i with (*this)[i] == value, or -1 if no coordinate equals value.
Definition point.hpp:267
Closed region bounded by one outer simple polygon minus disjoint polygonal holes.
Definition polygonwithholes.hpp:89
Closed simple polygon stored by its vertices.
Definition polygon.hpp:59
Half-infinite line starting from one source point plus optional ray label.
Definition ray.hpp:51
constexpr A & label() const
Returns the ray label.
Definition ray.hpp:303
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
bool visitEdges(Fn fn) const
Calls fn(Segment) on every edge, with its stored label.
Definition triangulation.hpp:823
typename TriangleType::PointType PointType
Definition triangulation.hpp:176
bool empty() const
True if the triangulation stores no in-domain triangles.
Definition triangulation.hpp:695
std::size_t numEdges() const
Number of undirected edges incident to the visible triangulation.
Definition triangulation.hpp:673
Arrangement< PointType, TriId > asArrangement() const
Returns the visible mesh as an arrangement, labeling each triangle face by its ID.
Definition arrangement.hpp:4526
SegmentType_ SegmentType
Definition triangulation.hpp:175
detail::Handle< TriTag > TriId
Handle of a triangle of this triangulation specialization.
Definition triangulation.hpp:183
TriId locateId(const PointType &p) const
Finds the triangle containing the query point, as a handle.
Definition triangulation.hpp:1037
Arrangement< Point< ResultNumber >, PointType > voronoiDiagram() const
Returns the Voronoi diagram dual to this Delaunay triangulation.
Mutable triangulation of a point set or simple polygon.