Pangolin
Header-only C++20 plane computational geometry library
Loading...
Searching...
No Matches
triangulation.hpp
Go to the documentation of this file.
1#pragma once
2
4
40
41#include <algorithm>
42#include <array>
43#include <cassert>
44#include <cstddef>
45#include <cstdint>
46#include <deque>
47#include <limits>
48#include <map>
49#include <memory>
50#include <optional>
51#include <queue>
52#include <random>
53#include <set>
54#include <type_traits>
55#include <unordered_map>
56#include <unordered_set>
57#include <utility>
58#include <variant>
59#include <vector>
60
61namespace pgl {
62
63namespace detail {
64// The entity's LabelType, or NoLabel if it has none. Lets the triangulation
65// pick up a triangle label the moment pgl::Triangle exposes one, without
66// breaking while it still has none.
67template <class T, class = void>
68struct optional_label {
69 using type = NoLabel;
70};
71template <class T>
72struct optional_label<T, std::void_t<typename T::LabelType>> {
73 using type = typename T::LabelType;
74};
75template <class T>
76using optional_label_t = typename optional_label<T>::type;
77
78// A directed or undirected segment. The triangulation's traversal queries
79// accept either, tracing from endpoint [0] to endpoint [1] (for an oriented
80// segment that is source -> target; for a plain segment, its sorted endpoints).
81template <class T>
82concept SegmentOrOriented = SegmentConcept<T> || OrientedSegmentConcept<T>;
83
84// A doubly-infinite straight query: a line or an oriented line. It is traced in
85// order by the same directed walk as a segment — the only differences are that
86// it enters the hull at a ghost found by a directional descent (rather than at a
87// finite source) and never stops at a finite target, so it runs until it leaves
88// the hull. An oriented line keeps its own direction; a plain line's order
89// follows its (arbitrary) defining-point direction.
90template <class T>
91concept LineOrOriented = LineConcept<T> || OrientedLineConcept<T>;
92
93// Queries traced in order by the directed walk of visitTrianglesIntersecting:
94// segments, oriented segments, lines, oriented lines, and rays. A ray keeps a
95// finite source (entered like a segment, or at a directional ghost when the
96// source is outside the hull) but is unbounded forwards, so like a line it runs
97// until it leaves the hull instead of stopping at a target.
98template <class T>
99concept DirectedTraversal = SegmentOrOriented<T> || LineOrOriented<T> || RayConcept<T>;
100
101// A connected convex query shape accepted by the region-traversal overloads of
102// visitTrianglesIntersecting / trianglesIntersecting. Segments, oriented
103// segments, (oriented) lines, and rays are deliberately excluded — they have
104// their own directed walk. The set is convex so its intersection with the
105// (convex) triangulated hull stays connected, which is what lets a single seed
106// flood-fill find every triangle it meets. (Non-convex polygons are future work.)
107template <class T>
108concept TriangulationRegionQuery =
109 PointConcept<T> || TriangleConcept<T> || RectangleConcept<T> || ConvexConcept<T> ||
110 DiskConcept<T> || HalfplaneConcept<T>;
111
112// A polygonal chain query: a polyline or a monotone chain. It is neither straight
113// (so no single directed walk covers it) nor convex (its intersection with the
114// hull may fall apart into several arcs, so no single flood fill covers it
115// either), and it gets its own traversal: the directed segment walk run over each
116// edge in turn, in chain order.
117template <class T>
118concept ChainTraversal = PolylineConcept<T> || MonotoneChainConcept<T>;
119
120// A query shape bounded by a closed polygonal chain: a triangle, rectangle,
121// convex polygon, or (possibly non-convex) polygon. Containment of
122// such a shape in the triangulated domain reduces to containment of its boundary
123// edges: the domain is a simply connected region (a simple polygon, or the convex
124// hull), so its complement is connected — a point of the shape outside the domain
125// could be joined to infinity without ever meeting the domain, and that path has
126// to cross the shape's boundary, which is inside it. Contradiction.
127template <class T>
128concept PolygonalRegion =
129 TriangleConcept<T> || RectangleConcept<T> || ConvexConcept<T> || PolygonConcept<T>;
130
131// Any shape the triangulation's intersection queries accept: a directed-traversal
132// shape (traced in order by the directed walk), a chain (traced edge by edge, in
133// order), or a region-query shape (grown by the flood fill, unspecified order).
134// The derived wrappers — trianglesIntersecting, the interior-intersecting
135// variants, the edge variants — are all built on visitTrianglesIntersecting plus
136// a per-shape predicate, so they take this whole set; only the three
137// visitTrianglesIntersecting overloads carry distinct walks.
138template <class T>
139concept TriangulationQuery =
140 DirectedTraversal<T> || ChainTraversal<T> || TriangulationRegionQuery<T>;
141
142// Narrow access point for the paper-specific graph used by
143// Polygon::convexCovering(). Defined after Triangulation is complete.
144struct ConvexCoverBuilder;
145} // namespace detail
146
164template <TriangleConcept TriangleType_,
165 SegmentConcept SegmentType_ = typename TriangleType_::template BoundaryType<false>>
167 private:
168 // Tags that keep the two handle families distinct types, so a vertex handle
169 // can never be passed where a triangle one is meant.
170 struct VertexTag;
171 struct TriTag;
172
173 public:
174 using TriangleType = TriangleType_;
175 using SegmentType = SegmentType_;
176 using PointType = typename TriangleType::PointType;
177 using NumberType = typename PointType::NumberType;
178 using SegmentLabel = typename SegmentType::LabelType;
179 using TriangleLabel = detail::optional_label_t<TriangleType>;
181 using VertexId = detail::Handle<VertexTag>;
183 using TriId = detail::Handle<TriTag>;
184
186 Triangulation() = default;
187
201 template <class TriangleRange>
203 explicit Triangulation(const TriangleRange& tris) {
204 std::unordered_map<PointType, VertexIndex> vid;
205 const auto idOfPoint = makeVertexInterner(vid);
206 // First pass: intern every vertex so vertices_ is complete, then keep it
207 // in Hilbert order for cache locality (see the point-set constructor) and
208 // rebuild the point->id map against the reordered vertices.
209 for (const auto& tr : tris) {
210 idOfPoint(tr[0]);
211 idOfPoint(tr[1]);
212 idOfPoint(tr[2]);
213 }
214 hilbertSort(vertices_);
215 syncVertexApproximations();
216 vid.clear();
217 for (VertexIndex i = 0; i < static_cast<VertexIndex>(vertices_.size()); ++i) {
218 vid.emplace(vertices_[i], i);
219 }
220 // Second pass: resolve each triangle's vertices against the reordered ids.
221 std::vector<std::array<VertexIndex, 3>> triples;
222 std::vector<TriangleLabel> triLabels;
223 for (const auto& tr : tris) {
224 triples.push_back({vid.at(tr[0]), vid.at(tr[1]), vid.at(tr[2])});
225 triLabels.push_back(detail::copyLabel<TriangleLabel>(tr));
226 }
227 buildFromTriples(triples, triLabels);
228 }
229
243 template <class SegmentRange>
245 explicit Triangulation(const SegmentRange& segs) {
246 // Intern endpoints so vertices_ is complete, then keep it in Hilbert
247 // order for cache locality (see the point-set constructor) and rebuild
248 // the point->id map against the reordered vertices.
249 std::unordered_map<PointType, VertexIndex> vid;
250 const auto idOfPoint = makeVertexInterner(vid);
251 for (const auto& s : segs) {
252 idOfPoint(s[0]);
253 idOfPoint(s[1]);
254 }
255 hilbertSort(vertices_);
256 syncVertexApproximations();
257 vid.clear();
258 for (VertexIndex i = 0; i < static_cast<VertexIndex>(vertices_.size()); ++i) {
259 vid.emplace(vertices_[i], i);
260 }
261 // Collect the edges as vertex-id pairs against the reordered ids.
262 std::vector<std::pair<VertexIndex, VertexIndex>> elist;
263 for (const auto& s : segs) {
264 VertexIndex a = vid.at(s[0]);
265 VertexIndex b = vid.at(s[1]);
266 if (a != b) {
267 elist.emplace_back(a, b);
268 }
269 }
270 const VertexIndex n = static_cast<VertexIndex>(vertices_.size());
271
272 // Undirected adjacency, deduplicated.
273 std::vector<std::vector<VertexIndex>> adj(static_cast<std::size_t>(n));
274 std::set<std::pair<VertexIndex, VertexIndex>> seen;
275 for (auto [a, b] : elist) {
276 auto k = a < b ? std::pair{a, b} : std::pair{b, a};
277 if (seen.insert(k).second) {
278 adj[a].push_back(b);
279 adj[b].push_back(a);
280 }
281 }
282
283 // Sort each vertex's neighbors counterclockwise; record their position.
284 std::vector<std::unordered_map<VertexIndex, int>> posIn(static_cast<std::size_t>(n));
285 for (VertexIndex v = 0; v < n; ++v) {
286 std::vector<PointType> nbr;
287 nbr.reserve(adj[v].size());
288 for (VertexIndex w : adj[v]) {
289 nbr.push_back(vertices_[w]);
290 }
291 sortAround(nbr, vertices_[v]);
292 adj[v].clear();
293 for (const auto& p : nbr) {
294 adj[v].push_back(vid.at(p));
295 }
296 for (int i = 0; i < static_cast<int>(adj[v].size()); ++i) {
297 posIn[v][adj[v][i]] = i;
298 }
299 }
300
301 // next(u->v): the half-edge leaving v that keeps the face on its left,
302 // i.e. v -> (neighbor of v immediately clockwise from u). Tracing it
303 // walks each face; bounded faces come out CCW, the outer face CW.
304 const auto nextHE = [&](VertexIndex u, VertexIndex v) -> std::pair<VertexIndex, VertexIndex> {
305 const auto& a = adj[v];
306 const int deg = static_cast<int>(a.size());
307 const int pu = posIn[v].at(u);
308 return {v, a[(pu + deg - 1) % deg]};
309 };
310
311 std::vector<std::array<VertexIndex, 3>> triples;
312 std::set<std::pair<VertexIndex, VertexIndex>> visited;
313 for (VertexIndex v = 0; v < n; ++v) {
314 for (VertexIndex w : adj[v]) {
315 std::pair<VertexIndex, VertexIndex> h{v, w};
316 if (visited.count(h)) {
317 continue;
318 }
319 std::vector<VertexIndex> cycle;
320 while (visited.insert(h).second) {
321 cycle.push_back(h.first);
322 h = nextHE(h.first, h.second);
323 }
324 if (cycle.size() == 3 &&
325 orientationSign(vertices_[cycle[0]], vertices_[cycle[1]], vertices_[cycle[2]]) > 0) {
326 triples.push_back({cycle[0], cycle[1], cycle[2]});
327 }
328 }
329 }
330 buildFromTriples(triples, std::vector<TriangleLabel>(triples.size()));
331
332 // Carry each input segment's label onto its edge record.
333 if constexpr (detail::has_label_v<SegmentLabel>) {
334 for (const auto& s : segs) {
335 auto it = segmentMap().find(s);
336 if (it != segmentMap().end()) {
337 it->second.segLabel = detail::copyLabel<SegmentLabel>(s);
338 }
339 }
340 }
341 }
342
355 template <class PointRange>
357 explicit Triangulation(const PointRange& pts) {
358 std::unordered_map<PointType, VertexIndex> vid;
359 const auto idOfPoint = makeVertexInterner(vid);
360 for (const auto& p : pts) {
361 idOfPoint(PointType(p));
362 }
363 // Store the vertices in Hilbert-curve order: spatially close points then
364 // sit close together both in vertices_ and — because triangles are
365 // created in insertion order — in triangles_. That keeps the incremental
366 // build's point-location walks short (each is seeded from the previous
367 // insertion) and improves cache locality for later query walks too.
368 // Vertex order is purely internal, so this is transparent downstream.
369 hilbertSort(vertices_);
370 syncVertexApproximations();
371 auto triples = delaunayTriples(vertices_, vertexApproximations_);
372 buildFromTriples(triples, std::vector<TriangleLabel>(triples.size()));
373 }
374
395 template <class PointRange, class SegmentRange>
398 Triangulation(const PointRange& pts, const SegmentRange& segments) {
399 std::unordered_map<PointType, VertexIndex> vid;
400 const auto idOfPoint = makeVertexInterner(vid);
401 for (const auto& p : pts) {
402 idOfPoint(PointType(p));
403 }
404 for (const auto& s : segments) {
405 idOfPoint(PointType(s[0]));
406 idOfPoint(PointType(s[1]));
407 }
408 // Keep vertices_ in Hilbert order (see the point-set constructor).
409 hilbertSort(vertices_);
410 syncVertexApproximations();
411 auto triples = delaunayTriples(vertices_, vertexApproximations_);
412 buildFromTriples(triples, std::vector<TriangleLabel>(triples.size()));
413
414 // Resolve the constraint endpoints against the final ids — the
415 // interner's are stale after the reorder and the ghost prepend (see
416 // constructConstrained) — then force each segment in as a constrained
417 // edge and restore the constrained Delaunay property.
418 vid.clear();
419 for (VertexIndex i = 1; i < static_cast<VertexIndex>(vertices_.size()); ++i) {
420 vid.emplace(vertices_[i], i);
421 }
422 for (const auto& s : segments) {
423 const VertexIndex a = vid.at(PointType(s[0]));
424 const VertexIndex b = vid.at(PointType(s[1]));
425 if (a != b) {
426 insertConstraint(a, b);
427 }
428 }
429 restoreConstrainedDelaunay();
430
431 // Carry each constraint segment's label onto its edge record; the
432 // edges exist (constrained edges are never flipped away).
433 if constexpr (detail::has_label_v<SegmentLabel>) {
434 for (const auto& s : segments) {
435 auto it = segmentMap().find(SegmentType(PointType(s[0]), PointType(s[1])));
436 if (it != segmentMap().end()) {
437 it->second.segLabel = detail::copyLabel<SegmentLabel>(s);
438 }
439 }
440 }
441 }
442
463 explicit Triangulation(const Polygon<PointType>& poly) {
464 constructConstrained({poly}, std::array<PointType, 0>{}, std::array<SegmentType, 0>{});
465 }
466
472 template <class PointRange>
474 Triangulation(const Polygon<PointType>& poly, const PointRange& points) {
475 constructConstrained({poly}, points, std::array<SegmentType, 0>{});
476 }
477
484 template <class SegmentRange>
486 Triangulation(const Polygon<PointType>& poly, const SegmentRange& segments) {
487 constructConstrained({poly}, std::array<PointType, 0>{}, segments);
488 }
489
497 template <class PointRange, class SegmentRange>
500 Triangulation(const Polygon<PointType>& poly, const PointRange& points,
501 const SegmentRange& segments) {
502 constructConstrained({poly}, points, segments);
503 }
504
527 constructConstrained({region.outer()}, std::array<PointType, 0>{},
528 std::array<SegmentType, 0>{}, region.holes());
529 }
530
536 template <class PointRange>
538 Triangulation(const PolygonWithHoles<PointType>& region, const PointRange& points) {
539 constructConstrained({region.outer()}, points, std::array<SegmentType, 0>{}, region.holes());
540 }
541
548 template <class SegmentRange>
550 Triangulation(const PolygonWithHoles<PointType>& region, const SegmentRange& segments) {
551 constructConstrained({region.outer()}, std::array<PointType, 0>{}, segments, region.holes());
552 }
553
561 template <class PointRange, class SegmentRange>
564 Triangulation(const PolygonWithHoles<PointType>& region, const PointRange& points,
565 const SegmentRange& segments) {
566 constructConstrained({region.outer()}, points, segments, region.holes());
567 }
568
588 constructConstrained(setOuters(set), std::array<PointType, 0>{},
589 std::array<SegmentType, 0>{}, setHoles(set));
590 }
591
597 template <class PointRange>
599 Triangulation(const PolygonSet<PointType>& set, const PointRange& points) {
600 constructConstrained(setOuters(set), points, std::array<SegmentType, 0>{}, setHoles(set));
601 }
602
609 template <class SegmentRange>
611 Triangulation(const PolygonSet<PointType>& set, const SegmentRange& segments) {
612 constructConstrained(setOuters(set), std::array<PointType, 0>{}, segments, setHoles(set));
613 }
614
622 template <class PointRange, class SegmentRange>
625 Triangulation(const PolygonSet<PointType>& set, const PointRange& points,
626 const SegmentRange& segments) {
627 constructConstrained(setOuters(set), points, segments, setHoles(set));
628 }
629
658 template <class ResultNumber = division_result_t<NumberType>>
660
661 // ---- sizes -----------------------------------------------------------
662
664 [[nodiscard]] std::size_t numVertices() const {
665 // A nonempty vertices_ always holds the ghost vertex at index GHOST.
666 return vertices_.empty() ? 0 : vertices_.size() - 1;
667 }
668
670 [[nodiscard]] std::size_t numTriangles() const { return domainTriangleCount_; }
671
673 [[nodiscard]] std::size_t numEdges() const {
674 // Counted off the triangles rather than off the segment map, which
675 // holds exactly these edges but which a triangulation asked only for a
676 // count should not have to fill. An edge between two real triangles is
677 // reached from both and counted from the lower-numbered one; every
678 // other edge has one real side and is reached once.
679 std::size_t count = 0;
680 for (TriIndex t = 0; t < firstGhost_; ++t) {
681 for (std::int8_t s = 0; s < 3; ++s) {
682 const TriIndex other = triangles_[t].nbr[s];
683 if (other != NO_TRI && other < firstGhost_ && other < t) {
684 continue;
685 }
686 if (edgeInDomain(Edge{t, s})) {
687 ++count;
688 }
689 }
690 }
691 return count;
692 }
693
695 [[nodiscard]] bool empty() const { return domainTriangleCount_ == 0; }
696
697 // ---- membership ------------------------------------------------------
698
700 [[nodiscard]] bool has(const TriangleType& t) const { return inDomain(idOf(t)); }
701
703 [[nodiscard]] bool has(const SegmentType& s) const {
704 auto se = segmentMap().find(s);
705 return se != segmentMap().end() && edgeInDomain(se->second);
706 }
707
708 // ---- navigation ------------------------------------------------------
709
718 [[nodiscard]] std::optional<TriangleType> otherTriangle(const TriangleType& t,
719 const SegmentType& shared) const {
720 auto se = segmentMap().find(shared);
721 if (se == segmentMap().end()) {
722 return std::nullopt;
723 }
724 const TriIndex given = idOf(t);
725 if (given == NO_TRI) {
726 return std::nullopt;
727 }
728 const Edge e = se->second;
729 const TriIndex i1 = e.tri;
730 const TriIndex i2 = mirror(e).tri;
731 TriIndex other = (given == i1) ? i2 : (given == i2 ? i1 : NO_TRI);
732 if (!inDomain(other)) {
733 return std::nullopt; // shared not on t, or boundary
734 }
735 return triangleValue(other);
736 }
737
739 [[nodiscard]] std::vector<TriangleType> edgeAdjacentTriangles(const TriangleType& t) const {
740 return trianglesOf(edgeAdjacentTriangles(triHandle(idOf(t))));
741 }
742
754 [[nodiscard]] std::vector<TriangleType> vertexAdjacentTriangles(const TriangleType& t) const {
755 return trianglesOf(vertexAdjacentTriangles(triHandle(idOf(t))));
756 }
757
759 [[nodiscard]] std::vector<TriangleType> incidentTriangles(const SegmentType& s) const {
760 std::vector<TriangleType> out;
761 auto se = segmentMap().find(s);
762 if (se == segmentMap().end()) {
763 return out;
764 }
765 const Edge e = se->second;
766 if (inDomain(e.tri)) {
767 out.push_back(triangleValue(e.tri));
768 }
769 const TriIndex other = mirror(e).tri;
770 if (inDomain(other)) {
771 out.push_back(triangleValue(other));
772 }
773 return out;
774 }
775
784 [[nodiscard]] std::vector<TriangleType> incidentTriangles(const PointType& p) const {
785 return trianglesOf(incidentTriangles(getId(p)));
786 }
787
801 template <class Fn>
802 bool visitTriangles(Fn fn) const {
803 for (TriIndex t = 0; t < firstGhost_; ++t) {
804 if (!inDomain(t)) {
805 continue;
806 }
807 if (reportTriangle(fn, t)) {
808 return true;
809 }
810 }
811 return false;
812 }
813
822 template <class Fn>
823 bool visitEdges(Fn fn) const {
824 for (const auto& [seg, e] : segmentMap()) {
825 (void)seg;
826 if (!edgeInDomain(e)) {
827 continue;
828 }
829 if (detail::invokeVisitor(fn, edgeSegment(e))) {
830 return true;
831 }
832 }
833 return false;
834 }
835
837 [[nodiscard]] std::vector<TriangleType> triangles() const {
838 std::vector<TriangleType> out;
839 out.reserve(numTriangles());
840 visitTriangles([&](const TriangleType& t) { out.push_back(t); });
841 std::sort(out.begin(), out.end());
842 return out;
843 }
844
846 [[nodiscard]] std::vector<SegmentType> edges() const {
847 std::vector<SegmentType> out;
848 out.reserve(numEdges());
849 visitEdges([&](const SegmentType& s) { out.push_back(s); });
850 std::sort(out.begin(), out.end());
851 return out;
852 }
853
873 [[nodiscard]] Graph<PointType> asGraph() const {
874 Graph<PointType> result;
875 for (VertexIndex v = GHOST + 1; v < static_cast<VertexIndex>(vertices_.size()); ++v) {
876 result.addVertex(vertices_[v]);
877 }
878 visitEdges([&](const SegmentType& s) { result.addEdge(s[0], s[1]); });
879 return result;
880 }
881
894 [[nodiscard]] Arrangement<PointType, TriId> asArrangement() const;
895
933 void buildPointLocation();
934
941 void clearPointLocation() noexcept { pointLocation_.reset(); }
942
944 [[nodiscard]] bool hasPointLocation() const noexcept {
945 return static_cast<bool>(pointLocation_);
946 }
947
957 [[nodiscard]] bool hasCurrentPointLocation() const noexcept {
958 return pointLocation_ && pointLocationRevision_ == revision_;
959 }
960
961 // ---- low-level navigation --------------------------------------------
962
970 [[nodiscard]] std::vector<TriId> triangleIds() const {
971 std::vector<TriId> out;
972 out.reserve(numTriangles());
973 visitTriangles([&](TriId t) { out.push_back(t); });
974 return out;
975 }
976
984 [[nodiscard]] std::vector<VertexId> vertexIds() const {
985 std::vector<VertexId> out;
986 out.reserve(numVertices());
987 for (VertexIndex v = GHOST + 1; v < static_cast<VertexIndex>(vertices_.size()); ++v) {
988 out.push_back(vertexHandle(v));
989 }
990 return out;
991 }
992
1004 [[nodiscard]] std::size_t triangleIndexBound() const {
1005 return static_cast<std::size_t>(firstGhost_);
1006 }
1007
1018 [[nodiscard]] std::size_t vertexIndexBound() const { return vertices_.size(); }
1019
1037 [[nodiscard]] TriId locateId(const PointType& p) const {
1038 // The hierarchy lands on the triangle itself rather than beside it, so
1039 // where it does the walk has nothing left to do and is not run: what it
1040 // would return is a triangle whose closure holds p, which this is.
1041 bool answered = false;
1042 const TriIndex seed = pointLocationSeed(p, answered);
1043 if (answered) {
1044 hint_ = seed;
1045 return triHandle(inDomain(seed) ? seed : NO_TRI);
1046 }
1047 const TriIndex id = locateIndex(p, seed);
1048 return triHandle(inDomain(id) ? id : NO_TRI);
1049 }
1050
1057 template <PointConcept QueryPoint>
1058 [[nodiscard]] TriId locateId(const QueryPoint& p) const {
1059 if constexpr (std::same_as<typename QueryPoint::NumberType, NumberType> &&
1060 std::constructible_from<PointType, const QueryPoint&>) {
1061 return locateId(PointType(p));
1062 }
1063 const TriIndex id = locateIndex(p);
1064 return triHandle(inDomain(id) ? id : NO_TRI);
1065 }
1066
1073 [[nodiscard]] TriangleType getShape(TriId t) const {
1074 assert(has(t) && "getShape(): the handle is not a triangle of the triangulation");
1075 return triangleValue(indexOf(t));
1076 }
1077
1084 [[nodiscard]] const PointType& getShape(VertexId v) const {
1085 assert(has(v) && "getShape(): the handle is not a vertex of the triangulation");
1086 return vertices_[static_cast<std::size_t>(indexOf(v))];
1087 }
1088
1090 [[nodiscard]] TriangleType operator[](TriId t) const { return getShape(t); }
1091
1093 [[nodiscard]] const PointType& operator[](VertexId v) const { return getShape(v); }
1094
1106 [[nodiscard]] TriId getId(const TriangleType& t) const {
1107 const TriIndex id = idOf(t);
1108 return triHandle(inDomain(id) ? id : NO_TRI);
1109 }
1110
1120 [[nodiscard]] VertexId getId(const PointType& p) const {
1121 return vertexHandle(vertexIndexAt(p));
1122 }
1123
1125 [[nodiscard]] bool has(TriId t) const { return inDomain(indexOf(t)); }
1126
1128 [[nodiscard]] bool has(VertexId v) const { return realVertex(indexOf(v)); }
1129
1140 [[nodiscard]] std::array<VertexId, 3> vertices(TriId t) const {
1141 assert(has(t) && "vertices(): the handle is not a triangle of the triangulation");
1142 const TriIndex id = indexOf(t);
1143 const auto& v = triangles_[static_cast<std::size_t>(id)].v;
1144 const int first = firstVertex(id);
1145 return {vertexHandle(v[first]), vertexHandle(v[(first + 1) % 3]),
1146 vertexHandle(v[(first + 2) % 3])};
1147 }
1148
1163 [[nodiscard]] std::optional<TriId> otherTriangle(TriId t, int side) const {
1164 assert(has(t) && "otherTriangle(): the handle is not a triangle of the triangulation");
1165 assert(side >= 0 && side < 3 && "otherTriangle(): side is not one of 0, 1, 2");
1166 const TriIndex id = indexOf(t);
1167 const TriIndex nb = triangles_[static_cast<std::size_t>(id)].nbr[internalSide(id, side)];
1168 return inDomain(nb) ? std::optional<TriId>(triHandle(nb)) : std::nullopt;
1169 }
1170
1185 [[nodiscard]] bool isConstrained(TriId t, int side) const {
1186 assert(has(t) && "isConstrained(): the handle is not a triangle of the triangulation");
1187 assert(side >= 0 && side < 3 && "isConstrained(): side is not one of 0, 1, 2");
1188 const TriIndex id = indexOf(t);
1189 return bit(triangles_[static_cast<std::size_t>(id)].constrainedMask,
1190 internalSide(id, side));
1191 }
1192
1205 void setConstrained(TriId t, int side, bool value = true) {
1206 assert(has(t) && "setConstrained(): the handle is not a triangle of the triangulation");
1207 assert(side >= 0 && side < 3 && "setConstrained(): side is not one of 0, 1, 2");
1208 const TriIndex id = indexOf(t);
1209 const Edge e{id, static_cast<std::int8_t>(internalSide(id, side))};
1210 setBit(triangles_[static_cast<std::size_t>(id)].constrainedMask, e.side, value);
1211 const Edge m = mirror(e);
1212 if (m.tri != NO_TRI) {
1213 setBit(triangles_[static_cast<std::size_t>(m.tri)].constrainedMask, m.side, value);
1214 }
1215 }
1216
1229 [[nodiscard]] std::optional<TriId> otherTriangle(TriId t, VertexId a, VertexId b) const {
1230 const TriIndex id = indexOf(t);
1231 if (!inDomain(id)) {
1232 return std::nullopt;
1233 }
1234 const VertexIndex first = indexOf(a);
1235 const VertexIndex second = indexOf(b);
1236 const auto& v = triangles_[static_cast<std::size_t>(id)].v;
1237 for (int s = 0; s < 3; ++s) {
1238 const VertexIndex left = v[(s + 1) % 3];
1239 const VertexIndex right = v[(s + 2) % 3];
1240 if ((left == first && right == second) || (left == second && right == first)) {
1241 const TriIndex nb = triangles_[static_cast<std::size_t>(id)].nbr[s];
1242 return inDomain(nb) ? std::optional<TriId>(triHandle(nb)) : std::nullopt;
1243 }
1244 }
1245 return std::nullopt; // a b is not an edge of t
1246 }
1247
1249 [[nodiscard]] std::vector<TriId> edgeAdjacentTriangles(TriId t) const {
1250 std::vector<TriId> out;
1251 const TriIndex id = indexOf(t);
1252 if (!realTriangle(id)) {
1253 return out;
1254 }
1255 for (int s = 0; s < 3; ++s) {
1256 const TriIndex nb = triangles_[static_cast<std::size_t>(id)].nbr[s];
1257 if (inDomain(nb)) {
1258 out.push_back(triHandle(nb));
1259 }
1260 }
1261 return out;
1262 }
1263
1270 [[nodiscard]] std::vector<TriId> vertexAdjacentTriangles(TriId t) const {
1271 std::vector<TriId> out;
1272 const TriIndex self = indexOf(t);
1273 if (!realTriangle(self)) {
1274 return out;
1275 }
1276 // "Already listed" is tracked by the per-triangle walkMark bit (as in
1277 // visitTrianglesIntersecting): marking `self` first skips t itself, and
1278 // the guard clears every set bit on the way out so the const query
1279 // leaves the triangulation pristine.
1280 std::vector<TriIndex> marked{self};
1281 triangles_[self].walkMark = 1;
1282 struct MarkClearer {
1283 const std::vector<Tri>& tris;
1284 const std::vector<TriIndex>& marked;
1285 ~MarkClearer() { for (TriIndex m : marked) tris[m].walkMark = 0; }
1286 } markClearer{triangles_, marked};
1287 for (const VertexIndex w : triangles_[self].v) {
1288 visitVertexFan(self, w, [&](TriIndex cur) {
1289 if (inDomain(cur) && !triangles_[cur].walkMark) {
1290 triangles_[cur].walkMark = 1;
1291 marked.push_back(cur);
1292 out.push_back(triHandle(cur));
1293 }
1294 });
1295 }
1296 return out;
1297 }
1298
1308 [[nodiscard]] std::vector<TriId> incidentTriangles(VertexId v) const {
1309 std::vector<TriId> out;
1310 const VertexIndex w = indexOf(v);
1311 if (!realVertex(w)) {
1312 return out;
1313 }
1314 const TriIndex start = fanSeedOf(w);
1315 if (start == NO_TRI) {
1316 return out;
1317 }
1318 visitVertexFan(start, w, [&](TriIndex cur) {
1319 if (inDomain(cur)) {
1320 out.push_back(triHandle(cur));
1321 }
1322 });
1323 return out;
1324 }
1325
1335 template <class L = TriangleLabel>
1336 requires(detail::has_label_v<L>)
1337 [[nodiscard]] L& label(TriId t) {
1338 assert(has(t) && "label(): the handle is not a triangle of the triangulation");
1339 return triangles_[static_cast<std::size_t>(indexOf(t))].triLabel;
1340 }
1341
1343 template <class L = TriangleLabel>
1344 requires(detail::has_label_v<L>)
1345 [[nodiscard]] const L& label(TriId t) const {
1346 assert(has(t) && "label(): the handle is not a triangle of the triangulation");
1347 return triangles_[static_cast<std::size_t>(indexOf(t))].triLabel;
1348 }
1349
1350 // ---- visibility ------------------------------------------------------
1351
1382 [[nodiscard]] Graph<PointType> visibilityGraph() const;
1383
1404 [[nodiscard]] Graph<PointType> clearVisibilityGraph() const;
1405
1443 [[nodiscard]] Graph<PointType> reducedVisibilityGraph() const;
1444
1470 [[nodiscard]] std::vector<PointType> visibleVertices(const PointType& query) const;
1471
1485 [[nodiscard]] std::vector<PointType> clearlyVisibleVertices(const PointType& query) const;
1486
1516 template <class ResultNumber = division_result_t<NumberType>>
1518 const PointType& query) const;
1519
1520 private:
1521 friend struct detail::ConvexCoverBuilder;
1522
1523 // Full visibility of two mesh triangles needs only the new boundary edges
1524 // introduced by their convex hull. A hull edge whose endpoints both belong
1525 // to one input triangle already lies in that triangle and therefore in the
1526 // domain; only the (at most two) bridges between the triangles need a mesh
1527 // walk. The generic contains(Convex) path would locate both endpoints and
1528 // walk every hull edge, including all of those known-contained chains.
1529 [[nodiscard]] bool trianglesFullyVisible(
1530 std::int32_t a, std::int32_t b, const std::vector<std::uint8_t>& visible) const {
1531 const TriangleType first = triangleValue(a);
1532 const TriangleType second = triangleValue(b);
1533 std::array<PointType, 6> points{
1534 first.a(), first.b(), first.c(), second.a(), second.b(), second.c()
1535 };
1536 std::sort(points.begin(), points.end());
1537 const auto uniqueEnd = std::unique(points.begin(), points.end());
1538 const std::size_t pointCount =
1539 static_cast<std::size_t>(std::distance(points.begin(), uniqueEnd));
1540
1541 // Andrew's monotone chain on six points, kept entirely on the stack.
1542 // Twelve slots cover the lower and upper chains before their repeated
1543 // endpoint is discarded.
1544 std::array<PointType, 12> hull;
1545 std::size_t hullSize = 0;
1546 for (std::size_t i = 0; i < pointCount; ++i) {
1547 while (hullSize >= 2 &&
1548 orientationSign(hull[hullSize - 2], hull[hullSize - 1], points[i]) <= 0) {
1549 --hullSize;
1550 }
1551 hull[hullSize++] = points[i];
1552 }
1553 const std::size_t lowerSize = hullSize;
1554 for (std::size_t i = pointCount - 1; i-- != 0;) {
1555 while (hullSize > lowerSize &&
1556 orientationSign(hull[hullSize - 2], hull[hullSize - 1], points[i]) <= 0) {
1557 --hullSize;
1558 }
1559 hull[hullSize++] = points[i];
1560 }
1561 assert(hullSize >= 2);
1562 --hullSize; // the first vertex was repeated at the end
1563
1564 const auto isVertexOf = [&](TriIndex triangle, const PointType& point) {
1565 const auto& ids = triangles_[triangle].v;
1566 return vertices_[ids[0]] == point || vertices_[ids[1]] == point ||
1567 vertices_[ids[2]] == point;
1568 };
1569
1570 // The candidate triangle is already visible if each of its sides that
1571 // became internal to the joint hull borders a visible triangle. Their
1572 // union fills the candidate-facing neighborhood of every such side.
1573 // Conversely, an internalized side on the domain boundary proves that
1574 // the hull escapes immediately. These are the paper implementation's
1575 // inexpensive positive and negative tests.
1576 bool boundedByVisibleTriangles = true;
1577 for (std::int8_t side = 0; side < 3; ++side) {
1578 const PointType u = vertices_[triangles_[b].v[(side + 1) % 3]];
1579 const PointType v = vertices_[triangles_[b].v[(side + 2) % 3]];
1580 bool onHullBoundary = false;
1581 for (std::size_t i = 0; i < hullSize; ++i) {
1582 const Segment<PointType> hullSide(hull[i], hull[(i + 1) % hullSize]);
1583 if (hullSide.contains(u) && hullSide.contains(v)) {
1584 onHullBoundary = true;
1585 break;
1586 }
1587 }
1588 if (onHullBoundary) {
1589 continue;
1590 }
1591 const TriIndex across = triangles_[b].nbr[side];
1592 if (!inDomain(across)) {
1593 return false;
1594 }
1595 if (!visible[static_cast<std::size_t>(across)]) {
1596 boundedByVisibleTriangles = false;
1597 }
1598 }
1599 if (boundedByVisibleTriangles && holeWitnesses_.empty()) {
1600 return true;
1601 }
1602
1603 for (std::size_t i = 0; i < hullSize; ++i) {
1604 const PointType u = hull[i];
1605 const PointType v = hull[(i + 1) % hullSize];
1606 if ((isVertexOf(a, u) && isVertexOf(a, v)) ||
1607 (isVertexOf(b, u) && isVertexOf(b, v))) {
1608 continue;
1609 }
1610 if (!segmentInteriorContained(Segment<PointType>(u, v))) {
1611 return false;
1612 }
1613 }
1614
1615 // This helper is currently used by Polygon::convexCovering(), whose
1616 // domain has no holes. Retain the general triangulation semantics here:
1617 // a contained boundary must not have enclosed a hole.
1618 if (!holeWitnesses_.empty()) {
1619 const std::vector<PointType> hullVertices(hull.begin(), hull.begin() + hullSize);
1620 const Convex<PointType> convexHull(hullVertices, true);
1621 for (const TriangleType& witness : holeWitnesses_) {
1622 if (convexHull.contains(witness)) {
1623 return false;
1624 }
1625 }
1626 }
1627 return true;
1628 }
1629
1661 [[nodiscard]] Graph<TriangleType> convexCoverVisibilityGraph() const {
1662 Graph<TriangleType> result;
1663 std::vector<TriIndex> sources;
1664 sources.reserve(domainTriangleCount_);
1665 for (TriIndex source = 0; source < firstGhost_; ++source) {
1666 if (inDomain(source)) {
1667 sources.push_back(source);
1668 result.addVertex(triangleValue(source));
1669 }
1670 }
1671
1672 // A depth-first source order tends to make a previously processed
1673 // source a neighbor of the next one. Its already known symmetric edges
1674 // can then seed more of the next BFS without another geometric test.
1675 std::vector<TriIndex> sourceOrder;
1676 sourceOrder.reserve(sources.size());
1677 std::vector<bool> ordered(static_cast<std::size_t>(firstGhost_), false);
1678 std::vector<TriIndex> stack;
1679 for (const TriIndex seed : sources) {
1680 if (ordered[static_cast<std::size_t>(seed)]) {
1681 continue;
1682 }
1683 stack.push_back(seed);
1684 while (!stack.empty()) {
1685 const TriIndex current = stack.back();
1686 stack.pop_back();
1687 if (ordered[static_cast<std::size_t>(current)]) {
1688 continue;
1689 }
1690 ordered[static_cast<std::size_t>(current)] = true;
1691 sourceOrder.push_back(current);
1692 for (const TriIndex neighbor : triangles_[current].nbr) {
1693 if (inDomain(neighbor) &&
1694 !ordered[static_cast<std::size_t>(neighbor)]) {
1695 stack.push_back(neighbor);
1696 }
1697 }
1698 }
1699 }
1700
1701 std::vector<std::uint8_t> visited(static_cast<std::size_t>(firstGhost_));
1702 std::vector<std::uint8_t> visible(static_cast<std::size_t>(firstGhost_));
1703 std::queue<TriIndex> queue;
1704 for (const TriIndex source : sourceOrder) {
1705 std::fill(visited.begin(), visited.end(), false);
1706 std::fill(visible.begin(), visible.end(), false);
1707 visited[static_cast<std::size_t>(source)] = true;
1708 visible[static_cast<std::size_t>(source)] = true;
1709 queue.push(source);
1710 const TriangleType sourceTriangle = triangleValue(source);
1711
1712 while (!queue.empty()) {
1713 const TriIndex current = queue.front();
1714 queue.pop();
1715 for (const TriIndex neighbor : triangles_[current].nbr) {
1716 if (!inDomain(neighbor) ||
1717 visited[static_cast<std::size_t>(neighbor)]) {
1718 continue;
1719 }
1720 visited[static_cast<std::size_t>(neighbor)] = true;
1721 const TriangleType neighborTriangle = triangleValue(neighbor);
1722
1723 bool fullyVisible = result.containsEdge(sourceTriangle, neighborTriangle);
1724 if (!fullyVisible) {
1725 fullyVisible = trianglesFullyVisible(source, neighbor, visible);
1726 }
1727 if (fullyVisible) {
1728 result.addEdge(sourceTriangle, neighborTriangle);
1729 visible[static_cast<std::size_t>(neighbor)] = true;
1730 queue.push(neighbor);
1731 }
1732 }
1733 }
1734 }
1735
1736 return result;
1737 }
1738
1739 public:
1740 // ---- convex partition ------------------------------------------------
1741
1778 [[nodiscard]] std::vector<Convex<PointType>> convexPartition() const {
1779 std::vector<Convex<PointType>> pieces;
1780 if (domainTriangleCount_ == 0) {
1781 return pieces;
1782 }
1783
1784 // Pieces are grown by union-find over the triangles, and each piece
1785 // carries its boundary as a circular list of half-edges. A half-edge is
1786 // one (triangle, side) pair — `3 * tri + side` — which is exactly the
1787 // directed edge `v[(side+1)%3] -> v[(side+2)%3]`, CCW around the triangle
1788 // because the vertices are. A triangle starts as its own piece, so its
1789 // three sides start as its ring, in that same CCW order.
1790 std::vector<TriIndex> parent(static_cast<std::size_t>(firstGhost_));
1791 for (TriIndex t = 0; t < firstGhost_; ++t) {
1792 parent[static_cast<std::size_t>(t)] = t;
1793 }
1794 const auto findRoot = [&parent](TriIndex x) {
1795 while (parent[static_cast<std::size_t>(x)] != x) {
1796 parent[static_cast<std::size_t>(x)] =
1797 parent[static_cast<std::size_t>(parent[static_cast<std::size_t>(x)])];
1798 x = parent[static_cast<std::size_t>(x)];
1799 }
1800 return x;
1801 };
1802
1803 const std::size_t slots = static_cast<std::size_t>(firstGhost_) * 3;
1804 std::vector<std::int32_t> succ(slots, -1);
1805 std::vector<std::int32_t> pred(slots, -1);
1806 std::vector<std::int32_t> diagonals;
1807 for (TriIndex t = 0; t < firstGhost_; ++t) {
1808 if (!inDomain(t)) {
1809 continue;
1810 }
1811 for (int s = 0; s < 3; ++s) {
1812 const std::int32_t h = 3 * t + s;
1813 succ[static_cast<std::size_t>(h)] = 3 * t + (s + 1) % 3;
1814 pred[static_cast<std::size_t>(3 * t + (s + 1) % 3)] = h;
1815 // Each interior unconstrained edge is a candidate once, from the
1816 // lower-numbered of the two triangles that meet along it.
1817 const TriIndex n = triangles_[static_cast<std::size_t>(t)].nbr[s];
1818 if (n > t && inDomain(n) &&
1819 !bit(triangles_[static_cast<std::size_t>(t)].constrainedMask, s)) {
1820 diagonals.push_back(h);
1821 }
1822 }
1823 }
1824
1825 const auto originOf = [this](std::int32_t h) {
1826 return triangles_[static_cast<std::size_t>(h / 3)].v[static_cast<std::size_t>((h % 3 + 1) % 3)];
1827 };
1828 const auto targetOf = [this](std::int32_t h) {
1829 return triangles_[static_cast<std::size_t>(h / 3)].v[static_cast<std::size_t>((h % 3 + 2) % 3)];
1830 };
1831 const auto neighborOf = [this](std::int32_t h) {
1832 return triangles_[static_cast<std::size_t>(h / 3)].nbr[static_cast<std::size_t>(h % 3)];
1833 };
1834 // How many edges the piece holding `start` shares with the piece rooted
1835 // at `otherRoot`. Two convex sets with disjoint interiors are separated by
1836 // a line, so they can only share more than one edge if those edges are
1837 // collinear — which takes a slit or collinear vertices, and is rare
1838 // enough to pay a ring walk for. Splicing across one of two shared edges
1839 // would leave a boundary pinched shut at the other, which is not a ring.
1840 const auto sharedEdges = [&](std::int32_t start, TriIndex otherRoot) {
1841 int count = 0;
1842 std::int32_t h = start;
1843 do {
1844 const TriIndex n = neighborOf(h);
1845 if (n != NO_TRI && inDomain(n) && findRoot(n) == otherRoot) {
1846 ++count;
1847 }
1848 h = succ[static_cast<std::size_t>(h)];
1849 } while (h != start);
1850 return count;
1851 };
1852
1853 for (const std::int32_t h : diagonals) {
1854 const TriIndex t = h / 3;
1855 const TriIndex n = neighborOf(h);
1856 const TriIndex rootA = findRoot(t);
1857 const TriIndex rootB = findRoot(n);
1858 if (rootA == rootB) {
1859 continue; // an earlier merge already swallowed this diagonal
1860 }
1861 const std::int32_t twin = 3 * n + findSide(n, t);
1862 // The ring runs ... -> p -> u -> v -> q -> ... on this side and
1863 // ... -> r -> v -> u -> w -> ... on the other, so deleting the two
1864 // half-edges u->v and v->u leaves u followed by w and v followed by q.
1865 const VertexIndex u = originOf(h);
1866 const VertexIndex v = targetOf(h);
1867 const VertexIndex p = originOf(pred[static_cast<std::size_t>(h)]);
1868 const VertexIndex q = targetOf(succ[static_cast<std::size_t>(h)]);
1869 const VertexIndex r = originOf(pred[static_cast<std::size_t>(twin)]);
1870 const VertexIndex w = targetOf(succ[static_cast<std::size_t>(twin)]);
1871 const auto convexAt = [this](VertexIndex before, VertexIndex at, VertexIndex after) {
1872 return orientationSign(vertices_[static_cast<std::size_t>(before)],
1873 vertices_[static_cast<std::size_t>(at)],
1874 vertices_[static_cast<std::size_t>(after)]) >= 0;
1875 };
1876 // Collinear counts as convex: the merged piece is still convex and
1877 // carries one fewer corner, which is the whole point.
1878 if (!convexAt(p, u, w) || !convexAt(r, v, q)) {
1879 continue;
1880 }
1881 if (sharedEdges(h, rootB) != 1) {
1882 continue;
1883 }
1884 const std::int32_t beforeU = pred[static_cast<std::size_t>(h)];
1885 const std::int32_t afterU = succ[static_cast<std::size_t>(twin)];
1886 const std::int32_t beforeV = pred[static_cast<std::size_t>(twin)];
1887 const std::int32_t afterV = succ[static_cast<std::size_t>(h)];
1888 succ[static_cast<std::size_t>(beforeU)] = afterU;
1889 pred[static_cast<std::size_t>(afterU)] = beforeU;
1890 succ[static_cast<std::size_t>(beforeV)] = afterV;
1891 pred[static_cast<std::size_t>(afterV)] = beforeV;
1892 succ[static_cast<std::size_t>(h)] = -1;
1893 pred[static_cast<std::size_t>(h)] = -1;
1894 succ[static_cast<std::size_t>(twin)] = -1;
1895 pred[static_cast<std::size_t>(twin)] = -1;
1896 parent[static_cast<std::size_t>(rootA)] = rootB;
1897 }
1898
1899 // Read each surviving ring out once, in the canonical form a trusted
1900 // `Convex` asks for: CCW, no vertex in the middle of a straight stretch,
1901 // starting at the lexicographically smallest. The rings are convex, so a
1902 // vertex is interior to a straight stretch exactly when its own two
1903 // neighbours in the ring are collinear with it, and dropping every such
1904 // vertex in one pass is correct however many run together.
1905 std::vector<char> read(slots, 0);
1906 std::vector<PointType> ring;
1907 std::vector<PointType> kept;
1908 for (std::int32_t start = 0; start < static_cast<std::int32_t>(slots); ++start) {
1909 if (succ[static_cast<std::size_t>(start)] < 0 || read[static_cast<std::size_t>(start)]) {
1910 continue;
1911 }
1912 ring.clear();
1913 std::int32_t h = start;
1914 do {
1915 read[static_cast<std::size_t>(h)] = 1;
1916 ring.push_back(vertices_[static_cast<std::size_t>(originOf(h))]);
1917 h = succ[static_cast<std::size_t>(h)];
1918 } while (h != start);
1919
1920 kept.clear();
1921 for (std::size_t i = 0; i < ring.size(); ++i) {
1922 const PointType& before = ring[(i + ring.size() - 1) % ring.size()];
1923 const PointType& after = ring[(i + 1) % ring.size()];
1924 if (orientationSign(before, ring[i], after) != 0) {
1925 kept.push_back(ring[i]);
1926 }
1927 }
1928 if (kept.size() < 3) {
1929 continue; // no area, so nothing of the domain to carry
1930 }
1931 std::rotate(kept.begin(), std::min_element(kept.begin(), kept.end()), kept.end());
1932 pieces.push_back(Convex<PointType>(kept, /*trusted=*/true));
1933 }
1934 std::sort(pieces.begin(), pieces.end());
1935 return pieces;
1936 }
1937
1966 [[nodiscard]] std::vector<Convex<PointType>> convexCovering() const {
1967 std::vector<Convex<PointType>> result;
1968 if (domainTriangleCount_ == 0) {
1969 return result;
1970 }
1971
1972 const std::size_t triangleSlots = static_cast<std::size_t>(firstGhost_);
1973 const std::size_t halfEdgeSlots = triangleSlots * 3;
1974 const auto originOf = [this](std::int32_t h) {
1975 return triangles_[static_cast<std::size_t>(h / 3)]
1976 .v[static_cast<std::size_t>((h % 3 + 1) % 3)];
1977 };
1978 const auto targetOf = [this](std::int32_t h) {
1979 return triangles_[static_cast<std::size_t>(h / 3)]
1980 .v[static_cast<std::size_t>((h % 3 + 2) % 3)];
1981 };
1982 const auto convexAt = [this](VertexIndex before, VertexIndex at, VertexIndex after) {
1983 return orientationSign(vertices_[static_cast<std::size_t>(before)],
1984 vertices_[static_cast<std::size_t>(at)],
1985 vertices_[static_cast<std::size_t>(after)]) >= 0;
1986 };
1987
1988 struct Candidate {
1989 std::vector<TriIndex> triangles;
1990 Convex<PointType> convex;
1991 };
1992 std::vector<Candidate> candidates;
1993 candidates.reserve(domainTriangleCount_);
1994
1995 for (TriIndex seed = 0; seed < firstGhost_; ++seed) {
1996 if (!inDomain(seed)) {
1997 continue;
1998 }
1999
2000 // Only the growing candidate has a live ring. Newly fused triangles
2001 // receive their original three-edge ring immediately before it is
2002 // spliced into that candidate.
2003 std::vector<std::int32_t> succ(halfEdgeSlots, -1);
2004 std::vector<std::int32_t> pred(halfEdgeSlots, -1);
2005 std::vector<char> fused(triangleSlots, 0);
2006 std::vector<TriIndex> fusedTriangles{seed};
2007 fused[static_cast<std::size_t>(seed)] = 1;
2008
2009 const auto initializeRing = [&](TriIndex t) {
2010 for (int s = 0; s < 3; ++s) {
2011 const std::int32_t h = 3 * t + s;
2012 const std::int32_t next = 3 * t + (s + 1) % 3;
2013 succ[static_cast<std::size_t>(h)] = next;
2014 pred[static_cast<std::size_t>(next)] = h;
2015 }
2016 };
2017 initializeRing(seed);
2018
2019 std::deque<std::int32_t> frontier;
2020 for (int s = 0; s < 3; ++s) {
2021 frontier.push_back(3 * seed + s);
2022 }
2023
2024 while (!frontier.empty()) {
2025 const std::int32_t h = frontier.front();
2026 frontier.pop_front();
2027 if (succ[static_cast<std::size_t>(h)] < 0) {
2028 continue; // removed by an earlier fusion
2029 }
2030
2031 const TriIndex t = h / 3;
2032 const int side = h % 3;
2033 const TriIndex neighbor = triangles_[static_cast<std::size_t>(t)].nbr[side];
2034 if (!inDomain(neighbor) || fused[static_cast<std::size_t>(neighbor)] ||
2035 bit(triangles_[static_cast<std::size_t>(t)].constrainedMask, side)) {
2036 continue;
2037 }
2038
2039 // Splicing one pair of twins is valid only while this triangle
2040 // meets the candidate along exactly that one edge. Once it meets
2041 // along two edges, that count can only grow, so it need not be
2042 // reconsidered.
2043 int sharedEdges = 0;
2044 for (const TriIndex adjacent :
2045 triangles_[static_cast<std::size_t>(neighbor)].nbr) {
2046 if (inDomain(adjacent) && fused[static_cast<std::size_t>(adjacent)]) {
2047 ++sharedEdges;
2048 }
2049 }
2050 if (sharedEdges != 1) {
2051 continue;
2052 }
2053
2054 const std::int32_t twin = 3 * neighbor + findSide(neighbor, t);
2055 initializeRing(neighbor);
2056
2057 const VertexIndex u = originOf(h);
2058 const VertexIndex v = targetOf(h);
2059 const VertexIndex p = originOf(pred[static_cast<std::size_t>(h)]);
2060 const VertexIndex q = targetOf(succ[static_cast<std::size_t>(h)]);
2061 const VertexIndex r = originOf(pred[static_cast<std::size_t>(twin)]);
2062 const VertexIndex w = targetOf(succ[static_cast<std::size_t>(twin)]);
2063 if (!convexAt(p, u, w) || !convexAt(r, v, q)) {
2064 // Initializing a rejected triangle must not leave a live ring.
2065 for (int s = 0; s < 3; ++s) {
2066 const std::size_t rejected =
2067 static_cast<std::size_t>(3 * neighbor + s);
2068 succ[rejected] = -1;
2069 pred[rejected] = -1;
2070 }
2071 continue;
2072 }
2073
2074 const std::int32_t beforeU = pred[static_cast<std::size_t>(h)];
2075 const std::int32_t afterU = succ[static_cast<std::size_t>(twin)];
2076 const std::int32_t beforeV = pred[static_cast<std::size_t>(twin)];
2077 const std::int32_t afterV = succ[static_cast<std::size_t>(h)];
2078 succ[static_cast<std::size_t>(beforeU)] = afterU;
2079 pred[static_cast<std::size_t>(afterU)] = beforeU;
2080 succ[static_cast<std::size_t>(beforeV)] = afterV;
2081 pred[static_cast<std::size_t>(afterV)] = beforeV;
2082 succ[static_cast<std::size_t>(h)] = -1;
2083 pred[static_cast<std::size_t>(h)] = -1;
2084 succ[static_cast<std::size_t>(twin)] = -1;
2085 pred[static_cast<std::size_t>(twin)] = -1;
2086 fused[static_cast<std::size_t>(neighbor)] = 1;
2087 fusedTriangles.push_back(neighbor);
2088
2089 // These are precisely the live edges whose endpoint angles are
2090 // new, including both newly exposed sides of `neighbor`.
2091 frontier.push_back(beforeU);
2092 frontier.push_back(afterU);
2093 frontier.push_back(beforeV);
2094 frontier.push_back(afterV);
2095 }
2096
2097 std::int32_t start = -1;
2098 for (std::int32_t h = 0; h < static_cast<std::int32_t>(halfEdgeSlots); ++h) {
2099 if (succ[static_cast<std::size_t>(h)] >= 0) {
2100 start = h;
2101 break;
2102 }
2103 }
2104 assert(start >= 0);
2105
2106 std::vector<PointType> ring;
2107 std::int32_t h = start;
2108 do {
2109 ring.push_back(vertices_[static_cast<std::size_t>(originOf(h))]);
2110 h = succ[static_cast<std::size_t>(h)];
2111 } while (h != start);
2112
2113 std::vector<PointType> kept;
2114 kept.reserve(ring.size());
2115 for (std::size_t i = 0; i < ring.size(); ++i) {
2116 const PointType& before = ring[(i + ring.size() - 1) % ring.size()];
2117 const PointType& after = ring[(i + 1) % ring.size()];
2118 if (orientationSign(before, ring[i], after) != 0) {
2119 kept.push_back(ring[i]);
2120 }
2121 }
2122 assert(kept.size() >= 3);
2123 std::rotate(kept.begin(), std::min_element(kept.begin(), kept.end()), kept.end());
2124 candidates.push_back(
2125 Candidate{std::move(fusedTriangles), Convex<PointType>(kept, /*trusted=*/true)});
2126 }
2127
2128 // Maintain each candidate's uncovered gain through the inverse
2129 // triangle-to-candidate incidence lists. Every incidence is processed at
2130 // most once, when its triangle first becomes covered.
2131 std::vector<std::vector<std::size_t>> coverers(triangleSlots);
2132 std::vector<std::size_t> gain(candidates.size());
2133 for (std::size_t c = 0; c < candidates.size(); ++c) {
2134 gain[c] = candidates[c].triangles.size();
2135 for (const TriIndex t : candidates[c].triangles) {
2136 coverers[static_cast<std::size_t>(t)].push_back(c);
2137 }
2138 }
2139
2140 std::vector<char> covered(triangleSlots, 0);
2141 std::vector<char> selected(candidates.size(), 0);
2142 std::vector<std::size_t> selectionOrder;
2143 std::size_t remaining = domainTriangleCount_;
2144 while (remaining != 0) {
2145 std::size_t best = candidates.size();
2146 std::size_t bestGain = 0;
2147 for (std::size_t c = 0; c < candidates.size(); ++c) {
2148 if (!selected[c] && gain[c] > bestGain) {
2149 best = c;
2150 bestGain = gain[c];
2151 }
2152 }
2153 assert(best != candidates.size() && bestGain != 0);
2154 selected[best] = 1;
2155 selectionOrder.push_back(best);
2156 for (const TriIndex t : candidates[best].triangles) {
2157 const std::size_t ti = static_cast<std::size_t>(t);
2158 if (covered[ti]) {
2159 continue;
2160 }
2161 covered[ti] = 1;
2162 --remaining;
2163 for (const std::size_t c : coverers[ti]) {
2164 assert(gain[c] != 0);
2165 --gain[c];
2166 }
2167 }
2168 }
2169
2170 std::vector<std::size_t> coverageCount(triangleSlots, 0);
2171 for (const std::size_t c : selectionOrder) {
2172 for (const TriIndex t : candidates[c].triangles) {
2173 ++coverageCount[static_cast<std::size_t>(t)];
2174 }
2175 }
2176 for (auto it = selectionOrder.rbegin(); it != selectionOrder.rend(); ++it) {
2177 const std::size_t c = *it;
2178 const bool redundant = std::ranges::all_of(
2179 candidates[c].triangles, [&](TriIndex t) {
2180 return coverageCount[static_cast<std::size_t>(t)] > 1;
2181 });
2182 if (!redundant) {
2183 continue;
2184 }
2185 selected[c] = 0;
2186 for (const TriIndex t : candidates[c].triangles) {
2187 --coverageCount[static_cast<std::size_t>(t)];
2188 }
2189 }
2190
2191 result.reserve(selectionOrder.size());
2192 for (const std::size_t c : selectionOrder) {
2193 if (selected[c]) {
2194 result.push_back(std::move(candidates[c].convex));
2195 }
2196 }
2197 std::sort(result.begin(), result.end());
2198 return result;
2199 }
2200
2201 // ---- traversal along a segment or (oriented) line --------------------
2202
2229 template <detail::DirectedTraversal OS, class Fn>
2230 bool visitTrianglesIntersecting(const OS& s, Fn f) const {
2231 return visitTriangleIdsIntersecting<false>(
2232 s, [&](TriIndex t) { return reportTriangle(f, t); });
2233 }
2234
2235 private:
2250 template <bool AllTriangles, detail::DirectedTraversal OS, class Fn>
2251 bool visitTriangleIdsIntersecting(const OS& s, Fn f) const {
2252 if (firstGhost_ == 0) return false;
2253 const auto a = s[0];
2254 const auto b = s[1];
2255 // The query's two defining points are read by every sign this walk
2256 // takes — a dozen of them, once per triangle of the path and once per
2257 // edge of each — so they are converted here rather than at each.
2258 const auto fa = filteredPoint(a);
2259 const auto fb = filteredPoint(b);
2260 // How the directed query extends past its defining points `a`, `b` (which
2261 // always give its supporting line and forward direction a->b):
2262 // - unboundedBack: no finite source (a line / oriented line). Entered at
2263 // the ghost where a->b crosses into the hull, found by a directional
2264 // descent, rather than by locating a source.
2265 // - unboundedFront: no finite target (a line / oriented line / ray). The
2266 // walk never stops at `b`; it runs until it leaves the hull, and picks
2267 // each forward exit edge by direction rather than by segment bounds.
2268 constexpr bool unboundedBack = detail::LineOrOriented<OS>;
2269 constexpr bool unboundedFront = detail::LineOrOriented<OS> || RayConcept<OS>;
2270
2271 // "Already emitted" is tracked by a per-triangle mark bit rather than a
2272 // hash set: O(1) test, no per-lookup allocation. `marked` records which
2273 // bits were set; the guard clears every one of them on the way out —
2274 // normal return, early exit, or an exception from f — so the const walk
2275 // leaves the triangulation pristine. NOTE: because the mark lives on the
2276 // triangulation, the walk is not reentrant: f must not start another
2277 // walk on the same Triangulation.
2278 std::vector<TriIndex> marked;
2279 struct MarkClearer {
2280 const std::vector<Tri>& tris;
2281 const std::vector<TriIndex>& marked;
2282 ~MarkClearer() { for (TriIndex t : marked) tris[t].walkMark = 0; }
2283 } markClearer{triangles_, marked};
2284 bool stop = false;
2285 // The triangle the walk came from, so it never steps straight back out
2286 // through the edge it just crossed. Set by the entry helpers below too.
2287 TriIndex prev = NO_TRI;
2288 // Emit a triangle to f, once. Ghost triangles are walked through for
2289 // navigation but never reported here, and out-of-domain (polygon
2290 // hull-fill) ones only under AllTriangles, so the reported set is exactly
2291 // the (in-domain, or all real) triangles s meets.
2292 const auto emit = [&](TriIndex t) {
2293 const bool report = AllTriangles ? (t != NO_TRI && !isGhost(t)) : inDomain(t);
2294 if (!stop && report && !triangles_[t].walkMark) {
2295 triangles_[t].walkMark = 1;
2296 marked.push_back(t);
2297 if (f(t)) stop = true;
2298 }
2299 };
2300 // The next triangle when rotating around vertex w, having arrived from
2301 // `from` (NO_TRI to start). Orientation-independent (ghost triangles are
2302 // not CCW-normalized): leave through the w-incident edge we didn't enter.
2303 const auto rotateAround = [&](TriIndex cur, VertexIndex w, TriIndex from) -> TriIndex {
2304 const int lw = localIndex(cur, w);
2305 int s1 = -1, s2 = -1;
2306 for (int s = 0; s < 3; ++s) {
2307 if (s != lw) {
2308 if (s1 < 0) s1 = s; else s2 = s;
2309 }
2310 }
2311 return triangles_[cur].nbr[triangles_[cur].nbr[s1] == from ? s2 : s1];
2312 };
2313 // Every real triangle of vertex w's fan (rotate around w through ghosts).
2314 const auto emitFan = [&](VertexIndex w, TriIndex startTri) {
2315 TriIndex cur = startTri, from = NO_TRI;
2316 std::size_t g = 0, lim = triangles_.size() + 1;
2317 do {
2318 emit(cur);
2319 const TriIndex next = rotateAround(cur, w, from);
2320 from = cur;
2321 cur = next;
2322 } while (cur != startTri && cur != NO_TRI && !stop && ++g < lim);
2323 };
2324 // Continuing forward from p — a point on the closure of t — does the query
2325 // enter t's interior? 1 = enters, 0 = no, -1 = runs collinearly along an
2326 // edge of t. Forward is the *direction* a->b, not "towards the point b":
2327 // b is the far end only for a segment, while a line or a ray runs past its
2328 // second defining point, and beyond it "towards b" points backwards.
2329 // cross(w - u, b - a) is that direction's side of the edge (u,w), and the
2330 // triangle being CCW its interior is the positive side.
2331 const auto rayEnters = [&](TriIndex t, const auto& p) -> int {
2332 const auto& v = triangles_[t].v;
2333 const auto fp = filteredPoint(p);
2334 for (int k = 0; k < 3; ++k) {
2335 const auto& u = vertices_[v[(k + 1) % 3]];
2336 const auto& w = vertices_[v[(k + 2) % 3]];
2337 if (detail::orientationSignOf(filteredVertex(v[(k + 1) % 3]),
2338 filteredVertex(v[(k + 2) % 3]), fp).value() == 0) {
2339 const auto forward =
2341 if (forward < 0) return 0;
2342 if (forward == 0) return -1;
2343 }
2344 }
2345 return 1;
2346 };
2347 // The fan triangle around w whose interior the ray w->b enters (or NO_TRI).
2348 const auto forwardAround = [&](VertexIndex w, TriIndex startTri) -> TriIndex {
2349 TriIndex cur = startTri, from = NO_TRI;
2350 std::size_t g = 0, lim = triangles_.size() + 1;
2351 do {
2352 if (!isGhost(cur) && rayEnters(cur, vertices_[w]) == 1) return cur;
2353 const TriIndex next = rotateAround(cur, w, from);
2354 from = cur;
2355 cur = next;
2356 } while (cur != startTri && cur != NO_TRI && ++g < lim);
2357 return NO_TRI;
2358 };
2359 // Three-way order of two points along the directed line a->b: the sign
2360 // of q - p projected on that direction. Every question the trace asks
2361 // about progress along the segment is a comparison of two such
2362 // projections, and their difference is a single dot product -- so the
2363 // signed progress of each point never has to be formed. Comparing
2364 // against the target b is how the walk stops at the far end.
2365 const auto alongOrder = [&](const auto& p, const auto& q) {
2366 return dotSign(p, q, a, b);
2367 };
2368 const auto vertexOrder = [&](VertexIndex u, VertexIndex w) {
2369 return alongOrder(vertices_[u], vertices_[w]);
2370 };
2371 // From a vertex w on the segment, emit its fan and follow the segment: if
2372 // it enters a triangle interior return that triangle (resume the trace);
2373 // if it continues collinearly along an edge, hop to the next on-segment
2374 // vertex and repeat; return NO_TRI when the segment leaves/ends here.
2375 const auto advanceFromVertex = [&](VertexIndex w, TriIndex anchor) -> TriIndex {
2376 std::size_t hops = 0, lim = vertices_.size() + 1;
2377 while (!stop && ++hops < lim) {
2378 emitFan(w, anchor);
2379 if (stop) return NO_TRI;
2380 if constexpr (!unboundedFront) {
2381 if (alongOrder(b, vertices_[w]) >= 0) return NO_TRI; // reached the target end
2382 }
2383 const TriIndex interior = forwardAround(w, anchor);
2384 if (interior != NO_TRI) return interior;
2385 // Look for a forward neighbor x with edge {w,x} collinear with a->b,
2386 // not past the target.
2387 VertexIndex nextV = NO_TRI;
2388 TriIndex nextAnchor = NO_TRI;
2389 TriIndex cur = anchor, from = NO_TRI;
2390 std::size_t g = 0, glim = triangles_.size() + 1;
2391 do {
2392 if (!isGhost(cur)) {
2393 for (VertexIndex y : triangles_[cur].v) {
2394 if (y != w &&
2395 detail::orientationSignOf(fa, fb, filteredVertex(y)).value() == 0 &&
2396 vertexOrder(w, y) > 0 &&
2397 (unboundedFront || alongOrder(b, vertices_[y]) <= 0)) {
2398 nextV = y;
2399 nextAnchor = cur;
2400 break;
2401 }
2402 }
2403 }
2404 if (nextV != NO_TRI) break;
2405 const TriIndex next = rotateAround(cur, w, from);
2406 from = cur;
2407 cur = next;
2408 } while (cur != anchor && cur != NO_TRI && ++g < glim);
2409 if (nextV == NO_TRI) return NO_TRI; // segment leaves or ends at w
2410 w = nextV;
2411 anchor = nextAnchor;
2412 }
2413 return NO_TRI;
2414 };
2415
2416 // Whether the directed line a->b crosses ghost g's hull edge from outside
2417 // into the hull (rather than exiting it). The interior lies on the side of
2418 // the hull edge holding the inside triangle's apex; the line enters iff its
2419 // forward direction a->b points toward that same side. (Line mode only.)
2420 [[maybe_unused]] const auto lineEnters = [&](TriIndex g) -> bool {
2421 const VertexIndex va = triangles_[g].v[0];
2422 const VertexIndex vb = triangles_[g].v[1];
2423 const auto& iv = triangles_[triangles_[g].nbr[2]].v; // inside (real) triangle
2424 VertexIndex apex = iv[0];
2425 for (int i = 1; i < 3; ++i) {
2426 if (iv[i] != va && iv[i] != vb) apex = iv[i];
2427 }
2428 const auto insideSide =
2429 orientationDeterminant(vertices_[va], vertices_[vb], vertices_[apex]);
2430 // (vb - va) x (b - a): the side of the hull edge that a->b points toward.
2431 const auto dirCross = orientationDeterminant(vertices_[va], vertices_[vb], b) -
2432 orientationDeterminant(vertices_[va], vertices_[vb], a);
2433 return dirCross != 0 && (dirCross > 0) == (insideSide > 0);
2434 };
2435
2436 // The ghost whose hull edge the directed query crosses to ENTER the hull,
2437 // or NO_TRI if it never reaches the hull at all. Used by every query whose
2438 // start lies outside: the direction a->b is what picks the entry out of the
2439 // (at most two) hull edges the supporting line meets, so a segment starting
2440 // outside is entered exactly like a line.
2441 //
2442 // Ghost triangles share a single placeholder apex (they are topological,
2443 // not Euclidean), so navigation uses only the real hull endpoints: each step
2444 // moves toward whichever endpoint of the current hull edge is closer to the
2445 // supporting line a->b (smaller |orientation determinant|), so the descent
2446 // walks O(arc) ghosts toward a crossing rather than scanning all of them.
2447 //
2448 // That descent lands on *a* crossing, which need not be the entry: it may be
2449 // where the query leaves the hull, and a crossing where the line merely
2450 // touches a vertex the two hull edges share is no entry at all. So the ring
2451 // is then followed until a hull edge the line genuinely enters through.
2452 // A line running *along* a hull edge enters through no edge, yet still meets
2453 // the triangles lining it, so that collinear edge is kept as a fallback.
2454 // @p g0 seeds the descent: the ghost the query's own source located, when it
2455 // has one, so the walk starts near its crossing and stays O(arc).
2456 [[maybe_unused]] const auto lineEntry = [&](TriIndex g0) -> TriIndex {
2457 TriIndex g = g0, crossing = NO_TRI;
2458 std::size_t guard = 0, lim = triangles_.size() + 1;
2459 while (g != NO_TRI && isGhost(g) && ++guard < lim) {
2460 const VertexIndex va = triangles_[g].v[0];
2461 const VertexIndex vb = triangles_[g].v[1];
2462 const auto da = orientationDeterminant(a, b, vertices_[va]);
2463 const auto db = orientationDeterminant(a, b, vertices_[vb]);
2464 if (da == 0 || db == 0 || (da > 0) != (db > 0)) { crossing = g; break; }
2465 const auto absA = da < 0 ? -da : da;
2466 const auto absB = db < 0 ? -db : db;
2467 g = triangles_[g].nbr[absA < absB ? 1 : 0];
2468 }
2469 if (crossing == NO_TRI) return NO_TRI; // the supporting line misses the hull
2470 TriIndex h = crossing, from = NO_TRI, collinear = NO_TRI;
2471 guard = 0;
2472 while (h != NO_TRI && isGhost(h) && ++guard < lim) {
2473 const VertexIndex va = triangles_[h].v[0];
2474 const VertexIndex vb = triangles_[h].v[1];
2475 const auto da = orientationDeterminant(a, b, vertices_[va]);
2476 const auto db = orientationDeterminant(a, b, vertices_[vb]);
2477 const bool straddle = da == 0 || db == 0 || (da > 0) != (db > 0);
2478 if (da == 0 && db == 0) {
2479 collinear = h; // the query runs along this hull edge
2480 } else if (straddle && lineEnters(h)) {
2481 return h;
2482 }
2483 const TriIndex next = triangles_[h].nbr[0] == from ? triangles_[h].nbr[1]
2484 : triangles_[h].nbr[0];
2485 from = h;
2486 h = next;
2487 if (h == crossing) break; // came full circle
2488 }
2489 return collinear;
2490 };
2491
2492 // Begin the trace at the hull edge of ghost `g`, which the query crosses to
2493 // enter the triangulated region. Crossing through an *endpoint* of that edge
2494 // — a mesh vertex — is not the same as crossing through its relative
2495 // interior: there the query meets that vertex's whole fan, and may only
2496 // graze the hull there or run on along its boundary. So it enters exactly
2497 // like any other on-vertex advance (fan, then follow), which is also what
2498 // makes the entry independent of which of the vertex's two ghosts was found.
2499 // Otherwise it simply enters the triangle just inside the edge.
2500 [[maybe_unused]] const auto enterThroughGhost = [&](TriIndex g) -> TriIndex {
2501 const VertexIndex va = triangles_[g].v[0];
2502 const VertexIndex vb = triangles_[g].v[1];
2503 const bool onA = detail::orientationSignOf(fa, fb, filteredVertex(va)).value() == 0;
2504 const bool onB = detail::orientationSignOf(fa, fb, filteredVertex(vb)).value() == 0;
2505 if (onA || onB) {
2506 VertexIndex w = onA ? va : vb;
2507 if (onA && onB) {
2508 // Along the hull edge: start at the endpoint met first, never at
2509 // one behind a finite source.
2510 const VertexIndex first = vertexOrder(va, vb) >= 0 ? va : vb;
2511 const VertexIndex second = (first == va) ? vb : va;
2512 w = (unboundedBack || alongOrder(a, vertices_[first]) >= 0) ? first : second;
2513 }
2514 prev = NO_TRI;
2515 return advanceFromVertex(w, g);
2516 }
2517 prev = g; // entry boundary edge; don't step back out through it
2518 const TriIndex inside = triangles_[g].nbr[2];
2519 return (inside != NO_TRI && !isGhost(inside)) ? inside : NO_TRI;
2520 };
2521
2522 // Pick the triangle to begin tracing the component reached at `start`,
2523 // which contains point `p`; visit p's contact triangles along the way.
2524 [[maybe_unused]] const auto enterAt = [&](const auto& p, TriIndex start) -> TriIndex {
2525 const auto& v = triangles_[start].v;
2526 const auto fp = filteredPoint(p);
2527 int zeros = 0, z0 = -1, z1 = -1;
2528 for (int k = 0; k < 3; ++k) {
2529 if (detail::orientationSignOf(filteredVertex(v[(k + 1) % 3]),
2530 filteredVertex(v[(k + 2) % 3]), fp).value() == 0) {
2531 ++zeros;
2532 if (z0 < 0) z0 = k; else z1 = k;
2533 }
2534 }
2535 if (zeros >= 2) { // p is a vertex: visit its fan, then continue
2536 return advanceFromVertex(v[3 - z0 - z1], start);
2537 }
2538 if (zeros == 1) { // p on an edge: visit both sides, continue into the forward one
2539 const TriIndex other = triangles_[start].nbr[z0];
2540 emit(start);
2541 emit(other);
2542 if (rayEnters(start, p) == 1) return start;
2543 if (other != NO_TRI && !isGhost(other) && rayEnters(other, p) == 1) return other;
2544 const VertexIndex e1 = v[(z0 + 1) % 3];
2545 const VertexIndex e2 = v[(z0 + 2) % 3];
2546 // Continue only if the segment runs ALONG the shared edge (hop to
2547 // its forward endpoint). Otherwise the segment crosses the edge
2548 // into the other side already emitted — if that side is outside
2549 // the hull, nothing more lies on this side.
2550 if (detail::orientationSignOf(filteredVertex(e1), filteredVertex(e2), fb)
2551 .value() != 0) {
2552 return NO_TRI;
2553 }
2554 const VertexIndex fwd = vertexOrder(e2, e1) > 0 ? e1 : e2;
2555 if constexpr (!unboundedFront) {
2556 // The target lies strictly inside this edge: s stops before the
2557 // forward endpoint, so its fan must not be visited. The two
2558 // triangles sharing the edge are all s meets, and both are out.
2559 if (alongOrder(b, vertices_[fwd]) > 0) {
2560 return NO_TRI;
2561 }
2562 }
2563 return advanceFromVertex(fwd, start);
2564 }
2565 emit(start); // p strictly inside
2566 return start;
2567 };
2568
2569 // Emit exactly the triangle(s) whose closure contains the target b — the
2570 // one triangle it is interior to, both triangles sharing the edge it lies
2571 // on, or the whole fan if b is a mesh vertex — then the walk ends. (Unlike
2572 // enterAt, this never continues, so it cannot over-emit a vertex fan when
2573 // b merely lies on an edge.)
2574 [[maybe_unused]] const auto emitTargetContacts = [&](TriIndex t) {
2575 const auto& v = triangles_[t].v;
2576 int zeros = 0, z0 = -1, z1 = -1;
2577 for (int k = 0; k < 3; ++k) {
2578 if (detail::orientationSignOf(filteredVertex(v[(k + 1) % 3]),
2579 filteredVertex(v[(k + 2) % 3]), fb).value() == 0) {
2580 ++zeros;
2581 if (z0 < 0) z0 = k; else z1 = k;
2582 }
2583 }
2584 if (zeros >= 2) { // b is a mesh vertex: every incident triangle touches it
2585 emitFan(v[3 - z0 - z1], t);
2586 return;
2587 }
2588 emit(t);
2589 if (zeros == 1) { // b on an edge: the triangle on the far side also touches it
2590 emit(triangles_[t].nbr[z0]);
2591 }
2592 };
2593
2594 TriIndex t;
2595 if constexpr (unboundedBack) {
2596 // A line has no finite source: enter the hull at the ghost where the
2597 // directed line a->b crosses in, then trace forward like a segment. With
2598 // no source to locate, the descent starts at an arbitrary ghost.
2599 const TriIndex g = lineEntry(firstGhost_);
2600 if (g == NO_TRI) {
2601 return false; // the line misses the triangulated region
2602 }
2603 t = enterThroughGhost(g);
2604 } else {
2605 t = locateIndex(a);
2606 if (t == NO_TRI) {
2607 return false; // empty triangulation
2608 }
2609 if (isGhost(t)) {
2610 // Source outside the hull: the hull boundary is crossed twice, so it
2611 // is the direction a->b that says which crossing is the way in — for
2612 // a segment and a ray no less than for a line — and the query must
2613 // then actually reach that edge to meet the region at all. The ghost
2614 // the source landed in seeds the descent, keeping it O(arc).
2615 const TriIndex g = lineEntry(t);
2616 if (g == NO_TRI || !s.intersects(edgeSegment(Edge{g, 2}))) {
2617 return false; // s never meets the triangulated region
2618 }
2619 t = enterThroughGhost(g);
2620 } else {
2621 t = enterAt(a, t); // source inside the hull: start there
2622 }
2623 }
2624
2625 const std::size_t cap = triangles_.size() * 4 + 16;
2626 std::size_t guard = 0;
2627 while (!stop && t != NO_TRI) {
2628 if (++guard > cap) break; // safety net
2629 emit(t);
2630 if (stop) break;
2631 if constexpr (!unboundedFront) {
2632 if (pointInClosure(b, t)) { // target reached: visit its contact triangles
2633 emitTargetContacts(t);
2634 break;
2635 }
2636 }
2637 const auto& v = triangles_[t].v;
2638 // Forward exit edge: the supporting line ab crosses it, it isn't where
2639 // we came from, and we leave forward through it — for a segment that is
2640 // "the segment ab straddles the edge"; for an unbounded-forward query
2641 // (ray / line) it is "a->b points out of the triangle across the edge"
2642 // (no finite endpoint to bound the crossing).
2643 int exitK = -1;
2644 for (int k = 0; k < 3; ++k) {
2645 if (triangles_[t].nbr[k] == prev) continue;
2646 const auto& u = vertices_[v[(k + 1) % 3]];
2647 const auto& w = vertices_[v[(k + 2) % 3]];
2648 const auto fu = filteredVertex(v[(k + 1) % 3]);
2649 const auto fw = filteredVertex(v[(k + 2) % 3]);
2650 const auto du = detail::orientationSignOf(fa, fb, fu).value();
2651 const auto dw = detail::orientationSignOf(fa, fb, fw).value();
2652 const bool straddleLine = (du > 0 && dw < 0) || (du < 0 && dw > 0);
2653 if (!straddleLine) continue;
2654 bool forward;
2655 if constexpr (unboundedFront) {
2656 // a->b leaves t outward across (u,w): its direction points to the
2657 // side of (u,w) away from the apex v[k] (the triangle interior).
2658 const auto apexSide =
2659 detail::orientationSignOf(fu, fw, filteredVertex(v[k])).value();
2660 const auto dirCross = orientationDeterminant(u, w, b) -
2661 orientationDeterminant(u, w, a);
2662 forward = dirCross != 0 && (dirCross > 0) != (apexSide > 0);
2663 } else {
2664 const auto ea = detail::orientationSignOf(fu, fw, fa).value();
2665 const auto eb = detail::orientationSignOf(fu, fw, fb).value();
2666 forward = (ea > 0 && eb < 0) || (ea < 0 && eb > 0);
2667 }
2668 if (forward) { exitK = k; break; }
2669 }
2670 if (exitK != -1) {
2671 prev = t;
2672 t = triangles_[t].nbr[exitK];
2673 if (isGhost(t)) {
2674 // Left the triangulated region; by convexity s cannot re-enter.
2675 // Under AllTriangles report the ghost we stepped into, so the
2676 // caller can tell this exit from a mere touch of the boundary.
2677 if constexpr (AllTriangles) {
2678 (void)f(t);
2679 }
2680 break;
2681 }
2682 continue;
2683 }
2684 // Degenerate: s leaves t through a vertex or runs along an edge.
2685 int onCount = 0, on0 = -1, on1 = -1;
2686 for (int m = 0; m < 3; ++m) {
2687 if (detail::orientationSignOf(fa, fb, filteredVertex(v[m])).value() == 0) {
2688 ++onCount;
2689 if (on0 < 0) on0 = m; else on1 = m;
2690 }
2691 }
2692 if (onCount >= 1) {
2693 // s leaves t through the on-line vertex farthest along a->b; from
2694 // there, advance (fan + collinear hops) until it re-enters an interior.
2695 const int m = (onCount >= 2 && vertexOrder(v[on0], v[on1]) > 0) ? on1 : on0;
2696 t = advanceFromVertex(v[m], t);
2697 prev = NO_TRI;
2698 continue;
2699 }
2700 break; // no progress (should not happen for a valid triangulation)
2701 }
2702 return stop;
2703 }
2704
2705 public:
2721 template <detail::TriangulationQuery OS>
2722 [[nodiscard]] std::vector<TriangleType> trianglesIntersecting(const OS& s) const {
2723 std::vector<TriangleType> out;
2724 visitTrianglesIntersecting(s, [&](const TriangleType& t) { out.push_back(t); });
2725 return out;
2726 }
2727
2742 template <detail::TriangulationQuery OS, class Fn>
2743 bool visitTrianglesInteriorIntersecting(const OS& s, Fn f) const {
2744 return visitTrianglesIntersecting(s, [&](TriId id) -> bool {
2745 const TriangleType t = triangleValue(indexOf(id));
2746 if (!t.interiorsIntersect(s)) {
2747 return false; // only boundary contact: skip, but keep walking
2748 }
2749 if constexpr (std::is_invocable_v<Fn&, const TriangleType&>) {
2750 return detail::invokeVisitor(f, t);
2751 } else {
2752 return detail::invokeVisitor(f, id);
2753 }
2754 });
2755 }
2756
2770 template <detail::TriangulationQuery OS>
2771 [[nodiscard]] std::vector<TriangleType> trianglesInteriorIntersecting(const OS& s) const {
2772 std::vector<TriangleType> out;
2773 visitTrianglesInteriorIntersecting(s, [&](const TriangleType& t) { out.push_back(t); });
2774 return out;
2775 }
2776
2792 template <detail::TriangulationQuery OS, class Fn>
2793 bool visitEdgesIntersecting(const OS& s, Fn f) const {
2794 std::unordered_set<SegmentType> seen;
2795 bool stop = false;
2796 visitTrianglesIntersecting(s, [&](const TriangleType& t) -> bool {
2797 for (const auto& edge : t.edges()) {
2798 if (!s.intersects(edge)) {
2799 continue;
2800 }
2801 // Canonical edge (the constructor re-sorts the endpoints), used
2802 // for reporting and for deduplicating across the two incident
2803 // triangles. Only when edges carry a label do we look it up in
2804 // segToEdge_ to recover the stored label; otherwise the segment
2805 // built from the endpoints already is the value to report.
2806 SegmentType seg(edge[0], edge[1]);
2807 if constexpr (detail::has_label_v<SegmentLabel>) {
2808 auto se = segmentMap().find(seg);
2809 if (se == segmentMap().end()) {
2810 continue; // not a stored edge (should not happen)
2811 }
2812 seg = edgeSegment(se->second);
2813 }
2814 if (seen.insert(seg).second && detail::invokeVisitor(f, seg)) {
2815 stop = true;
2816 return true;
2817 }
2818 }
2819 return false;
2820 });
2821 return stop;
2822 }
2823
2837 template <detail::TriangulationQuery OS, class Fn>
2838 bool visitEdgesInteriorIntersecting(const OS& s, Fn f) const {
2839 return visitEdgesIntersecting(s, [&](const SegmentType& e) -> bool {
2840 if (s.interiorsIntersect(e)) {
2841 return detail::invokeVisitor(f, e);
2842 }
2843 return false; // endpoint-only contact: skip, but keep walking
2844 });
2845 }
2846
2860 template <detail::TriangulationQuery OS>
2861 [[nodiscard]] std::vector<SegmentType> edgesIntersecting(const OS& s) const {
2862 std::vector<SegmentType> out;
2863 visitEdgesIntersecting(s, [&](const SegmentType& e) { out.push_back(e); });
2864 return out;
2865 }
2866
2880 template <detail::TriangulationQuery OS>
2881 [[nodiscard]] std::vector<SegmentType> edgesInteriorIntersecting(const OS& s) const {
2882 std::vector<SegmentType> out;
2883 visitEdgesInteriorIntersecting(s, [&](const SegmentType& e) { out.push_back(e); });
2884 return out;
2885 }
2886
2887 // ---- traversal over a region (a connected convex query shape) --------
2888
2913 template <detail::TriangulationRegionQuery Q, class Fn>
2914 bool visitTrianglesIntersecting(const Q& shape, Fn f) const {
2915 if (firstGhost_ == 0) return false; // no real triangles
2916 const std::vector<TriIndex> seeds = seedTrianglesIntersecting(shape);
2917 if (seeds.empty()) return false;
2918 return floodTriangleIdsIntersecting<false>(
2919 shape, seeds, [&](TriIndex t) { return reportTriangle(f, t); });
2920 }
2921
2922 // The materialized form — trianglesIntersecting(shape) — and the
2923 // interior-intersecting and edge variants are the shared wrappers above,
2924 // constrained on detail::TriangulationQuery, so they accept a region shape
2925 // here just as they accept a segment.
2926
2927 // ---- traversal along a chain (a polyline or a monotone chain) --------
2928
2952 template <detail::ChainTraversal C, class Fn>
2953 bool visitTrianglesIntersecting(const C& c, Fn f) const {
2954 if (firstGhost_ == 0) return false; // no real triangles
2955 if (c.size() < 2) {
2956 return c.empty() ? false : visitTrianglesIntersecting(c[0], f);
2957 }
2958 // Triangles already reported, so an edge re-entering a triangle an
2959 // earlier edge met does not report it again (the per-edge walks each
2960 // deduplicate only within themselves).
2961 std::unordered_set<TriId> seen;
2962 bool stop = false;
2963 for (const auto& e : c.orientedEdgesView()) {
2964 visitTrianglesIntersecting(e, [&](TriId t) -> bool {
2965 if (!seen.insert(t).second) {
2966 return false; // already reported by an earlier edge
2967 }
2968 stop = reportTriangle(f, indexOf(t));
2969 return stop;
2970 });
2971 if (stop) {
2972 break;
2973 }
2974 }
2975 return stop;
2976 }
2977
2978 // As for a region shape, the materialized form — trianglesIntersecting(c) —
2979 // and the interior-intersecting and edge variants are the shared wrappers
2980 // above, constrained on detail::TriangulationQuery.
2981
2982 // ---- point location --------------------------------------------------
2983
2997 template <PointConcept QueryPoint>
2998 [[nodiscard]] std::optional<TriangleType> locate(const QueryPoint& p) const {
2999 const TriId id = locateId(p);
3000 if (!id.valid()) {
3001 return std::nullopt; // outside the region, or in a hull-fill triangle
3002 }
3003 return getShape(id);
3004 }
3005
3006 // ---- predicates against the domain -----------------------------------
3007
3028 template <PointConcept QueryPoint>
3029 [[nodiscard]] bool contains(const QueryPoint& shape) const {
3030 // The domain is closed, so containing a point and meeting it are the same
3031 // question. Note that locate() would not answer it: for a point on the
3032 // domain boundary the visibility walk may land on the out-of-domain side,
3033 // and the point is contained all the same.
3034 return intersects(shape);
3035 }
3036
3038 template <detail::SegmentOrOriented S>
3039 [[nodiscard]] bool contains(const S& shape) const {
3040 if (!contains(shape[0]) || !contains(shape[1])) {
3041 return false;
3042 }
3043 return segmentInteriorContained(shape);
3044 }
3045
3046 private:
3047 // Segment containment once its endpoints are known to lie in the closed
3048 // domain. Kept separate because mesh vertices satisfy that precondition and
3049 // the convex-cover visibility hot path must not locate them repeatedly.
3050 template <detail::SegmentOrOriented S>
3051 [[nodiscard]] bool segmentInteriorContained(const S& shape) const {
3052 // Both endpoints are in the domain, so the segment can only escape between
3053 // them, and the walk sees every triangle it crosses on the way. It escapes
3054 // iff it (a) enters the interior of an out-of-domain hull-fill triangle,
3055 // (b) runs along an edge with no in-domain side — a diagonal of the fill
3056 // region, which no interior is entered by — or (c) leaves the triangulated
3057 // region altogether, which the walk reports as a step into a ghost. Merely
3058 // touching a fill triangle (along a boundary edge, or at a boundary vertex
3059 // whose fan the walk sweeps) is not an escape: the boundary belongs to the
3060 // domain.
3061 return !visitTriangleIdsIntersecting<true>(shape, [&](TriIndex t) {
3062 if (isGhost(t)) {
3063 return true; // (c)
3064 }
3065 if (inDomain(t)) {
3066 return false;
3067 }
3068 if (triangleValue(t).interiorsIntersect(shape)) {
3069 return true; // (a)
3070 }
3071 for (std::int8_t side = 0; side < 3; ++side) {
3072 const Edge e{t, side};
3073 if (!edgeInDomain(e) && shape.interiorsIntersect(edgeSegment(e))) {
3074 return true; // (b)
3075 }
3076 }
3077 return false;
3078 });
3079 }
3080
3081 public:
3082
3084 template <detail::ChainTraversal C>
3085 [[nodiscard]] bool contains(const C& shape) const {
3086 if (shape.empty()) {
3087 return true;
3088 }
3089 if (shape.size() < 2) {
3090 return contains(shape[0]);
3091 }
3092 return containsBoundary(shape.orientedEdgesView(), shape);
3093 }
3094
3096 template <detail::PolygonalRegion Q>
3097 [[nodiscard]] bool contains(const Q& shape) const {
3098 // A closed boundary inside the domain encloses nothing outside it — see
3099 // detail::PolygonalRegion — so the edges decide, as long as the domain is
3100 // simply connected.
3101 if (!containsBoundary(shape.edges(), shape)) {
3102 return false;
3103 }
3104 // A region domain is not simply connected, and the one thing its holes
3105 // let a contained boundary enclose is a hole. The shape's boundary is
3106 // inside the domain, hence clear of every hole interior, so each hole is
3107 // wholly inside the shape or wholly outside it and one witness triangle
3108 // per hole settles it.
3109 for (const TriangleType& witness : holeWitnesses_) {
3110 if (shape.contains(witness)) {
3111 return false;
3112 }
3113 }
3114 return true;
3115 }
3116
3118 template <PolygonWithHolesConcept Q>
3119 [[nodiscard]] bool contains(const Q& shape) const {
3120 if (shape.outer().empty()) {
3121 return true;
3122 }
3123 bool traced = false;
3124 for (const auto& edge : shape.edges()) {
3125 if (edge[0] == edge[1]) {
3126 continue;
3127 }
3128 traced = true;
3129 if (!contains(edge)) {
3130 return false;
3131 }
3132 }
3133 if (!traced && !contains(shape.outer()[0])) {
3134 return false;
3135 }
3136 for (const TriangleType& witness : holeWitnesses_) {
3137 if (shape.contains(witness)) {
3138 return false;
3139 }
3140 }
3141 return true;
3142 }
3143
3145 template <PolygonSetConcept Q>
3146 [[nodiscard]] bool contains(const Q& shape) const {
3147 for (const auto& component : shape.components()) {
3148 if (!contains(component)) {
3149 return false;
3150 }
3151 }
3152 return true;
3153 }
3154
3156 template <HalfplaneIntersectionConcept Q>
3157 [[nodiscard]] bool contains(const Q& shape) const {
3158 if (shape.empty()) {
3159 return true;
3160 }
3161 if (!shape.isBounded()) {
3162 return false;
3163 }
3164 return contains(shape.template asConvex<division_result_t<typename Q::NumberType>>());
3165 }
3166
3168 template <DiskConcept D>
3169 [[nodiscard]] bool contains(const D& shape) const {
3170 // A disk has no edges to trace, so it is grown by the region flood fill
3171 // instead. The domain is closed, so the disk lies in it iff its *interior*
3172 // does, and the open disk escapes iff it reaches into the interior of an
3173 // out-of-domain hull-fill triangle or crosses out through a boundary edge
3174 // of the triangulated region (one whose far side is a ghost). Neither
3175 // fires on a disk that only touches the domain boundary — an open disk on
3176 // one side of a line never meets that line — so containment also demands
3177 // that the open disk actually reach inside: a disk resting against the
3178 // boundary from the outside, meeting the domain in a single point, escapes
3179 // through no triangle and no edge, and is still not contained.
3180 const std::vector<TriIndex> seeds = seedTrianglesIntersecting(shape);
3181 if (seeds.empty()) {
3182 return false; // the disk misses the triangulated region entirely
3183 }
3184 bool reachesInside = false;
3185 const bool escapes = floodTriangleIdsIntersecting<true>(shape, seeds, [&](TriIndex t) {
3186 const TriangleType triangle = triangleValue(t);
3187 if (triangle.interiorsIntersect(shape)) {
3188 if (!inDomain(t)) {
3189 return true; // reaches into the hull-fill region
3190 }
3191 reachesInside = true;
3192 }
3193 for (std::int8_t side = 0; side < 3; ++side) {
3194 if (isGhost(triangles_[t].nbr[side]) &&
3195 shape.interiorsIntersect(edgeSegment(Edge{t, side}))) {
3196 return true; // crosses out through the boundary of the region
3197 }
3198 }
3199 return false;
3200 });
3201 // reachesInside is complete only when the fill ran to the end, which is
3202 // exactly when nothing escaped.
3203 return !escapes && reachesInside;
3204 }
3205
3207 template <class U>
3208 requires detail::LineOrOriented<U> || RayConcept<U> || HalfplaneConcept<U>
3209 [[nodiscard]] bool contains(const U&) const {
3210 return false;
3211 }
3212
3214 template <EmptyShapeConcept E>
3215 [[nodiscard]] bool contains(const E&) const {
3216 return true;
3217 }
3218
3220 [[nodiscard]] bool contains(const Shape<PointType>& shape) const {
3221 return std::visit([this](const auto& s) { return this->contains(s); }, shape.variant());
3222 }
3223
3234 template <detail::TriangulationQuery Q>
3235 [[nodiscard]] bool intersects(const Q& shape) const {
3236 return visitTrianglesIntersecting(shape, [](const TriangleType&) { return true; });
3237 }
3238
3240 template <PolygonConcept Q>
3241 [[nodiscard]] bool intersects(const Q& shape) const {
3242 // A polygon is not a traversal query (it need not be convex), so it is
3243 // taken edge by edge. If its boundary misses the domain entirely then
3244 // each connected piece of the domain is either inside the polygon or
3245 // disjoint from it, and one vertex of each settles which.
3246 for (const auto& e : shape.edges()) {
3247 if (e[0] != e[1] && intersects(e)) {
3248 return true;
3249 }
3250 }
3251 return anyDomainComponent(
3252 [&](TriIndex t) { return shape.contains(vertices_[triangles_[t].v[0]]); });
3253 }
3254
3256 template <PolygonWithHolesConcept Q>
3257 [[nodiscard]] bool intersects(const Q& shape) const {
3258 if (shape.outer().empty()) {
3259 return false;
3260 }
3261 for (const auto& edge : shape.edges()) {
3262 if (edge[0] != edge[1] && intersects(edge)) {
3263 return true;
3264 }
3265 }
3266 return anyDomainComponent(
3267 [&](TriIndex t) { return shape.contains(vertices_[triangles_[t].v[0]]); });
3268 }
3269
3271 template <PolygonSetConcept Q>
3272 [[nodiscard]] bool intersects(const Q& shape) const {
3273 for (const auto& component : shape.components()) {
3274 if (intersects(component)) {
3275 return true;
3276 }
3277 }
3278 return false;
3279 }
3280
3282 template <HalfplaneIntersectionConcept Q>
3283 [[nodiscard]] bool intersects(const Q& shape) const {
3284 for (TriIndex t = 0; t < static_cast<TriIndex>(triangles_.size()); ++t) {
3285 if (inDomain(t) && shape.intersects(triangleValue(t))) {
3286 return true;
3287 }
3288 }
3289 return false;
3290 }
3291
3293 template <EmptyShapeConcept E>
3294 [[nodiscard]] bool intersects(const E&) const {
3295 return false;
3296 }
3297
3299 [[nodiscard]] bool intersects(const Shape<PointType>& shape) const {
3300 return std::visit([this](const auto& s) { return this->intersects(s); }, shape.variant());
3301 }
3302
3314 template <detail::TriangulationQuery Q>
3315 [[nodiscard]] bool interiorContains(const Q& shape) const {
3316 return contains(shape) && !meetsDomainBoundary(shape);
3317 }
3318
3320 template <PolygonConcept Q>
3321 [[nodiscard]] bool interiorContains(const Q& shape) const {
3322 // As for contains(), the edges decide: a polygon lying in the domain can
3323 // only touch ∂D along its own boundary. An interior point of the polygon
3324 // touching ∂D would have points outside the domain arbitrarily close to it,
3325 // yet a whole neighbourhood of it lies in the polygon, hence in the domain.
3326 return containsBoundary<true>(shape.edges(), shape);
3327 }
3328
3330 template <PolygonWithHolesConcept Q>
3331 [[nodiscard]] bool interiorContains(const Q& shape) const {
3332 if (shape.outer().empty()) {
3333 return true;
3334 }
3335 bool traced = false;
3336 for (const auto& edge : shape.edges()) {
3337 if (edge[0] == edge[1]) {
3338 continue;
3339 }
3340 traced = true;
3341 if (!interiorContains(edge)) {
3342 return false;
3343 }
3344 }
3345 if (!traced && !interiorContains(shape.outer()[0])) {
3346 return false;
3347 }
3348 for (const TriangleType& witness : holeWitnesses_) {
3349 if (shape.contains(witness)) {
3350 return false;
3351 }
3352 }
3353 return true;
3354 }
3355
3357 template <PolygonSetConcept Q>
3358 [[nodiscard]] bool interiorContains(const Q& shape) const {
3359 for (const auto& component : shape.components()) {
3360 if (!interiorContains(component)) {
3361 return false;
3362 }
3363 }
3364 return true;
3365 }
3366
3368 template <HalfplaneIntersectionConcept Q>
3369 [[nodiscard]] bool interiorContains(const Q& shape) const {
3370 if (shape.empty()) {
3371 return true;
3372 }
3373 if (!shape.isBounded()) {
3374 return false;
3375 }
3376 if (!contains(shape)) {
3377 return false;
3378 }
3379 return interiorContains(shape.template asConvex<division_result_t<typename Q::NumberType>>());
3380 }
3381
3383 template <EmptyShapeConcept E>
3384 [[nodiscard]] bool interiorContains(const E&) const {
3385 return true;
3386 }
3387
3389 [[nodiscard]] bool interiorContains(const Shape<PointType>& shape) const {
3390 return std::visit([this](const auto& s) { return this->interiorContains(s); },
3391 shape.variant());
3392 }
3393
3404 template <PointConcept QueryPoint>
3405 [[nodiscard]] bool interiorsIntersect(const QueryPoint& shape) const {
3406 return interiorContains(shape); // a point's interior is the point itself
3407 }
3408
3410 template <detail::TriangulationQuery Q>
3411 requires(!PointConcept<Q>)
3412 [[nodiscard]] bool interiorsIntersect(const Q& shape) const {
3413 // The interior of the domain is the open triangles plus the relative
3414 // interiors of the interior edges (those with an in-domain triangle on
3415 // both sides) plus the interior vertices — and the vertices are redundant
3416 // here: a shape of positive dimension whose interior reaches one of them
3417 // also reaches, arbitrarily close by, an open triangle or an interior
3418 // edge. (For a point it would not, which is why a point is its own case.)
3419 return visitTrianglesIntersecting(shape, [&](const TriangleType& t) {
3420 if (t.interiorsIntersect(shape)) {
3421 return true;
3422 }
3423 return anyEdgeOf(t, [&](const SegmentType& e, Edge handle) {
3424 return interiorEdge(handle) && e.interiorsIntersect(shape);
3425 });
3426 });
3427 }
3428
3430 template <PolygonConcept Q>
3431 [[nodiscard]] bool interiorsIntersect(const Q& shape) const {
3432 // Edge by edge, as for the other polygon overloads. If no edge reaches the
3433 // domain's interior, then the polygon's boundary misses it entirely, and
3434 // the interiors meet iff some connected piece of the domain lies inside
3435 // the polygon — one triangle of each piece settles it.
3436 for (const auto& e : shape.edges()) {
3437 if (e[0] != e[1] && interiorsIntersect(e)) {
3438 return true;
3439 }
3440 }
3441 return anyDomainComponent(
3442 [&](TriIndex t) { return shape.interiorsIntersect(triangleValue(t)); });
3443 }
3444
3446 template <PolygonWithHolesConcept Q>
3447 [[nodiscard]] bool interiorsIntersect(const Q& shape) const {
3448 if (shape.outer().empty()) {
3449 return false;
3450 }
3451 for (const auto& edge : shape.edges()) {
3452 if (edge[0] != edge[1] && interiorsIntersect(edge)) {
3453 return true;
3454 }
3455 }
3456 return anyDomainComponent(
3457 [&](TriIndex t) { return shape.interiorsIntersect(triangleValue(t)); });
3458 }
3459
3461 template <PolygonSetConcept Q>
3462 [[nodiscard]] bool interiorsIntersect(const Q& shape) const {
3463 for (const auto& component : shape.components()) {
3464 if (interiorsIntersect(component)) {
3465 return true;
3466 }
3467 }
3468 return false;
3469 }
3470
3472 template <HalfplaneIntersectionConcept Q>
3473 [[nodiscard]] bool interiorsIntersect(const Q& shape) const {
3474 for (TriIndex t = 0; t < static_cast<TriIndex>(triangles_.size()); ++t) {
3475 if (inDomain(t) && triangleValue(t).interiorsIntersect(shape)) {
3476 return true;
3477 }
3478 }
3479 return false;
3480 }
3481
3483 template <EmptyShapeConcept E>
3484 [[nodiscard]] bool interiorsIntersect(const E&) const {
3485 return false;
3486 }
3487
3489 [[nodiscard]] bool interiorsIntersect(const Shape<PointType>& shape) const {
3490 return std::visit([this](const auto& s) { return this->interiorsIntersect(s); },
3491 shape.variant());
3492 }
3493
3494 // ---- constrained edges ----------------------------------------------
3495
3497 [[nodiscard]] bool isConstrained(const SegmentType& s) const {
3498 auto se = segmentMap().find(s);
3499 return se != segmentMap().end() && bit(triangles_[se->second.tri].constrainedMask, se->second.side);
3500 }
3501
3503 void setConstrained(const SegmentType& s, bool value = true) {
3504 auto se = segmentMap().find(s);
3505 if (se == segmentMap().end()) {
3506 return;
3507 }
3508 const Edge e = se->second;
3509 setBit(triangles_[e.tri].constrainedMask, e.side, value);
3510 const Edge m = mirror(e);
3511 if (m.tri != NO_TRI) {
3512 setBit(triangles_[m.tri].constrainedMask, m.side, value);
3513 }
3514 }
3515
3516 // ---- labels ----------------------------------------------------------
3517
3530 template <class L = TriangleLabel>
3531 requires(detail::has_label_v<L>)
3532 [[nodiscard]] L& label(const TriangleType& t) {
3533 const TriIndex id = idOf(t);
3534 assert(inDomain(id) && "label(): triangle is not part of the triangulation");
3535 return triangles_[id].triLabel;
3536 }
3537
3539 template <class L = TriangleLabel>
3540 requires(detail::has_label_v<L>)
3541 [[nodiscard]] const L& label(const TriangleType& t) const {
3542 const TriIndex id = idOf(t);
3543 assert(inDomain(id) && "label(): triangle is not part of the triangulation");
3544 return triangles_[id].triLabel;
3545 }
3546
3559 template <class L = SegmentLabel>
3560 requires(detail::has_label_v<L>)
3561 [[nodiscard]] L& label(const SegmentType& s) {
3562 auto se = segmentMap().find(s);
3563 assert(se != segmentMap().end() && "label(): segment is not an edge of the triangulation");
3564 return se->second.segLabel;
3565 }
3566
3568 template <class L = SegmentLabel>
3569 requires(detail::has_label_v<L>)
3570 [[nodiscard]] const L& label(const SegmentType& s) const {
3571 auto se = segmentMap().find(s);
3572 assert(se != segmentMap().end() && "label(): segment is not an edge of the triangulation");
3573 return se->second.segLabel;
3574 }
3575
3576 // ---- mutation --------------------------------------------------------
3577
3579 [[nodiscard]] bool flippable(const SegmentType& s) const {
3580 auto se = segmentMap().find(s);
3581 return se != segmentMap().end() && flippableEdge(se->second);
3582 }
3583
3594 std::optional<SegmentType> flip(const SegmentType& s) {
3595 auto se = segmentMap().find(s);
3596 if (se == segmentMap().end()) {
3597 return std::nullopt;
3598 }
3599 const Edge e = se->second;
3600 if (!flippableEdge(e)) {
3601 return std::nullopt;
3602 }
3603 const TriIndex t = e.tri;
3604 const TriIndex t2 = mirror(e).tri;
3605 flipEdge(e);
3606 // The shared edge is gone; re-register the six sides of the two
3607 // rewritten triangles (the four surrounding edges get fresh handles and
3608 // the new diagonal is added).
3609 segmentMap().erase(s);
3610 registerSides(t);
3611 registerSides(t2);
3612 ++revision_;
3613 return edgeSegment(Edge{t, 1}); // new diagonal (side opposite a in t)
3614 }
3615
3628 template <class EdgeRange>
3630 [[nodiscard]] bool flippable(const EdgeRange& edges) const {
3631 std::unordered_set<TriIndex> claimed; // triangles already covered by some quad
3632 for (const auto& s : edges) {
3633 const auto se = segmentMap().find(SegmentType(s[0], s[1]));
3634 if (se == segmentMap().end() || !flippableEdge(se->second)) {
3635 return false;
3636 }
3637 const Edge e = se->second;
3638 if (!claimed.insert(e.tri).second || !claimed.insert(mirror(e).tri).second) {
3639 return false; // a quad shares a triangle with an earlier one
3640 }
3641 }
3642 return true;
3643 }
3644
3659 template <class EdgeRange>
3661 std::optional<std::vector<SegmentType>> flip(const EdgeRange& edges) {
3662 if (!flippable(edges)) {
3663 return std::nullopt;
3664 }
3665 std::vector<SegmentType> diagonals;
3666 for (const auto& s : edges) {
3667 diagonals.push_back(*flip(SegmentType(s[0], s[1]))); // disjoint: each succeeds
3668 }
3669 return diagonals;
3670 }
3671
3672 // ---- point insertion -------------------------------------------------
3673
3703 bool insert(const PointType& p) {
3704 const bool inserted = insertVertexImpl(p).has_value();
3705 if (inserted) {
3706 ++revision_;
3707 }
3708 return inserted;
3709 }
3710
3730 bool insertDelaunay(const PointType& p) {
3731 const auto inserted = insertVertexImpl(p);
3732 if (!inserted) {
3733 return false;
3734 }
3735 const auto [vp, start] = *inserted;
3736 // The suspect edges are the new vertex's link: the side opposite vp in
3737 // every real triangle of its fan.
3738 std::vector<SegmentType> suspect;
3739 visitVertexFan(start, vp, [&](TriIndex cur) {
3740 if (!isGhost(cur)) {
3741 suspect.push_back(
3742 edgeSegment(Edge{cur, static_cast<std::int8_t>(localIndex(cur, vp))}));
3743 }
3744 });
3745 legalize(suspect);
3746 ++revision_;
3747 return true;
3748 }
3749
3750 // ---- validation ------------------------------------------------------
3751
3756 [[nodiscard]] bool checkInvariants() const {
3757 for (TriIndex t = 0; t < static_cast<TriIndex>(triangles_.size()); ++t) {
3758 const auto& T = triangles_[t];
3759 if (!isGhost(t) &&
3760 !(orientationSign(vertices_[T.v[0]], vertices_[T.v[1]], vertices_[T.v[2]]) > 0)) {
3761 return false;
3762 }
3763 for (int i = 0; i < 3; ++i) {
3764 const TriIndex t2 = T.nbr[i];
3765 if (t2 == NO_TRI) {
3766 continue;
3767 }
3768 int j = -1;
3769 for (int s = 0; s < 3; ++s) {
3770 if (triangles_[t2].nbr[s] == t) {
3771 j = s;
3772 }
3773 }
3774 if (j < 0) {
3775 return false; // link not mutual
3776 }
3777 const VertexIndex a = T.v[(i + 1) % 3];
3778 const VertexIndex b = T.v[(i + 2) % 3];
3779 const VertexIndex c = triangles_[t2].v[(j + 1) % 3];
3780 const VertexIndex d = triangles_[t2].v[(j + 2) % 3];
3781 if (!((a == c && b == d) || (a == d && b == c))) {
3782 return false; // links disagree on the shared edge
3783 }
3784 }
3785 }
3786 return true;
3787 }
3788
3795 friend Canvas& operator<<(Canvas& canvas, const Triangulation& triangulation) {
3796 for (const auto &t : triangulation.triangles()) {
3797 canvas << t;
3798 }
3799 return canvas;
3800 }
3801
3802 private:
3803 // ---- internal vocabulary (never exposed) -----------------------------
3804
3805 using VertexIndex = std::int32_t; // index into vertices_
3806 using TriIndex = std::int32_t; // index into triangles_
3807 static constexpr TriIndex NO_TRI = -1;
3808 static constexpr VertexIndex NO_VERTEX = -1;
3809
3810 // A directed reference to one side of one triangle: `side` selects the edge
3811 // opposite vertex `side`, i.e. { v[(side+1)%3], v[(side+2)%3] }. As the value
3812 // stored in segToEdge_ it also carries that edge's label.
3813 struct Edge {
3814 TriIndex tri = NO_TRI;
3815 std::int8_t side = 0;
3816 [[no_unique_address]] SegmentLabel segLabel{};
3817 };
3818
3819 // One triangle record: three CCW vertices, three neighbors (neighbor i is
3820 // across the edge opposite v[i]), a constrained-edge mask, an out-of-domain
3821 // flag (set for hull-fill triangles outside a polygon), a transient
3822 // walk-visited mark (used by visitTrianglesIntersecting in lieu of a hash
3823 // set; always cleared again before that const method returns), and the
3824 // label. `walkMark` is mutable because the walk is a const query.
3825 struct Tri {
3826 std::array<VertexIndex, 3> v{NO_TRI, NO_TRI, NO_TRI};
3827 std::array<TriIndex, 3> nbr{NO_TRI, NO_TRI, NO_TRI};
3828 std::uint8_t constrainedMask = 0;
3829 std::uint8_t outOfDomain = 0;
3830 mutable std::uint8_t walkMark = 0;
3831 [[no_unique_address]] TriangleLabel triLabel{};
3832 };
3833 static_assert(sizeof(Tri) == 28 || detail::has_label_v<TriangleLabel>,
3834 "Tri should stay 28 bytes when unlabeled");
3835
3836 // The coordinate type the vertex approximations are kept for: the in-circle
3837 // test promotes furthest of the predicates run here, so it is the one whose
3838 // gate decides whether keeping them is worth it at all. A predicate that
3839 // promotes less reads its own gate and simply ignores them.
3840 using VertexCoordinate = detail::incircle_coordinate_t<typename PointType::NumberType>;
3841
3842 // Rebuilds every approximation, after vertices_ is reordered or refilled.
3843 void syncVertexApproximations() {
3844 if constexpr (detail::filtersSign<VertexCoordinate>) {
3845 vertexApproximations_.clear();
3846 vertexApproximations_.reserve(vertices_.size());
3847 for (const auto& vertex : vertices_) {
3848 vertexApproximations_.push_back(detail::approximatePoint(vertex));
3849 }
3850 }
3851 }
3852
3853 // Appends a vertex and its approximation together.
3854 VertexIndex appendVertex(const PointType& p) {
3855 const VertexIndex id = static_cast<VertexIndex>(vertices_.size());
3856 vertices_.push_back(p);
3857 if constexpr (detail::filtersSign<VertexCoordinate>) {
3858 vertexApproximations_.push_back(detail::approximatePoint(p));
3859 }
3860 return id;
3861 }
3862
3863 // A vertex paired with the approximation kept for it, for the sign
3864 // predicates in detail:: that take filtered operands.
3865 [[nodiscard]] auto filteredVertex(VertexIndex v) const {
3866 const auto index = static_cast<std::size_t>(v);
3867 return detail::filtered<VertexCoordinate>(vertices_[index], vertexApproximations_, index);
3868 }
3869
3870 // A point that is not a vertex — a query, or one about to be inserted —
3871 // converted once for however many signs read it. Its own coordinate type
3872 // joins the gate: a rational query point against `int` vertices promotes
3873 // the predicates that read it into the range where filtering pays, even
3874 // though the vertices alone would not.
3875 template <class QueryPoint>
3876 [[nodiscard]] static auto filteredPoint(const QueryPoint& p) {
3877 using QueryCoordinate =
3878 detail::incircle_coordinate_t<typename PointType::NumberType,
3879 typename QueryPoint::NumberType>;
3880 return detail::filtered<QueryCoordinate>(p);
3881 }
3882
3883 std::vector<PointType> vertices_; // ghost vertex at index 0 (when nonempty),
3884 // then the real vertices; inserts append
3885 // Approximations of vertices_, parallel to it, or empty where the filter
3886 // would not pay for itself. Every sign predicate here reads its operands
3887 // out of vertices_ and the same vertices come up again and again — the walk
3888 // revisits them, the flood-fill revisits them, each legalization revisits
3889 // them — so converting a coordinate to double once per vertex rather than
3890 // once per predicate is most of what the filter costs. Kept in step with
3891 // vertices_ by syncVertexApproximations and appendVertex.
3892 std::vector<detail::ApproximatePoint> vertexApproximations_;
3893 std::vector<Tri> triangles_; // real triangles [0,firstGhost_), then ghost triangles
3894 // Outside edge -> internal handle, reached through @ref segmentMap. Mutable
3895 // and paired with `mapStale_` because a bulk build leaves it unfilled: see
3896 // @ref materializeSegmentMap.
3897 mutable std::unordered_map<SegmentType, Edge> segToEdge_;
3898 mutable bool mapStale_ = false;
3899 // One triangle inside each hole of a region domain, empty otherwise. A
3900 // closed boundary drawn inside such a domain can enclose a hole, which the
3901 // boundary alone never reveals, so the region queries consult these.
3902 std::vector<TriangleType> holeWitnesses_;
3903 // One real triangle incident to each vertex, indexed by VertexIndex — the seed
3904 // a fan rotation needs to reach a vertex's neighbourhood without scanning
3905 // every triangle. Only a *hint*: it is refreshed by registerSides and
3906 // verified by incidentTriangleOf, so an entry left stale by an edit that
3907 // does not re-register costs a fallback, never an answer.
3908 std::vector<TriIndex> vertexTri_;
3909 static constexpr VertexIndex GHOST = 0; // index of the ghost vertex
3910 TriIndex firstGhost_ = 0; // first ghost triangle index
3911 std::size_t domainTriangleCount_ = 0; // in-domain real triangles (<= firstGhost_)
3912 mutable TriIndex hint_ = NO_TRI; // last located triangle (walk seed)
3913 mutable std::mt19937 rng_; // drives the stochastic walk in locateIndex
3914 // The Kirkpatrick hierarchy: a stack of triangulations of the same box, the
3915 // finest being the mesh (plus the ring filling the box around it) and each
3916 // coarser one obtained by removing an independent set of vertices and
3917 // retriangulating the holes. A query descends it one cell per level.
3918 //
3919 // Every cell above level 0 is a triangle of the retriangulation of one
3920 // removed vertex's star, so the cells below it are consecutive triangles of
3921 // that star's fan, and which of them holds a point already known to be in
3922 // the parent is decided by where the point falls in the fan — one sign per
3923 // fan edge crossed, and none at all where the parent has a single child.
3924 // That is what a cell stores: the removed vertex, and the run of fan edges
3925 // and cells between them. It needs no triangle of its own; only the top
3926 // level, whose cells a query is not yet known to be inside, keeps one.
3927 struct Kirkpatrick {
3928 // Set in runCount when the run closes the whole fan, which happens for
3929 // the one triangle of a retriangulation that holds the removed vertex:
3930 // the run then spans a full turn instead of an arc, so a query cannot
3931 // assume it starts inside it. On a cell with no children — where there
3932 // is no run to mark — the same bit says the cell is one of those
3933 // filling the box rather than a mesh triangle, so what it carries only
3934 // starts the walk instead of answering it.
3935 static constexpr std::uint32_t FULL_TURN = 0x80000000u;
3936 struct Cell {
3937 std::uint32_t apex = 0; // the removed vertex the fan turns around
3938 std::uint32_t runBegin = 0; // where the run starts, or a leaf's seed
3939 std::uint32_t runCount = 0; // children, 0 for a leaf, plus FULL_TURN
3940 };
3941 std::vector<Cell> cells;
3942 // The fan runs, one per cell with children: the vertices bounding the
3943 // fan triangles, interleaved with the cells between them, as
3944 // `link, cell, link, cell, ..., link` — one more vertex than cells.
3945 std::vector<std::uint32_t> run;
3946 std::vector<std::uint32_t> roots; // the top level
3947 std::vector<std::array<VertexIndex, 3>> rootShape; // and its triangles
3948 // The four box corners, addressed by vertex indices vertices_.size() + i.
3949 std::vector<PointType> extra;
3950 std::vector<detail::ApproximatePoint> extraApprox;
3951 };
3952 // Immutable once built, so copies share it rather than redrawing it.
3953 std::shared_ptr<const Kirkpatrick> pointLocation_;
3954
3955 // Whether the descent's signs are exact, and its answer therefore final. An
3956 // integer coordinate's are — the promotion holds every product the
3957 // orientation forms — and so are a coordinate type carrying its own exact
3958 // arithmetic. Everything else, a fixed-width rational (whose products
3959 // overflow) as much as a floating-point coordinate, can put the descent in
3960 // a triangle beside the right one, and there the walk finishes the query
3961 // exactly as it does for a mesh the hierarchy has fallen behind.
3962 static constexpr bool exactDescent =
3963 detail::extended_integral<NumberType> || detail::arbitraryPrecision<NumberType>;
3964
3965 // The largest star a level takes off. A Delaunay vertex has six triangles
3966 // around it on average and all but a handful have ten or fewer, so a bound
3967 // here only turns away the rare crowded vertex — which its own neighbours
3968 // would usually have blocked anyway. Query time measures the same over
3969 // 6..20 at 100,000 vertices, and from 12 up the hierarchy comes out
3970 // identical; ten sits in the middle of that plateau.
3971 static constexpr std::size_t pointLocationMaxStar = 10;
3972
3973 // Where the hierarchy stops growing. Its top level is scanned triangle by
3974 // triangle, so it has to stay small; anything from 2 to 16 measures the
3975 // same, the levels that would replace the scan costing about what the scan
3976 // does, and above that the scan starts to show (64 triangles cost a tenth
3977 // of the query).
3978 static constexpr std::size_t pointLocationTopSize = 8;
3979
3980 // Counts the structural edits — the ones that move vertices or connectivity
3981 // on from what the index was drawn against. Nothing invalidates the index,
3982 // which stays correct across any edit; this only tells buildPointLocation
3983 // whether redrawing would find anything new, and answers
3984 // hasCurrentPointLocation. Constraint flags do not count: they change no
3985 // geometry a walk can see.
3986 std::size_t revision_ = 0;
3987 std::size_t pointLocationRevision_ = 0;
3988
3989 // Where the query lands in the hierarchy: the mesh triangle holding it, and
3990 // whether that is the answer or only where the walk should start. It is the
3991 // answer when the descent settled the query — it reached the triangle
3992 // itself, or left the box altogether — and was exact and drawn against the
3993 // mesh as it now stands. Otherwise the triangle is still one of this mesh,
3994 // and still beside the query, so the walk goes on from there; and where
3995 // there is neither index nor triangle, the walk starts where it would have
3996 // without one.
3997 [[nodiscard]] TriIndex pointLocationSeed(const PointType& p, bool& answered) const {
3998 answered = false;
3999 if (!pointLocation_) {
4000 return NO_TRI;
4001 }
4002 bool settled = false;
4003 const TriIndex seed = kirkpatrickSeed(p, settled);
4004 answered = settled && exactDescent && pointLocationRevision_ == revision_;
4005 return realTriangle(seed) ? seed : NO_TRI;
4006 }
4007
4008 // A hierarchy vertex, filtered: the mesh's own vertices keep the stored
4009 // approximation, the four box corners the hierarchy's own.
4010 [[nodiscard]] auto kirkpatrickVertex(const Kirkpatrick& kp, VertexIndex v) const {
4011 const std::size_t i = static_cast<std::size_t>(v);
4012 const std::size_t meshVertices = vertices_.size();
4013 return i < meshVertices
4014 ? filteredVertex(v)
4015 : detail::filtered<VertexCoordinate>(kp.extra[i - meshVertices],
4016 kp.extraApprox, i - meshVertices);
4017 }
4018
4019 // Where the descent lands: one cell per level, ending on the level-0 cell
4020 // whose closure holds the query. @p settled says the descent decided the
4021 // query rather than only narrowing it — it ended on a mesh triangle, or off
4022 // the box, which the hull is strictly inside of — as against ending on a
4023 // triangle filling the box, whose closure holds a point of the hull's
4024 // boundary too, and whose answer is therefore the neighbouring mesh
4025 // triangle it seeds the walk with.
4026 template <class QueryPoint>
4027 [[nodiscard]] TriIndex kirkpatrickSeed(const QueryPoint& p, bool& settled) const {
4028 const Kirkpatrick& kp = *pointLocation_;
4029 settled = false;
4030 // The descent tests p against several fan edges per level, so p is
4031 // converted once here rather than once per sign.
4032 const auto q = filteredPoint(p);
4033 // The fan scan turns about one vertex, so that one is converted once per
4034 // cell and only the edge's far end is fetched per sign.
4035 const auto leftOfFrom = [&](const auto& a, VertexIndex b) {
4036 return !(detail::orientationSignOf(a, kirkpatrickVertex(kp, b), q).value() < 0);
4037 };
4038 const auto leftOf = [&](VertexIndex a, VertexIndex b) {
4039 return leftOfFrom(kirkpatrickVertex(kp, a), b);
4040 };
4041
4042 // The top level is the only one a query has to be placed in rather than
4043 // handed down into, so it is the only one whose triangles are tested.
4044 std::uint32_t cell = ~0u;
4045 for (std::size_t i = 0; i < kp.roots.size(); ++i) {
4046 const auto& corners = kp.rootShape[i];
4047 if (leftOf(corners[0], corners[1]) && leftOf(corners[1], corners[2]) &&
4048 leftOf(corners[2], corners[0])) {
4049 cell = kp.roots[i];
4050 break;
4051 }
4052 }
4053 if (cell == ~0u) {
4054 settled = true;
4055 return NO_TRI; // outside the box, so outside the mesh
4056 }
4057
4058 for (;;) {
4059 const auto& current = kp.cells[cell];
4060 const std::uint32_t count = current.runCount & ~Kirkpatrick::FULL_TURN;
4061 if (count == 0) {
4062 settled = current.runCount == 0;
4063 return static_cast<TriIndex>(current.runBegin);
4064 }
4065 const std::uint32_t* const fan = kp.run.data() + current.runBegin;
4066 cell = fan[2 * count - 1]; // the last child, where the scan runs out
4067 if (count > 1) {
4068 const auto apex =
4069 kirkpatrickVertex(kp, static_cast<VertexIndex>(current.apex));
4070 // p is already known to be past the run's first edge — the
4071 // parent starts there — unless the run is a whole turn, which
4072 // starts nowhere in particular.
4073 bool inside = (current.runCount & Kirkpatrick::FULL_TURN) == 0 ||
4074 leftOfFrom(apex, static_cast<VertexIndex>(fan[0]));
4075 for (std::uint32_t m = 1; m < count; ++m) {
4076 const bool beyond = leftOfFrom(apex, static_cast<VertexIndex>(fan[2 * m]));
4077 if (inside && !beyond) {
4078 cell = fan[2 * m - 1];
4079 break;
4080 }
4081 inside = beyond;
4082 }
4083 }
4084 }
4085 }
4086
4087 // ---- small helpers ---------------------------------------------------
4088
4089 // ---- handles <-> internal indices ------------------------------------
4090
4091 // The public handle for an internal index, and back. An invalid handle
4092 // holds ~0u, which is exactly NO_TRI / NO_VERTEX read as unsigned, so both
4093 // directions are the plain cast and the invalid handle needs no special
4094 // case. A handle a caller made up out of range survives the round trip
4095 // unchanged and is then rejected by realTriangle / realVertex / inDomain.
4096 static constexpr TriId triHandle(TriIndex t) {
4097 return TriId(static_cast<std::uint32_t>(t));
4098 }
4099 static constexpr VertexId vertexHandle(VertexIndex v) {
4100 return VertexId(static_cast<std::uint32_t>(v));
4101 }
4102 static constexpr TriIndex indexOf(TriId t) { return static_cast<TriIndex>(t.index()); }
4103 static constexpr VertexIndex indexOf(VertexId v) {
4104 return static_cast<VertexIndex>(v.index());
4105 }
4106
4107 // Position within the stored triple of the vertex the public vertex order
4108 // starts at. The stored order is counterclockwise but starts anywhere;
4109 // rotating it onto the lexicographically smallest vertex is exactly what the
4110 // Triangle constructor does to the same three points, so the public order is
4111 // the one triangleValue() reports.
4112 [[nodiscard]] int firstVertex(TriIndex t) const {
4113 const auto& v = triangles_[static_cast<std::size_t>(t)].v;
4114 int first = 0;
4115 for (int i = 1; i < 3; ++i) {
4116 if (vertices_[static_cast<std::size_t>(v[i])] <
4117 vertices_[static_cast<std::size_t>(v[first])]) {
4118 first = i;
4119 }
4120 }
4121 return first;
4122 }
4123
4124 // The stored side index of public side `side`: the public sides follow the
4125 // public vertex order (side i joins public vertices i and i+1, as
4126 // Triangle::edges does), while a stored side is the one *opposite* its
4127 // vertex, which is the third one.
4128 [[nodiscard]] int internalSide(TriIndex t, int side) const {
4129 return (firstVertex(t) + side + 2) % 3;
4130 }
4131
4132 // True if the index refers to a stored real (non-ghost) triangle, whether
4133 // in the domain or a hull-fill one outside it.
4134 [[nodiscard]] bool realTriangle(TriIndex t) const { return t >= 0 && t < firstGhost_; }
4135
4136 // True if the index refers to a stored vertex other than the ghost.
4137 [[nodiscard]] bool realVertex(VertexIndex v) const {
4138 return v > GHOST && static_cast<std::size_t>(v) < vertices_.size();
4139 }
4140
4141 // Hands one triangle to a user visitor: a callable that accepts a Triangle
4142 // gets the value, one that accepts only a TriId gets the handle (a generic
4143 // callable accepts both and gets the value). Every triangle visitor of the
4144 // public interface reports through here, so the rule is the same for all.
4145 template <class Fn>
4146 bool reportTriangle(Fn& f, TriIndex t) const {
4147 if constexpr (std::is_invocable_v<Fn&, const TriangleType&>) {
4148 return detail::invokeVisitor(f, triangleValue(t));
4149 } else {
4150 return detail::invokeVisitor(f, triHandle(t));
4151 }
4152 }
4153
4154 // The triangle values of a list of handles, in the same order.
4155 [[nodiscard]] std::vector<TriangleType> trianglesOf(const std::vector<TriId>& ids) const {
4156 std::vector<TriangleType> out;
4157 out.reserve(ids.size());
4158 for (const TriId id : ids) {
4159 out.push_back(triangleValue(indexOf(id)));
4160 }
4161 return out;
4162 }
4163
4164 // The vertex p is stored as, or NO_VERTEX when p is not a vertex of the
4165 // triangulation (or lies outside the hull): locate p, then match it against
4166 // the vertices of the triangle found.
4167 [[nodiscard]] VertexIndex vertexIndexAt(const PointType& p) const {
4168 const TriIndex start = locateIndex(p);
4169 if (start == NO_TRI || isGhost(start)) {
4170 return NO_VERTEX; // empty triangulation, or p outside the hull
4171 }
4172 const auto& sv = triangles_[start].v;
4173 for (int i = 0; i < 3; ++i) {
4174 if (vertices_[sv[i]] == p) {
4175 return sv[i];
4176 }
4177 }
4178 return NO_VERTEX;
4179 }
4180
4181 // A real triangle of vertex w's fan, or NO_TRI when w carries none. The
4182 // per-vertex hint answers in O(1) when it still holds; a hint left stale by
4183 // an edit that did not re-register falls back on locating w's position.
4184 [[nodiscard]] TriIndex fanSeedOf(VertexIndex w) const {
4185 const TriIndex hinted = incidentTriangleOf(w);
4186 if (hinted != NO_TRI) {
4187 return hinted;
4188 }
4189 const TriIndex start = locateIndex(vertices_[static_cast<std::size_t>(w)]);
4190 if (start == NO_TRI) {
4191 return NO_TRI;
4192 }
4193 const auto& v = triangles_[start].v;
4194 return (v[0] == w || v[1] == w || v[2] == w) ? start : NO_TRI;
4195 }
4196
4197 // Reads bit `i` of a 3-bit edge mask (e.g. constrainedMask).
4198 static constexpr bool bit(std::uint8_t mask, int i) { return (mask >> i) & 1; }
4199 // Sets or clears bit `i` of a 3-bit edge mask.
4200 static constexpr void setBit(std::uint8_t& mask, int i, bool value) {
4201 mask = static_cast<std::uint8_t>(value ? (mask | (1u << i)) : (mask & ~(1u << i)));
4202 }
4203 // Packs three per-edge flags into a 3-bit mask (bit i is edge i).
4204 static constexpr std::uint8_t mask(bool b0, bool b1, bool b2) {
4205 return static_cast<std::uint8_t>((b0 ? 1 : 0) | (b1 ? 2 : 0) | (b2 ? 4 : 0));
4206 }
4207
4208 // True if t is one of the boundary-closing ghost triangles (stored last).
4209 [[nodiscard]] bool isGhost(TriIndex t) const { return t >= firstGhost_; }
4210
4211 // A real triangle that is part of the visible triangulation: not a ghost and
4212 // not a hull-fill triangle outside a polygon's boundary. The public view
4213 // (sizes, navigation, locate) speaks only of in-domain triangles, while the
4214 // internal walks still traverse every real triangle, including fill ones.
4215 [[nodiscard]] bool inDomain(TriIndex t) const {
4216 return t != NO_TRI && t < firstGhost_ && !triangles_[t].outOfDomain;
4217 }
4218
4219 // ---- visibility internals (defined in implementation/visibilitygraph.hpp) ---
4220
4221 // True if side `s` of in-domain triangle `t` stops sight: a constrained edge
4222 // is an opaque wall, and so is the boundary of the domain.
4223 [[nodiscard]] bool blocksVisibility(TriIndex t, int s) const {
4224 return bit(triangles_[t].constrainedMask, s) || !inDomain(triangles_[t].nbr[s]);
4225 }
4226
4227 // One step of the expansion: cross `side` of `tri` carrying the open cone
4228 // whose clockwise bound is the ray origin->right and whose counterclockwise
4229 // bound is origin->left. Both bounds are vertices, so every test the
4230 // traversal makes is one orientation predicate on stored points and no
4231 // coordinate is ever constructed. The crossed edge runs from its clockwise
4232 // end `tri.v[(side+1)%3]` to its counterclockwise end `tri.v[(side+2)%3]`.
4233 struct VisibilityCone {
4234 TriIndex tri;
4235 std::int8_t side;
4236 VertexIndex right;
4237 VertexIndex left;
4238 };
4239
4240 // Where an expansion starts from a query point: the cones covering the
4241 // directions that immediately enter the domain, sorted counterclockwise by
4242 // their clockwise bound and grouped into contiguous arcs — one arc per lobe
4243 // the visible region reaches the query point along. `direct` holds the
4244 // vertices visible without any expansion at all, each flagged when sight to
4245 // it only grazes (runs along the boundary or a wall) rather than passing
4246 // through the interior.
4247 struct VisibilitySeeds {
4248 std::vector<VisibilityCone> cones;
4249 std::vector<std::pair<VertexIndex, bool>> direct;
4250 std::vector<std::size_t> arcs; // index into `cones` where each arc opens
4251 bool located = false; // the query point lies in the domain
4252 bool fullTurn = false; // one arc, covering every direction
4253 };
4254
4255 [[nodiscard]] VisibilitySeeds visibilitySeeds(const PointType& query) const;
4256
4257 // An in-domain triangle whose closure holds `query`, starting from the
4258 // triangle `start` that locateIndex stopped at. That walk may stop on a ghost or
4259 // a hull-fill triangle when the query sits on the domain boundary — which
4260 // every polygon vertex does — so a boundary query needs this to find the
4261 // triangle it belongs to. NO_TRI when the query really is outside.
4262 [[nodiscard]] TriIndex inDomainTriangleAt(const PointType& query, TriIndex start) const;
4263
4264 // Drains the open-cone expansion of `start`, calling onVertex(VertexIndex) for
4265 // each clearly visible vertex met and onBlocked(VisibilityCone) for each
4266 // blocking edge a cone runs into. Leaves come out counterclockwise, which is
4267 // what lets regularizedVisiblePolygon assemble its ring without sorting.
4268 // `scratch` is the traversal stack, passed in so repeated calls reuse it.
4269 template <class OnVertex, class OnBlocked>
4270 void expandVisibility(const PointType& origin, VisibilityCone start,
4271 std::vector<VisibilityCone>& scratch,
4272 OnVertex onVertex, OnBlocked onBlocked) const;
4273
4274 // The vertices clearly visible from `query`, by id. `grazing` selects whether
4275 // the seeds reached along the boundary come too, which is what separates
4276 // visibleVertices from clearlyVisibleVertices.
4277 [[nodiscard]] std::vector<VertexIndex> visibleIds(const PointType& query,
4278 const VisibilitySeeds& seeds,
4279 bool grazing) const;
4280
4281 // Clear-visibility adjacency indexed by vertex id (slot GHOST stays empty),
4282 // built by one triangular expansion per vertex.
4283 [[nodiscard]] std::vector<std::vector<VertexIndex>> clearVisibleAdjacency() const;
4284
4285 // clearVisibleAdjacency plus the mesh's own blocking edges — together the
4286 // pairs that see each other with no vertex in between — closed along
4287 // collinear chains, which is the full visibility relation.
4288 [[nodiscard]] std::vector<std::vector<VertexIndex>> visibleAdjacency() const;
4289
4290 // The other endpoint of every wall incident to each vertex, indexed by
4291 // vertex id. Drives the tangency test of reducedVisibilityGraph.
4292 [[nodiscard]] std::vector<std::vector<VertexIndex>> wallNeighbors() const;
4293
4294 // Whether some line through vertex `m` stays in the domain on both sides of
4295 // it, so that a visibility segment can pass straight through. Only a
4296 // strictly convex corner of the domain fails. Deciding whether the collinear
4297 // closure need look at `m` at all, it is allowed to answer optimistically —
4298 // never the other way round.
4299 [[nodiscard]] bool passesThrough(VertexIndex m) const;
4300
4301 // The next mesh vertex met by the ray leaving `current` in the direction
4302 // `current - tail`, when the segment reaching it stays in the domain and
4303 // crosses no wall; GHOST when the ray leaves the domain first. `tail` is a
4304 // point rather than a vertex so a chain can start at a query point.
4305 [[nodiscard]] VertexIndex nextVertexAlongRay(const PointType& tail, VertexIndex current) const;
4306
4307 // Side of triangle x whose neighbor is `target` (x shares <=1 edge with it).
4308 [[nodiscard]] std::int8_t findSide(TriIndex x, TriIndex target) const {
4309 const auto& n = triangles_[x].nbr;
4310 return static_cast<std::int8_t>(n[0] == target ? 0 : (n[1] == target ? 1 : 2));
4311 }
4312
4313 // An edge belongs to the visible triangulation if at least one of its two
4314 // incident triangles is in-domain.
4315 [[nodiscard]] bool edgeInDomain(Edge e) const {
4316 return inDomain(e.tri) || inDomain(mirror(e).tri);
4317 }
4318
4319 // An edge of the domain's interior: an in-domain triangle on both sides, so a
4320 // neighbourhood of its relative interior lies in the domain.
4321 [[nodiscard]] bool interiorEdge(Edge e) const {
4322 return inDomain(e.tri) && inDomain(mirror(e).tri);
4323 }
4324
4325 // An edge of the domain's boundary (∂D): in-domain on exactly one side, the
4326 // other being a ghost or a hull-fill triangle.
4327 [[nodiscard]] bool boundaryEdge(Edge e) const { return edgeInDomain(e) && !interiorEdge(e); }
4328
4329 // Calls f(edge, handle) on the three edges of mesh triangle `t` until one
4330 // returns true; returns whether one did.
4331 template <class Fn>
4332 [[nodiscard]] bool anyEdgeOf(const TriangleType& t, Fn f) const {
4333 for (const auto& edge : t.edges()) {
4334 const SegmentType seg(edge[0], edge[1]);
4335 const auto se = segmentMap().find(seg);
4336 if (se != segmentMap().end() && f(seg, se->second)) {
4337 return true;
4338 }
4339 }
4340 return false;
4341 }
4342
4343 // True if `shape` touches the boundary of the domain. Sound for a shape known
4344 // to lie in the domain, which is where interiorContains() uses it: every
4345 // boundary edge such a shape meets belongs to an in-domain triangle it meets,
4346 // and the traversal reports all of those.
4347 template <detail::TriangulationQuery Q>
4348 [[nodiscard]] bool meetsDomainBoundary(const Q& shape) const {
4349 return visitTrianglesIntersecting(shape, [&](const TriangleType& t) {
4350 return anyEdgeOf(t, [&](const SegmentType& e, Edge handle) {
4351 return boundaryEdge(handle) && e.intersects(shape);
4352 });
4353 });
4354 }
4355
4356 // contains() — or, with Interior, interiorContains() — for a shape given by
4357 // its boundary edges: a chain, or a shape bounded by a closed polygonal chain.
4358 // Every edge of it must be contained. Zero-length edges are skipped, so a flat
4359 // rectangle (or a chain with a repeated vertex) is traced along the edges it
4360 // does have; one whose edges are all degenerate has collapsed to a single
4361 // point, which then decides.
4362 template <bool Interior = false, class EdgeRange, class Q>
4363 [[nodiscard]] bool containsBoundary(const EdgeRange& edges, const Q& shape) const {
4364 const auto held = [&](const auto& piece) {
4365 if constexpr (Interior) {
4366 return interiorContains(piece);
4367 } else {
4368 return contains(piece);
4369 }
4370 };
4371 bool traced = false;
4372 for (const auto& e : edges) {
4373 if (e[0] == e[1]) {
4374 continue;
4375 }
4376 traced = true;
4377 if (!held(e)) {
4378 return false;
4379 }
4380 }
4381 return traced || held(shape[0]);
4382 }
4383
4384 // Rewrites a ring's vertex loop so that consecutive entries span an
4385 // unobstructed edge: rings are allowed to touch, and where they do a vertex
4386 // of one lands in the relative interior of an edge of the other, which
4387 // insertConstraint cannot force through. Every such vertex is spliced into
4388 // the loop, in order along the edge carrying it.
4389 //
4390 // Only vertices of the *other* rings can obstruct an edge — a ring is simple,
4391 // so a vertex of its own inside one of its edges would be a self-crossing —
4392 // but scanning all of @p candidates is simpler and costs the same test.
4393 std::vector<VertexIndex> expandRing(const std::vector<VertexIndex>& ring,
4394 const std::vector<VertexIndex>& candidates) const {
4395 std::vector<VertexIndex> expanded;
4396 expanded.reserve(ring.size());
4397 std::vector<VertexIndex> onEdge;
4398 for (std::size_t i = 0; i < ring.size(); ++i) {
4399 const VertexIndex a = ring[i];
4400 const VertexIndex b = ring[(i + 1) % ring.size()];
4401 expanded.push_back(a);
4402 const SegmentType edge(vertices_[a], vertices_[b]);
4403 onEdge.clear();
4404 for (const VertexIndex v : candidates) {
4405 if (v != a && v != b && edge.contains(vertices_[v])) {
4406 onEdge.push_back(v);
4407 }
4408 }
4409 if (onEdge.empty()) {
4410 continue;
4411 }
4412 // The obstructing vertices are collinear with the edge, so ordering
4413 // them lexicographically orders them along it, forward or backward.
4414 std::sort(onEdge.begin(), onEdge.end(),
4415 [this](VertexIndex p, VertexIndex q) { return vertices_[p] < vertices_[q]; });
4416 if (vertices_[b] < vertices_[a]) {
4417 std::reverse(onEdge.begin(), onEdge.end());
4418 }
4419 expanded.insert(expanded.end(), onEdge.begin(), onEdge.end());
4420 }
4421 return expanded;
4422 }
4423
4424 // The triangle lying to the left of the directed edge a -> b, or NO_TRI when
4425 // that edge is not in the triangulation. Triangle vertices are stored
4426 // counterclockwise and side s spans v[s+1] -> v[s+2], so the triangle
4427 // carrying the edge in that direction is the one on its left.
4428 [[nodiscard]] TriIndex triangleLeftOf(VertexIndex a, VertexIndex b) const {
4429 const auto it = segmentMap().find(SegmentType(vertices_[a], vertices_[b]));
4430 if (it == segmentMap().end()) {
4431 return NO_TRI;
4432 }
4433 for (const Edge& e : {it->second, mirror(it->second)}) {
4434 if (e.tri == NO_TRI) {
4435 continue;
4436 }
4437 const auto& v = triangles_[e.tri].v;
4438 if (v[(e.side + 1) % 3] == a && v[(e.side + 2) % 3] == b) {
4439 return e.tri;
4440 }
4441 }
4442 return NO_TRI;
4443 }
4444
4445 // The same edge seen from the triangle on its other side (an invalid Edge if
4446 // there is none). mirror(e).tri is the neighbor across e.
4447 [[nodiscard]] Edge mirror(Edge e) const {
4448 const TriIndex t2 = triangles_[e.tri].nbr[e.side];
4449 if (t2 == NO_TRI) {
4450 return Edge{NO_TRI, 0};
4451 }
4452 return Edge{t2, findSide(t2, e.tri)};
4453 }
4454
4455 // Materializes triangle t as a public Triangle value (with its label, if any).
4456 [[nodiscard]] TriangleType triangleValue(TriIndex t) const {
4457 const auto& T = triangles_[t];
4458 TriangleType tri(vertices_[T.v[0]], vertices_[T.v[1]], vertices_[T.v[2]]);
4459 // Activates automatically once pgl::Triangle gains a mutable label():
4460 // until then TriangleLabel is NoLabel and this branch is discarded.
4461 if constexpr (detail::has_label_v<TriangleLabel>) {
4462 tri.label() = T.triLabel;
4463 }
4464 return tri;
4465 }
4466
4467 // Materializes the edge as a Segment, attaching the label stored on @p e.
4468 // (Transient handles from mirror()/registerSides carry a default label, used
4469 // only for key construction where labels are ignored.)
4470 [[nodiscard]] SegmentType edgeSegment(Edge e) const {
4471 const auto& T = triangles_[e.tri];
4472 SegmentType s(vertices_[T.v[(e.side + 1) % 3]], vertices_[T.v[(e.side + 2) % 3]]);
4473 if constexpr (detail::has_label_v<SegmentLabel>) {
4474 s.label() = e.segLabel;
4475 }
4476 return s;
4477 }
4478
4479 // The vertex of t not lying on edge s (its apex relative to s).
4480 [[nodiscard]] static PointType apexOf(const TriangleType& t, const SegmentType& s) {
4481 for (const auto& p : t.vertices()) {
4482 if (p != s[0] && p != s[1]) {
4483 return p;
4484 }
4485 }
4486 return t[0]; // unreachable when s is an edge of t
4487 }
4488
4489 // Resolves a public Triangle to its internal id, or NO_TRI if absent: look
4490 // up one of its edges, then pick the incident side whose apex matches t.
4491 [[nodiscard]] TriIndex idOf(const TriangleType& t) const {
4492 const auto edges = t.edges();
4493 auto se = segmentMap().find(edges[0]);
4494 if (se == segmentMap().end()) {
4495 return NO_TRI;
4496 }
4497 const PointType apex = apexOf(t, edges[0]);
4498 const Edge e = se->second;
4499 if (vertices_[triangles_[e.tri].v[e.side]] == apex) {
4500 return e.tri;
4501 }
4502 const Edge m = mirror(e);
4503 if (m.tri != NO_TRI && !isGhost(m.tri) && vertices_[triangles_[m.tri].v[m.side]] == apex) {
4504 return m.tri;
4505 }
4506 return NO_TRI;
4507 }
4508
4509 // True if p lies in the closed triangle t (interior or boundary). Assumes t
4510 // is CCW; @p p may use a different point type.
4511 template <class QueryPoint>
4512 [[nodiscard]] bool pointInClosure(const QueryPoint& p, TriIndex t) const {
4513 const auto& v = triangles_[t].v;
4514 const auto fq = filteredPoint(p);
4515 const auto f0 = filteredVertex(v[0]);
4516 const auto f1 = filteredVertex(v[1]);
4517 const auto f2 = filteredVertex(v[2]);
4518 return detail::orientationSignOf(f0, f1, fq).value() >= 0 &&
4519 detail::orientationSignOf(f1, f2, fq).value() >= 0 &&
4520 detail::orientationSignOf(f2, f0, fq).value() >= 0;
4521 }
4522
4523 // Position (0,1,2) of vertex w within triangle t.
4524 [[nodiscard]] int localIndex(TriIndex t, VertexIndex w) const {
4525 const auto& v = triangles_[t].v;
4526 return v[0] == w ? 0 : (v[1] == w ? 1 : 2);
4527 }
4528
4529 // The neighbour of `cur` reached by rotating around vertex `w`, having
4530 // arrived from `from` (NO_TRI to start): leave through the w-incident edge we
4531 // did not enter. Orientation-independent (ghosts are not CCW-normalized), so
4532 // it steps through the ghost triangles of w's fan as well. Mirrors the lambda
4533 // in the segment walk; shared by the region flood fill.
4534 [[nodiscard]] TriIndex rotateAroundVertex(TriIndex cur, VertexIndex w, TriIndex from) const {
4535 const int lw = localIndex(cur, w);
4536 int s1 = -1, s2 = -1;
4537 for (int s = 0; s < 3; ++s) {
4538 if (s != lw) {
4539 if (s1 < 0) s1 = s; else s2 = s;
4540 }
4541 }
4542 return triangles_[cur].nbr[triangles_[cur].nbr[s1] == from ? s2 : s1];
4543 }
4544
4545 // Calls fn(TriIndex) on every triangle of vertex w's fan, in rotational order
4546 // starting from `start` (which must have w as a vertex). The rotation steps
4547 // through ghost and fill triangles too — that is what closes the ring — so
4548 // fn also sees those and must filter with inDomain if it wants only visible
4549 // triangles.
4550 template <class Fn>
4551 void visitVertexFan(TriIndex start, VertexIndex w, Fn fn) const {
4552 TriIndex cur = start, from = NO_TRI;
4553 std::size_t g = 0, lim = triangles_.size() + 1;
4554 do {
4555 fn(cur);
4556 const TriIndex next = rotateAroundVertex(cur, w, from);
4557 from = cur;
4558 cur = next;
4559 } while (cur != start && cur != NO_TRI && ++g < lim);
4560 }
4561
4562 // The first in-domain real triangle, or NO_TRI if the domain is empty.
4563 [[nodiscard]] TriIndex firstInDomainTriangle() const {
4564 for (TriIndex t = 0; t < firstGhost_; ++t) {
4565 if (!triangles_[t].outOfDomain) return t;
4566 }
4567 return NO_TRI;
4568 }
4569
4570 // Calls f(TriIndex) once per connected component of the domain, stopping at the
4571 // first true. This is what a query falls back on when the shape's boundary
4572 // misses the domain entirely: every component then lies wholly inside the
4573 // shape or wholly outside it, and one triangle of each settles which.
4574 //
4575 // Only a region's domain can come apart — a hole spanning it leaves two
4576 // slabs, and two holes meeting at a point pinch it likewise — so a domain
4577 // without holes keeps answering from its first triangle at O(1). Components
4578 // are the edge-connected classes of in-domain triangles: sharing an edge
4579 // puts the open edge in the domain's interior and joins them, sharing only a
4580 // vertex does not.
4581 template <class Fn>
4582 bool anyDomainComponent(Fn&& f) const {
4583 const TriIndex first = firstInDomainTriangle();
4584 if (first == NO_TRI) {
4585 return false;
4586 }
4587 if (holeWitnesses_.empty()) {
4588 return f(first);
4589 }
4590 std::vector<char> seen(triangles_.size(), 0);
4591 std::vector<TriIndex> stack;
4592 for (TriIndex t = 0; t < firstGhost_; ++t) {
4593 if (!inDomain(t) || seen[t]) {
4594 continue;
4595 }
4596 if (f(t)) {
4597 return true;
4598 }
4599 seen[t] = 1;
4600 stack.push_back(t);
4601 while (!stack.empty()) {
4602 const TriIndex cur = stack.back();
4603 stack.pop_back();
4604 for (int s = 0; s < 3; ++s) {
4605 const TriIndex nb = triangles_[cur].nbr[s];
4606 if (nb != NO_TRI && !seen[nb] && inDomain(nb)) {
4607 seen[nb] = 1;
4608 stack.push_back(nb);
4609 }
4610 }
4611 }
4612 }
4613 return false;
4614 }
4615
4616 // Finds one or more triangles meeting `shape` to seed the region flood fill,
4617 // by navigation rather than a full scan. Returns an empty vector when `shape`
4618 // does not meet the triangulated region at all. (Helper for the
4619 // detail::TriangulationRegionQuery overload of visitTrianglesIntersecting.)
4620 template <class Q>
4621 [[nodiscard]] std::vector<TriIndex> seedTrianglesIntersecting(const Q& shape) const {
4622 std::vector<TriIndex> seeds;
4623 if constexpr (PointConcept<Q>) {
4624 // A point: the triangle locate lands in already meets it (its closure
4625 // contains the point). Outside the hull, locate returns a ghost.
4626 const TriIndex t = locateIndex(shape);
4627 if (t != NO_TRI && !isGhost(t)) {
4628 seeds.push_back(t);
4629 }
4630 } else if constexpr (HalfplaneConcept<Q>) {
4631 // Unbounded shape: it meets the triangulated region iff it meets some
4632 // convex-hull edge. Scan the ghost ring for the first such edge; the
4633 // real triangle just inside it is a seed. (O(hull).) Segments, lines,
4634 // oriented lines, and rays are not region queries — they have their
4635 // own ordered walk.
4636 for (TriIndex g = firstGhost_; g < static_cast<TriIndex>(triangles_.size()); ++g) {
4637 if (edgeSegment(Edge{g, 2}).intersects(shape)) {
4638 seeds.push_back(triangles_[g].nbr[2]); // the real triangle inside
4639 break;
4640 }
4641 }
4642 } else if constexpr (DiskConcept<Q>) {
4643 // A disk is bounded but has no straight edges to trace. One of its
4644 // defining boundary points, shape[0], lies in the (closed) disk, so
4645 // the triangle locate lands in is a seed when that point is inside
4646 // the hull. Otherwise the disk reaches in from outside the hull, so —
4647 // as for an unbounded shape — a hull edge it crosses gives the seed.
4648 const TriIndex t = locateIndex(shape[0]);
4649 if (t != NO_TRI && !isGhost(t)) {
4650 seeds.push_back(t);
4651 } else {
4652 for (TriIndex g = firstGhost_; g < static_cast<TriIndex>(triangles_.size()); ++g) {
4653 if (edgeSegment(Edge{g, 2}).intersects(shape)) {
4654 seeds.push_back(triangles_[g].nbr[2]);
4655 break;
4656 }
4657 }
4658 }
4659 } else {
4660 // Bounded shape with straight edges (triangle, rectangle, convex
4661 // polygon): any triangle its boundary meets is a seed. Trace each edge
4662 // with the segment walk and take the first triangle it reports.
4663 for (const auto& e : shape.edges()) {
4664 TriIndex found = NO_TRI;
4665 visitTrianglesIntersecting(e, [&](const TriangleType& t) {
4666 found = idOf(t);
4667 return true; // the first triangle suffices
4668 });
4669 if (found != NO_TRI) {
4670 seeds.push_back(found);
4671 break;
4672 }
4673 }
4674 // No edge met the domain: each connected piece of it is either
4675 // inside `shape` or disjoint from it, and one vertex of each decides.
4676 // Every piece inside gets a seed — a hole can split the domain, and
4677 // the flood does not cross from one piece to another.
4678 if (seeds.empty() && numVertices() > 0) {
4679 anyDomainComponent([&](TriIndex t) {
4680 if (shape.contains(vertices_[triangles_[t].v[0]])) {
4681 seeds.push_back(t);
4682 }
4683 return false; // every component, not just the first
4684 });
4685 }
4686 }
4687 return seeds;
4688 }
4689
4690 // Grows the set of triangles meeting `shape` outward from `seeds` by a flood
4691 // fill over edge/vertex adjacency, emitting the in-domain ones to `f` (every
4692 // real one under AllTriangles, as in the directed walk — what contains() needs
4693 // to see the fill region a shape reaches into). `f` takes a TriIndex and returns
4694 // bool. Every real (non-ghost) triangle meeting `shape` and reachable from a
4695 // seed through other meeting triangles is visited; ghosts are walls. A
4696 // per-triangle mark bit stands in for a visited set and is cleared on every
4697 // exit path (normal, early-stop, or an exception from f), so the const query
4698 // leaves the mesh pristine. Like the segment walk, it is not reentrant: f must
4699 // not start another walk on the same Triangulation. Returns whether f stopped
4700 // early.
4701 template <bool AllTriangles, class Q, class Fn>
4702 bool floodTriangleIdsIntersecting(const Q& shape, const std::vector<TriIndex>& seeds, Fn f) const {
4703 std::vector<TriIndex> marked;
4704 struct MarkClearer {
4705 const std::vector<Tri>& tris;
4706 const std::vector<TriIndex>& marked;
4707 ~MarkClearer() { for (TriIndex t : marked) tris[t].walkMark = 0; }
4708 } markClearer{triangles_, marked};
4709
4710 std::vector<TriIndex> stack;
4711 // Marks t seen; if it is a real triangle meeting `shape`, queues it for
4712 // expansion. Rejected triangles are marked too, so each is tested once.
4713 const auto consider = [&](TriIndex t) {
4714 if (t == NO_TRI || isGhost(t) || triangles_[t].walkMark) {
4715 return;
4716 }
4717 triangles_[t].walkMark = 1;
4718 marked.push_back(t);
4719 if (triangleValue(t).intersects(shape)) {
4720 stack.push_back(t);
4721 }
4722 };
4723 for (const TriIndex s : seeds) {
4724 consider(s);
4725 }
4726
4727 bool stop = false;
4728 while (!stop && !stack.empty()) {
4729 const TriIndex t = stack.back();
4730 stack.pop_back();
4731 if ((AllTriangles || inDomain(t)) && f(t)) {
4732 stop = true;
4733 break;
4734 }
4735 // Expand through every triangle sharing a vertex with t (which covers
4736 // its edge neighbours too): the meeting set is connected under this
4737 // adjacency, so this reaches all of it without scanning the mesh.
4738 for (const VertexIndex w : triangles_[t].v) {
4739 TriIndex cur = t, from = NO_TRI;
4740 std::size_t g = 0, lim = triangles_.size() + 1;
4741 do {
4742 consider(cur);
4743 const TriIndex next = rotateAroundVertex(cur, w, from);
4744 from = cur;
4745 cur = next;
4746 } while (cur != t && cur != NO_TRI && ++g < lim);
4747 }
4748 }
4749 return stop;
4750 }
4751
4752 // Inserts t's three edges into the segment-to-edge map, keyed by Segment,
4753 // and records t as the fan seed of each of its vertices. The two go
4754 // together: every edit that rewrites a triangle's vertices re-registers it,
4755 // so hooking the seed here is what keeps it fresh across a flip — the flip
4756 // re-registers both rewritten triangles, and a vertex of the quad ends up in
4757 // at least one of them.
4758 void registerSides(TriIndex t) {
4759 for (std::int8_t s = 0; s < 3; ++s) {
4760 segmentMap()[edgeSegment(Edge{t, s})] = Edge{t, s};
4761 }
4762 noteVertexIncidence(t);
4763 }
4764
4765 // Records t as the fan seed of each of its real vertices. Ghost triangles
4766 // are skipped: a fan rotation may pass through them but never starts there.
4767 void noteVertexIncidence(TriIndex t) {
4768 if (t < 0 || t >= firstGhost_) {
4769 return;
4770 }
4771 if (vertexTri_.size() < vertices_.size()) {
4772 vertexTri_.resize(vertices_.size(), NO_TRI);
4773 }
4774 for (const VertexIndex w : triangles_[t].v) {
4775 if (w != GHOST) {
4776 vertexTri_[static_cast<std::size_t>(w)] = t;
4777 }
4778 }
4779 }
4780
4781 // A real triangle having w as a vertex, or NO_TRI when none is recorded.
4782 // The record is verified against the triangle's current vertices, so it is
4783 // authoritative when it answers: a triangle that still lists w really is in
4784 // w's fan.
4785 [[nodiscard]] TriIndex incidentTriangleOf(VertexIndex w) const {
4786 if (static_cast<std::size_t>(w) >= vertexTri_.size()) {
4787 return NO_TRI;
4788 }
4789 const TriIndex t = vertexTri_[static_cast<std::size_t>(w)];
4790 if (t == NO_TRI || t >= firstGhost_) {
4791 return NO_TRI;
4792 }
4793 const auto& v = triangles_[t].v;
4794 return (v[0] == w || v[1] == w || v[2] == w) ? t : NO_TRI;
4795 }
4796
4797 // Fills the segment-to-edge map from the mesh, over all real triangles.
4798 //
4799 // A bulk build leaves the map unfilled and comes here on the first lookup
4800 // instead. Registering an edge costs a hash insert keyed by a segment --
4801 // two exact points copied and hashed -- three per triangle, and a
4802 // triangulation that is only ever asked about triangles, points or
4803 // neighbours never reads one of them. What the build cannot defer is the
4804 // per-triangle vertex-incidence hint, which the walks do read; that is why
4805 // the two are registered apart.
4806 //
4807 // Only the edge handles are recovered here. An edge's segment label is set
4808 // on the record afterwards, by whoever had the labelled segment, and every
4809 // site that does so goes through the accessor first.
4810 void materializeSegmentMap() const {
4811 segToEdge_.clear();
4812 // Three sides a triangle, each shared by at most two of them: the map
4813 // ends up between one and a half and three entries per triangle, and
4814 // sizing it once beats rehashing it as it fills.
4815 segToEdge_.reserve(static_cast<std::size_t>(std::max(firstGhost_, TriIndex(0))) * 2);
4816 for (TriIndex t = 0; t < firstGhost_; ++t) {
4817 for (std::int8_t s = 0; s < 3; ++s) {
4818 segToEdge_[edgeSegment(Edge{t, s})] = Edge{t, s};
4819 }
4820 }
4821 mapStale_ = false;
4822 }
4823
4824 // The segment-to-edge map, filled first if a bulk build left it deferred.
4825 [[nodiscard]] std::unordered_map<SegmentType, Edge>& segmentMap() const {
4826 if (mapStale_) {
4827 materializeSegmentMap();
4828 }
4829 return segToEdge_;
4830 }
4831
4832 // Returns a function that maps a point to its vertex id, appending it to
4833 // vertices_ (and to `vid`) the first time it is seen.
4834 auto makeVertexInterner(std::unordered_map<PointType, VertexIndex>& vid) {
4835 return [this, &vid](const PointType& p) -> VertexIndex {
4836 auto it = vid.find(p);
4837 if (it != vid.end()) {
4838 return it->second;
4839 }
4840 VertexIndex id = static_cast<VertexIndex>(vertices_.size());
4841 vertices_.push_back(p);
4842 vid.emplace(p, id);
4843 return id;
4844 };
4845 }
4846
4847 // Exact Delaunay triangle triples (CCW, vertex indices into `pts`) via
4848 // incremental Bowyer–Watson insertion. A single symbolic "vertex at infinity"
4849 // (INF) closes the convex hull instead of a containing super-triangle: every
4850 // hull edge a->b carries a ghost triangle {b,a,INF} that tiles the exterior,
4851 // so the working triangulation is a closed surface in which every triangle has
4852 // three neighbours. No far-away coordinates are ever constructed, so every
4853 // in-circle / orientation test runs on the real point coordinates at the
4854 // library's normal exactness — no BigInt, and no magnitude assumption (which
4855 // is why a super-triangle is unsound for rational points: its required scale
4856 // is unbounded).
4857 //
4858 // Each point is located by a visibility walk over the neighbour links (the
4859 // same walk locateIndex() runs on the finished structure) and inserted by
4860 // carving out the triangles whose open circumdisk contains it — found by a
4861 // local flood-fill from the located triangle, not a global scan — then
4862 // re-fanning the star-shaped cavity to the new vertex. With the random
4863 // insertion order the walk is short (seeded from the previously inserted
4864 // triangle), so the build is ~O(n^1.5) here rather than the O(n^2) of testing
4865 // every triangle against every point.
4866 static std::vector<std::array<VertexIndex, 3>>
4867 delaunayTriples(const std::vector<PointType>& pts,
4868 const std::vector<detail::ApproximatePoint>& approximations) {
4869 const VertexIndex n = static_cast<VertexIndex>(pts.size());
4870 std::vector<std::array<VertexIndex, 3>> out;
4871 if (n < 3) {
4872 return out;
4873 }
4874 const VertexIndex INF = n; // the symbolic vertex at infinity
4875
4876 // Every predicate below reads its operands out of `pts`, and the walk
4877 // and the flood-fill revisit the same vertices over and over, so the
4878 // approximations the caller keeps are what stop each of those reads
4879 // from converting a coordinate to double again.
4880 const auto fp = [&](VertexIndex v) {
4881 const auto index = static_cast<std::size_t>(v);
4882 return detail::filtered<VertexCoordinate>(pts[index], approximations, index);
4883 };
4884
4885 // Local closed triangulation: CCW vertices (ghosts contain INF) and three
4886 // neighbours each (nbr[i] is across the edge opposite v[i]). Killed
4887 // triangles keep `dead` set and recycle their slots through freeList; no
4888 // live triangle ever references a dead slot, so `dead` doubles as the
4889 // per-insertion "already in the cavity" mark during the flood-fill.
4890 struct LTri {
4891 std::array<VertexIndex, 3> v{};
4892 std::array<int, 3> nbr{-1, -1, -1};
4893 bool dead = false;
4894 };
4895 std::vector<LTri> tri;
4896 std::vector<int> freeList;
4897
4898 auto newTri = [&](VertexIndex a, VertexIndex b, VertexIndex d) -> int {
4899 int id;
4900 if (!freeList.empty()) {
4901 id = freeList.back();
4902 freeList.pop_back();
4903 tri[id] = LTri{{a, b, d}, {-1, -1, -1}, false};
4904 } else {
4905 id = static_cast<int>(tri.size());
4906 tri.push_back(LTri{{a, b, d}, {-1, -1, -1}, false});
4907 }
4908 return id;
4909 };
4910
4911 auto isGhost = [&](int t) {
4912 const auto& q = tri[t].v;
4913 return q[0] == INF || q[1] == INF || q[2] == INF;
4914 };
4915
4916 // Inside-circumdisk test, finite or ghost. The circle through two finite
4917 // points and INF degenerates to the line through them, so for a ghost
4918 // {.,.,INF} "inside the open disk" reduces to "left of the finite directed
4919 // edge" (the edge opposite INF, in CCW order so its left is the hull
4920 // exterior).
4921 //
4922 // The collinear boundary case (orientation 0) is decided so the re-fan
4923 // never builds a zero-area triangle: p exactly on the open hull-edge
4924 // segment counts as inside, pulling the ghost into the cavity so the edge
4925 // splits in two instead of spanning a degenerate {edge, p}. p on the
4926 // edge's line but beyond an endpoint stays outside — the hull just extends
4927 // straight along the line, no degenerate triangle either way.
4928 auto inDisk = [&](int t, VertexIndex p) -> bool {
4929 const auto& q = tri[t].v;
4930 const int inf = q[0] == INF ? 0 : (q[1] == INF ? 1 : (q[2] == INF ? 2 : -1));
4931 if (inf < 0) {
4932 return detail::inCircleSignOf(fp(q[0]), fp(q[1]),
4933 fp(q[2]), fp(p)) ==
4934 std::partial_ordering::greater;
4935 }
4936 const VertexIndex u = q[(inf + 1) % 3];
4937 const VertexIndex w = q[(inf + 2) % 3];
4938 const auto side =
4939 detail::orientationSignOf(fp(u), fp(w), fp(p)).value();
4940 if (side > 0) {
4941 return true;
4942 }
4943 if (side == 0) { // collinear: inside iff strictly between u and w
4944 // Betweenness only reads the signs of the two dot products, and
4945 // dotSign takes them in the promoted coordinate type — the bare
4946 // dot product multiplies in the coordinate type, where a wrap
4947 // would silently corrupt the triangulation.
4948 return dotSign(pts[p] - pts[u], pts[w] - pts[u]) > 0 &&
4949 dotSign(pts[p] - pts[w], pts[u] - pts[w]) > 0;
4950 }
4951 return false;
4952 };
4953
4954 // Seed with the first non-collinear triple, oriented CCW, plus the three
4955 // ghost triangles covering its hull edges. Points 2..c-1 (if any) are
4956 // collinear with 0 and 1 and get inserted in the main loop like any other.
4957 VertexIndex c = 2;
4958 while (c < n &&
4959 detail::orientationSignOf(fp(0), fp(1), fp(c)).value() == 0) {
4960 ++c;
4961 }
4962 if (c == n) {
4963 return out; // all points collinear: the Delaunay triangulation is empty
4964 }
4965 const std::array<VertexIndex, 3> seed =
4966 detail::orientationSignOf(fp(0), fp(1), fp(c)).value() > 0
4967 ? std::array<VertexIndex, 3>{0, 1, c}
4968 : std::array<VertexIndex, 3>{1, 0, c};
4969 const int seedTris[4] = {
4970 newTri(seed[0], seed[1], seed[2]),
4971 newTri(seed[1], seed[0], INF), // ghost outside edge seed0->seed1
4972 newTri(seed[2], seed[1], INF), // ghost outside edge seed1->seed2
4973 newTri(seed[0], seed[2], INF), // ghost outside edge seed2->seed0
4974 };
4975 // Link the seed: match each directed edge (a,b) to the neighbour carrying
4976 // its reverse (b,a). The four triangles tile the sphere, so every side
4977 // finds a partner.
4978 for (int t : seedTris) {
4979 for (int s = 0; s < 3; ++s) {
4980 const VertexIndex a = tri[t].v[(s + 1) % 3];
4981 const VertexIndex b = tri[t].v[(s + 2) % 3];
4982 for (int u : seedTris) {
4983 for (int q = 0; q < 3; ++q) {
4984 if (tri[u].v[(q + 1) % 3] == b && tri[u].v[(q + 2) % 3] == a) {
4985 tri[t].nbr[s] = u;
4986 }
4987 }
4988 }
4989 }
4990 }
4991
4992 // Visibility walk: from triangle t, step across whichever edge p lies
4993 // strictly to the right of (outside), until p is inside the current
4994 // triangle (no such edge) or a ghost is reached (p outside the hull). A
4995 // randomised start edge guarantees termination; the step cap is a safety
4996 // net only.
4997 std::uint64_t rngState = 0x9e3779b97f4a7c15ULL;
4998 auto walk = [&](VertexIndex p, int t) -> int {
4999 int from = -1;
5000 const int64_t cap = int64_t(3) * static_cast<int64_t>(tri.size()) + 16;
5001 for (int64_t step = 0; step < cap; ++step) {
5002 if (isGhost(t)) {
5003 return t;
5004 }
5005 rngState = rngState * 6364136223846793005ULL + 1442695040888963407ULL;
5006 const int begin = static_cast<int>((rngState >> 33) % 3);
5007 int next = -1;
5008 for (int k = 0; k < 3; ++k) {
5009 const int s = (begin + k) % 3;
5010 if (tri[t].nbr[s] == from) {
5011 continue;
5012 }
5013 const VertexIndex ea = tri[t].v[(s + 1) % 3];
5014 const VertexIndex eb = tri[t].v[(s + 2) % 3];
5015 if (detail::orientationSignOf(fp(ea), fp(eb),
5016 fp(p)).value() < 0) {
5017 next = tri[t].nbr[s];
5018 break;
5019 }
5020 }
5021 if (next < 0) {
5022 return t;
5023 }
5024 from = t;
5025 t = next;
5026 }
5027 return t;
5028 };
5029
5030 std::vector<int> cavity;
5031 // A boundary edge of the cavity: its directed edge (a,b), the surviving
5032 // triangle behind it, and that triangle's side facing the cavity.
5033 struct Bnd {
5034 VertexIndex a, b;
5035 int surv, survSide;
5036 };
5037 std::vector<Bnd> boundary;
5038 // Spoke edges {vertex,p} awaiting their partner among the new triangles.
5039 struct Spoke {
5040 VertexIndex vertex;
5041 int tri, side;
5042 };
5043 std::vector<Spoke> spokes;
5044
5045 int hint = seedTris[0];
5046 for (VertexIndex i = 0; i < n; ++i) {
5047 if (i == seed[0] || i == seed[1] || i == seed[2]) {
5048 continue;
5049 }
5050 const int start = walk(i, hint);
5051 if (!inDisk(start, i)) {
5052 continue; // i lies in no open circumdisk (collinear/duplicate): skip
5053 }
5054
5055 // Flood-fill the cavity: every triangle whose open disk contains i,
5056 // reachable from `start` through neighbours. Mark them dead as we go
5057 // (so they double as the visited set); record each edge where the
5058 // flood meets a surviving triangle.
5059 cavity.clear();
5060 boundary.clear();
5061 tri[start].dead = true;
5062 cavity.push_back(start);
5063 for (std::size_t qi = 0; qi < cavity.size(); ++qi) {
5064 const int t = cavity[qi];
5065 for (int s = 0; s < 3; ++s) {
5066 const int nb = tri[t].nbr[s];
5067 if (tri[nb].dead) {
5068 continue; // already carved into the cavity this insertion
5069 }
5070 if (inDisk(nb, i)) {
5071 tri[nb].dead = true;
5072 cavity.push_back(nb);
5073 } else {
5074 const int survSide =
5075 tri[nb].nbr[0] == t ? 0 : (tri[nb].nbr[1] == t ? 1 : 2);
5076 boundary.push_back(
5077 {tri[t].v[(s + 1) % 3], tri[t].v[(s + 2) % 3], nb, survSide});
5078 }
5079 }
5080 }
5081 for (int t : cavity) {
5082 freeList.push_back(t);
5083 }
5084
5085 // Re-fan: one new triangle {a,b,i} per boundary edge, linked across its
5086 // base {a,b} to the surviving triangle and across its two spokes
5087 // {b,i}/{a,i} to the adjacent new triangles, paired by shared vertex.
5088 spokes.clear();
5089 int newReal = -1;
5090 for (const Bnd& e : boundary) {
5091 const int nt = newTri(e.a, e.b, i);
5092 tri[nt].nbr[2] = e.surv; // side 2 (opposite i) is the base edge {a,b}
5093 tri[e.surv].nbr[e.survSide] = nt;
5094 if (newReal < 0 && !isGhost(nt)) {
5095 newReal = nt;
5096 }
5097 // side 0 (opposite a) is edge {b,i}; side 1 (opposite b) is {a,i}.
5098 for (const auto& [vertex, side] :
5099 {std::pair<VertexIndex, int>{e.b, 0}, std::pair<VertexIndex, int>{e.a, 1}}) {
5100 bool paired = false;
5101 for (std::size_t k = 0; k < spokes.size(); ++k) {
5102 if (spokes[k].vertex == vertex) {
5103 tri[nt].nbr[side] = spokes[k].tri;
5104 tri[spokes[k].tri].nbr[spokes[k].side] = nt;
5105 spokes[k] = spokes.back();
5106 spokes.pop_back();
5107 paired = true;
5108 break;
5109 }
5110 }
5111 if (!paired) {
5112 spokes.push_back({vertex, nt, side});
5113 }
5114 }
5115 }
5116 if (newReal >= 0) {
5117 hint = newReal;
5118 }
5119 }
5120
5121 for (int t = 0; t < static_cast<int>(tri.size()); ++t) {
5122 if (tri[t].dead) {
5123 continue;
5124 }
5125 const auto& q = tri[t].v;
5126 if (q[0] != INF && q[1] != INF && q[2] != INF) {
5127 out.push_back({q[0], q[1], q[2]});
5128 }
5129 }
5130 return out;
5131 }
5132
5133 // ---- constrained Delaunay (polygon constructor) ----------------------
5134
5135 // True if the open segments AB and CD cross properly (no shared endpoint,
5136 // no collinear contact). Exact via orientationSign.
5137 [[nodiscard]] bool properCross(const PointType& a, const PointType& b,
5138 const PointType& c, const PointType& d) const {
5139 const auto s1 = orientationSign(a, b, c);
5140 const auto s2 = orientationSign(a, b, d);
5141 const auto s3 = orientationSign(c, d, a);
5142 const auto s4 = orientationSign(c, d, b);
5143 if (s1 == 0 || s2 == 0 || s3 == 0 || s4 == 0) {
5144 return false;
5145 }
5146 return ((s1 > 0) != (s2 > 0)) && ((s3 > 0) != (s4 > 0));
5147 }
5148
5149 // The current internal handle of edge {p,q}, or an invalid edge if absent.
5150 [[nodiscard]] Edge edgeHandle(VertexIndex p, VertexIndex q) const {
5151 auto se = segmentMap().find(SegmentType(vertices_[p], vertices_[q]));
5152 return se == segmentMap().end() ? Edge{NO_TRI, 0} : se->second;
5153 }
5154
5155 [[nodiscard]] bool edgeExists(VertexIndex p, VertexIndex q) const {
5156 return segmentMap().contains(SegmentType(vertices_[p], vertices_[q]));
5157 }
5158
5159 // The interior edges that the open segment va->vb crosses, as vertex pairs,
5160 // in order from va to vb. Empty when {va,vb} is already an edge. Assumes no
5161 // vertex lies in the interior of the segment (true for simple-polygon edges).
5162 [[nodiscard]] std::vector<std::pair<VertexIndex, VertexIndex>>
5163 collectCrossings(VertexIndex va, VertexIndex vb) const {
5164 const PointType& A = vertices_[va];
5165 const PointType& B = vertices_[vb];
5166 std::vector<std::pair<VertexIndex, VertexIndex>> out;
5167
5168 // Find the triangle incident to va that the segment first enters. Only
5169 // va's own fan can hold it, so the search rotates around va rather than
5170 // scanning the mesh: the scan is what made inserting k constraints cost
5171 // O(k * triangles), which dominated every constrained triangulation
5172 // here. Under this method's precondition the answer is unique — the
5173 // link of va is star-shaped about it, so a segment leaving va properly
5174 // crosses exactly one link edge — and the visit order therefore does not
5175 // matter.
5176 TriIndex t = NO_TRI;
5177 std::pair<VertexIndex, VertexIndex> entry{NO_TRI, NO_TRI};
5178 const auto enterFrom = [&](TriIndex k) {
5179 if (t != NO_TRI || k == NO_TRI || isGhost(k)) {
5180 return; // already found, or a ghost triangle closing the fan
5181 }
5182 const auto& v = triangles_[k].v;
5183 const int i = localIndex(k, va);
5184 const VertexIndex p = v[(i + 1) % 3];
5185 const VertexIndex q = v[(i + 2) % 3];
5186 if (properCross(A, B, vertices_[p], vertices_[q])) {
5187 t = triangles_[k].nbr[i];
5188 entry = {p, q};
5189 out.push_back({p, q});
5190 }
5191 };
5192 const TriIndex seed = incidentTriangleOf(va);
5193 if (seed != NO_TRI) {
5194 visitVertexFan(seed, va, enterFrom);
5195 } else {
5196 // No seed recorded (a mesh built by a path that never registered
5197 // one): fall back to the scan, which needs no incidence at all.
5198 for (TriIndex k = 0; k < firstGhost_ && t == NO_TRI; ++k) {
5199 const auto& v = triangles_[k].v;
5200 if (v[0] == va || v[1] == va || v[2] == va) {
5201 enterFrom(k);
5202 }
5203 }
5204 }
5205 if (t == NO_TRI) {
5206 return out; // {va,vb} is already an edge (or va not incident anywhere)
5207 }
5208
5209 // Walk across the crossed edges until a triangle holds vb.
5210 std::size_t guard = 0, cap = triangles_.size() + 1;
5211 while (++guard < cap) {
5212 const auto& v = triangles_[t].v;
5213 if (v[0] == vb || v[1] == vb || v[2] == vb) {
5214 break;
5215 }
5216 int exitK = -1;
5217 for (int k = 0; k < 3; ++k) {
5218 const VertexIndex p = v[(k + 1) % 3];
5219 const VertexIndex q = v[(k + 2) % 3];
5220 const bool sameEntry = (p == entry.first && q == entry.second) ||
5221 (p == entry.second && q == entry.first);
5222 if (sameEntry) {
5223 continue;
5224 }
5225 if (properCross(A, B, vertices_[p], vertices_[q])) {
5226 exitK = k;
5227 entry = {p, q};
5228 out.push_back({p, q});
5229 break;
5230 }
5231 }
5232 if (exitK < 0) {
5233 break; // should not happen for a valid triangulation
5234 }
5235 t = triangles_[t].nbr[exitK];
5236 }
5237 return out;
5238 }
5239
5240 // Inserts the segment {va,vb} as an edge by flipping the edges it crosses
5241 // (Sloan's flip algorithm), then flags it constrained on both sides.
5242 //
5243 // A FIFO queue of crossing edges is processed front-to-back: a convex
5244 // crossing edge is flipped, and if its new diagonal still crosses the
5245 // constraint it goes to the back; a non-convex one is deferred to the back
5246 // until its quad becomes convex. This ordering guarantees progress (a naive
5247 // "flip the first convex one" can oscillate, repeatedly flipping a diagonal
5248 // back and forth).
5249 void insertConstraint(VertexIndex va, VertexIndex vb) {
5250 if (va == vb || edgeExists(va, vb)) {
5251 setConstrained(SegmentType(vertices_[va], vertices_[vb]), true);
5252 return;
5253 }
5254 const PointType& A = vertices_[va];
5255 const PointType& B = vertices_[vb];
5256 std::deque<std::pair<VertexIndex, VertexIndex>> queue;
5257 for (const auto& pq : collectCrossings(va, vb)) {
5258 queue.push_back(pq);
5259 }
5260 std::size_t guard = 0, cap = (queue.size() + 1) * (triangles_.size() + 1) * 4 + 64;
5261 while (!queue.empty() && ++guard < cap) {
5262 auto [p, q] = queue.front();
5263 queue.pop_front();
5264 const Edge e = edgeHandle(p, q);
5265 if (e.tri == NO_TRI || !properCross(A, B, vertices_[p], vertices_[q])) {
5266 continue; // edge gone, or no longer crosses the constraint
5267 }
5268 if (!flippableEdge(e)) {
5269 queue.push_back({p, q}); // quad not yet convex; revisit later
5270 continue;
5271 }
5272 const VertexIndex r = triangles_[e.tri].v[e.side]; // apex on one side
5273 const Edge m = mirror(e);
5274 const VertexIndex l = triangles_[m.tri].v[m.side]; // apex on the other
5275 flip(SegmentType(vertices_[p], vertices_[q]));
5276 if (properCross(A, B, vertices_[r], vertices_[l])) {
5277 queue.push_back({r, l}); // new diagonal still crosses; re-queue
5278 }
5279 }
5280 setConstrained(SegmentType(vertices_[va], vertices_[vb]), true);
5281 }
5282
5283 // Restores the constrained Delaunay property: flip every non-constrained
5284 // interior edge whose opposite apex lies inside the incident circumcircle,
5285 // until none remain. Constrained edges are never flipped.
5286 //
5287 // A triangulation is Delaunay exactly when every edge is locally Delaunay,
5288 // and an edge's local test reads only its two incident triangles — so
5289 // @ref legalize's work queue reaches the same fixpoint as a sweep, for the
5290 // cost of the flips instead of the cost of the mesh per flip. Seeding it
5291 // with every edge is what makes it a *global* restore: both callers reach
5292 // here having forced constraints into a mesh whose edges have not been
5293 // tested since, so none can be assumed legal.
5294 void restoreConstrainedDelaunay() {
5295 std::vector<SegmentType> suspect;
5296 suspect.reserve(segmentMap().size());
5297 for (const auto& [seg, handle] : segmentMap()) {
5298 (void)handle;
5299 suspect.push_back(seg);
5300 }
5301 legalize(suspect);
5302 }
5303
5304 // Flood-fills from the ghost (outside) across non-constrained edges, marking
5305 // every real triangle reachable without crossing the polygon boundary as
5306 // out-of-domain (the hull-fill triangles between polygon and convex hull).
5307 //
5308 // A region with holes seeds the same flood once more per hole, from a
5309 // triangle inside it: hole interiors are fenced off from the outside by the
5310 // outer boundary, so nothing else would reach them.
5311 void markOutOfDomain(const std::vector<TriIndex>& holeSeeds = {}) {
5312 std::vector<char> seen(triangles_.size(), 0);
5313 std::vector<TriIndex> stack;
5314 for (TriIndex g = firstGhost_; g < static_cast<TriIndex>(triangles_.size()); ++g) {
5315 seen[g] = 1;
5316 stack.push_back(g);
5317 }
5318 std::size_t marked = 0;
5319 for (const TriIndex seed : holeSeeds) {
5320 if (seed == NO_TRI || seen[seed]) {
5321 continue; // two rings bounding the same triangle: seeded already
5322 }
5323 seen[seed] = 1;
5324 triangles_[seed].outOfDomain = 1;
5325 ++marked;
5326 stack.push_back(seed);
5327 }
5328 while (!stack.empty()) {
5329 const TriIndex t = stack.back();
5330 stack.pop_back();
5331 for (int s = 0; s < 3; ++s) {
5332 if (bit(triangles_[t].constrainedMask, s)) {
5333 continue; // the polygon boundary fences off the interior
5334 }
5335 const TriIndex nb = triangles_[t].nbr[s];
5336 if (nb == NO_TRI || seen[nb]) {
5337 continue;
5338 }
5339 seen[nb] = 1;
5340 if (!isGhost(nb)) {
5341 triangles_[nb].outOfDomain = 1;
5342 ++marked;
5343 }
5344 stack.push_back(nb);
5345 }
5346 }
5347 domainTriangleCount_ = static_cast<std::size_t>(firstGhost_) - marked;
5348 }
5349
5350 // The outer rings and, respectively, all the hole rings of a set's
5351 // components, flattened for @ref constructConstrained — which asks for the
5352 // two ring kinds separately because only holes need a flood seed of their
5353 // own.
5354 static std::vector<Polygon<PointType>> setOuters(const PolygonSet<PointType>& set) {
5355 std::vector<Polygon<PointType>> outers;
5356 outers.reserve(set.componentCount());
5357 for (const auto& component : set) {
5358 outers.push_back(component.outer());
5359 }
5360 return outers;
5361 }
5362
5363 static std::vector<Polygon<PointType>> setHoles(const PolygonSet<PointType>& set) {
5364 std::vector<Polygon<PointType>> holes;
5365 holes.reserve(set.holeCount());
5366 for (const auto& component : set) {
5367 for (const auto& hole : component.holes()) {
5368 holes.push_back(hole);
5369 }
5370 }
5371 return holes;
5372 }
5373
5374 // Shared implementation of the polygon, region and region-set constructors.
5375 // Triangulates the union of the ring vertices, the extra interior points,
5376 // and the constraint segment endpoints; constrains every ring and every
5377 // interior segment; then marks the exterior (between the rings and their
5378 // convex hull) out of domain, along with each hole. Interior constraints
5379 // never reach the exterior flood — the boundary fences it off — so they stay
5380 // in-domain. @p extraPoints and @p constraintSegments are assumed to lie in
5381 // the domain (not checked).
5382 //
5383 // @p outers holds one outer ring per piece. Several of them need nothing
5384 // extra: the gap between two pieces is reached by the same exterior flood,
5385 // which the pieces' own rings fence it out of, and a piece stranded inside
5386 // another's hole is fenced out of that hole's flood the same way.
5387 template <class PointRange, class SegmentRange>
5388 void constructConstrained(const std::vector<Polygon<PointType>>& outers,
5389 const PointRange& extraPoints,
5390 const SegmentRange& constraintSegments,
5391 const std::vector<Polygon<PointType>>& holes = {}) {
5392 std::unordered_map<PointType, VertexIndex> vid;
5393 const auto idOfPoint = makeVertexInterner(vid);
5394 for (const auto& outer : outers) {
5395 for (std::size_t i = 0; i < outer.size(); ++i) {
5396 idOfPoint(outer[i]);
5397 }
5398 }
5399 for (const auto& hole : holes) {
5400 for (std::size_t i = 0; i < hole.size(); ++i) {
5401 idOfPoint(hole[i]);
5402 }
5403 }
5404 for (const auto& p : extraPoints) {
5405 idOfPoint(PointType(p));
5406 }
5407 for (const auto& s : constraintSegments) {
5408 idOfPoint(PointType(s[0]));
5409 idOfPoint(PointType(s[1]));
5410 }
5411 // Keep vertices_ in Hilbert order (see the point-set constructor).
5412 hilbertSort(vertices_);
5413 syncVertexApproximations();
5414 auto triples = delaunayTriples(vertices_, vertexApproximations_);
5415 buildFromTriples(triples, std::vector<TriangleLabel>(triples.size()));
5416
5417 // The interner's ids went stale twice — the Hilbert reorder and the
5418 // ghost vertex buildFromTriples prepended — so resolve the boundary
5419 // loop and constraint endpoints against the final ids (reals start
5420 // at 1; index 0 is the ghost, whose placeholder coordinates must not
5421 // shadow a real vertex in the map).
5422 vid.clear();
5423 for (VertexIndex i = 1; i < static_cast<VertexIndex>(vertices_.size()); ++i) {
5424 vid.emplace(vertices_[i], i);
5425 }
5426 std::vector<std::vector<VertexIndex>> outerLoops;
5427 outerLoops.reserve(outers.size());
5428 for (const auto& outer : outers) {
5429 std::vector<VertexIndex> loop;
5430 loop.reserve(outer.size());
5431 for (std::size_t i = 0; i < outer.size(); ++i) {
5432 loop.push_back(vid.at(outer[i]));
5433 }
5434 outerLoops.push_back(std::move(loop));
5435 }
5436
5437 std::vector<std::vector<VertexIndex>> holeLoops;
5438 holeLoops.reserve(holes.size());
5439 for (const auto& hole : holes) {
5440 std::vector<VertexIndex> ring;
5441 ring.reserve(hole.size());
5442 for (std::size_t i = 0; i < hole.size(); ++i) {
5443 ring.push_back(vid.at(hole[i]));
5444 }
5445 holeLoops.push_back(std::move(ring));
5446 }
5447
5448 // Where rings touch, one ring's vertex can sit inside another ring's
5449 // edge; splice those in so every constrained edge is unobstructed. Only
5450 // several rings can produce them, so a lone polygon skips the scan.
5451 if (!holes.empty() || outers.size() > 1) {
5452 std::vector<VertexIndex> ringVertices;
5453 for (const auto& ring : outerLoops) {
5454 ringVertices.insert(ringVertices.end(), ring.begin(), ring.end());
5455 }
5456 for (const auto& ring : holeLoops) {
5457 ringVertices.insert(ringVertices.end(), ring.begin(), ring.end());
5458 }
5459 for (auto& ring : outerLoops) {
5460 ring = expandRing(ring, ringVertices);
5461 }
5462 for (auto& ring : holeLoops) {
5463 ring = expandRing(ring, ringVertices);
5464 }
5465 }
5466
5467 // Constrain every outer ring, every hole ring, and every interior
5468 // segment, restore the constrained Delaunay property, then carve away
5469 // the exterior and the hole interiors.
5470 for (const auto& ring : outerLoops) {
5471 for (std::size_t i = 0; i < ring.size(); ++i) {
5472 insertConstraint(ring[i], ring[(i + 1) % ring.size()]);
5473 }
5474 }
5475 for (const auto& ring : holeLoops) {
5476 for (std::size_t i = 0; i < ring.size(); ++i) {
5477 insertConstraint(ring[i], ring[(i + 1) % ring.size()]);
5478 }
5479 }
5480 for (const auto& s : constraintSegments) {
5481 const VertexIndex a = vid.at(PointType(s[0]));
5482 const VertexIndex b = vid.at(PointType(s[1]));
5483 if (a != b) {
5484 insertConstraint(a, b);
5485 }
5486 }
5487 restoreConstrainedDelaunay();
5488
5489 // One seed per hole, taken from a directed ring edge: a hole ring is
5490 // counterclockwise, so the triangle on the left of any of its edges lies
5491 // inside it. Recorded as a triangle value too — that is what tells a
5492 // region query later whether it has swallowed a hole, and unlike a
5493 // triangle id it survives the edits that follow.
5494 std::vector<TriIndex> holeSeeds;
5495 holeSeeds.reserve(holeLoops.size());
5496 for (const auto& ring : holeLoops) {
5497 const TriIndex seed = triangleLeftOf(ring[0], ring[1 % ring.size()]);
5498 if (seed != NO_TRI && !isGhost(seed)) {
5499 holeSeeds.push_back(seed);
5500 holeWitnesses_.push_back(triangleValue(seed));
5501 }
5502 }
5503 markOutOfDomain(holeSeeds);
5504
5505 // Carry each constraint segment's label onto its edge record. Constrained
5506 // edges are never flipped, so they are still in segToEdge_ (keyed by
5507 // coordinates; the label is ignored for the lookup). Mirrors the
5508 // segment-range constructor.
5509 if constexpr (detail::has_label_v<SegmentLabel>) {
5510 for (const auto& s : constraintSegments) {
5511 auto it = segmentMap().find(SegmentType(PointType(s[0]), PointType(s[1])));
5512 if (it != segmentMap().end()) {
5513 it->second.segLabel = detail::copyLabel<SegmentLabel>(s);
5514 }
5515 }
5516 }
5517 }
5518
5519 // Prepends the ghost vertex at index 0, materializes the triangle records
5520 // (normalized to CCW) from vertex-index triples — 0-based into the
5521 // pre-ghost vertices_, so shifted by one here — and their parallel labels,
5522 // then links adjacency and the edge map. With the ghost first, the real
5523 // vertices are the contiguous range [1, vertices_.size()) and insertions
5524 // can append real vertices without disturbing it.
5525 void buildFromTriples(std::vector<std::array<VertexIndex, 3>>& triples,
5526 const std::vector<TriangleLabel>& triLabels) {
5527 vertices_.insert(vertices_.begin(), PointType{}); // ghost (GHOST); coordinates unused
5528 syncVertexApproximations();
5529
5530 for (std::size_t k = 0; k < triples.size(); ++k) {
5531 VertexIndex x = triples[k][0] + 1, y = triples[k][1] + 1, z = triples[k][2] + 1;
5532 if (orientationSign(vertices_[x], vertices_[y], vertices_[z]) < 0) {
5533 std::swap(y, z);
5534 }
5535 assert(orientationSign(vertices_[x], vertices_[y], vertices_[z]) > 0 &&
5536 "Triangulation: degenerate triangle");
5537 triangles_.push_back(Tri{{x, y, z}, {NO_TRI, NO_TRI, NO_TRI}, 0, 0, 0, triLabels[k]});
5538 }
5539 firstGhost_ = static_cast<TriIndex>(triangles_.size());
5540 domainTriangleCount_ = static_cast<std::size_t>(firstGhost_);
5541 buildAdjacency();
5542 for (TriIndex t = 0; t < firstGhost_; ++t) {
5543 noteVertexIncidence(t);
5544 }
5545 mapStale_ = true; // materialized on the first lookup that needs it
5546 assert(checkInvariants());
5547 }
5548
5549 // ---- internal predicates / mutation ----------------------------------
5550
5551 // True if edge e can be flipped: unconstrained, interior (both sides real),
5552 // and the two incident triangles form a strictly convex quadrilateral.
5553 [[nodiscard]] bool flippableEdge(Edge e) const {
5554 const TriIndex t = e.tri;
5555 if (t == NO_TRI || bit(triangles_[t].constrainedMask, e.side)) {
5556 return false;
5557 }
5558 const TriIndex t2 = triangles_[t].nbr[e.side];
5559 if (t2 == NO_TRI || isGhost(t) || isGhost(t2)) {
5560 return false;
5561 }
5562 const Edge m = mirror(e);
5563 const VertexIndex c = triangles_[t].v[e.side];
5564 const VertexIndex a = triangles_[t].v[(e.side + 1) % 3];
5565 const VertexIndex b = triangles_[t].v[(e.side + 2) % 3];
5566 const VertexIndex d = triangles_[t2].v[m.side];
5567 const auto fc = filteredVertex(c);
5568 const auto fd = filteredVertex(d);
5569 const auto oa = detail::orientationSignOf(fc, fd, filteredVertex(a)).value();
5570 const auto ob = detail::orientationSignOf(fc, fd, filteredVertex(b)).value();
5571 return (oa > 0 && ob < 0) || (oa < 0 && ob > 0); // strictly convex quad
5572 }
5573
5574 // Replaces edge e by the opposite diagonal of its quadrilateral, rewriting
5575 // the two incident triangle records and relinking the four outer neighbors.
5576 // Surrounding constrained flags are carried over; the new diagonal is
5577 // unconstrained. Does not touch segToEdge_ (callers re-register). Returns
5578 // false if e is not flippable.
5579 bool flipEdge(Edge e) {
5580 if (!flippableEdge(e)) {
5581 return false;
5582 }
5583 const TriIndex t = e.tri;
5584 const int i = e.side;
5585 const Edge m = mirror(e);
5586 const TriIndex t2 = m.tri;
5587 const int j = m.side;
5588
5589 const VertexIndex c = triangles_[t].v[i];
5590 const VertexIndex a = triangles_[t].v[(i + 1) % 3];
5591 const VertexIndex b = triangles_[t].v[(i + 2) % 3];
5592 const VertexIndex d = triangles_[t2].v[j];
5593
5594 const TriIndex nCA = triangles_[t].nbr[(i + 2) % 3];
5595 const TriIndex nBC = triangles_[t].nbr[(i + 1) % 3];
5596 const TriIndex nDB = triangles_[t2].nbr[(j + 2) % 3];
5597 const TriIndex nAD = triangles_[t2].nbr[(j + 1) % 3];
5598 const bool cCA = bit(triangles_[t].constrainedMask, (i + 2) % 3);
5599 const bool cBC = bit(triangles_[t].constrainedMask, (i + 1) % 3);
5600 const bool cDB = bit(triangles_[t2].constrainedMask, (j + 2) % 3);
5601 const bool cAD = bit(triangles_[t2].constrainedMask, (j + 1) % 3);
5602
5603 const int sAD = (nAD != NO_TRI) ? findSide(nAD, t2) : -1;
5604 const int sBC = (nBC != NO_TRI) ? findSide(nBC, t) : -1;
5605
5606 triangles_[t].v = {c, a, d};
5607 triangles_[t].nbr = {nAD, t2, nCA};
5608 triangles_[t].constrainedMask = mask(cAD, false, cCA);
5609 triangles_[t].triLabel = TriangleLabel{}; // flipped-in triangle has no source label
5610
5611 triangles_[t2].v = {c, d, b};
5612 triangles_[t2].nbr = {nDB, nBC, t};
5613 triangles_[t2].constrainedMask = mask(cDB, cBC, false);
5614 triangles_[t2].triLabel = TriangleLabel{};
5615
5616 if (sAD >= 0) {
5617 triangles_[nAD].nbr[sAD] = t;
5618 }
5619 if (sBC >= 0) {
5620 triangles_[nBC].nbr[sBC] = t2;
5621 }
5622 // assert(checkInvariants()); // O(n): uncomment when debugging
5623 return true;
5624 }
5625
5626 // ---- point insertion internals ----------------------------------------
5627
5628 // Re-registers the three sides of real triangle t in segToEdge_, keeping
5629 // any label already stored under a persisting edge key. (registerSides
5630 // would reset those labels — acceptable for flip, whose contract says so,
5631 // but an insertion only re-points surviving edges and must not wipe them.)
5632 void reRegisterSides(TriIndex t) {
5633 for (std::int8_t s = 0; s < 3; ++s) {
5634 const SegmentType key = edgeSegment(Edge{t, s});
5635 auto it = segmentMap().find(key);
5636 if (it == segmentMap().end()) {
5637 segmentMap().emplace(key, Edge{t, s});
5638 } else {
5639 it->second.tri = t;
5640 it->second.side = s;
5641 }
5642 }
5643 }
5644
5645 // Ensures room for `extra` more elements without reallocation, growing
5646 // geometrically when needed. (A bare reserve(size + k) reallocates to
5647 // exactly that capacity, which would make repeated insertions quadratic.)
5648 template <class T>
5649 static void reserveExtra(std::vector<T>& v, std::size_t extra) {
5650 if (v.capacity() < v.size() + extra) {
5651 v.reserve(std::max(v.size() + extra, v.capacity() * 2));
5652 }
5653 }
5654
5655 // Frees k slots directly above the real block — for the real triangles an
5656 // insertion creates — by relocating the k lowest ghost triangles to the
5657 // end of triangles_ and bumping firstGhost_. Only neighbor links reference
5658 // ghost triangles (segToEdge_ and hint_ never do), so relocation rewires
5659 // those and nothing else. Requires k ghosts (any nonempty triangulation
5660 // has >= 3) and spare capacity (callers reserve). Returns the first freed
5661 // slot; the freed slots hold stale copies the caller must overwrite.
5662 TriIndex makeRoom(int k) {
5663 const TriIndex base = firstGhost_;
5664 const TriIndex oldSize = static_cast<TriIndex>(triangles_.size());
5665 assert(oldSize - base >= k);
5666 for (int j = 0; j < k; ++j) {
5667 triangles_.push_back(triangles_[base + j]);
5668 }
5669 for (int j = 0; j < k; ++j) {
5670 const TriIndex moved = oldSize + j;
5671 for (int s = 0; s < 3; ++s) {
5672 TriIndex& nb = triangles_[moved].nbr[s];
5673 assert(nb != NO_TRI); // ghosts always have three neighbors
5674 if (nb >= base && nb < base + k) {
5675 nb = nb - base + oldSize; // the neighbor was relocated too
5676 }
5677 for (int q = 0; q < 3; ++q) {
5678 if (triangles_[nb].nbr[q] == base + j) {
5679 triangles_[nb].nbr[q] = moved;
5680 }
5681 }
5682 }
5683 }
5684 firstGhost_ += k;
5685 return base;
5686 }
5687
5688 // Structural vertex insertion shared by insert and insertDelaunay: locates
5689 // @p p and subdivides the triangle (1->3) or edge (2->4; 2->3 on the hull,
5690 // where the ghost across splits too) containing it, or grows the hull when
5691 // p is outside. Returns the new vertex and one real triangle incident to
5692 // it, or nullopt — with the triangulation unchanged — when p is already a
5693 // vertex or the triangulation is empty. Splits inherit their parents'
5694 // out-of-domain flags, so an insertion inside a polygon's domain keeps the
5695 // carved-away region carved (a point outside the closed polygon is a
5696 // precondition violation; see insert).
5697 std::optional<std::pair<VertexIndex, TriIndex>> insertVertexImpl(const PointType& p) {
5698 const TriIndex t0 = locateIndex(p);
5699 if (t0 == NO_TRI) {
5700 return std::nullopt; // empty triangulation
5701 }
5702 if (isGhost(t0)) {
5703 // p is strictly outside the hull (a point on the hull boundary
5704 // lands in a real triangle's closure), so it cannot be a duplicate.
5705 return growHull(t0, p);
5706 }
5707 const auto& tv = triangles_[t0].v;
5708 for (int k = 0; k < 3; ++k) {
5709 if (vertices_[tv[k]] == p) {
5710 return std::nullopt; // p is already a vertex
5711 }
5712 }
5713 // p is in t0's closure (locateIndex stopped here) and is not a vertex, so
5714 // it lies strictly inside either the triangle or exactly one edge.
5715 int onSide = -1;
5716 const auto fp = filteredPoint(p);
5717 for (int k = 0; k < 3; ++k) {
5718 if (detail::orientationSignOf(filteredVertex(tv[(k + 1) % 3]),
5719 filteredVertex(tv[(k + 2) % 3]), fp).value() == 0) {
5720 onSide = k;
5721 }
5722 }
5723 if (onSide < 0) {
5724 return splitTriangle(t0, p);
5725 }
5726 return splitEdge(t0, onSide, p);
5727 }
5728
5729 // 1->3 subdivision: replaces the in-domain triangle t, whose interior
5730 // strictly contains p, by the fan from its three edges to the new vertex.
5731 std::pair<VertexIndex, TriIndex> splitTriangle(TriIndex t, const PointType& p) {
5732 reserveExtra(vertices_, 1);
5733 reserveExtra(triangles_, 2);
5734 const VertexIndex vp = appendVertex(p);
5735 const TriIndex n1 = makeRoom(2);
5736 const TriIndex n2 = n1 + 1;
5737
5738 // Copy the record after makeRoom, whose relocation already fixed the
5739 // neighbor links of every triangle adjacent to a moved ghost.
5740 const Tri old = triangles_[t];
5741 const VertexIndex a = old.v[0];
5742 const VertexIndex b = old.v[1];
5743 const VertexIndex c = old.v[2];
5744 // Children (CCW because p is strictly interior); each keeps one parent
5745 // edge — with that edge's constrained flag — as its side 2, opposite
5746 // vp, and inherits the parent's out-of-domain flag.
5747 triangles_[t] = Tri{{a, b, vp},
5748 {n1, n2, old.nbr[2]},
5749 mask(false, false, bit(old.constrainedMask, 2)),
5750 old.outOfDomain, 0, TriangleLabel{}};
5751 triangles_[n1] = Tri{{b, c, vp},
5752 {n2, t, old.nbr[0]},
5753 mask(false, false, bit(old.constrainedMask, 0)),
5754 old.outOfDomain, 0, TriangleLabel{}};
5755 triangles_[n2] = Tri{{c, a, vp},
5756 {t, n1, old.nbr[1]},
5757 mask(false, false, bit(old.constrainedMask, 1)),
5758 old.outOfDomain, 0, TriangleLabel{}};
5759 triangles_[old.nbr[0]].nbr[findSide(old.nbr[0], t)] = n1;
5760 triangles_[old.nbr[1]].nbr[findSide(old.nbr[1], t)] = n2;
5761 domainTriangleCount_ += old.outOfDomain ? 0 : 2;
5762 for (const TriIndex x : {t, n1, n2}) {
5763 reRegisterSides(x);
5764 }
5765 hint_ = t;
5766 // assert(checkInvariants() && checkEdgeMap()); // O(n): uncomment when debugging
5767 return {vp, t};
5768 }
5769
5770 // 2->4 (interior edge) or 2->3 (hull edge, where the ghost across splits
5771 // too) subdivision: p lies strictly inside the edge of triangle t opposite
5772 // t.v[s]. The edge splits into two collinear halves, which inherit its
5773 // constrained flag and label; each incident triangle splits in two, its
5774 // children inheriting its out-of-domain flag.
5775 std::pair<VertexIndex, TriIndex> splitEdge(TriIndex t, int s, const PointType& p) {
5776 const bool ghostSide = isGhost(triangles_[t].nbr[s]);
5777 reserveExtra(vertices_, 1);
5778 reserveExtra(triangles_, 2);
5779
5780 const bool cUW = bit(triangles_[t].constrainedMask, s);
5781 const VertexIndex vp = appendVertex(p);
5782 const TriIndex n1 = makeRoom(ghostSide ? 1 : 2);
5783
5784 // Read the records after makeRoom: it relocates the lowest ghosts, so a
5785 // ghost id read before it is stale (and now names a freed real slot),
5786 // while its relocation already fixed the neighbor links of every
5787 // triangle adjacent to a moved ghost.
5788 const TriIndex across = triangles_[t].nbr[s];
5789 const Tri oldT = triangles_[t];
5790 const VertexIndex apex = oldT.v[s];
5791 const VertexIndex u = oldT.v[(s + 1) % 3];
5792 const VertexIndex w = oldT.v[(s + 2) % 3];
5793 const TriIndex nWApex = oldT.nbr[(s + 1) % 3]; // neighbor across {w, apex}
5794 const TriIndex nApexU = oldT.nbr[(s + 2) % 3]; // neighbor across {apex, u}
5795 const bool cWApex = bit(oldT.constrainedMask, (s + 1) % 3);
5796 const bool cApexU = bit(oldT.constrainedMask, (s + 2) % 3);
5797
5798 // Both halves inherit the split edge's label; the old key is dropped
5799 // now and the halves are registered (then labeled) below.
5800 SegmentLabel halfLabel{};
5801 {
5802 const auto it = segmentMap().find(SegmentType(vertices_[u], vertices_[w]));
5803 assert(it != segmentMap().end());
5804 halfLabel = it->second.segLabel;
5805 segmentMap().erase(it);
5806 }
5807
5808 if (!ghostSide) {
5809 // Interior edge: t = (apex,u,w) and t2 = (apex2,w,u) become the
5810 // four triangles fanning around vp.
5811 const TriIndex n2 = n1 + 1;
5812 const TriIndex t2 = across;
5813 const int j = findSide(t2, t);
5814 const Tri oldT2 = triangles_[t2];
5815 const VertexIndex apex2 = oldT2.v[j];
5816 assert(oldT2.v[(j + 1) % 3] == w && oldT2.v[(j + 2) % 3] == u);
5817 const TriIndex nApex2W = oldT2.nbr[(j + 2) % 3]; // across {apex2, w}
5818 const TriIndex nUApex2 = oldT2.nbr[(j + 1) % 3]; // across {u, apex2}
5819 const bool cApex2W = bit(oldT2.constrainedMask, (j + 2) % 3);
5820 const bool cUApex2 = bit(oldT2.constrainedMask, (j + 1) % 3);
5821
5822 triangles_[t] = Tri{{apex, u, vp},
5823 {n2, n1, nApexU},
5824 mask(cUW, false, cApexU),
5825 oldT.outOfDomain, 0, TriangleLabel{}};
5826 triangles_[n1] = Tri{{apex, vp, w},
5827 {t2, nWApex, t},
5828 mask(cUW, cWApex, false),
5829 oldT.outOfDomain, 0, TriangleLabel{}};
5830 triangles_[t2] = Tri{{apex2, w, vp},
5831 {n1, n2, nApex2W},
5832 mask(cUW, false, cApex2W),
5833 oldT2.outOfDomain, 0, TriangleLabel{}};
5834 triangles_[n2] = Tri{{apex2, vp, u},
5835 {t, nUApex2, t2},
5836 mask(cUW, cUApex2, false),
5837 oldT2.outOfDomain, 0, TriangleLabel{}};
5838 triangles_[nWApex].nbr[findSide(nWApex, t)] = n1;
5839 triangles_[nUApex2].nbr[findSide(nUApex2, t2)] = n2;
5840 domainTriangleCount_ += (oldT.outOfDomain ? 0 : 1) + (oldT2.outOfDomain ? 0 : 1);
5841 for (const TriIndex x : {t, n1, t2, n2}) {
5842 reRegisterSides(x);
5843 }
5844 } else {
5845 // Hull edge: t splits in two and so does the ghost across, keeping
5846 // the ghost convention v = {real, real, GHOST} with nbr[2] real.
5847 const TriIndex g = across;
5848 const Tri oldG = triangles_[g];
5849 assert(oldG.v[0] == u && oldG.v[1] == w && oldG.v[2] == GHOST && oldG.nbr[2] == t);
5850 const TriIndex gw = oldG.nbr[0]; // ghost-ring neighbor across {w, ghost}
5851 const TriIndex gu = oldG.nbr[1]; // ghost-ring neighbor across {u, ghost}
5852
5853 const TriIndex g2 = static_cast<TriIndex>(triangles_.size());
5854 triangles_.push_back(Tri{{vp, w, GHOST},
5855 {gw, g, n1},
5856 mask(false, false, cUW),
5857 0, 0, TriangleLabel{}});
5858 triangles_[t] = Tri{{apex, u, vp},
5859 {g, n1, nApexU},
5860 mask(cUW, false, cApexU),
5861 oldT.outOfDomain, 0, TriangleLabel{}};
5862 triangles_[n1] = Tri{{apex, vp, w},
5863 {g2, nWApex, t},
5864 mask(cUW, cWApex, false),
5865 oldT.outOfDomain, 0, TriangleLabel{}};
5866 triangles_[g] = Tri{{u, vp, GHOST},
5867 {g2, gu, t},
5868 mask(false, false, cUW),
5869 0, 0, TriangleLabel{}};
5870 triangles_[nWApex].nbr[findSide(nWApex, t)] = n1;
5871 triangles_[gw].nbr[findSide(gw, g)] = g2;
5872 domainTriangleCount_ += oldT.outOfDomain ? 0 : 1;
5873 for (const TriIndex x : {t, n1}) {
5874 reRegisterSides(x);
5875 }
5876 }
5877 if constexpr (detail::has_label_v<SegmentLabel>) {
5878 segmentMap().at(SegmentType(vertices_[u], vertices_[vp])).segLabel = halfLabel;
5879 segmentMap().at(SegmentType(vertices_[vp], vertices_[w])).segLabel = halfLabel;
5880 }
5881 hint_ = t;
5882 // assert(checkInvariants() && checkEdgeMap()); // O(n): uncomment when debugging
5883 return std::pair{vp, t};
5884 }
5885
5886 // Hull growth: p lies strictly outside the convex hull, and the locate
5887 // walk stopped at ghost g0, whose base hull edge p strictly sees. Joins p
5888 // to the maximal contiguous chain of hull edges visible from it — the
5889 // fan is the only triangulation of the pocket between hull and point, as
5890 // any other diagonal would cut into the old hull — replacing the chain's
5891 // m ghosts by m real triangles, closed by two new ghosts along the new
5892 // hull edges {u_0, p} and {p, u_m}. Visibility is strict, so hull edges
5893 // collinear with p are never in the chain and no degenerate triangle can
5894 // arise. The new triangles are in-domain; a base hull edge that is
5895 // constrained stays constrained and simply becomes interior. (For a
5896 // polygon triangulation an outside point is a precondition violation —
5897 // see insert — so no domain fencing happens here.)
5898 std::pair<VertexIndex, TriIndex> growHull(TriIndex g0, const PointType& p) {
5899 const auto fp = filteredPoint(p);
5900 const auto visible = [&](TriIndex g) {
5901 const auto& gv = triangles_[g].v;
5902 return detail::orientationSignOf(filteredVertex(gv[0]), filteredVertex(gv[1]), fp)
5903 .value() < 0;
5904 };
5905 assert(isGhost(g0) && visible(g0));
5906
5907 // Rewind to the first visible ghost, then record the chain through its
5908 // inner anchors (r, s): ghost ids go stale across makeRoom, the inner
5909 // real triangles do not. For a ghost {a, b, GHOST}, nbr[1] is the ring
5910 // predecessor (sharing a) and nbr[0] the successor (sharing b).
5911 std::size_t guard = 0;
5912 const std::size_t ringCap = triangles_.size() + 1;
5913 TriIndex gStart = g0;
5914 while (visible(triangles_[gStart].nbr[1]) && ++guard < ringCap) {
5915 gStart = triangles_[gStart].nbr[1];
5916 }
5917 std::vector<TriIndex> inner; // r_i: the real triangle inside base i
5918 std::vector<std::int8_t> innerSide; // its side facing that base
5919 std::vector<VertexIndex> u; // hull chain u_0 -> ... -> u_m
5920 TriIndex g = gStart;
5921 guard = 0;
5922 while (visible(g) && ++guard < ringCap) {
5923 const TriIndex r = triangles_[g].nbr[2];
5924 inner.push_back(r);
5925 innerSide.push_back(findSide(r, g));
5926 u.push_back(triangles_[g].v[0]);
5927 g = triangles_[g].nbr[0];
5928 }
5929 u.push_back(triangles_[g].v[0]); // == v[1] of the last chain ghost
5930 const int m = static_cast<int>(inner.size());
5931 assert(m >= 1);
5932
5933 reserveExtra(vertices_, 1);
5934 reserveExtra(triangles_, m == 1 ? 2 : static_cast<std::size_t>(m));
5935 const VertexIndex vp = appendVertex(p);
5936 const TriIndex nr = makeRoom(m); // slots for the m new real triangles
5937
5938 // Re-resolve the (possibly relocated) chain ghosts and the ring ends.
5939 std::vector<TriIndex> dead(static_cast<std::size_t>(m));
5940 for (int i = 0; i < m; ++i) {
5941 dead[i] = triangles_[inner[i]].nbr[innerSide[i]];
5942 }
5943 const TriIndex prevG = triangles_[dead.front()].nbr[1];
5944 const TriIndex nextG = triangles_[dead.back()].nbr[0];
5945
5946 // The two new ghosts reuse dead chain slots (plus a push_back when
5947 // only one ghost died); leftover dead slots are compacted away below,
5948 // so the vector grows by exactly two triangles in every case.
5949 const TriIndex ga = dead[0];
5950 TriIndex gb;
5951 if (m == 1) {
5952 gb = static_cast<TriIndex>(triangles_.size());
5953 triangles_.push_back(Tri{}); // written below
5954 } else {
5955 gb = dead[1];
5956 }
5957
5958 // The m pocket triangles {u_{i+1}, u_i, vp} fanning vp: side 2 is the
5959 // base hull edge (keeping its constrained flag), sides 0/1 the spokes.
5960 for (int i = 0; i < m; ++i) {
5961 const bool cBase = bit(triangles_[inner[i]].constrainedMask, innerSide[i]);
5962 triangles_[nr + i] = Tri{{u[i + 1], u[i], vp},
5963 {i > 0 ? nr + i - 1 : ga,
5964 i < m - 1 ? nr + i + 1 : gb, inner[i]},
5965 mask(false, false, cBase),
5966 0, 0, TriangleLabel{}};
5967 triangles_[inner[i]].nbr[innerSide[i]] = nr + i;
5968 }
5969 triangles_[ga] = Tri{{u.front(), vp, GHOST}, {gb, prevG, nr}, 0, 0, 0, TriangleLabel{}};
5970 triangles_[gb] =
5971 Tri{{vp, u.back(), GHOST}, {nextG, ga, nr + m - 1}, 0, 0, 0, TriangleLabel{}};
5972 triangles_[prevG].nbr[0] = ga;
5973 triangles_[nextG].nbr[1] = gb;
5974
5975 if (m >= 3) {
5976 // m ghosts died but only two slots were reused: fill the remaining
5977 // holes with live ghosts taken from the end of triangles_ (the
5978 // same link rewiring as makeRoom, in the other direction), then
5979 // drop the all-dead tail.
5980 std::vector<TriIndex> holes(dead.begin() + 2, dead.end());
5981 std::sort(holes.begin(), holes.end());
5982 const auto isHole = [&](TriIndex t) {
5983 return std::binary_search(holes.begin(), holes.end(), t);
5984 };
5985 TriIndex last = static_cast<TriIndex>(triangles_.size()) - 1;
5986 for (std::size_t h = 0; h < holes.size() && holes[h] < last;) {
5987 if (isHole(last)) {
5988 --last; // already dead: it will be truncated
5989 continue;
5990 }
5991 const TriIndex hole = holes[h];
5992 triangles_[hole] = triangles_[last];
5993 for (int s = 0; s < 3; ++s) {
5994 const TriIndex nb = triangles_[hole].nbr[s];
5995 for (int q = 0; q < 3; ++q) {
5996 if (triangles_[nb].nbr[q] == last) {
5997 triangles_[nb].nbr[q] = hole;
5998 }
5999 }
6000 }
6001 ++h;
6002 --last;
6003 }
6004 triangles_.resize(triangles_.size() - static_cast<std::size_t>(m - 2));
6005 }
6006
6007 for (int i = 0; i < m; ++i) {
6008 reRegisterSides(nr + i);
6009 }
6010 domainTriangleCount_ += static_cast<std::size_t>(m);
6011 hint_ = nr;
6012 // assert(checkInvariants() && checkEdgeMap()); // O(n): uncomment when debugging
6013 return {vp, nr};
6014 }
6015
6016 // Restores the local Delaunay property after an insertion by Lawson flips:
6017 // pops suspect edges, flips any that are flippable (so never a constrained
6018 // edge) with an apex strictly inside the opposite circumcircle, and
6019 // re-queues the rewritten triangles' sides. Terminates by Lawson's
6020 // argument; the guard is a safety net only.
6021 void legalize(std::vector<SegmentType>& suspect) {
6022 std::size_t guard = 0;
6023 const std::size_t cap = triangles_.size() * triangles_.size() + 64;
6024 while (!suspect.empty() && ++guard < cap) {
6025 const SegmentType s = suspect.back();
6026 suspect.pop_back();
6027 const auto se = segmentMap().find(s);
6028 if (se == segmentMap().end() || !flippableEdge(se->second)) {
6029 continue; // edge gone, constrained, or quad not convex
6030 }
6031 const Edge e = se->second;
6032 const Edge m = mirror(e);
6033 const auto& tv = triangles_[e.tri].v;
6034 const VertexIndex d = triangles_[m.tri].v[m.side];
6035 if (detail::inCircleSignOf(filteredVertex(tv[0]), filteredVertex(tv[1]),
6036 filteredVertex(tv[2]), filteredVertex(d)) !=
6037 std::partial_ordering::greater) {
6038 continue; // locally Delaunay (the test is symmetric across e)
6039 }
6040 const TriIndex t = e.tri;
6041 const TriIndex t2 = m.tri;
6042 flip(s);
6043 // The rewritten triangles' sides — the four quad edges plus the new
6044 // diagonal, which the test above now accepts — are suspect again.
6045 for (const TriIndex x : {t, t2}) {
6046 for (std::int8_t q = 0; q < 3; ++q) {
6047 suspect.push_back(edgeSegment(Edge{x, q}));
6048 }
6049 }
6050 }
6051 assert(suspect.empty() && "Triangulation: legalization did not terminate");
6052 }
6053
6054 // Debug validation of segToEdge_: every real triangle side is registered
6055 // under its edge key, and every entry references a real triangle that
6056 // still owns the keyed edge (no stale handles or leftover keys). Kept
6057 // separate from checkInvariants because flipEdge checks invariants at a
6058 // point where the map is deliberately stale (its callers re-register).
6059 [[nodiscard]] bool checkEdgeMap() const {
6060 for (TriIndex t = 0; t < firstGhost_; ++t) {
6061 for (std::int8_t s = 0; s < 3; ++s) {
6062 if (!segmentMap().contains(edgeSegment(Edge{t, s}))) {
6063 return false;
6064 }
6065 }
6066 }
6067 for (const auto& [seg, e] : segmentMap()) {
6068 if (e.tri < 0 || e.tri >= firstGhost_) {
6069 return false;
6070 }
6071 const auto& tv = triangles_[e.tri].v;
6072 if (SegmentType(vertices_[tv[(e.side + 1) % 3]],
6073 vertices_[tv[(e.side + 2) % 3]]) != seg) {
6074 return false;
6075 }
6076 }
6077 return true;
6078 }
6079
6080 // Locates the triangle containing p by a stochastic visibility walk (random
6081 // start edge guarantees termination). Returns a ghost triangle if p is
6082 // outside the triangulated region, NO_TRI only if empty. Starts at @p start
6083 // when that is a real triangle, else seeds from hint_; updates hint_ either
6084 // way. @p p may use a different point type.
6085 template <class QueryPoint>
6086 [[nodiscard]] TriIndex locateIndex(const QueryPoint& p, TriIndex start = NO_TRI) const {
6087 if (triangles_.empty()) {
6088 return NO_TRI;
6089 }
6090 TriIndex t = realTriangle(start) ? start
6091 : ((hint_ != NO_TRI && !isGhost(hint_)) ? hint_ : 0);
6092 TriIndex from = NO_TRI;
6093 const std::size_t cap = triangles_.size() * 3 + 16;
6094 // The walk tests p against three edges of every triangle it steps
6095 // through, so p is converted once here rather than once per test.
6096 const auto fq = filteredPoint(p);
6097 for (std::size_t step = 0; step < cap; ++step) {
6098 if (isGhost(t)) {
6099 hint_ = NO_TRI;
6100 return t; // outside the triangulated region
6101 }
6102 const auto& T = triangles_[t];
6103 const int begin = static_cast<int>(rng_() % 3); // random start: provably terminates
6104 TriIndex next = NO_TRI;
6105 for (int k = 0; k < 3; ++k) {
6106 const int s = (begin + k) % 3;
6107 if (T.nbr[s] == from) {
6108 continue;
6109 }
6110 const VertexIndex ea = T.v[(s + 1) % 3];
6111 const VertexIndex eb = T.v[(s + 2) % 3];
6112 if (detail::orientationSignOf(filteredVertex(ea), filteredVertex(eb), fq)
6113 .value() < 0) {
6114 next = T.nbr[s];
6115 break;
6116 }
6117 }
6118 if (next == NO_TRI) {
6119 hint_ = t;
6120 return t;
6121 }
6122 from = t;
6123 t = next;
6124 }
6125 return t;
6126 }
6127
6128 // Links neighbor pointers between real triangles sharing an edge, then closes
6129 // every still-unmatched (boundary) edge with a ghost triangle to the ghost
6130 // vertex and links those ghosts into a ring, so every edge has two sides.
6131 void buildAdjacency() {
6132 // Undirected edge key (endpoints sorted) for matching the two sides.
6133 const auto key = [](VertexIndex u, VertexIndex w) {
6134 return u < w ? std::pair<VertexIndex, VertexIndex>{u, w} : std::pair<VertexIndex, VertexIndex>{w, u};
6135 };
6136
6137 // Every side of every triangle, sorted by its edge key, so the two
6138 // sides of an interior edge land next to each other and a boundary
6139 // edge's single side lands alone. Sorting an array beats matching them
6140 // through a search tree, which allocates a node per edge and revisits
6141 // it to erase.
6142 struct Side {
6143 std::pair<VertexIndex, VertexIndex> edge;
6144 TriIndex tri;
6145 int side;
6146 };
6147 std::vector<Side> sides;
6148 sides.reserve(static_cast<std::size_t>(std::max(firstGhost_, TriIndex(0))) * 3);
6149 for (TriIndex t = 0; t < firstGhost_; ++t) {
6150 for (int i = 0; i < 3; ++i) {
6151 sides.push_back(Side{key(triangles_[t].v[(i + 1) % 3],
6152 triangles_[t].v[(i + 2) % 3]),
6153 t, i});
6154 }
6155 }
6156 std::sort(sides.begin(), sides.end(),
6157 [](const Side& a, const Side& b) { return a.edge < b.edge; });
6158
6159 // The still-unmatched sides, which are the boundary ones.
6160 std::vector<Side> edges;
6161 for (std::size_t i = 0; i < sides.size();) {
6162 if (i + 1 < sides.size() && sides[i].edge == sides[i + 1].edge) {
6163 const Side& one = sides[i];
6164 const Side& other = sides[i + 1];
6165 triangles_[one.tri].nbr[one.side] = other.tri;
6166 triangles_[other.tri].nbr[other.side] = one.tri;
6167 i += 2;
6168 } else {
6169 edges.push_back(sides[i]);
6170 ++i;
6171 }
6172 }
6173
6174 std::map<VertexIndex, std::pair<TriIndex, int>> ghostEdges;
6175 for (const Side& unmatched : edges) {
6176 const TriIndex t = unmatched.tri;
6177 const int i = unmatched.side;
6178 const VertexIndex a = triangles_[t].v[(i + 1) % 3];
6179 const VertexIndex b = triangles_[t].v[(i + 2) % 3];
6180 const TriIndex g = static_cast<TriIndex>(triangles_.size());
6181 triangles_.push_back(Tri{{a, b, GHOST}, {NO_TRI, NO_TRI, NO_TRI}, 0});
6182 triangles_[g].nbr[2] = t; // side 2 (opposite ghost) is the shared edge {a,b}
6183 triangles_[t].nbr[i] = g;
6184 for (auto [realVertex, side] :
6185 {std::pair<VertexIndex, int>{b, 0}, std::pair<VertexIndex, int>{a, 1}}) {
6186 auto it = ghostEdges.find(realVertex);
6187 if (it == ghostEdges.end()) {
6188 ghostEdges.emplace(realVertex, std::pair<TriIndex, int>{g, side});
6189 } else {
6190 auto [g2, s2] = it->second;
6191 triangles_[g].nbr[side] = g2;
6192 triangles_[g2].nbr[s2] = g;
6193 ghostEdges.erase(it);
6194 }
6195 }
6196 }
6197 assert(ghostEdges.empty() && "Triangulation: open boundary (input is not a triangulation)");
6198 }
6199};
6200
6201// The Kirkpatrick hierarchy. Level 0 is the mesh, extended by a ring of
6202// triangles filling an enclosing box, so that every vertex but the four box
6203// corners is interior and can be removed. Each further level removes an
6204// independent set of low-degree vertices and retriangulates the hole each star
6205// leaves; the new triangles record the ones they cover, which is what a query
6206// descends. The four corners survive every level, so the top is the box itself
6207// as a couple of triangles.
6208template <TriangleConcept TriangleType, SegmentConcept SegmentType>
6211 return; // already drawn against this mesh; redrawing would find nothing
6212 }
6213 pointLocation_.reset();
6214 const std::size_t meshCells = static_cast<std::size_t>(firstGhost_);
6215 if (meshCells == 0 || triangles_.size() <= meshCells) {
6216 return; // no triangle to index, or no ghost ring to read the hull off
6217 }
6218 auto kp = std::make_shared<Kirkpatrick>();
6219 using Cell = typename Kirkpatrick::Cell;
6220 // The triangle of every cell. A finished hierarchy keeps only the top
6221 // level's, a query below it being handed down rather than placed, but the
6222 // construction reads all of them.
6223 std::vector<std::array<VertexIndex, 3>> shape;
6224
6225 // ---- level 0, first half: the mesh triangles, at their own indices ----
6226 kp->cells.resize(meshCells);
6227 shape.resize(meshCells);
6228 for (std::size_t t = 0; t < meshCells; ++t) {
6229 shape[t] = triangles_[t].v;
6230 kp->cells[t].runBegin = static_cast<std::uint32_t>(t);
6231 }
6232
6233 // ---- the hull, read off the ghost ring -------------------------------
6234 // ring[i] -> ring[i + 1] is a boundary edge with the mesh on its left, and
6235 // ringTri[i] is the mesh triangle there.
6236 std::vector<VertexIndex> ring;
6237 std::vector<TriIndex> ringTri;
6238 {
6239 const TriIndex first = firstGhost_;
6240 TriIndex g = first;
6241 do {
6242 const Tri& ghost = triangles_[static_cast<std::size_t>(g)];
6243 ring.push_back(ghost.v[0]);
6244 ringTri.push_back(ghost.nbr[2]);
6245 g = ghost.nbr[0]; // the ghost across {ghost.v[1], GHOST}
6246 } while (g != first && ring.size() <= triangles_.size());
6247 if (g != first || ring.size() < 3) {
6248 return;
6249 }
6250 }
6251 const std::uint32_t hullSize = static_cast<std::uint32_t>(ring.size());
6252
6253 // ---- the enclosing box -----------------------------------------------
6254 NumberType xlo = vertices_[static_cast<std::size_t>(ring[0])].x();
6255 NumberType xhi = xlo;
6256 NumberType ylo = vertices_[static_cast<std::size_t>(ring[0])].y();
6257 NumberType yhi = ylo;
6258 for (const VertexIndex v : ring) {
6259 const PointType& p = vertices_[static_cast<std::size_t>(v)];
6260 if (p.x() < xlo) xlo = p.x();
6261 if (xhi < p.x()) xhi = p.x();
6262 if (p.y() < ylo) ylo = p.y();
6263 if (yhi < p.y()) yhi = p.y();
6264 }
6265 if constexpr (std::numeric_limits<NumberType>::is_specialized &&
6266 std::numeric_limits<NumberType>::is_integer &&
6267 std::numeric_limits<NumberType>::is_bounded) {
6268 // Stepping the box out would wrap: leave the mesh unindexed rather than
6269 // fold the outside onto the inside.
6270 if (xlo == std::numeric_limits<NumberType>::lowest() ||
6271 ylo == std::numeric_limits<NumberType>::lowest() ||
6272 xhi == std::numeric_limits<NumberType>::max() ||
6273 yhi == std::numeric_limits<NumberType>::max()) {
6274 return;
6275 }
6276 }
6277 const NumberType unit(1);
6278 const NumberType bxlo = xlo - unit;
6279 const NumberType bylo = ylo - unit;
6280 const NumberType bxhi = xhi + unit;
6281 const NumberType byhi = yhi + unit;
6282 if (!(bxlo < xlo) || !(bylo < ylo) || !(xhi < bxhi) || !(yhi < byhi)) {
6283 return; // coordinates too large to step away from (inexact types)
6284 }
6285 kp->extra = {PointType(bxlo, bylo), PointType(bxhi, bylo), PointType(bxhi, byhi),
6286 PointType(bxlo, byhi)};
6287 if constexpr (detail::filtersSign<VertexCoordinate>) {
6288 kp->extraApprox.reserve(kp->extra.size());
6289 for (const PointType& corner : kp->extra) {
6290 kp->extraApprox.push_back(detail::approximatePoint(corner));
6291 }
6292 }
6293 const auto cornerVertex = [&](std::size_t j) {
6294 return static_cast<VertexIndex>(vertices_.size() + j);
6295 };
6296
6297 // ---- the geometry the construction runs on ---------------------------
6298 const auto orient = [&](VertexIndex a, VertexIndex b, VertexIndex c) {
6299 return detail::orientationSignOf(kirkpatrickVertex(*kp, a), kirkpatrickVertex(*kp, b),
6300 kirkpatrickVertex(*kp, c))
6301 .value();
6302 };
6303 const auto holds = [&](VertexIndex a, VertexIndex b, VertexIndex c, VertexIndex p) {
6304 return !(orient(a, b, p) < 0) && !(orient(b, c, p) < 0) && !(orient(c, a, p) < 0);
6305 };
6306 const auto addLeaf = [&](VertexIndex a, VertexIndex b, VertexIndex c, TriIndex seed) {
6307 Cell cell;
6308 cell.runBegin = static_cast<std::uint32_t>(seed);
6309 cell.runCount = Kirkpatrick::FULL_TURN; // fills the box; seeds, never answers
6310 kp->cells.push_back(cell);
6311 shape.push_back({a, b, c});
6312 };
6313
6314 // Ear clipping of a simple counterclockwise polygon, reporting each ear as
6315 // the triple of *positions* it cut, in clipping order. Only a strictly
6316 // convex corner is cut, so every corner of the polygon survives in some
6317 // triangle: a collinear one dropped instead would leave the vertex on an
6318 // edge of the retriangulation without being a corner of it, which no later
6319 // level could then remove. @p mountain says the polygon is a monotone
6320 // mountain over its first edge: its two base corners are never cut, and
6321 // every convex corner is then an ear, which is what lets the pass skip
6322 // testing a corner against the rest of the polygon.
6323 std::vector<std::array<std::uint32_t, 3>> ears;
6324 std::vector<std::uint32_t> prevAt;
6325 std::vector<std::uint32_t> nextAt;
6326 std::vector<std::int8_t> turnAt;
6327 const auto earClip = [&](const std::vector<VertexIndex>& poly, bool mountain) {
6328 ears.clear();
6329 const std::uint32_t m = static_cast<std::uint32_t>(poly.size());
6330 if (m < 3) {
6331 return false;
6332 }
6333 prevAt.resize(m);
6334 nextAt.resize(m);
6335 turnAt.resize(m);
6336 for (std::uint32_t i = 0; i < m; ++i) {
6337 prevAt[i] = (i + m - 1) % m;
6338 nextAt[i] = (i + 1) % m;
6339 }
6340 const auto turn = [&](std::uint32_t i) -> std::int8_t {
6341 const auto side = orient(poly[prevAt[i]], poly[i], poly[nextAt[i]]);
6342 return side > 0 ? std::int8_t{1} : (side < 0 ? std::int8_t{-1} : std::int8_t{0});
6343 };
6344 std::uint32_t blockingCount = 0; // corners that can sit inside an ear
6345 for (std::uint32_t i = 0; i < m; ++i) {
6346 turnAt[i] = turn(i);
6347 blockingCount += turnAt[i] <= 0 ? 1u : 0u;
6348 }
6349 std::uint32_t remaining = m;
6350 std::uint32_t cursor = mountain ? 2 : 0;
6351 std::uint32_t skipped = 0;
6352 while (remaining > 3) {
6353 if (skipped > remaining) {
6354 return false; // a whole turn with no ear: not a simple polygon
6355 }
6356 const std::uint32_t i = cursor;
6357 cursor = nextAt[cursor];
6358 if (turnAt[i] <= 0 || (mountain && i < 2)) {
6359 ++skipped;
6360 continue;
6361 }
6362 const std::uint32_t p = prevAt[i];
6363 const std::uint32_t n = nextAt[i];
6364 if (!mountain && blockingCount != 0) {
6365 bool clean = true;
6366 for (std::uint32_t k = nextAt[n]; k != p; k = nextAt[k]) {
6367 if (turnAt[k] <= 0 && holds(poly[p], poly[i], poly[n], poly[k])) {
6368 clean = false;
6369 break;
6370 }
6371 }
6372 if (!clean) {
6373 ++skipped;
6374 continue;
6375 }
6376 }
6377 ears.push_back({p, i, n});
6378 nextAt[p] = n;
6379 prevAt[n] = p;
6380 --remaining;
6381 for (const std::uint32_t z : {p, n}) {
6382 const std::int8_t was = turnAt[z];
6383 turnAt[z] = turn(z);
6384 blockingCount += (turnAt[z] <= 0 ? 1u : 0u) - (was <= 0 ? 1u : 0u);
6385 }
6386 cursor = p;
6387 skipped = 0;
6388 }
6389 const std::uint32_t a = cursor;
6390 const std::uint32_t b = nextAt[a];
6391 ears.push_back({a, b, nextAt[b]});
6392 return true;
6393 };
6394
6395 // ---- level 0, second half: the ring filling the box ------------------
6396 // The hull vertex extreme in a diagonal direction anchors a spoke to the box
6397 // corner facing it: the supporting line separates the two, so the spoke runs
6398 // outside the hull. The four spokes cut the ring into monotone mountains —
6399 // a box side as base, a monotone chain above it — which ear clipping
6400 // triangulates in one pass.
6401 std::array<std::uint32_t, 4> anchor{0, 0, 0, 0};
6402 {
6403 using Wide = detail::promoted_number_t<NumberType>;
6404 const auto wx = [&](std::uint32_t i) {
6405 return detail::asNumber<Wide>(vertices_[static_cast<std::size_t>(ring[i])].x());
6406 };
6407 const auto wy = [&](std::uint32_t i) {
6408 return detail::asNumber<Wide>(vertices_[static_cast<std::size_t>(ring[i])].y());
6409 };
6410 Wide leastSum = wx(0) + wy(0);
6411 Wide mostSum = leastSum;
6412 for (std::uint32_t i = 1; i < hullSize; ++i) {
6413 const Wide sum = wx(i) + wy(i);
6414 if (sum < leastSum) {
6415 leastSum = sum;
6416 anchor[0] = i;
6417 }
6418 if (mostSum < sum) {
6419 mostSum = sum;
6420 anchor[2] = i;
6421 }
6422 // The extremes of x - y, compared without forming a difference.
6423 if (wx(anchor[1]) + wy(i) < wx(i) + wy(anchor[1])) {
6424 anchor[1] = i;
6425 }
6426 if (wx(i) + wy(anchor[3]) < wx(anchor[3]) + wy(i)) {
6427 anchor[3] = i;
6428 }
6429 }
6430 }
6431 {
6432 std::vector<VertexIndex> poly;
6433 std::vector<TriIndex> edgeSeed;
6434 for (std::size_t j = 0; j < 4; ++j) {
6435 poly.clear();
6436 edgeSeed.clear();
6437 poly.push_back(cornerVertex(j));
6438 edgeSeed.push_back(NO_TRI);
6439 poly.push_back(cornerVertex((j + 1) % 4));
6440 edgeSeed.push_back(NO_TRI);
6441 // The hull walked backwards, from the next corner's anchor to this
6442 // one's: the filling runs counterclockwise where the hull runs
6443 // clockwise, since it lies on the hull's other side.
6444 for (std::uint32_t k = anchor[(j + 1) % 4];; k = (k + hullSize - 1) % hullSize) {
6445 poly.push_back(ring[k]);
6446 const bool last = k == anchor[j];
6447 edgeSeed.push_back(last ? NO_TRI : ringTri[(k + hullSize - 1) % hullSize]);
6448 if (last) {
6449 break;
6450 }
6451 }
6452 if (!earClip(poly, /*mountain=*/true)) {
6453 return;
6454 }
6455 for (const auto& ear : ears) {
6456 // The seed a query landing in this triangle walks from: the mesh
6457 // triangle across whichever of its edges lies on the hull, which
6458 // the diagonal it leaves behind then carries to its neighbors.
6459 TriIndex seed = edgeSeed[ear[0]];
6460 if (seed == NO_TRI) seed = edgeSeed[ear[1]];
6461 if (seed == NO_TRI) seed = edgeSeed[ear[2]];
6462 addLeaf(poly[ear[0]], poly[ear[1]], poly[ear[2]], seed);
6463 edgeSeed[ear[0]] = seed;
6464 }
6465 }
6466 }
6467
6468 // ---- the levels above ------------------------------------------------
6469 std::vector<std::uint32_t> active(kp->cells.size());
6470 for (std::uint32_t i = 0; i < static_cast<std::uint32_t>(active.size()); ++i) {
6471 active[i] = i;
6472 }
6473
6474 std::vector<std::int32_t> slotOf(vertices_.size() + kp->extra.size(), -1);
6475 std::vector<VertexIndex> used;
6476 std::vector<std::uint32_t> starBegin;
6477 std::vector<std::uint32_t> starFill;
6478 std::vector<std::uint32_t> starCells;
6479 std::vector<VertexIndex> starFrom;
6480 std::vector<VertexIndex> starTo;
6481 std::vector<std::uint8_t> blocked;
6482 std::vector<std::uint32_t> order;
6483 std::vector<std::uint8_t> replaced;
6484 std::vector<std::uint32_t> fan;
6485 std::vector<VertexIndex> link;
6486 std::vector<std::uint32_t> fresh;
6487 std::vector<std::uint32_t> kept;
6488 std::size_t degreeLimit = pointLocationMaxStar;
6489
6490 while (active.size() > pointLocationTopSize) {
6491 // The vertices this level still has, and the cells around each of them.
6492 used.clear();
6493 for (const std::uint32_t c : active) {
6494 for (const VertexIndex v : shape[c]) {
6495 if (slotOf[static_cast<std::size_t>(v)] < 0) {
6496 slotOf[static_cast<std::size_t>(v)] = static_cast<std::int32_t>(used.size());
6497 used.push_back(v);
6498 }
6499 }
6500 }
6501 const std::uint32_t vertexCount = static_cast<std::uint32_t>(used.size());
6502 starBegin.assign(vertexCount + 1, 0);
6503 for (const std::uint32_t c : active) {
6504 for (const VertexIndex v : shape[c]) {
6505 ++starBegin[static_cast<std::size_t>(slotOf[static_cast<std::size_t>(v)]) + 1];
6506 }
6507 }
6508 for (std::uint32_t s = 0; s < vertexCount; ++s) {
6509 starBegin[s + 1] += starBegin[s];
6510 }
6511 starFill = starBegin;
6512 starCells.resize(starBegin[vertexCount]);
6513 for (const std::uint32_t c : active) {
6514 for (const VertexIndex v : shape[c]) {
6515 starCells[starFill[static_cast<std::size_t>(
6516 slotOf[static_cast<std::size_t>(v)])]++] = c;
6517 }
6518 }
6519
6520 // Candidates, smallest star first: a small star makes few triangles and
6521 // blocks few other candidates, so taking those first leaves more room.
6522 // This, rather than either constant above, is what decides how far a
6523 // level gets — taking the candidates in storage order instead costs 16%
6524 // more cells, two more levels and 28% of the query time at 100,000
6525 // vertices, a maximal independent set being that much smaller when a
6526 // crowded vertex blocks its neighbours first.
6527 order.clear();
6528 {
6529 std::vector<std::uint32_t> tally(degreeLimit + 2, 0);
6530 for (std::uint32_t s = 0; s < vertexCount; ++s) {
6531 const std::size_t degree = starBegin[s + 1] - starBegin[s];
6532 if (degree >= 3 && degree <= degreeLimit) {
6533 ++tally[degree + 1];
6534 }
6535 }
6536 for (std::size_t d = 1; d < tally.size(); ++d) {
6537 tally[d] += tally[d - 1];
6538 }
6539 order.resize(tally.back());
6540 for (std::uint32_t s = 0; s < vertexCount; ++s) {
6541 const std::size_t degree = starBegin[s + 1] - starBegin[s];
6542 if (degree >= 3 && degree <= degreeLimit) {
6543 order[tally[degree]++] = s;
6544 }
6545 }
6546 }
6547
6548 blocked.assign(vertexCount, 0);
6549 replaced.assign(kp->cells.size(), 0);
6550 fresh.clear();
6551 for (const std::uint32_t slot : order) {
6552 if (blocked[slot]) {
6553 continue;
6554 }
6555 const VertexIndex v = used[slot];
6556 const std::uint32_t begin = starBegin[slot];
6557 const std::uint32_t degree = starBegin[slot + 1] - begin;
6558
6559 // Walk the star into a fan: cell m of it is (v, link[m], link[m+1]).
6560 // A vertex whose cells do not close into a single turn — a box
6561 // corner, or one a neighboring cell only touches — is not one this
6562 // can remove, and the walk finding no successor is how it says so.
6563 starFrom.resize(degree);
6564 starTo.resize(degree);
6565 bool closes = true;
6566 for (std::uint32_t k = 0; k < degree && closes; ++k) {
6567 const auto& corners = shape[starCells[begin + k]];
6568 std::uint32_t position = 0;
6569 while (position < 3 && corners[position] != v) {
6570 ++position;
6571 }
6572 closes = position < 3;
6573 if (closes) {
6574 starFrom[k] = corners[(position + 1) % 3];
6575 starTo[k] = corners[(position + 2) % 3];
6576 }
6577 }
6578 fan.clear();
6579 link.clear();
6580 for (std::uint32_t step = 0, at = 0; closes && step < degree; ++step) {
6581 fan.push_back(starCells[begin + at]);
6582 link.push_back(starFrom[at]);
6583 const VertexIndex after = starTo[at];
6584 if (step + 1 == degree) {
6585 closes = after == link[0];
6586 break;
6587 }
6588 std::uint32_t following = degree;
6589 for (std::uint32_t k = 0; k < degree; ++k) {
6590 if (starFrom[k] == after) {
6591 following = k;
6592 break;
6593 }
6594 }
6595 closes = following < degree;
6596 at = closes ? following : 0;
6597 }
6598 if (!closes || link.size() != degree || !earClip(link, /*mountain=*/false)) {
6599 blocked[slot] = 1; // not removable; its neighbors stay free
6600 continue;
6601 }
6602
6603 for (const auto& ear : ears) {
6604 // The fan cells this triangle covers. Consecutive link vertices
6605 // turn around v, so a triangle that does not hold v spans one
6606 // arc of them — the two gaps other than the one wider than half
6607 // a turn — and it covers exactly the cells in that arc.
6608 std::uint32_t from = 0;
6609 std::uint32_t count = degree;
6610 int outward = -1;
6611 bool decided = true;
6612 for (int g = 0; g < 3 && decided; ++g) {
6613 const auto side =
6614 orient(v, link[ear[static_cast<std::size_t>(g)]],
6615 link[ear[static_cast<std::size_t>((g + 1) % 3)]]);
6616 if (side == 0) {
6617 decided = false;
6618 } else if (side < 0) {
6619 decided = outward < 0;
6620 outward = g;
6621 }
6622 }
6623 if (decided && outward >= 0) {
6624 from = ear[static_cast<std::size_t>((outward + 1) % 3)];
6625 const std::uint32_t stop = ear[static_cast<std::size_t>(outward)];
6626 count = (stop + degree - from) % degree;
6627 if (count == 0) {
6628 count = degree;
6629 }
6630 }
6631 Cell cell;
6632 cell.apex = static_cast<std::uint32_t>(v);
6633 cell.runBegin = static_cast<std::uint32_t>(kp->run.size());
6634 cell.runCount = count | (count == degree ? Kirkpatrick::FULL_TURN : 0u);
6635 for (std::uint32_t k = 0; k < count; ++k) {
6636 kp->run.push_back(static_cast<std::uint32_t>(link[(from + k) % degree]));
6637 kp->run.push_back(fan[(from + k) % degree]);
6638 }
6639 kp->run.push_back(static_cast<std::uint32_t>(link[(from + count) % degree]));
6640 fresh.push_back(static_cast<std::uint32_t>(kp->cells.size()));
6641 kp->cells.push_back(cell);
6642 shape.push_back({link[ear[0]], link[ear[1]], link[ear[2]]});
6643 }
6644
6645 blocked[slot] = 1;
6646 for (const VertexIndex u : link) {
6647 blocked[static_cast<std::size_t>(slotOf[static_cast<std::size_t>(u)])] = 1;
6648 }
6649 for (const std::uint32_t cell : fan) {
6650 replaced[cell] = 1;
6651 }
6652 }
6653
6654 for (const VertexIndex v : used) {
6655 slotOf[static_cast<std::size_t>(v)] = -1;
6656 }
6657
6658 if (fresh.empty()) {
6659 // Nothing came off at this degree: let the next pass reach further,
6660 // and give up once even a generous star finds nothing.
6661 if (degreeLimit >= 64) {
6662 break;
6663 }
6664 degreeLimit += 4;
6665 continue;
6666 }
6667 kept.clear();
6668 kept.reserve(active.size());
6669 for (const std::uint32_t c : active) {
6670 if (!replaced[c]) {
6671 kept.push_back(c);
6672 }
6673 }
6674 kept.insert(kept.end(), fresh.begin(), fresh.end());
6675 active.swap(kept);
6676 degreeLimit = pointLocationMaxStar;
6677 }
6678
6679 // ---- lay the cells out along the descent -----------------------------
6680 // A query reads one cell per level, and the cells it reads are scattered
6681 // over the order they were made in — which follows the removal order,
6682 // itself sorted by star size. Renumbering them depth first from the top
6683 // instead puts a cell beside the child it hands the query to, so a descent
6684 // walks memory forwards rather than jumping over the whole hierarchy.
6685 {
6686 // The top level keeps its triangles, being the one a query has to be
6687 // placed in; the rest go now, before the relaid arrays double what the
6688 // hierarchy holds.
6689 kp->roots.reserve(active.size());
6690 kp->rootShape.reserve(active.size());
6691 for (const std::uint32_t c : active) {
6692 kp->rootShape.push_back(shape[c]);
6693 }
6694 shape.clear();
6695 shape.shrink_to_fit();
6696
6697 const std::uint32_t cellCount = static_cast<std::uint32_t>(kp->cells.size());
6698 std::vector<std::uint32_t> relabel(cellCount, ~0u);
6699 std::vector<Cell> laid;
6700 laid.reserve(cellCount);
6701 std::vector<std::uint32_t> laidRun;
6702 laidRun.reserve(kp->run.size());
6703 std::vector<std::uint32_t> stack(active.rbegin(), active.rend());
6704 while (!stack.empty()) {
6705 const std::uint32_t c = stack.back();
6706 stack.pop_back();
6707 if (relabel[c] != ~0u) {
6708 continue;
6709 }
6710 relabel[c] = static_cast<std::uint32_t>(laid.size());
6711 laid.push_back(kp->cells[c]);
6712 const Cell& cell = kp->cells[c];
6713 for (std::uint32_t k = cell.runCount & ~Kirkpatrick::FULL_TURN; k-- > 0;) {
6714 stack.push_back(kp->run[cell.runBegin + 2 * k + 1]);
6715 }
6716 }
6717 for (Cell& cell : laid) {
6718 const std::uint32_t count = cell.runCount & ~Kirkpatrick::FULL_TURN;
6719 if (count == 0) {
6720 continue; // runBegin is this leaf's seed, not a run
6721 }
6722 const std::uint32_t from = cell.runBegin;
6723 cell.runBegin = static_cast<std::uint32_t>(laidRun.size());
6724 for (std::uint32_t k = 0; k < count; ++k) {
6725 laidRun.push_back(kp->run[from + 2 * k]);
6726 laidRun.push_back(relabel[kp->run[from + 2 * k + 1]]);
6727 }
6728 laidRun.push_back(kp->run[from + 2 * count]);
6729 }
6730 for (const std::uint32_t c : active) {
6731 kp->roots.push_back(relabel[c]);
6732 }
6733 kp->cells.swap(laid);
6734 kp->run.swap(laidRun);
6735 }
6736 pointLocation_ = std::move(kp);
6737 pointLocationRevision_ = revision_;
6738}
6739
6740namespace detail {
6741
6743struct ConvexCoverBuilder {
6744 template <TriangleConcept TriangleType, SegmentConcept SegmentType>
6745 [[nodiscard]] static Graph<TriangleType> visibilityGraph(
6746 const Triangulation<TriangleType, SegmentType>& triangulation) {
6747 return triangulation.convexCoverVisibilityGraph();
6748 }
6749};
6750
6751} // namespace detail
6752
6753// Deduction guides: the stored triangle type is not deducible from the
6754// container-templated constructors on their own. From a triangle container it is
6755// the element type (keeping its labels); from a segment container it is
6756// `Triangle<PointType>`. The `requires` clauses keep the two guides disjoint.
6757template <class TriangleRange>
6759Triangulation(const TriangleRange&)
6761
6762template <class SegmentRange>
6764Triangulation(const SegmentRange&)
6766 typename SegmentRange::value_type>;
6767
6768template <class PointRange>
6770Triangulation(const PointRange&)
6772
6773// Point set with constraint segments (conforming constrained Delaunay): the
6774// point type comes from the point range, the stored edge type takes the
6775// segments' label so it survives. Disjoint from the polygon guides below —
6776// Polygon is not a point range.
6777template <class PointRange, class SegmentRange>
6780Triangulation(const PointRange&, const SegmentRange&)
6783 typename SegmentRange::value_type::LabelType>>;
6784
6785template <class PointType>
6787
6788// Polygon with extra interior points and/or constraint segments: the point and
6789// triangle types come from the polygon. When constraint segments are present the
6790// stored edge type takes their label (over the polygon's own point type), so the
6791// segments' labels survive; otherwise the edge type defaults like the
6792// polygon-only guide. The `requires` clauses disambiguate the two two-argument
6793// forms (a points range vs a segment range), mirroring the disjoint guides above.
6794template <class PolyPoint, class PointRange>
6796Triangulation(const Polygon<PolyPoint>&, const PointRange&)
6798
6799template <class PolyPoint, class SegmentRange>
6801Triangulation(const Polygon<PolyPoint>&, const SegmentRange&)
6804
6805template <class PolyPoint, class PointRange, class SegmentRange>
6808Triangulation(const Polygon<PolyPoint>&, const PointRange&, const SegmentRange&)
6811
6812// Region guides, mirroring the polygon ones above.
6813template <class RegionPoint>
6815
6816template <class RegionPoint, class PointRange>
6820
6821template <class RegionPoint, class SegmentRange>
6826
6827template <class RegionPoint, class PointRange, class SegmentRange>
6830Triangulation(const PolygonWithHoles<RegionPoint>&, const PointRange&, const SegmentRange&)
6833
6834// Region-set guides, mirroring the region ones above.
6835template <class SetPoint>
6837
6838template <class SetPoint, class PointRange>
6840Triangulation(const PolygonSet<SetPoint>&, const PointRange&)
6842
6843template <class SetPoint, class SegmentRange>
6845Triangulation(const PolygonSet<SetPoint>&, const SegmentRange&)
6848
6849template <class SetPoint, class PointRange, class SegmentRange>
6852Triangulation(const PolygonSet<SetPoint>&, const PointRange&, const SegmentRange&)
6855
6856// Out-of-line: Polygon::triangulation is declared in shape/polygon.hpp (which
6857// precedes this header in the layering) but can only be defined once
6858// Triangulation and its deduction guides are visible.
6859template <class PointType_, class TLabel>
6861 return Triangulation(*this);
6862}
6863
6864template <class PointType_, class TLabel>
6865template <class SegmentRange>
6866auto Polygon<PointType_, TLabel>::triangulation(const SegmentRange& segments) const {
6867 return Triangulation(*this, segments);
6868}
6869
6870template <class PointType_, class TLabel>
6871template <class PointRange, class SegmentRange>
6873 const SegmentRange& segments) const {
6874 return Triangulation(*this, points, segments);
6875}
6876
6877template <class PointType_, class TLabel>
6878std::vector<Convex<PointType_>> Polygon<PointType_, TLabel>::convexPartition() const {
6879 return triangulation().convexPartition();
6880}
6881
6882template <class PointType_, class TLabel>
6883std::vector<Convex<PointType_>> Polygon<PointType_, TLabel>::convexCovering() const {
6884 const auto partition = triangulation();
6885 const auto triangles = partition.triangles();
6886 const auto cliques = detail::ConvexCoverBuilder::visibilityGraph(partition).cliqueCover();
6887
6888 std::vector<Convex<PointType_>> result;
6889 result.reserve(cliques.size());
6890 for (const auto& clique : cliques) {
6891 std::vector<PointType_> vertices;
6892 vertices.reserve(3 * clique.size());
6893 for (const auto& triangle : clique) {
6894 vertices.push_back(triangle.a());
6895 vertices.push_back(triangle.b());
6896 vertices.push_back(triangle.c());
6897 }
6898 result.emplace_back(vertices);
6899 }
6900
6901 // A clique partition covers every source triangle, but one clique hull may
6902 // also cover triangles assigned to other cliques. Remove such redundant
6903 // hulls while preserving coverage of the whole triangulation.
6904 for (std::size_t i = result.size(); i-- > 0;) {
6905 bool redundant = true;
6906 for (const auto& triangle : triangles) {
6907 bool coveredElsewhere = false;
6908 for (std::size_t j = 0; j < result.size(); ++j) {
6909 if (j != i && result[j].contains(triangle)) {
6910 coveredElsewhere = true;
6911 break;
6912 }
6913 }
6914 if (!coveredElsewhere) {
6915 redundant = false;
6916 break;
6917 }
6918 }
6919 if (redundant) {
6920 result.erase(result.begin() + static_cast<std::ptrdiff_t>(i));
6921 }
6922 }
6923
6924 std::sort(result.begin(), result.end());
6925 return result;
6926}
6927
6928// Out-of-line for the same reason: declared in shape/polygonwithholes.hpp.
6929template <class PointType_, class TLabel>
6933
6934template <class PointType_, class TLabel>
6935std::vector<Convex<PointType_>> PolygonWithHoles<PointType_, TLabel>::convexPartition() const {
6936 return triangulation().convexPartition();
6937}
6938
6939template <class PointType_, class TLabel>
6940std::vector<Convex<PointType_>> PolygonWithHoles<PointType_, TLabel>::convexCovering() const {
6941 return triangulation().convexCovering();
6942}
6943
6944template <class PointType_, class TLabel>
6945template <class SegmentRange>
6946auto PolygonWithHoles<PointType_, TLabel>::triangulation(const SegmentRange& segments) const {
6947 return Triangulation(*this, segments);
6948}
6949
6950template <class PointType_, class TLabel>
6952 return Triangulation(*this);
6953}
6954
6955template <class PointType_, class TLabel>
6956template <class SegmentRange>
6957auto PolygonSet<PointType_, TLabel>::triangulation(const SegmentRange& segments) const {
6958 return Triangulation(*this, segments);
6959}
6960
6961template <class PointType_, class TLabel>
6962std::vector<Convex<PointType_>> PolygonSet<PointType_, TLabel>::convexPartition() const {
6963 return triangulation().convexPartition();
6964}
6965
6966template <class PointType_, class TLabel>
6967std::vector<Convex<PointType_>> PolygonSet<PointType_, TLabel>::convexCovering() const {
6968 return triangulation().convexCovering();
6969}
6970
6971// A set's interior is the union of its components', so any component's own
6972// witness serves — no triangulation of the whole set needed.
6973template <class PointType_, class TLabel>
6974template <class ResultNumber>
6976 if (components_.empty()) {
6977 return Point<ResultNumber>();
6978 }
6979 return components_.front().template pointInside<ResultNumber>();
6980}
6981
6982// pointInside also lives here rather than in measures.hpp. It first tries the
6983// outer ring's cheap ear/diagonal witness. A hole can occupy that ear or
6984// interrupt that diagonal, however, so a witness in or on a hole falls back to
6985// the triangulated domain: every one of its triangles is inside the region by
6986// construction.
6987template <class PointType_, class TLabel>
6988template <class ResultNumber>
6990 if (isDegenerate()) {
6991 // No interior to point at (UB per the library contract); fall back to a
6992 // representative point rather than triangulating nothing.
6994 }
6995
6996 const auto outerWitness = outer_.template pointInside<ResultNumber>();
6997 bool insideHole = false;
6998 for (const auto& hole : holes_) {
6999 if (hole.contains(outerWitness)) {
7000 insideHole = true;
7001 break;
7002 }
7003 }
7004 if (!insideHole) {
7005 return outerWitness;
7006 }
7007
7008 // Any domain triangle serves, so stop at the first non-degenerate one the
7009 // visit meets rather than materializing and sorting the whole mesh.
7010 const auto mesh = triangulation();
7012 mesh.visitTriangles([&](const auto& triangle) {
7013 if (triangle.isDegenerate()) {
7014 return false;
7015 }
7016 witness = triangle.template pointInside<ResultNumber>();
7017 return true;
7018 });
7019 return witness;
7020}
7021
7022template <class PointType_, class TLabel>
7023template <class OtherShape>
7025 const auto witness = pointInside<NumberType>();
7026 if (interiorContains(witness)) {
7027 return shape.interiorContains(witness);
7028 }
7029 // pointInside() divides by 4 (Triangle::pointInside), so integer truncation
7030 // can round it onto the boundary; scaling by 4 makes it exact without
7031 // changing containment. Scaling is a similarity, so the scaled region's
7032 // triangulation is the scaled triangulation and the witness scales with it.
7033 return (shape * 4).interiorContains((*this * 4).template pointInside<NumberType>());
7034}
7035
7036} // namespace pgl
One bit per cell over a fixed rectangular window of the integer grid.
The planar subdivision induced by a set of one-dimensional shapes.
Definition arrangement.hpp:171
Stores drawable objects and exports them as an SVG image.
Definition canvas.hpp:128
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:312
Definition forward.hpp:306
Definition forward.hpp:311
Definition forward.hpp:307
Definition forward.hpp:314
Definition arrangement.hpp:67
@ y
Definition intervaltree.hpp:24
@ x
Definition intervaltree.hpp:24
@ edge
Definition bitmatrix.hpp:37
@ vertex
Definition bitmatrix.hpp:37
auto convexHull(const Container &points_)
Computes the convex hull of a point container.
Definition convexhull.hpp:222
typename DivisionResult< Number >::type division_result_t
Convenience alias for DivisionResult.
Definition rational.hpp:1175
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
void hilbertSort(std::vector< Point< Number, Label > > &points)
Sorts points along a Hilbert space-filling curve.
Definition sortpoints.hpp:192
Triangulation(const TriangleRange &) -> Triangulation< typename TriangleRange::value_type >
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 bool collinear(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Tests whether three points are collinear.
Definition orientation.hpp:651
void sortAround(std::vector< Point< Number, Label > > &points, const Point< CenterNumber, CenterLabel > &p)
Sorts points counterclockwise around a center point.
Definition sortpoints.hpp:48
constexpr auto orientationDeterminant(const Point< ANumber, ALabel > &a, const Point< BNumber, BLabel > &b, const Point< CNumber, CLabel > &c)
Returns the signed orientation determinant of three points.
Definition orientation.hpp:518
Closed convex polygon stored by its vertices.
Definition convex.hpp:170
Two-dimensional point with optional label payload.
Definition point.hpp:129
Set of closed regions with pairwise disjoint interiors.
Definition polygonset.hpp:165
std::vector< Convex< PointType > > convexCovering() const
Covers this set with a greedily selected set of convex polygons.
Definition triangulation.hpp:6967
auto triangulation() const
Builds the constrained Delaunay triangulation of this set.
Definition triangulation.hpp:6951
std::vector< Convex< PointType > > convexPartition() const
Cuts this set into convex pieces with disjoint interiors.
Definition triangulation.hpp:6962
Point< ResultNumber > pointInside() const
Returns a point strictly inside the set.
Definition triangulation.hpp:6975
Closed region bounded by one outer simple polygon minus disjoint polygonal holes.
Definition polygonwithholes.hpp:89
constexpr bool isDegenerate() const
Tests whether the region has zero area.
Definition polygonwithholes.hpp:441
constexpr const PolygonType & hole(std::size_t index) const
Accesses a hole by index.
Definition polygonwithholes.hpp:196
constexpr Point< ResultNumber > verticesCentroid() const
Computes the centroid of the vertex set over all rings.
Definition measures.hpp:1109
Point< ResultNumber > pointInside() const
Returns a point strictly inside the region.
Definition triangulation.hpp:6989
constexpr bool interiorContains(const OtherPoint &point) const
Tests whether this shape's interior contains the other shape (A∖∂A ⊇ B).
Definition interiorcontains.hpp:2223
constexpr const PolygonType & outer() const
Returns the outer boundary.
Definition polygonwithholes.hpp:178
bool pointInsideInteriorContainedIn(const OtherShape &shape) const
Tests whether some point in this shape's relative interior lies in the strict interior of shape.
Definition triangulation.hpp:7024
std::vector< Convex< PointType > > convexPartition() const
Cuts this region into convex pieces with disjoint interiors.
Definition triangulation.hpp:6935
std::vector< Convex< PointType > > convexCovering() const
Covers this region with a greedily selected set of convex polygons.
Definition triangulation.hpp:6940
constexpr const std::vector< PolygonType > & holes() const
Returns the holes in canonical order.
Definition polygonwithholes.hpp:202
auto triangulation() const
Builds the constrained Delaunay triangulation of this region.
Definition triangulation.hpp:6930
Closed simple polygon stored by its vertices.
Definition polygon.hpp:59
constexpr bool contains(const OtherPoint &point) const
Tests whether this shape contains the other shape (A ⊇ B).
Definition contains.hpp:1296
auto triangulation() const
Builds the constrained Delaunay triangulation of this polygon.
Definition triangulation.hpp:6860
constexpr std::vector< PointType > vertices() const
Returns the vertices of the polygon (translation applied).
Definition polygon.hpp:587
std::vector< Convex< PointType > > convexPartition() const
Cuts this polygon into convex pieces with disjoint interiors.
Definition triangulation.hpp:6878
std::vector< Convex< PointType > > convexCovering() const
Covers this polygon with convex hulls derived from triangle cliques.
Definition triangulation.hpp:6883
Unoriented closed segment between two endpoints plus optional segment label.
Definition segment.hpp:58
Runtime variant wrapper over the supported primitive shapes.
Definition shape.hpp:160
constexpr const Variant & variant() const
Returns the underlying variant.
Definition shape.hpp:264
Definition triangulation.hpp:3936
std::uint32_t runCount
Definition triangulation.hpp:3939
std::uint32_t runBegin
Definition triangulation.hpp:3938
std::uint32_t apex
Definition triangulation.hpp:3937
Triangulation whose connectivity may change and whose vertex set may grow.
Definition triangulation.hpp:166
TriangleType getShape(TriId t) const
The triangle a handle refers to, with its stored label.
Definition triangulation.hpp:1073
Triangulation(const Polygon< PointType > &poly, const PointRange &points, const SegmentRange &segments)
Adds both interior points and constraint segments.
Definition triangulation.hpp:500
VertexId getId(const PointType &p) const
The handle of a vertex of this triangulation.
Definition triangulation.hpp:1120
bool contains(const Q &shape) const
Definition triangulation.hpp:3097
TriId getId(const TriangleType &t) const
The handle of a triangle of this triangulation.
Definition triangulation.hpp:1106
bool visitTrianglesIntersecting(const Q &shape, Fn f) const
Visits every in-domain triangle that intersects shape.
Definition triangulation.hpp:2914
Graph< PointType > reducedVisibilityGraph() const
Returns the reduced visibility graph of the mesh vertices.
Definition visibilitygraph.hpp:704
std::vector< TriangleType > trianglesIntersecting(const OS &s) const
Returns the triangles met by s (a segment, (oriented) line, ray, or chain, in order,...
Definition triangulation.hpp:2722
const PointType & operator[](VertexId v) const
Same as getShape(VertexId) const.
Definition triangulation.hpp:1093
const L & label(const TriangleType &t) const
Definition triangulation.hpp:3541
std::vector< TriId > incidentTriangles(VertexId v) const
The triangles incident to vertex v — its full fan.
Definition triangulation.hpp:1308
bool visitEdgesIntersecting(const OS &s, Fn f) const
Visits every triangulation edge met by s.
Definition triangulation.hpp:2793
Graph< PointType > visibilityGraph() const
Returns the visibility graph of the mesh vertices.
Definition visibilitygraph.hpp:684
bool visitEdges(Fn fn) const
Calls fn(Segment) on every edge, with its stored label.
Definition triangulation.hpp:823
TriId locateId(const QueryPoint &p) const
Definition triangulation.hpp:1058
Triangulation(const PolygonWithHoles< PointType > &region, const PointRange &points, const SegmentRange &segments)
Adds both interior points and constraint segments.
Definition triangulation.hpp:564
bool flippable(const EdgeRange &edges) const
True if every edge in edges can be flipped simultaneously.
Definition triangulation.hpp:3630
detail::Handle< VertexTag > VertexId
Handle of a vertex of this triangulation specialization.
Definition triangulation.hpp:181
Polygon< Point< ResultNumber > > regularizedVisiblePolygon(const PointType &query) const
The region of the domain visible from query, regularized.
Definition visibilitygraph.hpp:752
std::optional< TriangleType > locate(const QueryPoint &p) const
Finds the triangle containing the query point by walking the mesh.
Definition triangulation.hpp:2998
Triangulation(const PolygonWithHoles< PointType > &region)
Builds the constrained Delaunay triangulation of a region with holes, optionally with extra interior ...
Definition triangulation.hpp:526
bool has(TriId t) const
True if t is a handle of one of the triangles of this triangulation.
Definition triangulation.hpp:1125
L & label(const SegmentType &s)
Returns a reference to the label stored for edge s.
Definition triangulation.hpp:3561
Triangulation(const Polygon< PointType > &poly)
Builds the constrained Delaunay triangulation of a simple polygon, optionally with extra interior poi...
Definition triangulation.hpp:463
Triangulation(const PointRange &pts, const SegmentRange &segments)
Builds the conforming constrained Delaunay triangulation of a point set with constraint segments.
Definition triangulation.hpp:398
std::optional< TriId > otherTriangle(TriId t, VertexId a, VertexId b) const
The triangle on the other side of edge a b from t.
Definition triangulation.hpp:1229
std::array< VertexId, 3 > vertices(TriId t) const
The three vertices of a triangle, counterclockwise.
Definition triangulation.hpp:1140
bool intersects(const E &) const
Definition triangulation.hpp:3294
std::size_t triangleIndexBound() const
One past the largest index a TriId of this triangulation can carry.
Definition triangulation.hpp:1004
std::vector< PointType > visibleVertices(const PointType &query) const
The mesh vertices visible from query.
Definition visibilitygraph.hpp:396
bool checkInvariants() const
Checks the structural invariants (orientation + neighbor symmetry). Intended for debug assertions.
Definition triangulation.hpp:3756
Triangulation(const SegmentRange &segs)
Builds a triangulation from its set of edges.
Definition triangulation.hpp:245
bool intersects(const Q &shape) const
True if the triangulated domain meets shape (A ∩ B ≠ ∅).
Definition triangulation.hpp:3235
bool isConstrained(TriId t, int side) const
True if side side of t is a constrained edge.
Definition triangulation.hpp:1185
std::vector< TriangleType > incidentTriangles(const PointType &p) const
The triangles incident to vertex p — its full fan.
Definition triangulation.hpp:784
bool has(const TriangleType &t) const
True if t is one of the triangles of this triangulation.
Definition triangulation.hpp:700
bool interiorContains(const E &) const
Definition triangulation.hpp:3384
std::vector< TriId > edgeAdjacentTriangles(TriId t) const
The (up to three) triangles sharing an edge with t.
Definition triangulation.hpp:1249
std::vector< Convex< PointType > > convexCovering() const
Covers the domain with a greedily selected set of convex polygons.
Definition triangulation.hpp:1966
std::vector< SegmentType > edges() const
Returns all edges, sorted, each with its stored label.
Definition triangulation.hpp:846
bool contains(const E &) const
Definition triangulation.hpp:3215
Graph< PointType > asGraph() const
Returns the mesh's vertices and edges as a Graph.
Definition triangulation.hpp:873
void buildPointLocation()
Builds the point-location index: a Kirkpatrick hierarchy over this mesh.
Definition triangulation.hpp:6209
typename TriangleType::PointType PointType
Definition triangulation.hpp:176
bool insertDelaunay(const PointType &p)
Inserts p as a new vertex and restores the constrained Delaunay property around it.
Definition triangulation.hpp:3730
std::vector< TriangleType > incidentTriangles(const SegmentType &s) const
The (up to two) triangles incident to edge s.
Definition triangulation.hpp:759
std::vector< TriId > triangleIds() const
The handles of every triangle, in storage order.
Definition triangulation.hpp:970
bool interiorsIntersect(const Shape< PointType > &shape) const
Definition triangulation.hpp:3489
bool has(VertexId v) const
True if v is a handle of one of the vertices of this triangulation.
Definition triangulation.hpp:1128
bool has(const SegmentType &s) const
True if s is an edge incident to the visible triangulation.
Definition triangulation.hpp:703
std::size_t vertexIndexBound() const
One past the largest index a VertexId of this triangulation can carry.
Definition triangulation.hpp:1018
std::vector< VertexId > vertexIds() const
The handles of every vertex, in storage order.
Definition triangulation.hpp:984
bool empty() const
True if the triangulation stores no in-domain triangles.
Definition triangulation.hpp:695
bool visitTrianglesIntersecting(const OS &s, Fn f) const
Visits every triangle met by the directed query s, in order.
Definition triangulation.hpp:2230
bool visitTrianglesInteriorIntersecting(const OS &s, Fn f) const
Visits the triangles whose interior s actually enters.
Definition triangulation.hpp:2743
const L & label(TriId t) const
Definition triangulation.hpp:1345
TriangleType_ TriangleType
Definition triangulation.hpp:174
const PointType & getShape(VertexId v) const
The position of the vertex a handle refers to.
Definition triangulation.hpp:1084
typename SegmentType::LabelType SegmentLabel
Definition triangulation.hpp:178
bool interiorsIntersect(const Q &shape) const
Definition triangulation.hpp:3412
bool intersects(const Shape< PointType > &shape) const
Definition triangulation.hpp:3299
std::vector< TriangleType > trianglesInteriorIntersecting(const OS &s) const
Returns the triangles whose interior s enters.
Definition triangulation.hpp:2771
bool contains(const D &shape) const
Definition triangulation.hpp:3169
std::vector< TriangleType > vertexAdjacentTriangles(const TriangleType &t) const
The triangles sharing at least one vertex with t (excluding t).
Definition triangulation.hpp:754
Triangulation(const Polygon< PointType > &poly, const SegmentRange &segments)
Adds the interior segments as constrained edges and vertices.
Definition triangulation.hpp:486
std::vector< TriId > vertexAdjacentTriangles(TriId t) const
The triangles sharing at least one vertex with t (excluding t).
Definition triangulation.hpp:1270
std::size_t numVertices() const
Number of real vertices (excludes the ghost vertex).
Definition triangulation.hpp:664
std::vector< TriangleType > edgeAdjacentTriangles(const TriangleType &t) const
The (up to three) triangles sharing an edge with t.
Definition triangulation.hpp:739
typename PointType::NumberType NumberType
Definition triangulation.hpp:177
Triangulation(const PolygonSet< PointType > &set, const PointRange &points)
Adds the interior points as extra triangulation vertices.
Definition triangulation.hpp:599
std::size_t numEdges() const
Number of undirected edges incident to the visible triangulation.
Definition triangulation.hpp:673
bool interiorsIntersect(const E &) const
Definition triangulation.hpp:3484
std::optional< TriId > otherTriangle(TriId t, int side) const
The triangle on the other side of side side of t.
Definition triangulation.hpp:1163
const L & label(const SegmentType &s) const
Definition triangulation.hpp:3570
bool contains(const Shape< PointType > &shape) const
Definition triangulation.hpp:3220
bool interiorContains(const Shape< PointType > &shape) const
Definition triangulation.hpp:3389
bool interiorsIntersect(const QueryPoint &shape) const
True if the domain's interior meets shape's interior (A∖∂A ∩ B∖∂B ≠ ∅).
Definition triangulation.hpp:3405
void clearPointLocation() noexcept
Releases the point-location index.
Definition triangulation.hpp:941
Triangulation(const PolygonSet< PointType > &set, const SegmentRange &segments)
Adds the interior segments as constrained edges and vertices.
Definition triangulation.hpp:611
Arrangement< PointType, TriId > asArrangement() const
Returns the visible mesh as an arrangement, labeling each triangle face by its ID.
Definition arrangement.hpp:4526
L & label(const TriangleType &t)
Returns a reference to the label stored for triangle t.
Definition triangulation.hpp:3532
std::optional< TriangleType > otherTriangle(const TriangleType &t, const SegmentType &shared) const
The triangle on the other side of shared from t.
Definition triangulation.hpp:718
bool insert(const PointType &p)
Inserts p as a new vertex.
Definition triangulation.hpp:3703
Triangulation(const PointRange &pts)
Builds the Delaunay triangulation of a set of points.
Definition triangulation.hpp:357
bool contains(const QueryPoint &shape) const
True if the triangulated domain contains shape (A ⊇ B).
Definition triangulation.hpp:3029
void setConstrained(TriId t, int side, bool value=true)
Constrains (or unconstrains) side side of t.
Definition triangulation.hpp:1205
Triangulation(const PolygonWithHoles< PointType > &region, const SegmentRange &segments)
Adds the interior segments as constrained edges and vertices.
Definition triangulation.hpp:550
std::vector< Convex< PointType > > convexPartition() const
The domain cut into convex pieces with pairwise disjoint interiors.
Definition triangulation.hpp:1778
SegmentType_ SegmentType
Definition triangulation.hpp:175
bool contains(const S &shape) const
Definition triangulation.hpp:3039
TriangleType operator[](TriId t) const
Same as getShape(TriId) const.
Definition triangulation.hpp:1090
Triangulation(const PolygonSet< PointType > &set, const PointRange &points, const SegmentRange &segments)
Adds both interior points and constraint segments.
Definition triangulation.hpp:625
Triangulation()=default
Creates an empty triangulation.
bool visitEdgesInteriorIntersecting(const OS &s, Fn f) const
Visits the triangulation edges whose interior s crosses.
Definition triangulation.hpp:2838
friend Canvas & operator<<(Canvas &canvas, const Triangulation &triangulation)
Draws every triangle to a canvas.
Definition triangulation.hpp:3795
bool interiorContains(const Q &shape) const
True if the domain's interior contains shape (A∖∂A ⊇ B).
Definition triangulation.hpp:3315
detail::optional_label_t< TriangleType > TriangleLabel
Definition triangulation.hpp:179
std::vector< PointType > clearlyVisibleVertices(const PointType &query) const
The mesh vertices clearly visible from query.
Definition visibilitygraph.hpp:383
detail::Handle< TriTag > TriId
Handle of a triangle of this triangulation specialization.
Definition triangulation.hpp:183
bool hasPointLocation() const noexcept
True if locate and locateId currently use the point-location index.
Definition triangulation.hpp:944
bool contains(const C &shape) const
Definition triangulation.hpp:3085
std::vector< SegmentType > edgesInteriorIntersecting(const OS &s) const
Returns the triangulation edges whose interior s crosses.
Definition triangulation.hpp:2881
bool interiorsIntersect(const Q &shape) const
Definition triangulation.hpp:3431
Graph< PointType > clearVisibilityGraph() const
Returns the clear visibility graph of the mesh vertices.
Definition visibilitygraph.hpp:664
bool visitTrianglesIntersecting(const C &c, Fn f) const
Visits every in-domain triangle that intersects the chain c, in the order the chain first meets them.
Definition triangulation.hpp:2953
std::vector< SegmentType > edgesIntersecting(const OS &s) const
Returns the triangulation edges met by s.
Definition triangulation.hpp:2861
std::vector< TriangleType > triangles() const
Returns all triangles, sorted.
Definition triangulation.hpp:837
std::optional< SegmentType > flip(const SegmentType &s)
Flips edge s, replacing it by the opposite diagonal.
Definition triangulation.hpp:3594
Triangulation(const PolygonWithHoles< PointType > &region, const PointRange &points)
Adds the interior points as extra triangulation vertices.
Definition triangulation.hpp:538
bool visitTriangles(Fn fn) const
Calls fn(Triangle) — or fn(TriId) — on every triangle.
Definition triangulation.hpp:802
Triangulation(const PolygonSet< PointType > &set)
Builds the constrained Delaunay triangulation of a set of regions, optionally with extra interior poi...
Definition triangulation.hpp:587
bool isConstrained(const SegmentType &s) const
True if edge s is flagged as constrained.
Definition triangulation.hpp:3497
L & label(TriId t)
Returns a reference to the label stored for the triangle t.
Definition triangulation.hpp:1337
TriId locateId(const PointType &p) const
Finds the triangle containing the query point, as a handle.
Definition triangulation.hpp:1037
bool flippable(const SegmentType &s) const
True if edge s can be flipped (unconstrained, interior, convex quad).
Definition triangulation.hpp:3579
std::optional< std::vector< SegmentType > > flip(const EdgeRange &edges)
Flips every edge in edges at once, if the whole set allows it.
Definition triangulation.hpp:3661
void setConstrained(const SegmentType &s, bool value=true)
Flags (or clears) edge s as constrained on both incident sides.
Definition triangulation.hpp:3503
bool hasCurrentPointLocation() const noexcept
True if the index is in place and was drawn against the mesh as it now stands.
Definition triangulation.hpp:957
Triangulation(const TriangleRange &tris)
Builds a triangulation from a set of triangles.
Definition triangulation.hpp:203
bool contains(const U &) const
Definition triangulation.hpp:3209
std::size_t numTriangles() const
Number of triangles (excludes ghost and out-of-domain fill triangles).
Definition triangulation.hpp:670
Triangulation(const Polygon< PointType > &poly, const PointRange &points)
Adds the interior points as extra triangulation vertices.
Definition triangulation.hpp:474
Arrangement< Point< ResultNumber >, PointType > voronoiDiagram() const
Returns the Voronoi diagram dual to this Delaunay triangulation.