Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
geom_algorithms.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
189# ifndef GEOM_ALGORITHMS_H
190# define GEOM_ALGORITHMS_H
191
192# include <sstream>
193# include <polygon.H>
194# include <htlist.H>
195# include <tpl_dynDlist.H>
196# include <tpl_dynSetTree.H>
197# include <tpl_arrayQueue.H>
198# include <tpl_arrayStack.H>
199# include <tpl_sort_utils.H>
200# include <tpl_2dtree.H>
201# include <ah-errors.H>
202# include <algorithm>
203# include <utility>
204# include <random>
205# include <map>
206# include <set>
207# include <queue>
208# include <memory>
209# include <limits>
210# include <cmath>
211
212# include <ah-unique.H>
213# include <tpl_dynBinHeap.H>
214
215namespace Aleph
216{
232 {
233 public:
236 {
238 verts.reserve(poly.size());
239 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
240 verts.append(it.get_current_vertex());
241 return verts;
242 }
243
250 {
251 Geom_Number sum = 0;
252 for (size_t i = 0; i < verts.size(); ++i)
253 {
254 const Point & a = verts(i);
255 const Point & b = verts((i + 1) % verts.size());
256 sum += a.get_x() * b.get_y() - a.get_y() * b.get_x();
257 }
258 return sum;
259 }
260
263 {
265 }
266
269 {
270 return signed_double_area(verts) / 2;
271 }
272
274 [[nodiscard]] static Geom_Number signed_area(const Polygon & poly)
275 {
276 return signed_double_area(poly) / 2;
277 }
278
281 {
283 return a < 0 ? Geom_Number(-a) : a;
284 }
285
287 [[nodiscard]] static Geom_Number area(const Polygon & poly)
288 {
289 Geom_Number a = signed_area(poly);
290 return a < 0 ? Geom_Number(-a) : a;
291 }
292
294 [[nodiscard]] static bool is_convex(const Array<Point> & verts)
295 {
296 if (verts.size() < 3)
297 return true;
298
299 int sign = 0;
300 const size_t n = verts.size();
301 for (size_t i = 0; i < n; ++i)
302 {
303 const Geom_Number turn =
304 area_of_parallelogram(verts(i), verts((i + 1) % n),
305 verts((i + 2) % n));
306 if (turn == 0)
307 continue;
308 const int curr = turn > 0 ? 1 : -1;
309 if (sign == 0)
310 sign = curr;
311 else if (sign != curr)
312 return false;
313 }
314 return true;
315 }
316
318 {
319 if (signed_double_area(verts) < 0)
320 for (size_t i = 0; i < verts.size() / 2; ++i)
321 {
322 const Point tmp = verts(i);
323 verts(i) = verts(verts.size() - 1 - i);
324 verts(verts.size() - 1 - i) = tmp;
325 }
326 }
327 };
328
341 {
342 public:
346 struct EdgeRef
347 {
348 size_t u;
349 size_t v;
350 size_t tri;
351 size_t third;
352 };
353
354 private:
355 static void append_edge(Array<EdgeRef> & edges, size_t a, size_t b,
356 const size_t tri, const size_t third)
357 {
358 if (a > b)
359 {
360 const size_t tmp = a;
361 a = b;
362 b = tmp;
363 }
364
365 edges.append(EdgeRef{a, b, tri, third});
366 }
367
368 public:
369 template <typename TriangleArray, typename GroupFn>
370 static void for_each_sorted_edge_group(const TriangleArray & triangles,
372 {
373 Array<EdgeRef> edges;
374 edges.reserve(triangles.size() * 3);
375 for (size_t tri_idx = 0; tri_idx < triangles.size(); ++tri_idx)
376 {
377 const auto & tri = triangles(tri_idx);
378 append_edge(edges, tri.i, tri.j, tri_idx, tri.k);
379 append_edge(edges, tri.j, tri.k, tri_idx, tri.i);
380 append_edge(edges, tri.k, tri.i, tri_idx, tri.j);
381 }
382
383 quicksort_op(edges, [](const EdgeRef & a, const EdgeRef & b)
384 {
385 if (a.u != b.u)
386 return a.u < b.u;
387 if (a.v != b.v)
388 return a.v < b.v;
389 return a.tri < b.tri;
390 });
391
392 size_t first = 0;
393 while (first < edges.size())
394 {
395 size_t last = first + 1;
396 while (last < edges.size() and
397 edges(last).u == edges(first).u and
398 edges(last).v == edges(first).v)
399 ++last;
400
401 on_group(edges, first, last);
402 first = last;
403 }
404 }
405 };
406
411 {
412 private:
414 {
415 size_t a;
416 size_t b;
417 size_t c;
418 bool alive;
419 };
420
422 {
423 size_t u;
424 size_t v;
425 };
426
428 {
429 bool operator ()(const UndirectedEdge & a, const UndirectedEdge & b) const
430 {
431 if (a.u != b.u)
432 return a.u < b.u;
433 return a.v < b.v;
434 }
435 };
436
438
439 static void toggle_edge(EdgeSet & boundary, size_t u, size_t v)
440 {
441 if (u > v)
442 {
443 const size_t tmp = u;
444 u = v;
445 v = tmp;
446 }
447
448 const UndirectedEdge e{u, v};
449 if (const auto *ptr = boundary.search(e); ptr != nullptr)
450 boundary.remove(*ptr);
451 else
452 boundary.insert(e);
453 }
454
455 public:
456 template <typename IndexedTriangle, typename InCirclePredicate>
458 triangulate(Array<Point> pts, const size_t n,
459 InCirclePredicate point_in_circumcircle)
460 {
462 if (n < 3)
463 return out;
464
465 Geom_Number minx = pts(0).get_x();
466 Geom_Number maxx = pts(0).get_x();
467 Geom_Number miny = pts(0).get_y();
468 Geom_Number maxy = pts(0).get_y();
469
470 for (size_t i = 1; i < n; ++i)
471 {
472 if (pts(i).get_x() < minx) minx = pts(i).get_x();
473 if (pts(i).get_x() > maxx) maxx = pts(i).get_x();
474 if (pts(i).get_y() < miny) miny = pts(i).get_y();
475 if (pts(i).get_y() > maxy) maxy = pts(i).get_y();
476 }
477
478 const Geom_Number dx = maxx - minx;
479 const Geom_Number dy = maxy - miny;
480 Geom_Number delta = dx > dy ? dx : dy;
481 if (delta == 0)
482 delta = 1;
483
484 const Geom_Number span = delta * 16 + 1;
485 const Geom_Number two_span = span + span;
486 const Geom_Number midx = (minx + maxx) / 2;
487 const Geom_Number midy = (miny + maxy) / 2;
488
489 const Point s0(midx - two_span, midy - span);
490 const Point s1(midx + two_span, midy - span);
491 const Point s2(midx, midy + two_span);
492
493 pts.append(s0);
494 pts.append(s1);
495 pts.append(s2);
496
497 const size_t i0 = n;
498 const size_t i1 = n + 1;
499 const size_t i2 = n + 2;
500
502 work.append(WorkTriangle{i0, i1, i2, true});
503
504 for (size_t pidx = 0; pidx < n; ++pidx)
505 {
507 bad.reserve(work.size());
508
509 for (size_t t = 0; t < work.size(); ++t)
510 {
511 const auto & [a, b, c, alive] = work(t);
512 if (not alive)
513 continue;
514
515 if (point_in_circumcircle(pts, a, b, c, pidx))
516 bad.append(t);
517 }
518
519 if (bad.is_empty())
520 continue;
521
522 EdgeSet boundary;
523
524 for (size_t bad_idx = 0; bad_idx < bad.size(); ++bad_idx)
525 {
526 auto & [a, b, c, alive] = work(bad(bad_idx));
527 if (not alive)
528 continue;
529
530 toggle_edge(boundary, a, b);
531 toggle_edge(boundary, b, c);
532 toggle_edge(boundary, c, a);
533 alive = false;
534 }
535
536 boundary.for_each([&](const UndirectedEdge & edge)
537 {
538 size_t u = edge.u;
539 size_t v = edge.v;
540
541 const Orientation o = orientation(pts(u), pts(v), pts(pidx));
543 return;
544
545 if (o == Orientation::CW)
546 {
547 const size_t tmp = u;
548 u = v;
549 v = tmp;
550 }
551
552 work.append(WorkTriangle{u, v, pidx, true});
553 });
554
556 compacted.reserve(work.size());
557 for (size_t t = 0; t < work.size(); ++t)
558 if (work(t).alive)
559 compacted.append(work(t));
560 work = std::move(compacted);
561 }
562
563 out.reserve(work.size());
564 for (size_t t = 0; t < work.size(); ++t)
565 {
566 const auto & [a, b, c, alive] = work(t);
567 if (not alive)
568 continue;
569
570 if (a >= n or b >= n or c >= n)
571 continue;
572
573 if (orientation(pts(a), pts(b), pts(c)) == Orientation::COLLINEAR)
574 continue;
575
576 out.append(IndexedTriangle{a, b, c});
577 }
578
579 return out;
580 }
581 };
582
583 // ============================================================================
584 // Triangulation Algorithms
585 // ============================================================================
586
640 {
642
644 {
646 }
647
652
653 static void normalize_to_ccw(Polygon & p)
654 {
656 ah_domain_error_if(area2 == 0) << "Polygon is degenerate (zero area)";
657 if (area2 > 0)
658 return;
659
661 Polygon ccw;
662 for (size_t i = verts.size(); i > 0; --i)
663 ccw.add_vertex(verts(i - 1));
664 ccw.close();
665 p = std::move(ccw);
666 }
667
668 public:
679 static bool diagonalize(const Polygon & p, const Segment & s)
680 {
681 for (Polygon::Segment_Iterator it(p); it.has_curr(); it.next_ne())
682 if (Segment curr = it.get_current_segment();
683 curr.get_src_point() != s.get_src_point() and
684 curr.get_tgt_point() != s.get_src_point() and
685 curr.get_src_point() != s.get_tgt_point() and
686 curr.get_tgt_point() != s.get_tgt_point() and
687 s.intersects_with(curr))
688 return false;
689 return true;
690 }
691
702 static bool in_cone(const Polygon & p, const Vertex & a, const Vertex & b)
703 {
704 // a0 -> a -> a1 are consecutive vertices
705 const Vertex & a0 = p.get_prev_vertex(a);
706 const Vertex & a1 = p.get_next_vertex(a);
707
708 if (a0.is_to_left_on_from(a, a1))
709 return a0.is_left_of(a, b) and a1.is_left_of(b, a);
710
711 return not (a1.is_to_left_on_from(a, b) and
712 a0.is_to_left_on_from(b, a));
713 }
714
723 static bool diagonal(const Polygon & p, const Vertex & a, const Vertex & b)
724 {
725 return in_cone(p, a, b) and in_cone(p, b, a) and
726 diagonalize(p, Segment(a.to_point(), b.to_point()));
727 }
728
735 static EarsSet init_ears(const Polygon & p)
736 {
737 EarsSet ret;
738
739 for (Polygon::Vertex_Iterator it(p); it.has_curr(); it.next_ne())
740 {
741 Vertex & curr = it.get_current_vertex();
742 const Vertex & prev = p.get_prev_vertex(curr);
743 if (const Vertex & next = p.get_next_vertex(curr); diagonal(p, prev, next))
744 ret.insert(&curr);
745 }
746
747 return ret;
748 }
749
750 public:
763 {
764 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
765 ah_domain_error_if(poly.size() < 3) << "Polygon has less than 3 vertices";
766
767 Polygon p = poly; // work on a copy
769
771 if (p.size() > 3)
772 {
773 ears = init_ears(p);
774 ah_domain_error_if(ears.is_empty())
775 << "No valid ear found; polygon may be non-simple";
776 }
777
779
780 while (p.size() > 3)
781 {
782 ah_domain_error_if(ears.is_empty())
783 << "No valid ear found during triangulation";
784 const Vertex *curr = ears.remove_pos(0);
785
786 const Vertex & prev = p.get_prev_vertex(*curr);
787 const Vertex & next = p.get_next_vertex(*curr);
788
789 ret.append(Triangle(prev.to_point(), curr->to_point(), next.to_point()));
790 p.remove_vertex(*curr);
791
792 // After removal, prev and next are adjacent; recompute their
793 // outer neighbors for ear tests on the updated topology.
794 const Vertex & new_prev_prev = p.get_prev_vertex(prev);
796
797 if (diagonal(p, new_prev_prev, next))
798 ears.insert(&prev);
799 else
800 ears.remove(&prev);
801
802 if (diagonal(p, prev, new_next_next))
803 ears.insert(&next);
804 else
805 ears.remove(&next);
806 }
807
808 assert(p.size() == 3);
809
810 const Vertex & a = p.get_first_vertex();
811 const Vertex & b = a.next_vertex();
812 const Vertex & c = b.next_vertex();
813
815
816 return ret;
817 }
818 };
819
820 // ============================================================================
821 // Closest Pair of Points
822 // ============================================================================
823
844 {
845 public:
852
853 private:
860 {
861 bool operator ()(const Point & p1, const Point & p2) const
862 {
863 if (p1.get_x() < p2.get_x())
864 return true;
865
866 if (p2.get_x() < p1.get_x())
867 return false;
868
869 return p1.get_y() < p2.get_y();
870 }
871 };
872
879 struct ByYCmp
880 {
881 bool operator ()(const Point & p1, const Point & p2) const
882 {
883 if (p1.get_y() < p2.get_y())
884 return true;
885
886 if (p2.get_y() < p1.get_y())
887 return false;
888
889 if (p1.get_x() < p2.get_x())
890 return true;
891
892 if (p2.get_x() < p1.get_x())
893 return false;
894
895 return false;
896 }
897 };
898
904 [[nodiscard]] static Geom_Number dist2(const Point & a, const Point & b)
905 {
906 return a.distance_squared_to(b);
907 }
908
912 [[nodiscard]] static Result make_result(const Point & a, const Point & b)
913 {
914 return {a, b, dist2(a, b)};
915 }
916
928 const size_t l, const size_t r)
929 {
930 assert(r - l >= 2);
931
932 Result best = make_result(px(l), px(l + 1));
933
934 for (size_t i = l; i < r; ++i)
935 for (size_t j = i + 1; j < r; ++j)
936 if (Result cand = make_result(px(i), px(j)); cand.distance_squared < best.distance_squared)
937 best = cand;
938
939 return best;
940 }
941
955 [[nodiscard]] static Result recurse(const Array<Point> & px, const size_t l,
956 const size_t r, Array<Point> & py)
957 {
958 const size_t n = r - l;
959 if (n <= 3)
960 return brute_force(px, l, r);
961
962 const size_t mid = l + n / 2;
963 const Point & mid_point = px(mid);
964
967 left_py.reserve(mid - l);
968 right_py.reserve(r - mid);
969
970 const size_t left_target = mid - l;
971 for (size_t i = 0; i < py.size(); ++i)
972 {
973 const Point & p = py(i);
974 if (p.get_x() < mid_point.get_x())
975 left_py.append(p);
976 else if (p.get_x() > mid_point.get_x())
977 right_py.append(p);
978 else // tie on x — fill left first, then right
979 {
980 if (left_py.size() < left_target)
981 left_py.append(p);
982 else
983 right_py.append(p);
984 }
985 }
986
987 const Result best_left = recurse(px, l, mid, left_py);
989 Result best = best_left.distance_squared < best_right.distance_squared ?
990 best_left :
992
994 strip.reserve(py.size());
995 for (size_t i = 0; i < py.size(); ++i)
996 {
997 const Point & p = py(i);
998 if (const Geom_Number dx = p.get_x() - mid_point.get_x(); dx * dx < best.distance_squared)
999 strip.append(p);
1000 }
1001
1002 for (size_t i = 0; i < strip.size(); ++i)
1003 for (size_t j = i + 1; j < strip.size(); ++j)
1004 {
1005 if (const Geom_Number dy = strip(j).get_y() - strip(i).get_y(); dy * dy >= best.distance_squared)
1006 break;
1007
1008 if (Result cand = make_result(strip(i), strip(j)); cand.distance_squared < best.distance_squared)
1009 best = cand;
1010 }
1011
1012 return best;
1013 }
1014
1015 public:
1025 {
1027 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
1028 px.append(it.get_curr());
1029
1030 ah_domain_error_if(px.size() < 2)
1031 << "Closest pair requires at least 2 points";
1032
1034
1035 // Early-exit on duplicate points: exact minimum distance is zero.
1036 for (size_t i = 1; i < px.size(); ++i)
1037 if (px(i) == px(i - 1))
1038 return {px(i - 1), px(i), 0};
1039
1040 Array<Point> py = px;
1042
1043 return recurse(px, 0, px.size(), py);
1044 }
1045
1053 {
1054 Result r = (*this)(point_set);
1055 return {r.first, r.second};
1056 }
1057 };
1058
1059 // ============================================================================
1060 // Minimum Enclosing Circle (Welzl)
1061 // ============================================================================
1062
1091 {
1092 public:
1094 struct Circle
1095 {
1098
1101 {
1103 }
1104
1106 [[nodiscard]] bool contains(const Point & p) const
1107 {
1109 }
1110 };
1111
1113 [[nodiscard]] static Circle from_one_point(const Point & a)
1114 {
1115 return {a, 0};
1116 }
1117
1120 const Point & b)
1121 {
1122 const Point c = a.midpoint(b);
1123 return {c, c.distance_squared_to(a)};
1124 }
1125
1131 const Point & b,
1132 const Point & c)
1133 {
1134 const Geom_Number & ax = a.get_x();
1135 const Geom_Number & ay = a.get_y();
1136 const Geom_Number & bx = b.get_x();
1137 const Geom_Number & by = b.get_y();
1138 const Geom_Number & cx = c.get_x();
1139 const Geom_Number & cy = c.get_y();
1140
1141 const Geom_Number d = ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
1142
1143 if (d == 0) // collinear — return diameter of farthest pair
1144 {
1148
1149 if (d_ab >= d_bc && d_ab >= d_ac)
1150 return from_two_points(a, b);
1151 if (d_bc >= d_ab && d_bc >= d_ac)
1152 return from_two_points(b, c);
1153 return from_two_points(a, c);
1154 }
1155
1156 const Geom_Number den = d + d;
1157 const Geom_Number a2 = ax * ax + ay * ay;
1158 const Geom_Number b2 = bx * bx + by * by;
1159 const Geom_Number c2 = cx * cx + cy * cy;
1160
1161 const Geom_Number ux =
1162 (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / den;
1163 const Geom_Number uy =
1164 (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / den;
1165
1166 const Point center{ux, uy};
1167 return {center, center.distance_squared_to(a)};
1168 }
1169
1177 [[nodiscard]] Circle operator()(const DynList<Point> & points) const
1178 {
1180 for (DynList<Point>::Iterator it(points); it.has_curr(); it.next_ne())
1181 pts.append(it.get_curr());
1182
1183 ah_domain_error_if(pts.size() == 0)
1184 << "MinimumEnclosingCircle: empty point set";
1185
1186 // Fisher-Yates shuffle
1187 {
1188 std::random_device rd;
1189 std::mt19937 gen(rd());
1190 for (size_t i = pts.size() - 1; i > 0; --i)
1191 {
1192 std::uniform_int_distribution<size_t> dist(0, i);
1193 if (const size_t j = dist(gen); i != j)
1194 std::swap(pts(i), pts(j));
1195 }
1196 }
1197
1198 return welzl_iterative(pts);
1199 }
1200
1202 [[nodiscard]] Circle operator()(std::initializer_list<Point> points) const
1203 {
1205 for (const auto & p: points)
1206 l.append(p);
1207 return (*this)(l);
1208 }
1209
1210 private:
1213 size_t n,
1214 const Point & p1)
1215 {
1216 Circle d = from_one_point(p1);
1217 for (size_t j = 0; j < n; ++j)
1218 if (not d.contains(pts(j)))
1219 d = mec_with_two_points(pts, j, p1, pts(j));
1220 return d;
1221 }
1222
1225 size_t n,
1226 const Point & p1,
1227 const Point & p2)
1228 {
1229 Circle d = from_two_points(p1, p2);
1230 for (size_t k = 0; k < n; ++k)
1231 if (not d.contains(pts(k)))
1232 d = from_three_points(p1, p2, pts(k));
1233 return d;
1234 }
1235
1238 {
1239 Circle d = from_one_point(pts(0));
1240
1241 for (size_t i = 1; i < pts.size(); ++i)
1242 if (not d.contains(pts(i)))
1243 d = mec_with_point(pts, i, pts(i));
1244
1245 return d;
1246 }
1247 };
1248
1249 // ============================================================================
1250 // Rotating Calipers (Convex Polygon Metrics)
1251 // ============================================================================
1252
1275 {
1276 public:
1283
1291
1292 private:
1303 {
1304 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1305
1306 ah_domain_error_if(poly.size() < 2) << "Rotating calipers requires at least 2 vertices";
1307
1309 }
1310
1320 [[nodiscard]] static bool is_convex(const Array<Point> & verts)
1321 {
1323 }
1324
1325 [[nodiscard]] static DiameterResult make_diameter(const Point & a, const Point & b)
1326 {
1327 return {a, b, a.distance_squared_to(b)};
1328 }
1329
1330 public:
1339 static DiameterResult diameter(const Polygon & poly)
1340 {
1341 const Array<Point> verts = extract_vertices(poly);
1342 ah_domain_error_if(not is_convex(verts)) << "Polygon must be convex";
1343
1344 const size_t n = verts.size();
1345 if (n == 2)
1346 return make_diameter(verts(0), verts(1));
1347
1348 auto edge_area = [&verts, n](const size_t i, const size_t k) -> Geom_Number
1349 {
1350 Geom_Number area =
1351 area_of_parallelogram(verts(i), verts((i + 1) % n), verts(k));
1352 if (area < 0)
1353 area = -area;
1354 return area;
1355 };
1356
1357 size_t j = 1;
1358 size_t safety = 0;
1359 while (edge_area(0, (j + 1) % n) > edge_area(0, j) and safety++ < n)
1360 j = (j + 1) % n;
1361
1363
1364 for (size_t i = 0; i < n; ++i)
1365 {
1366 const size_t ni = (i + 1) % n;
1367 safety = 0;
1368 while (edge_area(i, (j + 1) % n) > edge_area(i, j) and safety++ < n)
1369 j = (j + 1) % n;
1370
1371 if (DiameterResult cand1 = make_diameter(verts(i), verts(j)); cand1.distance_squared > best.distance_squared)
1372 best = cand1;
1373
1374 if (DiameterResult cand2 = make_diameter(verts(ni), verts(j)); cand2.distance_squared > best.distance_squared)
1375 best = cand2;
1376 }
1377
1378 return best;
1379 }
1380
1394 static WidthResult minimum_width(const Polygon & poly)
1395 {
1396 const Array<Point> verts = extract_vertices(poly);
1397 ah_domain_error_if(not is_convex(verts)) << "Polygon must be convex";
1398
1399 const size_t n = verts.size();
1400 if (n == 2)
1401 return {verts(0), verts(1), verts(0), 0};
1402
1403 auto area_to_edge = [&verts, n](const size_t i, const size_t k) -> Geom_Number
1404 {
1405 Geom_Number area =
1406 area_of_parallelogram(verts(i), verts((i + 1) % n), verts(k));
1407 if (area < 0)
1408 area = -area;
1409 return area;
1410 };
1411
1412 auto edge_len2 = [&verts, n](const size_t i) -> Geom_Number
1413 {
1414 return verts(i).distance_squared_to(verts((i + 1) % n));
1415 };
1416
1417 size_t best_i = 0;
1418 size_t best_ni = 1;
1419 size_t best_j = 0;
1421 bool initialized = false;
1422
1423 // Initialize antipodal index for edge (0,1).
1424 size_t j = 1 % n;
1425 size_t safety = 0;
1426 while (area_to_edge(0, (j + 1) % n) > area_to_edge(0, j) and safety++ < n)
1427 j = (j + 1) % n;
1428
1429 for (size_t i = 0; i < n; ++i)
1430 {
1431 const size_t ni = (i + 1) % n;
1432
1433 const Geom_Number len2 = edge_len2(i);
1434 if (len2 == 0)
1435 continue;
1436
1437 // Advance antipodal index while area increases.
1438 safety = 0;
1439 while (area_to_edge(i, (j + 1) % n) > area_to_edge(i, j) and safety++ < n)
1440 j = (j + 1) % n;
1441
1442 const Geom_Number num = area_to_edge(i, j);
1443 const Geom_Number width_sq = (num * num) / len2;
1445 {
1446 best_i = i;
1447 best_ni = ni;
1448 best_j = j;
1450 initialized = true;
1451 }
1452 }
1453
1454 if (not initialized)
1455 return {verts(0), verts(1), verts(0), 0};
1456
1458 }
1459 };
1460
1461 // ============================================================================
1462 // Point-in-Polygon
1463 // ============================================================================
1464
1490 {
1491 public:
1492 enum class Location
1493 {
1494 Outside,
1495 Boundary,
1496 Inside
1497 };
1498
1508 static Location locate(const Polygon & poly, const Point & p)
1509 {
1510 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1511
1512 ah_domain_error_if(poly.size() < 3) << "Point-in-polygon requires at least 3 vertices";
1513
1514 switch (poly.locate_point(p))
1515 {
1517 return Location::Boundary;
1519 return Location::Inside;
1521 default:
1522 return Location::Outside;
1523 }
1524 }
1525
1533 [[nodiscard]] static bool contains(const Polygon & poly, const Point & p)
1534 {
1535 const Location loc = locate(poly, p);
1536 return loc != Location::Outside;
1537 }
1538
1546 [[nodiscard]] static bool strictly_contains(const Polygon & poly,
1547 const Point & p)
1548 {
1549 return locate(poly, p) == Location::Inside;
1550 }
1551 };
1552
1553 // ============================================================================
1554 // Polygon Intersection (Basic)
1555 // ============================================================================
1556
1577 {
1579 {
1580 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1581
1582 ah_domain_error_if(poly.size() < 3) << "Polygon must have at least 3 vertices";
1583
1585 }
1586
1591
1592 [[nodiscard]] static bool is_convex(const Array<Point> & verts)
1593 {
1594 if (verts.size() < 3)
1595 return false;
1597 }
1598
1599 [[nodiscard]] static bool inside_half_plane(const Point & p, const Point & a,
1600 const Point & b,
1601 const bool clip_ccw)
1602 {
1603 const Orientation o = orientation(a, b, p);
1604 return clip_ccw ? o != Orientation::CW : o != Orientation::CCW;
1605 }
1606
1607 [[nodiscard]] static Point line_intersection(const Point & s, const Point & e,
1608 const Point & a, const Point & b)
1609 {
1610 const Geom_Number rx = e.get_x() - s.get_x();
1611 const Geom_Number ry = e.get_y() - s.get_y();
1612 const Geom_Number sx = b.get_x() - a.get_x();
1613 const Geom_Number sy = b.get_y() - a.get_y();
1614
1615 const Geom_Number den = rx * sy - ry * sx;
1616 if (den == 0)
1617 {
1618 if (orientation(a, b, s) == Orientation::COLLINEAR)
1619 return s;
1620 if (orientation(a, b, e) == Orientation::COLLINEAR)
1621 return e;
1622 return s;
1623 }
1624
1625 const Geom_Number qpx = a.get_x() - s.get_x();
1626 const Geom_Number qpy = a.get_y() - s.get_y();
1627 const Geom_Number t = (qpx * sy - qpy * sx) / den;
1628
1629 return {s.get_x() + t * rx, s.get_y() + t * ry};
1630 }
1631
1632 static void push_clean(Array<Point> & out, const Point & p)
1633 {
1634 if (out.is_empty())
1635 {
1636 out.append(p);
1637 return;
1638 }
1639
1640 if (out.get_last() == p)
1641 return;
1642
1643 while (out.size() >= 2)
1644 {
1645 const Point & a = out(out.size() - 2);
1646 const Point & b = out(out.size() - 1);
1647 if (orientation(a, b, p) != Orientation::COLLINEAR)
1648 break;
1649
1650 if (const Segment ab(a, b); on_segment(ab, p))
1651 return;
1652
1653 static_cast<void>(out.remove_last());
1654 }
1655
1656 out.append(p);
1657 }
1658
1660 {
1662 ret.reserve(pts.size());
1663 for (size_t i = 0; i < pts.size(); ++i)
1664 push_clean(ret, pts(i));
1665
1666 if (ret.size() > 1 and ret(0) == ret.get_last())
1667 static_cast<void>(ret.remove_last());
1668
1669 return ret;
1670 }
1671
1673 {
1674 Polygon ret;
1676
1677 for (size_t i = 0; i < clean.size(); ++i)
1678 ret.add_vertex(clean(i));
1679
1680 if (ret.size() >= 3)
1681 ret.close();
1682
1683 return ret;
1684 }
1685
1686 public:
1697 const Polygon & clip) const
1698 {
1701
1702 ah_domain_error_if(not is_convex(subj)) << "Subject polygon must be convex";
1703 ah_domain_error_if(not is_convex(clp)) << "Clip polygon must be convex";
1704
1706 ah_domain_error_if(clip_area2 == 0) << "Clip polygon is degenerate";
1707
1708 const bool clip_ccw = clip_area2 > 0;
1709
1711
1712 for (size_t i = 0; i < clp.size(); ++i)
1713 {
1714 if (output.is_empty())
1715 break;
1716
1717 const Point & a = clp(i);
1718 const Point & b = clp((i + 1) % clp.size());
1719
1720 const Array<Point> input = output;
1721 output = Array<Point>();
1722 output.reserve(input.size() + 2);
1723
1724 Point s = input.get_last();
1725 bool s_inside = inside_half_plane(s, a, b, clip_ccw);
1726
1727 for (size_t j = 0; j < input.size(); ++j)
1728 {
1729 const Point & e = input(j);
1730 const bool e_inside = inside_half_plane(e, a, b, clip_ccw);
1731
1732 if (e_inside)
1733 {
1734 if (not s_inside)
1735 push_clean(output, line_intersection(s, e, a, b));
1736 push_clean(output, e);
1737 }
1738 else if (s_inside)
1739 push_clean(output, line_intersection(s, e, a, b));
1740
1741 s = e;
1743 }
1744
1746 }
1747
1748 return build_polygon(output);
1749 }
1750 };
1751
1752 // ============================================================================
1753 // Half-Plane Intersection
1754 // ============================================================================
1755
1773 {
1774 public:
1776 {
1779
1780 HalfPlane() = default;
1781
1782 HalfPlane(Point p_, Point q_) : p(std::move(p_)), q(std::move(q_)) {}
1783
1784 [[nodiscard]] Geom_Number dx() const { return q.get_x() - p.get_x(); }
1785
1786 [[nodiscard]] Geom_Number dy() const { return q.get_y() - p.get_y(); }
1787
1789 {
1790 // n = (-dy, dx), inequality n.x*x + n.y*y >= offset
1791 return -dy() * p.get_x() + dx() * p.get_y();
1792 }
1793
1794 [[nodiscard]] bool outside(const Point & x) const
1795 {
1796 return orientation(p, q, x) == Orientation::CW;
1797 }
1798 };
1799
1800 private:
1805
1806 [[nodiscard]] static bool upper_half(const HalfPlane & h)
1807 {
1808 return h.dy() > 0 or h.dy() == 0 and h.dx() >= 0;
1809 }
1810
1812 const HalfPlane & b)
1813 {
1814 return a.dx() * b.dy() - a.dy() * b.dx();
1815 }
1816
1818 const HalfPlane & b)
1819 {
1820 return a.dx() * b.dx() + a.dy() * b.dy();
1821 }
1822
1823 [[nodiscard]] static bool same_direction(const HalfPlane & a,
1824 const HalfPlane & b)
1825 {
1826 return cross_dir(a, b) == 0 and dot_dir(a, b) > 0;
1827 }
1828
1829 [[nodiscard]] static bool parallel(const HalfPlane & a, const HalfPlane & b)
1830 {
1831 return cross_dir(a, b) == 0;
1832 }
1833
1835 const HalfPlane & b)
1836 {
1837 const Geom_Number rx = a.dx();
1838 const Geom_Number ry = a.dy();
1839 const Geom_Number sx = b.dx();
1840 const Geom_Number sy = b.dy();
1841
1842 const Geom_Number den = rx * sy - ry * sx;
1843 ah_domain_error_if(den == 0) << "Parallel half-plane boundaries";
1844
1845 const Geom_Number qpx = b.p.get_x() - a.p.get_x();
1846 const Geom_Number qpy = b.p.get_y() - a.p.get_y();
1847 const Geom_Number t = (qpx * sy - qpy * sx) / den;
1848
1849 return {a.p.get_x() + t * rx, a.p.get_y() + t * ry};
1850 }
1851
1852 static void push_clean(Array<Point> & out, const Point & p)
1853 {
1854 if (out.is_empty())
1855 {
1856 out.append(p);
1857 return;
1858 }
1859
1860 if (out.get_last() == p)
1861 return;
1862
1863 while (out.size() >= 2)
1864 {
1865 const Point & a = out(out.size() - 2);
1866 const Point & b = out(out.size() - 1);
1867 if (orientation(a, b, p) != Orientation::COLLINEAR)
1868 break;
1869
1870 if (const Segment ab(a, b); on_segment(ab, p))
1871 return;
1872
1873 static_cast<void>(out.remove_last());
1874 }
1875
1876 out.append(p);
1877 }
1878
1880 {
1882 ret.reserve(pts.size());
1883 for (size_t i = 0; i < pts.size(); ++i)
1884 push_clean(ret, pts(i));
1885
1886 if (ret.size() > 1 and ret(0) == ret.get_last())
1887 static_cast<void>(ret.remove_last());
1888
1889 return ret;
1890 }
1891
1893 {
1894 Polygon ret;
1896
1897 for (size_t i = 0; i < clean.size(); ++i)
1898 ret.add_vertex(clean(i));
1899
1900 if (ret.size() >= 3)
1901 ret.close();
1902
1903 return ret;
1904 }
1905
1906 public:
1917 {
1918 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1919 ah_domain_error_if(poly.size() < 3) << "Polygon must have at least 3 vertices";
1920
1922 verts.reserve(poly.size());
1923 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
1924 verts.append(it.get_current_vertex());
1925
1927 ah_domain_error_if(area2 == 0) << "Polygon is degenerate";
1928 const bool ccw = area2 > 0;
1929
1931 hs.reserve(verts.size());
1932 for (size_t i = 0; i < verts.size(); ++i)
1933 {
1934 const Point & a = verts(i);
1935 const Point & b = verts((i + 1) % verts.size());
1936 hs.append(ccw ? HalfPlane(a, b) : HalfPlane(b, a));
1937 }
1938 return hs;
1939 }
1940
1948 {
1949 if (halfplanes.size() < 3)
1950 return {};
1951
1953 quicksort_op(hps, [](const HalfPlane & a, const HalfPlane & b)
1954 {
1955 const bool ha = upper_half(a);
1956 const bool hb = upper_half(b);
1957 if (ha != hb)
1958 return ha and not hb;
1959
1960 if (const Geom_Number cr = cross_dir(a, b); cr != 0)
1961 return cr > 0;
1962
1963 // Same direction: keep stronger constraints first.
1964 return a.offset() > b.offset();
1965 });
1966
1968 unique.reserve(hps.size());
1969 for (size_t i = 0; i < hps.size(); ++i)
1970 {
1971 const HalfPlane & hp = hps(i);
1972 if (unique.is_empty())
1973 {
1974 unique.append(hp);
1975 continue;
1976 }
1977
1978 if (const HalfPlane & last = unique.get_last(); same_direction(last, hp))
1979 {
1980 if (hp.offset() > last.offset())
1981 {
1982 static_cast<void>(unique.remove_last());
1983 unique.append(hp);
1984 }
1985 continue;
1986 }
1987
1988 unique.append(hp);
1989 }
1990
1993
1994 for (size_t i = 0; i < unique.size(); ++i)
1995 {
1996 const HalfPlane & hp = unique(i);
1997
1998 while (not intersections.is_empty() and hp.outside(intersections.get_last()))
1999 {
2000 dq.remove_last();
2001 intersections.remove_last();
2002 }
2003
2004 while (not intersections.is_empty() and hp.outside(intersections.get_first()))
2005 {
2006 dq.remove_first();
2007 intersections.remove_first();
2008 }
2009
2010 if (not dq.is_empty() and parallel(dq.get_last(), hp))
2011 {
2012 if (same_direction(dq.get_last(), hp))
2013 {
2014 if (hp.offset() > dq.get_last().offset())
2015 {
2016 dq.remove_last();
2017 if (not intersections.is_empty())
2018 intersections.remove_last();
2019 }
2020 else
2021 continue;
2022 }
2023 else // Opposite parallel boundaries cannot define a bounded polygon here.
2024 return {};
2025 }
2026
2027 if (not dq.is_empty())
2028 intersections.append(line_intersection(dq.get_last(), hp));
2029
2030 dq.append(hp);
2031 }
2032
2033 while (not intersections.is_empty() and dq.get_first().outside(intersections.get_last()))
2034 {
2035 dq.remove_last();
2036 intersections.remove_last();
2037 }
2038
2039 while (not intersections.is_empty() and dq.get_last().outside(intersections.get_first()))
2040 {
2041 dq.remove_first();
2042 intersections.remove_first();
2043 }
2044
2045 if (dq.size() < 3)
2046 return {};
2047
2048 if (parallel(dq.get_last(), dq.get_first()))
2049 return {};
2050
2051 const Point closing = line_intersection(dq.get_last(), dq.get_first());
2052
2054 verts.reserve(intersections.size() + 1);
2055
2057 while (not tmp.is_empty())
2058 verts.append(tmp.remove_first());
2059 verts.append(closing);
2060
2061 return build_polygon(verts);
2062 }
2063
2070 [[nodiscard]] Polygon operator ()(const std::initializer_list<HalfPlane> il) const
2071 {
2073 hps.reserve(il.size());
2074 for (const HalfPlane & hp: il)
2075 hps.append(hp);
2076 return (*this)(hps);
2077 }
2078 };
2079
2080 // ============================================================================
2081 // Delaunay Triangulation (Bowyer-Watson)
2082 // ============================================================================
2083
2114 {
2115 public:
2122 {
2123 size_t i;
2124 size_t j;
2125 size_t k;
2126 };
2127
2136
2137 private:
2141 [[nodiscard]] static bool lexicographic_less(const Point & p1, const Point & p2)
2142 {
2143 if (p1.get_x() < p2.get_x())
2144 return true;
2145 if (p2.get_x() < p1.get_x())
2146 return false;
2147 return p1.get_y() < p2.get_y();
2148 }
2149
2153 [[nodiscard]] static bool all_collinear(const Array<Point> & pts)
2154 {
2155 if (pts.size() < 3)
2156 return true;
2157
2158 for (size_t i = 2; i < pts.size(); ++i)
2159 if (orientation(pts(0), pts(1), pts(i)) != Orientation::COLLINEAR)
2160 return false;
2161
2162 return true;
2163 }
2164
2169 const size_t ia,
2170 const size_t ib,
2171 const size_t ic,
2172 const size_t ip)
2173 {
2174 const Point & a = pts(ia);
2175 const Point & b = pts(ib);
2176 const Point & c = pts(ic);
2177 const Point & p = pts(ip);
2178
2179 const Geom_Number det = in_circle_determinant(a, b, c, p);
2180
2181 const Orientation o = orientation(a, b, c);
2182 if (o == Orientation::CCW)
2183 {
2184 if (det > 0)
2185 return true;
2186 if (det < 0)
2187 return false;
2188 }
2189 if (o == Orientation::CW)
2190 {
2191 if (det < 0)
2192 return true;
2193 if (det > 0)
2194 return false;
2195 }
2196
2197 // Cocircular / degenerate tie-break for deterministic output.
2198 size_t max_idx = ia;
2199 if (ib > max_idx) max_idx = ib;
2200 if (ic > max_idx) max_idx = ic;
2201 return ip < max_idx;
2202 }
2203
2208 {
2210 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
2211 all.append(it.get_curr());
2212
2213 quicksort_op(all, [](const Point & p1, const Point & p2)
2214 {
2215 return lexicographic_less(p1, p2);
2216 });
2217
2219 ret.reserve(all.size());
2220 for (size_t i = 0; i < all.size(); ++i)
2221 if (ret.is_empty() or ret.get_last() != all(i))
2222 ret.append(all(i));
2223
2224 return ret;
2225 }
2226
2227 public:
2235 {
2236 Result ret;
2238 const size_t n = ret.sites.size();
2239
2240 if (n < 3 or all_collinear(ret.sites))
2241 return ret;
2242
2243 Array<Point> pts = ret.sites;
2244 ret.triangles =
2245 GeomBowyerWatsonUtils::triangulate<IndexedTriangle>(
2246 std::move(pts), n,
2247 [](const Array<Point> & all_pts,
2248 const size_t ia, const size_t ib,
2249 const size_t ic, const size_t ip)
2250 {
2251 return
2253 all_pts, ia, ib, ic, ip);
2254 });
2255
2256 return ret;
2257 }
2258
2265 [[nodiscard]] Result operator ()(const std::initializer_list<Point> il) const
2266 {
2267 DynList<Point> points;
2268 for (const Point & p: il)
2269 points.append(p);
2270 return (*this)(points);
2271 }
2272
2280 {
2282 for (size_t tria_idx = 0; tria_idx < result.triangles.size(); ++tria_idx)
2283 {
2284 const auto & [i, j, k] = result.triangles(tria_idx);
2285 out.append(Triangle(result.sites(i),
2286 result.sites(j),
2287 result.sites(k)));
2288 }
2289 return out;
2290 }
2291 };
2292
2293 // ============================================================================
2294 // Regular Triangulation (Weighted Delaunay) — Bowyer-Watson
2295 // ============================================================================
2296
2332 {
2333 public:
2340
2342
2348
2349 private:
2350 [[nodiscard]] static bool lex_less(const Point & p1, const Point & p2)
2351 {
2352 if (p1.get_x() < p2.get_x())
2353 return true;
2354 if (p2.get_x() < p1.get_x())
2355 return false;
2356 return p1.get_y() < p2.get_y();
2357 }
2358
2360 {
2361 if (s.size() < 3)
2362 return true;
2363
2364 for (size_t i = 2; i < s.size(); ++i)
2365 if (orientation(s(0).position, s(1).position, s(i).position)
2367 return false;
2368
2369 return true;
2370 }
2371
2389 const Array<Point> & pts, const Array<Geom_Number> & wts,
2390 const size_t ia, const size_t ib, const size_t ic, const size_t ip)
2391 {
2392 const Point & a = pts(ia);
2393 const Point & b = pts(ib);
2394 const Point & c = pts(ic);
2395 const Point & p = pts(ip);
2396
2397 const Geom_Number adx = a.get_x() - p.get_x();
2398 const Geom_Number ady = a.get_y() - p.get_y();
2399 const Geom_Number bdx = b.get_x() - p.get_x();
2400 const Geom_Number bdy = b.get_y() - p.get_y();
2401 const Geom_Number cdx = c.get_x() - p.get_x();
2402 const Geom_Number cdy = c.get_y() - p.get_y();
2403
2404 const Geom_Number ad2 = adx * adx + ady * ady - wts(ia) + wts(ip);
2405 const Geom_Number bd2 = bdx * bdx + bdy * bdy - wts(ib) + wts(ip);
2406 const Geom_Number cd2 = cdx * cdx + cdy * cdy - wts(ic) + wts(ip);
2407
2408 const Geom_Number det = ad2 * (bdx * cdy - bdy * cdx) -
2409 bd2 * (adx * cdy - ady * cdx) +
2410 cd2 * (adx * bdy - ady * bdx);
2411
2412 const Orientation o = orientation(a, b, c);
2413 if (o == Orientation::CCW)
2414 {
2415 if (det > 0) return true;
2416 if (det < 0) return false;
2417 }
2418 if (o == Orientation::CW)
2419 {
2420 if (det < 0) return true;
2421 if (det > 0) return false;
2422 }
2423
2424 // Cocircular / degenerate tie-break for deterministic output.
2425 size_t max_idx = ia;
2426 if (ib > max_idx) max_idx = ib;
2427 if (ic > max_idx) max_idx = ic;
2428 return ip < max_idx;
2429 }
2430
2433 {
2435 all.reserve(input.size());
2436 for (size_t i = 0; i < input.size(); ++i)
2437 all.append(input(i));
2438
2439 quicksort_op(all, [](const WeightedSite & a, const WeightedSite & b)
2440 {
2441 return lex_less(a.position, b.position);
2442 });
2443
2445 ret.reserve(all.size());
2446 for (size_t i = 0; i < all.size(); ++i)
2447 if (ret.is_empty() or ret.get_last().position != all(i).position)
2448 ret.append(all(i));
2449
2450 return ret;
2451 }
2452
2453 public:
2461 {
2462 Result ret;
2464 const size_t n = ret.sites.size();
2465
2466 if (n < 3 or all_collinear(ret.sites))
2467 return ret;
2468
2471 pts.reserve(n + 3);
2472 wts.reserve(n + 3);
2473 for (size_t i = 0; i < n; ++i)
2474 {
2475 pts.append(ret.sites(i).position);
2476 wts.append(ret.sites(i).weight);
2477 }
2478 wts.append(Geom_Number(0));
2479 wts.append(Geom_Number(0));
2480 wts.append(Geom_Number(0));
2481 ret.triangles = GeomBowyerWatsonUtils::triangulate<IndexedTriangle>(
2482 std::move(pts), n,
2483 [&wts](const Array<Point> & all_pts,
2484 const size_t ia, const size_t ib,
2485 const size_t ic, const size_t ip)
2486 {
2487 return
2489 all_pts, wts, ia, ib, ic, ip);
2490 });
2491
2492 return ret;
2493 }
2494 };
2495
2496 // ============================================================================
2497 // Delaunay Triangulation — Randomized Incremental O(n log n) expected
2498 // ============================================================================
2499
2514 {
2515 public:
2518
2519 private:
2520 static constexpr size_t NONE = ~static_cast<size_t>(0);
2521
2522 struct Tri
2523 {
2524 size_t v[3];
2525 size_t adj[3];
2526 bool alive;
2527 };
2528
2529 struct DagNode
2530 {
2531 size_t tri;
2533 };
2534
2536 [[nodiscard]] static size_t local_of(const Tri & t, const size_t id)
2537 {
2538 for (int i = 0; i < 3; ++i)
2539 if (t.v[i] == id) return static_cast<size_t>(i);
2540 return NONE;
2541 }
2542
2544 [[nodiscard]] static size_t adj_of(const Tri & t, const size_t n)
2545 {
2546 for (int i = 0; i < 3; ++i)
2547 if (t.adj[i] == n) return static_cast<size_t>(i);
2548 return NONE;
2549 }
2550
2552 [[nodiscard]] static bool point_in_tri(const Array<Point> & pts,
2553 const Tri & t, const size_t pidx)
2554 {
2555 const Orientation o0 = orientation(pts(t.v[0]), pts(t.v[1]), pts(pidx));
2556 const Orientation o1 = orientation(pts(t.v[1]), pts(t.v[2]), pts(pidx));
2557 const Orientation o2 = orientation(pts(t.v[2]), pts(t.v[0]), pts(pidx));
2558 const bool has_cw = o0 == Orientation::CW or o1 == Orientation::CW
2560 const bool has_ccw = o0 == Orientation::CCW or o1 == Orientation::CCW
2562 return not (has_cw and has_ccw);
2563 }
2564
2568 [[nodiscard]] static bool in_cc(const Array<Point> & pts,
2569 const size_t ia, const size_t ib,
2570 const size_t ic, const size_t ip)
2571 {
2573 pts(ic), pts(ip));
2574 const Orientation o = orientation(pts(ia), pts(ib), pts(ic));
2575 if (o == Orientation::CCW)
2576 {
2577 if (det > 0) return true;
2578 if (det < 0) return false;
2579 }
2580 if (o == Orientation::CW)
2581 {
2582 if (det < 0) return true;
2583 if (det > 0) return false;
2584 }
2585 size_t mx = ia;
2586 if (ib > mx) mx = ib;
2587 if (ic > mx) mx = ic;
2588 return ip < mx;
2589 }
2590
2596 [[nodiscard]] static size_t locate(const Array<Point> & pts,
2597 const Array<Tri> & tris,
2598 const Array<DagNode> & dag,
2599 const size_t pidx, const size_t root)
2600 {
2601 size_t cur = root;
2602 while (not dag(cur).children.is_empty())
2603 {
2604 bool found = false;
2605 for (size_t c = 0; c < dag(cur).children.size(); ++c)
2606 {
2607 if (const size_t child = dag(cur).children(c); point_in_tri(pts, tris(child), pidx))
2608 {
2609 cur = child;
2610 found = true;
2611 break;
2612 }
2613 }
2614 if (not found)
2615 break;
2616 }
2617 return cur;
2618 }
2619
2621 static void remap_adj(Array<Tri> & tris, const size_t ot, const size_t nt)
2622 {
2623 for (const unsigned long nb: tris(ot).adj)
2624 {
2625 if (nb == NONE) continue;
2626 if (const size_t li = adj_of(tris(nb), ot); li != NONE)
2627 tris(nb).adj[li] = nt;
2628 }
2629 }
2630
2631 public:
2633 {
2634 Result ret;
2635
2636 // Build unique sorted points.
2637 {
2639 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
2640 all.append(it.get_curr());
2641 quicksort_op(all, [](const Point & a, const Point & b)
2642 {
2643 return a.get_x() < b.get_x() or
2644 (a.get_x() == b.get_x() and a.get_y() < b.get_y());
2645 });
2646 ret.sites = Array<Point>();
2647 ret.sites.reserve(all.size());
2648 for (size_t i = 0; i < all.size(); ++i)
2649 if (ret.sites.is_empty() or ret.sites.get_last() != all(i))
2650 ret.sites.append(all(i));
2651 }
2652
2653 const size_t n = ret.sites.size();
2654 if (n < 3)
2655 return ret;
2656
2657 // Check all-collinear.
2658 {
2659 bool collinear = true;
2660 for (size_t i = 2; i < n and collinear; ++i)
2661 if (orientation(ret.sites(0), ret.sites(1), ret.sites(i)) != Orientation::COLLINEAR)
2662 collinear = false;
2663 if (collinear) return ret;
2664 }
2665
2666 // Build pts array with super-triangle appended.
2667 Array<Point> pts = ret.sites;
2668 Geom_Number mnx = pts(0).get_x(), mxx = mnx;
2669 Geom_Number mny = pts(0).get_y(), mxy = mny;
2670 for (size_t i = 1; i < n; ++i)
2671 {
2672 if (pts(i).get_x() < mnx) mnx = pts(i).get_x();
2673 if (pts(i).get_x() > mxx) mxx = pts(i).get_x();
2674 if (pts(i).get_y() < mny) mny = pts(i).get_y();
2675 if (pts(i).get_y() > mxy) mxy = pts(i).get_y();
2676 }
2677 Geom_Number delta = mxx - mnx > mxy - mny ? mxx - mnx : mxy - mny;
2678 if (delta == 0)
2679 delta = 1;
2680 const Geom_Number sp = delta * 16 + 1;
2681 const Geom_Number cx = (mnx + mxx) / 2, cy = (mny + mxy) / 2;
2682 pts.append(Point(cx - sp - sp, cy - sp));
2683 pts.append(Point(cx + sp + sp, cy - sp));
2684 pts.append(Point(cx, cy + sp + sp));
2685
2686 // Random insertion order for input points [0..n).
2687 Array<size_t> order;
2688 order.reserve(n);
2689 for (size_t i = 0; i < n; ++i)
2690 order.append(i);
2691 // Fisher-Yates shuffle.
2692 {
2693 std::random_device rd;
2694 std::mt19937 gen(rd());
2695 for (size_t i = n - 1; i > 0; --i)
2696 {
2697 std::uniform_int_distribution<size_t> dis(0, i);
2698 const size_t j = dis(gen);
2699 const size_t tmp = order(i);
2700 order(i) = order(j);
2701 order(j) = tmp;
2702 }
2703 }
2704
2705 // Initialize with super-triangle.
2707 Array<DagNode> dag;
2708 tris.append(Tri{{n, n + 1, n + 2}, {NONE, NONE, NONE}, true});
2709 dag.append(DagNode{0, Array<size_t>()});
2710
2711 // Insert each point using the local Bowyer-Watson cavity approach.
2712 // 1) Locate containing triangle via DAG — O(log n) expected.
2713 // 2) BFS from it to find all "bad" triangles (circumcircle
2714 // contains the new point).
2715 // 3) Extract the boundary polygon of the cavity.
2716 // 4) Re-triangulate cavity by connecting boundary edges to
2717 // the new point.
2718 // Expected cavity size is O(1) for random insertion order,
2719 // giving O(n log n) total expected time.
2720 for (size_t oi = 0; oi < n; ++oi)
2721 {
2722 const size_t pidx = order(oi);
2723 const size_t ti = locate(pts, tris, dag, pidx, 0);
2724
2725 // --- BFS to find the cavity ---
2726 const size_t ntris_before = tris.size();
2729 for (size_t i = 0; i < ntris_before; ++i)
2730 is_bad.append(false);
2731
2733 is_bad(ti) = true;
2734 cavity.append(ti);
2735
2736 // Frontier queue: at most one enqueue per triangle.
2738 frontier.put(ti);
2739 while (not frontier.is_empty())
2740 for (const size_t ct = frontier.get(); unsigned long nb: tris(ct).adj)
2741 {
2742 if (nb == NONE or nb >= ntris_before or not tris(nb).alive or is_bad(nb))
2743 continue;
2744 if (in_cc(pts, tris(nb).v[0], tris(nb).v[1], tris(nb).v[2], pidx))
2745 {
2746 is_bad(nb) = true;
2747 cavity.append(nb);
2748 frontier.put(nb);
2749 }
2750 }
2751
2752 // --- Extract boundary edges ---
2753 // Each boundary edge is an edge of a cavity triangle whose
2754 // neighbor is outside the cavity (or NONE).
2755 struct BEdge
2756 {
2757 size_t u, v, ext, old_ct;
2758 };
2759 Array<BEdge> boundary;
2760 for (size_t ci = 0; ci < cavity.size(); ++ci)
2761 {
2762 const size_t ct = cavity(ci);
2763 for (int e = 0; e < 3; ++e)
2764 {
2765 const size_t nb = tris(ct).adj[e];
2766 if (nb != NONE and nb < ntris_before and is_bad(nb))
2767 continue; // internal cavity edge — skip
2768 // Boundary edge opposite v[e]: vertices (v[(e+1)%3], v[(e+2)%3]).
2769 boundary.append(BEdge{
2770 tris(ct).v[(e + 1) % 3],
2771 tris(ct).v[(e + 2) % 3],
2772 nb, ct
2773 });
2774 }
2775 }
2776
2777 // --- Create new triangles ---
2778 // For each boundary edge (u,v) create triangle (u, v, pidx).
2779 // adj[2] (opposite pidx) = external neighbor.
2780 const size_t V = pts.size();
2781 Array<size_t> as_u, as_v; // vertex → new tri where it's u / v
2782 as_u.reserve(V);
2783 as_v.reserve(V);
2784 for (size_t i = 0; i < V; ++i)
2785 {
2786 as_u.append(NONE);
2787 as_v.append(NONE);
2788 }
2789
2791 new_tris.reserve(boundary.size());
2792
2793 for (size_t bi = 0; bi < boundary.size(); ++bi)
2794 {
2795 size_t u = boundary(bi).u, v = boundary(bi).v;
2796 const size_t ext = boundary(bi).ext;
2797 const size_t old_ct = boundary(bi).old_ct;
2798
2799 // Ensure CCW winding.
2800 if (orientation(pts(u), pts(v), pts(pidx)) != Orientation::CCW)
2801 {
2802 const size_t tmp = u;
2803 u = v;
2804 v = tmp;
2805 }
2806
2807 const size_t nt = tris.size();
2808 tris.append(Tri{{u, v, pidx}, {NONE, NONE, ext}, true});
2809 dag.append(DagNode{nt, Array<size_t>()});
2810 new_tris.append(nt);
2811
2812 as_u(u) = nt;
2813 as_v(v) = nt;
2814
2815 // Remap external neighbor to point to the new triangle.
2816 if (ext != NONE)
2817 if (const size_t li = adj_of(tris(ext), old_ct); li != NONE)
2818 tris(ext).adj[li] = nt;
2819 }
2820
2821 // --- Internal adjacencies between new triangles ---
2822 // Two new triangles share an edge through pidx.
2823 // For triangle (u, v, pidx):
2824 // adj[0] opp u = edge(v, pidx) → the new tri with v as u
2825 // adj[1] opp v = edge(pidx, u) → the new tri with u as v
2826 for (size_t ni = 0; ni < new_tris.size(); ++ni)
2827 {
2828 const size_t nt = new_tris(ni);
2829 const size_t u = tris(nt).v[0];
2830 const size_t v = tris(nt).v[1];
2831 tris(nt).adj[0] = as_u(v); // neighbor across (v, pidx)
2832 tris(nt).adj[1] = as_v(u); // neighbor across (pidx, u)
2833 }
2834
2835 // --- Kill cavity and update DAG ---
2836 for (size_t ci = 0; ci < cavity.size(); ++ci)
2837 {
2838 tris(cavity(ci)).alive = false;
2839 for (size_t ni = 0; ni < new_tris.size(); ++ni)
2840 dag(cavity(ci)).children.append(new_tris(ni));
2841 }
2842 }
2843
2844 // Collect alive triangles that don't reference super-triangle.
2845 ret.triangles.reserve(2 * n);
2846 for (size_t t = 0; t < tris.size(); ++t)
2847 {
2848 const Tri & tr = tris(t);
2849 if (not tr.alive) continue;
2850 if (tr.v[0] >= n or tr.v[1] >= n or tr.v[2] >= n) continue;
2851 if (orientation(pts(tr.v[0]), pts(tr.v[1]), pts(tr.v[2])) == Orientation::COLLINEAR)
2852 continue;
2853 ret.triangles.append(IndexedTriangle{tr.v[0], tr.v[1], tr.v[2]});
2854 }
2855
2856 return ret;
2857 }
2858
2859 [[nodiscard]] Result operator()(const std::initializer_list<Point> il) const
2860 {
2861 DynList<Point> points;
2862 for (const Point & p: il)
2863 points.append(p);
2864 return (*this)(points);
2865 }
2866 };
2867
2868 // ============================================================================
2869 // Constrained Delaunay Triangulation (CDT)
2870 // ============================================================================
2871
2897 {
2898 public:
2900
2902 {
2903 size_t u;
2904 size_t v;
2905 };
2906
2913
2914 private:
2915 static constexpr size_t NONE = ~static_cast<size_t>(0);
2916
2917 struct Tri
2918 {
2919 size_t v[3];
2920 size_t adj[3];
2921 bool constrained[3];
2922 bool alive;
2923 };
2924
2925 [[nodiscard]] static bool lexicographic_less(const Point & p1,
2926 const Point & p2)
2927 {
2928 if (p1.get_x() < p2.get_x())
2929 return true;
2930 if (p2.get_x() < p1.get_x())
2931 return false;
2932 return p1.get_y() < p2.get_y();
2933 }
2934
2936 [[nodiscard]] static size_t find_point_index(const Array<Point> & pts,
2937 const Point & p)
2938 {
2939 size_t lo = 0, hi = pts.size();
2940 while (lo < hi)
2941 if (const size_t mid = lo + (hi - lo) / 2; lexicographic_less(pts(mid), p))
2942 lo = mid + 1;
2943 else if (lexicographic_less(p, pts(mid)))
2944 hi = mid;
2945 else
2946 return mid;
2947 return NONE;
2948 }
2949
2951 [[nodiscard]] static size_t local_of(const Tri & t, const size_t id)
2952 {
2953 for (int i = 0; i < 3; ++i)
2954 if (t.v[i] == id) return static_cast<size_t>(i);
2955 return NONE;
2956 }
2957
2959 [[nodiscard]] static size_t adj_of(const Tri & t, const size_t n)
2960 {
2961 for (int i = 0; i < 3; ++i)
2962 if (t.adj[i] == n) return static_cast<size_t>(i);
2963 return NONE;
2964 }
2965
2968 [[nodiscard]] static size_t edge_opposite(const Tri & t,
2969 const size_t a, const size_t b)
2970 {
2971 for (int i = 0; i < 3; ++i)
2972 {
2973 const size_t e0 = t.v[(i + 1) % 3];
2974 const size_t e1 = t.v[(i + 2) % 3];
2975 if ((e0 == a and e1 == b) or (e0 == b and e1 == a))
2976 return static_cast<size_t>(i);
2977 }
2978 return NONE;
2979 }
2980
2984 {
2985 tris.reserve(dt_tris.size());
2986 for (size_t i = 0; i < dt_tris.size(); ++i)
2987 {
2988 const auto & t = dt_tris(i);
2989 tris.append(Tri{
2990 {t.i, t.j, t.k},
2991 {NONE, NONE, NONE},
2992 {false, false, false},
2993 true
2994 });
2995 }
2996
2998 dt_tris,
3000 edges,
3001 const size_t first, const size_t last)
3002 {
3003 if (last - first != 2)
3004 return; // boundary edge (1 tri) or degenerate
3005
3006 const auto & e0 = edges(first);
3007 const auto & e1 = edges(first + 1);
3008
3009 const size_t t0 = e0.tri;
3010 const size_t t1 = e1.tri;
3011 const size_t loc0 =
3012 edge_opposite(tris(t0), e0.u, e0.v);
3013 const size_t loc1 =
3014 edge_opposite(tris(t1), e1.u, e1.v);
3015
3016 if (loc0 != NONE)
3017 tris(t0).adj[loc0] = t1;
3018 if (loc1 != NONE)
3019 tris(t1).adj[loc1] = t0;
3020 });
3021 }
3022
3024 [[nodiscard]] static bool edge_exists(const Array<Tri> & tris,
3025 const size_t u, const size_t v,
3026 size_t & out_tri,
3027 size_t & out_local)
3028 {
3029 for (size_t t = 0; t < tris.size(); ++t)
3030 {
3031 if (not tris(t).alive)
3032 continue;
3033
3034 if (const size_t loc = edge_opposite(tris(t), u, v); loc != NONE)
3035 {
3036 out_tri = t;
3037 out_local = loc;
3038 return true;
3039 }
3040 }
3041 return false;
3042 }
3043
3050 [[nodiscard]] static bool is_convex_quad(const Array<Point> & pts,
3051 const size_t a, const size_t b,
3052 const size_t c, const size_t d)
3053 {
3054 const Orientation o1 = orientation(pts(a), pts(d), pts(b));
3055 const Orientation o2 = orientation(pts(a), pts(d), pts(c));
3057 return false;
3058 if (o1 == o2)
3059 return false;
3060
3061 const Orientation o3 = orientation(pts(b), pts(c), pts(a));
3062 const Orientation o4 = orientation(pts(b), pts(c), pts(d));
3064 return false;
3065 if (o3 == o4)
3066 return false;
3067
3068 return true;
3069 }
3070
3075 static void flip_edge(Array<Tri> & tris, const size_t tri_a,
3076 const size_t tri_b)
3077 {
3078 Tri & ta = tris(tri_a);
3079 Tri & tb = tris(tri_b);
3080
3081 // Identify the shared edge.
3082 const size_t la = adj_of(ta, tri_b);
3083 const size_t lb = adj_of(tb, tri_a);
3084 if (la == NONE or lb == NONE)
3085 return;
3086
3087 // Vertices: a_opp is opposite the shared edge in ta,
3088 // b_opp is opposite the shared edge in tb.
3089 const size_t a_opp = ta.v[la];
3090 const size_t b_opp = tb.v[lb];
3091 const size_t p = ta.v[(la + 1) % 3]; // shared edge start
3092 const size_t q = ta.v[(la + 2) % 3]; // shared edge end
3093
3094 // External neighbors before the flip.
3095 const size_t ext_a1 = ta.adj[(la + 1) % 3]; // neighbor opp p in ta
3096 const size_t ext_a2 = ta.adj[(la + 2) % 3]; // neighbor opp q in ta
3097 const size_t ext_b1 = tb.adj[(lb + 1) % 3]; // neighbor opp p_adj in tb
3098 const size_t ext_b2 = tb.adj[(lb + 2) % 3]; // neighbor opp q_adj in tb
3099
3100 // Constrained flags for external edges.
3101 const bool con_a1 = ta.constrained[(la + 1) % 3];
3102 const bool con_a2 = ta.constrained[(la + 2) % 3];
3103 const bool con_b1 = tb.constrained[(lb + 1) % 3];
3104 const bool con_b2 = tb.constrained[(lb + 2) % 3];
3105
3106 // Identify which external neighbors of tb go with which new triangle.
3107 // tb has vertices: b_opp, p', q' where p' = tb.v[(lb+1)%3], q' = tb.v[(lb+2)%3]
3108 // p' and q' are p and q (in some order).
3109 const size_t bp = tb.v[(lb + 1) % 3]; // one of {p, q}
3110 const size_t bq = tb.v[(lb + 2) % 3]; // the other
3111
3112 // New triangle A: (a_opp, b_opp, q) — uses new diagonal + edge (b_opp, q)
3113 // New triangle B: (b_opp, a_opp, p) — uses new diagonal + edge (a_opp, p)
3114 // Ensure CCW winding.
3115
3116 // After flip, new triangles share edge (a_opp, b_opp).
3117 // tri_a becomes (a_opp, b_opp, q) [CCW if original was CCW]
3118 // tri_b becomes (b_opp, a_opp, p) [CCW if original was CCW]
3119
3120 // New tri_a = (a_opp, b_opp, q):
3121 // - adj[0] opp a_opp = edge(b_opp, q) — this was in tb between b_opp and q
3122 // - adj[1] opp b_opp = edge(q, a_opp) — this was in ta between q and a_opp
3123 // - adj[2] opp q = tri_b (new diagonal)
3124 // New tri_b = (b_opp, a_opp, p):
3125 // - adj[0] opp b_opp = edge(a_opp, p) — this was in ta between a_opp and p
3126 // - adj[1] opp a_opp = edge(p, b_opp) — this was in tb between p and b_opp
3127 // - adj[2] opp p = tri_a (new diagonal)
3128
3129 // Figure out external neighbors and constrained flags.
3130 // From ta: edge opp (la+1)%3 i.e., opp ta.v[(la+1)%3] = opp p = edge(a_opp, q)
3131 // edge opp (la+2)%3 i.e., opp ta.v[(la+2)%3] = opp q = edge(p, a_opp)
3132 // So: ext_a1 is neighbor across edge(a_opp, q), ext_a2 across edge(p, a_opp).
3133 //
3134 // From tb: bp = tb.v[(lb+1)%3], bq = tb.v[(lb+2)%3]
3135 // ext_b1 is neighbor across edge(b_opp, bq), opp bp
3136 // ext_b2 is neighbor across edge(bp, b_opp), opp bq
3137
3138 // New tri_a = (a_opp, b_opp, q):
3139 // adj[0] opp a_opp = edge(b_opp, q): this is from tb. If bq==q, ext_b1; if bp==q, ext_b2
3140 // adj[1] opp b_opp = edge(q, a_opp): this is ext_a1
3141 // adj[2] opp q = tri_b
3142 size_t new_a_adj0;
3143 bool new_a_con0;
3144
3145 const size_t new_a_adj1 = ext_a1; // edge(q, a_opp) was opp p in ta
3146 const bool new_a_con1 = con_a1;
3147 const size_t new_a_adj2 = tri_b;
3148 constexpr bool new_a_con2 = false; // the new diagonal is not constrained
3149
3150 if (bq == q)
3151 {
3152 new_a_adj0 = ext_b1; // edge(b_opp, q) was opp bp in tb
3154 }
3155 else
3156 {
3157 new_a_adj0 = ext_b2; // edge(b_opp, q) was opp bq in tb, so bp==q
3159 }
3160
3161 // New tri_b = (b_opp, a_opp, p):
3162 // adj[0] opp b_opp = edge(a_opp, p): this is ext_a2
3163 // adj[1] opp a_opp = edge(p, b_opp): from tb. If bp==p, ext_b2; if bq==p, ext_b1
3164 // adj[2] opp p = tri_a
3165 size_t new_b_adj1;
3166 bool new_b_con1;
3167
3168 const size_t new_b_adj0 = ext_a2; // edge(p, a_opp) was opp q in ta
3169 const bool new_b_con0 = con_a2;
3170 const size_t new_b_adj2 = tri_a;
3171 constexpr bool new_b_con2 = false; // new diagonal
3172
3173 if (bp == p)
3174 {
3175 new_b_adj1 = ext_b2; // edge(p, b_opp) was opp bq in tb
3177 }
3178 else
3179 {
3180 new_b_adj1 = ext_b1; // edge(p, b_opp) was opp bp in tb, bq==p
3182 }
3183
3184 // Write new triangles.
3185 ta.v[0] = a_opp;
3186 ta.v[1] = b_opp;
3187 ta.v[2] = q;
3188 ta.adj[0] = new_a_adj0;
3189 ta.adj[1] = new_a_adj1;
3190 ta.adj[2] = new_a_adj2;
3191 ta.constrained[0] = new_a_con0;
3192 ta.constrained[1] = new_a_con1;
3193 ta.constrained[2] = new_a_con2;
3194
3195 tb.v[0] = b_opp;
3196 tb.v[1] = a_opp;
3197 tb.v[2] = p;
3198 tb.adj[0] = new_b_adj0;
3199 tb.adj[1] = new_b_adj1;
3200 tb.adj[2] = new_b_adj2;
3201 tb.constrained[0] = new_b_con0;
3202 tb.constrained[1] = new_b_con1;
3203 tb.constrained[2] = new_b_con2;
3204
3205 // Remap external neighbors to point to correct new triangle.
3206 if (new_a_adj0 != NONE)
3207 if (const size_t li = adj_of(tris(new_a_adj0), tri_b); li != NONE) tris(new_a_adj0).adj[li] = tri_a;
3208 if (new_b_adj0 != NONE)
3209 if (const size_t li = adj_of(tris(new_b_adj0), tri_a); li != NONE) tris(new_b_adj0).adj[li] = tri_b;
3210 if (new_b_adj1 != NONE)
3211 if (const size_t li = adj_of(tris(new_b_adj1), tri_a); li != NONE) tris(new_b_adj1).adj[li] = tri_b;
3212 if (new_a_adj1 != NONE)
3213 if (const size_t li = adj_of(tris(new_a_adj1), tri_b); li != NONE) tris(new_a_adj1).adj[li] = tri_a;
3214 }
3215
3219 {
3220 size_t tri;
3221 size_t local;
3222 };
3223
3226 const size_t u, const size_t v)
3227 {
3229 const Segment seg_uv(pts(u), pts(v));
3230
3231 for (size_t t = 0; t < tris.size(); ++t)
3232 {
3233 if (not tris(t).alive)
3234 continue;
3235
3236 for (int e = 0; e < 3; ++e)
3237 {
3238 const size_t w1 = tris(t).v[(e + 1) % 3];
3239 const size_t w2 = tris(t).v[(e + 2) % 3];
3240
3241 // Skip edges incident to u or v (they can't "cross").
3242 if (w1 == u or w1 == v or w2 == u or w2 == v)
3243 continue;
3244
3245 // Only process each undirected edge once (from the triangle
3246 // with the smaller index, or from the boundary side).
3247 const size_t nb = tris(t).adj[e];
3248 if (nb != NONE and nb < t)
3249 continue;
3250
3252 {
3253 crossings.append(CrossingEdge{t, static_cast<size_t>(e)});
3254 // Also record from the neighbor's side.
3255 if (nb != NONE and tris(nb).alive)
3256 if (const size_t lb = adj_of(tris(nb), t); lb != NONE)
3257 crossings.append(CrossingEdge{nb, lb});
3258 }
3259 }
3260 }
3261
3262 return crossings;
3263 }
3264
3267 const size_t u, const size_t v)
3268 {
3269 for (size_t t = 0; t < tris.size(); ++t)
3270 {
3271 if (not tris(t).alive)
3272 continue;
3273
3274 if (const size_t loc = edge_opposite(tris(t), u, v); loc != NONE)
3275 tris(t).constrained[loc] = true;
3276 }
3277 }
3278
3281 const size_t u, const size_t v)
3282 {
3283 // Check if edge already in mesh.
3284 size_t et, el;
3285 if (edge_exists(tris, u, v, et, el))
3286 {
3287 mark_constrained(tris, u, v);
3288 return;
3289 }
3290
3291 // Find crossing edges and flip them.
3292 const size_t max_iterations = tris.size() * 4 + 100;
3293 for (size_t iter = 0; iter < max_iterations; ++iter)
3294 {
3295 // Check if the edge now exists.
3296 if (edge_exists(tris, u, v, et, el))
3297 {
3298 mark_constrained(tris, u, v);
3299 return;
3300 }
3301
3302 Array<CrossingEdge> crossings = find_crossing_edges(pts, tris, u, v);
3303 if (crossings.is_empty())
3304 {
3305 // Edge should exist now, or no crossings found.
3306 if (edge_exists(tris, u, v, et, el))
3307 mark_constrained(tris, u, v);
3308 return;
3309 }
3310
3311 bool flipped_any = false;
3312 for (size_t ci = 0; ci < crossings.size(); ++ci)
3313 {
3314 const size_t tri_idx = crossings(ci).tri;
3315 const size_t loc_idx = crossings(ci).local;
3316
3317 if (not tris(tri_idx).alive)
3318 continue;
3319
3320 if (tris(tri_idx).constrained[loc_idx])
3321 continue; // don't flip constrained edges
3322
3323 const size_t nb = tris(tri_idx).adj[loc_idx];
3324 if (nb == NONE or not tris(nb).alive)
3325 continue;
3326
3327 // Get the four vertices of the quadrilateral.
3328 const size_t a = tris(tri_idx).v[loc_idx]; // opposite vertex in tri_idx
3329 const size_t lb = adj_of(tris(nb), tri_idx);
3330 if (lb == NONE)
3331 continue;
3332 const size_t d = tris(nb).v[lb]; // opposite vertex in nb
3333 const size_t b = tris(tri_idx).v[(loc_idx + 1) % 3];
3334 const size_t c = tris(tri_idx).v[(loc_idx + 2) % 3];
3335
3336 if (is_convex_quad(pts, a, b, c, d))
3337 {
3338 flip_edge(tris, tri_idx, nb);
3339 flipped_any = true;
3340 break; // restart since crossings changed
3341 }
3342 }
3343
3344 if (not flipped_any)
3345 {
3346 // Try flipping any crossing edge even non-convex (won't flip but
3347 // we break infinite loop).
3348 break;
3349 }
3350 }
3351
3352 // Final check — mark if the edge appeared.
3353 if (edge_exists(tris, u, v, et, el))
3354 mark_constrained(tris, u, v);
3355 }
3356
3359 {
3360 // Collect all non-constrained interior edges.
3361 // Use a simple iterative approach: keep flipping until no more flips.
3362 bool changed = true;
3363 const size_t max_passes = tris.size() * 4 + 100;
3364 size_t pass = 0;
3365
3366 while (changed and pass++ < max_passes)
3367 {
3368 changed = false;
3369
3370 for (size_t t = 0; t < tris.size(); ++t)
3371 {
3372 if (not tris(t).alive)
3373 continue;
3374
3375 for (int e = 0; e < 3; ++e)
3376 {
3377 if (tris(t).constrained[e])
3378 continue; // don't flip constrained edges
3379
3380 const size_t nb = tris(t).adj[e];
3381 if (nb == NONE or nb <= t) // process each pair once
3382 continue;
3383 if (not tris(nb).alive)
3384 continue;
3385
3386 // in-circle test: is the opposite vertex of nb inside
3387 // the circumcircle of t?
3388 const size_t lb = adj_of(tris(nb), t);
3389 if (lb == NONE)
3390 continue;
3391
3392 const size_t d = tris(nb).v[lb]; // opposite vertex in nb
3393
3394 // Check CCW orientation of triangle t
3395 const Orientation o = orientation(pts(tris(t).v[0]),
3396 pts(tris(t).v[1]),
3397 pts(tris(t).v[2]));
3399 continue;
3400
3402 pts(tris(t).v[0]), pts(tris(t).v[1]),
3403 pts(tris(t).v[2]), pts(d));
3404
3405 bool violates = false;
3406 if (o == Orientation::CCW and det > 0)
3407 violates = true;
3408 else if (o == Orientation::CW and det < 0)
3409 violates = true;
3410
3411 if (not violates)
3412 continue;
3413
3414 // Check convexity before flipping.
3415 const size_t a = tris(t).v[e];
3416 const size_t b = tris(t).v[(e + 1) % 3];
3417 const size_t c = tris(t).v[(e + 2) % 3];
3418
3419 if (is_convex_quad(pts, a, b, c, d))
3420 {
3421 flip_edge(tris, t, nb);
3422 changed = true;
3423 }
3424 }
3425 }
3426 }
3427 }
3428
3431 [[nodiscard]] static Array<Point>
3434 {
3436 for (DynList<Point>::Iterator it(points); it.has_curr(); it.next_ne())
3437 all.append(it.get_curr());
3438
3439 // Collect constraint segments into an array for pairwise checks.
3441 for (DynList<Segment>::Iterator it(constraints); it.has_curr(); it.next_ne())
3442 {
3443 const Segment & s = it.get_curr();
3444 all.append(s.get_src_point());
3445 all.append(s.get_tgt_point());
3446 con_arr.append(s);
3447 }
3448
3449 // Compute pairwise intersection points of constraints.
3450 for (size_t i = 0; i < con_arr.size(); ++i)
3451 for (size_t j = i + 1; j < con_arr.size(); ++j)
3452 if (con_arr(i).intersects_properly_with(con_arr(j)))
3454
3455 quicksort_op(all, [](const Point & a, const Point & b)
3456 {
3457 return lexicographic_less(a, b);
3458 });
3459
3461 ret.reserve(all.size());
3462 for (size_t i = 0; i < all.size(); ++i)
3463 if (ret.is_empty() or ret.get_last() != all(i))
3464 ret.append(all(i));
3465
3466 return ret;
3467 }
3468
3470 [[nodiscard]] static Array<IndexedEdge>
3473 {
3474 Array<IndexedEdge> result;
3475
3476 for (DynList<Segment>::Iterator it(constraints); it.has_curr(); it.next_ne())
3477 {
3478 const Segment & seg = it.get_curr();
3479 const size_t u = find_point_index(sites, seg.get_src_point());
3480 const size_t v = find_point_index(sites, seg.get_tgt_point());
3481
3482 if (u == NONE or v == NONE or u == v)
3483 continue;
3484
3485 // Find interior collinear points and split.
3487 chain.append(u);
3488 for (size_t i = 0; i < sites.size(); ++i)
3489 {
3490 if (i == u or i == v)
3491 continue;
3492 if (seg.contains(sites(i)))
3493 chain.append(i);
3494 }
3495 chain.append(v);
3496
3497 // Sort chain by distance from u.
3498 if (chain.size() > 2)
3499 {
3500 const Point & pu = sites(u);
3501 quicksort_op(chain, [&](const size_t a, const size_t b)
3502 {
3503 const Geom_Number da =
3504 (sites(a).get_x() - pu.get_x()) *
3505 (sites(a).get_x() - pu.get_x()) +
3506 (sites(a).get_y() - pu.get_y()) *
3507 (sites(a).get_y() - pu.get_y());
3508 const Geom_Number db =
3509 (sites(b).get_x() - pu.get_x()) *
3510 (sites(b).get_x() - pu.get_x()) +
3511 (sites(b).get_y() - pu.get_y()) *
3512 (sites(b).get_y() - pu.get_y());
3513 return da < db;
3514 });
3515 }
3516
3517 for (size_t i = 0; i + 1 < chain.size(); ++i)
3518 {
3519 const size_t a = chain(i) < chain(i + 1) ? chain(i) : chain(i + 1);
3520 const size_t b = chain(i) < chain(i + 1) ? chain(i + 1) : chain(i);
3521 // Avoid duplicates.
3522 bool dup = false;
3523 for (size_t j = 0; j < result.size(); ++j)
3524 if (result(j).u == a and result(j).v == b)
3525 {
3526 dup = true;
3527 break;
3528 }
3529 if (not dup)
3530 result.append(IndexedEdge{a, b});
3531 }
3532 }
3533
3534 return result;
3535 }
3536
3537 public:
3546 const DynList<Segment> & constraints) const
3547 {
3548 Result ret;
3549
3550 // 1. Merge and deduplicate.
3551 ret.sites = merge_and_deduplicate(points, constraints);
3552 const size_t n = ret.sites.size();
3553
3554 if (n < 3)
3555 return ret;
3556
3557 // Check all-collinear.
3558 {
3559 bool collinear = true;
3560 for (size_t i = 2; i < n and collinear; ++i)
3561 if (orientation(ret.sites(0), ret.sites(1), ret.sites(i))
3563 collinear = false;
3564 if (collinear)
3565 return ret;
3566 }
3567
3568 // 2. Compute unconstrained DT.
3570 for (size_t i = 0; i < n; ++i)
3571 site_list.append(ret.sites(i));
3572
3574 auto [sites, triangles] = dt_algo(site_list);
3575
3576 if (triangles.is_empty())
3577 return ret;
3578
3579 // The DT may have reordered sites. We need to use its sites array
3580 // and remap. Since both are sorted/deduped, they should match.
3581 ret.sites = sites;
3582
3583 // 3. Build adjacency mesh.
3585 build_adjacency(tris, triangles);
3586
3587 // 4. Map constraints to index pairs (with splitting).
3589 map_constraints(ret.sites, constraints);
3590
3591 // 5. Enforce each constraint.
3592 for (size_t i = 0; i < indexed_constraints.size(); ++i)
3593 enforce_constraint(ret.sites, tris, indexed_constraints(i).u,
3594 indexed_constraints(i).v);
3595
3596 // 6. Lawson flip pass.
3597 lawson_flip(ret.sites, tris);
3598
3599 // 7. Extract result.
3600 ret.triangles.reserve(tris.size());
3601 for (size_t t = 0; t < tris.size(); ++t)
3602 {
3603 if (not tris(t).alive)
3604 continue;
3605 if (orientation(ret.sites(tris(t).v[0]),
3606 ret.sites(tris(t).v[1]),
3607 ret.sites(tris(t).v[2])) == Orientation::COLLINEAR)
3608 continue;
3609 ret.triangles.append(IndexedTriangle{
3610 tris(t).v[0], tris(t).v[1],
3611 tris(t).v[2]
3612 });
3613 }
3614
3615 // Collect constrained edges.
3616 ret.constrained_edges.reserve(indexed_constraints.size());
3617 for (size_t i = 0; i < indexed_constraints.size(); ++i)
3618 ret.constrained_edges.append(indexed_constraints(i));
3619
3620 return ret;
3621 }
3622
3626 [[nodiscard]] Result operator()(const std::initializer_list<Point> points,
3627 const std::initializer_list<Segment> constraints) const
3628 {
3630 for (const Point & p: points)
3631 pts.append(p);
3632
3634 for (const Segment & s: constraints)
3635 segs.append(s);
3636
3637 return (*this)(pts, segs);
3638 }
3639
3644 {
3646 for (size_t i = 0; i < result.triangles.size(); ++i)
3647 {
3648 const auto & [ii, j, k] = result.triangles(i);
3649 out.append(Triangle(result.sites(ii),
3650 result.sites(j),
3651 result.sites(k)));
3652 }
3653 return out;
3654 }
3655 };
3656
3657 // ============================================================================
3658 // Voronoi Diagram (Dual of Delaunay)
3659 // ============================================================================
3660
3682 {
3683 public:
3696
3707
3717
3728
3729 private:
3731
3735 [[nodiscard]] static Point circumcenter(const Point & a,
3736 const Point & b,
3737 const Point & c)
3738 {
3739 const Geom_Number & ax = a.get_x();
3740 const Geom_Number & ay = a.get_y();
3741 const Geom_Number & bx = b.get_x();
3742 const Geom_Number & by = b.get_y();
3743 const Geom_Number & cx = c.get_x();
3744 const Geom_Number & cy = c.get_y();
3745
3746 const Geom_Number a2 = ax * ax + ay * ay;
3747 const Geom_Number b2 = bx * bx + by * by;
3748 const Geom_Number c2 = cx * cx + cy * cy;
3749
3750 const Geom_Number d = ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
3751 ah_domain_error_if(d == 0) << "Circumcenter undefined for collinear points";
3752 const Geom_Number den = d + d;
3753
3754 const Geom_Number ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / den;
3755 const Geom_Number uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / den;
3756
3757 return {ux, uy};
3758 }
3759
3764 {
3765 ah_domain_error_if(not p.is_closed()) << "Polygon must be closed";
3766 ah_domain_error_if(p.size() < 3) << "Polygon must have at least 3 vertices";
3767
3769 }
3770
3774 [[nodiscard]] static bool is_convex(const Array<Point> & verts)
3775 {
3776 if (verts.size() < 3)
3777 return false;
3779 }
3780
3786 {
3787 const Point mid((s.get_x() + t.get_x()) / 2,
3788 (s.get_y() + t.get_y()) / 2);
3789
3790 const Geom_Number dx = t.get_x() - s.get_x();
3791 const Geom_Number dy = t.get_y() - s.get_y();
3792 const Point q(mid.get_x() - dy, mid.get_y() + dx);
3793
3794 return {mid, q}; // the left side contains s
3795 }
3796
3797 [[nodiscard]] static Array<ClippedCell>
3799 {
3800 ah_domain_error_if(sites.size() != polys.size())
3801 << "Sites and clipped polygons size mismatch";
3802
3804 ret.reserve(sites.size());
3805
3806 for (size_t i = 0; i < sites.size(); ++i)
3807 {
3808 ClippedCell cell;
3809 cell.site_index = i;
3810 cell.site = sites(i);
3811 cell.polygon = polys(i);
3812 ret.append(std::move(cell));
3813 }
3814
3815 return ret;
3816 }
3817
3818 public:
3825 [[nodiscard]] Result
3827 {
3828 Result ret;
3829 ret.sites = dt.sites;
3830
3831 if (dt.triangles.is_empty())
3832 return ret;
3833
3835 centers.reserve(dt.triangles.size());
3836 for (size_t idx = 0; idx < dt.triangles.size(); ++idx)
3837 {
3838 const auto & [i, j, k] = dt.triangles(idx);
3839 centers.append(circumcenter(dt.sites(i), dt.sites(j), dt.sites(k)));
3840 }
3841 ret.vertices = centers;
3842
3844 on_hull.reserve(dt.sites.size());
3845 for (size_t i = 0; i < dt.sites.size(); ++i)
3846 on_hull.append(0);
3847
3849 dt.triangles,
3851 edges,
3852 const size_t first, const size_t last)
3853 {
3854 if (const size_t cnt = last - first; cnt >= 2)
3855 {
3856 const Point & p1 = centers(edges(first).tri);
3857 const Point & p2 = centers(edges(first + 1).tri);
3858 ret.edges.append(Edge{
3859 edges(first).u, edges(first).v,
3860 p1, p2, false, Point()
3861 });
3862 return;
3863 }
3864
3865 const auto & edge = edges(first);
3866 const size_t u = edge.u;
3867 const size_t v = edge.v;
3868 const size_t tri = edge.tri;
3869 const size_t third = edge.third;
3870 on_hull(u) = 1;
3871 on_hull(v) = 1;
3872
3873 const Point & pu = dt.sites(u);
3874 const Point & pv = dt.sites(v);
3875 const Point & pw = dt.sites(third);
3876
3877 const Geom_Number ex = pv.get_x() - pu.get_x();
3878 const Geom_Number ey = pv.get_y() - pu.get_y();
3879
3880 Geom_Number dirx = -ey;
3883 {
3884 dirx = ey;
3885 diry = -ex;
3886 }
3887
3888 const Point & src = centers(tri);
3889 ret.edges.append(Edge{
3890 u, v, src, src, true, Point(dirx, diry)
3891 });
3892 });
3893
3894 // Pre-build site -> incident triangles index (O(T) instead of O(n*T)).
3896 incidence.reserve(dt.sites.size());
3897 for (size_t s = 0; s < dt.sites.size(); ++s)
3898 incidence.append(Array<size_t>());
3899 for (size_t t = 0; t < dt.triangles.size(); ++t)
3900 {
3901 const auto & [ti, tj, tk] = dt.triangles(t);
3902 incidence(ti).append(t);
3903 incidence(tj).append(t);
3904 incidence(tk).append(t);
3905 }
3906
3907 ret.cells.reserve(dt.sites.size());
3908 for (size_t s = 0; s < dt.sites.size(); ++s)
3909 {
3912 for (size_t idx = 0; idx < incidence(s).size(); ++idx)
3913 verts.append(centers(incidence(s)(idx)));
3914
3915 const Point site = dt.sites(s);
3916 if (verts.size() > 1)
3917 {
3918 quicksort_op(verts, [&site](const Point & a, const Point & b)
3919 {
3920 const Geom_Number ax = a.get_x() - site.get_x();
3921 const Geom_Number ay = a.get_y() - site.get_y();
3922 const Geom_Number bx = b.get_x() - site.get_x();
3923 const Geom_Number by = b.get_y() - site.get_y();
3924
3925 const bool au = (ay > 0) or (ay == 0 and ax >= 0);
3926 const bool bu = (by > 0) or (by == 0 and bx >= 0);
3927 if (au != bu)
3928 return au and not bu;
3929
3930 if (const Geom_Number cr = ax * by - ay * bx; cr != 0)
3931 return cr > 0;
3932
3933 return (ax * ax + ay * ay) < (bx * bx + by * by);
3934 });
3935 }
3936
3938 clean.reserve(verts.size());
3939 for (size_t k = 0; k < verts.size(); ++k)
3940 if (clean.is_empty() or clean.get_last() != verts(k))
3941 clean.append(verts(k));
3942 if (clean.size() > 1 and clean(0) == clean.get_last())
3943 static_cast<void>(clean.remove_last());
3944
3945 Cell cell;
3946 cell.site_index = s;
3947 cell.site = site;
3948 cell.bounded = on_hull(s) == 0;
3949 cell.vertices = clean;
3950 ret.cells.append(std::move(cell));
3951 }
3952
3953 return ret;
3954 }
3955
3962 [[nodiscard]] Result operator ()(const DynList<Point> & point_set) const
3963 {
3964 return (*this)(delaunay(point_set));
3965 }
3966
3973 [[nodiscard]] Result operator ()(const std::initializer_list<Point> il) const
3974 {
3975 return (*this)(delaunay(il));
3976 }
3977
3991 const Polygon & clip)
3992 {
3993 const Array<Point> clip_verts = extract_vertices(clip);
3994 ah_domain_error_if(not is_convex(clip_verts)) << "Clip polygon must be convex";
3995
3998
3999 const size_t n = sites.size();
4000
4001 // Compute Delaunay to get adjacency: each site's Voronoi cell is
4002 // bounded only by bisectors with its Delaunay neighbors (~6 on avg),
4003 // reducing total complexity from O(n^2 log n) to O(n log n).
4006 for (size_t i = 0; i < n; ++i)
4007 site_list.append(sites(i));
4008 auto dt = dt_algo(site_list);
4009
4010 // Map Delaunay site indices back to input indices.
4011 // dt.sites may be reordered/deduped, so match by position.
4013 dt_to_input.reserve(dt.sites.size());
4014 for (size_t di = 0; di < dt.sites.size(); ++di)
4015 {
4016 size_t match = 0;
4017 for (size_t oi = 0; oi < n; ++oi)
4018 if (dt.sites(di) == sites(oi))
4019 {
4020 match = oi;
4021 break;
4022 }
4023 dt_to_input.append(match);
4024 }
4025
4026 // Build adjacency lists from Delaunay triangles (in input-index space)
4028 for (size_t i = 0; i < n; ++i)
4030 for (size_t t = 0; t < dt.triangles.size(); ++t)
4031 {
4032 const size_t a = dt_to_input(dt.triangles(t).i);
4033 const size_t b = dt_to_input(dt.triangles(t).j);
4034 const size_t c = dt_to_input(dt.triangles(t).k);
4035 adj(a).insert(b);
4036 adj(a).insert(c);
4037 adj(b).insert(a);
4038 adj(b).insert(c);
4039 adj(c).insert(a);
4040 adj(c).insert(b);
4041 }
4042
4044 ret.reserve(n);
4045
4046 for (size_t i = 0; i < n; ++i)
4047 {
4050
4051 adj(i).for_each([&](size_t j)
4052 {
4053 if (sites(j) != sites(i))
4054 hps.append(bisector_halfplane_for_site(sites(i), sites(j)));
4055 });
4056
4057 // If a site has no Delaunay neighbors (degenerate case),
4058 // fall back to the clip polygon alone.
4059 ret.append(hpi(hps));
4060 }
4061
4062 return ret;
4063 }
4064
4073 const Polygon & clip)
4074 {
4075 return clipped_cells(vor.sites, clip);
4076 }
4077
4086 const Polygon & clip) const
4087 {
4088 return clipped_cells(delaunay(point_set).sites, clip);
4089 }
4090
4099 clipped_cells(const std::initializer_list<Point> il, const Polygon & clip) const
4100 {
4101 return clipped_cells(delaunay(il).sites, clip);
4102 }
4103
4111 static Array<ClippedCell>
4113 {
4114 return indexed_clipped_cells(sites, clipped_cells(sites, clip));
4115 }
4116
4124 [[nodiscard]] static Array<ClippedCell>
4126 {
4127 return clipped_cells_indexed(vor.sites, clip);
4128 }
4129
4139 {
4140 return clipped_cells_indexed(delaunay(point_set).sites, clip);
4141 }
4142
4151 clipped_cells_indexed(const std::initializer_list<Point> il,
4152 const Polygon & clip) const
4153 {
4154 return clipped_cells_indexed(delaunay(il).sites, clip);
4155 }
4156 };
4157
4158 // ============================================================================
4159 // Voronoi Diagram — O(n log n) via Incremental Delaunay Dual
4160 // ============================================================================
4161
4199 {
4202
4203 public:
4208
4215 {
4216 const auto dt = delaunay_(pts);
4217 return voronoi_(dt);
4218 }
4219
4225 [[nodiscard]] Result operator()(const std::initializer_list<Point> il) const
4226 {
4228 for (const Point & p: il)
4229 pts.append(p);
4230 return (*this)(pts);
4231 }
4232
4241 {
4242 auto [sites, triangles] = delaunay_(pts);
4244 }
4245 };
4246
4262 {
4263 public:
4268
4269 private:
4270 struct Arc;
4271
4275 struct Event
4276 {
4277 double y = 0.0;
4278 double x = 0.0;
4279 bool is_site = false;
4280 size_t site = 0;
4281 Arc *arc = nullptr;
4282 size_t id = 0;
4283 bool valid = true;
4284 };
4285
4289 struct Arc
4290 {
4291 size_t site = 0;
4292 Arc *prev = nullptr;
4293 Arc *next = nullptr;
4294 Event *circle = nullptr;
4295 };
4296
4301 {
4302 bool operator ()(const Event *a, const Event *b) const
4303 {
4304 // DynBinHeap is a min-heap, so define the smallest key as the
4305 // highest-priority event: max y, then min x, site before circle, then
4306 // smallest id.
4307 if (a->y != b->y) return a->y > b->y; // larger y has smaller key
4308 if (a->x != b->x) return a->x < b->x; // smaller x has smaller key
4309 if (a->is_site != b->is_site) return a->is_site > b->is_site; // site (0) < circle (1)
4310 return a->id < b->id;
4311 }
4312 };
4313
4317 struct TriKey
4318 {
4319 size_t a = 0;
4320 size_t b = 0;
4321 size_t c = 0;
4322
4323 bool operator <(const TriKey & o) const
4324 {
4325 if (a != o.a) return a < o.a;
4326 if (b != o.b) return b < o.b;
4327 return c < o.c;
4328 }
4329 };
4330
4331 static constexpr double kEps = 1e-9;
4332
4335
4336 [[nodiscard]] static double as_double(const Geom_Number & v)
4337 {
4338 return geom_number_to_double(v);
4339 }
4340
4341 [[nodiscard]] static bool all_collinear(const Array<Point> & pts)
4342 {
4343 if (pts.size() < 3)
4344 return true;
4345
4346 for (size_t i = 2; i < pts.size(); ++i)
4347 if (orientation(pts(0), pts(1), pts(i)) != Orientation::COLLINEAR)
4348 return false;
4349
4350 return true;
4351 }
4352
4354 normalized_triangle(const size_t i, const size_t j, const size_t k,
4355 const Array<Point> & sites)
4356 {
4358 if (orientation(sites(t.i), sites(t.j), sites(t.k)) == Orientation::CW)
4359 std::swap(t.j, t.k);
4360 return t;
4361 }
4362
4363 [[nodiscard]] static double breakpoint_x(const Point & left_site,
4364 const Point & right_site,
4365 const double sweepline_y)
4366 {
4367 const double px = as_double(left_site.get_x());
4368 const double py = as_double(left_site.get_y());
4369 const double qx = as_double(right_site.get_x());
4370 const double qy = as_double(right_site.get_y());
4371
4372 if (std::fabs(py - qy) <= kEps)
4373 return (px + qx) / 2.0;
4374 if (std::fabs(py - sweepline_y) <= kEps)
4375 return px;
4376 if (std::fabs(qy - sweepline_y) <= kEps)
4377 return qx;
4378
4379 const double z0 = 2.0 * (py - sweepline_y);
4380 const double z1 = 2.0 * (qy - sweepline_y);
4381
4382 const double a = 1.0 / z0 - 1.0 / z1;
4383 const double b = -2.0 * (px / z0 - qx / z1);
4384 const double c = (px * px + py * py - sweepline_y * sweepline_y) / z0 -
4385 (qx * qx + qy * qy - sweepline_y * sweepline_y) / z1;
4386
4387 if (std::fabs(a) <= kEps)
4388 return -c / b;
4389
4390 double disc = b * b - 4.0 * a * c;
4392 disc = 0.0;
4393 ah_domain_error_if(disc < 0.0) << "Negative discriminant in Fortune breakpoint";
4394
4395 const double sq = std::sqrt(disc);
4396 const double x1 = (-b - sq) / (2.0 * a);
4397 const double x2 = (-b + sq) / (2.0 * a);
4398 return py < qy ? std::max(x1, x2) : std::min(x1, x2);
4399 }
4400
4401 [[nodiscard]] static Arc *
4402 locate_arc(Arc *head, const Array<Point> & sites,
4403 const double x, const double sweepline_y)
4404 {
4405 if (head == nullptr)
4406 return nullptr;
4407
4408 for (Arc *arc = head; arc != nullptr; arc = arc->next)
4409 {
4410 const double left = arc->prev == nullptr ?
4411 -std::numeric_limits<double>::infinity() :
4412 breakpoint_x(sites(arc->prev->site), sites(arc->site), sweepline_y);
4413 const double right = arc->next == nullptr ?
4414 std::numeric_limits<double>::infinity() :
4415 breakpoint_x(sites(arc->site), sites(arc->next->site), sweepline_y);
4416
4417 if (x >= left - kEps and x <= right + kEps)
4418 return arc;
4419 }
4420
4421 Arc *tail = head;
4422 while (tail->next != nullptr)
4423 tail = tail->next;
4424 return tail;
4425 }
4426
4428 {
4429 if (arc != nullptr and arc->circle != nullptr)
4430 {
4431 arc->circle->valid = false;
4432 arc->circle = nullptr;
4433 }
4434 }
4435
4436 static void enqueue_circle_event(Arc *arc,
4437 const double sweepline_y,
4438 const Array<Point> & sites,
4440 Array<std::unique_ptr<Event>> & event_pool,
4441 size_t & next_event_id)
4442 {
4444
4445 if (arc == nullptr or arc->prev == nullptr or arc->next == nullptr)
4446 return;
4447
4448 const size_t ia = arc->prev->site;
4449 const size_t ib = arc->site;
4450 const size_t ic = arc->next->site;
4451 if (ia == ib or ib == ic or ia == ic)
4452 return;
4453
4454 const Point & a = sites(ia);
4455 const Point & b = sites(ib);
4456 const Point & c = sites(ic);
4457
4458 if (orientation(a, b, c) != Orientation::CW)
4459 return;
4460
4461 const double ax = as_double(a.get_x());
4462 const double ay = as_double(a.get_y());
4463 const double bx = as_double(b.get_x());
4464 const double by = as_double(b.get_y());
4465 const double cx = as_double(c.get_x());
4466 const double cy = as_double(c.get_y());
4467
4468 const double det = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
4469 if (std::fabs(det) <= kEps)
4470 return;
4471
4472 const double a2 = ax * ax + ay * ay;
4473 const double b2 = bx * bx + by * by;
4474 const double c2 = cx * cx + cy * cy;
4475
4476 const double ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / det;
4477 const double uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / det;
4478
4479 const double r = std::hypot(ax - ux, ay - uy);
4480 const double event_y = uy - r;
4481 if (event_y >= sweepline_y - kEps)
4482 return;
4483
4484 event_pool.append(std::make_unique<Event>());
4485 Event *ev = event_pool.get_last().get();
4486 ev->y = event_y;
4487 ev->x = ux;
4488 ev->is_site = false;
4489 ev->site = 0;
4490 ev->arc = arc;
4491 ev->id = next_event_id++;
4492 ev->valid = true;
4493
4494 arc->circle = ev;
4495 queue.put(ev);
4496 }
4497
4498 [[nodiscard]] static bool
4500 {
4501 for (size_t t = 0; t < dt.triangles.size(); ++t)
4502 {
4503 const auto & [i, j, k] = dt.triangles(t);
4504 const Point & a = dt.sites(i);
4505 const Point & b = dt.sites(j);
4506 const Point & c = dt.sites(k);
4507 const Orientation o = orientation(a, b, c);
4509 return false;
4510
4511 for (size_t p = 0; p < dt.sites.size(); ++p)
4512 {
4513 if (p == i or p == j or p == k)
4514 continue;
4515
4516 if (const Geom_Number det = in_circle_determinant(a, b, c, dt.sites(p));
4517 (o == Orientation::CCW and det > 0) or (o == Orientation::CW and det < 0))
4518 return false;
4519 }
4520 }
4521
4522 return true;
4523 }
4524
4527 {
4530
4531 DTResult out;
4532 out.sites = sites;
4533
4534 const size_t n = sites.size();
4535 if (n < 3 or all_collinear(sites))
4536 return out;
4537
4539 auto event_pool = Array<std::unique_ptr<Event>>::create(4 * n + 8);
4540 auto arc_pool = Array<std::unique_ptr<Arc>>::create(3 * n + 8);
4541
4542 auto make_arc = [&](const size_t site_idx) -> Arc *
4543 {
4544 arc_pool.append(std::make_unique<Arc>());
4545 Arc *a = arc_pool.get_last().get();
4546 a->site = site_idx;
4547 return a;
4548 };
4549
4550 size_t next_event_id = 1;
4551 for (size_t i = 0; i < n; ++i)
4552 {
4553 event_pool.append(std::make_unique<Event>());
4554 Event *ev = event_pool.get_last().get();
4555 ev->y = as_double(sites(i).get_y());
4556 ev->x = as_double(sites(i).get_x());
4557 ev->is_site = true;
4558 ev->site = i;
4559 ev->id = next_event_id++;
4560 queue.put(ev);
4561 }
4562
4563 Arc *head = nullptr;
4564
4567 tris.reserve(2 * n);
4568
4569 auto append_triangle = [&](const size_t i, const size_t j, const size_t k)
4570 {
4571 if (i == j or j == k or i == k)
4572 return;
4573 if (orientation(sites(i), sites(j), sites(k)) == Orientation::COLLINEAR)
4574 return;
4575
4576 size_t a = i, b = j, c = k;
4577 if (a > b) std::swap(a, b);
4578 if (b > c) std::swap(b, c);
4579 if (a > b) std::swap(a, b);
4580
4581 if (const auto ptr = seen.insert(TriKey{a, b, c}); ptr == nullptr)
4582 return;
4583
4584 tris.append(normalized_triangle(i, j, k, sites));
4585 };
4586
4587 while (not queue.is_empty())
4588 {
4589 const Event *ev = queue.get();
4590 if (not ev->valid)
4591 continue;
4592
4593 const double ly = ev->y;
4594
4595 if (ev->is_site)
4596 {
4597 const size_t site_idx = ev->site;
4598 const double sx = as_double(sites(site_idx).get_x());
4599
4600 if (head == nullptr)
4601 {
4602 head = make_arc(site_idx);
4603 continue;
4604 }
4605
4606 Arc *arc = locate_arc(head, sites, sx, ly - 10.0 * kEps);
4607 ah_domain_error_if(arc == nullptr) << "Could not locate arc in Fortune site event";
4608
4610
4612 Arc *right = make_arc(arc->site);
4613
4614 right->next = arc->next;
4615 if (right->next != nullptr)
4616 right->next->prev = right;
4617
4618 arc->next = middle;
4619 middle->prev = arc;
4620 middle->next = right;
4621 right->prev = middle;
4622
4623 enqueue_circle_event(arc, ly, sites, queue, event_pool, next_event_id);
4624 enqueue_circle_event(right, ly, sites, queue, event_pool, next_event_id);
4625 }
4626 else
4627 {
4628 Arc *arc = ev->arc;
4629 if (arc == nullptr or arc->circle != ev)
4630 continue;
4631
4632 Arc *left = arc->prev;
4633 Arc *right = arc->next;
4634 if (left == nullptr or right == nullptr)
4635 continue;
4636
4637 append_triangle(left->site, arc->site, right->site);
4638
4642
4643 left->next = right;
4644 right->prev = left;
4645
4646 enqueue_circle_event(left, ly, sites, queue, event_pool, next_event_id);
4647 enqueue_circle_event(right, ly, sites, queue, event_pool, next_event_id);
4648 }
4649 }
4650
4651 out.triangles = std::move(tris);
4652 return out;
4653 }
4654
4655 public:
4662 {
4663 // Reuse canonical site handling and keep a robust fallback path.
4664 const auto dt_ref = fallback_(pts);
4665 if (dt_ref.sites.size() < 3 or dt_ref.triangles.is_empty())
4666 return voronoi_(dt_ref);
4667
4668 const auto dt_sweep = triangulate_sweep(dt_ref.sites);
4669 if (dt_sweep.triangles.is_empty() or
4670 dt_sweep.triangles.size() != dt_ref.triangles.size() or
4672 return voronoi_(dt_ref);
4673
4674 return voronoi_(dt_sweep);
4675 }
4676
4682 [[nodiscard]] Result operator()(const std::initializer_list<Point> il) const
4683 {
4685 for (const Point & p: il)
4686 pts.append(p);
4687 return (*this)(pts);
4688 }
4689
4698 {
4700 }
4701
4704 clipped_cells(const std::initializer_list<Point> il,
4705 const Polygon & clip) const
4706 {
4708 for (const Point & p: il)
4709 pts.append(p);
4710 return clipped_cells(pts, clip);
4711 }
4712 };
4713
4714 // ============================================================================
4715 // Convex Hull Algorithms
4716 // ============================================================================
4717
4742 {
4747 {
4748 bool operator ()(const Point & p1, const Point & p2) const
4749 {
4750 if (p1.get_x() < p2.get_x())
4751 return true;
4752
4753 if (p2.get_x() < p1.get_x())
4754 return false;
4755
4756 return p1.get_y() < p2.get_y();
4757 }
4758 };
4759
4764 {
4765 Array<Point> points;
4766 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
4767 points.append(it.get_curr());
4768
4769 if (points.size() <= 1)
4770 return points;
4771
4772 quicksort_op(points, LexicographicCmp());
4773
4775 unique.reserve(points.size());
4776 unique.append(points(0));
4777 for (size_t i = 1; i < points.size(); ++i)
4778 if (points(i) != unique.get_last())
4779 unique.append(points(i));
4780
4781 return unique;
4782 }
4783
4787 [[nodiscard]] static Geom_Number turn(const Point & a, const Point & b, const Point & c)
4788 {
4789 return area_of_parallelogram(a, b, c);
4790 }
4791
4792 public:
4800 {
4801 Polygon ret;
4803 const size_t n = points.size();
4804
4805 if (n == 0)
4806 return ret;
4807
4808 if (n == 1)
4809 {
4810 ret.add_vertex(points(0));
4811 return ret;
4812 }
4813
4815 lower.reserve(n);
4816 for (size_t i = 0; i < n; ++i)
4817 {
4818 while (lower.size() >= 2 &&
4819 turn(lower(lower.size() - 2), lower(lower.size() - 1), points(i)) <= 0)
4820 static_cast<void>(lower.remove_last());
4821 lower.append(points(i));
4822 }
4823
4825 upper.reserve(n);
4826 for (size_t i = n; i > 0; --i)
4827 {
4828 const Point & p = points(i - 1);
4829 while (upper.size() >= 2 &&
4830 turn(upper(upper.size() - 2), upper(upper.size() - 1), p) <= 0)
4831 static_cast<void>(upper.remove_last());
4832 upper.append(p);
4833 }
4834
4835 // Remove duplicate endpoints before concatenating chains.
4836 static_cast<void>(lower.remove_last());
4837 static_cast<void>(upper.remove_last());
4838
4839 for (size_t i = 0; i < lower.size(); ++i)
4840 ret.add_vertex(lower(i));
4841
4842 for (size_t i = 0; i < upper.size(); ++i)
4843 ret.add_vertex(upper(i));
4844
4845 if (ret.size() >= 3)
4846 ret.close();
4847
4848 return ret;
4849 }
4850 };
4851
4877 {
4882 {
4883 bool operator ()(const Point & p1, const Point & p2) const
4884 {
4885 if (p1.get_x() < p2.get_x())
4886 return true;
4887
4888 if (p2.get_x() < p1.get_x())
4889 return false;
4890
4891 return p1.get_y() < p2.get_y();
4892 }
4893 };
4894
4899 {
4900 Array<Point> points;
4901 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
4902 points.append(it.get_curr());
4903
4904 if (points.size() <= 1)
4905 return points;
4906
4907 quicksort_op(points, LexicographicCmp());
4908
4910 unique.reserve(points.size());
4911 unique.append(points(0));
4912 for (size_t i = 1; i < points.size(); ++i)
4913 if (points(i) != unique.get_last())
4914 unique.append(points(i));
4915
4916 return unique;
4917 }
4918
4922 [[nodiscard]] static Geom_Number turn(const Point & a, const Point & b, const Point & c)
4923 {
4924 return area_of_parallelogram(a, b, c);
4925 }
4926
4927 public:
4935 {
4936 Polygon ret;
4938 const size_t n = points.size();
4939
4940 if (n == 0)
4941 return ret;
4942
4943 if (n == 1)
4944 {
4945 ret.add_vertex(points(0));
4946 return ret;
4947 }
4948
4949 // Pivot = lowest y, and lowest x on ties.
4950 size_t pivot_idx = 0;
4951 for (size_t i = 1; i < n; ++i)
4952 {
4953 if (points(i).get_y() < points(pivot_idx).get_y())
4954 {
4955 pivot_idx = i;
4956 continue;
4957 }
4958
4959 if (points(i).get_y() == points(pivot_idx).get_y() &&
4960 points(i).get_x() < points(pivot_idx).get_x())
4961 pivot_idx = i;
4962 }
4963
4964 const Point pivot = points(pivot_idx);
4965 if (pivot_idx != 0)
4966 {
4967 const Point tmp = points(0);
4968 points(0) = points(pivot_idx);
4969 points(pivot_idx) = tmp;
4970 }
4971
4972 Array<Point> polar;
4973 polar.reserve(n - 1);
4974 for (size_t i = 1; i < n; ++i)
4975 polar.append(points(i));
4976
4977 quicksort_op(polar, [&pivot](const Point & a, const Point & b)
4978 {
4979 const Geom_Number area = area_of_parallelogram(pivot, a, b);
4980 if (area > 0)
4981 return true;
4982 if (area < 0)
4983 return false;
4984
4985 // Same angle: keep nearer first; later we keep only farthest.
4986 return pivot.distance_squared_to(a) <
4987 pivot.distance_squared_to(b);
4988 });
4989
4990 // Keep only the farthest point per polar direction.
4992 filtered.reserve(polar.size());
4993 for (size_t i = 0; i < polar.size();)
4994 {
4995 size_t j = i;
4996 while (j + 1 < polar.size() &&
4997 area_of_parallelogram(pivot, polar(j), polar(j + 1)) == 0)
4998 ++j;
4999 filtered.append(polar(j));
5000 i = j + 1;
5001 }
5002
5003 if (filtered.size() == 1)
5004 {
5005 ret.add_vertex(pivot);
5006 ret.add_vertex(filtered(0));
5007 // Cannot close with only 2 vertices - return unclosed degenerate hull
5008 return ret;
5009 }
5010
5012 hull.reserve(filtered.size() + 1);
5013 hull.append(pivot);
5014 hull.append(filtered(0));
5015
5016 for (size_t i = 1; i < filtered.size(); ++i)
5017 {
5018 const Point & p = filtered(i);
5019 while (hull.size() >= 2 &&
5020 turn(hull(hull.size() - 2), hull(hull.size() - 1), p) <= 0)
5021 static_cast<void>(hull.remove_last());
5022 hull.append(p);
5023 }
5024
5025 for (size_t i = 0; i < hull.size(); ++i)
5026 ret.add_vertex(hull(i));
5027
5028 if (ret.size() >= 3)
5029 ret.close();
5030
5031 return ret;
5032 }
5033 };
5034
5065 {
5070 {
5072 static bool cmp_point(const Point & p1, const Point & p2)
5073 {
5074 if (p1.get_x() < p2.get_x())
5075 return true;
5076
5077 return not (p2.get_x() < p1.get_x()) and p1.get_y() < p2.get_y();
5078 }
5079
5081 bool operator ()(const Segment & s1, const Segment & s2) const
5082 {
5083 if (cmp_point(s1.get_src_point(), s2.get_src_point()))
5084 return true;
5085
5086 return not (cmp_point(s2.get_src_point(), s1.get_src_point())) and
5087 cmp_point(s1.get_tgt_point(), s2.get_tgt_point());
5088 }
5089 };
5090
5093
5097 static bool are_all_points_on_left(const DynList<Point> & l, const Segment & s)
5098 {
5099 for (PointIt it(l); it.has_curr(); it.next_ne())
5100 if (const Point & p = it.get_curr(); p.is_right_of(s))
5101 return false;
5102
5103 return true;
5104 }
5105
5107 {
5109
5110 for (PointIt i(point_set); i.has_curr(); i.next_ne())
5111 {
5112 const Point & p_i = i.get_curr();
5113
5114 for (PointIt j(point_set); j.has_curr(); j.next_ne())
5115 {
5116 const Point & p_j = j.get_curr();
5117
5118 if (p_i == p_j)
5119 continue;
5120
5122 ret.insert(s);
5123 }
5124 }
5125
5126 return ret;
5127 }
5128
5129 public:
5137 {
5138 Polygon ret;
5139
5140 if (point_set.is_empty())
5141 return ret;
5142
5144 const Point first = check_it.get_curr();
5145 bool has_distinct = false;
5146 check_it.next();
5147 for (; check_it.has_curr(); check_it.next_ne())
5148 if (check_it.get_curr() != first)
5149 {
5150 has_distinct = true;
5151 break;
5152 }
5153
5154 if (not has_distinct)
5155 {
5156 ret.add_vertex(first);
5157 return ret;
5158 }
5159
5161
5162 const Segment first_segment = extremes.remove_pos(0);
5163 ret.add_vertex(first_segment.get_src_point());
5164 ret.add_vertex(first_segment.get_tgt_point());
5165
5166 while (true)
5167 {
5168 const Vertex & last_vertex = ret.get_last_vertex();
5169
5170 const Segment *ptr = extremes.find_ptr([&last_vertex](const Segment & s)
5171 {
5172 return s.get_src_point() == last_vertex;
5173 });
5174
5175 ah_domain_error_if(ptr == nullptr)
5176 << "BruteForceConvexHull: broken chain (degenerate input?)";
5177
5178 if (ptr->get_tgt_point() == ret.get_first_vertex())
5179 break;
5180
5181 ret.add_vertex(ptr->get_tgt_point());
5182
5183 extremes.remove(*ptr);
5184 }
5185
5186 if (ret.size() >= 3)
5187 ret.close();
5188 return ret;
5189 }
5190 };
5191
5222 {
5227 {
5229 const Point *ret = &it.get_curr();
5230 it.next();
5231
5232 for (/* nothing */; it.has_curr(); it.next_ne())
5233 {
5234 const Point & p = it.get_curr();
5235 if (p.get_y() < ret->get_y() or
5236 (p.get_y() == ret->get_y() and p.get_x() < ret->get_x()))
5237 ret = &p;
5238 }
5239
5240 return ret;
5241 }
5242
5243 public:
5255 {
5256 Polygon ret;
5257
5258 if (point_set.is_empty())
5259 return ret;
5260
5261 const Point *start = get_lowest_point(point_set);
5262 const Point *current = start;
5263
5264 do
5265 {
5266 ret.add_vertex(*current);
5267 const Point *next = nullptr;
5268
5269 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
5270 {
5271 const Point & candidate = it.get_curr();
5272
5273 if (candidate == *current)
5274 continue;
5275
5276 if (next == nullptr)
5277 {
5278 next = &candidate;
5279 continue;
5280 }
5281
5282 if (const Orientation o = orientation(*current, *next, candidate); o == Orientation::CW)
5283 next = &candidate;
5284 else if (o == Orientation::COLLINEAR and
5285 current->distance_squared_to(candidate) >
5286 current->distance_squared_to(*next))
5287 next = &candidate;
5288 }
5289
5290 if (next == nullptr)
5291 break;
5292
5293 current = next;
5294 }
5295 while (current != start);
5296
5297 if (ret.size() >= 3)
5298 ret.close();
5299 return ret;
5300 }
5301 };
5302
5332 {
5350 const Segment & s)
5351 {
5352 // Compare perpendicular distances via |area_of_parallelogram|.
5353 // Since the segment base is constant for all candidates, comparing
5354 // |area| is equivalent to comparing distance (avoids sqrt).
5356 Point ret;
5357 const Point & a = s.get_src_point();
5358 const Point & b = s.get_tgt_point();
5359
5360 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
5361 {
5362 const Point & p = it.get_curr();
5363 Geom_Number area = area_of_parallelogram(a, b, p);
5364 if (area < 0)
5365 area = -area;
5366
5367 if (area > max_area)
5368 {
5369 ret = p;
5370 max_area = area;
5371 }
5372 }
5373
5374 return ret;
5375 }
5376
5401 static std::pair<DynList<Point>, DynList<Point>>
5403 const Point & a, const Point & b, const Point & c)
5404 {
5405 std::pair<DynList<Point>, DynList<Point>> ret;
5406
5407 while (not point_set.is_empty())
5408 {
5409 Point p = point_set.remove_first();
5410
5411 if (p != a and p != c and area_of_parallelogram(a, c, p) < 0)
5412 {
5413 ret.first.append(p);
5414 continue;
5415 }
5416
5417 if (p != c and p != b and area_of_parallelogram(c, b, p) < 0)
5418 ret.second.append(p);
5419 }
5420
5421 return ret;
5422 }
5423
5438 const Point & b)
5439 {
5440 if (point_set.is_empty())
5441 return {};
5442
5443 const Point c = get_farthest_point(point_set, Segment(a, b));
5444
5445 auto [s_ac, s_bc] = get_right_points(point_set, a, b, c);
5446
5449 ret.append(c);
5450 ret.concat(tmp);
5451
5452 return ret;
5453 }
5454
5462 static std::pair<Point, Point> search_extremes(const DynList<Point> & point_set)
5463 {
5465 Point leftmost = it.get_curr();
5466 Point rightmost = it.get_curr();
5467 it.next();
5468
5469 for (/* nothing */; it.has_curr(); it.next_ne())
5470 {
5471 const Point & p = it.get_curr();
5472
5473 if (p.get_x() < leftmost.get_x())
5474 leftmost = p;
5475
5476 if (p.get_x() > rightmost.get_x())
5477 rightmost = p;
5478 }
5479
5480 return std::make_pair(leftmost, rightmost);
5481 }
5482
5493 static std::pair<DynList<Point>, DynList<Point>>
5494 partition(const DynList<Point> & point_set, const Point & a, const Point & b)
5495 {
5496 std::pair<DynList<Point>, DynList<Point>> ret;
5497
5498 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
5499 if (const Point & p = it.get_curr(); area_of_parallelogram(a, b, p) < 0)
5500 ret.first.append(p);
5501 else
5502 ret.second.append(p);
5503
5504 return ret;
5505 }
5506
5507 public:
5515 {
5516 Polygon ret;
5517
5518 if (point_set.is_empty())
5519 return ret;
5520
5521 const auto [leftmost, rightmost] = search_extremes(point_set);
5522 if (leftmost == rightmost)
5523 {
5524 ret.add_vertex(leftmost);
5525 return ret;
5526 }
5527
5528 auto [right, left_or_collinear] = partition(point_set, leftmost, rightmost);
5529
5530 DynList<Point> s1 = quick_hull(right, leftmost, rightmost);
5531 DynList<Point> s2 = quick_hull(left_or_collinear, rightmost, leftmost);
5532
5534 convex_set.append(leftmost);
5535 convex_set.concat(s1);
5536 convex_set.append(rightmost);
5537 convex_set.concat(s2);
5538
5539 for (DynList<Point>::Iterator it(convex_set); it.has_curr(); it.next_ne())
5540 ret.add_vertex(it.get_curr());
5541
5542 if (ret.size() >= 3)
5543 ret.close();
5544 return ret;
5545 }
5546 };
5547
5548 // ============================================================================
5549 // Segment-Segment Intersection (Dedicated O(1))
5550 // ============================================================================
5551
5573 {
5574 public:
5578 enum class Kind
5579 {
5580 NONE,
5581 POINT,
5582 OVERLAP
5583 };
5584
5588 struct Result
5589 {
5593
5596 {
5597 return kind != Kind::NONE;
5598 }
5599 };
5600
5601 private:
5602 [[nodiscard]] static bool point_less_on_axis(const Point & a,
5603 const Point & b,
5604 const bool vertical_axis)
5605 {
5606 if (vertical_axis)
5607 {
5608 if (a.get_y() != b.get_y())
5609 return a.get_y() < b.get_y();
5610 return a.get_x() < b.get_x();
5611 }
5612
5613 if (a.get_x() != b.get_x())
5614 return a.get_x() < b.get_x();
5615 return a.get_y() < b.get_y();
5616 }
5617
5619 const Point & b,
5620 const bool vertical_axis)
5621 {
5622 return point_less_on_axis(a, b, vertical_axis) ? b : a;
5623 }
5624
5626 const Point & b,
5627 const bool vertical_axis)
5628 {
5629 return point_less_on_axis(a, b, vertical_axis) ? a : b;
5630 }
5631
5633 const Segment & s2)
5634 {
5635 const bool vertical_axis = s1.get_src_point().get_x() == s1.get_tgt_point().get_x();
5636
5637 Point a0 = s1.get_src_point();
5638 Point a1 = s1.get_tgt_point();
5639 Point b0 = s2.get_src_point();
5640 Point b1 = s2.get_tgt_point();
5641
5642 if (point_less_on_axis(a1, a0, vertical_axis))
5643 std::swap(a0, a1);
5644 if (point_less_on_axis(b1, b0, vertical_axis))
5645 std::swap(b0, b1);
5646
5647 const Point lo = point_max_on_axis(a0, b0, vertical_axis);
5648 const Point hi = point_min_on_axis(a1, b1, vertical_axis);
5649 return Segment(lo, hi);
5650 }
5651
5652 public:
5659 [[nodiscard]] Result operator ()(const Segment & s1, const Segment & s2) const
5660 {
5661 Result out;
5662
5664 return out;
5665
5666 if (not s1.is_parallel_with(s2))
5667 {
5669 out.point = s1.intersection_with(s2);
5670 return out;
5671 }
5672
5673 out.overlap = collinear_overlap(s1, s2);
5674 if (out.overlap.get_src_point() == out.overlap.get_tgt_point())
5675 {
5676 out.kind = Kind::POINT;
5677 out.point = out.overlap.get_src_point();
5678 return out;
5679 }
5680
5681 out.kind = Kind::OVERLAP;
5682 return out;
5683 }
5684 };
5685
5689 {
5690 return SegmentSegmentIntersection{}(s1, s2);
5691 }
5692
5693 // ============================================================================
5694 // Line Sweep / Event-Driven Framework
5695 // ============================================================================
5696
5740 template <typename Event, typename CmpEvent>
5742 {
5747 {
5748 Event event;
5749 size_t seq;
5750 };
5751
5756 {
5758
5759 bool operator ()(const SeqEvent & a, const SeqEvent & b) const
5760 {
5761 if (cmp(a.event, b.event)) return true;
5762 if (cmp(b.event, a.event)) return false;
5763 return a.seq < b.seq;
5764 }
5765 };
5766
5768 size_t seq_ = 0;
5769
5770 public:
5772 void enqueue(const Event & e)
5773 {
5774 queue_.insert(SeqEvent{e, seq_++});
5775 }
5776
5778 void enqueue(Event && e)
5779 {
5780 queue_.insert(SeqEvent{std::move(e), seq_++});
5781 }
5782
5785 {
5786 return not queue_.is_empty();
5787 }
5788
5791 {
5792 return queue_.size();
5793 }
5794
5796 Event dequeue()
5797 {
5798 SeqEvent se = queue_.min();
5799 queue_.remove(se);
5800 return std::move(se.event);
5801 }
5802
5804 [[nodiscard]] const Event &peek() const
5805 {
5806 return queue_.min().event;
5807 }
5808
5811 {
5812 queue_.empty(); // DynSetTree::empty() is a mutator that removes all elements
5813 seq_ = 0;
5814 }
5815
5828 template <typename Handler>
5829 void run(Handler && handler)
5830 {
5831 while (has_events())
5832 handler(*this, dequeue());
5833 }
5834
5841 template <typename Handler>
5842 void run(Handler && handler, Array<Event> & out)
5843 {
5844 while (has_events())
5845 {
5846 Event e = dequeue();
5847 out.append(e);
5848 handler(*this, e);
5849 }
5850 }
5851 };
5852
5853 // ============================================================================
5854 // Sweep Line Segment Intersection (Bentley-Ottmann)
5855 // ============================================================================
5856
5882 {
5883 public:
5886 {
5887 size_t seg_i;
5888 size_t seg_j;
5890 };
5891
5892 private:
5903 [[nodiscard]] static Geom_Number y_at_x(const Segment & s,
5904 const Geom_Number & x)
5905 {
5906 const Geom_Number & x1 = s.get_src_point().get_x();
5907 const Geom_Number & y1 = s.get_src_point().get_y();
5908 const Geom_Number & x2 = s.get_tgt_point().get_x();
5909 const Geom_Number & y2 = s.get_tgt_point().get_y();
5910
5911 if (x1 == x2)
5912 return (y1 + y2) / 2;
5913
5914 return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
5915 }
5916
5928 {
5929 const Point & a = s.get_src_point();
5930 const Point & b = s.get_tgt_point();
5931
5932 if (a.get_x() < b.get_x())
5933 return s;
5934 if (b.get_x() < a.get_x())
5935 return {b, a};
5936 // Vertical: lower endpoint first.
5937 if (a.get_y() < b.get_y())
5938 return s;
5939 return {b, a};
5940 }
5941
5946
5950 struct Event
5951 {
5954 size_t seg_a;
5955 size_t seg_b;
5956 };
5957
5966 [[nodiscard]] static bool event_less(const Event & a, const Event & b)
5967 {
5968 if (a.pt.get_x() != b.pt.get_x())
5969 return a.pt.get_x() < b.pt.get_x();
5970 if (a.pt.get_y() != b.pt.get_y())
5971 return a.pt.get_y() < b.pt.get_y();
5972 // LEFT < INTERSECTION < RIGHT for the same point.
5973 return static_cast<int>(a.type) < static_cast<int>(b.type);
5974 }
5975
5978 {
5979 bool operator()(const Event & a, const Event & b) const
5980 {
5981 return event_less(a, b);
5982 }
5983 };
5984
5985 [[nodiscard]] static bool slope_less(const Segment & a, const Segment & b)
5986 {
5987 const Geom_Number adx = a.get_tgt_point().get_x() - a.get_src_point().get_x();
5988 const Geom_Number ady = a.get_tgt_point().get_y() - a.get_src_point().get_y();
5989 const Geom_Number bdx = b.get_tgt_point().get_x() - b.get_src_point().get_x();
5990 const Geom_Number bdy = b.get_tgt_point().get_y() - b.get_src_point().get_y();
5991
5992 if (adx == 0 and bdx == 0)
5993 return ady < bdy;
5994 if (adx == 0)
5995 return false; // +inf slope
5996 if (bdx == 0)
5997 return true;
5998
5999 const Geom_Number lhs = ady * bdx;
6000 const Geom_Number rhs = bdy * adx;
6001 return lhs < rhs;
6002 }
6003
6004 [[nodiscard]] static bool status_less(const size_t lhs, const size_t rhs,
6005 const Geom_Number & sx,
6006 const Array<Segment> & segs)
6007 {
6008 if (lhs == rhs)
6009 return false;
6010
6011 const Geom_Number yl = y_at_x(segs(lhs), sx);
6012 const Geom_Number yr = y_at_x(segs(rhs), sx);
6013 if (yl != yr)
6014 return yl < yr;
6015
6016 if (slope_less(segs(lhs), segs(rhs)))
6017 return true;
6018 if (slope_less(segs(rhs), segs(lhs)))
6019 return false;
6020
6021 return lhs < rhs;
6022 }
6023
6033 {
6034 struct Node
6035 {
6036 size_t seg;
6038 unsigned priority;
6039 Node *left = nullptr;
6040 Node *right = nullptr;
6041 };
6042
6043 Node *root_ = nullptr;
6045 std::mt19937 rng_{0x5f3759dfu};
6047
6048 [[nodiscard]] static Node * merge(Node *a, Node *b)
6049 {
6050 if (a == nullptr) return b;
6051 if (b == nullptr) return a;
6052 if (a->priority < b->priority)
6053 {
6054 a->right = merge(a->right, b);
6055 return a;
6056 }
6057 b->left = merge(a, b->left);
6058 return b;
6059 }
6060
6062 {
6063 if (root == nullptr)
6064 return node;
6065
6066 if (node->priority < root->priority)
6067 {
6068 Node *left = nullptr;
6069 Node *right = nullptr;
6070 split_by_label(root, node->label, left, right);
6071 node->left = left;
6072 node->right = right;
6073 return node;
6074 }
6075
6076 if (node->label < root->label)
6077 root->left = insert_by_label(root->left, node);
6078 else
6079 root->right = insert_by_label(root->right, node);
6080 return root;
6081 }
6082
6083 static void split_by_label(Node *root, const Geom_Number & label,
6084 Node *& left, Node *& right)
6085 {
6086 if (root == nullptr)
6087 {
6088 left = nullptr;
6089 right = nullptr;
6090 return;
6091 }
6092
6093 if (root->label < label)
6094 {
6095 split_by_label(root->right, label, root->right, right);
6096 left = root;
6097 }
6098 else
6099 {
6100 split_by_label(root->left, label, left, root->left);
6101 right = root;
6102 }
6103 }
6104
6106 const Geom_Number & label,
6107 Node *& removed)
6108 {
6109 if (root == nullptr)
6110 return nullptr;
6111
6112 if (label < root->label)
6113 {
6114 root->left = erase_by_label(root->left, label, removed);
6115 return root;
6116 }
6117
6118 if (root->label < label)
6119 {
6120 root->right = erase_by_label(root->right, label, removed);
6121 return root;
6122 }
6123
6124 removed = root;
6125 return merge(root->left, root->right);
6126 }
6127
6128 static void destroy(const Node *n)
6129 {
6130 if (n == nullptr)
6131 return;
6132 destroy(n->left);
6133 destroy(n->right);
6134 delete n;
6135 }
6136
6137 [[nodiscard]] size_t predecessor_for_insert(const size_t seg,
6138 const Geom_Number & sx) const
6139 {
6140 size_t pred = SIZE_MAX;
6141 const Node *cur = root_;
6142 while (cur != nullptr)
6143 if (status_less(cur->seg, seg, sx, segs_))
6144 {
6145 pred = cur->seg;
6146 cur = cur->right;
6147 }
6148 else
6149 cur = cur->left;
6150 return pred;
6151 }
6152
6153 [[nodiscard]] size_t successor_for_insert(const size_t seg,
6154 const Geom_Number & sx) const
6155 {
6156 size_t succ = SIZE_MAX;
6157 const Node *cur = root_;
6158 while (cur != nullptr)
6159 if (status_less(seg, cur->seg, sx, segs_))
6160 {
6161 succ = cur->seg;
6162 cur = cur->left;
6163 }
6164 else
6165 cur = cur->right;
6166 return succ;
6167 }
6168
6170 fresh_label_between(const size_t pred, const size_t succ) const
6171 {
6172 if (pred != SIZE_MAX and succ != SIZE_MAX)
6173 return (nodes_(pred)->label + nodes_(succ)->label) / Geom_Number(2);
6174 if (pred != SIZE_MAX)
6175 return nodes_(pred)->label + Geom_Number(1);
6176 if (succ != SIZE_MAX)
6177 return nodes_(succ)->label - Geom_Number(1);
6178 return Geom_Number(0);
6179 }
6180
6181 public:
6183 {
6184 nodes_.reserve(segs.size());
6185 for (size_t i = 0; i < segs.size(); ++i)
6186 nodes_.append(nullptr);
6187 }
6188
6190 {
6191 destroy(root_);
6192 }
6193
6194 [[nodiscard]] bool contains(const size_t seg) const
6195 {
6196 return nodes_(seg) != nullptr;
6197 }
6198
6199 void insert(const size_t seg, const Geom_Number & sx)
6200 {
6201 if (contains(seg))
6202 return;
6203
6204 const size_t pred = predecessor_for_insert(seg, sx);
6205 const size_t succ = successor_for_insert(seg, sx);
6206 const auto node = new Node{
6207 seg, fresh_label_between(pred, succ),
6208 static_cast<unsigned>(rng_()),
6209 nullptr, nullptr
6210 };
6211 root_ = insert_by_label(root_, node);
6212 nodes_(seg) = node;
6213 }
6214
6215 void erase(const size_t seg)
6216 {
6217 if (not contains(seg))
6218 return;
6219
6220 Node *removed = nullptr;
6221 root_ = erase_by_label(root_, nodes_(seg)->label, removed);
6222 nodes_(seg) = nullptr;
6223 delete removed;
6224 }
6225
6226 [[nodiscard]] size_t predecessor(const size_t seg) const
6227 {
6228 if (not contains(seg))
6229 return SIZE_MAX;
6230
6231 const Geom_Number & label = nodes_(seg)->label;
6232 const Node *pred = nullptr;
6233 Node *cur = root_;
6234 while (cur != nullptr)
6235 if (cur->label < label)
6236 {
6237 pred = cur;
6238 cur = cur->right;
6239 }
6240 else
6241 cur = cur->left;
6242
6243 return pred == nullptr ? SIZE_MAX : pred->seg;
6244 }
6245
6246 [[nodiscard]] size_t successor(const size_t seg) const
6247 {
6248 if (not contains(seg))
6249 return SIZE_MAX;
6250
6251 const Geom_Number & label = nodes_(seg)->label;
6252 const Node *succ = nullptr;
6253 Node *cur = root_;
6254 while (cur != nullptr)
6255 if (label < cur->label)
6256 {
6257 succ = cur;
6258 cur = cur->left;
6259 }
6260 else
6261 cur = cur->right;
6262
6263 return succ == nullptr ? SIZE_MAX : succ->seg;
6264 }
6265 };
6266
6284 const size_t i, const size_t j,
6285 const Geom_Number & sx,
6288 const size_t n)
6289 {
6290 const size_t lo = i < j ? i : j;
6291 const size_t hi = i < j ? j : i;
6292 const size_t key = lo * n + hi;
6293 if (seen_pairs.search(key) != nullptr)
6294 return;
6295
6296 const Segment & sa = segs(lo);
6297 const Segment & sb = segs(hi);
6298 const auto hit = segment_segment_intersection(sa, sb);
6299 if (not hit.intersects())
6300 return;
6301
6303 {
6304 seen_pairs.insert(key);
6305 const Point overlap_start = hit.overlap.get_src_point();
6306 const Point overlap_end = hit.overlap.get_tgt_point();
6307 if (not (overlap_start.get_x() < sx))
6308 eq.enqueue(Event{overlap_start, EventType::INTERSECTION, lo, hi});
6309 if (not (overlap_end.get_x() < sx))
6310 eq.enqueue(Event{overlap_end, EventType::INTERSECTION, lo, hi});
6311 return;
6312 }
6313
6314 const Point ip = hit.point;
6315 if (ip.get_x() < sx)
6316 return; // Intersection is to the left of the sweep.
6317
6318 seen_pairs.insert(key);
6319 eq.enqueue(Event{ip, EventType::INTERSECTION, lo, hi});
6320 }
6321
6322 public:
6332 operator ()(const Array<Segment> & segments) const
6333 {
6334 const size_t n = segments.size();
6335 Array<Intersection> result;
6336
6337 if (n < 2)
6338 return result;
6339
6340 // Canonicalize: src is the left endpoint.
6342 segs.reserve(n);
6343 for (size_t i = 0; i < n; ++i)
6344 {
6345 ah_domain_error_if(segments(i).get_src_point() == segments(i).get_tgt_point())
6346 << "Segment " << i << " is degenerate (zero length)";
6347 segs.append(canonicalize(segments(i)));
6348 }
6349
6350 // Build the initial event queue (left + right endpoints).
6352 for (size_t i = 0; i < n; ++i)
6353 {
6354 eq.enqueue(Event{segs(i).get_src_point(), EventType::LEFT, i, 0});
6355 eq.enqueue(Event{segs(i).get_tgt_point(), EventType::RIGHT, i, 0});
6356 }
6357
6358 // Seen intersection pairs (key = lo * n + hi).
6361
6362 auto pair_key = [n](const size_t i, const size_t j)
6363 {
6364 const size_t lo = i < j ? i : j;
6365 const size_t hi = i < j ? j : i;
6366 return lo * n + hi;
6367 };
6368
6369 auto append_intersection = [&result, &reported_pairs, &pair_key](const size_t i,
6370 const size_t j,
6371 const Point & p)
6372 {
6373 const size_t key = pair_key(i, j);
6374 if (reported_pairs.search(key) != nullptr)
6375 return;
6376 reported_pairs.insert(key);
6377
6378 const size_t lo = i < j ? i : j;
6379 const size_t hi = i < j ? j : i;
6380 result.append(Intersection{lo, hi, p});
6381 };
6382
6383 // Sweep-line status: balanced tree with O(log n) updates.
6384 StatusTree status(segs);
6385
6386 while (eq.has_events())
6387 {
6388 const Event ev = eq.dequeue();
6389 const Geom_Number sx = ev.pt.get_x();
6390
6391 if (ev.type == EventType::LEFT)
6392 {
6393 const size_t idx = ev.seg_a;
6394 status.insert(idx, sx);
6395
6396 if (const size_t pred = status.predecessor(idx); pred != SIZE_MAX)
6397 check_and_enqueue(segs, pred, idx, sx, eq, seen_pairs, n);
6398 if (const size_t succ = status.successor(idx); succ != SIZE_MAX)
6399 check_and_enqueue(segs, idx, succ, sx, eq, seen_pairs, n);
6400 }
6401 else if (ev.type == EventType::RIGHT)
6402 {
6403 if (const size_t idx = ev.seg_a; status.contains(idx))
6404 {
6405 const size_t pred = status.predecessor(idx);
6406 const size_t succ = status.successor(idx);
6407 if (pred != SIZE_MAX and succ != SIZE_MAX)
6408 check_and_enqueue(segs, pred, succ, sx, eq, seen_pairs, n);
6409 status.erase(idx);
6410 }
6411 }
6412 else // INTERSECTION
6413 {
6414 const size_t a = ev.seg_a;
6415 const size_t b = ev.seg_b;
6416 append_intersection(a, b, ev.pt);
6417
6418 // Walk outward from a (or b) through the status tree to find
6419 // all active segments passing through ev.pt. Segments sharing
6420 // a point are adjacent in sweep-line order, so this is O(k)
6421 // instead of the former O(n) scan.
6422 Array<size_t> block;
6423
6424 // Pick a seed segment that is still in the status tree
6425 size_t seed = SIZE_MAX;
6426 if (status.contains(a) and segs(a).contains(ev.pt))
6427 seed = a;
6428 else if (status.contains(b) and segs(b).contains(ev.pt))
6429 seed = b;
6430
6431 if (seed != SIZE_MAX)
6432 {
6433 block.append(seed);
6434
6435 // Walk predecessors
6436 for (size_t p = status.predecessor(seed);
6437 p != SIZE_MAX and segs(p).contains(ev.pt);
6438 p = status.predecessor(p))
6439 block.append(p);
6440
6441 // Walk successors
6442 for (size_t s = status.successor(seed);
6443 s != SIZE_MAX and segs(s).contains(ev.pt);
6444 s = status.successor(s))
6445 block.append(s);
6446 }
6447
6448 if (block.size() > 2)
6449 {
6450 for (size_t i = 0; i + 1 < block.size(); ++i)
6451 for (size_t j = i + 1; j < block.size(); ++j)
6452 append_intersection(block(i), block(j), ev.pt);
6453 }
6454
6455 if (block.size() == 0)
6456 {
6457 if (status.contains(a)) block.append(a);
6458 if (status.contains(b) and (block.size() == 0 or block(0) != b))
6459 block.append(b);
6460 }
6461
6462 for (size_t i = 0; i < block.size(); ++i)
6463 status.erase(block(i));
6464 for (size_t i = 0; i < block.size(); ++i)
6465 status.insert(block(i), sx);
6466
6467 for (size_t i = 0; i < block.size(); ++i)
6468 {
6469 const size_t s = block(i);
6470 if (const size_t pred = status.predecessor(s); pred != SIZE_MAX)
6471 check_and_enqueue(segs, pred, s, sx, eq, seen_pairs, n);
6472 if (const size_t succ = status.successor(s); succ != SIZE_MAX)
6473 check_and_enqueue(segs, s, succ, sx, eq, seen_pairs, n);
6474 }
6475 }
6476 }
6477
6478 // Sort results by (x, y).
6479 quicksort_op(result, [](const Intersection & a, const Intersection & b)
6480 {
6481 if (a.point.get_x() != b.point.get_x())
6482 return a.point.get_x() < b.point.get_x();
6483 if (a.point.get_y() != b.point.get_y())
6484 return a.point.get_y() < b.point.get_y();
6485 if (a.seg_i != b.seg_i)
6486 return a.seg_i < b.seg_i;
6487 return a.seg_j < b.seg_j;
6488 });
6489
6490 return result;
6491 }
6492 };
6493
6494 // ============================================================================
6495 // Monotone Polygon Triangulation
6496 // ============================================================================
6497
6523 {
6524 private:
6526 {
6528 }
6529
6534
6535 static void ensure_ccw(Array<Point> & v)
6536 {
6538 }
6539
6544
6548 [[nodiscard]] static bool is_above(const Point & a, const Point & b)
6549 {
6550 return a.get_y() > b.get_y() or
6551 (a.get_y() == b.get_y() and a.get_x() < b.get_x());
6552 }
6553
6557 [[nodiscard]] static bool is_below(const Point & a, const Point & b)
6558 {
6559 return is_above(b, a);
6560 }
6561
6566 const size_t edge)
6567 {
6568 return is_below(verts((edge + 1) % verts.size()), verts(edge));
6569 }
6570
6572 const size_t edge,
6573 const Geom_Number & y)
6574 {
6575 const Point & a = verts(edge);
6576 const Point & b = verts((edge + 1) % verts.size());
6577 if (a.get_y() == b.get_y())
6578 return a.get_x() < b.get_x() ? a.get_x() : b.get_x();
6579
6580 return a.get_x() + (y - a.get_y()) * (b.get_x() - a.get_x())
6581 / (b.get_y() - a.get_y());
6582 }
6583
6584 [[nodiscard]] static bool edge_status_less(const size_t lhs,
6585 const size_t rhs,
6586 const Geom_Number & sweep_y,
6587 const Array<Point> & verts)
6588 {
6589 if (lhs == rhs)
6590 return false;
6591
6593 const Geom_Number xr = edge_x_at_y(verts, rhs, sweep_y);
6594 if (xl != xr)
6595 return xl < xr;
6596 return lhs < rhs;
6597 }
6598
6600 {
6601 struct Node
6602 {
6603 size_t edge;
6605 unsigned priority;
6606 Node *left = nullptr;
6607 Node *right = nullptr;
6608 };
6609
6610 Node *root_ = nullptr;
6612 std::mt19937 rng_{0x31415926u};
6614
6615 static void split_by_label(Node *root, const Geom_Number & label,
6616 Node *& left, Node *& right)
6617 {
6618 if (root == nullptr)
6619 {
6620 left = nullptr;
6621 right = nullptr;
6622 return;
6623 }
6624
6625 if (root->label < label)
6626 {
6627 split_by_label(root->right, label, root->right, right);
6628 left = root;
6629 }
6630 else
6631 {
6632 split_by_label(root->left, label, left, root->left);
6633 right = root;
6634 }
6635 }
6636
6637 [[nodiscard]] static Node * merge(Node *a, Node *b)
6638 {
6639 if (a == nullptr) return b;
6640 if (b == nullptr) return a;
6641 if (a->priority < b->priority)
6642 {
6643 a->right = merge(a->right, b);
6644 return a;
6645 }
6646 b->left = merge(a, b->left);
6647 return b;
6648 }
6649
6651 {
6652 if (root == nullptr)
6653 return node;
6654
6655 if (node->priority < root->priority)
6656 {
6657 Node *left = nullptr;
6658 Node *right = nullptr;
6659 split_by_label(root, node->label, left, right);
6660 node->left = left;
6661 node->right = right;
6662 return node;
6663 }
6664
6665 if (node->label < root->label)
6666 root->left = insert_by_label(root->left, node);
6667 else
6668 root->right = insert_by_label(root->right, node);
6669 return root;
6670 }
6671
6673 const Geom_Number & label,
6674 Node *& removed)
6675 {
6676 if (root == nullptr)
6677 return nullptr;
6678
6679 if (label < root->label)
6680 {
6681 root->left = erase_by_label(root->left, label, removed);
6682 return root;
6683 }
6684
6685 if (root->label < label)
6686 {
6687 root->right = erase_by_label(root->right, label, removed);
6688 return root;
6689 }
6690
6691 removed = root;
6692 return merge(root->left, root->right);
6693 }
6694
6695 static void destroy(const Node *n)
6696 {
6697 if (n == nullptr)
6698 return;
6699 destroy(n->left);
6700 destroy(n->right);
6701 delete n;
6702 }
6703
6704 [[nodiscard]] size_t predecessor_for_insert(const size_t edge,
6705 const Geom_Number & sweep_y) const
6706 {
6707 size_t pred = SIZE_MAX;
6708 const Node *cur = root_;
6709 while (cur != nullptr)
6710 if (edge_status_less(cur->edge, edge, sweep_y, verts_))
6711 {
6712 pred = cur->edge;
6713 cur = cur->right;
6714 }
6715 else
6716 cur = cur->left;
6717 return pred;
6718 }
6719
6720 [[nodiscard]] size_t successor_for_insert(const size_t edge,
6721 const Geom_Number & sweep_y) const
6722 {
6723 size_t succ = SIZE_MAX;
6724 const Node *cur = root_;
6725 while (cur != nullptr)
6726 if (edge_status_less(edge, cur->edge, sweep_y, verts_))
6727 {
6728 succ = cur->edge;
6729 cur = cur->left;
6730 }
6731 else
6732 cur = cur->right;
6733 return succ;
6734 }
6735
6737 fresh_label_between(const size_t pred, const size_t succ) const
6738 {
6739 if (pred != SIZE_MAX and succ != SIZE_MAX)
6740 return (nodes_(pred)->label + nodes_(succ)->label) / Geom_Number(2);
6741 if (pred != SIZE_MAX)
6742 return nodes_(pred)->label + Geom_Number(1);
6743 if (succ != SIZE_MAX)
6744 return nodes_(succ)->label - Geom_Number(1);
6745 return {0};
6746 }
6747
6748 public:
6750 {
6751 nodes_.reserve(verts.size());
6752 for (size_t i = 0; i < verts.size(); ++i)
6753 nodes_.append(nullptr);
6754 }
6755
6757 {
6758 destroy(root_);
6759 }
6760
6761 [[nodiscard]] bool contains(const size_t edge) const
6762 {
6763 return nodes_(edge) != nullptr;
6764 }
6765
6766 void insert(const size_t edge, const Geom_Number & sweep_y)
6767 {
6768 if (contains(edge))
6769 return;
6770
6771 const size_t pred = predecessor_for_insert(edge, sweep_y);
6772 const size_t succ = successor_for_insert(edge, sweep_y);
6773 const auto node = new Node{
6774 edge, fresh_label_between(pred, succ),
6775 static_cast<unsigned>(rng_()),
6776 nullptr, nullptr
6777 };
6778 root_ = insert_by_label(root_, node);
6779 nodes_(edge) = node;
6780 }
6781
6782 void erase(const size_t edge)
6783 {
6784 if (not contains(edge))
6785 return;
6786
6787 Node *removed = nullptr;
6788 root_ = erase_by_label(root_, nodes_(edge)->label, removed);
6789 nodes_(edge) = nullptr;
6790 delete removed;
6791 }
6792
6793 [[nodiscard]] size_t left_edge_of_point(const Point & p,
6794 const Geom_Number & sweep_y) const
6795 {
6796 size_t best = SIZE_MAX;
6797 const Node *cur = root_;
6798 while (cur != nullptr)
6799 {
6800 const Geom_Number x = edge_x_at_y(verts_, cur->edge, sweep_y);
6801 if (x < p.get_x())
6802 {
6803 best = cur->edge;
6804 cur = cur->right;
6805 }
6806 else
6807 cur = cur->left;
6808 }
6809 return best;
6810 }
6811 };
6812
6814 [[nodiscard]] static DynList<Triangle>
6816 {
6817 DynList<Triangle> result;
6818 const size_t n = verts.size();
6819 if (n < 3)
6820 return result;
6821 if (n == 3)
6822 {
6823 result.append(Triangle(verts(0), verts(1), verts(2)));
6824 return result;
6825 }
6826
6827 // Sort vertex indices by y descending, x ascending for ties.
6829 sorted.reserve(n);
6830 for (size_t i = 0; i < n; ++i)
6831 sorted.append(i);
6832
6833 quicksort_op(sorted, [&verts](const size_t a, const size_t b)
6834 {
6835 if (verts(a).get_y() != verts(b).get_y())
6836 return verts(a).get_y() > verts(b).get_y();
6837 return verts(a).get_x() < verts(b).get_x();
6838 });
6839
6840 // Identify left and right chains.
6841 // Top vertex = sorted(0), bottom vertex = sorted(n-1).
6842 // Left chain: goes from top to bottom counter-clockwise.
6843 const size_t top = sorted(0);
6844 const size_t bot = sorted(n - 1);
6845
6847 on_left.reserve(n);
6848 for (size_t i = 0; i < n; ++i)
6849 on_left.append(false);
6850
6851 // Walk from top to bottom going forward in the polygon.
6852 for (size_t i = top; i != bot; i = (i + 1) % n)
6853 on_left(i) = true;
6854 on_left(top) = true; // top is on both; mark left.
6855
6856 // Stack-based triangulation.
6857 FixedStack<size_t> stack(n);
6858 stack.push(sorted(0));
6859 stack.push(sorted(1));
6860
6861 for (size_t i = 2; i < n - 1; ++i)
6862 {
6863 const size_t curr = sorted(i);
6864 const size_t stk_top = stack.top();
6865 if (on_left(curr) != on_left(stk_top))
6866 {
6867 // Different chain: fan triangles from curr to all stack vertices.
6868 while (stack.size() > 1)
6869 {
6870 const size_t a = stack.top();
6871 const size_t b = stack.top(1);
6872 result.append(Triangle(verts(curr), verts(a), verts(b)));
6873 stack.pop();
6874 }
6875 stack.pop();
6876 stack.push(sorted(i - 1));
6877 stack.push(curr);
6878 }
6879 else
6880 {
6881 // Same chain: pop vertices while diagonal is inside.
6882 size_t last_popped = stack.top();
6883 stack.pop();
6884
6885 while (not stack.is_empty())
6886 {
6887 const size_t peek = stack.top();
6888 const Geom_Number area =
6890 verts(peek));
6891
6892 // On the left chain we need CW turn (area < 0);
6893 // on the right chain we need CCW turn (area > 0).
6894 if ((on_left(curr) and area >= 0) or (not on_left(curr) and area <= 0))
6895 break;
6896
6897 result.append(Triangle(verts(curr), verts(last_popped),
6898 verts(peek)));
6899 last_popped = peek;
6900 stack.pop();
6901 }
6902
6903 stack.push(last_popped);
6904 stack.push(curr);
6905 }
6906 }
6907
6908 // Process last vertex (bottom): fan to the remaining stack.
6909 const size_t last = sorted(n - 1);
6910 while (stack.size() > 1)
6911 {
6912 const size_t a = stack.top();
6913 const size_t b = stack.top(1);
6914 result.append(Triangle(verts(last), verts(a), verts(b)));
6915 stack.pop();
6916 }
6917
6918 return result;
6919 }
6920
6922 const size_t i)
6923 {
6924 const size_t n = v.size();
6925 const Point & prev = v((i + n - 1) % n);
6926 const Point & curr = v(i);
6927 const Point & next = v((i + 1) % n);
6928
6929 const bool prev_below = is_below(prev, curr);
6930 const bool next_below = is_below(next, curr);
6931 const bool prev_above = is_above(prev, curr);
6932 const bool next_above = is_above(next, curr);
6933 const bool reflex = orientation(prev, curr, next) == Orientation::CW;
6934
6939 return VertexType::REGULAR;
6940 }
6941
6943 const size_t i)
6944 {
6945 return is_below(v((i + 1) % v.size()), v(i));
6946 }
6947
6948 [[nodiscard]] static Array<Array<size_t>>
6950 const DynSetTree<std::pair<size_t, size_t>> & diagonals)
6951 {
6952 const size_t n = verts.size();
6954 adj.reserve(n);
6955 for (size_t i = 0; i < n; ++i)
6956 adj.append(Array<size_t>());
6957
6958 auto add_undirected_edge = [&adj](const size_t a, const size_t b)
6959 {
6960 adj(a).append(b);
6961 adj(b).append(a);
6962 };
6963
6964 for (size_t i = 0; i < n; ++i)
6965 add_undirected_edge(i, (i + 1) % n);
6966
6967 for (const auto & d: diagonals)
6968 add_undirected_edge(d.first, d.second);
6969
6970 for (size_t v = 0; v < n; ++v)
6971 {
6972 auto & nbrs = adj(v);
6976 [&verts, v](const size_t a, const size_t b)
6977 {
6978 const Geom_Number dax = verts(a).get_x() - verts(v).get_x();
6979 const Geom_Number day = verts(a).get_y() - verts(v).get_y();
6980 const Geom_Number dbx = verts(b).get_x() - verts(v).get_x();
6981 const Geom_Number dby = verts(b).get_y() - verts(v).get_y();
6982
6983 const bool upper_a = day > 0 or (day == 0 and dax >= 0);
6984 const bool upper_b = dby > 0 or (dby == 0 and dbx >= 0);
6985 if (upper_a != upper_b)
6986 return upper_a > upper_b;
6987
6988 if (const Geom_Number cross = dax * dby - day * dbx; cross != 0)
6989 return cross > 0;
6990
6991 const Geom_Number da2 = dax * dax + day * day;
6992 const Geom_Number db2 = dbx * dbx + dby * dby;
6993 return da2 < db2;
6994 });
6995 }
6996
6997 using HalfEdge = std::pair<size_t, size_t>;
6998 std::map<HalfEdge, HalfEdge> next_half;
6999 for (size_t v = 0; v < n; ++v)
7000 {
7001 const auto & nbrs = adj(v);
7002 if (nbrs.is_empty())
7003 continue;
7004 for (size_t k = 0; k < nbrs.size(); ++k)
7005 {
7006 const size_t u = nbrs(k);
7007 const size_t pred = nbrs((k + nbrs.size() - 1) % nbrs.size());
7008 next_half[HalfEdge{u, v}] = HalfEdge{v, pred};
7009 }
7010 }
7011
7012 auto face_area2 = [&verts](const Array<size_t> & face)
7013 {
7014 Geom_Number area2 = 0;
7015 for (size_t i = 0; i < face.size(); ++i)
7016 {
7017 const Point & a = verts(face(i));
7018 const Point & b = verts(face((i + 1) % face.size()));
7019 area2 += a.get_x() * b.get_y() - a.get_y() * b.get_x();
7020 }
7021 return area2;
7022 };
7023
7024 DynSetTree<HalfEdge> visited;
7025 Array<Array<size_t>> faces;
7026 for (const auto & [kv, _]: next_half)
7027 {
7028 const HalfEdge start = kv;
7029 if (visited.search(start) != nullptr)
7030 continue;
7031
7032 HalfEdge cur = start;
7033 Array<size_t> face;
7034 while (visited.insert(cur) != nullptr)
7035 {
7036 face.append(cur.first);
7037 auto it_next = next_half.find(cur);
7038 if (it_next == next_half.end())
7039 {
7040 face.empty();
7041 break;
7042 }
7043 cur = it_next->second;
7044 if (cur == start)
7045 break;
7046 }
7047
7048 if (face.is_empty() or cur != start or face.size() < 3)
7049 continue;
7050 if (face_area2(face) <= 0)
7051 continue; // Ignore outer face / degenerate loops.
7052
7054 poly_idx.reserve(face.size());
7055 for (const size_t idx: face)
7056 poly_idx.append(idx);
7057 faces.append(poly_idx);
7058 }
7059
7060 return faces;
7061 }
7062
7063 [[nodiscard]] static Array<Array<size_t>>
7065 {
7066 const size_t n = verts.size();
7067 Array<Array<size_t>> faces;
7068
7069 if (n < 3)
7070 return faces;
7071
7072 if (is_y_monotone(verts))
7073 {
7074 Array<size_t> one;
7075 one.reserve(n);
7076 for (size_t i = 0; i < n; ++i)
7077 one.append(i);
7078 faces.append(one);
7079 return faces;
7080 }
7081
7083 vtype.reserve(n);
7084 for (size_t i = 0; i < n; ++i)
7085 vtype.append(classify_vertex(verts, i));
7086
7087 Array<size_t> order;
7088 order.reserve(n);
7089 for (size_t i = 0; i < n; ++i)
7090 order.append(i);
7091 quicksort_op(order, [&verts](const size_t a, const size_t b)
7092 {
7093 if (verts(a).get_y() != verts(b).get_y())
7094 return verts(a).get_y() > verts(b).get_y();
7095 return verts(a).get_x() < verts(b).get_x();
7096 });
7097
7099 helper.reserve(n);
7100 for (size_t i = 0; i < n; ++i)
7101 helper.append(SIZE_MAX);
7102
7103 EdgeStatusTree status(verts);
7105
7106 auto add_diagonal = [&](const size_t a, const size_t b)
7107 {
7108 if (a == b or b == SIZE_MAX)
7109 return;
7110 if ((a + 1) % n == b or (b + 1) % n == a)
7111 return;
7112
7113 const size_t lo = a < b ? a : b;
7114 const size_t hi = a < b ? b : a;
7115 diagonals.insert({lo, hi});
7116 };
7117
7118 auto helper_is_merge = [&helper, &vtype](const size_t edge)
7119 {
7120 return helper(edge) != SIZE_MAX and
7121 vtype(helper(edge)) == VertexType::MERGE;
7122 };
7123
7124 for (size_t oi = 0; oi < n; ++oi)
7125 {
7126 const size_t vi = order(oi);
7127 const size_t prev = (vi + n - 1) % n;
7128 const size_t e_prev = prev; // edge prev -> vi
7129 const size_t e_curr = vi; // edge vi -> next
7130 const Geom_Number sweep_y = verts(vi).get_y();
7131
7132 switch (vtype(vi))
7133 {
7134 case VertexType::START:
7136 {
7137 status.insert(e_curr, sweep_y);
7138 helper(e_curr) = vi;
7139 }
7140 break;
7141
7142 case VertexType::END:
7144 {
7147 status.erase(e_prev);
7148 }
7149 break;
7150
7151 case VertexType::SPLIT:
7152 {
7153 if (const size_t left = status.left_edge_of_point(verts(vi), sweep_y); left != SIZE_MAX)
7154 {
7155 add_diagonal(vi, helper(left));
7156 helper(left) = vi;
7157 }
7158
7160 {
7161 status.insert(e_curr, sweep_y);
7162 helper(e_curr) = vi;
7163 }
7164 }
7165 break;
7166
7167 case VertexType::MERGE:
7169 {
7172 status.erase(e_prev);
7173 }
7174 if (const size_t left = status.left_edge_of_point(verts(vi), sweep_y);
7175 left != SIZE_MAX)
7176 {
7177 if (helper_is_merge(left))
7178 add_diagonal(vi, helper(left));
7179 helper(left) = vi;
7180 }
7181 break;
7182
7185 {
7187 {
7190 status.erase(e_prev);
7191 }
7193 {
7194 status.insert(e_curr, sweep_y);
7195 helper(e_curr) = vi;
7196 }
7197 }
7198 else if (const size_t left = status.left_edge_of_point(verts(vi), sweep_y);
7199 left != SIZE_MAX)
7200 {
7201 if (helper_is_merge(left))
7202 add_diagonal(vi, helper(left));
7203 helper(left) = vi;
7204 }
7205 break;
7206 }
7207 }
7208
7210 if (faces.size() == 0)
7211 {
7212 Array<size_t> one;
7213 one.reserve(n);
7214 for (size_t i = 0; i < n; ++i)
7215 one.append(i);
7216 faces.append(one);
7217 }
7218 return faces;
7219 }
7220
7221 public:
7240 {
7241 ah_domain_error_if(not p.is_closed()) << "Polygon must be closed";
7242 ah_domain_error_if(p.size() < 3) << "Polygon must have at least 3 vertices";
7243
7245 ah_domain_error_if(signed_double_area(verts) == 0) << "Polygon is degenerate (zero area)";
7246
7248
7249 // Check if the polygon is y-monotone.
7250 if (is_y_monotone(verts))
7252
7254 DynList<Triangle> result;
7255 bool produced_any = false;
7256
7257 for (size_t fi = 0; fi < faces.size(); ++fi)
7258 {
7259 if (faces(fi).size() < 3)
7260 continue;
7261
7263 mono.reserve(faces(fi).size());
7264 for (size_t i = 0; i < faces(fi).size(); ++i)
7265 mono.append(verts(faces(fi)(i)));
7266
7267 if (signed_double_area(mono) == 0)
7268 continue;
7270
7272 if (is_y_monotone(mono))
7274 else
7275 {
7276 // Defensive fallback for degenerate decomposition edge cases.
7277 Polygon piece;
7278 for (size_t i = 0; i < mono.size(); ++i)
7279 piece.add_vertex(mono(i));
7280 if (piece.size() >= 3)
7281 {
7282 piece.close();
7284 tris = ears(piece);
7285 }
7286 }
7287
7288 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
7289 {
7290 result.append(it.get_curr());
7291 produced_any = true;
7292 }
7293 }
7294
7295 if (produced_any)
7296 return result;
7297
7298 // Conservative fallback if decomposition could not produce valid faces.
7300 return ears(p);
7301 }
7302
7303 private:
7305 [[nodiscard]] static bool is_y_monotone(const Array<Point> & v)
7306 {
7307 const size_t n = v.size();
7308 if (n <= 3)
7309 return true;
7310
7311 // Find topmost and bottommost vertices.
7312 size_t top_idx = 0;
7313 size_t bot_idx = 0;
7314 for (size_t i = 1; i < n; ++i)
7315 {
7316 if (v(i).get_y() > v(top_idx).get_y() or
7317 (v(i).get_y() == v(top_idx).get_y() and
7318 v(i).get_x() < v(top_idx).get_x()))
7319 top_idx = i;
7320
7321 if (v(i).get_y() < v(bot_idx).get_y() or
7322 (v(i).get_y() == v(bot_idx).get_y() and
7323 v(i).get_x() > v(bot_idx).get_x()))
7324 bot_idx = i;
7325 }
7326
7327 // Walk from top to bottom along the forward chain — y must be
7328 // non-increasing.
7329 for (size_t i = top_idx; ;)
7330 {
7331 const size_t next = (i + 1) % n;
7332 if (next == bot_idx)
7333 break;
7334 if (v(next).get_y() > v(i).get_y())
7335 return false;
7336 i = next;
7337 }
7338
7339 // Walk from top to bottom along the backward chain — y must be
7340 // non-increasing.
7341 for (size_t i = top_idx; ;)
7342 {
7343 const size_t prev = (i + n - 1) % n;
7344 if (prev == bot_idx)
7345 break;
7346 if (v(prev).get_y() > v(i).get_y())
7347 return false;
7348 i = prev;
7349 }
7350
7351 return true;
7352 }
7353 };
7354
7355 // ============================================================================
7356 // Minkowski Sum for Convex Polygons — O(n + m)
7357 // ============================================================================
7358
7392 {
7393 private:
7395 {
7397 }
7398
7403
7404 [[nodiscard]] static bool is_convex(const Array<Point> & verts)
7405 {
7406 if (verts.size() < 3)
7407 return false;
7409 }
7410
7413 {
7415
7416 // Find bottom-most vertex (min y, then min x).
7417 size_t bot = 0;
7418 for (size_t i = 1; i < v.size(); ++i)
7419 if (v(i).get_y() < v(bot).get_y() or (v(i).get_y() == v(bot).get_y() and
7420 v(i).get_x() < v(bot).get_x()))
7421 bot = i;
7422
7423 Array<Point> result;
7424 result.reserve(v.size());
7425 for (size_t i = 0; i < v.size(); ++i)
7426 result.append(v((bot + i) % v.size()));
7427
7428 return result;
7429 }
7430
7431 [[nodiscard]] static Point edge_vec(const Array<Point> & v, const size_t i)
7432 {
7433 const size_t j = (i + 1) % v.size();
7434 return {v(j).get_x() - v(i).get_x(), v(j).get_y() - v(i).get_y()};
7435 }
7436
7437 [[nodiscard]] static Geom_Number cross(const Point & a, const Point & b)
7438 {
7439 return a.get_x() * b.get_y() - a.get_y() * b.get_x();
7440 }
7441
7442 public:
7454 const Polygon & Q) const
7455 {
7456 ah_domain_error_if(not P.is_closed()) << "First polygon must be closed";
7457 ah_domain_error_if(not Q.is_closed()) << "Second polygon must be closed";
7458 ah_domain_error_if(P.size() < 3) << "First polygon must have at least 3 vertices";
7459 ah_domain_error_if(Q.size() < 3) << "Second polygon must have at least 3 vertices";
7460
7463
7464 ah_domain_error_if(not is_convex(pv)) << "First polygon must be convex";
7465 ah_domain_error_if(not is_convex(qv)) << "Second polygon must be convex";
7466
7467 pv = normalize(pv);
7468 qv = normalize(qv);
7469
7470 const size_t np = pv.size();
7471 const size_t nq = qv.size();
7472
7473 Array<Point> result;
7474 result.reserve(np + nq);
7475
7476 size_t ip = 0;
7477 size_t iq = 0;
7478
7479 while (ip < np or iq < nq)
7480 {
7481 result.append(Point(pv(ip % np).get_x() + qv(iq % nq).get_x(),
7482 pv(ip % np).get_y() + qv(iq % nq).get_y()));
7483
7484 if (ip >= np)
7485 {
7486 ++iq;
7487 continue;
7488 }
7489 if (iq >= nq)
7490 {
7491 ++ip;
7492 continue;
7493 }
7494
7495 const Point ep = edge_vec(pv, ip % np);
7496 const Point eq = edge_vec(qv, iq % nq);
7497
7498 if (const Geom_Number cr = cross(ep, eq); cr > 0)
7499 ++ip;
7500 else if (cr < 0)
7501 ++iq;
7502 else
7503 {
7504 ++ip;
7505 ++iq;
7506 } // Parallel edges: advance both.
7507 }
7508
7509 // Remove collinear and duplicate vertices.
7511 clean.reserve(result.size());
7512 for (size_t i = 0; i < result.size(); ++i)
7513 {
7514 if (clean.is_empty() or clean.get_last() != result(i))
7515 clean.append(result(i));
7516 }
7517 if (clean.size() > 1 and clean(0) == clean.get_last())
7518 static_cast<void>(clean.remove_last());
7519
7520 Polygon ret;
7521 for (size_t i = 0; i < clean.size(); ++i)
7522 ret.add_vertex(clean(i));
7523
7524 if (ret.size() >= 3)
7525 ret.close();
7526
7527 return ret;
7528 }
7529 };
7530
7531 // ============================================================================
7532 // Convex Polygon Distance (GJK) — 2D
7533 // ============================================================================
7534
7552 {
7553 public:
7566
7567 private:
7572 {
7576 double vx = 0.0;
7577 double vy = 0.0;
7578 };
7579
7581 {
7582 std::vector<SupportPoint> reduced_simplex;
7583 double cx = 0.0;
7584 double cy = 0.0;
7587 bool contains_origin = false;
7588 };
7589
7590 static constexpr size_t kMaxIters = 64;
7591 static constexpr double kEps = 1e-12;
7592
7593 [[nodiscard]] static double to_double(const Geom_Number & v)
7594 {
7595 return geom_number_to_double(v);
7596 }
7597
7598 [[nodiscard]] static double dot2(const double ax, const double ay,
7599 const double bx, const double by) noexcept
7600 {
7601 return ax * bx + ay * by;
7602 }
7603
7604 [[nodiscard]] static double norm2(const double x, const double y) noexcept
7605 {
7606 return dot2(x, y, x, y);
7607 }
7608
7609 [[nodiscard]] static Point point_from_double(const double x, const double y)
7610 {
7611 return {Geom_Number(x), Geom_Number(y)};
7612 }
7613
7614 [[nodiscard]] static Point lerp_point(const Point & a, const Point & b,
7615 const double t)
7616 {
7617 const double ax = to_double(a.get_x());
7618 const double ay = to_double(a.get_y());
7619 const double bx = to_double(b.get_x());
7620 const double by = to_double(b.get_y());
7621 return point_from_double((1.0 - t) * ax + t * bx,
7622 (1.0 - t) * ay + t * by);
7623 }
7624
7626 {
7627 Geom_Number sx = 0;
7628 Geom_Number sy = 0;
7629 for (size_t i = 0; i < v.size(); ++i)
7630 {
7631 sx += v(i).get_x();
7632 sy += v(i).get_y();
7633 }
7634 return {sx / Geom_Number(v.size()), sy / Geom_Number(v.size())};
7635 }
7636
7637 [[nodiscard]] static SupportPoint
7639 const Array<Point> & q,
7640 const double dx,
7641 const double dy)
7642 {
7643 size_t ip = 0;
7644 size_t iq = 0;
7645 double best_p = -std::numeric_limits<double>::infinity();
7646 double best_q = -std::numeric_limits<double>::infinity();
7647
7648 for (size_t i = 0; i < p.size(); ++i)
7649 {
7650 const double proj = dot2(to_double(p(i).get_x()), to_double(p(i).get_y()),
7651 dx, dy);
7652 if (proj > best_p)
7653 {
7654 best_p = proj;
7655 ip = i;
7656 }
7657 }
7658
7659 // Support of Minkowski difference uses opposite direction in q.
7660 for (size_t i = 0; i < q.size(); ++i)
7661 {
7662 const double proj = dot2(to_double(q(i).get_x()), to_double(q(i).get_y()),
7663 -dx, -dy);
7664 if (proj > best_q)
7665 {
7666 best_q = proj;
7667 iq = i;
7668 }
7669 }
7670
7671 const Point & pa = p(ip);
7672 const Point & pb = q(iq);
7673 const Geom_Number vx = pa.get_x() - pb.get_x();
7674 const Geom_Number vy = pa.get_y() - pb.get_y();
7675 return SupportPoint{pa, pb, Point(vx, vy), to_double(vx), to_double(vy)};
7676 }
7677
7679 const Polygon & q)
7680 {
7681 for (Polygon::Segment_Iterator itp(p); itp.has_curr(); itp.next_ne())
7682 {
7683 const Segment sp = itp.get_current_segment();
7684 for (Polygon::Segment_Iterator itq(q); itq.has_curr(); itq.next_ne())
7685 if (sp.intersects_with(itq.get_current_segment()))
7686 return true;
7687 }
7688
7689 Point vp;
7690 for (Polygon::Vertex_Iterator it(p); it.has_curr(); it.next_ne())
7691 {
7692 vp = it.get_current_vertex().to_point();
7693 break;
7694 }
7695
7696 Point vq;
7697 for (Polygon::Vertex_Iterator it(q); it.has_curr(); it.next_ne())
7698 {
7699 vq = it.get_current_vertex().to_point();
7700 break;
7701 }
7702
7705 }
7706
7707 [[nodiscard]] static ClosestSimplexResult
7708 closest_from_simplex(const std::vector<SupportPoint> & simplex)
7709 {
7710 ah_domain_error_if(simplex.empty()) << "GJK simplex cannot be empty";
7711
7713
7714 if (simplex.size() == 1)
7715 {
7717 out.cx = simplex[0].vx;
7718 out.cy = simplex[0].vy;
7719 out.witness_a = simplex[0].a;
7720 out.witness_b = simplex[0].b;
7721 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7722 return out;
7723 }
7724
7725 if (simplex.size() == 2)
7726 {
7727 const SupportPoint & a = simplex[0];
7728 const SupportPoint & b = simplex[1];
7729 const double abx = b.vx - a.vx;
7730 const double aby = b.vy - a.vy;
7731 const double ab2 = norm2(abx, aby);
7732
7733 if (ab2 <= kEps * kEps)
7734 {
7735 out.reduced_simplex = {a};
7736 out.cx = a.vx;
7737 out.cy = a.vy;
7738 out.witness_a = a.a;
7739 out.witness_b = a.b;
7740 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7741 return out;
7742 }
7743
7744 double t = -dot2(a.vx, a.vy, abx, aby) / ab2;
7745 if (t < 0.0) t = 0.0;
7746 if (t > 1.0) t = 1.0;
7747
7748 if (t <= kEps)
7749 {
7750 out.reduced_simplex = {a};
7751 out.cx = a.vx;
7752 out.cy = a.vy;
7753 out.witness_a = a.a;
7754 out.witness_b = a.b;
7755 }
7756 else if (t >= 1.0 - kEps)
7757 {
7758 out.reduced_simplex = {b};
7759 out.cx = b.vx;
7760 out.cy = b.vy;
7761 out.witness_a = b.a;
7762 out.witness_b = b.b;
7763 }
7764 else
7765 {
7766 out.reduced_simplex = {a, b};
7767 out.cx = a.vx + t * abx;
7768 out.cy = a.vy + t * aby;
7769 out.witness_a = lerp_point(a.a, b.a, t);
7770 out.witness_b = lerp_point(a.b, b.b, t);
7771 }
7772
7773 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7774 return out;
7775 }
7776
7777 // Triangle case.
7778 const SupportPoint & a = simplex[0];
7779 const SupportPoint & b = simplex[1];
7780 const SupportPoint & c = simplex[2];
7781
7782 const double abx = b.vx - a.vx;
7783 const double aby = b.vy - a.vy;
7784 const double acx = c.vx - a.vx;
7785 const double acy = c.vy - a.vy;
7786 const double apx = -a.vx;
7787 const double apy = -a.vy;
7788
7789 const double d1 = dot2(abx, aby, apx, apy);
7790 const double d2 = dot2(acx, acy, apx, apy);
7791 if (d1 <= 0.0 and d2 <= 0.0)
7792 {
7793 out.reduced_simplex = {a};
7794 out.cx = a.vx;
7795 out.cy = a.vy;
7796 out.witness_a = a.a;
7797 out.witness_b = a.b;
7798 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7799 return out;
7800 }
7801
7802 const double bpx = -b.vx;
7803 const double bpy = -b.vy;
7804 const double d3 = dot2(abx, aby, bpx, bpy);
7805 const double d4 = dot2(acx, acy, bpx, bpy);
7806 if (d3 >= 0.0 and d4 <= d3)
7807 {
7808 out.reduced_simplex = {b};
7809 out.cx = b.vx;
7810 out.cy = b.vy;
7811 out.witness_a = b.a;
7812 out.witness_b = b.b;
7813 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7814 return out;
7815 }
7816
7817 const double vc = d1 * d4 - d3 * d2;
7818 if (vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0)
7819 {
7820 const double t = d1 / (d1 - d3);
7821 out.reduced_simplex = {a, b};
7822 out.cx = a.vx + t * abx;
7823 out.cy = a.vy + t * aby;
7824 out.witness_a = lerp_point(a.a, b.a, t);
7825 out.witness_b = lerp_point(a.b, b.b, t);
7826 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7827 return out;
7828 }
7829
7830 const double cpx = -c.vx;
7831 const double cpy = -c.vy;
7832 const double d5 = dot2(abx, aby, cpx, cpy);
7833 const double d6 = dot2(acx, acy, cpx, cpy);
7834 if (d6 >= 0.0 and d5 <= d6)
7835 {
7836 out.reduced_simplex = {c};
7837 out.cx = c.vx;
7838 out.cy = c.vy;
7839 out.witness_a = c.a;
7840 out.witness_b = c.b;
7841 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7842 return out;
7843 }
7844
7845 const double vb = d5 * d2 - d1 * d6;
7846 if (vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0)
7847 {
7848 const double t = d2 / (d2 - d6);
7849 out.reduced_simplex = {a, c};
7850 out.cx = a.vx + t * acx;
7851 out.cy = a.vy + t * acy;
7852 out.witness_a = lerp_point(a.a, c.a, t);
7853 out.witness_b = lerp_point(a.b, c.b, t);
7854 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7855 return out;
7856 }
7857
7858 const double va = d3 * d6 - d5 * d4;
7859 if (va <= 0.0 and d4 - d3 >= 0.0 and d5 - d6 >= 0.0)
7860 {
7861 const double t = (d4 - d3) / (d4 - d3 + (d5 - d6));
7862 const double bcx = c.vx - b.vx;
7863 const double bcy = c.vy - b.vy;
7864 out.reduced_simplex = {b, c};
7865 out.cx = b.vx + t * bcx;
7866 out.cy = b.vy + t * bcy;
7867 out.witness_a = lerp_point(b.a, c.a, t);
7868 out.witness_b = lerp_point(b.b, c.b, t);
7869 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7870 return out;
7871 }
7872
7873 // Origin is inside triangle.
7874 const double denom = va + vb + vc;
7875 if (std::fabs(denom) <= kEps)
7876 {
7877 out.reduced_simplex = {a, b, c};
7878 out.cx = 0.0;
7879 out.cy = 0.0;
7880 out.witness_a = a.a;
7881 out.witness_b = a.b;
7882 out.contains_origin = true;
7883 return out;
7884 }
7885
7886 const double inv = 1.0 / denom;
7887 const double wa = va * inv;
7888 const double wb = vb * inv;
7889 const double wc = vc * inv;
7890 out.reduced_simplex = {a, b, c};
7891 out.cx = 0.0;
7892 out.cy = 0.0;
7893 out.witness_a = point_from_double(wa * to_double(a.a.get_x()) +
7894 wb * to_double(b.a.get_x()) +
7895 wc * to_double(c.a.get_x()),
7896 wa * to_double(a.a.get_y()) +
7897 wb * to_double(b.a.get_y()) +
7898 wc * to_double(c.a.get_y()));
7899 out.witness_b = point_from_double(wa * to_double(a.b.get_x()) +
7900 wb * to_double(b.b.get_x()) +
7901 wc * to_double(c.b.get_x()),
7902 wa * to_double(a.b.get_y()) +
7903 wb * to_double(b.b.get_y()) +
7904 wc * to_double(c.b.get_y()));
7905 out.contains_origin = true;
7906 return out;
7907 }
7908
7909 [[nodiscard]] static Result
7911 const Polygon & q,
7912 const Array<Point> & pv,
7913 const Array<Point> & qv)
7914 {
7915 Result out;
7916 out.intersects = false;
7917
7919 {
7920 out.distance_squared = 0;
7921 out.distance = 0;
7922 out.closest_on_first = pv(0);
7923 out.closest_on_second = pv(0);
7924 out.intersects = true;
7925 return out;
7926 }
7927
7928 bool has_best = false;
7929 Geom_Number best_d2 = 0;
7931
7932 for (size_t i = 0; i < pv.size(); ++i)
7933 {
7934 const Point & pnt = pv(i);
7935 for (size_t j = 0; j < qv.size(); ++j)
7936 {
7937 const Point & q0 = qv(j);
7938 const Point & q1 = qv((j + 1) % qv.size());
7939 const Segment edge(q0, q1);
7940 const Point proj = edge.project(pnt);
7941 if (const Geom_Number d2 = pnt.distance_squared_to(proj); not has_best or d2 < best_d2)
7942 {
7943 has_best = true;
7944 best_d2 = d2;
7945 best_a = pnt;
7946 best_b = proj;
7947 }
7948 }
7949 }
7950
7951 for (size_t i = 0; i < qv.size(); ++i)
7952 {
7953 const Point & pnt = qv(i);
7954 for (size_t j = 0; j < pv.size(); ++j)
7955 {
7956 const Point & p0 = pv(j);
7957 const Point & p1 = pv((j + 1) % pv.size());
7958 const Segment edge(p0, p1);
7959 const Point proj = edge.project(pnt);
7960 if (const Geom_Number d2 = pnt.distance_squared_to(proj); not has_best or d2 < best_d2)
7961 {
7962 has_best = true;
7963 best_d2 = d2;
7964 best_a = proj;
7965 best_b = pnt;
7966 }
7967 }
7968 }
7969
7970 ah_domain_error_if(not has_best) << "Could not compute distance between convex polygons";
7971
7972 out.distance_squared = best_d2;
7973 out.distance = square_root(best_d2);
7974 out.closest_on_first = best_a;
7975 out.closest_on_second = best_b;
7976 out.intersects = (best_d2 == 0);
7977 return out;
7978 }
7979
7980 public:
7988 const Polygon & q) const
7989 {
7990 ah_domain_error_if(not p.is_closed()) << "First polygon must be closed";
7991 ah_domain_error_if(not q.is_closed()) << "Second polygon must be closed";
7992 ah_domain_error_if(p.size() < 3) << "First polygon must have at least 3 vertices";
7993 ah_domain_error_if(q.size() < 3) << "Second polygon must have at least 3 vertices";
7994
7997
7999 << "First polygon must be convex";
8001 << "Second polygon must be convex";
8002
8003 // Fast exact overlap check to avoid unnecessary iterations.
8005 {
8006 Result r;
8007 r.distance_squared = 0;
8008 r.distance = 0;
8009 r.closest_on_first = pv(0);
8010 r.closest_on_second = pv(0);
8011 r.intersects = true;
8012 return r;
8013 }
8014
8017 double dirx = to_double(cp.get_x() - cq.get_x());
8018 double diry = to_double(cp.get_y() - cq.get_y());
8019 if (norm2(dirx, diry) <= kEps * kEps)
8020 {
8021 dirx = 1.0;
8022 diry = 0.0;
8023 }
8024
8025 std::vector<SupportPoint> simplex;
8026 simplex.reserve(3);
8027 simplex.push_back(support(pv, qv, dirx, diry));
8028 dirx = -simplex[0].vx;
8029 diry = -simplex[0].vy;
8030
8031 double prev_d2 = std::numeric_limits<double>::infinity();
8032 size_t iters = 0;
8033
8034 for (; iters < kMaxIters; ++iters)
8035 {
8036 if (norm2(dirx, diry) <= kEps * kEps)
8037 break;
8038
8039 simplex.push_back(support(pv, qv, dirx, diry));
8040 if (simplex.size() > 3)
8041 simplex.erase(simplex.begin());
8042
8044 simplex = std::move(step.reduced_simplex);
8045
8046 const double d2 = norm2(step.cx, step.cy);
8047 if (step.contains_origin or d2 <= kEps * kEps)
8048 {
8049 Result r;
8050 r.distance_squared = 0;
8051 r.distance = 0;
8052 r.closest_on_first = step.witness_a;
8053 r.closest_on_second = step.witness_b;
8054 r.intersects = true;
8055 r.gjk_iterations = iters + 1;
8056 return r;
8057 }
8058
8059 if (prev_d2 - d2 <= kEps * std::max(1.0, prev_d2))
8060 break;
8061
8062 prev_d2 = d2;
8063 dirx = -step.cx;
8064 diry = -step.cy;
8065 }
8066
8068 refined.gjk_iterations = iters;
8069 return refined;
8070 }
8071 };
8072
8073 // ============================================================================
8074 // KD-Tree Point Search — O(log n) Nearest Neighbor
8075 // ============================================================================
8076
8105 {
8109
8110 [[nodiscard]] static bool lexicographic_less(const Point & a, const Point & b)
8111 {
8112 if (a.get_x() < b.get_x()) return true;
8113 if (b.get_x() < a.get_x()) return false;
8114 return a.get_y() < b.get_y();
8115 }
8116
8118 {
8119 quicksort_op(points, [](const Point & a, const Point & b)
8120 {
8121 return lexicographic_less(a, b);
8122 });
8124 uniq.reserve(points.size());
8125 for (size_t i = 0; i < points.size(); ++i)
8126 if (uniq.is_empty() or uniq.get_last() != points(i))
8127 uniq.append(points(i));
8128 return uniq;
8129 }
8130
8131 public:
8136 {
8138 bool split_on_x = true;
8140 size_t depth = 0;
8141 bool is_leaf = false;
8143 };
8144
8154
8163 KDTreePointSearch(const Geom_Number & xmin, const Geom_Number & ymin,
8164 const Geom_Number & xmax, const Geom_Number & ymax)
8165 : tree(xmin, ymin, xmax, ymax),
8166 bounds_(xmin, ymin, xmax, ymax)
8167 {}
8168
8179 [[nodiscard]] static KDTreePointSearch
8180 build(const Array<Point> & points,
8181 const Geom_Number & xmin, const Geom_Number & ymin,
8182 const Geom_Number & xmax, const Geom_Number & ymax)
8183 {
8184 KDTreePointSearch kd(xmin, ymin, xmax, ymax);
8185 kd.tree = K2Tree<>::build(points, Point(xmin, ymin),
8186 Point(xmax, ymax));
8187 kd.points_ = unique_points(points);
8188 return kd;
8189 }
8190
8192 bool insert(const Point & p)
8193 {
8194 if (not tree.insert(p))
8195 return false;
8196 points_.append(p);
8197 return true;
8198 }
8199
8201 [[nodiscard]] bool contains(const Point & p) const
8202 {
8203 return tree.contains(p);
8204 }
8205
8207 [[nodiscard]] std::optional<Point> nearest(const Point & p) const
8208 {
8209 return tree.nearest(p);
8210 }
8211
8213 void range(const Geom_Number & xmin, const Geom_Number & ymin,
8214 const Geom_Number & xmax, const Geom_Number & ymax,
8215 DynList<Point> *out) const
8216 {
8217 tree.range({xmin, ymin, xmax, ymax}, out);
8218 }
8219
8221 [[nodiscard]] size_t size() const noexcept { return tree.size(); }
8222
8225
8227 template <typename Op>
8228 void for_each(Op && op) const
8229 {
8230 tree.for_each(std::forward<Op>(op));
8231 }
8232
8241 {
8244 snap.points = points_;
8245 if (points_.is_empty())
8246 return snap;
8247
8248 auto recurse = [&](const auto & self,
8249 const Array<Point> & pts,
8250 const Rectangle & region,
8251 const size_t depth,
8252 const bool split_on_x) -> void
8253 {
8254 if (pts.is_empty())
8255 return;
8256
8258 quicksort_op(sorted, [split_on_x](const Point & a, const Point & b)
8259 {
8260 if (split_on_x)
8261 {
8262 if (a.get_x() != b.get_x()) return a.get_x() < b.get_x();
8263 return a.get_y() < b.get_y();
8264 }
8265 if (a.get_y() != b.get_y()) return a.get_y() < b.get_y();
8266 return a.get_x() < b.get_x();
8267 });
8268
8269 size_t mid = sorted.size() / 2;
8270 if (mid == 0) mid = 1;
8271 if (mid >= sorted.size()) mid = sorted.size() - 1;
8272
8273 const Point pivot = sorted(mid);
8275 part.region = region;
8276 part.split_on_x = split_on_x;
8277 part.split_value = split_on_x ? pivot.get_x() : pivot.get_y();
8278 part.depth = depth;
8279 part.is_leaf = sorted.size() == 1;
8280 part.representative = pivot;
8281 snap.partitions.append(part);
8282
8283 if (sorted.size() == 1)
8284 return;
8285
8289 right_pts.reserve(sorted.size() - mid);
8290 for (size_t i = 0; i < mid; ++i)
8291 left_pts.append(sorted(i));
8292 for (size_t i = mid; i < sorted.size(); ++i)
8293 right_pts.append(sorted(i));
8294
8295 if (split_on_x)
8296 {
8297 Rectangle left(region.get_xmin(), region.get_ymin(),
8298 pivot.get_x(), region.get_ymax());
8299 Rectangle right(pivot.get_x(), region.get_ymin(),
8300 region.get_xmax(), region.get_ymax());
8301 self(self, left_pts, left, depth + 1, false);
8302 self(self, right_pts, right, depth + 1, false);
8303 }
8304 else
8305 {
8306 Rectangle bottom(region.get_xmin(), region.get_ymin(),
8307 region.get_xmax(), pivot.get_y());
8308 Rectangle top(region.get_xmin(), pivot.get_y(),
8309 region.get_xmax(), region.get_ymax());
8310 self(self, left_pts, bottom, depth + 1, true);
8311 self(self, right_pts, top, depth + 1, true);
8312 }
8313 };
8314
8315 recurse(recurse, points_, bounds_, 0, true);
8316 return snap;
8317 }
8318 };
8319
8320 // ============================================================================
8321 // Convex Polygon Decomposition — Hertel-Mehlhorn
8322 // ============================================================================
8323
8357 {
8358 static constexpr size_t NONE = ~static_cast<size_t>(0);
8359
8360 [[nodiscard]] static size_t find_pos(const Array<size_t> & face, size_t v)
8361 {
8362 for (size_t i = 0; i < face.size(); ++i)
8363 if (face(i) == v)
8364 return i;
8365 return NONE;
8366 }
8367
8368 [[nodiscard]] static bool is_polygon_edge(size_t u, size_t v, size_t n)
8369 {
8370 if (u > v)
8371 {
8372 const size_t tmp = u;
8373 u = v;
8374 v = tmp;
8375 }
8376 return v == u + 1 or (u == 0 and v == n - 1);
8377 }
8378
8380 [[nodiscard]] static bool can_merge(const Array<Point> & pts,
8381 const Array<size_t> & f1,
8382 const Array<size_t> & f2,
8383 size_t u, size_t v)
8384 {
8385 const size_t n1 = f1.size();
8386 const size_t n2 = f2.size();
8387
8388 size_t pu1 = find_pos(f1, u);
8389 size_t pv1 = find_pos(f1, v);
8390
8391 // Ensure u is followed by v in f1 (CCW). If not, swap roles.
8392 if ((pu1 + 1) % n1 != pv1)
8393 {
8394 const size_t tmp = u;
8395 u = v;
8396 v = tmp;
8397 pu1 = find_pos(f1, u);
8398 pv1 = find_pos(f1, v);
8399 }
8400
8401 const size_t prev_u = f1((pu1 + n1 - 1) % n1);
8402 const size_t next_v = f1((pv1 + 1) % n1);
8403
8404 const size_t pu2 = find_pos(f2, u);
8405 const size_t pv2 = find_pos(f2, v);
8406
8407 const size_t next_u = f2((pu2 + 1) % n2);
8408 const size_t prev_v = f2((pv2 + n2 - 1) % n2);
8409
8411 return false;
8413 return false;
8414
8415 return true;
8416 }
8417
8420 const Array<size_t> & f2,
8421 size_t u, size_t v)
8422 {
8423 const size_t n1 = f1.size();
8424 const size_t n2 = f2.size();
8425
8426 size_t pu1 = find_pos(f1, u);
8427 size_t pv1 = find_pos(f1, v);
8428
8429 if ((pu1 + 1) % n1 != pv1)
8430 {
8431 const size_t tmp = u;
8432 u = v;
8433 v = tmp;
8434 pv1 = find_pos(f1, v);
8435 }
8436
8437 const size_t pu2 = find_pos(f2, u);
8438
8439 Array<size_t> merged;
8440 merged.reserve(n1 + n2 - 2);
8441
8442 // Part 1: f1 vertices from after v around to u (inclusive), skip v.
8443 for (size_t k = 0; k < n1 - 1; ++k)
8444 merged.append(f1((pv1 + 1 + k) % n1));
8445
8446 // Part 2: f2 vertices from after u around to v (inclusive), skip u.
8447 for (size_t k = 0; k < n2 - 1; ++k)
8448 merged.append(f2((pu2 + 1 + k) % n2));
8449
8450 return merged;
8451 }
8452
8454 {
8456 }
8457
8458 public:
8466 {
8467 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
8468 ah_domain_error_if(poly.size() < 3) << "Polygon must have >= 3 vertices";
8469
8470 const Array<Point> pts = extract_vertices(poly);
8471 const size_t n = pts.size();
8472
8473 if (n == 3)
8474 {
8475 Array<Polygon> result;
8476 result.append(poly);
8477 return result;
8478 }
8479
8480 // Triangulate.
8483
8484 // Build indexed faces from triangles.
8485 Array<Array<size_t>> faces;
8486 for (DynList<Triangle>::Iterator it(tri_list); it.has_curr(); it.next_ne())
8487 {
8488 const Triangle & t = it.get_curr();
8489 size_t i0 = NONE, i1 = NONE, i2 = NONE;
8490 for (size_t i = 0; i < n; ++i)
8491 {
8492 if (i0 == NONE and pts(i) == t.get_p1()) i0 = i;
8493 if (i1 == NONE and pts(i) == t.get_p2()) i1 = i;
8494 if (i2 == NONE and pts(i) == t.get_p3()) i2 = i;
8495 }
8496 if (i0 == NONE or i1 == NONE or i2 == NONE)
8497 continue;
8498
8500 {
8501 const size_t tmp = i1;
8502 i1 = i2;
8503 i2 = tmp;
8504 }
8505
8506 Array<size_t> face;
8507 face.append(i0);
8508 face.append(i1);
8509 face.append(i2);
8510 faces.append(std::move(face));
8511 }
8512
8513 // Hertel-Mehlhorn: greedily merge across diagonals.
8514 bool changed = true;
8515 while (changed)
8516 {
8517 changed = false;
8518 for (size_t fi = 0; fi < faces.size() and not changed; ++fi)
8519 {
8520 const auto f1 = faces(fi); // copy to avoid dangling when faces mutates
8521 for (size_t k = 0; k < f1.size() and not changed; ++k)
8522 {
8523 const size_t u = f1(k);
8524 const size_t v = f1((k + 1) % f1.size());
8525
8526 if (is_polygon_edge(u, v, n))
8527 continue;
8528
8529 for (size_t fj = fi + 1; fj < faces.size() and not changed; ++fj)
8530 {
8531 const auto f2 = faces(fj); // copy for the same reason
8532 if (find_pos(f2, u) == NONE or find_pos(f2, v) == NONE)
8533 continue;
8534
8535 if (not can_merge(pts, f1, f2, u, v))
8536 continue;
8537
8538 Array<size_t> merged = merge_faces(f1, f2, u, v);
8539
8541 new_faces.reserve(faces.size() - 1);
8542 for (size_t i = 0; i < faces.size(); ++i)
8543 {
8544 if (i == fi)
8545 new_faces.append(std::move(merged));
8546 else if (i != fj)
8547 new_faces.append(std::move(faces(i)));
8548 }
8549 faces = std::move(new_faces);
8550 changed = true;
8551 }
8552 }
8553 }
8554 }
8555
8556 // Convert index faces to Polygons.
8557 Array<Polygon> result;
8558 result.reserve(faces.size());
8559 for (size_t fi = 0; fi < faces.size(); ++fi)
8560 {
8561 Polygon p;
8562 for (size_t k = 0; k < faces(fi).size(); ++k)
8563 p.add_vertex(pts(faces(fi)(k)));
8564 if (p.size() >= 3)
8565 {
8566 p.close();
8567 result.append(std::move(p));
8568 }
8569 }
8570
8571 return result;
8572 }
8573 };
8574
8575 // ============================================================================
8576 // Range Tree 2D — Orthogonal Range Queries
8577 // ============================================================================
8578
8604 {
8605 public:
8610 {
8611 size_t tree_index = 0;
8612 size_t lo = 0;
8613 size_t hi = 0;
8619 size_t y_sorted_size = 0;
8620 bool is_leaf = false;
8621 };
8622
8631
8632 private:
8636 struct Node
8637 {
8639 };
8640
8643 size_t n_ = 0;
8644 bool built_ = false;
8645
8647 [[nodiscard]] static size_t lower_bound_y(const Array<Point> & arr,
8648 const Geom_Number & ymin)
8649 {
8650 size_t lo = 0, hi = arr.size();
8651 while (lo < hi)
8652 if (const size_t mid = lo + (hi - lo) / 2; arr(mid).get_y() < ymin)
8653 lo = mid + 1;
8654 else
8655 hi = mid;
8656 return lo;
8657 }
8658
8660 [[nodiscard]] static size_t upper_bound_y(const Array<Point> & arr,
8661 const Geom_Number & ymax)
8662 {
8663 size_t lo = 0, hi = arr.size();
8664 while (lo < hi)
8665 if (const size_t mid = lo + (hi - lo) / 2; arr(mid).get_y() <= ymax)
8666 lo = mid + 1;
8667 else
8668 hi = mid;
8669 return lo;
8670 }
8671
8673 [[nodiscard]] size_t lower_bound_x(const Geom_Number & xval) const
8674 {
8675 size_t lo = 0, hi = n_;
8676 while (lo < hi)
8677 if (const size_t mid = lo + (hi - lo) / 2; pts_(mid).get_x() < xval)
8678 lo = mid + 1;
8679 else
8680 hi = mid;
8681 return lo;
8682 }
8683
8685 [[nodiscard]] size_t upper_bound_x(const Geom_Number & xval) const
8686 {
8687 size_t lo = 0, hi = n_;
8688 while (lo < hi)
8689 if (const size_t mid = lo + (hi - lo) / 2; pts_(mid).get_x() <= xval)
8690 lo = mid + 1;
8691 else
8692 hi = mid;
8693 return lo;
8694 }
8695
8696 void build_node(const size_t node, const size_t lo, const size_t hi)
8697 {
8698 if (lo == hi)
8699 {
8700 tree_(node).y_sorted.append(pts_(lo));
8701 return;
8702 }
8703
8704 const size_t mid = lo + (hi - lo) / 2;
8705 build_node(2 * node, lo, mid);
8706 build_node(2 * node + 1, mid + 1, hi);
8707
8708 // Merge children's y-sorted arrays.
8709 const auto & left = tree_(2 * node).y_sorted;
8710 const auto & right = tree_(2 * node + 1).y_sorted;
8711 auto & merged = tree_(node).y_sorted;
8712 merged.reserve(left.size() + right.size());
8713
8714 size_t i = 0, j = 0;
8715 while (i < left.size() and j < right.size())
8716 if (left(i).get_y() < right(j).get_y() or
8717 (left(i).get_y() == right(j).get_y() &&
8718 left(i).get_x() <= right(j).get_x()))
8719 merged.append(left(i++));
8720 else
8721 merged.append(right(j++));
8722 while (i < left.size())
8723 merged.append(left(i++));
8724 while (j < right.size())
8725 merged.append(right(j++));
8726 }
8727
8728 void query_range(const size_t node, const size_t lo, const size_t hi,
8729 const size_t qlo, const size_t qhi,
8730 const Geom_Number & ymin, const Geom_Number & ymax,
8731 DynList<Point> & out) const
8732 {
8733 if (qlo > qhi or lo > qhi or hi < qlo)
8734 return;
8735
8736 if (qlo <= lo and hi <= qhi)
8737 {
8738 const auto & ys = tree_(node).y_sorted;
8739 const size_t from = lower_bound_y(ys, ymin);
8740 const size_t to = upper_bound_y(ys, ymax);
8741 for (size_t i = from; i < to; ++i)
8742 out.append(ys(i));
8743 return;
8744 }
8745
8746 const size_t mid = lo + (hi - lo) / 2;
8747 query_range(2 * node, lo, mid, qlo, qhi, ymin, ymax, out);
8748 query_range(2 * node + 1, mid + 1, hi, qlo, qhi, ymin, ymax, out);
8749 }
8750
8751 public:
8753 void build(const DynList<Point> & points)
8754 {
8755 pts_ = Array<Point>();
8756 for (DynList<Point>::Iterator it(points); it.has_curr(); it.next_ne())
8757 pts_.append(it.get_curr());
8758
8759 n_ = pts_.size();
8760 if (n_ == 0)
8761 {
8762 built_ = true;
8763 return;
8764 }
8765
8766 quicksort_op(pts_, [](const Point & a, const Point & b)
8767 {
8768 return a.get_x() < b.get_x() ||
8769 (a.get_x() == b.get_x() and a.get_y() < b.get_y());
8770 });
8771
8772 tree_ = Array<Node>();
8773 const size_t tree_sz = 4 * n_ + 4;
8774 tree_.reserve(tree_sz);
8775 for (size_t i = 0; i < tree_sz; ++i)
8776 tree_.append(Node{Array<Point>()});
8777
8778 build_node(1, 0, n_ - 1);
8779 built_ = true;
8780 }
8781
8784 const Geom_Number & xmax,
8785 const Geom_Number & ymin,
8786 const Geom_Number & ymax) const
8787 {
8789 if (not built_ or n_ == 0)
8790 return out;
8791
8792 const size_t qlo = lower_bound_x(xmin);
8793 const size_t qhi = upper_bound_x(xmax);
8794 if (qhi == 0)
8795 return out;
8796
8797 query_range(1, 0, n_ - 1, qlo, qhi - 1, ymin, ymax, out);
8798 return out;
8799 }
8800
8801 [[nodiscard]] size_t size() const noexcept { return n_; }
8802
8803 [[nodiscard]] bool is_empty() const noexcept { return n_ == 0; }
8804
8807 {
8810 if (not built_ or n_ == 0)
8811 return snap;
8812
8813 auto build_debug = [&](const auto & self,
8814 const size_t tree_idx,
8815 const size_t lo,
8816 const size_t hi) -> size_t
8817 {
8818 const size_t out_idx = snap.nodes.size();
8819 snap.nodes.append(DebugNode{});
8820 DebugNode & dn = snap.nodes(out_idx);
8821 dn.tree_index = tree_idx;
8822 dn.lo = lo;
8823 dn.hi = hi;
8824 dn.is_leaf = lo == hi;
8825 dn.xmin = pts_(lo).get_x();
8826 dn.xmax = pts_(hi).get_x();
8827 dn.y_sorted_size = tree_(tree_idx).y_sorted.size();
8828 dn.split_x = pts_((lo + hi) / 2).get_x();
8829
8830 if (lo < hi)
8831 {
8832 const size_t mid = lo + (hi - lo) / 2;
8833 dn.left = self(self, 2 * tree_idx, lo, mid);
8834 dn.right = self(self, 2 * tree_idx + 1, mid + 1, hi);
8835 }
8836
8837 return out_idx;
8838 };
8839
8840 build_debug(build_debug, 1, 0, n_ - 1);
8841 return snap;
8842 }
8843 };
8844
8845 // ============================================================================
8846 // Convex Polygon Offset (Inward / Outward)
8847 // ============================================================================
8848
8864 {
8865 friend class PolygonOffset;
8866
8868 {
8870 }
8871
8876
8877 [[nodiscard]] static bool is_convex(const Array<Point> & v)
8878 {
8879 if (v.size() < 3) return false;
8881 }
8882
8883 static void ensure_ccw(Array<Point> & v)
8884 {
8886 }
8887
8897 static void offset_edge(const Point & a, const Point & b,
8898 const Geom_Number & d, const bool inward,
8899 Point & oa, Point & ob)
8900 {
8901 const mpfr_class dx(b.get_x() - a.get_x());
8902 const mpfr_class dy(b.get_y() - a.get_y());
8903 const mpfr_class len = hypot(dx, dy);
8904 const mpfr_class md(d);
8905
8906 // For CCW polygon: inward normal = (dy, -dx)/len, outward = (-dy, dx)/len
8907 mpfr_class nx, ny;
8908 if (inward)
8909 {
8910 nx = -dy * md / len;
8911 ny = dx * md / len;
8912 }
8913 else
8914 {
8915 nx = dy * md / len;
8916 ny = -dx * md / len;
8917 }
8918
8920 Geom_Number(mpfr_class(a.get_y()) + ny));
8922 Geom_Number(mpfr_class(b.get_y()) + ny));
8923 }
8924
8926 [[nodiscard]] static Point line_intersect(const Point & a1, const Point & a2,
8927 const Point & b1, const Point & b2)
8928 {
8929 const Geom_Number &a1x = a1.get_x(), &a1y = a1.get_y();
8930 const Geom_Number &a2x = a2.get_x(), &a2y = a2.get_y();
8931 const Geom_Number &b1x = b1.get_x(), &b1y = b1.get_y();
8932 const Geom_Number &b2x = b2.get_x(), &b2y = b2.get_y();
8933
8934 const Geom_Number dax = a2x - a1x, day = a2y - a1y;
8935 const Geom_Number dbx = b2x - b1x, dby = b2y - b1y;
8936 const Geom_Number denom = dax * dby - day * dbx;
8937 if (denom == 0)
8938 {
8939 // Consecutive offset lines can be parallel when the input has a
8940 // collinear triple. In that case, use the shared shifted vertex.
8941 return a2;
8942 }
8943
8944 const Geom_Number t = ((b1x - a1x) * dby - (b1y - a1y) * dbx) / denom;
8945 return {a1x + t * dax, a1y + t * day};
8946 }
8947
8948 public:
8957 const Geom_Number & distance)
8958 {
8959 ah_domain_error_if(not convex_poly.is_closed()) << "Polygon must be closed";
8960
8962
8963 ah_domain_error_if(v.size() < 3) << "Polygon must have >= 3 vertices";
8964 ah_domain_error_if(not is_convex(v)) << "Polygon must be convex";
8965
8966 if (distance == 0) return convex_poly;
8967
8968 ensure_ccw(v);
8969 const size_t n = v.size();
8970
8972 hps.reserve(n);
8973
8974 for (size_t i = 0; i < n; ++i)
8975 {
8976 const size_t j = (i + 1) % n;
8977 Point oa, ob;
8978 offset_edge(v(i), v(j), distance, true, oa, ob);
8980 }
8981
8982 constexpr HalfPlaneIntersection hpi;
8983 return hpi(hps);
8984 }
8985
8993 static Polygon outward(const Polygon & convex_poly, const Geom_Number & distance)
8994 {
8995 ah_domain_error_if(not convex_poly.is_closed()) << "Polygon must be closed";
8996
8998
8999 ah_domain_error_if(v.size() < 3) << "Polygon must have >= 3 vertices";
9000 ah_domain_error_if(not is_convex(v)) << "Polygon must be convex";
9001
9002 if (distance == 0) return convex_poly;
9003
9004 ensure_ccw(v);
9005 const size_t n = v.size();
9006
9007 // Compute offset lines for each edge.
9008 Array<Point> oa, ob; // offset edge endpoints
9009 oa.reserve(n);
9010 ob.reserve(n);
9011 for (size_t i = 0; i < n; ++i)
9012 {
9013 Point a, b;
9014 offset_edge(v(i), v((i + 1) % n), distance, false, a, b);
9015 oa.append(a);
9016 ob.append(b);
9017 }
9018
9019 // Intersect consecutive offset lines → new vertices.
9020 Polygon result;
9021 for (size_t i = 0; i < n; ++i)
9022 {
9023 const size_t j = (i + 1) % n;
9024 const Geom_Number dax = ob(i).get_x() - oa(i).get_x();
9025 const Geom_Number day = ob(i).get_y() - oa(i).get_y();
9026 const Geom_Number dbx = ob(j).get_x() - oa(j).get_x();
9027 const Geom_Number dby = ob(j).get_y() - oa(j).get_y();
9028 if (const Geom_Number denom = dax * dby - day * dbx; denom == 0)
9029 // Collinear triples produce consecutive parallel offset lines:
9030 // there is no corner vertex to add for that junction.
9031 continue;
9032
9033 result.add_vertex(line_intersect(oa(i), ob(i), oa(j), ob(j)));
9034 }
9035 if (result.size() >= 3)
9036 result.close();
9037 return result;
9038 }
9039 };
9040
9041 // ============================================================================
9042 // General Polygon Offset (non-convex)
9043 // ============================================================================
9044
9072 {
9073 public:
9077 enum class JoinType
9078 {
9079 Miter,
9080 Bevel
9081 };
9082
9086 struct Result
9087 {
9089
9094 {
9095 return polygons.is_empty();
9096 }
9097
9099 {
9100 return polygons.size();
9101 }
9102 };
9103
9114 [[nodiscard]] Result
9115 operator()(const Polygon & poly, const Geom_Number & distance,
9117 const Geom_Number & miter_limit = Geom_Number(2)) const
9118 {
9119 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
9120 ah_domain_error_if(poly.size() < 3) << "Polygon must have >= 3 vertices";
9121
9122 if (distance == 0)
9123 {
9124 Result r;
9125 r.polygons.append(poly);
9126 return r;
9127 }
9128
9131
9132 const Array<Point> raw = compute_raw_offset(v, distance, join, miter_limit);
9133
9134 if (raw.size() < 3)
9135 return Result{};
9136
9137 return cleanup(raw);
9138 }
9139
9146 [[nodiscard]] Polygon
9147 offset_polygon(const Polygon & poly, const Geom_Number & distance,
9149 const Geom_Number & miter_limit = Geom_Number(2)) const
9150 {
9151 Result r = (*this)(poly, distance, join, miter_limit);
9152 if (r.is_empty())
9153 return {};
9154
9155 size_t best = 0;
9156 Geom_Number best_area = abs_area(r.polygons(0));
9157 for (size_t i = 1; i < r.polygons.size(); ++i)
9158 if (const Geom_Number a = abs_area(r.polygons(i)); a > best_area)
9159 {
9160 best_area = a;
9161 best = i;
9162 }
9163 return r.polygons(best);
9164 }
9165
9166 private:
9167 // ----- helpers -----
9168
9170 {
9172 if (a < 0) a = -a;
9173 return a;
9174 }
9175
9178 static void offset_edge(const Point & a, const Point & b,
9179 const Geom_Number & d, const bool inward,
9180 Point & oa, Point & ob)
9181 {
9182 ConvexPolygonOffset::offset_edge(a, b, d, inward, oa, ob);
9183 }
9184
9186 [[nodiscard]] static Point line_intersect(const Point & a1, const Point & a2,
9187 const Point & b1, const Point & b2)
9188 {
9189 return ConvexPolygonOffset::line_intersect(a1, a2, b1, b2);
9190 }
9191
9192 // ----- Phase 1: raw offset polygon -----
9193
9195 [[nodiscard]] static Geom_Number sq_dist(const Point & a, const Point & b)
9196 {
9197 const Geom_Number dx = b.get_x() - a.get_x();
9198 const Geom_Number dy = b.get_y() - a.get_y();
9199 return dx * dx + dy * dy;
9200 }
9201
9202 [[nodiscard]] static Array<Point>
9203 compute_raw_offset(const Array<Point> & v, const Geom_Number & distance,
9204 const JoinType join, const Geom_Number & miter_limit)
9205 {
9206 const size_t n = v.size();
9207 const bool inward = distance < 0;
9208 Geom_Number d = distance;
9209 if (inward) d = -distance;
9210
9211 // Compute offset edge pairs.
9213 oa.reserve(n);
9214 ob.reserve(n);
9215 for (size_t i = 0; i < n; ++i)
9216 {
9217 Point a, b;
9218 offset_edge(v(i), v((i + 1) % n), d, inward, a, b);
9219 oa.append(a);
9220 ob.append(b);
9221 }
9222
9223 // Join consecutive offset edges, tracking junction boundaries.
9224 const Geom_Number miter_sq = miter_limit * miter_limit * d * d;
9226 raw.reserve(n * 2);
9227
9228 // junction_first[i] = index in raw where junction i's vertices begin.
9229 // junction_count[i] = number of vertices emitted for junction i.
9232 junction_count.reserve(n);
9233
9234 for (size_t i = 0; i < n; ++i)
9235 {
9236 const size_t j = (i + 1) % n;
9237 junction_first.append(raw.size());
9238
9239 const Geom_Number dax = ob(i).get_x() - oa(i).get_x();
9240 const Geom_Number day = ob(i).get_y() - oa(i).get_y();
9241 const Geom_Number dbx = ob(j).get_x() - oa(j).get_x();
9242 const Geom_Number dby = ob(j).get_y() - oa(j).get_y();
9243
9244 if (const Geom_Number denom = dax * dby - day * dbx; denom == 0)
9245 {
9246 junction_count.append(0);
9247 continue;
9248 }
9249
9250 if (const Point miter = line_intersect(oa(i), ob(i), oa(j), ob(j)); join == JoinType::Bevel or
9251 (join == JoinType::Miter &&
9252 sq_dist(v((i + 1) % n), miter) >
9253 miter_sq))
9254 {
9255 raw.append(ob(i));
9256 raw.append(oa(j));
9257 junction_count.append(2);
9258 }
9259 else
9260 {
9261 raw.append(miter);
9262 junction_count.append(1);
9263 }
9264 }
9265
9266 // Validate edge directions: for each span between consecutive
9267 // junctions, the direction must match the corresponding original edge.
9268 // A reversed direction indicates that the offset has "crossed through"
9269 // the polygon (excessive offset).
9270 for (size_t i = 0; i < n; ++i)
9271 {
9272 const size_t j = (i + 1) % n;
9273 if (junction_count(i) == 0 || junction_count(j) == 0)
9274 continue;
9275
9276 // Span runs from the last vertex of junction i to the first
9277 // vertex of junction j, along offset edge (i+1)%n.
9278 const size_t span_start = junction_first(i) + junction_count(i) - 1;
9279 const size_t span_end = junction_first(j);
9280
9281 const Geom_Number sdx = raw(span_end).get_x() - raw(span_start).get_x();
9282 const Geom_Number sdy = raw(span_end).get_y() - raw(span_start).get_y();
9283
9284 // Direction of original edge (i+1)%n: v(j) → v((j+1)%n).
9285 const Geom_Number edx = v((j + 1) % n).get_x() - v(j).get_x();
9286 const Geom_Number edy = v((j + 1) % n).get_y() - v(j).get_y();
9287
9288 if (sdx * edx + sdy * edy < 0)
9289 return {}; // direction reversed → excessive offset
9290 }
9291
9292 return raw;
9293 }
9294
9295 // ----- Phase 2: self-intersection cleanup -----
9296
9299 {
9302 Geom_Number alpha_i, alpha_j; // parameter along each edge
9303 };
9304
9307 {
9309 bool is_intersection = false;
9310 size_t partner = SIZE_MAX; // index of same point on other edge
9311 bool visited = false;
9313 };
9314
9316 [[nodiscard]] static Geom_Number
9317 edge_param(const Point & P, const Point & Q, const Point & I)
9318 {
9319 if (const Geom_Number dx = Q.get_x() - P.get_x(); dx != 0)
9320 return (I.get_x() - P.get_x()) / dx;
9321 return (I.get_y() - P.get_y()) / (Q.get_y() - P.get_y());
9322 }
9323
9327 {
9328 const size_t n = raw.size();
9330
9331 for (size_t i = 0; i < n; ++i)
9332 {
9333 const Point & a0 = raw(i);
9334 const Point & a1 = raw((i + 1) % n);
9335 const Segment sa(a0, a1);
9336
9337 for (size_t j = i + 2; j < n; ++j)
9338 {
9339 // Skip adjacent edges (they share a vertex).
9340 if (j == (i + n - 1) % n)
9341 continue;
9342 if (i == 0 && j == n - 1)
9343 continue;
9344
9345 const Point & b0 = raw(j);
9346 const Point & b1 = raw((j + 1) % n);
9347 const Segment sb(b0, b1);
9348
9349 if (not sa.intersects_properly_with(sb))
9350 continue;
9351
9352 const Point ip = sa.intersection_with(sb);
9353 const Geom_Number ai = edge_param(a0, a1, ip);
9354 const Geom_Number aj = edge_param(b0, b1, ip);
9355 isects.append({ip, i, j, ai, aj});
9356 }
9357 }
9358
9359 return isects;
9360 }
9361
9363 static void sort_by_alpha(Array<size_t> & idx,
9364 const Array<Geom_Number> & keys)
9365 {
9366 for (size_t i = 1; i < idx.size(); ++i)
9367 {
9368 const size_t val = idx(i);
9369 const Geom_Number & key = keys(val);
9370 size_t j = i;
9371 while (j > 0 && keys(idx(j - 1)) > key)
9372 {
9373 idx(j) = idx(j - 1);
9374 --j;
9375 }
9376 idx(j) = val;
9377 }
9378 }
9379
9381 [[nodiscard]] static Array<AugVertex>
9384 {
9385 const size_t n = raw.size();
9386 const size_t m = isects.size();
9387
9388 // Position in the augmented list for each intersection occurrence.
9389 // Each intersection appears twice (once on each edge).
9391 pos_i.reserve(m);
9392 pos_j.reserve(m);
9393 for (size_t k = 0; k < m; ++k)
9394 {
9395 pos_i.append(0);
9396 pos_j.append(0);
9397 }
9398
9400 aug.reserve(n + 2 * m);
9401
9402 for (size_t i = 0; i < n; ++i)
9403 {
9404 // Append original vertex.
9405 AugVertex v;
9406 v.pt = raw(i);
9407 aug.append(v);
9408
9409 // Collect intersections on this edge.
9412 for (size_t k = 0; k < m; ++k)
9413 {
9414 if (isects(k).edge_i == i)
9415 {
9417 edge_alphas.append(isects(k).alpha_i);
9418 }
9419 else if (isects(k).edge_j == i)
9420 {
9421 edge_isects.append(k);
9422 edge_alphas.append(isects(k).alpha_j);
9423 }
9424 }
9425
9426 if (edge_isects.is_empty())
9427 continue;
9428
9429 // Sort by alpha.
9430 Array<size_t> order;
9431 for (size_t p = 0; p < edge_isects.size(); ++p)
9432 order.append(p);
9433 sort_by_alpha(order, edge_alphas);
9434
9435 for (size_t p = 0; p < order.size(); ++p)
9436 {
9437 const size_t k = edge_isects(order(p));
9438 const size_t aug_idx = aug.size();
9439
9440 // Record position for partner linking.
9441 if (isects(k).edge_i == i)
9442 pos_i(k) = aug_idx;
9443 else
9444 pos_j(k) = aug_idx;
9445
9446 AugVertex iv;
9447 iv.pt = isects(k).pt;
9448 iv.is_intersection = true;
9449 iv.alpha = edge_alphas(order(p));
9450 aug.append(iv);
9451 }
9452 }
9453
9454 // Set partner links.
9455 for (size_t k = 0; k < m; ++k)
9456 {
9457 aug(pos_i(k)).partner = pos_j(k);
9458 aug(pos_j(k)).partner = pos_i(k);
9459 }
9460
9461 return aug;
9462 }
9463
9466 {
9467 Polygon p;
9468 for (size_t i = 0; i < pts.size(); ++i)
9469 p.add_vertex(pts(i));
9470 if (pts.size() >= 3)
9471 p.close();
9472 return p;
9473 }
9474
9481 [[nodiscard]] static Array<Polygon>
9483 {
9484 Array<Polygon> result;
9485 const size_t N = aug.size();
9486 const size_t safety = N * 2 + 10;
9487
9488 for (size_t s = 0; s < N; ++s)
9489 {
9490 if (not aug(s).is_intersection || aug(s).visited)
9491 continue;
9492
9493 // Trace one contour starting from intersection s.
9494 // Jump to partner first, then walk forward.
9495 Array<Point> boundary;
9496 size_t steps = 0;
9497
9498 aug(s).visited = true;
9499 if (aug(s).partner != SIZE_MAX)
9500 aug(aug(s).partner).visited = true;
9501
9502 // Jump to partner to enter the "other branch".
9503 size_t idx = (aug(s).partner != SIZE_MAX) ? aug(s).partner : s;
9504 const size_t entry = idx; // we'll loop until we return here
9505
9506 do
9507 {
9508 // Append current vertex and walk forward.
9509 boundary.append(aug(idx).pt);
9510 idx = (idx + 1) % N;
9511
9512 while (not aug(idx).is_intersection)
9513 {
9514 boundary.append(aug(idx).pt);
9515 idx = (idx + 1) % N;
9516 if (++steps > safety)
9517 goto done_contour;
9518 }
9519
9520 // At the next intersection — mark visited and jump to partner.
9521 aug(idx).visited = true;
9522 if (aug(idx).partner != SIZE_MAX)
9523 {
9524 aug(aug(idx).partner).visited = true;
9525 idx = aug(idx).partner;
9526 }
9527
9528 if (++steps > safety)
9529 break;
9530 }
9531 while (idx != entry);
9532
9534 if (boundary.size() >= 3)
9535 {
9536 // Keep only CCW contours (positive signed area).
9537 if (const Geom_Number sa = GeomPolygonUtils::signed_double_area(boundary); sa > 0)
9538 result.append(build_poly(boundary));
9539 }
9540 }
9541
9542 return result;
9543 }
9544
9547 {
9548 const auto isects = find_self_intersections(raw);
9549
9550 if (isects.is_empty())
9551 {
9552 // No self-intersections — check if the raw polygon is valid CCW.
9554 Result r;
9555 if (sa > 0 && raw.size() >= 3)
9556 r.polygons.append(build_poly(raw));
9557 return r;
9558 }
9559
9560 auto aug = build_augmented(raw, isects);
9561 Result r;
9563 return r;
9564 }
9565 };
9566
9567 // ============================================================================
9568 // Visibility Polygon
9569 // ============================================================================
9570
9594 {
9596 [[nodiscard]] static int angle_quadrant(const Geom_Number & dx,
9597 const Geom_Number & dy)
9598 {
9599 if (dx > 0 and dy >= 0) return 0;
9600 if (dx <= 0 and dy > 0) return 1;
9601 if (dx < 0 and dy <= 0) return 2;
9602 return 3; // dx >= 0 and dy < 0
9603 }
9604
9606 [[nodiscard]] static bool angle_less(const Point & q,
9607 const Point & a,
9608 const Point & b)
9609 {
9610 const Geom_Number dax = a.get_x() - q.get_x();
9611 const Geom_Number day = a.get_y() - q.get_y();
9612 const Geom_Number dbx = b.get_x() - q.get_x();
9613 const Geom_Number dby = b.get_y() - q.get_y();
9614
9615 const int qa = angle_quadrant(dax, day);
9616 const int qb = angle_quadrant(dbx, dby);
9617 if (qa != qb) return qa < qb;
9618
9619 // Same quadrant: cross product. Positive = a is before b (CCW).
9620 if (const Geom_Number cross = dax * dby - day * dbx; cross != 0)
9621 return cross > 0;
9622
9623 // Same angle: closer point first.
9624 return dax * dax + day * day < (dbx * dbx + dby * dby);
9625 }
9626
9629 [[nodiscard]] static Geom_Number ray_param(const Point & q,
9630 const Point & dir,
9631 const Point & e0,
9632 const Point & e1)
9633 {
9634 const Geom_Number rdx = dir.get_x() - q.get_x();
9635 const Geom_Number rdy = dir.get_y() - q.get_y();
9636 const Geom_Number edx = e1.get_x() - e0.get_x();
9637 const Geom_Number edy = e1.get_y() - e0.get_y();
9638 const Geom_Number denom = rdx * edy - rdy * edx;
9639 if (denom == 0)
9640 return {-1};
9641
9642 const Geom_Number dx = e0.get_x() - q.get_x();
9643 const Geom_Number dy = e0.get_y() - q.get_y();
9644 const Geom_Number t = (dx * edy - dy * edx) / denom;
9645 const Geom_Number s = (dx * rdy - dy * rdx) / denom;
9646 if (s < 0 or s > 1 or t < 0)
9647 return {-1};
9648 return t;
9649 }
9650
9652 [[nodiscard]] static Point ray_edge_hit(const Point & q,
9653 const Point & dir,
9654 const Point & e0,
9655 const Point & e1)
9656 {
9657 const Geom_Number t = ray_param(q, dir, e0, e1);
9658 const Geom_Number rdx = dir.get_x() - q.get_x();
9659 const Geom_Number rdy = dir.get_y() - q.get_y();
9660 return {q.get_x() + t * rdx, q.get_y() + t * rdy};
9661 }
9662
9672 {
9673 struct EdgeKey
9674 {
9676 size_t edge;
9677 };
9678
9680 {
9681 bool operator()(const EdgeKey & a, const EdgeKey & b) const
9682 {
9683 if (a.t != b.t) return a.t < b.t;
9684 return a.edge < b.edge;
9685 }
9686 };
9687
9689 Array<Geom_Number> params_; // edge → ray_param at insertion time
9690 Array<bool> in_tree_; // edge → currently in tree
9692 const size_t n_;
9693 const Point & query_;
9694
9695 public:
9696 EdgeStatusTree(const Array<Point> & verts, const size_t n,
9697 const Point & query)
9698 : verts_(verts), n_(n), query_(query)
9699 {
9700 for (size_t i = 0; i < n; ++i)
9701 {
9702 params_.append(Geom_Number(-1));
9703 in_tree_.append(false);
9704 }
9705 }
9706
9707 void insert(const size_t edge, const Point & dir)
9708 {
9709 if (in_tree_(edge))
9710 return;
9711
9712 const Geom_Number t = ray_param(query_, dir,
9713 verts_(edge), verts_((edge + 1) % n_));
9714 params_(edge) = t;
9715 in_tree_(edge) = true;
9716 tree_.insert(EdgeKey{t, edge});
9717 }
9718
9719 void erase(const size_t edge)
9720 {
9721 if (not in_tree_(edge))
9722 return;
9723
9724 tree_.remove(EdgeKey{params_(edge), edge});
9725 in_tree_(edge) = false;
9726 }
9727
9730 [[nodiscard]] size_t min() const
9731 {
9732 if (tree_.is_empty())
9733 return n_;
9734 return tree_.min().edge;
9735 }
9736
9737 [[nodiscard]] bool contains(const size_t edge) const
9738 {
9739 return in_tree_(edge);
9740 }
9741
9742 [[nodiscard]] bool is_empty() const { return tree_.is_empty(); }
9743 };
9744
9745 public:
9754 const Point & query) const
9755 {
9756 ah_domain_error_if(not polygon.is_closed()) << "Polygon must be closed";
9757 ah_domain_error_if(polygon.size() < 3) << "Polygon must have >= 3 vertices";
9759 << "Query point must be strictly inside the polygon";
9760
9761 // Extract vertices.
9763 for (Polygon::Vertex_Iterator it(polygon); it.has_curr(); it.next_ne())
9764 verts.append(it.get_current_vertex());
9765 const size_t n = verts.size();
9766
9767 // Sort vertex indices by angle from query.
9768 Array<size_t> order;
9769 order.reserve(n);
9770 for (size_t i = 0; i < n; ++i)
9771 order.append(i);
9772 quicksort_op(order, [&](size_t a, size_t b)
9773 {
9774 return angle_less(query, verts(a), verts(b));
9775 });
9776
9777 // Determine which edges cross the initial ray (wrapping edges).
9778 // Their angular span straddles 0°, so their start/end events must be
9779 // swapped relative to non-wrapping edges.
9780 const Point init_dir(query.get_x() + 1, query.get_y());
9782 wrapping.reserve(n);
9783 for (size_t ei = 0; ei < n; ++ei)
9784 {
9785 const Geom_Number t = ray_param(query, init_dir,
9786 verts(ei), verts((ei + 1) % n));
9787 wrapping.append(t > 0);
9788 }
9789
9790 // Build edge event tables: for each vertex, which edges start/end there.
9791 // Edge i goes from verts(i) to verts((i+1)%n).
9792 // For non-wrapping edges: insert at the smaller-angle vertex, remove at
9793 // the larger-angle vertex (normal sweep order).
9794 // For wrapping edges (already in the initial status): remove at the
9795 // smaller-angle vertex (ray exits their span), insert at the
9796 // larger-angle vertex (ray re-enters their span).
9798 starts.reserve(n);
9799 ends.reserve(n);
9800 for (size_t i = 0; i < n; ++i)
9801 starts.append(DynList<size_t>());
9802 for (size_t i = 0; i < n; ++i)
9803 ends.append(DynList<size_t>());
9804
9805 for (size_t ei = 0; ei < n; ++ei)
9806 {
9807 const size_t a = ei, b = (ei + 1) % n;
9808 const bool a_less = angle_less(query, verts(a), verts(b));
9809 const size_t small_v = a_less ? a : b;
9810 const size_t large_v = a_less ? b : a;
9811
9812 if (wrapping(ei))
9813 {
9814 ends(small_v).append(ei); // remove when ray exits span
9815 starts(large_v).append(ei); // re-insert when ray re-enters
9816 }
9817 else
9818 {
9819 starts(small_v).append(ei); // insert when ray enters span
9820 ends(large_v).append(ei); // remove when ray exits span
9821 }
9822 }
9823
9824 // Initialize status with wrapping edges.
9825 EdgeStatusTree status(verts, n, query);
9826 for (size_t ei = 0; ei < n; ++ei)
9827 if (wrapping(ei))
9828 status.insert(ei, init_dir);
9829
9830 // Build visibility polygon.
9832
9833 for (size_t oi = 0; oi < n; ++oi)
9834 {
9835 const size_t vi = order(oi);
9836 const Point & v = verts(vi);
9837 const Point dir = v; // ray from query toward v
9838
9839 // Nearest edge BEFORE processing events at this vertex.
9840 const size_t prev_near = status.min();
9841
9842 // Remove ending edges.
9843 for (DynList<size_t>::Iterator it(ends(vi)); it.has_curr(); it.next_ne())
9844 status.erase(it.get_curr());
9845
9846 // Insert starting edges.
9847 for (DynList<size_t>::Iterator it(starts(vi)); it.has_curr(); it.next_ne())
9848 status.insert(it.get_curr(), dir);
9849
9850 // Nearest edge AFTER processing events.
9851 const size_t curr_near = status.min();
9852
9853 // Determine visibility.
9854 bool v_is_on_edge = false;
9855 if (prev_near < n)
9856 {
9857 const size_t ea = prev_near, eb = (prev_near + 1) % n;
9858 if (vi == ea or vi == eb) v_is_on_edge = true;
9859 }
9860 if (curr_near < n and not v_is_on_edge)
9861 {
9862 const size_t ea = curr_near, eb = (curr_near + 1) % n;
9863 if (vi == ea or vi == eb) v_is_on_edge = true;
9864 }
9865
9866 if (v_is_on_edge)
9867 {
9868 // Vertex is on the nearest edge — directly visible.
9869 vis_pts.append(v);
9870 }
9871 else if (prev_near != curr_near)
9872 {
9873 // Nearest edge changed — add intersection with old and new.
9874 if (prev_near < n)
9875 vis_pts.append(ray_edge_hit(query, dir,
9876 verts(prev_near), verts((prev_near + 1) % n)));
9877 vis_pts.append(v);
9878 if (curr_near < n)
9879 vis_pts.append(ray_edge_hit(query, dir,
9880 verts(curr_near), verts((curr_near + 1) % n)));
9881 }
9882 }
9883
9884 // Remove duplicate consecutive points.
9885 Polygon result;
9886 for (size_t i = 0; i < vis_pts.size(); ++i)
9887 {
9888 if (i > 0 and vis_pts(i) == vis_pts(i - 1))
9889 continue;
9890 result.add_vertex(vis_pts(i));
9891 }
9892 if (result.size() >= 3)
9893 {
9894 // Remove last if the same as first.
9896 for (Polygon::Vertex_Iterator it(result); it.has_curr(); it.next_ne())
9897 rv.append(it.get_current_vertex());
9898 if (rv.size() >= 2 and rv(0) == rv(rv.size() - 1))
9899 {
9901 for (size_t i = 0; i + 1 < rv.size(); ++i)
9902 cleaned.add_vertex(rv(i));
9903 if (cleaned.size() >= 3)
9904 cleaned.close();
9905 return cleaned;
9906 }
9907 result.close();
9908 }
9909 return result;
9910 }
9911 };
9912
9913 // ============================================================================
9914 // Shortest Path in Simple Polygon (Lee-Preparata Funnel)
9915 // ============================================================================
9916
9941 {
9942 static constexpr size_t NONE = ~static_cast<size_t>(0);
9943
9944 struct ITri
9945 {
9946 size_t v[3];
9947 size_t adj[3]; // adj[i] = neighbour sharing edge opposite v[i], or NONE
9948 };
9949
9951 [[nodiscard]] static size_t find_index(const Array<Point> & pts,
9952 const Point & p)
9953 {
9954 for (size_t i = 0; i < pts.size(); ++i)
9955 if (pts(i) == p) return i;
9956 return NONE;
9957 }
9958
9961 const DynList<Triangle> & tl)
9962 {
9963 // Convert to indexed form.
9965 for (DynList<Triangle>::Iterator it(tl); it.has_curr(); it.next_ne())
9966 {
9967 const Triangle & t = it.get_curr();
9968 ITri ti{};
9969 ti.v[0] = find_index(pts, t.get_p1());
9970 ti.v[1] = find_index(pts, t.get_p2());
9971 ti.v[2] = find_index(pts, t.get_p3());
9972 ti.adj[0] = ti.adj[1] = ti.adj[2] = NONE;
9973 tris.append(ti);
9974 }
9975
9976 // Build adjacency via an edge map.
9977 // Key: (min_idx, max_idx), Value: (triangle_index, local_edge_index)
9978 struct EdgeKey
9979 {
9980 size_t u, v;
9981 };
9982
9983 struct CmpEdge
9984 {
9985 bool operator()(const EdgeKey & a, const EdgeKey & b) const
9986 {
9987 if (a.u != b.u)
9988 return a.u < b.u;
9989 return a.v < b.v;
9990 }
9991 };
9992
9993 struct EdgeVal
9994 {
9995 size_t tri;
9996 size_t local;
9997 };
9998
10000
10001 // We need a separate map. Use parallel arrays since DynSetTree doesn't
10002 // store mapped values. Use a simpler approach: flat array scan.
10003 struct EdgeEntry
10004 {
10005 size_t u, v, tri, local;
10006 };
10007 Array<EdgeEntry> edges;
10008 edges.reserve(tris.size() * 3);
10009
10010 for (size_t ti = 0; ti < tris.size(); ++ti)
10011 for (int e = 0; e < 3; ++e)
10012 {
10013 size_t u = tris(ti).v[(e + 1) % 3];
10014 size_t v = tris(ti).v[(e + 2) % 3];
10015 if (u > v)
10016 {
10017 const size_t tmp = u;
10018 u = v;
10019 v = tmp;
10020 }
10021 edges.append(EdgeEntry{u, v, ti, size_t(e)});
10022 }
10023
10024 // Sort edges and find matching pairs.
10025 quicksort_op(edges, [](const EdgeEntry & a, const EdgeEntry & b)
10026 {
10027 if (a.u != b.u)
10028 return a.u < b.u;
10029 if (a.v != b.v)
10030 return a.v < b.v;
10031 return a.tri < b.tri;
10032 });
10033
10034 for (size_t i = 0; i + 1 < edges.size(); ++i)
10035 if (edges(i).u == edges(i + 1).u and edges(i).v == edges(i + 1).v)
10036 {
10037 const size_t t1 = edges(i).tri, l1 = edges(i).local;
10038 const size_t t2 = edges(i + 1).tri, l2 = edges(i + 1).local;
10039 tris(t1).adj[l1] = t2;
10040 tris(t2).adj[l2] = t1;
10041 ++i; // skip the matched pair
10042 }
10043
10044 return tris;
10045 }
10046
10049 const ITri & t,
10050 const Point & p)
10051 {
10052 const Orientation o0 = orientation(pts(t.v[0]), pts(t.v[1]), p);
10053 const Orientation o1 = orientation(pts(t.v[1]), pts(t.v[2]), p);
10054 const Orientation o2 = orientation(pts(t.v[2]), pts(t.v[0]), p);
10055 const bool has_cw = o0 == Orientation::CW or o1 == Orientation::CW
10057 const bool has_ccw = o0 == Orientation::CCW or o1 == Orientation::CCW
10059 return not (has_cw and has_ccw);
10060 }
10061
10063 [[nodiscard]] static size_t find_tri(const Array<Point> & pts,
10064 const Array<ITri> & tris,
10065 const Point & p)
10066 {
10067 for (size_t i = 0; i < tris.size(); ++i)
10068 if (point_in_triangle(pts, tris(i), p)) return i;
10069 return NONE;
10070 }
10071
10074 const size_t src, const size_t dst)
10075 {
10076 if (src == dst)
10077 {
10078 Array<size_t> s;
10079 s.append(src);
10080 return s;
10081 }
10082
10083 Array<size_t> parent;
10084 parent.reserve(tris.size());
10085 for (size_t i = 0; i < tris.size(); ++i)
10086 parent.append(NONE);
10087 parent(src) = src; // sentinel
10088
10089 DynList<size_t> queue;
10090 queue.append(src);
10091
10092 while (not queue.is_empty())
10093 {
10094 const size_t cur = queue.remove_first();
10095 if (cur == dst)
10096 break;
10097 for (unsigned long nb: tris(cur).adj)
10098 if (nb != NONE and parent(nb) == NONE)
10099 {
10100 parent(nb) = cur;
10101 queue.append(nb);
10102 }
10103 }
10104
10105 // Reconstruct path.
10106 Array<size_t> path;
10107 for (size_t cur = dst; cur != src; cur = parent(cur))
10108 path.append(cur);
10109 path.append(src);
10110
10111 // Reverse.
10112 for (size_t i = 0; i < path.size() / 2; ++i)
10113 {
10114 const size_t tmp = path(i);
10115 path(i) = path(path.size() - 1 - i);
10116 path(path.size() - 1 - i) = tmp;
10117 }
10118 return path;
10119 }
10120
10121
10123 [[nodiscard]] static Geom_Number cross(const Point & a,
10124 const Point & b,
10125 const Point & c)
10126 {
10127 return (b.get_x() - a.get_x()) * (c.get_y() - a.get_y()) -
10128 (b.get_y() - a.get_y()) * (c.get_x() - a.get_x());
10129 }
10130
10131 public:
10141 const Point & source,
10142 const Point & target) const
10143 {
10144 ah_domain_error_if(not polygon.is_closed()) << "Polygon must be closed";
10145 ah_domain_error_if(polygon.size() < 3) << "Polygon must have >= 3 vertices";
10147 << "Source must be inside the polygon";
10149 << "Target must be inside the polygon";
10150
10151 DynList<Point> result;
10152 if (source == target)
10153 {
10154 result.append(source);
10155 return result;
10156 }
10157
10158 // Check direct line of sight.
10159 {
10160 const Segment seg(source, target);
10161 bool blocked = false;
10162 for (Polygon::Segment_Iterator it(polygon); it.has_curr() and not blocked; it.next_ne())
10163 if (const Segment edge = it.get_current_segment(); seg.intersects_properly_with(edge))
10164 blocked = true;
10165 if (not blocked)
10166 {
10167 result.append(source);
10168 result.append(target);
10169 return result;
10170 }
10171 }
10172
10173 // Extract vertices.
10175 for (Polygon::Vertex_Iterator it(polygon); it.has_curr(); it.next_ne())
10176 pts.append(it.get_current_vertex());
10177
10178 // Triangulate.
10181
10182 ah_domain_error_if(tri_list.is_empty()) << "Triangulation failed";
10183
10184 // Build indexed triangulation with adjacency.
10186
10187 // Locate source and target triangles.
10188 const size_t src_t = find_tri(pts, tris, source);
10189 const size_t dst_t = find_tri(pts, tris, target);
10190
10191 ah_domain_error_if(src_t == NONE) << "Could not locate source in triangulation";
10192 ah_domain_error_if(dst_t == NONE) << "Could not locate target in triangulation";
10193
10194 // Find sleeve.
10196
10197 if (sleeve.size() <= 1)
10198 {
10199 result.append(source);
10200 result.append(target);
10201 return result;
10202 }
10203
10204 // ----------------------------------------------------------------
10205 // Simple Stupid Funnel Algorithm (Mononen / Lee-Preparata).
10206 //
10207 // Build a portal list from the sleeve diagonals, then walk it
10208 // maintaining a funnel (apex, left boundary, right boundary).
10209 // ----------------------------------------------------------------
10210
10211 // A portal is a pair (left, right) as seen when walking from source
10212 // toward target. Portal 0 = (source, source), last = (target, target).
10213 struct Portal
10214 {
10215 Point left;
10216 Point right;
10217 };
10218 Array<Portal> portals;
10219 portals.append(Portal{source, source});
10220
10221 // For each sleeve diagonal, find shared vertices and the "opposite"
10222 // vertex in the current triangle (the one NOT shared with the next).
10223 // Orient the portal using cross(V_prev, s0, s1):
10224 // cross < 0 → left = s0, right = s1
10225 // cross > 0 → left = s1, right = s0
10226 // This is robust regardless of triangulation order.
10227
10228 for (size_t i = 0; i + 1 < sleeve.size(); ++i)
10229 {
10230 size_t s0 = 0, s1 = 0;
10231 size_t v_prev = 0; {
10232 int sc = 0;
10233 bool found[3] = {false, false, false};
10234 for (int a = 0; a < 3; ++a)
10235 for (unsigned long b: tris(sleeve(i + 1)).v)
10236 if (tris(sleeve(i)).v[a] == b)
10237 {
10238 found[a] = true;
10239 break;
10240 }
10241
10242 for (int a = 0; a < 3; ++a)
10243 {
10244 if (found[a])
10245 {
10246 if (sc == 0) s0 = tris(sleeve(i)).v[a];
10247 else s1 = tris(sleeve(i)).v[a];
10248 ++sc;
10249 }
10250 else
10251 v_prev = tris(sleeve(i)).v[a];
10252 }
10253 }
10254
10255 const Geom_Number c = cross(pts(v_prev), pts(s0), pts(s1));
10256 if (c < 0)
10257 portals.append(Portal{pts(s0), pts(s1)});
10258 else
10259 portals.append(Portal{pts(s1), pts(s0)});
10260 }
10261
10262 portals.append(Portal{target, target});
10263
10264 // --- SSFA core ---
10265 Point apex = source;
10266 Point fl = source; // funnel left boundary
10267 Point fr = source; // funnel right boundary
10268 size_t ai = 0, li = 0, ri = 0;
10269
10270 result.append(source);
10271
10272 for (size_t i = 1; i < portals.size(); ++i)
10273 {
10274 const Point & pr = portals(i).right;
10275 const Point & pl = portals(i).left;
10276
10277 // --- tighten right boundary ---
10278 if (cross(apex, fr, pr) >= 0)
10279 {
10280 if (apex == fr or cross(apex, fl, pr) < 0)
10281 {
10282 fr = pr;
10283 ri = i;
10284 }
10285 else
10286 {
10287 // Right crossed over left → left becomes new apex.
10288 result.append(fl);
10289 apex = fl;
10290 ai = li;
10291 fl = apex;
10292 fr = apex;
10293 li = ai;
10294 ri = ai;
10295 i = ai; // loop increments to ai+1
10296 continue;
10297 }
10298 }
10299
10300 // --- tighten left boundary ---
10301 if (cross(apex, fl, pl) <= 0)
10302 {
10303 if (apex == fl or cross(apex, fr, pl) > 0)
10304 {
10305 fl = pl;
10306 li = i;
10307 }
10308 else
10309 {
10310 // Left crossed over right → right becomes new apex.
10311 result.append(fr);
10312 apex = fr;
10313 ai = ri;
10314 fl = apex;
10315 fr = apex;
10316 li = ai;
10317 ri = ai;
10318 i = ai;
10319 continue;
10320 }
10321 }
10322 }
10323
10324 if (result.get_last() != target)
10325 result.append(target);
10326 return result;
10327 }
10328 };
10329
10330 // ============================================================================
10331 // Segment Arrangement — Full Planar Subdivision
10332 // ============================================================================
10333
10360 {
10361 public:
10365 struct ArrEdge
10366 {
10367 size_t src;
10368 size_t tgt;
10369 size_t seg_idx;
10370 };
10371
10375 struct ArrFace
10376 {
10379 };
10380
10390
10391 private:
10392 static constexpr size_t NONE = ~static_cast<size_t>(0);
10393
10395 [[nodiscard]] static bool pt_less(const Point & a, const Point & b)
10396 {
10397 if (a.get_x() != b.get_x()) return a.get_x() < b.get_x();
10398 return a.get_y() < b.get_y();
10399 }
10400
10402 [[nodiscard]] static size_t find_vertex(const Array<Point> & verts,
10403 const Point & p)
10404 {
10405 size_t lo = 0, hi = verts.size();
10406 while (lo < hi)
10407 if (const size_t mid = lo + (hi - lo) / 2; pt_less(verts(mid), p))
10408 lo = mid + 1;
10409 else if (pt_less(p, verts(mid)))
10410 hi = mid;
10411 else
10412 return mid;
10413 return NONE;
10414 }
10415
10417 [[nodiscard]] static int angle_quad(const Geom_Number & dx,
10418 const Geom_Number & dy)
10419 {
10420 if (dx > 0 and dy >= 0) return 0;
10421 if (dx <= 0 and dy > 0) return 1;
10422 if (dx < 0 and dy <= 0) return 2;
10423 return 3;
10424 }
10425
10428 [[nodiscard]] static bool angle_lt(const Geom_Number & dx1,
10429 const Geom_Number & dy1,
10430 const Geom_Number & dx2,
10431 const Geom_Number & dy2)
10432 {
10433 const int q1 = angle_quad(dx1, dy1);
10434 const int q2 = angle_quad(dx2, dy2);
10435 if (q1 != q2) return q1 < q2;
10436 if (const Geom_Number cross = dx1 * dy2 - dy1 * dx2; cross != 0)
10437 return cross > 0;
10438 return dx1 * dx1 + dy1 * dy1 < (dx2 * dx2 + dy2 * dy2);
10439 }
10440
10445 {
10446 size_t origin;
10447 size_t target;
10448 size_t twin;
10449 size_t next;
10450 size_t face;
10451 size_t edge_idx;
10452 };
10453
10454 public:
10461 [[nodiscard]] Result operator()(const Array<Segment> & segments) const
10462 {
10463 Result ret;
10464 const size_t n = segments.size();
10465
10466 if (n == 0)
10467 {
10468 // Single unbounded face.
10469 ArrFace uf;
10470 uf.unbounded = true;
10471 ret.faces.append(uf);
10472 return ret;
10473 }
10474
10475 // --- Step 1: Compute all intersections. ---
10477 auto inters = sweep(segments);
10478
10479 // --- Step 2: Build a deduplicated vertex set. ---
10480 // Collect all endpoints + intersection points.
10482 all_pts.reserve(2 * n + inters.size());
10483 for (size_t i = 0; i < n; ++i)
10484 {
10485 all_pts.append(segments(i).get_src_point());
10486 all_pts.append(segments(i).get_tgt_point());
10487 }
10488 for (size_t i = 0; i < inters.size(); ++i)
10489 all_pts.append(inters(i).point);
10490
10491 // Sort lexicographically.
10492 quicksort_op(all_pts, [](const Point & a, const Point & b)
10493 {
10494 return pt_less(a, b);
10495 });
10496
10497 // Deduplicate.
10498 ret.vertices.reserve(all_pts.size());
10499 for (size_t i = 0; i < all_pts.size(); ++i)
10500 if (ret.vertices.is_empty() or ret.vertices.get_last() != all_pts(i))
10501 ret.vertices.append(all_pts(i));
10502
10503 // --- Step 3: Split segments into sub-edges. ---
10504 // For each segment, collect its vertices and sort along the segment.
10505 for (size_t si = 0; si < n; ++si)
10506 {
10507 const Point & sp = segments(si).get_src_point();
10508 const Point & tp = segments(si).get_tgt_point();
10509
10510 // Collect vertices on this segment.
10512 on_seg.append(find_vertex(ret.vertices, sp));
10513 on_seg.append(find_vertex(ret.vertices, tp));
10514
10515 // Add intersection points involving this segment.
10516 for (size_t ki = 0; ki < inters.size(); ++ki)
10517 if (inters(ki).seg_i == si or inters(ki).seg_j == si)
10518 if (const size_t vi = find_vertex(ret.vertices, inters(ki).point); vi != NONE)
10519 on_seg.append(vi);
10520
10521 // Deduplicate.
10522 quicksort_op(on_seg, [](const size_t a, const size_t b)
10523 {
10524 return a < b;
10525 }); {
10527 for (size_t i = 0; i < on_seg.size(); ++i)
10528 if (uniq.is_empty() or uniq.get_last() != on_seg(i))
10529 uniq.append(on_seg(i));
10530 on_seg = uniq;
10531 }
10532
10533 // Sort by parametric position along the segment.
10534 // Use the primary direction (x for non-vertical, y for vertical).
10535 const bool vertical = (sp.get_x() == tp.get_x());
10536 const auto & verts = ret.vertices;
10537 if (vertical)
10538 quicksort_op(on_seg, [&](const size_t a, const size_t b)
10539 {
10540 return verts(a).get_y() < verts(b).get_y();
10541 });
10542 else
10543 quicksort_op(on_seg, [&](const size_t a, const size_t b)
10544 {
10545 return verts(a).get_x() < verts(b).get_x();
10546 });
10547
10548 // Create edges between consecutive vertices.
10549 for (size_t i = 0; i + 1 < on_seg.size(); ++i)
10550 ret.edges.append(ArrEdge{on_seg(i), on_seg(i + 1), si});
10551 }
10552
10553 // --- Step 4 + 5: Build half-edges, compute next pointers, find faces. ---
10554 const size_t ne = ret.edges.size();
10555 const size_t nhe = 2 * ne;
10556 const size_t nv = ret.vertices.size();
10557
10558 if (ne == 0)
10559 {
10560 // Only isolated vertices, no edges → one unbounded face.
10561 ArrFace uf;
10562 uf.unbounded = true;
10563 ret.faces.append(uf);
10564 return ret;
10565 }
10566
10567 // Create half-edges: for edge i, half-edge 2*i goes src→tgt,
10568 // half-edge 2*i+1 goes tgt→src.
10570 he.reserve(nhe);
10571 for (size_t i = 0; i < ne; ++i)
10572 {
10573 he.append(HalfEdge{
10574 ret.edges(i).src, ret.edges(i).tgt,
10575 2 * i + 1, NONE, NONE, i
10576 });
10577 he.append(HalfEdge{
10578 ret.edges(i).tgt, ret.edges(i).src,
10579 2 * i, NONE, NONE, i
10580 });
10581 }
10582
10583 // Build incidence: for each vertex, list of outgoing half-edge indices.
10585 inc.reserve(nv);
10586 for (size_t i = 0; i < nv; ++i)
10587 inc.append(Array<size_t>());
10588 for (size_t h = 0; h < nhe; ++h)
10589 inc(he(h).origin).append(h);
10590
10591 // Sort outgoing half-edges at each vertex by angle.
10592 const auto & verts = ret.vertices;
10593 for (size_t v = 0; v < nv; ++v)
10594 {
10595 if (inc(v).size() <= 1) continue;
10596 quicksort_op(inc(v), [&](size_t a, size_t b)
10597 {
10598 const Geom_Number dxa = verts(he(a).target).get_x()
10599 - verts(v).get_x();
10600 const Geom_Number dya = verts(he(a).target).get_y()
10601 - verts(v).get_y();
10602 const Geom_Number dxb = verts(he(b).target).get_x()
10603 - verts(v).get_x();
10604 const Geom_Number dyb = verts(he(b).target).get_y()
10605 - verts(v).get_y();
10606 return angle_lt(dxa, dya, dxb, dyb);
10607 });
10608 }
10609
10610 // Compute "next" pointers.
10611 // For half-edge h = (u→v), the next half-edge around the face is:
10612 // at vertex v, take the twin of h (which is v→u), find its position
10613 // in the sorted incidence list of v, go one step CW (previous in CCW
10614 // order) → that's the next half-edge of the face.
10615 //
10616 // Equivalently: next(h) where h = u→v:
10617 // twin(h) = v→u. Find twin(h) in inc(v). The previous entry in
10618 // inc(v) (wrapping) is the next half-edge of the face.
10619 for (size_t v = 0; v < nv; ++v)
10620 {
10621 const auto & list = inc(v);
10622 const size_t sz = list.size();
10623 if (sz == 0) continue;
10624
10625 for (size_t i = 0; i < sz; ++i)
10626 {
10627 // Half-edge list(i) leaves v. Its twin arrives at v.
10628 // The twin is the "incoming" half-edge. The face's "next"
10629 // after the twin is the one that comes just before list(i)
10630 // in the CCW angular order, i.e. the one CW from it.
10631 // So: next(twin(list(i))) = list((i+1) % sz)
10632 // wrong — let me re-derive.
10633 //
10634 // We have inc(v) sorted CCW: h0, h1, ..., h_{k-1} (outgoing).
10635 // For outgoing h_i (= v → t_i), twin(h_i) = t_i → v (incoming).
10636 // The face containing twin(h_i) continues with the next outgoing
10637 // half-edge at v going CW from the incoming direction.
10638 //
10639 // Incoming direction of twin(h_i) at v: from t_i toward v.
10640 // This is the reverse of h_i's outgoing direction.
10641 // In the CCW list, h_i is at position i. The CW-next after
10642 // the reversed direction of h_i corresponds to h_{(i-1+k)%k}.
10643 //
10644 // So: next(twin(h_i)) = h_{(i-1+k) % k}.
10645
10646 const size_t prev = (i == 0) ? sz - 1 : i - 1;
10647 he(he(list(i)).twin).next = list(prev);
10648 }
10649 }
10650
10651 // --- Face traversal ---
10652 Array<bool> visited;
10653 visited.reserve(nhe);
10654 for (size_t i = 0; i < nhe; ++i)
10655 visited.append(false);
10656
10657 for (size_t h = 0; h < nhe; ++h)
10658 {
10659 if (visited(h)) continue;
10660
10661 ArrFace face;
10662 face.unbounded = false;
10663
10664 // Trace the face boundary.
10665 size_t cur = h;
10666 Geom_Number signed_area = 0;
10667 do
10668 {
10669 visited(cur) = true;
10670 face.boundary.append(he(cur).origin);
10671
10672 // Accumulate signed area (shoelace).
10673 const Point & p1 = verts(he(cur).origin);
10674 const Point & p2 = verts(he(cur).target);
10675 signed_area += p1.get_x() * p2.get_y() - p2.get_x() * p1.get_y();
10676
10677 cur = he(cur).next;
10678 }
10679 while (cur != h and cur != NONE);
10680
10681 // CW winding (negative area) → unbounded face.
10682 if (signed_area < 0)
10683 face.unbounded = true;
10684
10685 ret.faces.append(face);
10686 }
10687
10688 // Ensure exactly one unbounded face is marked.
10689 // If none was found (e.g. all half-edges form CCW cycles, which
10690 // happens when all segments are isolated non-intersecting ones),
10691 // mark the face with the largest absolute signed area as unbounded.
10692 {
10693 bool has_ub = false;
10694 for (size_t i = 0; i < ret.faces.size(); ++i)
10695 if (ret.faces(i).unbounded)
10696 {
10697 has_ub = true;
10698 break;
10699 }
10700 if (not has_ub and not ret.faces.is_empty())
10701 ret.faces(0).unbounded = true;
10702 }
10703
10704 return ret;
10705 }
10706 };
10707
10708 // ============================================================================
10709 // Alpha Shapes — Generalization of Convex Hull
10710 // ============================================================================
10711
10745 {
10746 public:
10756
10767 [[nodiscard]] Result
10769 const Geom_Number & alpha_squared) const
10770 {
10771 constexpr DelaunayTriangulationBowyerWatson delaunay;
10772 auto [sites, triangles] = delaunay(points);
10773
10774 Result ret;
10775 ret.sites = sites;
10776
10777 if (triangles.size() == 0)
10778 return ret;
10779
10780 // Filter triangles by circumradius².
10782 kept.reserve(triangles.size());
10783 for (size_t t = 0; t < triangles.size(); ++t)
10784 {
10785 const auto & tri = triangles(t);
10786 const Point & a = sites(tri.i);
10787 const Point & b = sites(tri.j);
10788 const Point & c = sites(tri.k);
10789
10790 // Circumradius² = (|AB|²|BC|²|CA|²) / (16 * area²)
10791 const Geom_Number ab2 = a.distance_squared_to(b);
10792 const Geom_Number bc2 = b.distance_squared_to(c);
10793 const Geom_Number ca2 = c.distance_squared_to(a);
10794 const Geom_Number area2x = area_of_parallelogram(a, b, c);
10796
10797 // circumradius² = ab2 * bc2 * ca2 / (4 * four_area_sq)
10798 // Compare: ab2 * bc2 * ca2 ≤ alpha_squared * 4 * four_area_sq
10799 const bool pass = ab2 * bc2 * ca2 <= alpha_squared * four_area_sq * 4;
10800 kept.append(pass);
10801 if (pass)
10802 ret.triangles.append(tri);
10803 }
10804
10805 // Extract boundary edges: edges appearing in exactly one kept triangle.
10806 // Use a simple count map via sorted edge keys.
10810 struct EdgeKey
10811 {
10812 size_t u, v;
10813
10815 bool operator==(const EdgeKey & o) const
10816 {
10817 return u == o.u and v == o.v;
10818 }
10819
10820 bool operator<(const EdgeKey & o) const
10821 {
10822 return u < o.u or (u == o.u and v < o.v);
10823 }
10824 };
10825
10826 auto make_key = [](const size_t a, const size_t b) -> EdgeKey
10827 {
10828 return a < b ? EdgeKey{a, b} : EdgeKey{b, a};
10829 };
10830
10832 all_edges.reserve(ret.triangles.size() * 3);
10833 for (size_t t = 0; t < ret.triangles.size(); ++t)
10834 {
10835 const auto & [i, j, k] = ret.triangles(t);
10836 all_edges.append(make_key(i, j));
10837 all_edges.append(make_key(j, k));
10838 all_edges.append(make_key(k, i));
10839 }
10840
10841 quicksort_op(all_edges, [](const EdgeKey & a, const EdgeKey & b)
10842 {
10843 return a < b;
10844 });
10845
10846 for (size_t i = 0; i < all_edges.size();)
10847 {
10848 size_t j = i + 1;
10849 while (j < all_edges.size() and all_edges(j) == all_edges(i))
10850 ++j;
10851 if (j - i == 1) // appears exactly once → boundary
10852 ret.boundary_edges.append(Segment(ret.sites(all_edges(i).u),
10853 ret.sites(all_edges(i).v)));
10854 i = j;
10855 }
10856
10857 return ret;
10858 }
10859 };
10860
10861 // ============================================================================
10862 // Power Diagram — Weighted Voronoi
10863 // ============================================================================
10864
10908 {
10909 public:
10918
10923 {
10924 size_t site_u;
10925 size_t site_v;
10930 };
10931
10943
10954
10960 [[nodiscard]] static Point
10962 const WeightedSite & c)
10963 {
10964 // Power center solves:
10965 // ||p - a||² - wa = ||p - b||² - wb = ||p - c||² - wc
10966 // Expanding: 2(bx-ax)px + 2(by-ay)py = bx²+by²-ax²-ay² - (wb - wa)
10967 // 2(cx-ax)px + 2(cy-ay)py = cx²+cy²-ax²-ay² - (wc - wa)
10968 const Geom_Number & ax = a.position.get_x();
10969 const Geom_Number & ay = a.position.get_y();
10970 const Geom_Number & bx = b.position.get_x();
10971 const Geom_Number & by = b.position.get_y();
10972 const Geom_Number & cx = c.position.get_x();
10973 const Geom_Number & cy = c.position.get_y();
10974
10975 const Geom_Number d1x = bx - ax, d1y = by - ay;
10976 const Geom_Number d2x = cx - ax, d2y = cy - ay;
10977
10978 const Geom_Number rhs1 = (bx * bx + by * by - ax * ax - ay * ay -
10979 (b.weight - a.weight)) / 2;
10980 const Geom_Number rhs2 = (cx * cx + cy * cy - ax * ax - ay * ay -
10981 (c.weight - a.weight)) / 2;
10982
10983 const Geom_Number det = d1x * d2y - d1y * d2x;
10984 ah_domain_error_if(det == 0) << "Degenerate configuration";
10985
10986 return {
10987 (rhs1 * d2y - rhs2 * d1y) / det,
10988 (d1x * rhs2 - d2x * rhs1) / det
10989 };
10990 }
10991
11002 {
11003 Result ret;
11004 const size_t n = sites.size();
11005 if (n == 0) return ret;
11006
11007 ret.sites = sites;
11008
11009 // Compute regular triangulation (weighted Delaunay).
11011 rt_input.reserve(n);
11012 for (size_t i = 0; i < n; ++i)
11013 rt_input.append({sites(i).position, sites(i).weight});
11014
11016 auto rt = reg_tri(rt_input);
11017
11018 if (rt.triangles.size() == 0) return ret;
11019
11020 // Build a mapping from regular-triangulation site indices to ours.
11021 // rt.sites may have been reordered/deduped, so match by position.
11023 rt_to_ours.reserve(rt.sites.size());
11024 for (size_t ri = 0; ri < rt.sites.size(); ++ri)
11025 {
11026 size_t match = 0;
11027 for (size_t oi = 0; oi < n; ++oi)
11028 if (rt.sites(ri).position == sites(oi).position)
11029 {
11030 match = oi;
11031 break;
11032 }
11033 rt_to_ours.append(match);
11034 }
11035
11036 // Compute power centers for each regular triangle.
11038 pcenters.reserve(rt.triangles.size());
11039 for (size_t t = 0; t < rt.triangles.size(); ++t)
11040 {
11041 const auto & tri = rt.triangles(t);
11042 pcenters.append(power_center(
11043 sites(rt_to_ours(tri.i)),
11044 sites(rt_to_ours(tri.j)),
11045 sites(rt_to_ours(tri.k))));
11046 }
11047
11048 ret.vertices = pcenters;
11049
11050 // Identify hull sites: sites on boundary edges (edges shared by
11051 // exactly one triangle) have unbounded power cells.
11053
11055 rt.triangles,
11057 edge_refs,
11058 const size_t first, const size_t last)
11059 {
11060 if (last - first == 1)
11061 {
11062 // Boundary edge: both sites are on the convex hull.
11063 hull_sites.insert(rt_to_ours(edge_refs(first).u));
11064 hull_sites.insert(rt_to_ours(edge_refs(first).v));
11065 }
11066
11067 // In a manifold triangulation this group has exactly 1 or 2 refs.
11068 // If degeneracies produce >2, connect all pairs defensively.
11069 if (last - first >= 2)
11070 for (size_t a = first; a + 1 < last; ++a)
11071 for (size_t b = a + 1; b < last; ++b)
11072 {
11073 PowerEdge pe;
11074 pe.site_u = rt_to_ours(edge_refs(first).u);
11075 pe.site_v = rt_to_ours(edge_refs(first).v);
11076 pe.src = pcenters(edge_refs(a).tri);
11077 pe.tgt = pcenters(edge_refs(b).tri);
11078 pe.unbounded = false;
11079 pe.direction = Point(0, 0);
11080 ret.edges.append(pe);
11081 }
11082 });
11083
11084 // Build cells: hull sites have unbounded cells.
11085 ret.cells.reserve(n);
11086 for (size_t s = 0; s < n; ++s)
11087 {
11088 PowerCell cell;
11089 cell.site_index = s;
11090 cell.site = sites(s).position;
11091 cell.weight = sites(s).weight;
11092 cell.bounded = hull_sites.search(s) == nullptr;
11093
11094 // Collect power centers of triangles incident to this site.
11095 for (size_t t = 0; t < rt.triangles.size(); ++t)
11096 {
11097 const auto & [i, j, k] = rt.triangles(t);
11098 if (rt_to_ours(i) == s or rt_to_ours(j) == s or
11099 rt_to_ours(k) == s)
11100 cell.vertices.append(pcenters(t));
11101 }
11102
11103 ret.cells.append(cell);
11104 }
11105
11106 return ret;
11107 }
11108 };
11109
11110 // ============================================================================
11111 // Bezier Curves
11112 // ============================================================================
11113
11134 {
11135 public:
11137 [[nodiscard]] static Point
11138 quadratic(const Point & p0, const Point & p1, const Point & p2,
11139 const Geom_Number & t)
11140 {
11141 const Geom_Number s = Geom_Number(1) - t;
11142 const Geom_Number s2 = s * s;
11143 const Geom_Number t2 = t * t;
11144 const Geom_Number st2 = Geom_Number(2) * s * t;
11145
11146 return {
11147 s2 * p0.get_x() + st2 * p1.get_x() + t2 * p2.get_x(),
11148 s2 * p0.get_y() + st2 * p1.get_y() + t2 * p2.get_y()
11149 };
11150 }
11151
11153 [[nodiscard]] static Point
11154 cubic(const Point & p0, const Point & p1,
11155 const Point & p2, const Point & p3,
11156 const Geom_Number & t)
11157 {
11158 const Geom_Number s = Geom_Number(1) - t;
11159 const Geom_Number s2 = s * s;
11160 const Geom_Number s3 = s2 * s;
11161 const Geom_Number t2 = t * t;
11162 const Geom_Number t3 = t2 * t;
11163 const Geom_Number c1 = Geom_Number(3) * s2 * t;
11164 const Geom_Number c2 = Geom_Number(3) * s * t2;
11165
11166 return {
11167 s3 * p0.get_x() + c1 * p1.get_x() +
11168 c2 * p2.get_x() + t3 * p3.get_x(),
11169 s3 * p0.get_y() + c1 * p1.get_y() +
11170 c2 * p2.get_y() + t3 * p3.get_y()
11171 };
11172 }
11173
11175 [[nodiscard]] static Array<Point>
11176 sample_quadratic(const Point & p0, const Point & p1, const Point & p2, const size_t n)
11177 {
11178 ah_domain_error_if(n == 0) << "Need at least 1 subdivision";
11180 pts.reserve(n + 1);
11181 for (size_t i = 0; i <= n; ++i)
11182 pts.append(quadratic(p0, p1, p2, Geom_Number(i) / Geom_Number(n)));
11183 return pts;
11184 }
11185
11187 [[nodiscard]] static Array<Point>
11188 sample_cubic(const Point & p0, const Point & p1,
11189 const Point & p2, const Point & p3,
11190 const size_t n)
11191 {
11192 ah_domain_error_if(n == 0) << "Need at least 1 subdivision";
11194 pts.reserve(n + 1);
11195 for (size_t i = 0; i <= n; ++i)
11196 pts.append(cubic(p0, p1, p2, p3, Geom_Number(i) / Geom_Number(n)));
11197 return pts;
11198 }
11199
11203 {
11206 };
11207
11208 [[nodiscard]] static SplitResult
11209 split_cubic(const Point & p0, const Point & p1,
11210 const Point & p2, const Point & p3,
11211 const Geom_Number & t)
11212 {
11213 auto lerp = [&](const Point & a, const Point & b) -> Point
11214 {
11215 const Geom_Number s = Geom_Number(1) - t;
11216 return {
11217 s * a.get_x() + t * b.get_x(),
11218 s * a.get_y() + t * b.get_y()
11219 };
11220 };
11221
11222 const Point q0 = lerp(p0, p1);
11223 const Point q1 = lerp(p1, p2);
11224 const Point q2 = lerp(p2, p3);
11225 const Point r0 = lerp(q0, q1);
11226 const Point r1 = lerp(q1, q2);
11227 const Point s0 = lerp(r0, r1);
11228
11230 sr.left[0] = p0;
11231 sr.left[1] = q0;
11232 sr.left[2] = r0;
11233 sr.left[3] = s0;
11234 sr.right[0] = s0;
11235 sr.right[1] = r1;
11236 sr.right[2] = q2;
11237 sr.right[3] = p3;
11238 return sr;
11239 }
11240
11242 [[nodiscard]] static Rectangle
11243 control_bbox(const Point & p0, const Point & p1,
11244 const Point & p2, const Point & p3)
11245 {
11246 Geom_Number mnx = p0.get_x(), mxx = p0.get_x();
11247 Geom_Number mny = p0.get_y(), mxy = p0.get_y();
11248 auto update = [&](const Point & p)
11249 {
11250 if (p.get_x() < mnx) mnx = p.get_x();
11251 if (p.get_x() > mxx) mxx = p.get_x();
11252 if (p.get_y() < mny) mny = p.get_y();
11253 if (p.get_y() > mxy) mxy = p.get_y();
11254 };
11255 update(p1);
11256 update(p2);
11257 update(p3);
11258 return {mnx, mny, mxx, mxy};
11259 }
11260
11262 [[nodiscard]] static Polygon
11263 approximate_quadratic(const Point & p0, const Point & p1,
11264 const Point & p2, const size_t n)
11265 {
11266 auto pts = sample_quadratic(p0, p1, p2, n);
11267 Polygon poly;
11268 for (size_t i = 0; i < pts.size(); ++i)
11269 poly.add_vertex(pts(i));
11270 return poly;
11271 }
11272
11274 [[nodiscard]] static Polygon
11275 approximate_cubic(const Point & p0, const Point & p1,
11276 const Point & p2, const Point & p3, const size_t n)
11277 {
11278 auto pts = sample_cubic(p0, p1, p2, p3, n);
11279 Polygon poly;
11280 for (size_t i = 0; i < pts.size(); ++i)
11281 poly.add_vertex(pts(i));
11282 return poly;
11283 }
11284 };
11285
11286 // ============================================================================
11287 // Boolean Polygon Operations
11288 // ============================================================================
11289
11336 {
11337 public:
11341 enum class Op
11342 {
11343 INTERSECTION,
11344 UNION,
11345 DIFFERENCE
11346 };
11347
11358 operator()(const Polygon & a, const Polygon & b, Op op) const
11359 {
11360 ah_domain_error_if(not a.is_closed()) << "First polygon must be closed";
11361 ah_domain_error_if(not b.is_closed()) << "Second polygon must be closed";
11362 ah_domain_error_if(a.size() < 3) << "First polygon must have >= 3 vertices";
11363 ah_domain_error_if(b.size() < 3) << "Second polygon must have >= 3 vertices";
11364
11365 switch (op)
11366 {
11367 case Op::INTERSECTION: return compute_intersection(a, b);
11368 case Op::UNION: return compute_union(a, b);
11369 case Op::DIFFERENCE: return compute_difference(a, b);
11370 }
11371 return {};
11372 }
11373
11376 intersection(const Polygon & a, const Polygon & b) const
11377 {
11378 return (*this)(a, b, Op::INTERSECTION);
11379 }
11380
11383 polygon_union(const Polygon & a, const Polygon & b) const
11384 {
11385 return (*this)(a, b, Op::UNION);
11386 }
11387
11390 difference(const Polygon & a, const Polygon & b) const
11391 {
11392 return (*this)(a, b, Op::DIFFERENCE);
11393 }
11394
11395 private:
11396 // ----- Greiner-Hormann node for augmented polygon vertex list -----
11397
11398 struct GHNode
11399 {
11401 bool intersect = false;
11402 bool entering = false;
11403 size_t neighbor = SIZE_MAX; // index in the other polygon's list
11404 bool visited = false;
11405 Geom_Number alpha = 0; // parameter along the original edge
11406 };
11407
11408 // ----- helpers -----
11409
11412 {
11414 }
11415
11417 static void ensure_ccw(Array<Point> & v)
11418 {
11420 }
11421
11424 {
11425 for (size_t i = 0, j = v.size() - 1; i < j; ++i, --j)
11426 {
11427 const Point tmp = v(i);
11428 v(i) = v(j);
11429 v(j) = tmp;
11430 }
11431 }
11432
11435 {
11436 Polygon p;
11437 for (size_t i = 0; i < pts.size(); ++i)
11438 p.add_vertex(pts(i));
11439 if (pts.size() >= 3)
11440 p.close();
11441 return p;
11442 }
11443
11445 [[nodiscard]] static Geom_Number
11446 edge_param(const Point & P, const Point & Q, const Point & I)
11447 {
11448 const Geom_Number dx = Q.get_x() - P.get_x();
11449 if (dx != 0)
11450 return (I.get_x() - P.get_x()) / dx;
11451 return (I.get_y() - P.get_y()) / (Q.get_y() - P.get_y());
11452 }
11453
11455 [[nodiscard]] static bool
11456 point_inside_ccw(const Array<Point> & poly, const Point & p)
11457 {
11458 int winding = 0;
11459 const size_t n = poly.size();
11460 for (size_t i = 0; i < n; ++i)
11461 {
11462 const Point & a = poly(i);
11463 const Point & b = poly((i + 1) % n);
11464 const Segment edge(a, b);
11465 if (edge.contains(p))
11466 return true;
11467 if (a.get_y() <= p.get_y())
11468 {
11469 if (b.get_y() > p.get_y() &&
11470 area_of_parallelogram(a, b, p) > 0)
11471 ++winding;
11472 }
11473 else
11474 {
11475 if (b.get_y() <= p.get_y() &&
11476 area_of_parallelogram(a, b, p) < 0)
11477 --winding;
11478 }
11479 }
11480 return winding != 0;
11481 }
11482
11485 const Array<Geom_Number> & keys)
11486 {
11487 for (size_t i = 1; i < idx.size(); ++i)
11488 {
11489 const size_t val = idx(i);
11490 const Geom_Number& key = keys(val);
11491 size_t j = i;
11492 while (j > 0 && keys(idx(j - 1)) > key)
11493 {
11494 idx(j) = idx(j - 1);
11495 --j;
11496 }
11497 idx(j) = val;
11498 }
11499 }
11500
11501 // ----- Core Greiner-Hormann algorithm -----
11502
11505 {
11509 };
11510
11516 [[nodiscard]] static Array<Polygon>
11518 bool start_entering)
11519 {
11520 const size_t na = va.size();
11521 const size_t nb = vb.size();
11522
11523 // --- Phase 1: find all proper intersection pairs ---
11524
11525 Array<IsectPair> pairs;
11526
11527 for (size_t i = 0; i < na; ++i)
11528 {
11529 const Point & a0 = va(i);
11530 const Point & a1 = va((i + 1) % na);
11531 const Segment sa(a0, a1);
11532
11533 for (size_t j = 0; j < nb; ++j)
11534 {
11535 const Point & b0 = vb(j);
11536 const Point & b1 = vb((j + 1) % nb);
11537 const Segment sb(b0, b1);
11538
11539 if (not sa.intersects_properly_with(sb))
11540 continue;
11541
11542 const Point ip = sa.intersection_with(sb);
11543 const Geom_Number aa = edge_param(a0, a1, ip);
11544 const Geom_Number ab = edge_param(b0, b1, ip);
11545 pairs.append({ip, i, j, aa, ab});
11546 }
11547 }
11548
11549 // --- Phase 2: build augmented lists ---
11550
11552 // pair_idx_a[k] / pair_idx_b[k] = position in list_a / list_b for
11553 // intersection pair k.
11555 pair_idx_a.reserve(pairs.size());
11556 pair_idx_b.reserve(pairs.size());
11557 for (size_t k = 0; k < pairs.size(); ++k)
11558 {
11559 pair_idx_a.append(0);
11560 pair_idx_b.append(0);
11561 }
11562
11563 // Build list_a: for each edge, add original vertex then sorted
11564 // intersections.
11565 for (size_t i = 0; i < na; ++i)
11566 {
11567 GHNode vn;
11568 vn.pt = va(i);
11569 list_a.append(vn);
11570
11571 // Collect pairs on this edge of A.
11574 for (size_t k = 0; k < pairs.size(); ++k)
11575 if (pairs(k).edge_a == i)
11576 {
11578 edge_alphas.append(pairs(k).alpha_a);
11579 }
11580
11581 // Sort by alpha.
11582 Array<size_t> order;
11583 for (size_t p = 0; p < edge_pairs.size(); ++p)
11584 order.append(p);
11586
11587 for (size_t p = 0; p < order.size(); ++p)
11588 {
11589 const size_t k = edge_pairs(order(p));
11590 pair_idx_a(k) = list_a.size();
11591 GHNode in;
11592 in.pt = pairs(k).pt;
11593 in.intersect = true;
11594 in.alpha = pairs(k).alpha_a;
11595 list_a.append(in);
11596 }
11597 }
11598
11599 // Build list_b analogously.
11600 for (size_t j = 0; j < nb; ++j)
11601 {
11602 GHNode vn;
11603 vn.pt = vb(j);
11604 list_b.append(vn);
11605
11608 for (size_t k = 0; k < pairs.size(); ++k)
11609 if (pairs(k).edge_b == j)
11610 {
11612 edge_alphas.append(pairs(k).alpha_b);
11613 }
11614
11615 Array<size_t> order;
11616 for (size_t p = 0; p < edge_pairs.size(); ++p)
11617 order.append(p);
11619
11620 for (size_t p = 0; p < order.size(); ++p)
11621 {
11622 const size_t k = edge_pairs(order(p));
11623 pair_idx_b(k) = list_b.size();
11624 GHNode in;
11625 in.pt = pairs(k).pt;
11626 in.intersect = true;
11627 in.alpha = pairs(k).alpha_b;
11628 list_b.append(in);
11629 }
11630 }
11631
11632 // Set neighbor links.
11633 for (size_t k = 0; k < pairs.size(); ++k)
11634 {
11635 list_a(pair_idx_a(k)).neighbor = pair_idx_b(k);
11636 list_b(pair_idx_b(k)).neighbor = pair_idx_a(k);
11637 }
11638
11639 // --- Phase 3: mark entry / exit ---
11640
11641 // Walk list_a; determine if the first original vertex of A is
11642 // inside B.
11643 {
11644 bool inside = point_inside_ccw(vb, va(0));
11645 for (size_t i = 0; i < list_a.size(); ++i)
11646 if (list_a(i).intersect)
11647 {
11648 list_a(i).entering = ! inside;
11649 inside = ! inside;
11650 }
11651 }
11652
11653 // Walk list_b similarly w.r.t. A.
11654 {
11655 bool inside = point_inside_ccw(va, vb(0));
11656 for (size_t j = 0; j < list_b.size(); ++j)
11657 if (list_b(j).intersect)
11658 {
11659 list_b(j).entering = ! inside;
11660 inside = ! inside;
11661 }
11662 }
11663
11664 // --- Phase 4: traverse ---
11665
11666 Array<Polygon> result;
11667 const size_t safety_limit = list_a.size() + list_b.size() + pairs.size() * 2;
11668
11669 for (size_t s = 0; s < list_a.size(); ++s)
11670 {
11671 if (not list_a(s).intersect)
11672 continue;
11673 if (list_a(s).visited)
11674 continue;
11675 if (list_a(s).entering != start_entering)
11676 continue;
11677
11678 // Trace one result polygon.
11679 Array<Point> boundary;
11680 bool on_a = true;
11681 size_t idx = s;
11682 size_t steps = 0;
11683
11684 do
11685 {
11686 auto & cur_list = on_a ? list_a : list_b;
11687
11688 // Mark current intersection visited (and its partner).
11689 cur_list(idx).visited = true;
11690 if (cur_list(idx).neighbor != SIZE_MAX)
11691 {
11692 auto & other = on_a ? list_b : list_a;
11693 other(cur_list(idx).neighbor).visited = true;
11694 }
11695
11696 // Record this intersection point.
11697 boundary.append(cur_list(idx).pt);
11698
11699 // Walk forward until the next intersection node.
11700 idx = (idx + 1) % cur_list.size();
11701 while (not cur_list(idx).intersect)
11702 {
11703 boundary.append(cur_list(idx).pt);
11704 idx = (idx + 1) % cur_list.size();
11705 if (++steps > safety_limit)
11706 goto done_poly;
11707 }
11708
11709 // idx is now at the next intersection — mark it and switch.
11710 cur_list(idx).visited = true; {
11711 auto & other = on_a ? list_b : list_a;
11712 const size_t nbr = cur_list(idx).neighbor;
11713 other(nbr).visited = true;
11714 on_a = ! on_a;
11715 idx = nbr;
11716 }
11717
11718 if (++steps > safety_limit)
11719 break;
11720 }
11721 while (! (on_a && idx == s));
11722
11723 done_poly:
11724 if (boundary.size() >= 3)
11725 result.append(build_poly(boundary));
11726 }
11727
11728 return result;
11729 }
11730
11731 // ----- Operation dispatchers -----
11732
11733 static Array<Polygon>
11735 {
11736 Array<Point> va = extract(a);
11737 Array<Point> vb = extract(b);
11738 ensure_ccw(va);
11739 ensure_ccw(vb);
11740
11741 Array<Polygon> res = greiner_hormann(va, vb, /*start_entering=*/true);
11742
11743 if (not res.is_empty())
11744 return res;
11745
11746 // No proper intersections — check containment.
11747 if (point_inside_ccw(vb, va(0)))
11748 {
11749 // A entirely inside B → intersection is A.
11750 res.append(a);
11751 return res;
11752 }
11753 if (point_inside_ccw(va, vb(0)))
11754 {
11755 // B entirely inside A → intersection is B.
11756 res.append(b);
11757 return res;
11758 }
11759 // Disjoint.
11760 return res;
11761 }
11762
11763 static Array<Polygon>
11764 compute_union(const Polygon & a, const Polygon & b)
11765 {
11766 Array<Point> va = extract(a);
11767 Array<Point> vb = extract(b);
11768 ensure_ccw(va);
11769 ensure_ccw(vb);
11770
11771 Array<Polygon> res = greiner_hormann(va, vb, /*start_entering=*/false);
11772
11773 if (! res.is_empty())
11774 return res;
11775
11776 // No proper intersections — check containment.
11777 if (point_inside_ccw(vb, va(0)))
11778 {
11779 // A inside B → union is B.
11780 res.append(b);
11781 return res;
11782 }
11783 if (point_inside_ccw(va, vb(0)))
11784 {
11785 // B inside A → union is A.
11786 res.append(a);
11787 return res;
11788 }
11789 // Disjoint → both.
11790 res.append(a);
11791 res.append(b);
11792 return res;
11793 }
11794
11795 static Array<Polygon>
11796 compute_difference(const Polygon & a, const Polygon & b)
11797 {
11798 Array<Point> va = extract(a);
11799 Array<Point> vb = extract(b);
11800 ensure_ccw(va);
11801 ensure_ccw(vb);
11802
11803 // A \ B = A ∩ complement(B). Reversing B's winding makes
11804 // "inside reversed-B" = "outside B", so intersection with
11805 // reversed-B gives the difference.
11807
11808 Array<Polygon> res = greiner_hormann(va, vb, /*start_entering=*/true);
11809
11810 if (! res.is_empty())
11811 return res;
11812
11813 // No proper intersections — check containment using ORIGINAL B.
11814 // Reverse vb back to original orientation for the test.
11816
11817 if (point_inside_ccw(vb, va(0)))
11818 {
11819 // A entirely inside B → difference is empty.
11820 return res;
11821 }
11822 // B inside A → difference has a hole (limitation: return A).
11823 // Disjoint → difference is A.
11824 res.append(a);
11825 return res;
11826 }
11827 };
11828
11829 // ============================================================================
11830 // Serialization — WKT and GeoJSON
11831 // ============================================================================
11832
11858 {
11862 static std::string dbl(const Geom_Number & n)
11863 {
11864 std::ostringstream os;
11865 os << std::setprecision(15) << geom_number_to_double(n);
11866 return os.str();
11867 }
11868
11869 public:
11870 // ---- WKT (Well-Known Text) ----
11871
11875 [[nodiscard]] static std::string to_wkt(const Point & p)
11876 {
11877 return "POINT (" + dbl(p.get_x()) + " " + dbl(p.get_y()) + ")";
11878 }
11879
11883 [[nodiscard]] static std::string to_wkt(const Segment & s)
11884 {
11885 return "LINESTRING (" +
11886 dbl(s.get_src_point().get_x()) + " " +
11887 dbl(s.get_src_point().get_y()) + ", " +
11888 dbl(s.get_tgt_point().get_x()) + " " +
11889 dbl(s.get_tgt_point().get_y()) + ")";
11890 }
11891
11895 [[nodiscard]] static std::string to_wkt(const Triangle & t)
11896 {
11897 auto pt = [](const Point & p)
11898 {
11899 return dbl(p.get_x()) + " " + dbl(p.get_y());
11900 };
11901 return "POLYGON ((" + pt(t.get_p1()) + ", " + pt(t.get_p2()) +
11902 ", " + pt(t.get_p3()) + ", " + pt(t.get_p1()) + "))";
11903 }
11904
11908 [[nodiscard]] static std::string to_wkt(const Rectangle & r)
11909 {
11910 return "POLYGON ((" +
11911 dbl(r.get_xmin()) + " " + dbl(r.get_ymin()) + ", " +
11912 dbl(r.get_xmax()) + " " + dbl(r.get_ymin()) + ", " +
11913 dbl(r.get_xmax()) + " " + dbl(r.get_ymax()) + ", " +
11914 dbl(r.get_xmin()) + " " + dbl(r.get_ymax()) + ", " +
11915 dbl(r.get_xmin()) + " " + dbl(r.get_ymin()) + "))";
11916 }
11917
11921 [[nodiscard]] static std::string to_wkt(const Polygon & poly)
11922 {
11923 std::ostringstream os;
11924 os << std::setprecision(15) << "POLYGON ((";
11925 bool first = true;
11927 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
11928 {
11929 const Point & v = it.get_current_vertex();
11930 if (first)
11931 {
11932 first_pt = v;
11933 first = false;
11934 }
11935 else os << ", ";
11936 os << geom_number_to_double(v.get_x()) << " "
11938 }
11939 if (not first)
11940 os << ", " << geom_number_to_double(first_pt.get_x()) << " "
11941 << geom_number_to_double(first_pt.get_y());
11942 os << "))";
11943 return os.str();
11944 }
11945
11949 [[nodiscard]] static std::string to_wkt(const Point3D & p)
11950 {
11951 return "POINT Z (" + dbl(p.get_x()) + " " + dbl(p.get_y()) +
11952 " " + dbl(p.get_z()) + ")";
11953 }
11954
11955 // ---- GeoJSON ----
11956
11960 [[nodiscard]] static std::string to_geojson(const Point & p)
11961 {
11962 return R"({"type":"Point","coordinates":[)" +
11963 dbl(p.get_x()) + "," + dbl(p.get_y()) + "]}";
11964 }
11965
11969 [[nodiscard]] static std::string to_geojson(const Segment & s)
11970 {
11971 return R"({"type":"LineString","coordinates":[[)" +
11972 dbl(s.get_src_point().get_x()) + "," +
11973 dbl(s.get_src_point().get_y()) + "],[" +
11974 dbl(s.get_tgt_point().get_x()) + "," +
11975 dbl(s.get_tgt_point().get_y()) + "]]}";
11976 }
11977
11979 [[nodiscard]] static std::string to_geojson(const Triangle & t)
11980 {
11981 auto pt = [](const Point & p)
11982 {
11983 return "[" + dbl(p.get_x()) + "," + dbl(p.get_y()) + "]";
11984 };
11985 return R"({"type":"Polygon","coordinates":[[)" +
11986 pt(t.get_p1()) + "," + pt(t.get_p2()) + "," +
11987 pt(t.get_p3()) + "," + pt(t.get_p1()) + "]]}";
11988 }
11989
11991 [[nodiscard]] static std::string to_geojson(const Polygon & poly)
11992 {
11993 std::ostringstream os;
11994 os << std::setprecision(15);
11995 os << R"({"type":"Polygon","coordinates":[[)";
11996 bool first = true;
11998 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
11999 {
12000 const Point & v = it.get_current_vertex();
12001 if (first)
12002 {
12003 first_pt = v;
12004 first = false;
12005 }
12006 else os << ",";
12007 os << "[" << geom_number_to_double(v.get_x()) << ","
12008 << geom_number_to_double(v.get_y()) << "]";
12009 }
12010 if (not first)
12011 os << ",[" << geom_number_to_double(first_pt.get_x()) << ","
12012 << geom_number_to_double(first_pt.get_y()) << "]";
12013 os << "]]}";
12014 return os.str();
12015 }
12016
12018 [[nodiscard]] static std::string to_geojson(const Point3D & p)
12019 {
12020 return R"({"type":"Point","coordinates":[)" +
12021 dbl(p.get_x()) + "," + dbl(p.get_y()) + "," +
12022 dbl(p.get_z()) + "]}";
12023 }
12024 };
12025
12026 // ============================================================================
12027 // AABB Tree — Axis-Aligned Bounding Box Tree
12028 // ============================================================================
12029
12064 {
12065 public:
12069 struct Entry
12070 {
12072 size_t index;
12073 };
12074
12089
12098
12099 private:
12103 struct Node
12104 {
12109
12113 [[nodiscard]] bool is_leaf() const { return entry_idx != ~static_cast<size_t>(0); }
12114 };
12115
12118
12119 static constexpr size_t NONE = ~static_cast<size_t>(0);
12120
12125 const Rectangle & b)
12126 {
12127 const Geom_Number xmin = a.get_xmin() < b.get_xmin() ? a.get_xmin() : b.get_xmin();
12128 const Geom_Number ymin = a.get_ymin() < b.get_ymin() ? a.get_ymin() : b.get_ymin();
12129 const Geom_Number xmax = a.get_xmax() > b.get_xmax() ? a.get_xmax() : b.get_xmax();
12130 const Geom_Number ymax = a.get_ymax() > b.get_ymax() ? a.get_ymax() : b.get_ymax();
12131 return {xmin, ymin, xmax, ymax};
12132 }
12133
12137 [[nodiscard]] static bool boxes_overlap(const Rectangle & a,
12138 const Rectangle & b)
12139 {
12140 return a.get_xmin() <= b.get_xmax() and a.get_xmax() >= b.get_xmin() and
12141 a.get_ymin() <= b.get_ymax() and a.get_ymax() >= b.get_ymin();
12142 }
12143
12147 [[nodiscard]] static bool box_contains_point(const Rectangle & r,
12148 const Point & p)
12149 {
12150 return p.get_x() >= r.get_xmin() and p.get_x() <= r.get_xmax() and
12151 p.get_y() >= r.get_ymin() and p.get_y() <= r.get_ymax();
12152 }
12153
12154 [[nodiscard]] Geom_Number center_coord(const size_t entry_idx,
12155 const bool split_x) const
12156 {
12157 return split_x ?
12158 entries_(entry_idx).bbox.get_xmin() + entries_(entry_idx).bbox.get_xmax() :
12159 entries_(entry_idx).bbox.get_ymin() + entries_(entry_idx).bbox.get_ymax();
12160 }
12161
12162 void insertion_sort_by_center(Array<size_t> & idx, const size_t lo,
12163 const size_t hi, const bool split_x) const
12164 {
12165 for (size_t i = lo + 1; i < hi; ++i)
12166 {
12167 const size_t key = idx(i);
12168 const Geom_Number key_val = center_coord(key, split_x);
12169 size_t j = i;
12170 while (j > lo and center_coord(idx(j - 1), split_x) > key_val)
12171 {
12172 idx(j) = idx(j - 1);
12173 --j;
12174 }
12175 idx(j) = key;
12176 }
12177 }
12178
12179 // Partially reorder idx[lo..hi) so idx[kth] is in its sorted position
12180 // by center along the split axis (expected linear time).
12181 void select_kth_by_center(Array<size_t> & idx, size_t lo, size_t hi,
12182 const size_t kth, const bool split_x) const
12183 {
12184 while (hi - lo > 16)
12185 {
12186 const Geom_Number pivot =
12187 center_coord(idx(lo + (hi - lo) / 2), split_x);
12188
12189 size_t lt = lo;
12190 size_t i = lo;
12191 size_t gt = hi;
12192 while (i < gt)
12193 {
12194 const Geom_Number val = center_coord(idx(i), split_x);
12195 if (val < pivot)
12196 {
12197 const size_t tmp = idx(lt);
12198 idx(lt) = idx(i);
12199 idx(i) = tmp;
12200 ++lt;
12201 ++i;
12202 }
12203 else if (val > pivot)
12204 {
12205 --gt;
12206 const size_t tmp = idx(gt);
12207 idx(gt) = idx(i);
12208 idx(i) = tmp;
12209 }
12210 else
12211 ++i;
12212 }
12213
12214 if (kth < lt)
12215 hi = lt;
12216 else if (kth >= gt)
12217 lo = gt;
12218 else
12219 return;
12220 }
12221
12222 insertion_sort_by_center(idx, lo, hi, split_x);
12223 }
12224
12225 // Build a subtree for entries[lo..hi) and return its node index.
12226 size_t build(Array<size_t> & idx, const size_t lo, const size_t hi)
12227 {
12228 if (hi - lo == 1)
12229 {
12230 Node n;
12231 n.bbox = entries_(idx(lo)).bbox;
12232 n.entry_idx = idx(lo);
12233 const size_t ni = nodes_.size();
12234 nodes_.append(n);
12235 return ni;
12236 }
12237
12238 // Compute enclosing bbox.
12239 Rectangle total = entries_(idx(lo)).bbox;
12240 for (size_t i = lo + 1; i < hi; ++i)
12241 total = union_bbox(total, entries_(idx(i)).bbox);
12242
12243 // Split along the longest axis.
12244 const Geom_Number dx = total.get_xmax() - total.get_xmin();
12245 const Geom_Number dy = total.get_ymax() - total.get_ymin();
12246 const bool split_x = dx >= dy;
12247
12248 const size_t mid = lo + (hi - lo) / 2;
12249 select_kth_by_center(idx, lo, hi, mid, split_x);
12250
12251 const size_t left = build(idx, lo, mid);
12252 const size_t right = build(idx, mid, hi);
12253
12254 Node n;
12255 n.bbox = union_bbox(nodes_(left).bbox, nodes_(right).bbox);
12256 n.left = left;
12257 n.right = right;
12258 const size_t ni = nodes_.size();
12259 nodes_.append(n);
12260 return ni;
12261 }
12262
12263 void query_impl(const size_t ni, const Rectangle & q,
12264 Array<size_t> & results) const
12265 {
12266 if (ni == NONE) return;
12267 const Node & n = nodes_(ni);
12268 if (not boxes_overlap(n.bbox, q)) return;
12269
12270 if (n.is_leaf())
12271 {
12272 results.append(entries_(n.entry_idx).index);
12273 return;
12274 }
12275
12276 query_impl(n.left, q, results);
12277 query_impl(n.right, q, results);
12278 }
12279
12280 void query_point_impl(const size_t ni, const Point & p,
12281 Array<size_t> & results) const
12282 {
12283 if (ni == NONE) return;
12284 const Node & n = nodes_(ni);
12285 if (not box_contains_point(n.bbox, p)) return;
12286
12287 if (n.is_leaf())
12288 {
12289 if (box_contains_point(entries_(n.entry_idx).bbox, p))
12290 results.append(entries_(n.entry_idx).index);
12291 return;
12292 }
12293
12296 }
12297
12298 size_t root_ = NONE;
12299
12300 public:
12301 AABBTree() = default;
12302
12308 void build(const Array<Entry> & entries)
12309 {
12310 entries_ = entries;
12311 nodes_ = Array<Node>();
12312 if (entries_.is_empty())
12313 {
12314 root_ = NONE;
12315 return;
12316 }
12317
12318 Array<size_t> idx;
12319 idx.reserve(entries_.size());
12320 for (size_t i = 0; i < entries_.size(); ++i)
12321 idx.append(i);
12322
12323 root_ = build(idx, 0, idx.size());
12324 }
12325
12333 {
12335 if (root_ != NONE)
12337 return results;
12338 }
12339
12347 {
12349 if (root_ != NONE)
12351 return results;
12352 }
12353
12356 {
12357 ah_domain_error_if(root_ == NONE) << "Empty tree";
12358 return nodes_(root_).bbox;
12359 }
12360
12362 [[nodiscard]] size_t size() const { return entries_.size(); }
12363
12365 [[nodiscard]] bool is_empty() const { return entries_.is_empty(); }
12366
12369 {
12371 if (root_ == NONE)
12372 return snap;
12373
12374 auto dfs = [&](const auto & self, const size_t ni, const size_t depth) -> size_t
12375 {
12376 const Node & n = nodes_(ni);
12377 const size_t out_idx = snap.nodes.size();
12378 snap.nodes.append(DebugNode{});
12379 DebugNode & dn = snap.nodes(out_idx);
12380 dn.node_index = ni;
12381 dn.bbox = n.bbox;
12382 dn.is_leaf = n.is_leaf();
12383 dn.depth = depth;
12384 if (dn.is_leaf)
12385 {
12386 dn.entry_index = n.entry_idx;
12387 dn.user_index = entries_(n.entry_idx).index;
12388 }
12389 else
12390 {
12391 dn.left = self(self, n.left, depth + 1);
12392 dn.right = self(self, n.right, depth + 1);
12393 }
12394 return out_idx;
12395 };
12396
12397 snap.root = dfs(dfs, root_, 0);
12398 return snap;
12399 }
12400 };
12401
12402 // ============================================================================
12403 // Polyline / Polygon Simplification
12404 // ============================================================================
12405
12422 {
12423 static void dp_recurse(const Array<Point> & pts, const size_t first,
12424 const size_t last, const Geom_Number & eps2,
12425 Array<bool> & keep)
12426 {
12427 if (last <= first + 1)
12428 return;
12429
12430 const Segment seg(pts(first), pts(last));
12432 size_t max_idx = first;
12433
12434 for (size_t i = first + 1; i < last; ++i)
12435 {
12436 Geom_Number d = seg.distance_to(pts(i));
12437 if (Geom_Number d2 = d * d; d2 > max_dist2)
12438 {
12439 max_dist2 = d2;
12440 max_idx = i;
12441 }
12442 }
12443
12444 if (max_dist2 > eps2)
12445 {
12446 keep(max_idx) = true;
12447 dp_recurse(pts, first, max_idx, eps2, keep);
12448 dp_recurse(pts, max_idx, last, eps2, keep);
12449 }
12450 }
12451
12452 public:
12464 const Geom_Number & epsilon) const
12465 {
12466 const size_t n = polyline.size();
12467 if (n <= 2)
12468 return polyline;
12469
12470 const Geom_Number eps2 = epsilon * epsilon;
12471 Array<bool> keep(n);
12472 for (size_t i = 0; i < n; ++i)
12473 keep.append(false);
12474 keep(0) = true;
12475 keep(n - 1) = true;
12476
12477 dp_recurse(polyline, 0, n - 1, eps2, keep);
12478
12479 Array<Point> result;
12480 for (size_t i = 0; i < n; ++i)
12481 if (keep(i))
12482 result.append(polyline(i));
12483
12484 return result;
12485 }
12486
12490 const Geom_Number & epsilon) const
12491 {
12492 Array<Point> arr;
12493 for (typename DynList<Point>::Iterator it(polyline);
12494 it.has_curr(); it.next_ne())
12495 arr.append(it.get_curr());
12496 return (*this)(arr, epsilon);
12497 }
12498
12509 [[nodiscard]] Polygon
12511 const Geom_Number & epsilon) const
12512 {
12514 const size_t n = verts.size();
12515 if (n <= 3)
12516 return poly;
12517
12518 // Find vertex farthest from vertex 0
12519 size_t far_idx = 1;
12521 for (size_t i = 1; i < n; ++i)
12522 {
12523 Geom_Number dx = verts(i).get_x() - verts(0).get_x();
12524 Geom_Number dy = verts(i).get_y() - verts(0).get_y();
12525 if (Geom_Number d2 = dx * dx + dy * dy; d2 > far_dist2)
12526 {
12527 far_dist2 = d2;
12528 far_idx = i;
12529 }
12530 }
12531
12532 // Build two open chains sharing endpoints at vertex 0 and far_idx
12533 Array<Point> chain1; // 0 → far_idx
12534 for (size_t i = 0; i <= far_idx; ++i)
12535 chain1.append(verts(i));
12536
12537 Array<Point> chain2; // far_idx → 0 (wrapping around)
12538 for (size_t i = far_idx; i < n; ++i)
12539 chain2.append(verts(i));
12540 chain2.append(verts(0));
12541
12542 auto s1 = (*this)(chain1, epsilon);
12543 auto s2 = (*this)(chain2, epsilon);
12544
12545 // Merge: s1 + s2 without duplicate junction points
12546 Array<Point> merged;
12547 for (size_t i = 0; i < s1.size(); ++i)
12548 merged.append(s1(i));
12549 for (size_t i = 1; i + 1 < s2.size(); ++i) // skip first (dup) & last (dup)
12550 merged.append(s2(i));
12551
12552 if (merged.size() < 3)
12553 return poly;
12554
12555 Polygon result;
12556 for (size_t i = 0; i < merged.size(); ++i)
12557 result.add_vertex(merged(i));
12558 result.close();
12559 return result;
12560 }
12561 };
12562
12576 {
12580 struct VWEntry
12581 {
12582 size_t idx;
12584
12586 bool operator <(const VWEntry & o) const { return area < o.area; }
12588 bool operator >(const VWEntry & o) const { return area > o.area; }
12589 };
12590
12591 [[nodiscard]] static Geom_Number
12592 effective_area(const Point & a, const Point & b, const Point & c)
12593 {
12595 if (ap < 0) ap = -ap;
12596 return ap / 2;
12597 }
12598
12599 [[nodiscard]] static Array<Point>
12602 {
12603 const size_t n = polyline.size();
12604 if (n <= 2)
12605 return polyline;
12606
12607 // prev/next linked list (index-based)
12608 Array<size_t> prev(n), next(n);
12609 Array<bool> alive(n);
12610 for (size_t i = 0; i < n; ++i)
12611 {
12612 prev.append(i == 0 ? n : i - 1); // n = sentinel for "no prev"
12613 next.append(i == n - 1 ? n : i + 1);
12614 alive.append(true);
12615 }
12616
12617 // Min-heap
12619
12620 // Compute initial areas for interior vertices
12622 for (size_t i = 0; i < n; ++i)
12623 areas.append(Geom_Number(-1));
12624
12625 for (size_t i = 1; i + 1 < n; ++i)
12626 {
12627 areas(i) = effective_area(polyline(prev(i)), polyline(i),
12628 polyline(next(i)));
12629 heap.put({i, areas(i)});
12630 }
12631
12632 while (not heap.is_empty())
12633 {
12634 auto [idx, area] = heap.get();
12635
12636 if (not alive(idx))
12637 continue;
12638 if (area != areas(idx))
12639 continue; // stale entry
12640 if (area >= area_threshold)
12641 break;
12642
12643 // Remove this vertex
12644 alive(idx) = false;
12645 const size_t p = prev(idx);
12646 const size_t nx = next(idx);
12647
12648 if (p < n)
12649 next(p) = nx;
12650 if (nx < n)
12651 prev(nx) = p;
12652
12653 // Recompute neighbor areas
12654 if (p < n and p != 0 and prev(p) < n)
12655 {
12656 areas(p) = effective_area(polyline(prev(p)), polyline(p),
12657 polyline(next(p)));
12658 heap.put({p, areas(p)});
12659 }
12660 if (nx < n and nx != n - 1 and next(nx) < n)
12661 {
12663 polyline(next(nx)));
12664 heap.put({nx, areas(nx)});
12665 }
12666 }
12667
12668 Array<Point> result;
12669 for (size_t i = 0; i < n; ++i)
12670 if (alive(i))
12671 result.append(polyline(i));
12672
12673 return result;
12674 }
12675
12676 public:
12688 const Geom_Number & area_threshold) const
12689 {
12691 }
12692
12696 const Geom_Number & area_threshold) const
12697 {
12698 Array<Point> arr;
12699 for (typename DynList<Point>::Iterator it(polyline);
12700 it.has_curr(); it.next_ne())
12701 arr.append(it.get_curr());
12703 }
12704
12714 [[nodiscard]] static Polygon
12717 {
12719 const size_t n = verts.size();
12720 if (n <= 3)
12721 return poly;
12722
12723 // Cyclic prev/next linked list
12724 Array<size_t> prev(n), next(n);
12725 Array<bool> alive(n);
12726 for (size_t i = 0; i < n; ++i)
12727 {
12728 prev.append(i == 0 ? n - 1 : i - 1);
12729 next.append(i == n - 1 ? 0 : i + 1);
12730 alive.append(true);
12731 }
12732
12734
12736 for (size_t i = 0; i < n; ++i)
12737 {
12738 areas.append(effective_area(verts(prev(i)), verts(i),
12739 verts(next(i))));
12740 heap.put({i, areas(i)});
12741 }
12742
12743 size_t remaining = n;
12744 while (not heap.is_empty() and remaining > 3)
12745 {
12746 auto [idx, area] = heap.get();
12747
12748 if (not alive(idx))
12749 continue;
12750 if (area != areas(idx))
12751 continue;
12752 if (area >= area_threshold)
12753 break;
12754
12755 alive(idx) = false;
12756 --remaining;
12757 const size_t p = prev(idx);
12758 const size_t nx = next(idx);
12759
12760 next(p) = nx;
12761 prev(nx) = p;
12762
12763 // Recompute neighbor areas
12764 areas(p) = effective_area(verts(prev(p)), verts(p), verts(next(p)));
12765 heap.put({p, areas(p)});
12766 areas(nx) = effective_area(verts(prev(nx)), verts(nx),
12767 verts(next(nx)));
12768 heap.put({nx, areas(nx)});
12769 }
12770
12771 Array<Point> result;
12772 for (size_t i = 0; i < n; ++i)
12773 if (alive(i))
12774 result.append(verts(i));
12775
12776 if (result.size() < 3)
12777 return poly;
12778
12779 Polygon rpoly;
12780 for (size_t i = 0; i < result.size(); ++i)
12781 rpoly.add_vertex(result(i));
12782 rpoly.close();
12783 return rpoly;
12784 }
12785 };
12786
12787 // ============================================================================
12788 // Chaikin Corner-Cutting Subdivision (Polygon Smoothing)
12789 // ============================================================================
12790
12806 {
12807 [[nodiscard]] static Array<Point>
12809 {
12810 const size_t n = pts.size();
12811 Array<Point> result;
12812 result.append(pts(0));
12813 for (size_t i = 0; i + 1 < n; ++i)
12814 {
12815 const auto & p = pts(i);
12816 const auto & q = pts(i + 1);
12818 result.append(Point(p.get_x() * one_r + q.get_x() * r,
12819 p.get_y() * one_r + q.get_y() * r));
12820 result.append(Point(p.get_x() * r + q.get_x() * one_r,
12821 p.get_y() * r + q.get_y() * one_r));
12822 }
12823 result.append(pts(n - 1));
12824 return result;
12825 }
12826
12827 [[nodiscard]] static Array<Point>
12829 {
12830 const size_t n = pts.size();
12831 Array<Point> result;
12833 for (size_t i = 0; i < n; ++i)
12834 {
12835 const auto & p = pts(i);
12836 const auto & q = pts((i + 1) % n);
12837 result.append(Point(p.get_x() * one_r + q.get_x() * r,
12838 p.get_y() * one_r + q.get_y() * r));
12839 result.append(Point(p.get_x() * r + q.get_x() * one_r,
12840 p.get_y() * r + q.get_y() * one_r));
12841 }
12842 return result;
12843 }
12844
12845 public:
12857 operator()(const Array<Point> & polyline, size_t iterations,
12858 const Geom_Number & ratio = Geom_Number(1, 4)) const
12859 {
12860 ah_domain_error_if(iterations == 0) << "iterations must be >= 1";
12862 ratio >= Geom_Number(1, 2)) << "ratio must be in (0, 0.5)";
12863
12865 for (size_t k = 0; k < iterations; ++k)
12866 cur = smooth_open_once(cur, ratio);
12867 return cur;
12868 }
12869
12872 operator()(const DynList<Point> & polyline, size_t iterations,
12873 const Geom_Number & ratio = Geom_Number(1, 4)) const
12874 {
12875 Array<Point> arr;
12876 for (typename DynList<Point>::Iterator it(polyline);
12877 it.has_curr(); it.next_ne())
12878 arr.append(it.get_curr());
12879 return (*this)(arr, iterations, ratio);
12880 }
12881
12892 [[nodiscard]] static Polygon
12893 smooth_polygon(const Polygon & poly, size_t iterations,
12894 const Geom_Number & ratio = Geom_Number(1, 4))
12895 {
12896 ah_domain_error_if(iterations == 0) << "iterations must be >= 1";
12898 ratio >= Geom_Number(1, 2)) << "ratio must be in (0, 0.5)";
12899
12901 for (size_t k = 0; k < iterations; ++k)
12902 cur = smooth_closed_once(cur, ratio);
12903
12904 Polygon rpoly;
12905 for (size_t i = 0; i < cur.size(); ++i)
12906 rpoly.add_vertex(cur(i));
12907 rpoly.close();
12908 return rpoly;
12909 }
12910 };
12911
12912 // ============================================================================
12913 // Trapezoidal Map Point Location
12914 // ============================================================================
12915
12969 {
12970 public:
12971 static constexpr size_t NONE = ~static_cast<size_t>(0);
12972
12980 {
12981 size_t top;
12982 size_t bottom;
12983 size_t leftp;
12984 size_t rightp;
12985 size_t upper_left;
12986 size_t lower_left;
12989 size_t dag_leaf;
12990 bool active;
12991 };
12992
12996 enum class NodeType
12997 {
12998 X_NODE,
12999 Y_NODE,
13000 LEAF
13001 };
13002
13006 struct DagNode
13007 {
13009 size_t index;
13010 size_t left;
13011 size_t right;
13012 };
13013
13017 enum class LocationType
13018 {
13019 TRAPEZOID,
13020 ON_SEGMENT,
13021 ON_POINT
13022 };
13023
13034
13038 struct Result
13039 {
13044 size_t dag_root;
13047
13060 {
13061 size_t ni = dag_root;
13062 while (true)
13063 {
13064 const auto & [type, index, left, right] = dag(ni);
13065 if (type == NodeType::LEAF)
13066 return {LocationType::TRAPEZOID, index, NONE, NONE};
13067
13068 if (type == NodeType::X_NODE)
13069 {
13070 const Point & xp = points(index);
13071 if (p == xp)
13072 return {LocationType::ON_POINT, NONE, NONE, index};
13073 ni = p < xp ? left : right;
13074 }
13075 else // Y_NODE
13076 {
13077 const Segment & seg = segments(index);
13078 const Geom_Number cross =
13080 seg.get_tgt_point(), p);
13081 if (cross == 0)
13082 {
13083 // the point is on the segment line — check if between endpoints
13084 if (seg.contains(p))
13085 return {
13087 NONE
13088 };
13089 // collinear but outside the segment extent — go above
13090 ni = left;
13091 }
13092 else if (cross > 0)
13093 ni = left; // above (left of directed segment)
13094 else
13095 ni = right; // below
13096 }
13097 }
13098 }
13099
13110 [[nodiscard]] bool contains(const Point & p) const
13111 {
13112 // Check exact boundary / vertex hits first.
13113 for (size_t i = 0; i < num_input_points; ++i)
13114 if (p == points(i)) return true;
13115 for (size_t i = 0; i < num_input_segments; ++i)
13116 if (segments(i).contains(p)) return true;
13117
13118 // Vertical ray cast downward: count input segments strictly below p
13119 // whose x-range covers p.x (half-open [src.x, tgt.x)).
13120 // Segments are stored left-to-right (src < tgt), so src.x <= tgt.x.
13121 // Vertical segments have empty half-open interval and are skipped.
13122 size_t crossings = 0;
13123 for (size_t i = 0; i < num_input_segments; ++i)
13124 {
13125 const Segment & seg = segments(i);
13126 const Point & a = seg.get_src_point();
13127 const Point & b = seg.get_tgt_point();
13128 if (p.get_x() < a.get_x() or p.get_x() >= b.get_x())
13129 continue;
13130 // y of segment at p.x (b.x != a.x guaranteed by half-open check)
13131 const Geom_Number seg_y = a.get_y() +
13132 (b.get_y() - a.get_y()) * (p.get_x() - a.get_x()) /
13133 (b.get_x() - a.get_x());
13134 if (seg_y < p.get_y())
13135 ++crossings;
13136 }
13137 return crossings % 2 == 1;
13138 }
13139 };
13140
13141 private:
13144 const Point & p, const Segment & seg)
13145 {
13147 seg.get_tgt_point(), p) > 0;
13148 }
13149
13151 [[nodiscard]] static size_t dag_locate(
13152 const Array<Point> & pts, const Array<Segment> & segs,
13153 const Array<DagNode> & dag, const Point & p, size_t root)
13154 {
13155 size_t ni = root;
13156 while (true)
13157 {
13158 const auto & [type, index, left, right] = dag(ni);
13159 if (type == NodeType::LEAF)
13160 return index;
13161
13162 if (type == NodeType::X_NODE)
13163 {
13164 if (const Point & xp = pts(index); p == xp)
13165 ni = right; // tie-break: go right
13166 else
13167 ni = p < xp ? left : right;
13168 }
13169 else // Y_NODE
13170 {
13171 const Geom_Number cross =
13172 area_of_parallelogram(segs(index).get_src_point(),
13173 segs(index).get_tgt_point(), p);
13174 if (cross > 0)
13175 ni = left; // above
13176 else if (cross < 0)
13177 ni = right; // below
13178 else
13179 ni = left; // on segment line → go above
13180 }
13181 }
13182 }
13183
13186 const size_t top, const size_t bottom,
13187 const size_t leftp, const size_t rightp)
13188 {
13189 const size_t ti = traps.size();
13190 const size_t di = dag.size();
13191 traps.append(Trapezoid{
13192 top, bottom, leftp, rightp,
13193 NONE, NONE, NONE, NONE, di, true
13194 });
13196 return ti;
13197 }
13198
13200 static void replace_leaf(Array<DagNode> & dag, const size_t leaf_idx,
13201 const NodeType type, const size_t index,
13202 const size_t left, const size_t right)
13203 {
13204 dag(leaf_idx).type = type;
13205 dag(leaf_idx).index = index;
13206 dag(leaf_idx).left = left;
13207 dag(leaf_idx).right = right;
13208 }
13209
13212 const size_t neighbor_idx, const size_t old_ti,
13213 const size_t new_ti)
13214 {
13215 if (neighbor_idx == NONE) return;
13217 if (nb.upper_left == old_ti) nb.upper_left = new_ti;
13218 if (nb.lower_left == old_ti) nb.lower_left = new_ti;
13219 if (nb.upper_right == old_ti) nb.upper_right = new_ti;
13220 if (nb.lower_right == old_ti) nb.lower_right = new_ti;
13221 }
13222
13225 [[nodiscard]] static Array<size_t>
13227 const Array<Trapezoid> & traps,
13228 const size_t seg_idx, const size_t start_trap)
13229 {
13232
13233 const Segment & seg = segs(seg_idx);
13234 const Point & rp = seg.get_tgt_point();
13235
13236 size_t ti = start_trap;
13237 while (true)
13238 {
13239 const Trapezoid & t = traps(ti);
13240 const Point & rightp = pts(t.rightp);
13241 // If the right boundary of this trapezoid is at or past the
13242 // segment's right endpoint, we're done.
13243 if (rp < rightp or rp == rightp)
13244 break;
13245
13246 // Go right: the segment exits through the right wall of t.
13247 // If the right endpoint of the segment is above the segment
13248 // at the right boundary x, go to upper_right; else lower_right.
13249 if (t.upper_right != NONE and t.lower_right != NONE)
13250 {
13251 // Test which neighbor the segment enters.
13252 if (point_above_segment(rightp, seg))
13253 ti = t.lower_right;
13254 else
13255 ti = t.upper_right;
13256 }
13257 else if (t.upper_right != NONE)
13258 ti = t.upper_right;
13259 else if (t.lower_right != NONE)
13260 ti = t.lower_right;
13261 else
13262 break; // no right neighbor
13263
13264 crossed.append(ti);
13265 }
13266 return crossed;
13267 }
13268
13272 const size_t seg_idx, const size_t trap_idx)
13273 {
13274 const Trapezoid old_t = traps(trap_idx);
13275 const Segment & seg = segs(seg_idx);
13276 const Point & lp = seg.get_src_point();
13277 const Point & rp = seg.get_tgt_point();
13278
13279 // Find point indices.
13280 size_t lp_idx = NONE, rp_idx = NONE;
13281 for (size_t i = 0; i < pts.size(); ++i)
13282 {
13283 if (pts(i) == lp) lp_idx = i;
13284 if (pts(i) == rp) rp_idx = i;
13285 }
13286
13287 const bool shared_left = (old_t.leftp == lp_idx);
13288 const bool shared_right = (old_t.rightp == rp_idx);
13289
13290 // Deactivate old trapezoid.
13291 traps(trap_idx).active = false;
13292
13293 // Create new trapezoids:
13294 // A = left of lp (if not shared)
13295 // B = above segment (between lp and rp)
13296 // C = below segment (between lp and rp)
13297 // D = right of rp (if not shared)
13298
13299 const size_t dag_old = old_t.dag_leaf;
13300
13301 size_t a_idx = NONE, d_idx = NONE;
13302
13303 if (not shared_left)
13304 a_idx = make_trapezoid(traps, dag, old_t.top, old_t.bottom,
13305 old_t.leftp, lp_idx);
13306 const size_t b_idx = make_trapezoid(traps, dag, old_t.top, seg_idx,
13307 lp_idx, rp_idx);
13308 const size_t c_idx = make_trapezoid(traps, dag, seg_idx, old_t.bottom,
13309 lp_idx, rp_idx);
13310 if (not shared_right)
13311 d_idx = make_trapezoid(traps, dag, old_t.top, old_t.bottom,
13312 rp_idx, old_t.rightp);
13313
13314 // Set neighbor pointers.
13315 // A neighbors:
13316 if (a_idx != NONE)
13317 {
13318 traps(a_idx).upper_left = old_t.upper_left;
13319 traps(a_idx).lower_left = old_t.lower_left;
13320 traps(a_idx).upper_right = b_idx;
13321 traps(a_idx).lower_right = c_idx;
13322 update_neighbor(traps, old_t.upper_left, trap_idx, a_idx);
13323 update_neighbor(traps, old_t.lower_left, trap_idx, a_idx);
13324 }
13325
13326 // B neighbors:
13327 traps(b_idx).upper_left = shared_left ? old_t.upper_left : a_idx;
13328 traps(b_idx).lower_left = shared_left ? old_t.lower_left : a_idx;
13329 traps(b_idx).upper_right = shared_right ? old_t.upper_right : d_idx;
13330 traps(b_idx).lower_right = shared_right ? old_t.lower_right : d_idx;
13331
13332 // C neighbors:
13333 traps(c_idx).upper_left = shared_left ? old_t.upper_left : a_idx;
13334 traps(c_idx).lower_left = shared_left ? old_t.lower_left : a_idx;
13335 traps(c_idx).upper_right = shared_right ? old_t.upper_right : d_idx;
13336 traps(c_idx).lower_right = shared_right ? old_t.lower_right : d_idx;
13337
13338 if (shared_left)
13339 {
13340 update_neighbor(traps, old_t.upper_left, trap_idx, b_idx);
13341 update_neighbor(traps, old_t.lower_left, trap_idx, c_idx);
13342 }
13343
13344 // D neighbors:
13345 if (d_idx != NONE)
13346 {
13347 traps(d_idx).upper_left = b_idx;
13348 traps(d_idx).lower_left = c_idx;
13349 traps(d_idx).upper_right = old_t.upper_right;
13350 traps(d_idx).lower_right = old_t.lower_right;
13351 update_neighbor(traps, old_t.upper_right, trap_idx, d_idx);
13352 update_neighbor(traps, old_t.lower_right, trap_idx, d_idx);
13353 }
13354
13355 if (shared_right)
13356 {
13357 update_neighbor(traps, old_t.upper_right, trap_idx, b_idx);
13358 update_neighbor(traps, old_t.lower_right, trap_idx, c_idx);
13359 }
13360
13361 // Build DAG sub-tree rooted at dag_old.
13362 // Structure depends on shared_left / shared_right.
13364 {
13365 // X(lp) -> left=A, right=X(rp) -> left=Y(seg)->above=B,below=C, right=D
13366 const size_t y_node = dag.size();
13367 dag.append(DagNode{
13368 NodeType::Y_NODE, seg_idx,
13369 traps(b_idx).dag_leaf, traps(c_idx).dag_leaf
13370 });
13371 const size_t x_rp = dag.size();
13372 dag.append(DagNode{
13374 y_node, traps(d_idx).dag_leaf
13375 });
13377 traps(a_idx).dag_leaf, x_rp);
13378 }
13380 {
13381 // Y(seg) -> above=B, below=C wrapped in X(rp)->left=Y, right=D
13382 const size_t y_node = dag.size();
13383 dag.append(DagNode{
13384 NodeType::Y_NODE, seg_idx,
13385 traps(b_idx).dag_leaf, traps(c_idx).dag_leaf
13386 });
13388 y_node, traps(d_idx).dag_leaf);
13389 }
13391 {
13392 // X(lp) -> left=A, right=Y(seg)->above=B, below=C
13393 const size_t y_node = dag.size();
13394 dag.append(DagNode{
13395 NodeType::Y_NODE, seg_idx,
13396 traps(b_idx).dag_leaf, traps(c_idx).dag_leaf
13397 });
13399 traps(a_idx).dag_leaf, y_node);
13400 }
13401 else // both shared
13402 {
13403 // Just Y(seg) -> above=B, below=C
13404 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx,
13405 traps(b_idx).dag_leaf, traps(c_idx).dag_leaf);
13406 }
13407 }
13408
13412 const size_t seg_idx,
13413 const Array<size_t> & crossed)
13414 {
13415 const Segment & seg = segs(seg_idx);
13416 const Point & lp = seg.get_src_point();
13417 const Point & rp = seg.get_tgt_point();
13418
13419 size_t lp_idx = NONE, rp_idx = NONE;
13420 for (size_t i = 0; i < pts.size(); ++i)
13421 {
13422 if (pts(i) == lp) lp_idx = i;
13423 if (pts(i) == rp) rp_idx = i;
13424 }
13425
13426 const size_t nc = crossed.size();
13427
13428 // For each crossed trapezoid, create above/below sub-trapezoids.
13429 // Adjacent above (or below) trapezoids that share the same top (or
13430 // bottom) segment are merged.
13431 Array<size_t> above_traps; // one per crossed, but may be merged
13434 below_traps.reserve(nc);
13435
13436 size_t prev_above = NONE, prev_below = NONE;
13437 size_t left_trap = NONE; // left-of-lp trapezoid
13438
13439 for (size_t ci = 0; ci < nc; ++ci)
13440 {
13441 const size_t ti = crossed(ci);
13442 const Trapezoid old_t = traps(ti);
13443 traps(ti).active = false;
13444
13445 const bool is_first = (ci == 0);
13446 const bool is_last = (ci == nc - 1);
13447 const bool shared_left = is_first and (old_t.leftp == lp_idx);
13448 const bool shared_right = is_last and (old_t.rightp == rp_idx);
13449
13450 // Determine the left/right x-boundaries for the new sub-trapezoids.
13451 const size_t new_leftp = is_first ? lp_idx : old_t.leftp;
13452 const size_t new_rightp = is_last ? rp_idx : old_t.rightp;
13453
13454 // Above trapezoid.
13455 bool merged_above = false;
13456 if (prev_above != NONE and traps(prev_above).top == old_t.top)
13457 {
13458 // Merge: extend prev_above to the right.
13459 traps(prev_above).rightp = new_rightp;
13460 above_traps.append(prev_above);
13461 merged_above = true;
13462 }
13463 else
13464 {
13465 const size_t ai = make_trapezoid(traps, dag, old_t.top, seg_idx,
13467 above_traps.append(ai);
13468 prev_above = ai;
13469 }
13470
13471 // Below trapezoid.
13472 bool merged_below = false;
13473 if (prev_below != NONE and traps(prev_below).bottom == old_t.bottom)
13474 {
13475 traps(prev_below).rightp = new_rightp;
13476 below_traps.append(prev_below);
13477 merged_below = true;
13478 }
13479 else
13480 {
13481 const size_t bi = make_trapezoid(traps, dag, seg_idx, old_t.bottom,
13483 below_traps.append(bi);
13484 prev_below = bi;
13485 }
13486
13487 const size_t cur_above = above_traps(ci);
13488 const size_t cur_below = below_traps(ci);
13489
13490 // Handle the left trapezoid (only for the first crossed trapezoid).
13492 {
13493 left_trap = make_trapezoid(traps, dag, old_t.top, old_t.bottom,
13494 old_t.leftp, lp_idx);
13495 traps(left_trap).upper_left = old_t.upper_left;
13496 traps(left_trap).lower_left = old_t.lower_left;
13497 traps(left_trap).upper_right = cur_above;
13498 traps(left_trap).lower_right = cur_below;
13499 update_neighbor(traps, old_t.upper_left, ti, left_trap);
13500 update_neighbor(traps, old_t.lower_left, ti, left_trap);
13501 }
13502
13503 // Set left neighbors for above/below.
13504 if (is_first)
13505 {
13506 if (not merged_above)
13507 {
13508 if (shared_left)
13509 {
13510 traps(cur_above).upper_left = old_t.upper_left;
13511 traps(cur_above).lower_left = old_t.lower_left;
13512 update_neighbor(traps, old_t.upper_left, ti, cur_above);
13513 }
13514 else
13515 {
13516 traps(cur_above).upper_left = left_trap;
13517 traps(cur_above).lower_left = left_trap;
13518 }
13519 }
13520 if (not merged_below)
13521 {
13522 if (shared_left)
13523 {
13524 traps(cur_below).upper_left = old_t.upper_left;
13525 traps(cur_below).lower_left = old_t.lower_left;
13526 update_neighbor(traps, old_t.lower_left, ti, cur_below);
13527 }
13528 else
13529 {
13530 traps(cur_below).upper_left = left_trap;
13531 traps(cur_below).lower_left = left_trap;
13532 }
13533 }
13534 }
13535 else
13536 {
13537 // Interior crossed trapezoid.
13538 if (not merged_above)
13539 {
13540 // Left neighbor of new above is the previous above.
13541 traps(cur_above).upper_left = above_traps(ci - 1);
13542 traps(cur_above).lower_left = above_traps(ci - 1);
13543 // Also set the previous above's right neighbors.
13544 if (above_traps(ci - 1) != cur_above)
13545 {
13546 traps(above_traps(ci - 1)).upper_right = cur_above;
13547 traps(above_traps(ci - 1)).lower_right = cur_above;
13548 }
13549 }
13550 if (not merged_below)
13551 {
13552 traps(cur_below).upper_left = below_traps(ci - 1);
13553 traps(cur_below).lower_left = below_traps(ci - 1);
13554 if (below_traps(ci - 1) != cur_below)
13555 {
13556 traps(below_traps(ci - 1)).upper_right = cur_below;
13557 traps(below_traps(ci - 1)).lower_right = cur_below;
13558 }
13559 }
13560
13561 // Connect left neighbors from old trapezoid.
13562 if (not merged_above)
13563 if (old_t.upper_left != NONE and old_t.upper_left != crossed(ci - 1))
13564 {
13565 traps(cur_above).upper_left = old_t.upper_left;
13566 update_neighbor(traps, old_t.upper_left, ti, cur_above);
13567 }
13568 if (not merged_below)
13569 if (old_t.lower_left != NONE and old_t.lower_left != crossed(ci - 1))
13570 {
13571 traps(cur_below).lower_left = old_t.lower_left;
13572 update_neighbor(traps, old_t.lower_left, ti, cur_below);
13573 }
13574 }
13575
13576 // Handle right trapezoid (only for last crossed trapezoid).
13577 if (is_last)
13578 {
13579 if (not shared_right)
13580 {
13581 const size_t right_trap = make_trapezoid(traps, dag, old_t.top, old_t.bottom,
13582 rp_idx, old_t.rightp);
13583 traps(right_trap).upper_left = cur_above;
13584 traps(right_trap).lower_left = cur_below;
13585 traps(right_trap).upper_right = old_t.upper_right;
13586 traps(right_trap).lower_right = old_t.lower_right;
13587 update_neighbor(traps, old_t.upper_right, ti, right_trap);
13588 update_neighbor(traps, old_t.lower_right, ti, right_trap);
13589 traps(cur_above).upper_right = right_trap;
13590 traps(cur_above).lower_right = right_trap;
13591 traps(cur_below).upper_right = right_trap;
13592 traps(cur_below).lower_right = right_trap;
13593
13594 // Build DAG for this trapezoid.
13595 const size_t y_node = dag.size();
13596 dag.append(DagNode{
13597 NodeType::Y_NODE, seg_idx,
13598 traps(cur_above).dag_leaf,
13599 traps(cur_below).dag_leaf
13600 });
13601 const size_t dag_old = old_t.dag_leaf;
13603 y_node, traps(right_trap).dag_leaf);
13604 }
13605 else
13606 {
13607 traps(cur_above).upper_right = old_t.upper_right;
13608 traps(cur_above).lower_right = old_t.lower_right;
13609 traps(cur_below).upper_right = old_t.upper_right;
13610 traps(cur_below).lower_right = old_t.lower_right;
13611 update_neighbor(traps, old_t.upper_right, ti, cur_above);
13612 update_neighbor(traps, old_t.lower_right, ti, cur_below);
13613
13614 // Build DAG for this trapezoid.
13615 const size_t dag_old = old_t.dag_leaf;
13616 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx,
13617 traps(cur_above).dag_leaf,
13618 traps(cur_below).dag_leaf);
13619 }
13620 }
13621 else if (is_first)
13622 {
13623 // First but not last — build DAG with X-node for left endpoint.
13624 const size_t dag_old = old_t.dag_leaf;
13625 const size_t y_node = dag.size();
13626 dag.append(DagNode{
13627 NodeType::Y_NODE, seg_idx,
13628 traps(cur_above).dag_leaf,
13629 traps(cur_below).dag_leaf
13630 });
13631 if (shared_left)
13632 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx,
13633 traps(cur_above).dag_leaf,
13634 traps(cur_below).dag_leaf);
13635 else
13637 traps(left_trap).dag_leaf, y_node);
13638 }
13639 else
13640 {
13641 // Middle trapezoid — just a Y-node.
13642 const size_t dag_old = old_t.dag_leaf;
13643 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx,
13644 traps(cur_above).dag_leaf,
13645 traps(cur_below).dag_leaf);
13646 }
13647 }
13648 }
13649
13650 public:
13659 [[nodiscard]] Result
13661 {
13662 Result ret;
13663 const size_t n = input_segments.size();
13665
13666 // Collect unique points from segments.
13667 // Orient all segments left-to-right (src < tgt lexicographically).
13668 ret.segments.reserve(n + 2);
13669 for (size_t i = 0; i < n; ++i)
13670 {
13671 const Point & s = input_segments(i).get_src_point();
13672 const Point & t = input_segments(i).get_tgt_point();
13673 ah_domain_error_if(s == t) << "Degenerate segment (src == tgt) at index " << i;
13674 if (s < t)
13675 ret.segments.append(Segment(s, t));
13676 else
13677 ret.segments.append(Segment(t, s));
13678 }
13679
13680 // Collect and deduplicate points.
13681 {
13683 all_pts.reserve(2 * n);
13684 for (size_t i = 0; i < n; ++i)
13685 {
13686 all_pts.append(ret.segments(i).get_src_point());
13687 all_pts.append(ret.segments(i).get_tgt_point());
13688 }
13689 // Sort lexicographically.
13691 // Deduplicate.
13692 for (size_t i = 0; i < all_pts.size(); ++i)
13693 {
13694 if (ret.points.is_empty() or
13695 not (ret.points(ret.points.size() - 1) == all_pts(i)))
13696 ret.points.append(all_pts(i));
13697 }
13698 }
13699
13700 ret.num_input_points = ret.points.size();
13701
13702 // Compute bounding box with margin.
13703 if (ret.points.is_empty())
13704 {
13705 // No input segments: create a trivial bounding box.
13706 ret.points.append(Point(-1, -1));
13707 ret.points.append(Point(1, -1));
13708 ret.points.append(Point(1, 1));
13709 ret.points.append(Point(-1, 1));
13710 }
13711
13712 Geom_Number mnx = ret.points(0).get_x();
13714 Geom_Number mny = ret.points(0).get_y();
13716 for (size_t i = 1; i < ret.points.size(); ++i)
13717 {
13718 if (ret.points(i).get_x() < mnx) mnx = ret.points(i).get_x();
13719 if (ret.points(i).get_x() > mxx) mxx = ret.points(i).get_x();
13720 if (ret.points(i).get_y() < mny) mny = ret.points(i).get_y();
13721 if (ret.points(i).get_y() > mxy) mxy = ret.points(i).get_y();
13722 }
13723
13724 Geom_Number margin = (mxx - mnx > mxy - mny) ? mxx - mnx : mxy - mny;
13725 if (margin == 0) margin = 1;
13726 margin = margin + 1;
13727
13728 // Bounding box corner points.
13729 const Point bl(mnx - margin, mny - margin);
13730 const Point br(mxx + margin, mny - margin);
13731 const Point tl(mnx - margin, mxy + margin);
13732 const Point tr(mxx + margin, mxy + margin);
13733
13734 const size_t bl_idx = ret.points.size();
13735 ret.points.append(bl);
13736 ret.points.append(br);
13737 ret.points.append(tl);
13738 const size_t tr_idx = ret.points.size();
13739 ret.points.append(tr);
13740
13741 // Bounding box segments: top (tl→tr) and bottom (bl→br).
13742 const size_t top_seg = ret.segments.size();
13743 ret.segments.append(Segment(tl, tr));
13744 const size_t bot_seg = ret.segments.size();
13745 ret.segments.append(Segment(bl, br));
13746
13747 // Initialize: one trapezoid spanning the bounding box.
13748 ret.dag_root = 0;
13749 const size_t t0 = make_trapezoid(ret.trapezoids, ret.dag,
13751 (void) t0;
13752
13753 // Randomized insertion order.
13754 if (n == 0) return ret;
13755
13756 Array<size_t> order;
13757 order.reserve(n);
13758 for (size_t i = 0; i < n; ++i)
13759 order.append(i);
13760
13761 // Fisher-Yates shuffle.
13762 {
13763 std::random_device rd;
13764 std::mt19937 gen(rd());
13765 for (size_t i = n - 1; i > 0; --i)
13766 {
13767 std::uniform_int_distribution<size_t> dis(0, i);
13768 const size_t j = dis(gen);
13769 const size_t tmp = order(i);
13770 order(i) = order(j);
13771 order(j) = tmp;
13772 }
13773 }
13774
13775 // Insert segments one by one.
13776 for (size_t oi = 0; oi < n; ++oi)
13777 {
13778 const size_t si = order(oi);
13779 const Segment & seg = ret.segments(si);
13780
13781 // Locate the trapezoid containing the left endpoint.
13782 const size_t start = dag_locate(ret.points, ret.segments,
13783 ret.dag, seg.get_src_point(),
13784 ret.dag_root);
13785
13786 // Find all trapezoids crossed by this segment.
13788 ret.points, ret.segments, ret.trapezoids, si, start);
13789
13790 if (crossed.size() == 1)
13791 split_single_trapezoid(ret.points, ret.segments,
13792 ret.trapezoids, ret.dag,
13793 si, crossed(0));
13794 else
13795 split_multiple_trapezoids(ret.points, ret.segments,
13796 ret.trapezoids, ret.dag,
13797 si, crossed);
13798 }
13799
13800 return ret;
13801 }
13802
13809 [[nodiscard]] Result operator()(const Polygon & polygon) const
13810 {
13811 ah_domain_error_if(not polygon.is_closed()) << "Polygon must be closed";
13812 ah_domain_error_if(polygon.size() < 3) << "Polygon must have at least 3 vertices";
13813
13815 for (Polygon::Segment_Iterator it(polygon); it.has_curr(); it.next_ne())
13816 segs.append(it.get_current_segment());
13817 return (*this)(segs);
13818 }
13819
13826 [[nodiscard]] Result operator()(const Array<Polygon> & polygons) const
13827 {
13829 for (size_t i = 0; i < polygons.size(); ++i)
13830 {
13831 const Polygon & poly = polygons(i);
13832 ah_domain_error_if(not poly.is_closed()) << "Polygon " << i << " must be closed";
13833 ah_domain_error_if(poly.size() < 3) << "Polygon " << i << " must have at least 3 vertices";
13834 for (Polygon::Segment_Iterator it(poly); it.has_curr(); it.next_ne())
13835 segs.append(it.get_current_segment());
13836 }
13837 return (*this)(segs);
13838 }
13839 };
13840
13841 // ============================================================================
13842 // GeomNumber Concept (C++20)
13843 // ============================================================================
13844
13845#if __cplusplus >= 202002L
13846
13847# include <concepts>
13848
13870 template <typename T>
13871 concept GeomNumberType = requires(T a, T b, int i)
13872 {
13873 { T(i) } -> std::convertible_to<T>;
13874 { a + b } -> std::convertible_to<T>;
13875 { a - b } -> std::convertible_to<T>;
13876 { a * b } -> std::convertible_to<T>;
13877 { a / b } -> std::convertible_to<T>;
13878 { a == b } -> std::convertible_to<bool>;
13879 { a != b } -> std::convertible_to<bool>;
13880 { a < b } -> std::convertible_to<bool>;
13881 { a <= b } -> std::convertible_to<bool>;
13882 { a > b } -> std::convertible_to<bool>;
13883 { a >= b } -> std::convertible_to<bool>;
13884 { -a } -> std::convertible_to<T>;
13885 };
13886
13887 // Verify that Geom_Number satisfies the concept.
13888 static_assert(GeomNumberType<Geom_Number>,
13889 "Geom_Number must satisfy GeomNumberType");
13890 static_assert(GeomNumberType<double>,
13891 "double must satisfy GeomNumberType");
13892 static_assert(GeomNumberType<long long>,
13893 "long long must satisfy GeomNumberType");
13894
13895#endif // C++20
13896} // namespace Aleph
13897
13898# endif // GEOM_ALGORITHMS_H
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:522
bool operator==(const Time &l, const Time &r)
Definition ah-time.H:133
bool operator<(const Time &l, const Time &r)
Definition ah-time.H:142
Deduplicate sequential Aleph containers in-place.
WeightedDigraph::Arc Arc
long double h
Definition btreepic.C:154
Axis-aligned bounding box tree for spatial queries.
Rectangle root_bbox() const
Return the root bounding box (union of all entries).
Array< Entry > entries_
Original entries stored in the tree.
size_t build(Array< size_t > &idx, const size_t lo, const size_t hi)
void select_kth_by_center(Array< size_t > &idx, size_t lo, size_t hi, const size_t kth, const bool split_x) const
static bool box_contains_point(const Rectangle &r, const Point &p)
Checks if a rectangle contains a point.
static Rectangle union_bbox(const Rectangle &a, const Rectangle &b)
Computes the union of two axis-aligned bounding boxes.
void query_point_impl(const size_t ni, const Point &p, Array< size_t > &results) const
Geom_Number center_coord(const size_t entry_idx, const bool split_x) const
AABBTree()=default
bool is_empty() const
Whether the tree is empty.
size_t size() const
Number of entries.
void insertion_sort_by_center(Array< size_t > &idx, const size_t lo, const size_t hi, const bool split_x) const
static constexpr size_t NONE
void build(const Array< Entry > &entries)
Build the AABB tree from an array of entries.
DebugSnapshot debug_snapshot() const
Return the full tree structure for visualization/debug.
static bool boxes_overlap(const Rectangle &a, const Rectangle &b)
Checks if two rectangles overlap.
Array< size_t > query(const Rectangle &query) const
Find all entries whose bounding box overlaps the query rectangle.
Array< size_t > query_point(const Point &p) const
Find all entries whose bounding box contains the query point.
Array< Node > nodes_
Internal array storing tree nodes.
void query_impl(const size_t ni, const Rectangle &q, Array< size_t > &results) const
Alpha shape of a point set.
Result operator()(const DynList< Point > &points, const Geom_Number &alpha_squared) const
Compute the α-shape.
Andrew's monotonic chain convex hull algorithm.
Polygon operator()(const DynList< Point > &point_set) const
Compute the convex hull of a point set.
static Array< Point > sorted_unique_points(const DynList< Point > &point_set)
Extracts points from a list, sorts them and removes duplicates.
static Geom_Number turn(const Point &a, const Point &b, const Point &c)
Computes the turn orientation of triple (a, b, c).
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
void empty() noexcept
Empties the container.
Definition tpl_array.H:336
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:348
T & insert(const T &data)
insert a copy of data at the beginning of the array.
Definition tpl_array.H:281
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
T & get_last() noexcept
return a modifiable reference to the last element.
Definition tpl_array.H:366
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Quadratic and cubic Bézier curves with exact rational arithmetic.
static Polygon approximate_quadratic(const Point &p0, const Point &p1, const Point &p2, const size_t n)
Approximate a quadratic Bézier as a polyline (polygon without closing).
static Array< Point > sample_cubic(const Point &p0, const Point &p1, const Point &p2, const Point &p3, const size_t n)
Sample a cubic Bézier into n+1 points.
static Rectangle control_bbox(const Point &p0, const Point &p1, const Point &p2, const Point &p3)
Compute the bounding box of a cubic Bézier's control polygon.
static Array< Point > sample_quadratic(const Point &p0, const Point &p1, const Point &p2, const size_t n)
Sample a quadratic Bézier into n+1 points (t = 0, 1/n, ..., 1).
static SplitResult split_cubic(const Point &p0, const Point &p1, const Point &p2, const Point &p3, const Geom_Number &t)
static Point quadratic(const Point &p0, const Point &p1, const Point &p2, const Geom_Number &t)
Evaluate a quadratic Bézier at parameter t.
static Polygon approximate_cubic(const Point &p0, const Point &p1, const Point &p2, const Point &p3, const size_t n)
Approximate a cubic Bézier as a polyline.
static Point cubic(const Point &p0, const Point &p1, const Point &p2, const Point &p3, const Geom_Number &t)
Evaluate a cubic Bézier at parameter t.
Boolean operations on simple polygons (union, intersection, difference) using the Greiner-Hormann alg...
static void sort_indices_by_alpha(Array< size_t > &idx, const Array< Geom_Number > &keys)
Insertion-sort indices by a key array.
static Array< Point > extract(const Polygon &p)
Extract vertices from a polygon into an Array.
static void reverse_array(Array< Point > &v)
Reverse a vertex array in place.
static Geom_Number edge_param(const Point &P, const Point &Q, const Point &I)
Compute parameter t of point I along directed edge P→Q.
static Array< Polygon > compute_union(const Polygon &a, const Polygon &b)
Array< Polygon > difference(const Polygon &a, const Polygon &b) const
Convenience: difference (a minus b).
Array< Polygon > polygon_union(const Polygon &a, const Polygon &b) const
Convenience: union.
Array< Polygon > intersection(const Polygon &a, const Polygon &b) const
Convenience: intersection.
Array< Polygon > operator()(const Polygon &a, const Polygon &b, Op op) const
Compute a boolean operation on two simple polygons.
static bool point_inside_ccw(const Array< Point > &poly, const Point &p)
Point-in-polygon test on a CCW vertex array (winding number).
static Array< Polygon > greiner_hormann(const Array< Point > &va, const Array< Point > &vb, bool start_entering)
Run Greiner-Hormann and return result polygons.
Op
Type of Boolean operation to perform.
@ INTERSECTION
Find regions common to both polygons.
@ DIFFERENCE
Find regions in the first polygon not covered by the second.
@ UNION
Find regions covered by either polygon.
static Polygon build_poly(const Array< Point > &pts)
Build a polygon from a vertex array (bypasses colinearity merge).
static Array< Polygon > compute_difference(const Polygon &a, const Polygon &b)
static void ensure_ccw(Array< Point > &v)
Ensure vertices are in CCW order.
static Array< Polygon > compute_intersection(const Polygon &a, const Polygon &b)
Brute force convex hull algorithm.
static SegmentSet extreme_edges(const DynList< Point > &point_set)
Polygon operator()(const DynList< Point > &point_set) const
Compute the convex hull of a point set.
static bool are_all_points_on_left(const DynList< Point > &l, const Segment &s)
Checks if all points in a list lie to the left of a segment.
Chaikin corner-cutting subdivision for polygon smoothing.
static Array< Point > smooth_closed_once(const Array< Point > &pts, const Geom_Number &r)
Array< Point > operator()(const DynList< Point > &polyline, size_t iterations, const Geom_Number &ratio=Geom_Number(1, 4)) const
Overload accepting DynList.
static Polygon smooth_polygon(const Polygon &poly, size_t iterations, const Geom_Number &ratio=Geom_Number(1, 4))
Smooth a closed polygon.
Array< Point > operator()(const Array< Point > &polyline, size_t iterations, const Geom_Number &ratio=Geom_Number(1, 4)) const
Smooth an open polyline.
static Array< Point > smooth_open_once(const Array< Point > &pts, const Geom_Number &r)
Closest pair of points via divide and conquer.
static Result make_result(const Point &a, const Point &b)
Construct a Result object for a pair (a,b).
static Result brute_force(const Array< Point > &px, const size_t l, const size_t r)
Brute-force closest pair on a small subarray.
static Geom_Number dist2(const Point &a, const Point &b)
Squared Euclidean distance between two points.
Result operator()(const DynList< Point > &point_set) const
Compute the closest pair of points.
Segment closest_segment(DynList< Point > &point_set) const
Convenience wrapper returning the closest segment.
static Result recurse(const Array< Point > &px, const size_t l, const size_t r, Array< Point > &py)
Recursive divide-and-conquer step.
Constrained Delaunay Triangulation via Sloan's flip-based method.
Result operator()(const std::initializer_list< Point > points, const std::initializer_list< Segment > constraints) const
Convenience overload using initializer lists.
static void flip_edge(Array< Tri > &tris, const size_t tri_a, const size_t tri_b)
Flip the diagonal of the quad formed by tri_a and tri_b.
static bool lexicographic_less(const Point &p1, const Point &p2)
static Array< Point > merge_and_deduplicate(const DynList< Point > &points, const DynList< Segment > &constraints)
Merge input points, constraint endpoints, and constraint-constraint intersection points; then dedupli...
static size_t adj_of(const Tri &t, const size_t n)
Return the local index of the edge shared with neighbor n.
static bool edge_exists(const Array< Tri > &tris, const size_t u, const size_t v, size_t &out_tri, size_t &out_local)
Check if edge (u,v) already exists in the mesh.
static void build_adjacency(Array< Tri > &tris, const Array< IndexedTriangle > &dt_tris)
Build adjacency mesh from DT output.
static Array< IndexedEdge > map_constraints(const Array< Point > &sites, const DynList< Segment > &constraints)
Split constraints at interior collinear points.
static bool is_convex_quad(const Array< Point > &pts, const size_t a, const size_t b, const size_t c, const size_t d)
True if diagonal (a,d) in quad (a,b,d) + (a,d,c) forms a convex quad.
static void lawson_flip(const Array< Point > &pts, Array< Tri > &tris)
Lawson flip pass: restore Delaunay for non-constrained edges.
static void mark_constrained(Array< Tri > &tris, const size_t u, const size_t v)
Mark edge (u,v) as constrained in the mesh.
static void enforce_constraint(Array< Point > &pts, Array< Tri > &tris, const size_t u, const size_t v)
Enforce a single constraint edge (u,v) using Sloan's flip approach.
static DynList< Triangle > as_triangles(const Result &result)
Convert indexed triangulation to geometric triangles.
Result operator()(const DynList< Point > &points, const DynList< Segment > &constraints) const
Compute constrained Delaunay triangulation.
static size_t find_point_index(const Array< Point > &pts, const Point &p)
Find index of point p in sorted unique array, or NONE.
static size_t local_of(const Tri &t, const size_t id)
Return local index (0,1,2) of vertex id in triangle t, or NONE.
static Array< CrossingEdge > find_crossing_edges(const Array< Point > &pts, const Array< Tri > &tris, const size_t u, const size_t v)
static size_t edge_opposite(const Tri &t, const size_t a, const size_t b)
Return the local edge index for edge (a,b) in triangle t.
Decompose a simple polygon into convex parts using Hertel-Mehlhorn.
static Array< Point > extract_vertices(const Polygon &p)
static size_t find_pos(const Array< size_t > &face, size_t v)
Array< Polygon > operator()(const Polygon &poly) const
Decompose polygon poly into convex parts.
static bool can_merge(const Array< Point > &pts, const Array< size_t > &f1, const Array< size_t > &f2, size_t u, size_t v)
Check whether merging faces f1 and f2 across diagonal (u,v) is convex.
static Array< size_t > merge_faces(const Array< size_t > &f1, const Array< size_t > &f2, size_t u, size_t v)
Merge f1 and f2 by removing their shared edge (u,v).
static bool is_polygon_edge(size_t u, size_t v, size_t n)
Distance between two closed convex polygons using GJK.
Result operator()(const Polygon &p, const Polygon &q) const
Compute the distance between two closed convex polygons.
static Result exact_refine_disjoint(const Polygon &p, const Polygon &q, const Array< Point > &pv, const Array< Point > &qv)
static double norm2(const double x, const double y) noexcept
static ClosestSimplexResult closest_from_simplex(const std::vector< SupportPoint > &simplex)
static constexpr size_t kMaxIters
static double dot2(const double ax, const double ay, const double bx, const double by) noexcept
static Point point_from_double(const double x, const double y)
static bool polygons_intersect_or_contain(const Polygon &p, const Polygon &q)
static SupportPoint support(const Array< Point > &p, const Array< Point > &q, const double dx, const double dy)
static Point lerp_point(const Point &a, const Point &b, const double t)
static double to_double(const Geom_Number &v)
static Point centroid_of(const Array< Point > &v)
Basic exact intersection for closed convex polygons.
Polygon operator()(const Polygon &subject, const Polygon &clip) const
Intersect two closed convex polygons.
static Array< Point > normalize_vertices(const Array< Point > &pts)
static Point line_intersection(const Point &s, const Point &e, const Point &a, const Point &b)
static Geom_Number signed_double_area(const Array< Point > &verts)
static bool inside_half_plane(const Point &p, const Point &a, const Point &b, const bool clip_ccw)
static Array< Point > extract_vertices(const Polygon &poly)
static void push_clean(Array< Point > &out, const Point &p)
static Polygon build_polygon(const Array< Point > &pts)
static bool is_convex(const Array< Point > &verts)
Inward (erosion) and outward (dilation) offset of convex polygons.
static Point line_intersect(const Point &a1, const Point &a2, const Point &b1, const Point &b2)
Line-line intersection of (a1,a2) and (b1,b2).
static bool is_convex(const Array< Point > &v)
static void ensure_ccw(Array< Point > &v)
static Geom_Number signed_double_area(const Array< Point > &v)
static void offset_edge(const Point &a, const Point &b, const Geom_Number &d, const bool inward, Point &oa, Point &ob)
Compute two points on the offset line of edge (a, b) moved by distance d along its outward (or inward...
static Polygon outward(const Polygon &convex_poly, const Geom_Number &distance)
Outward offset (dilation) of a convex polygon.
static Array< Point > extract_verts(const Polygon &poly)
static Polygon inward(const Polygon &convex_poly, const Geom_Number &distance)
Inward offset (erosion) of a convex polygon.
Polygon triangulation using the ear-cutting algorithm.
static Geom_Number signed_double_area(const Polygon &p)
static Array< Point > extract_vertices(const Polygon &p)
static bool diagonal(const Polygon &p, const Vertex &a, const Vertex &b)
Check if segment (a, b) is a valid internal diagonal.
static EarsSet init_ears(const Polygon &p)
Initialize the set of ear vertices.
static void normalize_to_ccw(Polygon &p)
static bool in_cone(const Polygon &p, const Vertex &a, const Vertex &b)
Check if vertex b is inside the cone formed at vertex a.
static bool diagonalize(const Polygon &p, const Segment &s)
Check if a segment is a valid diagonal of the polygon.
DynList< Triangle > operator()(const Polygon &poly) const
Triangulate the polygon.
Exact Delaunay triangulation using the Bowyer-Watson incremental algorithm.
static bool all_collinear(const Array< Point > &pts)
Check if all points in an array are collinear.
Result operator()(const DynList< Point > &point_set) const
Compute Delaunay triangulation of a point set.
static bool point_in_circumcircle(const Array< Point > &pts, const size_t ia, const size_t ib, const size_t ic, const size_t ip)
Predicate to check if a point lies inside the circumcircle of a triangle.
static DynList< Triangle > as_triangles(const Result &result)
Convert indexed triangulation to geometric triangles.
static Array< Point > unique_points(const DynList< Point > &point_set)
Extracts unique points from a list and sorts them lexicographically.
static bool lexicographic_less(const Point &p1, const Point &p2)
Lexicographical comparison for points.
O(n log n) expected-time Delaunay's triangulation.
static bool in_cc(const Array< Point > &pts, const size_t ia, const size_t ib, const size_t ic, const size_t ip)
Standard in-circumcircle test using exact in_circle_determinant.
Result operator()(const std::initializer_list< Point > il) const
static void remap_adj(Array< Tri > &tris, const size_t ot, const size_t nt)
Update neighbor n of the old triangle ot to point to a new triangle nt.
static bool point_in_tri(const Array< Point > &pts, const Tri &t, const size_t pidx)
True if point p is inside or on triangle t (using orientation tests).
static size_t adj_of(const Tri &t, const size_t n)
Return the local index of the edge shared with neighbor n.
static size_t locate(const Array< Point > &pts, const Array< Tri > &tris, const Array< DagNode > &dag, const size_t pidx, const size_t root)
Locate the leaf triangle containing point pidx via a DAG walk.
static size_t local_of(const Tri &t, const size_t id)
Return local index (0,1,2) of vertex id in triangle t, or NONE.
Result operator()(const DynList< Point > &point_set) const
Douglas-Peucker polyline simplification.
static void dp_recurse(const Array< Point > &pts, const size_t first, const size_t last, const Geom_Number &eps2, Array< bool > &keep)
Polygon simplify_polygon(const Polygon &poly, const Geom_Number &epsilon) const
Simplify a closed polygon.
Array< Point > operator()(const DynList< Point > &polyline, const Geom_Number &epsilon) const
Overload accepting DynList.
Array< Point > operator()(const Array< Point > &polyline, const Geom_Number &epsilon) const
Simplify an open polyline.
Dynamic heap of elements of type T ordered by a comparison functor.
T get()
Alias for getMin().
T & put(const T &item)
Synonym of insert().
Dynamic doubly linked list with O(1) size and bidirectional access.
T & append(const T &item)
Append a copied item at the end of the list.
Iterator on the items of list.
Definition htlist.H:1420
T & get_curr() const
Return the current item.
Definition htlist.H:1446
Dynamic singly linked list with functional programming support.
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
T & get_last() const
Return the last item of the list.
Definition htlist.H:1363
Dynamic set backed by balanced binary search trees with automatic memory management.
long position(const Key &key) const
Returns the infix (ordered) position of the key.
Key * insert(const Key &key)
Inserts a key into the dynamic set.
size_t remove(const Key &key)
Removes a key from the dynamic set.
Key * search(const Key &key) const
Find an element in the set.
Very simple queue implemented with a contiguous array.
Fixed length stack.
size_t size() const noexcept
Return the number of elements stored in the stack.
bool is_empty() const noexcept
Return true if stack is empty.
T pop() noexcept
Pop by moving the top of stack.
T & top() noexcept
Return a modifiable reference to stack's top.
T & push(const T &data) noexcept(std::is_nothrow_copy_assignable_v< T >)
Push a copy of data
bool is_empty() const noexcept
Shared Bowyer-Watson core parameterized by in-circle predicate.
static Array< IndexedTriangle > triangulate(Array< Point > pts, const size_t n, InCirclePredicate point_in_circumcircle)
static void toggle_edge(EdgeSet &boundary, size_t u, size_t v)
Shared polygon helpers used across multiple geometry algorithms.
static Geom_Number area(const Array< Point > &verts)
Compute the absolute area of a vertex array.
static Geom_Number signed_double_area(const Polygon &poly)
Compute twice the signed area of a polygon.
static Geom_Number signed_area(const Polygon &poly)
Compute the actual signed area of a polygon.
static bool is_convex(const Array< Point > &verts)
Check if a vertex array forms a convex polygon.
static Geom_Number signed_double_area(const Array< Point > &verts)
Compute twice the signed area (shoelace formula without division).
static void ensure_ccw(Array< Point > &verts)
static Geom_Number signed_area(const Array< Point > &verts)
Compute the actual signed area of a vertex array.
static Geom_Number area(const Polygon &poly)
Compute the absolute area of a polygon.
static Array< Point > extract_vertices(const Polygon &poly)
Extract vertices from a polygon into an array for indexed access.
Serialization utilities for geometry objects.
static std::string to_geojson(const Triangle &t)
Triangle → GeoJSON Polygon.
static std::string to_wkt(const Point3D &p)
Converts a Point3D to WKT format: "POINT Z (x y z)".
static std::string to_geojson(const Polygon &poly)
Polygon → GeoJSON Polygon.
static std::string to_wkt(const Polygon &poly)
Converts a Polygon to WKT format: "POLYGON ((x1 y1, x2 y2, ..., x1 y1))".
static std::string to_wkt(const Rectangle &r)
Converts a Rectangle to WKT format: "POLYGON ((xmin ymin, xmax ymin, xmax ymax, xmin ymax,...
static std::string to_wkt(const Triangle &t)
Converts a Triangle to WKT format: "POLYGON ((x1 y1, x2 y2, x3 y3, x1 y1))".
static std::string dbl(const Geom_Number &n)
Formats a Geom_Number as a string with high precision.
static std::string to_geojson(const Segment &s)
Converts a Segment to a GeoJSON LineString.
static std::string to_wkt(const Segment &s)
Converts a Segment to WKT format: "LINESTRING (x1 y1, x2 y2)".
static std::string to_geojson(const Point &p)
Converts a Point to a GeoJSON geometry object.
static std::string to_wkt(const Point &p)
Converts a Point to WKT format: "POINT (x y)".
static std::string to_geojson(const Point3D &p)
Point3D → GeoJSON Point with Z.
Shared edge-group extraction for triangle meshes.
static void append_edge(Array< EdgeRef > &edges, size_t a, size_t b, const size_t tri, const size_t third)
static void for_each_sorted_edge_group(const TriangleArray &triangles, GroupFn on_group)
Gift wrapping (Jarvis march) convex hull algorithm.
static const Point * get_lowest_point(const DynList< Point > &point_set)
Finds the point with the minimum y-coordinate (and minimum x on ties).
Polygon operator()(const DynList< Point > &point_set) const
Compute the convex hull of a point set.
Graham scan convex hull algorithm.
static Array< Point > sorted_unique_points(const DynList< Point > &point_set)
Extracts points from a list, sorts them and removes duplicates.
static Geom_Number turn(const Point &a, const Point &b, const Point &c)
Computes the turn orientation of triple (a, b, c).
Polygon operator()(const DynList< Point > &point_set) const
Compute the convex hull of a point set.
void next_ne() noexcept
Move the iterator one position forward guaranteeing no exception.
Definition htlist.H:965
bool has_curr() const noexcept
Definition htlist.H:930
constexpr bool is_empty() const noexcept
Definition htlist.H:419
size_t size() const noexcept
Count the number of elements of the list.
Definition htlist.H:1065
Exact bounded intersection of half-planes.
static Point line_intersection(const HalfPlane &a, const HalfPlane &b)
static Geom_Number cross_dir(const HalfPlane &a, const HalfPlane &b)
static Geom_Number dot_dir(const HalfPlane &a, const HalfPlane &b)
static Polygon build_polygon(const Array< Point > &pts)
static bool parallel(const HalfPlane &a, const HalfPlane &b)
static bool upper_half(const HalfPlane &h)
static Geom_Number signed_double_area(const Array< Point > &verts)
static bool same_direction(const HalfPlane &a, const HalfPlane &b)
static void push_clean(Array< Point > &out, const Point &p)
static Array< HalfPlane > from_convex_polygon(const Polygon &poly)
Build half-planes from the edges of a closed convex polygon.
static Array< Point > normalize_vertices(const Array< Point > &pts)
Polygon operator()(const Array< HalfPlane > &halfplanes) const
Intersect half-planes and return bounded feasible polygon.
Spatial point index for O(log n) nearest-neighbor queries.
DebugSnapshot debug_snapshot() const
Build a deterministic geometric partition snapshot for visualization.
bool is_empty() const noexcept
Return true if the tree is empty.
static KDTreePointSearch build(const Array< Point > &points, const Geom_Number &xmin, const Geom_Number &ymin, const Geom_Number &xmax, const Geom_Number &ymax)
Build a balanced KD-tree from a point array.
size_t size() const noexcept
Return the number of points in the tree.
bool contains(const Point &p) const
Check if a point exists in the tree.
static bool lexicographic_less(const Point &a, const Point &b)
KDTreePointSearch(const Geom_Number &xmin, const Geom_Number &ymin, const Geom_Number &xmax, const Geom_Number &ymax)
Construct an empty KD-tree for the given bounding region.
std::optional< Point > nearest(const Point &p) const
Find the nearest neighbor to query point p.
void range(const Geom_Number &xmin, const Geom_Number &ymin, const Geom_Number &xmax, const Geom_Number &ymax, DynList< Point > *out) const
Collect all points inside the given rectangle.
static Array< Point > unique_points(Array< Point > points)
bool insert(const Point &p)
Insert a point. Returns true if inserted, false if duplicate.
void for_each(Op &&op) const
Apply an operation to every point (inorder traversal).
Reusable event-driven sweep line framework.
DynSetTree< SeqEvent, Avl_Tree, CmpSeqEvent > queue_
void enqueue(Event &&e)
Enqueue an event (move version).
bool has_events() const noexcept
True if the event queue is non-empty.
Event dequeue()
Remove and return the next (minimum) event.
void enqueue(const Event &e)
Enqueue an event.
void run(Handler &&handler)
Run the sweep: process every event through handler.
size_t pending() const noexcept
Number of pending events.
void run(Handler &&handler, Array< Event > &out)
Run the sweep, collecting every event into out.
const Event & peek() const
Peek at the next event without removing it.
void clear() noexcept
Discard all pending events.
Smallest circle enclosing a point set (Welzl's algorithm).
Circle operator()(const DynList< Point > &points) const
Compute the minimum enclosing circle of a point set.
static Circle from_two_points(const Point &a, const Point &b)
Smallest circle with a and b on its boundary (diameter).
static Circle from_one_point(const Point &a)
Circle through a single point (degenerate, radius = 0).
static Circle mec_with_two_points(const Array< Point > &pts, size_t n, const Point &p1, const Point &p2)
Minimum enclosing circle with boundary points p1 and p2.
static Circle welzl_iterative(const Array< Point > &pts)
Iterative Welzl: three nested loops.
static Circle from_three_points(const Point &a, const Point &b, const Point &c)
Circumscribed circle through three points.
static Circle mec_with_point(const Array< Point > &pts, size_t n, const Point &p1)
Minimum enclosing circle with boundary point p1.
Circle operator()(std::initializer_list< Point > points) const
Convenience overload accepting an initializer list.
Exact Minkowski sum of two closed convex polygons.
static Array< Point > normalize(Array< Point > v)
Ensure CCW and rotate so that the bottom-most vertex is first.
static Array< Point > extract_vertices(const Polygon &poly)
static Geom_Number signed_double_area(const Array< Point > &v)
Polygon operator()(const Polygon &P, const Polygon &Q) const
Compute the Minkowski sum of two convex polygons.
static Point edge_vec(const Array< Point > &v, const size_t i)
static bool is_convex(const Array< Point > &verts)
static Geom_Number cross(const Point &a, const Point &b)
size_t left_edge_of_point(const Point &p, const Geom_Number &sweep_y) const
static void split_by_label(Node *root, const Geom_Number &label, Node *&left, Node *&right)
size_t successor_for_insert(const size_t edge, const Geom_Number &sweep_y) const
static Node * insert_by_label(Node *root, Node *node)
Geom_Number fresh_label_between(const size_t pred, const size_t succ) const
size_t predecessor_for_insert(const size_t edge, const Geom_Number &sweep_y) const
void insert(const size_t edge, const Geom_Number &sweep_y)
static Node * erase_by_label(Node *root, const Geom_Number &label, Node *&removed)
O(n log n) triangulation of simple polygons via y-monotone partition + linear-time monotone triangula...
static Geom_Number signed_double_area(const Array< Point > &v)
static void ensure_ccw(Array< Point > &v)
static bool is_below(const Point &a, const Point &b)
Lexicographical "below" comparison.
static bool is_above(const Point &a, const Point &b)
Lexicographical "above" comparison (y primary, x secondary).
static bool edge_status_less(const size_t lhs, const size_t rhs, const Geom_Number &sweep_y, const Array< Point > &verts)
static Array< Array< size_t > > build_faces_from_diagonals(const Array< Point > &verts, const DynSetTree< std::pair< size_t, size_t > > &diagonals)
VertexType
Classification of vertices for y-monotone decomposition.
static bool is_y_monotone(const Array< Point > &v)
Check if a CCW polygon vertex array is y-monotone.
static Array< Array< size_t > > decompose_to_monotone_faces(const Array< Point > &verts)
DynList< Triangle > operator()(Polygon p) const
Triangulate a simple polygon.
static DynList< Triangle > triangulate_monotone(const Array< Point > &verts)
Triangulate a y-monotone polygon given as a vertex array.
static bool edge_goes_down(const Array< Point > &verts, const size_t edge)
Check if the edge starting at edge points downwards.
static VertexType classify_vertex(const Array< Point > &v, const size_t i)
static Array< Point > extract_vertices(const Polygon &p)
static Geom_Number edge_x_at_y(const Array< Point > &verts, const size_t edge, const Geom_Number &y)
static bool regular_interior_right(const Array< Point > &v, const size_t i)
Represents a point in 3D space with exact rational coordinates.
Definition point.H:2973
const Geom_Number & get_z() const
Gets the z-coordinate.
Definition point.H:3000
const Geom_Number & get_x() const
Gets the x-coordinate.
Definition point.H:2996
const Geom_Number & get_y() const
Gets the y-coordinate.
Definition point.H:2998
Exact point-in-polygon classification via winding number.
static bool strictly_contains(const Polygon &poly, const Point &p)
Return true only for strict interior points.
static Location locate(const Polygon &poly, const Point &p)
Classify point location with respect to a polygon.
static bool contains(const Polygon &poly, const Point &p)
Return true if the point is inside or on the boundary.
Represents a point with rectangular coordinates in a 2D plane.
Definition point.H:229
const Geom_Number & get_x() const noexcept
Gets the x-coordinate value.
Definition point.H:457
const Geom_Number & get_y() const noexcept
Gets the y-coordinate value.
Definition point.H:466
Point midpoint(const Point &other) const
Calculates the midpoint between this point and another.
Definition point.H:448
bool is_to_left_on_from(const Point &p1, const Point &p2) const
Checks if this point is to the left of or on the line from p1 to p2.
Definition point.H:524
bool is_left_of(const Point &p1, const Point &p2) const
Checks if this point is to the left of the directed line from p1 to p2.
Definition point.H:495
Geom_Number distance_squared_to(const Point &that) const
Calculates the squared Euclidean distance to another point.
Definition point.H:1454
Offset (inflate/deflate) an arbitrary simple polygon.
static Result cleanup(const Array< Point > &raw)
Full cleanup pipeline.
Result operator()(const Polygon &poly, const Geom_Number &distance, const JoinType join=JoinType::Miter, const Geom_Number &miter_limit=Geom_Number(2)) const
Offset a simple polygon by the given distance.
static Array< Polygon > extract_contours(Array< AugVertex > &aug)
Walk the augmented polygon and extract valid CCW contours.
JoinType
Strategy for connecting offset edges at vertices.
@ Bevel
Connect edge endpoints with a straight line.
@ Miter
Extend edges until they intersect.
static void sort_by_alpha(Array< size_t > &idx, const Array< Geom_Number > &keys)
Insertion-sort indices by alpha key.
Polygon offset_polygon(const Polygon &poly, const Geom_Number &distance, JoinType join=JoinType::Miter, const Geom_Number &miter_limit=Geom_Number(2)) const
Convenience: return the single largest-area result polygon.
static Polygon build_poly(const Array< Point > &pts)
Build a polygon from a point array.
static void offset_edge(const Point &a, const Point &b, const Geom_Number &d, const bool inward, Point &oa, Point &ob)
Compute two points on the offset line of edge (a→b) shifted by |d| along its outward or inward normal...
static Array< Point > compute_raw_offset(const Array< Point > &v, const Geom_Number &distance, const JoinType join, const Geom_Number &miter_limit)
static Array< Intersection > find_self_intersections(const Array< Point > &raw)
O(n²) scan for all proper self-intersections.
static Geom_Number abs_area(const Polygon &p)
static Array< AugVertex > build_augmented(const Array< Point > &raw, const Array< Intersection > &isects)
Build the augmented vertex list with crossing links.
static Point line_intersect(const Point &a1, const Point &a2, const Point &b1, const Point &b2)
Line–line intersection of (a1,a2) and (b1,b2).
static Geom_Number sq_dist(const Point &a, const Point &b)
Compute the squared distance between two points.
static Geom_Number edge_param(const Point &P, const Point &Q, const Point &I)
Compute parameter t of point I along directed edge P→Q.
Iterator over the edges (segments) of a polygon.
Definition polygon.H:520
bool has_curr() const
Check if there is a current segment.
Definition polygon.H:537
A general (irregular) 2D polygon defined by a sequence of vertices.
Definition polygon.H:246
const Vertex & get_next_vertex(const Vertex &v) const
Get the vertex following v in the polygon.
Definition polygon.H:601
const Vertex & get_prev_vertex(const Vertex &v) const
Get the vertex preceding v in the polygon.
Definition polygon.H:615
void remove_vertex(const Vertex &v)
Remove a vertex from the polygon.
Definition polygon.H:777
PointLocation locate_point(const Point &p) const
Classify a point against this closed polygon.
Definition polygon.H:884
const Vertex & get_first_vertex() const
Get the first vertex of the polygon.
Definition polygon.H:578
void add_vertex(const Point &point)
Add a vertex to the polygon.
Definition polygon.H:677
void close()
Close the polygon.
Definition polygon.H:840
const bool & is_closed() const
Check if the polygon is closed.
Definition polygon.H:473
const size_t & size() const
Get the number of vertices.
Definition polygon.H:477
Power diagram (weighted Voronoi diagram).
static Point power_center(const WeightedSite &a, const WeightedSite &b, const WeightedSite &c)
Compute the power center of three weighted sites.
Result operator()(const Array< WeightedSite > &sites) const
Compute the power diagram.
QuickHull convex hull algorithm.
static std::pair< DynList< Point >, DynList< Point > > partition(const DynList< Point > &point_set, const Point &a, const Point &b)
Split points by the line through directed segment (a,b).
Polygon operator()(const DynList< Point > &point_set) const
Compute the convex hull of a point set.
static std::pair< Point, Point > search_extremes(const DynList< Point > &point_set)
Find leftmost and rightmost points by x coordinate.
static DynList< Point > quick_hull(DynList< Point > &point_set, const Point &a, const Point &b)
Recursive QuickHull step for a directed edge (a,b).
static std::pair< DynList< Point >, DynList< Point > > get_right_points(DynList< Point > &point_set, const Point &a, const Point &b, const Point &c)
Partition points to the right of edges (a,c) and (c,b).
static Point get_farthest_point(const DynList< Point > &point_set, const Segment &s)
Return the point in point_set farthest from segment s.
Static 2D range tree for orthogonal range queries.
void build_node(const size_t node, const size_t lo, const size_t hi)
bool is_empty() const noexcept
DynList< Point > query(const Geom_Number &xmin, const Geom_Number &xmax, const Geom_Number &ymin, const Geom_Number &ymax) const
Query: return all points inside [xmin,xmax] × [ymin,ymax].
Array< Point > pts_
points sorted by x (primary key)
Array< Node > tree_
implicit binary tree (1-indexed)
size_t size() const noexcept
size_t lower_bound_x(const Geom_Number &xval) const
Binary search: first index i in pts_ where pts_(i).get_x() >= xval.
static size_t lower_bound_y(const Array< Point > &arr, const Geom_Number &ymin)
Binary search: first index i in arr where arr(i).get_y() >= ymin.
void query_range(const size_t node, const size_t lo, const size_t hi, const size_t qlo, const size_t qhi, const Geom_Number &ymin, const Geom_Number &ymax, DynList< Point > &out) const
size_t upper_bound_x(const Geom_Number &xval) const
Binary search: first index i in pts_ where pts_(i).get_x() > xval.
void build(const DynList< Point > &points)
Build the range tree from a point set.
static size_t upper_bound_y(const Array< Point > &arr, const Geom_Number &ymax)
Binary search: first index i in arr where arr(i).get_y() > ymax.
DebugSnapshot debug_snapshot() const
Return structural snapshot for visualization/debug.
An axis-aligned rectangle.
Definition point.H:1737
const Geom_Number & get_xmin() const
Gets the minimum x-coordinate.
Definition point.H:1764
const Geom_Number & get_ymax() const
Gets the maximum y-coordinate.
Definition point.H:1770
const Geom_Number & get_ymin() const
Gets the minimum y-coordinate.
Definition point.H:1766
const Geom_Number & get_xmax() const
Gets the maximum x-coordinate.
Definition point.H:1768
Exact regular triangulation (weighted Delaunay) via Bowyer-Watson.
static bool all_collinear(const Array< WeightedSite > &s)
static bool point_in_power_circle(const Array< Point > &pts, const Array< Geom_Number > &wts, const size_t ia, const size_t ib, const size_t ic, const size_t ip)
Weighted in-circle (power) predicate.
static bool lex_less(const Point &p1, const Point &p2)
static Array< WeightedSite > unique_sites(const Array< WeightedSite > &input)
Result operator()(const Array< WeightedSite > &weighted_sites) const
Compute the regular triangulation of a weighted point set.
Rotating calipers metrics for convex polygons.
static DiameterResult make_diameter(const Point &a, const Point &b)
static Array< Point > extract_vertices(const Polygon &poly)
Extract polygon vertices into an array.
static DiameterResult diameter(const Polygon &poly)
Compute convex polygon diameter (farthest vertex pair).
static WidthResult minimum_width(const Polygon &poly)
Compute the minimum width of a closed convex polygon.
static bool is_convex(const Array< Point > &verts)
Check if a polygon vertex cycle is convex.
Compute the full planar subdivision induced by a set of segments.
static size_t find_vertex(const Array< Point > &verts, const Point &p)
Find the index of point p in a sorted vertex array (binary search).
static bool angle_lt(const Geom_Number &dx1, const Geom_Number &dy1, const Geom_Number &dx2, const Geom_Number &dy2)
Compare two directions from the same origin by angle (exact).
static constexpr size_t NONE
static int angle_quad(const Geom_Number &dx, const Geom_Number &dy)
Angular quadrant of direction (dx, dy).
Result operator()(const Array< Segment > &segments) const
Compute the arrangement of a set of segments.
static bool pt_less(const Point &a, const Point &b)
Lexicographic point comparison.
Dedicated exact intersection for a single pair of segments.
Kind
Types of intersection between two segments.
@ OVERLAP
Intersection over a collinear interval.
@ POINT
Intersection at a single point.
static Segment collinear_overlap(const Segment &s1, const Segment &s2)
static Point point_max_on_axis(const Point &a, const Point &b, const bool vertical_axis)
static bool point_less_on_axis(const Point &a, const Point &b, const bool vertical_axis)
Result operator()(const Segment &s1, const Segment &s2) const
Computes the intersection of two line segments.
static Point point_min_on_axis(const Point &a, const Point &b, const bool vertical_axis)
Represents a line segment between two points.
Definition point.H:827
bool intersects_properly_with(const Segment &s) const
Checks if this segment properly intersects another segment.
Definition point.H:1228
Point intersection_with(const Segment &s) const
Computes the intersection point of the infinite lines defined by two segments.
Definition point.H:1313
bool intersects_with(const Segment &s) const
Checks if this segment intersects another one (including endpoints and collinear overlap).
Definition point.H:1274
Point project(const Point &p) const
Orthogonal projection of a point onto this segment's infinite line, clamped to the segment's endpoint...
Definition point.H:1147
Geom_Number distance_to(const Point &p) const
Calculates the Euclidean distance from a point to this segment.
Definition point.H:1169
bool contains(const Point &p) const
Checks if a point lies on this segment.
Definition point.H:1246
const Point & get_tgt_point() const noexcept
Gets the target point of the segment.
Definition point.H:921
const Point & get_src_point() const noexcept
Gets the source point of the segment.
Definition point.H:915
Compute the shortest Euclidean path between two points inside a simple polygon.
static size_t find_tri(const Array< Point > &pts, const Array< ITri > &tris, const Point &p)
Find a triangle containing point p.
static constexpr size_t NONE
static Array< size_t > find_sleeve(const Array< ITri > &tris, const size_t src, const size_t dst)
BFS on dual graph → sleeve.
static Array< ITri > build_tris(const Array< Point > &pts, const DynList< Triangle > &tl)
Build indexed triangulation with adjacency.
static Geom_Number cross(const Point &a, const Point &b, const Point &c)
Cross product (b-a) x (c-a).
DynList< Point > operator()(const Polygon &polygon, const Point &source, const Point &target) const
Compute the shortest path between two points in a simple polygon.
static bool point_in_triangle(const Array< Point > &pts, const ITri &t, const Point &p)
Point-in-triangle test.
static size_t find_index(const Array< Point > &pts, const Point &p)
Match a Point from a Triangle to an index in pts (exact comparison).
Sweep-line status as a balanced tree with O(log n) updates.
size_t predecessor_for_insert(const size_t seg, const Geom_Number &sx) const
static Node * insert_by_label(Node *root, Node *node)
Geom_Number fresh_label_between(const size_t pred, const size_t succ) const
void insert(const size_t seg, const Geom_Number &sx)
size_t successor_for_insert(const size_t seg, const Geom_Number &sx) const
static void split_by_label(Node *root, const Geom_Number &label, Node *&left, Node *&right)
static Node * erase_by_label(Node *root, const Geom_Number &label, Node *&removed)
Report all pairwise intersection points among a set of segments.
static bool status_less(const size_t lhs, const size_t rhs, const Geom_Number &sx, const Array< Segment > &segs)
static void check_and_enqueue(const Array< Segment > &segs, const size_t i, const size_t j, const Geom_Number &sx, LineSweepFramework< Event, EventLess > &eq, DynSetTree< size_t, Treap_Rk > &seen_pairs, const size_t n)
Detect an intersection and enqueue it as a future event.
static bool slope_less(const Segment &a, const Segment &b)
static Segment canonicalize(const Segment &s)
Canonicalize a segment so its src is the "left" endpoint.
Array< Intersection > operator()(const Array< Segment > &segments) const
Find all pairwise segment intersection points.
static bool event_less(const Event &a, const Event &b)
Ordering for events in the sweep queue.
static Geom_Number y_at_x(const Segment &s, const Geom_Number &x)
Evaluate the y-coordinate of segment s at x = x.
EventType
Types of events in the Bentley-Ottmann sweep.
O(log n) point location via trapezoidal map with DAG search.
static void split_multiple_trapezoids(Array< Point > &pts, Array< Segment > &segs, Array< Trapezoid > &traps, Array< DagNode > &dag, const size_t seg_idx, const Array< size_t > &crossed)
Handle the case where a segment crosses multiple trapezoids.
LocationType
Classification of a point location query result.
@ TRAPEZOID
The point is strictly inside a trapezoid.
@ ON_POINT
The point coincides with a segment endpoint.
@ ON_SEGMENT
The point lies on a segment boundary.
Result operator()(const Array< Polygon > &polygons) const
Build a trapezoidal map from multiple closed polygons.
static void update_neighbor(Array< Trapezoid > &traps, const size_t neighbor_idx, const size_t old_ti, const size_t new_ti)
Update all neighbor pointers that referenced old_ti to point to new_ti.
static size_t dag_locate(const Array< Point > &pts, const Array< Segment > &segs, const Array< DagNode > &dag, const Point &p, size_t root)
Locate the DAG leaf (trapezoid) containing point p.
static Array< size_t > find_crossed_trapezoids(const Array< Point > &pts, const Array< Segment > &segs, const Array< Trapezoid > &traps, const size_t seg_idx, const size_t start_trap)
Find all trapezoids crossed by segment seg_idx, starting from the trapezoid containing the left endpo...
static bool point_above_segment(const Point &p, const Segment &seg)
Test if point p is above segment seg (left of the directed segment).
static size_t make_trapezoid(Array< Trapezoid > &traps, Array< DagNode > &dag, const size_t top, const size_t bottom, const size_t leftp, const size_t rightp)
Create a new trapezoid and a DAG leaf for it.
Result operator()(const Array< Segment > &input_segments) const
Build a trapezoidal map from a set of non-crossing segments.
static void split_single_trapezoid(Array< Point > &pts, Array< Segment > &segs, Array< Trapezoid > &traps, Array< DagNode > &dag, const size_t seg_idx, const size_t trap_idx)
Handle the case where a segment is fully contained in one trapezoid.
static void replace_leaf(Array< DagNode > &dag, const size_t leaf_idx, const NodeType type, const size_t index, const size_t left, const size_t right)
Replace a DAG leaf with an internal node (X or Y).
Result operator()(const Polygon &polygon) const
Build a trapezoidal map from a closed polygon's edges.
NodeType
Types of nodes in the search DAG.
@ X_NODE
A node that splits the search space by an X-coordinate.
@ LEAF
A leaf node representing a trapezoid.
@ Y_NODE
A node that splits the search space by a segment (Y-direction).
A non-degenerate triangle defined by three points.
Definition point.H:1478
const Point & get_p3() const
Gets the third vertex.
Definition point.H:1591
const Point & get_p2() const
Gets the second vertex.
Definition point.H:1589
const Point & get_p1() const
Gets the first vertex.
Definition point.H:1587
A vertex in a polygon's doubly linked vertex list.
Definition polygon.H:119
const Vertex & next_vertex() const
Get the next vertex in the polygon.
Definition polygon.H:189
Point to_point() const
Return this vertex as a plain Point value.
Definition polygon.H:150
BST-based sweep status for active edges, ordered by ray-intersection distance.
size_t min() const
Return the nearest edge (smallest ray_param).
DynSetTree< EdgeKey, Treap, EdgeKeyCmp > tree_
EdgeStatusTree(const Array< Point > &verts, const size_t n, const Point &query)
bool contains(const size_t edge) const
void insert(const size_t edge, const Point &dir)
Compute the visibility polygon from a point inside a simple polygon.
static bool angle_less(const Point &q, const Point &a, const Point &b)
True if direction (a - q) has a smaller angle than (b - q).
static Point ray_edge_hit(const Point &q, const Point &dir, const Point &e0, const Point &e1)
Intersection point of ray from q through dir with edge (e0, e1).
Polygon operator()(const Polygon &polygon, const Point &query) const
Compute the visibility polygon.
static int angle_quadrant(const Geom_Number &dx, const Geom_Number &dy)
Quadrant of direction (dx, dy): 0 = +x+y, 1 = -x+y, 2 = -x-y, 3 = +x-y.
static Geom_Number ray_param(const Point &q, const Point &dir, const Point &e0, const Point &e1)
Parametric t along ray q + t*(dir-q) for intersection with edge (e0,e1).
Visvalingam-Whyatt polyline simplification.
static Array< Point > simplify_open_array(const Array< Point > &polyline, const Geom_Number &area_threshold)
Array< Point > operator()(const Array< Point > &polyline, const Geom_Number &area_threshold) const
Simplify an open polyline.
static Geom_Number effective_area(const Point &a, const Point &b, const Point &c)
Array< Point > operator()(const DynList< Point > &polyline, const Geom_Number &area_threshold) const
Overload accepting DynList.
static Polygon simplify_polygon(const Polygon &poly, const Geom_Number &area_threshold)
Simplify a closed polygon.
Fortune sweep-line Voronoi construction.
Array< ClippedCell > clipped_cells(const DynList< Point > &pts, const Polygon &clip) const
Compute Voronoi cells clipped to a bounding polygon.
static bool all_collinear(const Array< Point > &pts)
static void enqueue_circle_event(Arc *arc, const double sweepline_y, const Array< Point > &sites, DynBinHeap< Event *, EventCmp > &queue, Array< std::unique_ptr< Event > > &event_pool, size_t &next_event_id)
VoronoiDiagramFromDelaunay voronoi_
Result operator()(const std::initializer_list< Point > il) const
Compute the Voronoi diagram from an initializer list.
DelaunayTriangulationBowyerWatson fallback_
static Arc * locate_arc(Arc *head, const Array< Point > &sites, const double x, const double sweepline_y)
static double as_double(const Geom_Number &v)
static void invalidate_circle_event(Arc *arc)
Array< ClippedCell > clipped_cells(const std::initializer_list< Point > il, const Polygon &clip) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
static DelaunayTriangulationBowyerWatson::IndexedTriangle normalized_triangle(const size_t i, const size_t j, const size_t k, const Array< Point > &sites)
static constexpr double kEps
static double breakpoint_x(const Point &left_site, const Point &right_site, const double sweepline_y)
static bool is_valid_delaunay(const DelaunayTriangulationBowyerWatson::Result &dt)
static DelaunayTriangulationBowyerWatson::Result triangulate_sweep(const Array< Point > &sites)
Result operator()(const DynList< Point > &pts) const
Compute the Voronoi diagram for a list of points.
Voronoi diagram derived as the dual of a Delaunay triangulation.
static Array< ClippedCell > indexed_clipped_cells(const Array< Point > &sites, const Array< Polygon > &polys)
Array< ClippedCell > clipped_cells_indexed(const std::initializer_list< Point > il, const Polygon &clip) const
Convenience overload with initializer-list input.
static Point circumcenter(const Point &a, const Point &b, const Point &c)
Computes the circumcenter of three points.
Result operator()(const DelaunayTriangulationBowyerWatson::Result &dt) const
Build Voronoi from a precomputed Delaunay triangulation.
static HalfPlaneIntersection::HalfPlane bisector_halfplane_for_site(const Point &s, const Point &t)
Creates a half-plane from a directed edge.
static bool is_convex(const Array< Point > &verts)
Checks if a vertex set forms a convex polygon.
static Array< ClippedCell > clipped_cells_indexed(const Array< Point > &sites, const Polygon &clip)
Clip Voronoi cells and return explicit site-indexed records.
Array< Polygon > clipped_cells(const std::initializer_list< Point > il, const Polygon &clip) const
Convenience overload with initializer-list input.
Array< ClippedCell > clipped_cells_indexed(const DynList< Point > &point_set, const Polygon &clip) const
Compute/clip Voronoi cells and return site-indexed records.
static Array< Polygon > clipped_cells(const Array< Point > &sites, const Polygon &clip)
Clip Voronoi cells against a closed convex polygon.
static Array< ClippedCell > clipped_cells_indexed(const Result &vor, const Polygon &clip)
Clip Voronoi cells (from result) into site-indexed records.
static Array< Point > extract_vertices(const Polygon &p)
Extracts vertices from a closed polygon.
Array< Polygon > clipped_cells(const DynList< Point > &point_set, const Polygon &clip) const
Compute Voronoi and clip its cells against a closed convex polygon.
DelaunayTriangulationBowyerWatson delaunay
Internal Delaunay triangulator.
static Array< Polygon > clipped_cells(const Result &vor, const Polygon &clip)
Clip Voronoi cells against a closed convex polygon.
O(n log n) Voronoi diagram construction.
VoronoiDiagramFromDelaunay voronoi_
Internal dual builder.
Array< ClippedCell > clipped_cells(const DynList< Point > &pts, const Polygon &clip) const
Compute Voronoi cells clipped to a bounding polygon.
DelaunayTriangulationRandomizedIncremental delaunay_
Internal Delaunay triangulator.
Result operator()(const std::initializer_list< Point > il) const
Compute the Voronoi diagram from an initializer list.
Result operator()(const DynList< Point > &pts) const
Compute the Voronoi diagram for a list of points.
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:779
2D k-d tree for efficient spatial point operations.
Definition tpl_2dtree.H:136
bool insert(const Point &p)
Insert a point into the tree.
Definition tpl_2dtree.H:371
constexpr size_t size() const noexcept
Get the number of points in the tree.
Definition tpl_2dtree.H:243
static void range(Node *root, const Rectangle &rect, DynList< Point > *q)
Recursively find points within a rectangle.
Definition tpl_2dtree.H:442
void for_each(Op &&op) const
Apply an operation to every point in the tree (inorder).
Definition tpl_2dtree.H:577
static K2Tree build(Array< Point > points, const Point &pmin, const Point &pmax)
Build a balanced k-d tree from an array of points.
Definition tpl_2dtree.H:599
bool contains(const Point &p) const noexcept
Check if the tree contains a point.
Definition tpl_2dtree.H:430
std::optional< Point > nearest(const Point &p) const noexcept
Find the nearest point to a query point.
Definition tpl_2dtree.H:557
constexpr bool is_empty() const noexcept
Check if the tree is empty.
Definition tpl_2dtree.H:240
constexpr size_t size() const noexcept
Returns the number of entries in the table.
Definition hashDry.H:619
pair< size_t, string > P
#define N
Definition fib.C:294
__gmp_expr< typename __gmp_resolve_expr< T, V >::value_type, __gmp_binary_expr< __gmp_expr< T, U >, __gmp_expr< V, W >, __gmp_hypot_function > > hypot(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4112
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_y1_function > > y1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4103
__gmp_expr< mpfr_t, mpfr_t > mpfr_class
Definition gmpfrxx.h:2446
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4060
bool vertical
If true, use vertical layout (default).
Singly linked list implementations with head-tail access.
Freq_Node * pred
Predecessor node in level-order traversal.
static int initialized
Definition mpfr_mul_d.c:4
static mpfr_t y
Definition mpfr_mul_d.c:3
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
void in_place_unique(Container &c, Compare cmp={})
Remove duplicates in-place preserving first occurrence order.
Definition ah-unique.H:74
Orientation
Classification of three-point orientation.
Definition point.H:2812
Geom_Number in_circle_determinant(const Point &a, const Point &b, const Point &c, const Point &p)
Return the exact in-circle determinant for (a,b,c,p).
Definition point.H:2835
bool all(Container &container, Operation &operation)
Return true if all elements satisfy a predicate.
bool eq(const C1 &c1, const C2 &c2, Eq e=Eq())
Check equality of two containers using a predicate.
Geom_Number area_of_parallelogram(const Point &a, const Point &b, const Point &c)
Compute the signed area of the parallelogram defined by vectors a->b and a->c.
Definition point.H:2803
Itor unique(Itor __first, Itor __last, BinaryPredicate __binary_pred=BinaryPredicate())
Remove consecutive duplicates in place.
Definition ahAlgo.H:1058
bool on_segment(const Segment &s, const Point &p)
Return true if p lies on segment s (exact).
Definition point.H:2876
size_t size(Node *root) noexcept
bool segments_intersect(const Segment &s1, const Segment &s2)
Return true if segments s1 and s2 intersect (including endpoints).
Definition point.H:2883
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
Point segment_intersection_point(const Segment &s1, const Segment &s2)
Compute the exact intersection point of segments s1 and s2.
Definition point.H:2900
bool contains(const std::string_view &str, const std::string_view &substr)
Check if substr appears inside str.
DynArray< T > & in_place_sort(DynArray< T > &c, Cmp cmp=Cmp())
Sorts a DynArray in place.
Definition ahSort.H:321
Geom_Number square_root(const Geom_Number &x)
Square root of x (wrapper over mpfr).
Definition point.H:205
double geom_number_to_double(const Geom_Number &n)
Converts a Geom_Number to its double precision representation.
Definition point.H:122
Orientation orientation(const Point &a, const Point &b, const Point &c)
Return the orientation of the triple (a, b, c).
Definition point.H:2821
mpq_class Geom_Number
Numeric type used by the geometry module.
Definition point.H:115
SegmentSegmentIntersection::Result segment_segment_intersection(const Segment &s1, const Segment &s2)
Convenience free-function wrapper for SegmentSegmentIntersection.
std::ostream & join(const C &c, const std::string &sep, std::ostream &out)
Join elements of an Aleph-style container into a stream.
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
void quicksort_op(C< T > &a, const Compare &cmp=Compare(), const size_t threshold=Quicksort_Threshold)
Optimized quicksort for containers using operator().
STL namespace.
2D polygon representation and geometric operations.
Structure used for debugging and visualizing the tree structure.
size_t user_index
Entry::index.
size_t node_index
Index of the node.
size_t entry_index
Index in entries_ array.
size_t right
Right child index.
size_t left
Left child index.
Rectangle bbox
Bounding box of the node.
size_t depth
Depth in the tree.
bool is_leaf
True if it's a leaf node.
A snapshot of the entire tree for debugging.
size_t root
Index of the root node.
Array< DebugNode > nodes
Array of all nodes in debug format.
An entry in the tree, consisting of a bounding box and a user-defined index.
size_t index
User-defined identifier for the entry.
Rectangle bbox
The axis-aligned bounding box.
Internal tree node structure.
bool is_leaf() const
Checks if this node is a leaf.
Rectangle bbox
Bounding box enclosing all descendants.
size_t entry_idx
Index in entries_ (leaf only).
size_t left
Left child index.
size_t right
Right child index.
Result of an alpha-shape computation.
Array< DelaunayTriangulationBowyerWatson::IndexedTriangle > triangles
Triangles that satisfy the alpha criterion.
Array< Point > sites
Deduplicated and sorted input sites.
Array< Segment > boundary_edges
Segments forming the boundary of the alpha-shape.
Lexicographical comparison for points (x primary, y secondary).
bool operator()(const Point &p1, const Point &p2) const
De Casteljau subdivision: split a cubic Bézier at parameter t into two cubic Béziers (left and right)...
Intersection pair found between edge i of A and edge j of B.
Comparator for segments to ensure strict weak ordering in sets.
bool operator()(const Segment &s1, const Segment &s2) const
Compares two segments lexicographically.
static bool cmp_point(const Point &p1, const Point &p2)
Strict lexicographical comparison for points.
Strict weak order for sorting points by (y,x).
bool operator()(const Point &p1, const Point &p2) const
Strict weak order for sorting points lexicographically by (x,y).
bool operator()(const Point &p1, const Point &p2) const
Collect edges that cross segment (u,v) by scanning all mesh edges.
size_t local
local index in tri (opposite vertex)
size_t adj[3]
adj[i] = neighbor across edge opposite v[i]
bool constrained[3]
constrained[i] = edge opp v[i] is constrained
Holds the results of the distance computation.
Geom_Number distance
The minimum Euclidean distance.
Geom_Number distance_squared
The minimum squared distance.
Point closest_on_second
Witness point on the second polygon.
bool intersects
True if the polygons overlap.
Point closest_on_first
Witness point on the first polygon.
size_t gjk_iterations
Count of iterations performed.
A point in the Minkowski difference and its source components.
Point a
Contributing point from the first polygon.
Point b
Contributing point from the second polygon.
Point v
Resulting point in Minkowski difference (a - b).
double vy
Double-precision cache for faster predicates.
double vx
Double-precision cache for faster predicates.
Represents a triangle by the indices of its three vertices.
The result of a Delaunay triangulation.
Array< IndexedTriangle > triangles
Triangles forming the Delaunay triangulation.
Array< Point > sites
Unique, sorted input points used for triangulation.
size_t adj[3]
adj[i] = neighbor across edge opposite v[i]
bool operator()(const UndirectedEdge &a, const UndirectedEdge &b) const
Represents a reference to a triangle edge.
size_t v
Index of the second vertex of the edge (u < v).
size_t third
Index of the third vertex in the triangle (not on the edge).
size_t u
Index of the first vertex of the edge (u < v).
size_t tri
Index of the triangle containing this edge.
Lexicographical comparison for points.
bool operator()(const Point &p1, const Point &p2) const
Represents a spatial partition or leaf in the KD-tree.
bool split_on_x
True if split is vertical, false if horizontal.
Rectangle region
Geometric region covered by this node.
Geom_Number split_value
The coordinate value of the split.
Point representative
Point stored in the leaf.
bool is_leaf
True if it's a leaf node.
size_t depth
Depth of the node in the tree.
A complete snapshot of the tree for debugging.
Array< DebugPartition > partitions
List of all internal and leaf partitions.
Array< Point > points
All points stored in the tree.
Rectangle bounds
Global bounding box of the tree.
Comparator for SeqEvent, ensuring a strict weak ordering.
CmpEvent cmp
The user-provided base comparator.
bool operator()(const SeqEvent &a, const SeqEvent &b) const
Internal event wrapper that adds a sequence number for tie-breaking.
size_t seq
Insertion sequence number.
Event event
The user-defined event payload.
Result type: a circle defined by center and squared radius.
Geom_Number radius() const
Exact radius (square root of radius_squared).
bool contains(const Point &p) const
True if point p lies inside or on the circle boundary.
Augmented vertex in the cleaned polygon.
Intersection found between edge i and edge j of the raw polygon.
The result of an offset operation, which may produce multiple polygons.
Array< Polygon > polygons
Set of resulting simple polygons.
size_t size() const noexcept
bool is_empty() const noexcept
Checks if the result contains no polygons.
Iterator over the vertices of a polygon.
Definition polygon.H:489
Represents a cell in the power diagram.
size_t site_index
Index of the site this cell belongs to.
bool bounded
True if the cell is completely bounded.
Array< Point > vertices
Vertices of the cell in CCW order.
Point site
Coordinates of the site.
Geom_Number weight
Weight of the site.
Represents an edge in the power diagram.
size_t site_v
Index of the second site sharing this edge.
Point tgt
Target point of the edge (if not unbounded).
size_t site_u
Index of the first site sharing this edge.
Point direction
Direction vector for unbounded rays.
bool unbounded
True if the edge is an unbounded ray.
Point src
Source point of the edge.
Complete result of a power diagram computation.
Array< PowerCell > cells
Cells of the power diagram.
Array< Point > vertices
Vertices of the power diagram.
Array< WeightedSite > sites
Sorted and unique input sites.
Array< PowerEdge > edges
Edges of the power diagram.
A site with an associated weight (squared radius).
Geom_Number weight
Weight of the site.
Point position
Coordinates of the site.
Represents a node in the range tree for debugging.
size_t left
Left child tree index.
size_t y_sorted_size
Number of points in the y-sorted secondary structure.
size_t right
Right child tree index.
Geom_Number split_x
X-coordinate used to split this node.
bool is_leaf
True if this is a leaf node.
Geom_Number xmin
Minimum x-coordinate in this subtree.
size_t tree_index
Implicit tree index (1-based).
size_t lo
Lower index into the x-sorted point array.
Geom_Number xmax
Maximum x-coordinate in this subtree.
size_t hi
Upper index into the x-sorted point array.
A complete snapshot of the range tree for debugging.
Array< DebugNode > nodes
List of all nodes in debug format.
Array< Point > x_sorted_points
The underlying x-sorted point array.
Internal node structure containing the y-sorted secondary array.
Array< Point > y_sorted
Points in this node's x-range, sorted by y.
A 2-D site with an associated weight (squared radius).
Represents an edge in the planar subdivision.
size_t tgt
Index of the target vertex.
size_t src
Index of the source vertex.
size_t seg_idx
Index of the original segment that generated this edge.
Represents a face (region) in the planar subdivision.
bool unbounded
True if this is the infinite outer face.
DynList< size_t > boundary
Ordered indices of vertices forming the face boundary.
Internal half-edge structure used for face traversal.
size_t edge_idx
Index of the corresponding undirected edge.
size_t twin
Index of the twin half-edge going in the opposite direction.
size_t next
Index of the next half-edge in CCW order around the face.
size_t origin
Index of the vertex where this half-edge starts.
size_t face
Index of the face this half-edge borders.
size_t target
Index of the vertex where this half-edge ends.
The complete result of the segment arrangement computation.
Array< Point > vertices
Unique vertices (endpoints and intersection points).
Array< ArrEdge > edges
Sub-segments forming the arrangement graph.
Array< ArrFace > faces
Faces defined by the arrangement.
Detailed result of a segment-segment intersection test.
bool intersects() const noexcept
Returns true if any intersection exists.
Point point
The intersection point (if kind is POINT).
Segment overlap
The overlap segment (if kind is OVERLAP).
Functor wrapper for event_less (used by LineSweepFramework).
bool operator()(const Event &a, const Event &b) const
Represents an event in the sweep-line algorithm.
size_t seg_b
Index of the second segment (only for INTERSECTION).
size_t seg_a
Index of the first segment involved.
size_t seg_j
Index of second segment (seg_i < seg_j).
A node in the search Directed Acyclic Graph (DAG).
size_t index
Index into points, segments, or trapezoids depending on type.
size_t right
Index of the right child (or above/right child).
size_t left
Index of the left child (or below/left child).
size_t segment_index
Index of the segment the point lies on.
size_t trapezoid_index
Index of the containing trapezoid (if any).
size_t point_index
Index of the point the query point coincides with.
LocationType type
Classification of the point location.
The trapezoidal map and DAG search structure.
Array< Trapezoid > trapezoids
All trapezoids created during construction.
Array< Segment > segments
Input segments.
Array< DagNode > dag
The search directed acyclic graph.
LocationResult locate(const Point &p) const
Locate a point in the trapezoidal map.
size_t num_input_points
Number of unique points derived from segments.
Array< Point > points
Unique endpoints of all segments.
size_t num_input_segments
Number of segments originally provided.
size_t dag_root
Index of the DAG root node.
bool contains(const Point &p) const
Check if point is inside any input polygon.
size_t leftp
Index into points array (left vertical wall).
size_t dag_leaf
Back-pointer to the corresponding DAG leaf node.
size_t lower_left
Neighbor trapezoid to the lower left.
size_t rightp
Index into points array (right vertical wall).
size_t upper_right
Neighbor trapezoid to the upper right.
bool active
True if this trapezoid is part of the current map.
size_t lower_right
Neighbor trapezoid to the lower right.
size_t upper_left
Neighbor trapezoid to the upper left.
size_t bottom
Index into segments array (lower boundary).
size_t top
Index into segments array (upper boundary).
bool operator()(const EdgeKey &a, const EdgeKey &b) const
Entry in the priority queue for vertex removal.
size_t idx
Index of the vertex in the polyline.
bool operator<(const VWEntry &o) const
Comparison based on effective area.
Geom_Number area
Effective area of the triangle formed with its neighbors.
bool operator>(const VWEntry &o) const
Comparison based on effective area.
Arc in the beach-line state structure.
Arc * next
Next arc in the beach-line.
Event * circle
Potential circle event generated by this arc.
Arc * prev
Previous arc in the beach-line.
size_t site
Site index generating this parabolic arc.
Comparator for prioritizing events in the sweep-line.
bool operator()(const Event *a, const Event *b) const
Forward declaration of beach-line arc.
bool valid
False if the circle event was invalidated.
double y
Y-coordinate of the event.
bool is_site
True if it's a site event, false for circle.
Arc * arc
Corresponding arc in the beach-line (circle events).
size_t id
Unique event ID for deterministic sorting.
double x
X-coordinate of the site or circle center.
Key for identifying a triangle by its vertex indices.
bool operator<(const TriKey &o) const
size_t site_index
Index of the site this cell belongs to.
Array< Point > vertices
Vertices of the cell in CCW order.
Point site
Coordinates of the site.
bool bounded
True if the cell is completely bounded.
Represents a Voronoi cell clipped by a bounding polygon.
Polygon polygon
Clipped convex polygon representing the cell.
Represents an edge in the Voronoi diagram.
Point tgt
Target point of the edge (only if not unbounded).
Point src
Source point of the edge.
size_t site_v
Index of the second site sharing this edge.
Point direction
Direction vector for unbounded rays.
size_t site_u
Index of the first site sharing this edge.
bool unbounded
True if the edge is an unbounded ray.
The complete result of a Voronoi diagram computation.
Array< Point > vertices
Voronoi vertices.
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
size_t V
DynList< int > l1
DynList< int > l2
int keys[]
ValueArg< size_t > seed
Definition testHash.C:48
static int * k
gsl_rng * r
2D k-d tree implementation for spatial point indexing.
Circular queue implementations backed by arrays.
Stack implementations backed by dynamic or fixed arrays.
Dynamic binary heap with node-based storage.
Dynamic doubly linked list implementation.
Dynamic set implementations based on balanced binary search trees.
Comprehensive sorting algorithms and search utilities for Aleph-w.
DynList< int > l
ofstream output
Definition writeHeap.C:215