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
188#ifndef GEOM_ALGORITHMS_H
189#define GEOM_ALGORITHMS_H
190
191#include <sstream>
192#include <polygon.H>
193#include <htlist.H>
194#include <tpl_dynDlist.H>
195#include <tpl_dynSetTree.H>
196#include <tpl_arrayQueue.H>
197#include <tpl_arrayStack.H>
198#include <tpl_sort_utils.H>
199#include <tpl_2dtree.H>
200#include <ah-errors.H>
201#include <algorithm>
202#include <utility>
203#include <random>
204#include <map>
205#include <set>
206#include <queue>
207#include <memory>
208#include <limits>
209#include <cmath>
210
211#include <ah-unique.H>
212#include <tpl_dynBinHeap.H>
213
214namespace Aleph {
230{
231public:
234 {
236 verts.reserve(poly.size());
237 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
238 verts.append(it.get_current_vertex());
239 return verts;
240 }
241
248 {
249 Geom_Number sum = 0;
250 for (size_t i = 0; i < verts.size(); ++i)
251 {
252 const Point &a = verts(i);
253 const Point &b = verts((i + 1) % verts.size());
254 sum += a.get_x() * b.get_y() - a.get_y() * b.get_x();
255 }
256 return sum;
257 }
258
261 {
263 }
264
267 {
268 return signed_double_area(verts) / 2;
269 }
270
272 [[nodiscard]] static Geom_Number signed_area(const Polygon &poly)
273 {
274 return signed_double_area(poly) / 2;
275 }
276
279 {
281 return a < 0 ? Geom_Number(-a) : a;
282 }
283
285 [[nodiscard]] static Geom_Number area(const Polygon &poly)
286 {
287 Geom_Number a = signed_area(poly);
288 return a < 0 ? Geom_Number(-a) : a;
289 }
290
292 [[nodiscard]] static bool is_convex(const Array<Point> &verts)
293 {
294 if (verts.size() < 3)
295 return true;
296
297 int sign = 0;
298 const size_t n = verts.size();
299 for (size_t i = 0; i < n; ++i)
300 {
301 const Geom_Number turn =
302 area_of_parallelogram(verts(i), verts((i + 1) % n), verts((i + 2) % n));
303 if (turn == 0)
304 continue;
305 const int curr = turn > 0 ? 1 : -1;
306 if (sign == 0)
307 sign = curr;
308 else if (sign != curr)
309 return false;
310 }
311 return true;
312 }
313
315 {
316 if (signed_double_area(verts) < 0)
317 for (size_t i = 0; i < verts.size() / 2; ++i)
318 {
319 const Point tmp = verts(i);
320 verts(i) = verts(verts.size() - 1 - i);
321 verts(verts.size() - 1 - i) = tmp;
322 }
323 }
324};
325
338{
339public:
343 struct EdgeRef
344 {
345 size_t u;
346 size_t v;
347 size_t tri;
348 size_t third;
349 };
350
351private:
352 static void append_edge(Array<EdgeRef> &edges, size_t a, size_t b, const size_t tri, const size_t third)
353 {
354 if (a > b)
355 {
356 const size_t tmp = a;
357 a = b;
358 b = tmp;
359 }
360
361 edges.append(EdgeRef{a, b, tri, third});
362 }
363
364public:
365 template <typename TriangleArray, typename GroupFn>
367 {
368 Array<EdgeRef> edges;
369 edges.reserve(triangles.size() * 3);
370 for (size_t tri_idx = 0; tri_idx < triangles.size(); ++tri_idx)
371 {
372 const auto &tri = triangles(tri_idx);
373 append_edge(edges, tri.i, tri.j, tri_idx, tri.k);
374 append_edge(edges, tri.j, tri.k, tri_idx, tri.i);
375 append_edge(edges, tri.k, tri.i, tri_idx, tri.j);
376 }
377
378 quicksort_op(edges, [](const EdgeRef &a, const EdgeRef &b)
379 {
380 if (a.u != b.u)
381 return a.u < b.u;
382 if (a.v != b.v)
383 return a.v < b.v;
384 return a.tri < b.tri;
385 });
386
387 size_t first = 0;
388 while (first < edges.size())
389 {
390 size_t last = first + 1;
391 while (last < edges.size() and edges(last).u == edges(first).u and
392 edges(last).v == edges(first).v)
393 ++last;
394
395 on_group(edges, first, last);
396 first = last;
397 }
398 }
399};
400
405{
406private:
408 {
409 size_t a;
410 size_t b;
411 size_t c;
412 bool alive;
413 };
414
416 {
417 size_t u;
418 size_t v;
419 };
420
422 {
423 bool operator () (const UndirectedEdge &a, const UndirectedEdge &b) const
424 {
425 if (a.u != b.u)
426 return a.u < b.u;
427 return a.v < b.v;
428 }
429 };
430
432
433 static void toggle_edge(EdgeSet &boundary, size_t u, size_t v)
434 {
435 if (u > v)
436 {
437 const size_t tmp = u;
438 u = v;
439 v = tmp;
440 }
441
442 const UndirectedEdge e{u, v};
443 if (const auto *ptr = boundary.search(e); ptr != nullptr)
444 boundary.remove(*ptr);
445 else
446 boundary.insert(e);
447 }
448
449public:
450 template <typename IndexedTriangle, typename InCirclePredicate>
452 const size_t n,
453 InCirclePredicate point_in_circumcircle)
454 {
456 if (n < 3)
457 return out;
458
459 Geom_Number minx = pts(0).get_x();
460 Geom_Number maxx = pts(0).get_x();
461 Geom_Number miny = pts(0).get_y();
462 Geom_Number maxy = pts(0).get_y();
463
464 for (size_t i = 1; i < n; ++i)
465 {
466 if (pts(i).get_x() < minx)
467 minx = pts(i).get_x();
468 if (pts(i).get_x() > maxx)
469 maxx = pts(i).get_x();
470 if (pts(i).get_y() < miny)
471 miny = pts(i).get_y();
472 if (pts(i).get_y() > maxy)
473 maxy = pts(i).get_y();
474 }
475
476 const Geom_Number dx = maxx - minx;
477 const Geom_Number dy = maxy - miny;
478 Geom_Number delta = dx > dy ? dx : dy;
479 if (delta == 0)
480 delta = 1;
481
482 const Geom_Number span = delta * 16 + 1;
483 const Geom_Number two_span = span + span;
484 const Geom_Number midx = (minx + maxx) / 2;
485 const Geom_Number midy = (miny + maxy) / 2;
486
487 const Point s0(midx - two_span, midy - span);
488 const Point s1(midx + two_span, midy - span);
489 const Point s2(midx, midy + two_span);
490
491 pts.append(s0);
492 pts.append(s1);
493 pts.append(s2);
494
495 const size_t i0 = n;
496 const size_t i1 = n + 1;
497 const size_t i2 = n + 2;
498
500 work.append(WorkTriangle{i0, i1, i2, true});
501
502 for (size_t pidx = 0; pidx < n; ++pidx)
503 {
505 bad.reserve(work.size());
506
507 for (size_t t = 0; t < work.size(); ++t)
508 {
509 const auto &[a, b, c, alive] = work(t);
510 if (not alive)
511 continue;
512
513 if (point_in_circumcircle(pts, a, b, c, pidx))
514 bad.append(t);
515 }
516
517 if (bad.is_empty())
518 continue;
519
520 EdgeSet boundary;
521
522 for (size_t bad_idx = 0; bad_idx < bad.size(); ++bad_idx)
523 {
524 auto &[a, b, c, alive] = work(bad(bad_idx));
525 if (not alive)
526 continue;
527
528 toggle_edge(boundary, a, b);
529 toggle_edge(boundary, b, c);
530 toggle_edge(boundary, c, a);
531 alive = false;
532 }
533
534 boundary.for_each([&](const UndirectedEdge &edge)
535 {
536 size_t u = edge.u;
537 size_t v = edge.v;
538
539 const Orientation o = orientation(pts(u), pts(v), pts(pidx));
541 return;
542
543 if (o == Orientation::CW)
544 {
545 const size_t tmp = u;
546 u = v;
547 v = tmp;
548 }
549
550 work.append(WorkTriangle{u, v, pidx, true});
551 });
552
554 compacted.reserve(work.size());
555 for (size_t t = 0; t < work.size(); ++t)
556 if (work(t).alive)
557 compacted.append(work(t));
558 work = std::move(compacted);
559 }
560
561 out.reserve(work.size());
562 for (size_t t = 0; t < work.size(); ++t)
563 {
564 const auto &[a, b, c, alive] = work(t);
565 if (not alive)
566 continue;
567
568 if (a >= n or b >= n or c >= n)
569 continue;
570
571 if (orientation(pts(a), pts(b), pts(c)) == Orientation::COLLINEAR)
572 continue;
573
574 out.append(IndexedTriangle{a, b, c});
575 }
576
577 return out;
578 }
579};
580
581// ============================================================================
582// Triangulation Algorithms
583// ============================================================================
584
638{
640
645
650
651 static void normalize_to_ccw(Polygon &p)
652 {
654 ah_domain_error_if(area2 == 0) << "Polygon is degenerate (zero area)";
655 if (area2 > 0)
656 return;
657
659 Polygon ccw;
660 for (size_t i = verts.size(); i > 0; --i)
661 ccw.add_vertex(verts(i - 1));
662 ccw.close();
663 p = std::move(ccw);
664 }
665
666public:
677 static bool diagonalize(const Polygon &p, const Segment &s)
678 {
679 for (Polygon::Segment_Iterator it(p); it.has_curr(); it.next_ne())
680 if (Segment curr = it.get_current_segment(); curr.get_src_point() != s.get_src_point() and
681 curr.get_tgt_point() != s.get_src_point() and curr.get_src_point() != s.get_tgt_point() and
682 curr.get_tgt_point() != s.get_tgt_point() and s.intersects_with(curr))
683 return false;
684 return true;
685 }
686
697 static bool in_cone(const Polygon &p, const Vertex &a, const Vertex &b)
698 {
699 // a0 -> a -> a1 are consecutive vertices
700 const Vertex &a0 = p.get_prev_vertex(a);
701 const Vertex &a1 = p.get_next_vertex(a);
702
703 if (a0.is_to_left_on_from(a, a1))
704 return a0.is_left_of(a, b) and a1.is_left_of(b, a);
705
706 return not (a1.is_to_left_on_from(a, b) and a0.is_to_left_on_from(b, a));
707 }
708
717 static bool diagonal(const Polygon &p, const Vertex &a, const Vertex &b)
718 {
719 return in_cone(p, a, b) and in_cone(p, b, a) and
720 diagonalize(p, Segment(a.to_point(), b.to_point()));
721 }
722
729 static EarsSet init_ears(const Polygon &p)
730 {
731 EarsSet ret;
732
733 for (Polygon::Vertex_Iterator it(p); it.has_curr(); it.next_ne())
734 {
735 Vertex &curr = it.get_current_vertex();
736 const Vertex &prev = p.get_prev_vertex(curr);
737 if (const Vertex &next = p.get_next_vertex(curr); diagonal(p, prev, next))
738 ret.insert(&curr);
739 }
740
741 return ret;
742 }
743
744public:
757 {
758 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
759 ah_domain_error_if(poly.size() < 3) << "Polygon has less than 3 vertices";
760
761 Polygon p = poly; // work on a copy
763
765 if (p.size() > 3)
766 {
767 ears = init_ears(p);
768 ah_domain_error_if(ears.is_empty()) << "No valid ear found; polygon may be non-simple";
769 }
770
772
773 while (p.size() > 3)
774 {
775 ah_domain_error_if(ears.is_empty()) << "No valid ear found during triangulation";
776 const Vertex *curr = ears.remove_pos(0);
777
778 const Vertex &prev = p.get_prev_vertex(*curr);
779 const Vertex &next = p.get_next_vertex(*curr);
780
781 ret.append(Triangle(prev.to_point(), curr->to_point(), next.to_point()));
782 p.remove_vertex(*curr);
783
784 // After removal, prev and next are adjacent; recompute their
785 // outer neighbors for ear tests on the updated topology.
786 const Vertex &new_prev_prev = p.get_prev_vertex(prev);
788
789 if (diagonal(p, new_prev_prev, next))
790 ears.insert(&prev);
791 else
792 ears.remove(&prev);
793
794 if (diagonal(p, prev, new_next_next))
795 ears.insert(&next);
796 else
797 ears.remove(&next);
798 }
799
800 assert(p.size() == 3);
801
802 const Vertex &a = p.get_first_vertex();
803 const Vertex &b = a.next_vertex();
804 const Vertex &c = b.next_vertex();
805
807
808 return ret;
809 }
810};
811
812// ============================================================================
813// Closest Pair of Points
814// ============================================================================
815
836{
837public:
844
845private:
852 {
853 bool operator () (const Point &p1, const Point &p2) const
854 {
855 if (p1.get_x() < p2.get_x())
856 return true;
857
858 if (p2.get_x() < p1.get_x())
859 return false;
860
861 return p1.get_y() < p2.get_y();
862 }
863 };
864
871 struct ByYCmp
872 {
873 bool operator () (const Point &p1, const Point &p2) const
874 {
875 if (p1.get_y() < p2.get_y())
876 return true;
877
878 if (p2.get_y() < p1.get_y())
879 return false;
880
881 if (p1.get_x() < p2.get_x())
882 return true;
883
884 if (p2.get_x() < p1.get_x())
885 return false;
886
887 return false;
888 }
889 };
890
896 [[nodiscard]] static Geom_Number dist2(const Point &a, const Point &b)
897 {
898 return a.distance_squared_to(b);
899 }
900
904 [[nodiscard]] static Result make_result(const Point &a, const Point &b)
905 {
906 return {a, b, dist2(a, b)};
907 }
908
919 [[nodiscard]] static Result brute_force(const Array<Point> &px, const size_t l, const size_t r)
920 {
921 assert(r - l >= 2);
922
923 Result best = make_result(px(l), px(l + 1));
924
925 for (size_t i = l; i < r; ++i)
926 for (size_t j = i + 1; j < r; ++j)
927 if (Result cand = make_result(px(i), px(j)); cand.distance_squared < best.distance_squared)
928 best = cand;
929
930 return best;
931 }
932
947 const size_t l,
948 const size_t r,
950 {
951 const size_t n = r - l;
952 if (n <= 3)
953 return brute_force(px, l, r);
954
955 const size_t mid = l + n / 2;
956 const Point &mid_point = px(mid);
957
960 left_py.reserve(mid - l);
961 right_py.reserve(r - mid);
962
963 const size_t left_target = mid - l;
964 for (size_t i = 0; i < py.size(); ++i)
965 {
966 const Point &p = py(i);
967 if (p.get_x() < mid_point.get_x())
968 left_py.append(p);
969 else if (p.get_x() > mid_point.get_x())
970 right_py.append(p);
971 else // tie on x — fill left first, then right
972 {
973 if (left_py.size() < left_target)
974 left_py.append(p);
975 else
976 right_py.append(p);
977 }
978 }
979
980 const Result best_left = recurse(px, l, mid, left_py);
983
985 strip.reserve(py.size());
986 for (size_t i = 0; i < py.size(); ++i)
987 {
988 const Point &p = py(i);
989 if (const Geom_Number dx = p.get_x() - mid_point.get_x(); dx * dx < best.distance_squared)
990 strip.append(p);
991 }
992
993 for (size_t i = 0; i < strip.size(); ++i)
994 for (size_t j = i + 1; j < strip.size(); ++j)
995 {
996 if (const Geom_Number dy = strip(j).get_y() - strip(i).get_y();
997 dy * dy >= best.distance_squared)
998 break;
999
1000 if (Result cand = make_result(strip(i), strip(j));
1001 cand.distance_squared < best.distance_squared)
1002 best = cand;
1003 }
1004
1005 return best;
1006 }
1007
1008public:
1018 {
1020 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
1021 px.append(it.get_curr());
1022
1023 ah_domain_error_if(px.size() < 2) << "Closest pair requires at least 2 points";
1024
1026
1027 // Early-exit on duplicate points: exact minimum distance is zero.
1028 for (size_t i = 1; i < px.size(); ++i)
1029 if (px(i) == px(i - 1))
1030 return {px(i - 1), px(i), 0};
1031
1032 Array<Point> py = px;
1034
1035 return recurse(px, 0, px.size(), py);
1036 }
1037
1045 {
1046 Result r = (*this)(point_set);
1047 return {r.first, r.second};
1048 }
1049};
1050
1051// ============================================================================
1052// Minimum Enclosing Circle (Welzl)
1053// ============================================================================
1054
1083{
1084public:
1086 struct Circle
1087 {
1090
1093 {
1095 }
1096
1098 [[nodiscard]] bool contains(const Point &p) const
1099 {
1101 }
1102 };
1103
1106 {
1107 return {a, 0};
1108 }
1109
1111 [[nodiscard]] static Circle from_two_points(const Point &a, const Point &b)
1112 {
1113 const Point c = a.midpoint(b);
1114 return {c, c.distance_squared_to(a)};
1115 }
1116
1121 [[nodiscard]] static Circle from_three_points(const Point &a, const Point &b, const Point &c)
1122 {
1123 const Geom_Number &ax = a.get_x();
1124 const Geom_Number &ay = a.get_y();
1125 const Geom_Number &bx = b.get_x();
1126 const Geom_Number &by = b.get_y();
1127 const Geom_Number &cx = c.get_x();
1128 const Geom_Number &cy = c.get_y();
1129
1130 const Geom_Number d = ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
1131
1132 if (d == 0) // collinear — return diameter of farthest pair
1133 {
1137
1138 if (d_ab >= d_bc && d_ab >= d_ac)
1139 return from_two_points(a, b);
1140 if (d_bc >= d_ab && d_bc >= d_ac)
1141 return from_two_points(b, c);
1142 return from_two_points(a, c);
1143 }
1144
1145 const Geom_Number den = d + d;
1146 const Geom_Number a2 = ax * ax + ay * ay;
1147 const Geom_Number b2 = bx * bx + by * by;
1148 const Geom_Number c2 = cx * cx + cy * cy;
1149
1150 const Geom_Number ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / den;
1151 const Geom_Number uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / den;
1152
1153 const Point center{ux, uy};
1154 return {center, center.distance_squared_to(a)};
1155 }
1156
1165 {
1167 for (DynList<Point>::Iterator it(points); it.has_curr(); it.next_ne())
1168 pts.append(it.get_curr());
1169
1170 ah_domain_error_if(pts.size() == 0) << "MinimumEnclosingCircle: empty point set";
1171
1172 // Fisher-Yates shuffle
1173 {
1174 std::random_device rd;
1175 std::mt19937 gen(rd());
1176 for (size_t i = pts.size() - 1; i > 0; --i)
1177 {
1178 std::uniform_int_distribution<size_t> dist(0, i);
1179 if (const size_t j = dist(gen); i != j)
1180 std::swap(pts(i), pts(j));
1181 }
1182 }
1183
1184 return welzl_iterative(pts);
1185 }
1186
1188 [[nodiscard]] Circle operator () (std::initializer_list<Point> points) const
1189 {
1191 for (const auto &p : points)
1192 l.append(p);
1193 return (*this)(l);
1194 }
1195
1196private:
1198 [[nodiscard]] static Circle mec_with_point(const Array<Point> &pts, size_t n, const Point &p1)
1199 {
1200 Circle d = from_one_point(p1);
1201 for (size_t j = 0; j < n; ++j)
1202 if (not d.contains(pts(j)))
1203 d = mec_with_two_points(pts, j, p1, pts(j));
1204 return d;
1205 }
1206
1209 size_t n,
1210 const Point &p1,
1211 const Point &p2)
1212 {
1213 Circle d = from_two_points(p1, p2);
1214 for (size_t k = 0; k < n; ++k)
1215 if (not d.contains(pts(k)))
1216 d = from_three_points(p1, p2, pts(k));
1217 return d;
1218 }
1219
1222 {
1223 Circle d = from_one_point(pts(0));
1224
1225 for (size_t i = 1; i < pts.size(); ++i)
1226 if (not d.contains(pts(i)))
1227 d = mec_with_point(pts, i, pts(i));
1228
1229 return d;
1230 }
1231};
1232
1233// ============================================================================
1234// Rotating Calipers (Convex Polygon Metrics)
1235// ============================================================================
1236
1259{
1260public:
1267
1275
1276private:
1287 {
1288 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1289
1290 ah_domain_error_if(poly.size() < 2) << "Rotating calipers requires at least 2 vertices";
1291
1293 }
1294
1304 [[nodiscard]] static bool is_convex(const Array<Point> &verts)
1305 {
1307 }
1308
1309 [[nodiscard]] static DiameterResult make_diameter(const Point &a, const Point &b)
1310 {
1311 return {a, b, a.distance_squared_to(b)};
1312 }
1313
1314public:
1323 static DiameterResult diameter(const Polygon &poly)
1324 {
1325 const Array<Point> verts = extract_vertices(poly);
1326 ah_domain_error_if(not is_convex(verts)) << "Polygon must be convex";
1327
1328 const size_t n = verts.size();
1329 if (n == 2)
1330 return make_diameter(verts(0), verts(1));
1331
1332 auto edge_area = [&verts, n](const size_t i, const size_t k) -> Geom_Number
1333 {
1334 Geom_Number area = area_of_parallelogram(verts(i), verts((i + 1) % n), verts(k));
1335 if (area < 0)
1336 area = -area;
1337 return area;
1338 };
1339
1340 size_t j = 1;
1341 size_t safety = 0;
1342 while (edge_area(0, (j + 1) % n) > edge_area(0, j) and safety++ < n)
1343 j = (j + 1) % n;
1344
1346
1347 for (size_t i = 0; i < n; ++i)
1348 {
1349 const size_t ni = (i + 1) % n;
1350 safety = 0;
1351 while (edge_area(i, (j + 1) % n) > edge_area(i, j) and safety++ < n)
1352 j = (j + 1) % n;
1353
1355 cand1.distance_squared > best.distance_squared)
1356 best = cand1;
1357
1359 cand2.distance_squared > best.distance_squared)
1360 best = cand2;
1361 }
1362
1363 return best;
1364 }
1365
1380 {
1381 const Array<Point> verts = extract_vertices(poly);
1382 ah_domain_error_if(not is_convex(verts)) << "Polygon must be convex";
1383
1384 const size_t n = verts.size();
1385 if (n == 2)
1386 return {verts(0), verts(1), verts(0), 0};
1387
1388 auto area_to_edge = [&verts, n](const size_t i, const size_t k) -> Geom_Number
1389 {
1390 Geom_Number area = area_of_parallelogram(verts(i), verts((i + 1) % n), verts(k));
1391 if (area < 0)
1392 area = -area;
1393 return area;
1394 };
1395
1396 auto edge_len2 = [&verts, n](const size_t i) -> Geom_Number
1397 {
1398 return verts(i).distance_squared_to(verts((i + 1) % n));
1399 };
1400
1401 size_t best_i = 0;
1402 size_t best_ni = 1;
1403 size_t best_j = 0;
1405 bool initialized = false;
1406
1407 // Initialize antipodal index for edge (0,1).
1408 size_t j = 1 % n;
1409 size_t safety = 0;
1410 while (area_to_edge(0, (j + 1) % n) > area_to_edge(0, j) and safety++ < n)
1411 j = (j + 1) % n;
1412
1413 for (size_t i = 0; i < n; ++i)
1414 {
1415 const size_t ni = (i + 1) % n;
1416
1417 const Geom_Number len2 = edge_len2(i);
1418 if (len2 == 0)
1419 continue;
1420
1421 // Advance antipodal index while area increases.
1422 safety = 0;
1423 while (area_to_edge(i, (j + 1) % n) > area_to_edge(i, j) and safety++ < n)
1424 j = (j + 1) % n;
1425
1426 const Geom_Number num = area_to_edge(i, j);
1427 const Geom_Number width_sq = (num * num) / len2;
1429 {
1430 best_i = i;
1431 best_ni = ni;
1432 best_j = j;
1434 initialized = true;
1435 }
1436 }
1437
1438 if (not initialized)
1439 return {verts(0), verts(1), verts(0), 0};
1440
1442 }
1443};
1444
1445// ============================================================================
1446// Point-in-Polygon
1447// ============================================================================
1448
1474{
1475public:
1476 enum class Location
1477 {
1478 Outside,
1479 Boundary,
1480 Inside
1481 };
1482
1492 static Location locate(const Polygon &poly, const Point &p)
1493 {
1494 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1495
1496 ah_domain_error_if(poly.size() < 3) << "Point-in-polygon requires at least 3 vertices";
1497
1498 switch (poly.locate_point(p))
1499 {
1501 return Location::Boundary;
1503 return Location::Inside;
1505 default:
1506 return Location::Outside;
1507 }
1508 }
1509
1517 [[nodiscard]] static bool contains(const Polygon &poly, const Point &p)
1518 {
1519 const Location loc = locate(poly, p);
1520 return loc != Location::Outside;
1521 }
1522
1530 [[nodiscard]] static bool strictly_contains(const Polygon &poly, const Point &p)
1531 {
1532 return locate(poly, p) == Location::Inside;
1533 }
1534};
1535
1536// ============================================================================
1537// Polygon Intersection (Basic)
1538// ============================================================================
1539
1560{
1562 {
1563 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1564
1565 ah_domain_error_if(poly.size() < 3) << "Polygon must have at least 3 vertices";
1566
1568 }
1569
1574
1575 [[nodiscard]] static bool is_convex(const Array<Point> &verts)
1576 {
1577 if (verts.size() < 3)
1578 return false;
1580 }
1581
1582 [[nodiscard]] static bool inside_half_plane(const Point &p,
1583 const Point &a,
1584 const Point &b,
1585 const bool clip_ccw)
1586 {
1587 const Orientation o = orientation(a, b, p);
1588 return clip_ccw ? o != Orientation::CW : o != Orientation::CCW;
1589 }
1590
1592 const Point &e,
1593 const Point &a,
1594 const Point &b)
1595 {
1596 const Geom_Number rx = e.get_x() - s.get_x();
1597 const Geom_Number ry = e.get_y() - s.get_y();
1598 const Geom_Number sx = b.get_x() - a.get_x();
1599 const Geom_Number sy = b.get_y() - a.get_y();
1600
1601 const Geom_Number den = rx * sy - ry * sx;
1602 if (den == 0)
1603 {
1604 if (orientation(a, b, s) == Orientation::COLLINEAR)
1605 return s;
1606 if (orientation(a, b, e) == Orientation::COLLINEAR)
1607 return e;
1608 return s;
1609 }
1610
1611 const Geom_Number qpx = a.get_x() - s.get_x();
1612 const Geom_Number qpy = a.get_y() - s.get_y();
1613 const Geom_Number t = (qpx * sy - qpy * sx) / den;
1614
1615 return {s.get_x() + t * rx, s.get_y() + t * ry};
1616 }
1617
1618 static void push_clean(Array<Point> &out, const Point &p)
1619 {
1620 if (out.is_empty())
1621 {
1622 out.append(p);
1623 return;
1624 }
1625
1626 if (out.get_last() == p)
1627 return;
1628
1629 while (out.size() >= 2)
1630 {
1631 const Point &a = out(out.size() - 2);
1632 const Point &b = out(out.size() - 1);
1633 if (orientation(a, b, p) != Orientation::COLLINEAR)
1634 break;
1635
1636 if (const Segment ab(a, b); on_segment(ab, p))
1637 return;
1638
1639 static_cast<void>(out.remove_last());
1640 }
1641
1642 out.append(p);
1643 }
1644
1646 {
1648 ret.reserve(pts.size());
1649 for (size_t i = 0; i < pts.size(); ++i)
1650 push_clean(ret, pts(i));
1651
1652 if (ret.size() > 1 and ret(0) == ret.get_last())
1653 static_cast<void>(ret.remove_last());
1654
1655 return ret;
1656 }
1657
1659 {
1660 Polygon ret;
1662
1663 for (size_t i = 0; i < clean.size(); ++i)
1664 ret.add_vertex(clean(i));
1665
1666 if (ret.size() >= 3)
1667 ret.close();
1668
1669 return ret;
1670 }
1671
1672public:
1683 {
1686
1687 ah_domain_error_if(not is_convex(subj)) << "Subject polygon must be convex";
1688 ah_domain_error_if(not is_convex(clp)) << "Clip polygon must be convex";
1689
1691 ah_domain_error_if(clip_area2 == 0) << "Clip polygon is degenerate";
1692
1693 const bool clip_ccw = clip_area2 > 0;
1694
1696
1697 for (size_t i = 0; i < clp.size(); ++i)
1698 {
1699 if (output.is_empty())
1700 break;
1701
1702 const Point &a = clp(i);
1703 const Point &b = clp((i + 1) % clp.size());
1704
1705 const Array<Point> input = output;
1706 output = Array<Point>();
1707 output.reserve(input.size() + 2);
1708
1709 Point s = input.get_last();
1710 bool s_inside = inside_half_plane(s, a, b, clip_ccw);
1711
1712 for (size_t j = 0; j < input.size(); ++j)
1713 {
1714 const Point &e = input(j);
1715 const bool e_inside = inside_half_plane(e, a, b, clip_ccw);
1716
1717 if (e_inside)
1718 {
1719 if (not s_inside)
1720 push_clean(output, line_intersection(s, e, a, b));
1721 push_clean(output, e);
1722 }
1723 else if (s_inside)
1724 push_clean(output, line_intersection(s, e, a, b));
1725
1726 s = e;
1728 }
1729
1731 }
1732
1733 return build_polygon(output);
1734 }
1735};
1736
1737// ============================================================================
1738// Half-Plane Intersection
1739// ============================================================================
1740
1758{
1759public:
1761 {
1764
1765 HalfPlane() = default;
1766
1767 HalfPlane(Point p_, Point q_) : p(std::move(p_)), q(std::move(q_)) {}
1768
1770 {
1771 return q.get_x() - p.get_x();
1772 }
1773
1775 {
1776 return q.get_y() - p.get_y();
1777 }
1778
1780 {
1781 // n = (-dy, dx), inequality n.x*x + n.y*y >= offset
1782 return -dy() * p.get_x() + dx() * p.get_y();
1783 }
1784
1785 [[nodiscard]] bool outside(const Point &x) const
1786 {
1787 return orientation(p, q, x) == Orientation::CW;
1788 }
1789 };
1790
1791private:
1796
1797 [[nodiscard]] static bool upper_half(const HalfPlane &h)
1798 {
1799 return h.dy() > 0 or h.dy() == 0 and h.dx() >= 0;
1800 }
1801
1802 [[nodiscard]] static Geom_Number cross_dir(const HalfPlane &a, const HalfPlane &b)
1803 {
1804 return a.dx() * b.dy() - a.dy() * b.dx();
1805 }
1806
1807 [[nodiscard]] static Geom_Number dot_dir(const HalfPlane &a, const HalfPlane &b)
1808 {
1809 return a.dx() * b.dx() + a.dy() * b.dy();
1810 }
1811
1812 [[nodiscard]] static bool same_direction(const HalfPlane &a, const HalfPlane &b)
1813 {
1814 return cross_dir(a, b) == 0 and dot_dir(a, b) > 0;
1815 }
1816
1817 [[nodiscard]] static bool parallel(const HalfPlane &a, const HalfPlane &b)
1818 {
1819 return cross_dir(a, b) == 0;
1820 }
1821
1822 [[nodiscard]] static Point line_intersection(const HalfPlane &a, const HalfPlane &b)
1823 {
1824 const Geom_Number rx = a.dx();
1825 const Geom_Number ry = a.dy();
1826 const Geom_Number sx = b.dx();
1827 const Geom_Number sy = b.dy();
1828
1829 const Geom_Number den = rx * sy - ry * sx;
1830 ah_domain_error_if(den == 0) << "Parallel half-plane boundaries";
1831
1832 const Geom_Number qpx = b.p.get_x() - a.p.get_x();
1833 const Geom_Number qpy = b.p.get_y() - a.p.get_y();
1834 const Geom_Number t = (qpx * sy - qpy * sx) / den;
1835
1836 return {a.p.get_x() + t * rx, a.p.get_y() + t * ry};
1837 }
1838
1839 static void push_clean(Array<Point> &out, const Point &p)
1840 {
1841 if (out.is_empty())
1842 {
1843 out.append(p);
1844 return;
1845 }
1846
1847 if (out.get_last() == p)
1848 return;
1849
1850 while (out.size() >= 2)
1851 {
1852 const Point &a = out(out.size() - 2);
1853 const Point &b = out(out.size() - 1);
1854 if (orientation(a, b, p) != Orientation::COLLINEAR)
1855 break;
1856
1857 if (const Segment ab(a, b); on_segment(ab, p))
1858 return;
1859
1860 static_cast<void>(out.remove_last());
1861 }
1862
1863 out.append(p);
1864 }
1865
1867 {
1869 ret.reserve(pts.size());
1870 for (size_t i = 0; i < pts.size(); ++i)
1871 push_clean(ret, pts(i));
1872
1873 if (ret.size() > 1 and ret(0) == ret.get_last())
1874 static_cast<void>(ret.remove_last());
1875
1876 return ret;
1877 }
1878
1880 {
1881 Polygon ret;
1883
1884 for (size_t i = 0; i < clean.size(); ++i)
1885 ret.add_vertex(clean(i));
1886
1887 if (ret.size() >= 3)
1888 ret.close();
1889
1890 return ret;
1891 }
1892
1893public:
1904 {
1905 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
1906 ah_domain_error_if(poly.size() < 3) << "Polygon must have at least 3 vertices";
1907
1909 verts.reserve(poly.size());
1910 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
1911 verts.append(it.get_current_vertex());
1912
1914 ah_domain_error_if(area2 == 0) << "Polygon is degenerate";
1915 const bool ccw = area2 > 0;
1916
1918 hs.reserve(verts.size());
1919 for (size_t i = 0; i < verts.size(); ++i)
1920 {
1921 const Point &a = verts(i);
1922 const Point &b = verts((i + 1) % verts.size());
1923 hs.append(ccw ? HalfPlane(a, b) : HalfPlane(b, a));
1924 }
1925 return hs;
1926 }
1927
1935 {
1936 if (halfplanes.size() < 3)
1937 return {};
1938
1940 quicksort_op(hps, [](const HalfPlane &a, const HalfPlane &b)
1941 {
1942 const bool ha = upper_half(a);
1943 const bool hb = upper_half(b);
1944 if (ha != hb)
1945 return ha and not hb;
1946
1947 if (const Geom_Number cr = cross_dir(a, b); cr != 0)
1948 return cr > 0;
1949
1950 // Same direction: keep stronger constraints first.
1951 return a.offset() > b.offset();
1952 });
1953
1955 unique.reserve(hps.size());
1956 for (size_t i = 0; i < hps.size(); ++i)
1957 {
1958 const HalfPlane &hp = hps(i);
1959 if (unique.is_empty())
1960 {
1961 unique.append(hp);
1962 continue;
1963 }
1964
1965 if (const HalfPlane &last = unique.get_last(); same_direction(last, hp))
1966 {
1967 if (hp.offset() > last.offset())
1968 {
1969 static_cast<void>(unique.remove_last());
1970 unique.append(hp);
1971 }
1972 continue;
1973 }
1974
1975 unique.append(hp);
1976 }
1977
1980
1981 for (size_t i = 0; i < unique.size(); ++i)
1982 {
1983 const HalfPlane &hp = unique(i);
1984
1985 while (not intersections.is_empty() and hp.outside(intersections.get_last()))
1986 {
1987 dq.remove_last();
1988 intersections.remove_last();
1989 }
1990
1991 while (not intersections.is_empty() and hp.outside(intersections.get_first()))
1992 {
1993 dq.remove_first();
1994 intersections.remove_first();
1995 }
1996
1997 if (not dq.is_empty() and parallel(dq.get_last(), hp))
1998 {
1999 if (same_direction(dq.get_last(), hp))
2000 {
2001 if (hp.offset() > dq.get_last().offset())
2002 {
2003 dq.remove_last();
2004 if (not intersections.is_empty())
2005 intersections.remove_last();
2006 }
2007 else
2008 continue;
2009 }
2010 else // Opposite parallel boundaries cannot define a bounded polygon here.
2011 return {};
2012 }
2013
2014 if (not dq.is_empty())
2015 intersections.append(line_intersection(dq.get_last(), hp));
2016
2017 dq.append(hp);
2018 }
2019
2020 while (not intersections.is_empty() and dq.get_first().outside(intersections.get_last()))
2021 {
2022 dq.remove_last();
2023 intersections.remove_last();
2024 }
2025
2026 while (not intersections.is_empty() and dq.get_last().outside(intersections.get_first()))
2027 {
2028 dq.remove_first();
2029 intersections.remove_first();
2030 }
2031
2032 if (dq.size() < 3)
2033 return {};
2034
2035 if (parallel(dq.get_last(), dq.get_first()))
2036 return {};
2037
2038 const Point closing = line_intersection(dq.get_last(), dq.get_first());
2039
2041 verts.reserve(intersections.size() + 1);
2042
2044 while (not tmp.is_empty())
2045 verts.append(tmp.remove_first());
2046 verts.append(closing);
2047
2048 return build_polygon(verts);
2049 }
2050
2057 [[nodiscard]] Polygon operator () (const std::initializer_list<HalfPlane> il) const
2058 {
2060 hps.reserve(il.size());
2061 for (const HalfPlane &hp : il)
2062 hps.append(hp);
2063 return (*this)(hps);
2064 }
2065};
2066
2067// ============================================================================
2068// Delaunay Triangulation (Bowyer-Watson)
2069// ============================================================================
2070
2101{
2102public:
2109 {
2110 size_t i;
2111 size_t j;
2112 size_t k;
2113 };
2114
2123
2124private:
2128 [[nodiscard]] static bool lexicographic_less(const Point &p1, const Point &p2)
2129 {
2130 if (p1.get_x() < p2.get_x())
2131 return true;
2132 if (p2.get_x() < p1.get_x())
2133 return false;
2134 return p1.get_y() < p2.get_y();
2135 }
2136
2140 [[nodiscard]] static bool all_collinear(const Array<Point> &pts)
2141 {
2142 if (pts.size() < 3)
2143 return true;
2144
2145 for (size_t i = 2; i < pts.size(); ++i)
2146 if (orientation(pts(0), pts(1), pts(i)) != Orientation::COLLINEAR)
2147 return false;
2148
2149 return true;
2150 }
2151
2156 const size_t ia,
2157 const size_t ib,
2158 const size_t ic,
2159 const size_t ip)
2160 {
2161 const Point &a = pts(ia);
2162 const Point &b = pts(ib);
2163 const Point &c = pts(ic);
2164 const Point &p = pts(ip);
2165
2166 const Geom_Number det = in_circle_determinant(a, b, c, p);
2167
2168 const Orientation o = orientation(a, b, c);
2169 if (o == Orientation::CCW)
2170 {
2171 if (det > 0)
2172 return true;
2173 if (det < 0)
2174 return false;
2175 }
2176 if (o == Orientation::CW)
2177 {
2178 if (det < 0)
2179 return true;
2180 if (det > 0)
2181 return false;
2182 }
2183
2184 // Cocircular / degenerate tie-break for deterministic output.
2185 size_t max_idx = ia;
2186 if (ib > max_idx)
2187 max_idx = ib;
2188 if (ic > max_idx)
2189 max_idx = ic;
2190 return ip < max_idx;
2191 }
2192
2197 {
2199 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
2200 all.append(it.get_curr());
2201
2202 quicksort_op(all, [](const Point &p1, const Point &p2)
2203 {
2204 return lexicographic_less(p1, p2);
2205 });
2206
2208 ret.reserve(all.size());
2209 for (size_t i = 0; i < all.size(); ++i)
2210 if (ret.is_empty() or ret.get_last() != all(i))
2211 ret.append(all(i));
2212
2213 return ret;
2214 }
2215
2216public:
2224 {
2225 Result ret;
2227 const size_t n = ret.sites.size();
2228
2229 if (n < 3 or all_collinear(ret.sites))
2230 return ret;
2231
2232 Array<Point> pts = ret.sites;
2233 ret.triangles = GeomBowyerWatsonUtils::triangulate<IndexedTriangle>(
2234 std::move(pts), n,
2235 [](const Array<Point> &all_pts, const size_t ia, const size_t ib, const size_t ic,
2236 const size_t ip)
2237 {
2239 });
2240
2241 return ret;
2242 }
2243
2250 [[nodiscard]] Result operator () (const std::initializer_list<Point> il) const
2251 {
2252 DynList<Point> points;
2253 for (const Point &p : il)
2254 points.append(p);
2255 return (*this)(points);
2256 }
2257
2265 {
2267 for (size_t tria_idx = 0; tria_idx < result.triangles.size(); ++tria_idx)
2268 {
2269 const auto &[i, j, k] = result.triangles(tria_idx);
2270 out.append(Triangle(result.sites(i), result.sites(j), result.sites(k)));
2271 }
2272 return out;
2273 }
2274};
2275
2276// ============================================================================
2277// Regular Triangulation (Weighted Delaunay) — Bowyer-Watson
2278// ============================================================================
2279
2315{
2316public:
2323
2325
2331
2332private:
2333 [[nodiscard]] static bool lex_less(const Point &p1, const Point &p2)
2334 {
2335 if (p1.get_x() < p2.get_x())
2336 return true;
2337 if (p2.get_x() < p1.get_x())
2338 return false;
2339 return p1.get_y() < p2.get_y();
2340 }
2341
2343 {
2344 if (s.size() < 3)
2345 return true;
2346
2347 for (size_t i = 2; i < s.size(); ++i)
2348 if (orientation(s(0).position, s(1).position, s(i).position) != Orientation::COLLINEAR)
2349 return false;
2350
2351 return true;
2352 }
2353
2371 const Array<Geom_Number> &wts,
2372 const size_t ia,
2373 const size_t ib,
2374 const size_t ic,
2375 const size_t ip)
2376 {
2377 const Point &a = pts(ia);
2378 const Point &b = pts(ib);
2379 const Point &c = pts(ic);
2380 const Point &p = pts(ip);
2381
2382 const Geom_Number adx = a.get_x() - p.get_x();
2383 const Geom_Number ady = a.get_y() - p.get_y();
2384 const Geom_Number bdx = b.get_x() - p.get_x();
2385 const Geom_Number bdy = b.get_y() - p.get_y();
2386 const Geom_Number cdx = c.get_x() - p.get_x();
2387 const Geom_Number cdy = c.get_y() - p.get_y();
2388
2389 const Geom_Number ad2 = adx * adx + ady * ady - wts(ia) + wts(ip);
2390 const Geom_Number bd2 = bdx * bdx + bdy * bdy - wts(ib) + wts(ip);
2391 const Geom_Number cd2 = cdx * cdx + cdy * cdy - wts(ic) + wts(ip);
2392
2393 const Geom_Number det =
2394 ad2 * (bdx * cdy - bdy * cdx) - bd2 * (adx * cdy - ady * cdx) + cd2 * (adx * bdy - ady * bdx);
2395
2396 const Orientation o = orientation(a, b, c);
2397 if (o == Orientation::CCW)
2398 {
2399 if (det > 0)
2400 return true;
2401 if (det < 0)
2402 return false;
2403 }
2404 if (o == Orientation::CW)
2405 {
2406 if (det < 0)
2407 return true;
2408 if (det > 0)
2409 return false;
2410 }
2411
2412 // Cocircular / degenerate tie-break for deterministic output.
2413 size_t max_idx = ia;
2414 if (ib > max_idx)
2415 max_idx = ib;
2416 if (ic > max_idx)
2417 max_idx = ic;
2418 return ip < max_idx;
2419 }
2420
2428 {
2430 all.reserve(input.size());
2431 for (size_t i = 0; i < input.size(); ++i)
2432 all.append(input(i));
2433
2434 quicksort_op(all, [](const WeightedSite &a, const WeightedSite &b)
2435 {
2436 return lex_less(a.position, b.position);
2437 });
2438
2440 ret.reserve(all.size());
2441 for (size_t i = 0; i < all.size(); ++i)
2442 if (ret.is_empty() or ret.get_last().position != all(i).position)
2443 ret.append(all(i));
2444
2445 return ret;
2446 }
2447
2448public:
2456 {
2457 Result ret;
2459 const size_t n = ret.sites.size();
2460
2461 if (n < 3 or all_collinear(ret.sites))
2462 return ret;
2463
2466 pts.reserve(n + 3);
2467 wts.reserve(n + 3);
2468 for (size_t i = 0; i < n; ++i)
2469 {
2470 pts.append(ret.sites(i).position);
2471 wts.append(ret.sites(i).weight);
2472 }
2473 wts.append(Geom_Number(0));
2474 wts.append(Geom_Number(0));
2475 wts.append(Geom_Number(0));
2476 ret.triangles = GeomBowyerWatsonUtils::triangulate<IndexedTriangle>(
2477 std::move(pts), n,
2478 [&wts](const Array<Point> &all_pts, const size_t ia, const size_t ib, const size_t ic,
2479 const size_t ip)
2480 {
2482 });
2483
2484 return ret;
2485 }
2486};
2487
2488// ============================================================================
2489// Delaunay Triangulation — Randomized Incremental O(n log n) expected
2490// ============================================================================
2491
2506{
2507public:
2510
2511private:
2512 static constexpr size_t NONE = ~static_cast<size_t>(0);
2513
2514 struct Tri
2515 {
2516 size_t v[3];
2517 size_t adj[3];
2518 bool alive;
2519 };
2520
2521 struct DagNode
2522 {
2523 size_t tri;
2525 };
2526
2528 [[nodiscard]] static size_t local_of(const Tri &t, const size_t id)
2529 {
2530 for (int i = 0; i < 3; ++i)
2531 if (t.v[i] == id)
2532 return static_cast<size_t>(i);
2533 return NONE;
2534 }
2535
2537 [[nodiscard]] static size_t adj_of(const Tri &t, const size_t n)
2538 {
2539 for (int i = 0; i < 3; ++i)
2540 if (t.adj[i] == n)
2541 return static_cast<size_t>(i);
2542 return NONE;
2543 }
2544
2546 [[nodiscard]] static bool point_in_tri(const Array<Point> &pts, const Tri &t, const size_t pidx)
2547 {
2548 const Orientation o0 = orientation(pts(t.v[0]), pts(t.v[1]), pts(pidx));
2549 const Orientation o1 = orientation(pts(t.v[1]), pts(t.v[2]), pts(pidx));
2550 const Orientation o2 = orientation(pts(t.v[2]), pts(t.v[0]), pts(pidx));
2553 return not (has_cw and has_ccw);
2554 }
2555
2559 [[nodiscard]] static bool in_cc(const Array<Point> &pts,
2560 const size_t ia,
2561 const size_t ib,
2562 const size_t ic,
2563 const size_t ip)
2564 {
2566 const Orientation o = orientation(pts(ia), pts(ib), pts(ic));
2567 if (o == Orientation::CCW)
2568 {
2569 if (det > 0)
2570 return true;
2571 if (det < 0)
2572 return false;
2573 }
2574 if (o == Orientation::CW)
2575 {
2576 if (det < 0)
2577 return true;
2578 if (det > 0)
2579 return false;
2580 }
2581 size_t mx = ia;
2582 if (ib > mx)
2583 mx = ib;
2584 if (ic > mx)
2585 mx = ic;
2586 return ip < mx;
2587 }
2588
2594 [[nodiscard]] static size_t locate(const Array<Point> &pts,
2595 const Array<Tri> &tris,
2596 const Array<DagNode> &dag,
2597 const size_t pidx,
2598 const size_t root)
2599 {
2600 size_t cur = root;
2601 while (not dag(cur).children.is_empty())
2602 {
2603 bool found = false;
2604 for (size_t c = 0; c < dag(cur).children.size(); ++c)
2605 {
2606 if (const size_t child = dag(cur).children(c); point_in_tri(pts, tris(child), pidx))
2607 {
2608 cur = child;
2609 found = true;
2610 break;
2611 }
2612 }
2613 if (not found)
2614 break;
2615 }
2616 return cur;
2617 }
2618
2620 static void remap_adj(Array<Tri> &tris, const size_t ot, const size_t nt)
2621 {
2622 for (const unsigned long nb : tris(ot).adj)
2623 {
2624 if (nb == NONE)
2625 continue;
2626 if (const size_t li = adj_of(tris(nb), ot); li != NONE)
2627 tris(nb).adj[li] = nt;
2628 }
2629 }
2630
2631public:
2641 {
2642 Result ret;
2643
2644 // Build unique sorted points.
2645 {
2647 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
2648 all.append(it.get_curr());
2649 quicksort_op(all, [](const Point &a, const Point &b)
2650 {
2651 return a.get_x() < b.get_x() or (a.get_x() == b.get_x() and a.get_y() < b.get_y());
2652 });
2653 ret.sites = Array<Point>();
2654 ret.sites.reserve(all.size());
2655 for (size_t i = 0; i < all.size(); ++i)
2656 if (ret.sites.is_empty() or ret.sites.get_last() != all(i))
2657 ret.sites.append(all(i));
2658 }
2659
2660 const size_t n = ret.sites.size();
2661 if (n < 3)
2662 return ret;
2663
2664 // Check all-collinear.
2665 {
2666 bool collinear = true;
2667 for (size_t i = 2; i < n and collinear; ++i)
2668 if (orientation(ret.sites(0), ret.sites(1), ret.sites(i)) != Orientation::COLLINEAR)
2669 collinear = false;
2670 if (collinear)
2671 return ret;
2672 }
2673
2674 // Build pts array with super-triangle appended.
2675 Array<Point> pts = ret.sites;
2676 Geom_Number mnx = pts(0).get_x(), mxx = mnx;
2677 Geom_Number mny = pts(0).get_y(), mxy = mny;
2678 for (size_t i = 1; i < n; ++i)
2679 {
2680 if (pts(i).get_x() < mnx)
2681 mnx = pts(i).get_x();
2682 if (pts(i).get_x() > mxx)
2683 mxx = pts(i).get_x();
2684 if (pts(i).get_y() < mny)
2685 mny = pts(i).get_y();
2686 if (pts(i).get_y() > mxy)
2687 mxy = pts(i).get_y();
2688 }
2689 Geom_Number delta = mxx - mnx > mxy - mny ? mxx - mnx : mxy - mny;
2690 if (delta == 0)
2691 delta = 1;
2692 const Geom_Number sp = delta * 16 + 1;
2693 const Geom_Number cx = (mnx + mxx) / 2, cy = (mny + mxy) / 2;
2694 pts.append(Point(cx - sp - sp, cy - sp));
2695 pts.append(Point(cx + sp + sp, cy - sp));
2696 pts.append(Point(cx, cy + sp + sp));
2697
2698 // Random insertion order for input points [0..n).
2699 Array<size_t> order;
2700 order.reserve(n);
2701 for (size_t i = 0; i < n; ++i)
2702 order.append(i);
2703 // Fisher-Yates shuffle.
2704 {
2705 std::random_device rd;
2706 std::mt19937 gen(rd());
2707 for (size_t i = n - 1; i > 0; --i)
2708 {
2709 std::uniform_int_distribution<size_t> dis(0, i);
2710 const size_t j = dis(gen);
2711 const size_t tmp = order(i);
2712 order(i) = order(j);
2713 order(j) = tmp;
2714 }
2715 }
2716
2717 // Initialize with super-triangle.
2719 Array<DagNode> dag;
2720 tris.append(Tri{{n, n + 1, n + 2}, {NONE, NONE, NONE}, true});
2721 dag.append(DagNode{0, Array<size_t>()});
2722
2723 // Insert each point using the local Bowyer-Watson cavity approach.
2724 // 1) Locate containing triangle via DAG — O(log n) expected.
2725 // 2) BFS from it to find all "bad" triangles (circumcircle
2726 // contains the new point).
2727 // 3) Extract the boundary polygon of the cavity.
2728 // 4) Re-triangulate cavity by connecting boundary edges to
2729 // the new point.
2730 // Expected cavity size is O(1) for random insertion order,
2731 // giving O(n log n) total expected time.
2732 for (size_t oi = 0; oi < n; ++oi)
2733 {
2734 const size_t pidx = order(oi);
2735 const size_t ti = locate(pts, tris, dag, pidx, 0);
2736
2737 // --- BFS to find the cavity ---
2738 const size_t ntris_before = tris.size();
2741 for (size_t i = 0; i < ntris_before; ++i)
2742 is_bad.append(false);
2743
2745 is_bad(ti) = true;
2746 cavity.append(ti);
2747
2748 // Frontier queue: at most one enqueue per triangle.
2750 frontier.put(ti);
2751 while (not frontier.is_empty())
2752 for (const size_t ct = frontier.get(); unsigned long nb : tris(ct).adj)
2753 {
2754 if (nb == NONE or nb >= ntris_before or not tris(nb).alive or is_bad(nb))
2755 continue;
2756 if (in_cc(pts, tris(nb).v[0], tris(nb).v[1], tris(nb).v[2], pidx))
2757 {
2758 is_bad(nb) = true;
2759 cavity.append(nb);
2760 frontier.put(nb);
2761 }
2762 }
2763
2764 // --- Extract boundary edges ---
2765 // Each boundary edge is an edge of a cavity triangle whose
2766 // neighbor is outside the cavity (or NONE).
2767 struct BEdge
2768 {
2769 size_t u, v, ext, old_ct;
2770 };
2771 Array<BEdge> boundary;
2772 for (size_t ci = 0; ci < cavity.size(); ++ci)
2773 {
2774 const size_t ct = cavity(ci);
2775 for (int e = 0; e < 3; ++e)
2776 {
2777 const size_t nb = tris(ct).adj[e];
2778 if (nb != NONE and nb < ntris_before and is_bad(nb))
2779 continue; // internal cavity edge — skip
2780 // Boundary edge opposite v[e]: vertices (v[(e+1)%3], v[(e+2)%3]).
2781 boundary.append(BEdge{tris(ct).v[(e + 1) % 3], tris(ct).v[(e + 2) % 3], nb, ct});
2782 }
2783 }
2784
2785 // --- Create new triangles ---
2786 // For each boundary edge (u,v) create triangle (u, v, pidx).
2787 // adj[2] (opposite pidx) = external neighbor.
2788 const size_t V = pts.size();
2789 Array<size_t> as_u, as_v; // vertex → new tri where it's u / v
2790 as_u.reserve(V);
2791 as_v.reserve(V);
2792 for (size_t i = 0; i < V; ++i)
2793 {
2794 as_u.append(NONE);
2795 as_v.append(NONE);
2796 }
2797
2799 new_tris.reserve(boundary.size());
2800
2801 for (size_t bi = 0; bi < boundary.size(); ++bi)
2802 {
2803 size_t u = boundary(bi).u, v = boundary(bi).v;
2804 const size_t ext = boundary(bi).ext;
2805 const size_t old_ct = boundary(bi).old_ct;
2806
2807 // Ensure CCW winding.
2808 if (orientation(pts(u), pts(v), pts(pidx)) != Orientation::CCW)
2809 {
2810 const size_t tmp = u;
2811 u = v;
2812 v = tmp;
2813 }
2814
2815 const size_t nt = tris.size();
2816 tris.append(Tri{{u, v, pidx}, {NONE, NONE, ext}, true});
2817 dag.append(DagNode{nt, Array<size_t>()});
2818 new_tris.append(nt);
2819
2820 as_u(u) = nt;
2821 as_v(v) = nt;
2822
2823 // Remap external neighbor to point to the new triangle.
2824 if (ext != NONE)
2825 if (const size_t li = adj_of(tris(ext), old_ct); li != NONE)
2826 tris(ext).adj[li] = nt;
2827 }
2828
2829 // --- Internal adjacencies between new triangles ---
2830 // Two new triangles share an edge through pidx.
2831 // For triangle (u, v, pidx):
2832 // adj[0] opp u = edge(v, pidx) → the new tri with v as u
2833 // adj[1] opp v = edge(pidx, u) → the new tri with u as v
2834 for (size_t ni = 0; ni < new_tris.size(); ++ni)
2835 {
2836 const size_t nt = new_tris(ni);
2837 const size_t u = tris(nt).v[0];
2838 const size_t v = tris(nt).v[1];
2839 tris(nt).adj[0] = as_u(v); // neighbor across (v, pidx)
2840 tris(nt).adj[1] = as_v(u); // neighbor across (pidx, u)
2841 }
2842
2843 // --- Kill cavity and update DAG ---
2844 for (size_t ci = 0; ci < cavity.size(); ++ci)
2845 {
2846 tris(cavity(ci)).alive = false;
2847 for (size_t ni = 0; ni < new_tris.size(); ++ni)
2848 dag(cavity(ci)).children.append(new_tris(ni));
2849 }
2850 }
2851
2852 // Collect alive triangles that don't reference super-triangle.
2853 ret.triangles.reserve(2 * n);
2854 for (size_t t = 0; t < tris.size(); ++t)
2855 {
2856 const Tri &tr = tris(t);
2857 if (not tr.alive)
2858 continue;
2859 if (tr.v[0] >= n or tr.v[1] >= n or tr.v[2] >= n)
2860 continue;
2861 if (orientation(pts(tr.v[0]), pts(tr.v[1]), pts(tr.v[2])) == Orientation::COLLINEAR)
2862 continue;
2863 ret.triangles.append(IndexedTriangle{tr.v[0], tr.v[1], tr.v[2]});
2864 }
2865
2866 return ret;
2867 }
2868
2875 [[nodiscard]] Result operator () (const std::initializer_list<Point> il) const
2876 {
2877 DynList<Point> points;
2878 for (const Point &p : il)
2879 points.append(p);
2880 return (*this)(points);
2881 }
2882};
2883
2884// ============================================================================
2885// Constrained Delaunay Triangulation (CDT)
2886// ============================================================================
2887
2913{
2914public:
2916
2918 {
2919 size_t u;
2920 size_t v;
2921 };
2922
2929
2930private:
2931 static constexpr size_t NONE = ~static_cast<size_t>(0);
2932
2933 struct Tri
2934 {
2935 size_t v[3];
2936 size_t adj[3];
2937 bool constrained[3];
2938 bool alive;
2939 };
2940
2941 [[nodiscard]] static bool lexicographic_less(const Point &p1, const Point &p2)
2942 {
2943 if (p1.get_x() < p2.get_x())
2944 return true;
2945 if (p2.get_x() < p1.get_x())
2946 return false;
2947 return p1.get_y() < p2.get_y();
2948 }
2949
2951 [[nodiscard]] static size_t find_point_index(const Array<Point> &pts, const Point &p)
2952 {
2953 size_t lo = 0, hi = pts.size();
2954 while (lo < hi)
2955 if (const size_t mid = lo + (hi - lo) / 2; lexicographic_less(pts(mid), p))
2956 lo = mid + 1;
2957 else if (lexicographic_less(p, pts(mid)))
2958 hi = mid;
2959 else
2960 return mid;
2961 return NONE;
2962 }
2963
2965 [[nodiscard]] static size_t local_of(const Tri &t, const size_t id)
2966 {
2967 for (int i = 0; i < 3; ++i)
2968 if (t.v[i] == id)
2969 return static_cast<size_t>(i);
2970 return NONE;
2971 }
2972
2974 [[nodiscard]] static size_t adj_of(const Tri &t, const size_t n)
2975 {
2976 for (int i = 0; i < 3; ++i)
2977 if (t.adj[i] == n)
2978 return static_cast<size_t>(i);
2979 return NONE;
2980 }
2981
2984 [[nodiscard]] static size_t edge_opposite(const Tri &t, const size_t a, const size_t b)
2985 {
2986 for (int i = 0; i < 3; ++i)
2987 {
2988 const size_t e0 = t.v[(i + 1) % 3];
2989 const size_t e1 = t.v[(i + 2) % 3];
2990 if ((e0 == a and e1 == b) or (e0 == b and e1 == a))
2991 return static_cast<size_t>(i);
2992 }
2993 return NONE;
2994 }
2995
2998 {
2999 tris.reserve(dt_tris.size());
3000 for (size_t i = 0; i < dt_tris.size(); ++i)
3001 {
3002 const auto &t = dt_tris(i);
3003 tris.append(Tri{{t.i, t.j, t.k}, {NONE, NONE, NONE}, {false, false, false}, true});
3004 }
3005
3007 dt_tris,
3008 [&](const Array<GeomTriangleAdjacencyUtils::EdgeRef> &edges, const size_t first,
3009 const size_t last)
3010 {
3011 if (last - first != 2)
3012 return; // boundary edge (1 tri) or degenerate
3013
3014 const auto &e0 = edges(first);
3015 const auto &e1 = edges(first + 1);
3016
3017 const size_t t0 = e0.tri;
3018 const size_t t1 = e1.tri;
3019 const size_t loc0 = edge_opposite(tris(t0), e0.u, e0.v);
3020 const size_t loc1 = edge_opposite(tris(t1), e1.u, e1.v);
3021
3022 if (loc0 != NONE)
3023 tris(t0).adj[loc0] = t1;
3024 if (loc1 != NONE)
3025 tris(t1).adj[loc1] = t0;
3026 });
3027 }
3028
3030 [[nodiscard]] static bool edge_exists(const Array<Tri> &tris,
3031 const size_t u,
3032 const size_t v,
3033 size_t &out_tri,
3034 size_t &out_local)
3035 {
3036 for (size_t t = 0; t < tris.size(); ++t)
3037 {
3038 if (not tris(t).alive)
3039 continue;
3040
3041 if (const size_t loc = edge_opposite(tris(t), u, v); loc != NONE)
3042 {
3043 out_tri = t;
3044 out_local = loc;
3045 return true;
3046 }
3047 }
3048 return false;
3049 }
3050
3058 const size_t a,
3059 const size_t b,
3060 const size_t c,
3061 const size_t d)
3062 {
3063 const Orientation o1 = orientation(pts(a), pts(d), pts(b));
3064 const Orientation o2 = orientation(pts(a), pts(d), pts(c));
3066 return false;
3067 if (o1 == o2)
3068 return false;
3069
3070 const Orientation o3 = orientation(pts(b), pts(c), pts(a));
3071 const Orientation o4 = orientation(pts(b), pts(c), pts(d));
3073 return false;
3074 if (o3 == o4)
3075 return false;
3076
3077 return true;
3078 }
3079
3084 static void flip_edge(Array<Tri> &tris, const size_t tri_a, const size_t tri_b)
3085 {
3086 Tri &ta = tris(tri_a);
3087 Tri &tb = tris(tri_b);
3088
3089 // Identify the shared edge.
3090 const size_t la = adj_of(ta, tri_b);
3091 const size_t lb = adj_of(tb, tri_a);
3092 if (la == NONE or lb == NONE)
3093 return;
3094
3095 // Vertices: a_opp is opposite the shared edge in ta,
3096 // b_opp is opposite the shared edge in tb.
3097 const size_t a_opp = ta.v[la];
3098 const size_t b_opp = tb.v[lb];
3099 const size_t p = ta.v[(la + 1) % 3]; // shared edge start
3100 const size_t q = ta.v[(la + 2) % 3]; // shared edge end
3101
3102 // External neighbors before the flip.
3103 const size_t ext_a1 = ta.adj[(la + 1) % 3]; // neighbor opp p in ta
3104 const size_t ext_a2 = ta.adj[(la + 2) % 3]; // neighbor opp q in ta
3105 const size_t ext_b1 = tb.adj[(lb + 1) % 3]; // neighbor opp p_adj in tb
3106 const size_t ext_b2 = tb.adj[(lb + 2) % 3]; // neighbor opp q_adj in tb
3107
3108 // Constrained flags for external edges.
3109 const bool con_a1 = ta.constrained[(la + 1) % 3];
3110 const bool con_a2 = ta.constrained[(la + 2) % 3];
3111 const bool con_b1 = tb.constrained[(lb + 1) % 3];
3112 const bool con_b2 = tb.constrained[(lb + 2) % 3];
3113
3114 // Identify which external neighbors of tb go with which new triangle.
3115 // tb has vertices: b_opp, p', q' where p' = tb.v[(lb+1)%3], q' = tb.v[(lb+2)%3]
3116 // p' and q' are p and q (in some order).
3117 const size_t bp = tb.v[(lb + 1) % 3]; // one of {p, q}
3118 const size_t bq = tb.v[(lb + 2) % 3]; // the other
3119
3120 // New triangle A: (a_opp, b_opp, q) — uses new diagonal + edge (b_opp, q)
3121 // New triangle B: (b_opp, a_opp, p) — uses new diagonal + edge (a_opp, p)
3122 // Ensure CCW winding.
3123
3124 // After flip, new triangles share edge (a_opp, b_opp).
3125 // tri_a becomes (a_opp, b_opp, q) [CCW if original was CCW]
3126 // tri_b becomes (b_opp, a_opp, p) [CCW if original was CCW]
3127
3128 // New tri_a = (a_opp, b_opp, q):
3129 // - adj[0] opp a_opp = edge(b_opp, q) — this was in tb between b_opp and q
3130 // - adj[1] opp b_opp = edge(q, a_opp) — this was in ta between q and a_opp
3131 // - adj[2] opp q = tri_b (new diagonal)
3132 // New tri_b = (b_opp, a_opp, p):
3133 // - adj[0] opp b_opp = edge(a_opp, p) — this was in ta between a_opp and p
3134 // - adj[1] opp a_opp = edge(p, b_opp) — this was in tb between p and b_opp
3135 // - adj[2] opp p = tri_a (new diagonal)
3136
3137 // Figure out external neighbors and constrained flags.
3138 // From ta: edge opp (la+1)%3 i.e., opp ta.v[(la+1)%3] = opp p = edge(a_opp, q)
3139 // edge opp (la+2)%3 i.e., opp ta.v[(la+2)%3] = opp q = edge(p, a_opp)
3140 // So: ext_a1 is neighbor across edge(a_opp, q), ext_a2 across edge(p, a_opp).
3141 //
3142 // From tb: bp = tb.v[(lb+1)%3], bq = tb.v[(lb+2)%3]
3143 // ext_b1 is neighbor across edge(b_opp, bq), opp bp
3144 // ext_b2 is neighbor across edge(bp, b_opp), opp bq
3145
3146 // New tri_a = (a_opp, b_opp, q):
3147 // adj[0] opp a_opp = edge(b_opp, q): this is from tb. If bq==q, ext_b1; if bp==q, ext_b2
3148 // adj[1] opp b_opp = edge(q, a_opp): this is ext_a1
3149 // adj[2] opp q = tri_b
3150 size_t new_a_adj0;
3151 bool new_a_con0;
3152
3153 const size_t new_a_adj1 = ext_a1; // edge(q, a_opp) was opp p in ta
3154 const bool new_a_con1 = con_a1;
3155 const size_t new_a_adj2 = tri_b;
3156 constexpr bool new_a_con2 = false; // the new diagonal is not constrained
3157
3158 if (bq == q)
3159 {
3160 new_a_adj0 = ext_b1; // edge(b_opp, q) was opp bp in tb
3162 }
3163 else
3164 {
3165 new_a_adj0 = ext_b2; // edge(b_opp, q) was opp bq in tb, so bp==q
3167 }
3168
3169 // New tri_b = (b_opp, a_opp, p):
3170 // adj[0] opp b_opp = edge(a_opp, p): this is ext_a2
3171 // adj[1] opp a_opp = edge(p, b_opp): from tb. If bp==p, ext_b2; if bq==p, ext_b1
3172 // adj[2] opp p = tri_a
3173 size_t new_b_adj1;
3174 bool new_b_con1;
3175
3176 const size_t new_b_adj0 = ext_a2; // edge(p, a_opp) was opp q in ta
3177 const bool new_b_con0 = con_a2;
3178 const size_t new_b_adj2 = tri_a;
3179 constexpr bool new_b_con2 = false; // new diagonal
3180
3181 if (bp == p)
3182 {
3183 new_b_adj1 = ext_b2; // edge(p, b_opp) was opp bq in tb
3185 }
3186 else
3187 {
3188 new_b_adj1 = ext_b1; // edge(p, b_opp) was opp bp in tb, bq==p
3190 }
3191
3192 // Write new triangles.
3193 ta.v[0] = a_opp;
3194 ta.v[1] = b_opp;
3195 ta.v[2] = q;
3196 ta.adj[0] = new_a_adj0;
3197 ta.adj[1] = new_a_adj1;
3198 ta.adj[2] = new_a_adj2;
3199 ta.constrained[0] = new_a_con0;
3200 ta.constrained[1] = new_a_con1;
3201 ta.constrained[2] = new_a_con2;
3202
3203 tb.v[0] = b_opp;
3204 tb.v[1] = a_opp;
3205 tb.v[2] = p;
3206 tb.adj[0] = new_b_adj0;
3207 tb.adj[1] = new_b_adj1;
3208 tb.adj[2] = new_b_adj2;
3209 tb.constrained[0] = new_b_con0;
3210 tb.constrained[1] = new_b_con1;
3211 tb.constrained[2] = new_b_con2;
3212
3213 // Remap external neighbors to point to correct new triangle.
3214 if (new_a_adj0 != NONE)
3215 if (const size_t li = adj_of(tris(new_a_adj0), tri_b); li != NONE)
3216 tris(new_a_adj0).adj[li] = tri_a;
3217 if (new_b_adj0 != NONE)
3218 if (const size_t li = adj_of(tris(new_b_adj0), tri_a); li != NONE)
3219 tris(new_b_adj0).adj[li] = tri_b;
3220 if (new_b_adj1 != NONE)
3221 if (const size_t li = adj_of(tris(new_b_adj1), tri_a); li != NONE)
3222 tris(new_b_adj1).adj[li] = tri_b;
3223 if (new_a_adj1 != NONE)
3224 if (const size_t li = adj_of(tris(new_a_adj1), tri_b); li != NONE)
3225 tris(new_a_adj1).adj[li] = tri_a;
3226 }
3227
3231 {
3232 size_t tri;
3233 size_t local;
3234 };
3235
3237 const Array<Tri> &tris,
3238 const size_t u,
3239 const size_t v)
3240 {
3242 const Segment seg_uv(pts(u), pts(v));
3243
3244 for (size_t t = 0; t < tris.size(); ++t)
3245 {
3246 if (not tris(t).alive)
3247 continue;
3248
3249 for (int e = 0; e < 3; ++e)
3250 {
3251 const size_t w1 = tris(t).v[(e + 1) % 3];
3252 const size_t w2 = tris(t).v[(e + 2) % 3];
3253
3254 // Skip edges incident to u or v (they can't "cross").
3255 if (w1 == u or w1 == v or w2 == u or w2 == v)
3256 continue;
3257
3258 // Only process each undirected edge once (from the triangle
3259 // with the smaller index, or from the boundary side).
3260 const size_t nb = tris(t).adj[e];
3261 if (nb != NONE and nb < t)
3262 continue;
3263
3265 {
3266 crossings.append(CrossingEdge{t, static_cast<size_t>(e)});
3267 // Also record from the neighbor's side.
3268 if (nb != NONE and tris(nb).alive)
3269 if (const size_t lb = adj_of(tris(nb), t); lb != NONE)
3270 crossings.append(CrossingEdge{nb, lb});
3271 }
3272 }
3273 }
3274
3275 return crossings;
3276 }
3277
3279 static void mark_constrained(Array<Tri> &tris, const size_t u, const size_t v)
3280 {
3281 for (size_t t = 0; t < tris.size(); ++t)
3282 {
3283 if (not tris(t).alive)
3284 continue;
3285
3286 if (const size_t loc = edge_opposite(tris(t), u, v); loc != NONE)
3287 tris(t).constrained[loc] = true;
3288 }
3289 }
3290
3292 static void enforce_constraint(Array<Point> &pts, Array<Tri> &tris, const size_t u, const size_t v)
3293 {
3294 // Check if edge already in mesh.
3295 size_t et, el;
3296 if (edge_exists(tris, u, v, et, el))
3297 {
3298 mark_constrained(tris, u, v);
3299 return;
3300 }
3301
3302 // Find crossing edges and flip them.
3303 const size_t max_iterations = tris.size() * 4 + 100;
3304 for (size_t iter = 0; iter < max_iterations; ++iter)
3305 {
3306 // Check if the edge now exists.
3307 if (edge_exists(tris, u, v, et, el))
3308 {
3309 mark_constrained(tris, u, v);
3310 return;
3311 }
3312
3313 Array<CrossingEdge> crossings = find_crossing_edges(pts, tris, u, v);
3314 if (crossings.is_empty())
3315 {
3316 // Edge should exist now, or no crossings found.
3317 if (edge_exists(tris, u, v, et, el))
3318 mark_constrained(tris, u, v);
3319 return;
3320 }
3321
3322 bool flipped_any = false;
3323 for (size_t ci = 0; ci < crossings.size(); ++ci)
3324 {
3325 const size_t tri_idx = crossings(ci).tri;
3326 const size_t loc_idx = crossings(ci).local;
3327
3328 if (not tris(tri_idx).alive)
3329 continue;
3330
3331 if (tris(tri_idx).constrained[loc_idx])
3332 continue; // don't flip constrained edges
3333
3334 const size_t nb = tris(tri_idx).adj[loc_idx];
3335 if (nb == NONE or not tris(nb).alive)
3336 continue;
3337
3338 // Get the four vertices of the quadrilateral.
3339 const size_t a = tris(tri_idx).v[loc_idx]; // opposite vertex in tri_idx
3340 const size_t lb = adj_of(tris(nb), tri_idx);
3341 if (lb == NONE)
3342 continue;
3343 const size_t d = tris(nb).v[lb]; // opposite vertex in nb
3344 const size_t b = tris(tri_idx).v[(loc_idx + 1) % 3];
3345 const size_t c = tris(tri_idx).v[(loc_idx + 2) % 3];
3346
3347 if (is_convex_quad(pts, a, b, c, d))
3348 {
3349 flip_edge(tris, tri_idx, nb);
3350 flipped_any = true;
3351 break; // restart since crossings changed
3352 }
3353 }
3354
3355 if (not flipped_any)
3356 {
3357 // Try flipping any crossing edge even non-convex (won't flip but
3358 // we break infinite loop).
3359 break;
3360 }
3361 }
3362
3363 // Final check — mark if the edge appeared.
3364 if (edge_exists(tris, u, v, et, el))
3365 mark_constrained(tris, u, v);
3366 }
3367
3370 {
3371 // Collect all non-constrained interior edges.
3372 // Use a simple iterative approach: keep flipping until no more flips.
3373 bool changed = true;
3374 const size_t max_passes = tris.size() * 4 + 100;
3375 size_t pass = 0;
3376
3377 while (changed and pass++ < max_passes)
3378 {
3379 changed = false;
3380
3381 for (size_t t = 0; t < tris.size(); ++t)
3382 {
3383 if (not tris(t).alive)
3384 continue;
3385
3386 for (int e = 0; e < 3; ++e)
3387 {
3388 if (tris(t).constrained[e])
3389 continue; // don't flip constrained edges
3390
3391 const size_t nb = tris(t).adj[e];
3392 if (nb == NONE or nb <= t) // process each pair once
3393 continue;
3394 if (not tris(nb).alive)
3395 continue;
3396
3397 // in-circle test: is the opposite vertex of nb inside
3398 // the circumcircle of t?
3399 const size_t lb = adj_of(tris(nb), t);
3400 if (lb == NONE)
3401 continue;
3402
3403 const size_t d = tris(nb).v[lb]; // opposite vertex in nb
3404
3405 // Check CCW orientation of triangle t
3406 const Orientation o =
3407 orientation(pts(tris(t).v[0]), pts(tris(t).v[1]), pts(tris(t).v[2]));
3409 continue;
3410
3411 const Geom_Number det = in_circle_determinant(pts(tris(t).v[0]), pts(tris(t).v[1]),
3412 pts(tris(t).v[2]), pts(d));
3413
3414 bool violates = false;
3415 if (o == Orientation::CCW and det > 0)
3416 violates = true;
3417 else if (o == Orientation::CW and det < 0)
3418 violates = true;
3419
3420 if (not violates)
3421 continue;
3422
3423 // Check convexity before flipping.
3424 const size_t a = tris(t).v[e];
3425 const size_t b = tris(t).v[(e + 1) % 3];
3426 const size_t c = tris(t).v[(e + 2) % 3];
3427
3428 if (is_convex_quad(pts, a, b, c, d))
3429 {
3430 flip_edge(tris, t, nb);
3431 changed = true;
3432 }
3433 }
3434 }
3435 }
3436 }
3437
3441 const DynList<Segment> &constraints)
3442 {
3444 for (DynList<Point>::Iterator it(points); it.has_curr(); it.next_ne())
3445 all.append(it.get_curr());
3446
3447 // Collect constraint segments into an array for pairwise checks.
3449 for (DynList<Segment>::Iterator it(constraints); it.has_curr(); it.next_ne())
3450 {
3451 const Segment &s = it.get_curr();
3452 all.append(s.get_src_point());
3453 all.append(s.get_tgt_point());
3454 con_arr.append(s);
3455 }
3456
3457 // Compute pairwise intersection points of constraints.
3458 for (size_t i = 0; i < con_arr.size(); ++i)
3459 for (size_t j = i + 1; j < con_arr.size(); ++j)
3460 if (con_arr(i).intersects_properly_with(con_arr(j)))
3462
3463 quicksort_op(all, [](const Point &a, const Point &b)
3464 {
3465 return lexicographic_less(a, b);
3466 });
3467
3469 ret.reserve(all.size());
3470 for (size_t i = 0; i < all.size(); ++i)
3471 if (ret.is_empty() or ret.get_last() != all(i))
3472 ret.append(all(i));
3473
3474 return ret;
3475 }
3476
3479 const DynList<Segment> &constraints)
3480 {
3481 Array<IndexedEdge> result;
3482
3483 for (DynList<Segment>::Iterator it(constraints); it.has_curr(); it.next_ne())
3484 {
3485 const Segment &seg = it.get_curr();
3486 const size_t u = find_point_index(sites, seg.get_src_point());
3487 const size_t v = find_point_index(sites, seg.get_tgt_point());
3488
3489 if (u == NONE or v == NONE or u == v)
3490 continue;
3491
3492 // Find interior collinear points and split.
3494 chain.append(u);
3495 for (size_t i = 0; i < sites.size(); ++i)
3496 {
3497 if (i == u or i == v)
3498 continue;
3499 if (seg.contains(sites(i)))
3500 chain.append(i);
3501 }
3502 chain.append(v);
3503
3504 // Sort chain by distance from u.
3505 if (chain.size() > 2)
3506 {
3507 const Point &pu = sites(u);
3508 quicksort_op(chain, [&](const size_t a, const size_t b)
3509 {
3510 const Geom_Number da =
3511 (sites(a).get_x() - pu.get_x()) * (sites(a).get_x() - pu.get_x()) +
3512 (sites(a).get_y() - pu.get_y()) * (sites(a).get_y() - pu.get_y());
3513 const Geom_Number db =
3514 (sites(b).get_x() - pu.get_x()) * (sites(b).get_x() - pu.get_x()) +
3515 (sites(b).get_y() - pu.get_y()) * (sites(b).get_y() - pu.get_y());
3516 return da < db;
3517 });
3518 }
3519
3520 for (size_t i = 0; i + 1 < chain.size(); ++i)
3521 {
3522 const size_t a = chain(i) < chain(i + 1) ? chain(i) : chain(i + 1);
3523 const size_t b = chain(i) < chain(i + 1) ? chain(i + 1) : chain(i);
3524 // Avoid duplicates.
3525 bool dup = false;
3526 for (size_t j = 0; j < result.size(); ++j)
3527 if (result(j).u == a and result(j).v == b)
3528 {
3529 dup = true;
3530 break;
3531 }
3532 if (not dup)
3533 result.append(IndexedEdge{a, b});
3534 }
3535 }
3536
3537 return result;
3538 }
3539
3540public:
3548 [[nodiscard]] Result operator () (const DynList<Point> &points,
3549 const DynList<Segment> &constraints) const
3550 {
3551 Result ret;
3552
3553 // 1. Merge and deduplicate.
3554 ret.sites = merge_and_deduplicate(points, constraints);
3555 const size_t n = ret.sites.size();
3556
3557 if (n < 3)
3558 return ret;
3559
3560 // Check all-collinear.
3561 {
3562 bool collinear = true;
3563 for (size_t i = 2; i < n and collinear; ++i)
3564 if (orientation(ret.sites(0), ret.sites(1), ret.sites(i)) != Orientation::COLLINEAR)
3565 collinear = false;
3566 if (collinear)
3567 return ret;
3568 }
3569
3570 // 2. Compute unconstrained DT.
3572 for (size_t i = 0; i < n; ++i)
3573 site_list.append(ret.sites(i));
3574
3576 auto [sites, triangles] = dt_algo(site_list);
3577
3578 if (triangles.is_empty())
3579 return ret;
3580
3581 // The DT may have reordered sites. We need to use its sites array
3582 // and remap. Since both are sorted/deduped, they should match.
3583 ret.sites = sites;
3584
3585 // 3. Build adjacency mesh.
3587 build_adjacency(tris, triangles);
3588
3589 // 4. Map constraints to index pairs (with splitting).
3590 Array<IndexedEdge> indexed_constraints = map_constraints(ret.sites, constraints);
3591
3592 // 5. Enforce each constraint.
3593 for (size_t i = 0; i < indexed_constraints.size(); ++i)
3594 enforce_constraint(ret.sites, tris, indexed_constraints(i).u, 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]), ret.sites(tris(t).v[1]), ret.sites(tris(t).v[2])) ==
3607 continue;
3608 ret.triangles.append(IndexedTriangle{tris(t).v[0], tris(t).v[1], tris(t).v[2]});
3609 }
3610
3611 // Collect constrained edges.
3612 ret.constrained_edges.reserve(indexed_constraints.size());
3613 for (size_t i = 0; i < indexed_constraints.size(); ++i)
3614 ret.constrained_edges.append(indexed_constraints(i));
3615
3616 return ret;
3617 }
3618
3622 [[nodiscard]] Result operator () (const std::initializer_list<Point> points,
3623 const std::initializer_list<Segment> constraints) const
3624 {
3626 for (const Point &p : points)
3627 pts.append(p);
3628
3630 for (const Segment &s : constraints)
3631 segs.append(s);
3632
3633 return (*this)(pts, segs);
3634 }
3635
3640 {
3642 for (size_t i = 0; i < result.triangles.size(); ++i)
3643 {
3644 const auto &[ii, j, k] = result.triangles(i);
3645 out.append(Triangle(result.sites(ii), result.sites(j), result.sites(k)));
3646 }
3647 return out;
3648 }
3649};
3650
3651// ============================================================================
3652// Voronoi Diagram (Dual of Delaunay)
3653// ============================================================================
3654
3676{
3677public:
3690
3701
3711
3722
3723private:
3725
3729 [[nodiscard]] static Point circumcenter(const Point &a, const Point &b, const Point &c)
3730 {
3731 const Geom_Number &ax = a.get_x();
3732 const Geom_Number &ay = a.get_y();
3733 const Geom_Number &bx = b.get_x();
3734 const Geom_Number &by = b.get_y();
3735 const Geom_Number &cx = c.get_x();
3736 const Geom_Number &cy = c.get_y();
3737
3738 const Geom_Number a2 = ax * ax + ay * ay;
3739 const Geom_Number b2 = bx * bx + by * by;
3740 const Geom_Number c2 = cx * cx + cy * cy;
3741
3742 const Geom_Number d = ax * (by - cy) + bx * (cy - ay) + cx * (ay - by);
3743 ah_domain_error_if(d == 0) << "Circumcenter undefined for collinear points";
3744 const Geom_Number den = d + d;
3745
3746 const Geom_Number ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / den;
3747 const Geom_Number uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / den;
3748
3749 return {ux, uy};
3750 }
3751
3756 {
3757 ah_domain_error_if(not p.is_closed()) << "Polygon must be closed";
3758 ah_domain_error_if(p.size() < 3) << "Polygon must have at least 3 vertices";
3759
3761 }
3762
3766 [[nodiscard]] static bool is_convex(const Array<Point> &verts)
3767 {
3768 if (verts.size() < 3)
3769 return false;
3771 }
3772
3777 const Point &t)
3778 {
3779 const Point mid((s.get_x() + t.get_x()) / 2, (s.get_y() + t.get_y()) / 2);
3780
3781 const Geom_Number dx = t.get_x() - s.get_x();
3782 const Geom_Number dy = t.get_y() - s.get_y();
3783 const Point q(mid.get_x() - dy, mid.get_y() + dx);
3784
3785 return {mid, q}; // the left side contains s
3786 }
3787
3789 const Array<Polygon> &polys)
3790 {
3791 ah_domain_error_if(sites.size() != polys.size()) << "Sites and clipped polygons size mismatch";
3792
3794 ret.reserve(sites.size());
3795
3796 for (size_t i = 0; i < sites.size(); ++i)
3797 {
3799 cell.site_index = i;
3800 cell.site = sites(i);
3801 cell.polygon = polys(i);
3802 ret.append(std::move(cell));
3803 }
3804
3805 return ret;
3806 }
3807
3808public:
3816 {
3817 Result ret;
3818 ret.sites = dt.sites;
3819
3820 if (dt.triangles.is_empty())
3821 return ret;
3822
3824 centers.reserve(dt.triangles.size());
3825 for (size_t idx = 0; idx < dt.triangles.size(); ++idx)
3826 {
3827 const auto &[i, j, k] = dt.triangles(idx);
3828 centers.append(circumcenter(dt.sites(i), dt.sites(j), dt.sites(k)));
3829 }
3830 ret.vertices = centers;
3831
3833 on_hull.reserve(dt.sites.size());
3834 for (size_t i = 0; i < dt.sites.size(); ++i)
3835 on_hull.append(0);
3836
3838 dt.triangles,
3839 [&](const Array<GeomTriangleAdjacencyUtils::EdgeRef> &edges, const size_t first,
3840 const size_t last)
3841 {
3842 if (const size_t cnt = last - first; cnt >= 2)
3843 {
3844 const Point &p1 = centers(edges(first).tri);
3845 const Point &p2 = centers(edges(first + 1).tri);
3846 ret.edges.append(Edge{edges(first).u, edges(first).v, p1, p2, false, Point()});
3847 return;
3848 }
3849
3850 const auto &edge = edges(first);
3851 const size_t u = edge.u;
3852 const size_t v = edge.v;
3853 const size_t tri = edge.tri;
3854 const size_t third = edge.third;
3855 on_hull(u) = 1;
3856 on_hull(v) = 1;
3857
3858 const Point &pu = dt.sites(u);
3859 const Point &pv = dt.sites(v);
3860 const Point &pw = dt.sites(third);
3861
3862 const Geom_Number ex = pv.get_x() - pu.get_x();
3863 const Geom_Number ey = pv.get_y() - pu.get_y();
3864
3865 Geom_Number dirx = -ey;
3868 {
3869 dirx = ey;
3870 diry = -ex;
3871 }
3872
3873 const Point &src = centers(tri);
3874 ret.edges.append(Edge{u, v, src, src, true, Point(dirx, diry)});
3875 });
3876
3877 // Pre-build site -> incident triangles index (O(T) instead of O(n*T)).
3879 incidence.reserve(dt.sites.size());
3880 for (size_t s = 0; s < dt.sites.size(); ++s)
3881 incidence.append(Array<size_t>());
3882 for (size_t t = 0; t < dt.triangles.size(); ++t)
3883 {
3884 const auto &[ti, tj, tk] = dt.triangles(t);
3885 incidence(ti).append(t);
3886 incidence(tj).append(t);
3887 incidence(tk).append(t);
3888 }
3889
3890 ret.cells.reserve(dt.sites.size());
3891 for (size_t s = 0; s < dt.sites.size(); ++s)
3892 {
3895 for (size_t idx = 0; idx < incidence(s).size(); ++idx)
3896 verts.append(centers(incidence(s)(idx)));
3897
3898 const Point site = dt.sites(s);
3899 if (verts.size() > 1)
3900 {
3901 quicksort_op(verts, [&site](const Point &a, const Point &b)
3902 {
3903 const Geom_Number ax = a.get_x() - site.get_x();
3904 const Geom_Number ay = a.get_y() - site.get_y();
3905 const Geom_Number bx = b.get_x() - site.get_x();
3906 const Geom_Number by = b.get_y() - site.get_y();
3907
3908 const bool au = (ay > 0) or (ay == 0 and ax >= 0);
3909 const bool bu = (by > 0) or (by == 0 and bx >= 0);
3910 if (au != bu)
3911 return au and not bu;
3912
3913 if (const Geom_Number cr = ax * by - ay * bx; cr != 0)
3914 return cr > 0;
3915
3916 return (ax * ax + ay * ay) < (bx * bx + by * by);
3917 });
3918 }
3919
3921 clean.reserve(verts.size());
3922 for (size_t k = 0; k < verts.size(); ++k)
3923 if (clean.is_empty() or clean.get_last() != verts(k))
3924 clean.append(verts(k));
3925 if (clean.size() > 1 and clean(0) == clean.get_last())
3926 static_cast<void>(clean.remove_last());
3927
3928 Cell cell;
3929 cell.site_index = s;
3930 cell.site = site;
3931 cell.bounded = on_hull(s) == 0;
3932 cell.vertices = clean;
3933 ret.cells.append(std::move(cell));
3934 }
3935
3936 return ret;
3937 }
3938
3945 [[nodiscard]] Result operator () (const DynList<Point> &point_set) const
3946 {
3947 return (*this)(delaunay(point_set));
3948 }
3949
3956 [[nodiscard]] Result operator () (const std::initializer_list<Point> il) const
3957 {
3958 return (*this)(delaunay(il));
3959 }
3960
3974 {
3975 const Array<Point> clip_verts = extract_vertices(clip);
3976 ah_domain_error_if(not is_convex(clip_verts)) << "Clip polygon must be convex";
3977
3980
3981 const size_t n = sites.size();
3982
3983 // Compute Delaunay to get adjacency: each site's Voronoi cell is
3984 // bounded only by bisectors with its Delaunay neighbors (~6 on avg),
3985 // reducing total complexity from O(n^2 log n) to O(n log n).
3988 for (size_t i = 0; i < n; ++i)
3989 site_list.append(sites(i));
3990 auto dt = dt_algo(site_list);
3991
3992 // Map Delaunay site indices back to input indices.
3993 // dt.sites may be reordered/deduped, so match by position.
3995 dt_to_input.reserve(dt.sites.size());
3996 for (size_t di = 0; di < dt.sites.size(); ++di)
3997 {
3998 size_t match = 0;
3999 for (size_t oi = 0; oi < n; ++oi)
4000 if (dt.sites(di) == sites(oi))
4001 {
4002 match = oi;
4003 break;
4004 }
4005 dt_to_input.append(match);
4006 }
4007
4008 // Build adjacency lists from Delaunay triangles (in input-index space)
4010 for (size_t i = 0; i < n; ++i)
4012 for (size_t t = 0; t < dt.triangles.size(); ++t)
4013 {
4014 const size_t a = dt_to_input(dt.triangles(t).i);
4015 const size_t b = dt_to_input(dt.triangles(t).j);
4016 const size_t c = dt_to_input(dt.triangles(t).k);
4017 adj(a).insert(b);
4018 adj(a).insert(c);
4019 adj(b).insert(a);
4020 adj(b).insert(c);
4021 adj(c).insert(a);
4022 adj(c).insert(b);
4023 }
4024
4026 ret.reserve(n);
4027
4028 for (size_t i = 0; i < n; ++i)
4029 {
4032
4033 adj(i).for_each([&](size_t j)
4034 {
4035 if (sites(j) != sites(i))
4036 hps.append(bisector_halfplane_for_site(sites(i), sites(j)));
4037 });
4038
4039 // If a site has no Delaunay neighbors (degenerate case),
4040 // fall back to the clip polygon alone.
4041 ret.append(hpi(hps));
4042 }
4043
4044 return ret;
4045 }
4046
4055 {
4056 return clipped_cells(vor.sites, clip);
4057 }
4058
4067 {
4068 return clipped_cells(delaunay(point_set).sites, clip);
4069 }
4070
4078 [[nodiscard]] Array<Polygon> clipped_cells(const std::initializer_list<Point> il,
4079 const Polygon &clip) const
4080 {
4081 return clipped_cells(delaunay(il).sites, clip);
4082 }
4083
4092 {
4093 return indexed_clipped_cells(sites, clipped_cells(sites, clip));
4094 }
4095
4104 {
4105 return clipped_cells_indexed(vor.sites, clip);
4106 }
4107
4116 const Polygon &clip) const
4117 {
4118 return clipped_cells_indexed(delaunay(point_set).sites, clip);
4119 }
4120
4128 [[nodiscard]] Array<ClippedCell> clipped_cells_indexed(const std::initializer_list<Point> il,
4129 const Polygon &clip) const
4130 {
4131 return clipped_cells_indexed(delaunay(il).sites, clip);
4132 }
4133};
4134
4135// ============================================================================
4136// Voronoi Diagram — O(n log n) via Incremental Delaunay Dual
4137// ============================================================================
4138
4176{
4179
4180public:
4185
4192 {
4193 const auto dt = delaunay_(pts);
4194 return voronoi_(dt);
4195 }
4196
4202 [[nodiscard]] Result operator () (const std::initializer_list<Point> il) const
4203 {
4205 for (const Point &p : il)
4206 pts.append(p);
4207 return (*this)(pts);
4208 }
4209
4217 {
4218 auto [sites, triangles] = delaunay_(pts);
4220 }
4221};
4222
4238{
4239public:
4244
4245private:
4246 struct Arc;
4247
4251 struct Event
4252 {
4253 double y = 0.0;
4254 double x = 0.0;
4255 bool is_site = false;
4256 size_t site = 0;
4257 Arc *arc = nullptr;
4258 size_t id = 0;
4259 bool valid = true;
4260 };
4261
4265 struct Arc
4266 {
4267 size_t site = 0;
4268 Arc *prev = nullptr;
4269 Arc *next = nullptr;
4270 Event *circle = nullptr;
4271 };
4272
4277 {
4278 bool operator () (const Event *a, const Event *b) const
4279 {
4280 // DynBinHeap is a min-heap, so define the smallest key as the
4281 // highest-priority event: max y, then min x, site before circle, then
4282 // smallest id.
4283 if (a->y != b->y)
4284 return a->y > b->y; // larger y has smaller key
4285 if (a->x != b->x)
4286 return a->x < b->x; // smaller x has smaller key
4287 if (a->is_site != b->is_site)
4288 return a->is_site > b->is_site; // site (0) < circle (1)
4289 return a->id < b->id;
4290 }
4291 };
4292
4296 struct TriKey
4297 {
4298 size_t a = 0;
4299 size_t b = 0;
4300 size_t c = 0;
4301
4302 bool operator < (const TriKey &o) const
4303 {
4304 if (a != o.a)
4305 return a < o.a;
4306 if (b != o.b)
4307 return b < o.b;
4308 return c < o.c;
4309 }
4310 };
4311
4312 static constexpr double kEps = 1e-9;
4313
4316
4317 [[nodiscard]] static double as_double(const Geom_Number &v)
4318 {
4319 return geom_number_to_double(v);
4320 }
4321
4322 [[nodiscard]] static bool all_collinear(const Array<Point> &pts)
4323 {
4324 if (pts.size() < 3)
4325 return true;
4326
4327 for (size_t i = 2; i < pts.size(); ++i)
4328 if (orientation(pts(0), pts(1), pts(i)) != Orientation::COLLINEAR)
4329 return false;
4330
4331 return true;
4332 }
4333
4335 normalized_triangle(const size_t i, const size_t j, const size_t k, const Array<Point> &sites)
4336 {
4338 if (orientation(sites(t.i), sites(t.j), sites(t.k)) == Orientation::CW)
4339 std::swap(t.j, t.k);
4340 return t;
4341 }
4342
4343 [[nodiscard]] static double breakpoint_x(const Point &left_site,
4344 const Point &right_site,
4345 const double sweepline_y)
4346 {
4347 const double px = as_double(left_site.get_x());
4348 const double py = as_double(left_site.get_y());
4349 const double qx = as_double(right_site.get_x());
4350 const double qy = as_double(right_site.get_y());
4351
4352 if (std::fabs(py - qy) <= kEps)
4353 return (px + qx) / 2.0;
4354 if (std::fabs(py - sweepline_y) <= kEps)
4355 return px;
4356 if (std::fabs(qy - sweepline_y) <= kEps)
4357 return qx;
4358
4359 const double z0 = 2.0 * (py - sweepline_y);
4360 const double z1 = 2.0 * (qy - sweepline_y);
4361
4362 const double a = 1.0 / z0 - 1.0 / z1;
4363 const double b = -2.0 * (px / z0 - qx / z1);
4364 const double c = (px * px + py * py - sweepline_y * sweepline_y) / z0 -
4365 (qx * qx + qy * qy - sweepline_y * sweepline_y) / z1;
4366
4367 if (std::fabs(a) <= kEps)
4368 return -c / b;
4369
4370 double disc = b * b - 4.0 * a * c;
4372 disc = 0.0;
4373 ah_domain_error_if(disc < 0.0) << "Negative discriminant in Fortune breakpoint";
4374
4375 const double sq = std::sqrt(disc);
4376 const double x1 = (-b - sq) / (2.0 * a);
4377 const double x2 = (-b + sq) / (2.0 * a);
4378 return py < qy ? std::max(x1, x2) : std::min(x1, x2);
4379 }
4380
4381 [[nodiscard]] static Arc *locate_arc(Arc *head,
4382 const Array<Point> &sites,
4383 const double x,
4384 const double sweepline_y)
4385 {
4386 if (head == nullptr)
4387 return nullptr;
4388
4389 for (Arc *arc = head; arc != nullptr; arc = arc->next)
4390 {
4391 const double left = arc->prev == nullptr
4392 ? -std::numeric_limits<double>::infinity()
4393 : breakpoint_x(sites(arc->prev->site), sites(arc->site), sweepline_y);
4394 const double right = arc->next == nullptr
4395 ? std::numeric_limits<double>::infinity()
4396 : breakpoint_x(sites(arc->site), sites(arc->next->site), sweepline_y);
4397
4398 if (x >= left - kEps and x <= right + kEps)
4399 return arc;
4400 }
4401
4402 Arc *tail = head;
4403 while (tail->next != nullptr)
4404 tail = tail->next;
4405 return tail;
4406 }
4407
4409 {
4410 if (arc != nullptr and arc->circle != nullptr)
4411 {
4412 arc->circle->valid = false;
4413 arc->circle = nullptr;
4414 }
4415 }
4416
4417 static void enqueue_circle_event(Arc *arc,
4418 const double sweepline_y,
4419 const Array<Point> &sites,
4421 Array<std::unique_ptr<Event>> &event_pool,
4422 size_t &next_event_id)
4423 {
4425
4426 if (arc == nullptr or arc->prev == nullptr or arc->next == nullptr)
4427 return;
4428
4429 const size_t ia = arc->prev->site;
4430 const size_t ib = arc->site;
4431 const size_t ic = arc->next->site;
4432 if (ia == ib or ib == ic or ia == ic)
4433 return;
4434
4435 const Point &a = sites(ia);
4436 const Point &b = sites(ib);
4437 const Point &c = sites(ic);
4438
4439 if (orientation(a, b, c) != Orientation::CW)
4440 return;
4441
4442 const double ax = as_double(a.get_x());
4443 const double ay = as_double(a.get_y());
4444 const double bx = as_double(b.get_x());
4445 const double by = as_double(b.get_y());
4446 const double cx = as_double(c.get_x());
4447 const double cy = as_double(c.get_y());
4448
4449 const double det = 2.0 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
4450 if (std::fabs(det) <= kEps)
4451 return;
4452
4453 const double a2 = ax * ax + ay * ay;
4454 const double b2 = bx * bx + by * by;
4455 const double c2 = cx * cx + cy * cy;
4456
4457 const double ux = (a2 * (by - cy) + b2 * (cy - ay) + c2 * (ay - by)) / det;
4458 const double uy = (a2 * (cx - bx) + b2 * (ax - cx) + c2 * (bx - ax)) / det;
4459
4460 const double r = std::hypot(ax - ux, ay - uy);
4461 const double event_y = uy - r;
4462 if (event_y >= sweepline_y - kEps)
4463 return;
4464
4465 event_pool.append(std::make_unique<Event>());
4466 Event *ev = event_pool.get_last().get();
4467 ev->y = event_y;
4468 ev->x = ux;
4469 ev->is_site = false;
4470 ev->site = 0;
4471 ev->arc = arc;
4472 ev->id = next_event_id++;
4473 ev->valid = true;
4474
4475 arc->circle = ev;
4476 queue.put(ev);
4477 }
4478
4486 {
4487 for (size_t t = 0; t < dt.triangles.size(); ++t)
4488 {
4489 const auto &[i, j, k] = dt.triangles(t);
4490 const Point &a = dt.sites(i);
4491 const Point &b = dt.sites(j);
4492 const Point &c = dt.sites(k);
4493 const Orientation o = orientation(a, b, c);
4495 return false;
4496
4497 for (size_t p = 0; p < dt.sites.size(); ++p)
4498 {
4499 if (p == i or p == j or p == k)
4500 continue;
4501
4502 if (const Geom_Number det = in_circle_determinant(a, b, c, dt.sites(p));
4503 (o == Orientation::CCW and det > 0) or (o == Orientation::CW and det < 0))
4504 return false;
4505 }
4506 }
4507
4508 return true;
4509 }
4510
4512 {
4515
4516 DTResult out;
4517 out.sites = sites;
4518
4519 const size_t n = sites.size();
4520 if (n < 3 or all_collinear(sites))
4521 return out;
4522
4524 auto event_pool = Array<std::unique_ptr<Event>>::create(4 * n + 8);
4525 auto arc_pool = Array<std::unique_ptr<Arc>>::create(3 * n + 8);
4526
4527 auto make_arc = [&](const size_t site_idx) -> Arc *
4528 {
4529 arc_pool.append(std::make_unique<Arc>());
4530 Arc *a = arc_pool.get_last().get();
4531 a->site = site_idx;
4532 return a;
4533 };
4534
4535 size_t next_event_id = 1;
4536 for (size_t i = 0; i < n; ++i)
4537 {
4538 event_pool.append(std::make_unique<Event>());
4539 Event *ev = event_pool.get_last().get();
4540 ev->y = as_double(sites(i).get_y());
4541 ev->x = as_double(sites(i).get_x());
4542 ev->is_site = true;
4543 ev->site = i;
4544 ev->id = next_event_id++;
4545 queue.put(ev);
4546 }
4547
4548 Arc *head = nullptr;
4549
4552 tris.reserve(2 * n);
4553
4554 auto append_triangle = [&](const size_t i, const size_t j, const size_t k)
4555 {
4556 if (i == j or j == k or i == k)
4557 return;
4558 if (orientation(sites(i), sites(j), sites(k)) == Orientation::COLLINEAR)
4559 return;
4560
4561 size_t a = i, b = j, c = k;
4562 if (a > b)
4563 std::swap(a, b);
4564 if (b > c)
4565 std::swap(b, c);
4566 if (a > b)
4567 std::swap(a, b);
4568
4569 if (const auto ptr = seen.insert(TriKey{a, b, c}); ptr == nullptr)
4570 return;
4571
4572 tris.append(normalized_triangle(i, j, k, sites));
4573 };
4574
4575 while (not queue.is_empty())
4576 {
4577 const Event *ev = queue.get();
4578 if (not ev->valid)
4579 continue;
4580
4581 const double ly = ev->y;
4582
4583 if (ev->is_site)
4584 {
4585 const size_t site_idx = ev->site;
4586 const double sx = as_double(sites(site_idx).get_x());
4587
4588 if (head == nullptr)
4589 {
4590 head = make_arc(site_idx);
4591 continue;
4592 }
4593
4594 Arc *arc = locate_arc(head, sites, sx, ly - 10.0 * kEps);
4595 ah_domain_error_if(arc == nullptr) << "Could not locate arc in Fortune site event";
4596
4598
4600 Arc *right = make_arc(arc->site);
4601
4602 right->next = arc->next;
4603 if (right->next != nullptr)
4604 right->next->prev = right;
4605
4606 arc->next = middle;
4607 middle->prev = arc;
4608 middle->next = right;
4609 right->prev = middle;
4610
4611 enqueue_circle_event(arc, ly, sites, queue, event_pool, next_event_id);
4612 enqueue_circle_event(right, ly, sites, queue, event_pool, next_event_id);
4613 }
4614 else
4615 {
4616 Arc *arc = ev->arc;
4617 if (arc == nullptr or arc->circle != ev)
4618 continue;
4619
4620 Arc *left = arc->prev;
4621 Arc *right = arc->next;
4622 if (left == nullptr or right == nullptr)
4623 continue;
4624
4625 append_triangle(left->site, arc->site, right->site);
4626
4630
4631 left->next = right;
4632 right->prev = left;
4633
4634 enqueue_circle_event(left, ly, sites, queue, event_pool, next_event_id);
4635 enqueue_circle_event(right, ly, sites, queue, event_pool, next_event_id);
4636 }
4637 }
4638
4639 out.triangles = std::move(tris);
4640 return out;
4641 }
4642
4643public:
4650 {
4651 // Reuse canonical site handling and keep a robust fallback path.
4652 const auto dt_ref = fallback_(pts);
4653 if (dt_ref.sites.size() < 3 or dt_ref.triangles.is_empty())
4654 return voronoi_(dt_ref);
4655
4656 const auto dt_sweep = triangulate_sweep(dt_ref.sites);
4657 if (dt_sweep.triangles.is_empty() or dt_sweep.triangles.size() != dt_ref.triangles.size() or
4659 return voronoi_(dt_ref);
4660
4661 return voronoi_(dt_sweep);
4662 }
4663
4669 [[nodiscard]] Result operator () (const std::initializer_list<Point> il) const
4670 {
4672 for (const Point &p : il)
4673 pts.append(p);
4674 return (*this)(pts);
4675 }
4676
4687
4689 [[nodiscard]] Array<ClippedCell> clipped_cells(const std::initializer_list<Point> il,
4690 const Polygon &clip) const
4691 {
4693 for (const Point &p : il)
4694 pts.append(p);
4695 return clipped_cells(pts, clip);
4696 }
4697};
4698
4699// ============================================================================
4700// Convex Hull Algorithms
4701// ============================================================================
4702
4727{
4732 {
4733 bool operator () (const Point &p1, const Point &p2) const
4734 {
4735 if (p1.get_x() < p2.get_x())
4736 return true;
4737
4738 if (p2.get_x() < p1.get_x())
4739 return false;
4740
4741 return p1.get_y() < p2.get_y();
4742 }
4743 };
4744
4749 {
4750 Array<Point> points;
4751 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
4752 points.append(it.get_curr());
4753
4754 if (points.size() <= 1)
4755 return points;
4756
4757 quicksort_op(points, LexicographicCmp());
4758
4760 unique.reserve(points.size());
4761 unique.append(points(0));
4762 for (size_t i = 1; i < points.size(); ++i)
4763 if (points(i) != unique.get_last())
4764 unique.append(points(i));
4765
4766 return unique;
4767 }
4768
4772 [[nodiscard]] static Geom_Number turn(const Point &a, const Point &b, const Point &c)
4773 {
4774 return area_of_parallelogram(a, b, c);
4775 }
4776
4777public:
4785 {
4786 Polygon ret;
4788 const size_t n = points.size();
4789
4790 if (n == 0)
4791 return ret;
4792
4793 if (n == 1)
4794 {
4795 ret.add_vertex(points(0));
4796 return ret;
4797 }
4798
4800 lower.reserve(n);
4801 for (size_t i = 0; i < n; ++i)
4802 {
4803 while (lower.size() >= 2 &&
4804 turn(lower(lower.size() - 2), lower(lower.size() - 1), points(i)) <= 0)
4805 static_cast<void>(lower.remove_last());
4806 lower.append(points(i));
4807 }
4808
4810 upper.reserve(n);
4811 for (size_t i = n; i > 0; --i)
4812 {
4813 const Point &p = points(i - 1);
4814 while (upper.size() >= 2 && turn(upper(upper.size() - 2), upper(upper.size() - 1), p) <= 0)
4815 static_cast<void>(upper.remove_last());
4816 upper.append(p);
4817 }
4818
4819 // Remove duplicate endpoints before concatenating chains.
4820 static_cast<void>(lower.remove_last());
4821 static_cast<void>(upper.remove_last());
4822
4823 for (size_t i = 0; i < lower.size(); ++i)
4824 ret.add_vertex(lower(i));
4825
4826 for (size_t i = 0; i < upper.size(); ++i)
4827 ret.add_vertex(upper(i));
4828
4829 if (ret.size() >= 3)
4830 ret.close();
4831
4832 return ret;
4833 }
4834};
4835
4861{
4866 {
4867 bool operator () (const Point &p1, const Point &p2) const
4868 {
4869 if (p1.get_x() < p2.get_x())
4870 return true;
4871
4872 if (p2.get_x() < p1.get_x())
4873 return false;
4874
4875 return p1.get_y() < p2.get_y();
4876 }
4877 };
4878
4883 {
4884 Array<Point> points;
4885 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
4886 points.append(it.get_curr());
4887
4888 if (points.size() <= 1)
4889 return points;
4890
4891 quicksort_op(points, LexicographicCmp());
4892
4894 unique.reserve(points.size());
4895 unique.append(points(0));
4896 for (size_t i = 1; i < points.size(); ++i)
4897 if (points(i) != unique.get_last())
4898 unique.append(points(i));
4899
4900 return unique;
4901 }
4902
4906 [[nodiscard]] static Geom_Number turn(const Point &a, const Point &b, const Point &c)
4907 {
4908 return area_of_parallelogram(a, b, c);
4909 }
4910
4911public:
4919 {
4920 Polygon ret;
4922 const size_t n = points.size();
4923
4924 if (n == 0)
4925 return ret;
4926
4927 if (n == 1)
4928 {
4929 ret.add_vertex(points(0));
4930 return ret;
4931 }
4932
4933 // Pivot = lowest y, and lowest x on ties.
4934 size_t pivot_idx = 0;
4935 for (size_t i = 1; i < n; ++i)
4936 {
4937 if (points(i).get_y() < points(pivot_idx).get_y())
4938 {
4939 pivot_idx = i;
4940 continue;
4941 }
4942
4943 if (points(i).get_y() == points(pivot_idx).get_y() &&
4944 points(i).get_x() < points(pivot_idx).get_x())
4945 pivot_idx = i;
4946 }
4947
4948 const Point pivot = points(pivot_idx);
4949 if (pivot_idx != 0)
4950 {
4951 const Point tmp = points(0);
4952 points(0) = points(pivot_idx);
4953 points(pivot_idx) = tmp;
4954 }
4955
4956 Array<Point> polar;
4957 polar.reserve(n - 1);
4958 for (size_t i = 1; i < n; ++i)
4959 polar.append(points(i));
4960
4961 quicksort_op(polar, [&pivot](const Point &a, const Point &b)
4962 {
4963 const Geom_Number area = area_of_parallelogram(pivot, a, b);
4964 if (area > 0)
4965 return true;
4966 if (area < 0)
4967 return false;
4968
4969 // Same angle: keep nearer first; later we keep only farthest.
4970 return pivot.distance_squared_to(a) < pivot.distance_squared_to(b);
4971 });
4972
4973 // Keep only the farthest point per polar direction.
4975 filtered.reserve(polar.size());
4976 for (size_t i = 0; i < polar.size();)
4977 {
4978 size_t j = i;
4979 while (j + 1 < polar.size() && area_of_parallelogram(pivot, polar(j), polar(j + 1)) == 0)
4980 ++j;
4981 filtered.append(polar(j));
4982 i = j + 1;
4983 }
4984
4985 if (filtered.size() == 1)
4986 {
4987 ret.add_vertex(pivot);
4988 ret.add_vertex(filtered(0));
4989 // Cannot close with only 2 vertices - return unclosed degenerate hull
4990 return ret;
4991 }
4992
4994 hull.reserve(filtered.size() + 1);
4995 hull.append(pivot);
4996 hull.append(filtered(0));
4997
4998 for (size_t i = 1; i < filtered.size(); ++i)
4999 {
5000 const Point &p = filtered(i);
5001 while (hull.size() >= 2 && turn(hull(hull.size() - 2), hull(hull.size() - 1), p) <= 0)
5002 static_cast<void>(hull.remove_last());
5003 hull.append(p);
5004 }
5005
5006 for (size_t i = 0; i < hull.size(); ++i)
5007 ret.add_vertex(hull(i));
5008
5009 if (ret.size() >= 3)
5010 ret.close();
5011
5012 return ret;
5013 }
5014};
5015
5046{
5051 {
5053 static bool cmp_point(const Point &p1, const Point &p2)
5054 {
5055 if (p1.get_x() < p2.get_x())
5056 return true;
5057
5058 return not (p2.get_x() < p1.get_x()) and p1.get_y() < p2.get_y();
5059 }
5060
5062 bool operator () (const Segment &s1, const Segment &s2) const
5063 {
5064 if (cmp_point(s1.get_src_point(), s2.get_src_point()))
5065 return true;
5066
5067 return not (cmp_point(s2.get_src_point(), s1.get_src_point())) and
5068 cmp_point(s1.get_tgt_point(), s2.get_tgt_point());
5069 }
5070 };
5071
5074
5078 static bool are_all_points_on_left(const DynList<Point> &l, const Segment &s)
5079 {
5080 for (PointIt it(l); it.has_curr(); it.next_ne())
5081 if (const Point &p = it.get_curr(); p.is_right_of(s))
5082 return false;
5083
5084 return true;
5085 }
5086
5094 {
5096
5097 for (PointIt i(point_set); i.has_curr(); i.next_ne())
5098 {
5099 const Point &p_i = i.get_curr();
5100
5101 for (PointIt j(point_set); j.has_curr(); j.next_ne())
5102 {
5103 const Point &p_j = j.get_curr();
5104
5105 if (p_i == p_j)
5106 continue;
5107
5109 ret.insert(s);
5110 }
5111 }
5112
5113 return ret;
5114 }
5115
5116public:
5124 {
5125 Polygon ret;
5126
5127 if (point_set.is_empty())
5128 return ret;
5129
5131 const Point first = check_it.get_curr();
5132 bool has_distinct = false;
5133 check_it.next();
5134 for (; check_it.has_curr(); check_it.next_ne())
5135 if (check_it.get_curr() != first)
5136 {
5137 has_distinct = true;
5138 break;
5139 }
5140
5141 if (not has_distinct)
5142 {
5143 ret.add_vertex(first);
5144 return ret;
5145 }
5146
5148
5149 const Segment first_segment = extremes.remove_pos(0);
5150 ret.add_vertex(first_segment.get_src_point());
5151 ret.add_vertex(first_segment.get_tgt_point());
5152
5153 while (true)
5154 {
5155 const Vertex &last_vertex = ret.get_last_vertex();
5156
5157 const Segment *ptr = extremes.find_ptr([&last_vertex](const Segment &s)
5158 {
5159 return s.get_src_point() == last_vertex;
5160 });
5161
5162 ah_domain_error_if(ptr == nullptr)
5163 << "BruteForceConvexHull: broken chain (degenerate input?)";
5164
5165 if (ptr->get_tgt_point() == ret.get_first_vertex())
5166 break;
5167
5168 ret.add_vertex(ptr->get_tgt_point());
5169
5170 extremes.remove(*ptr);
5171 }
5172
5173 if (ret.size() >= 3)
5174 ret.close();
5175 return ret;
5176 }
5177};
5178
5209{
5214 {
5216 const Point *ret = &it.get_curr();
5217 it.next();
5218
5219 for (/* nothing */; it.has_curr(); it.next_ne())
5220 {
5221 const Point &p = it.get_curr();
5222 if (p.get_y() < ret->get_y() or (p.get_y() == ret->get_y() and p.get_x() < ret->get_x()))
5223 ret = &p;
5224 }
5225
5226 return ret;
5227 }
5228
5229public:
5241 {
5242 Polygon ret;
5243
5244 if (point_set.is_empty())
5245 return ret;
5246
5247 const Point *start = get_lowest_point(point_set);
5248 const Point *current = start;
5249
5250 do
5251 {
5252 ret.add_vertex(*current);
5253 const Point *next = nullptr;
5254
5255 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
5256 {
5257 const Point &candidate = it.get_curr();
5258
5259 if (candidate == *current)
5260 continue;
5261
5262 if (next == nullptr)
5263 {
5264 next = &candidate;
5265 continue;
5266 }
5267
5268 if (const Orientation o = orientation(*current, *next, candidate); o == Orientation::CW)
5269 next = &candidate;
5270 else if (o == Orientation::COLLINEAR and
5272 next = &candidate;
5273 }
5274
5275 if (next == nullptr)
5276 break;
5277
5278 current = next;
5279 } while (current != start);
5280
5281 if (ret.size() >= 3)
5282 ret.close();
5283 return ret;
5284 }
5285};
5286
5316{
5334 {
5335 // Compare perpendicular distances via |area_of_parallelogram|.
5336 // Since the segment base is constant for all candidates, comparing
5337 // |area| is equivalent to comparing distance (avoids sqrt).
5339 Point ret;
5340 const Point &a = s.get_src_point();
5341 const Point &b = s.get_tgt_point();
5342
5343 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
5344 {
5345 const Point &p = it.get_curr();
5346 Geom_Number area = area_of_parallelogram(a, b, p);
5347 if (area < 0)
5348 area = -area;
5349
5350 if (area > max_area)
5351 {
5352 ret = p;
5353 max_area = area;
5354 }
5355 }
5356
5357 return ret;
5358 }
5359
5384 static std::pair<DynList<Point>, DynList<Point>> get_right_points(DynList<Point> &point_set,
5385 const Point &a,
5386 const Point &b,
5387 const Point &c)
5388 {
5389 std::pair<DynList<Point>, DynList<Point>> ret;
5390
5391 while (not point_set.is_empty())
5392 {
5393 Point p = point_set.remove_first();
5394
5395 if (p != a and p != c and area_of_parallelogram(a, c, p) < 0)
5396 {
5397 ret.first.append(p);
5398 continue;
5399 }
5400
5401 if (p != c and p != b and area_of_parallelogram(c, b, p) < 0)
5402 ret.second.append(p);
5403 }
5404
5405 return ret;
5406 }
5407
5422 {
5423 if (point_set.is_empty())
5424 return {};
5425
5426 const Point c = get_farthest_point(point_set, Segment(a, b));
5427
5428 auto [s_ac, s_bc] = get_right_points(point_set, a, b, c);
5429
5432 ret.append(c);
5433 ret.concat(tmp);
5434
5435 return ret;
5436 }
5437
5445 static std::pair<Point, Point> search_extremes(const DynList<Point> &point_set)
5446 {
5448 Point leftmost = it.get_curr();
5449 Point rightmost = it.get_curr();
5450 it.next();
5451
5452 for (/* nothing */; it.has_curr(); it.next_ne())
5453 {
5454 const Point &p = it.get_curr();
5455
5456 if (p.get_x() < leftmost.get_x())
5457 leftmost = p;
5458
5459 if (p.get_x() > rightmost.get_x())
5460 rightmost = p;
5461 }
5462
5463 return std::make_pair(leftmost, rightmost);
5464 }
5465
5476 static std::pair<DynList<Point>, DynList<Point>> partition(const DynList<Point> &point_set,
5477 const Point &a,
5478 const Point &b)
5479 {
5480 std::pair<DynList<Point>, DynList<Point>> ret;
5481
5482 for (DynList<Point>::Iterator it(point_set); it.has_curr(); it.next_ne())
5483 if (const Point &p = it.get_curr(); area_of_parallelogram(a, b, p) < 0)
5484 ret.first.append(p);
5485 else
5486 ret.second.append(p);
5487
5488 return ret;
5489 }
5490
5491public:
5499 {
5500 Polygon ret;
5501
5502 if (point_set.is_empty())
5503 return ret;
5504
5505 const auto [leftmost, rightmost] = search_extremes(point_set);
5506 if (leftmost == rightmost)
5507 {
5508 ret.add_vertex(leftmost);
5509 return ret;
5510 }
5511
5512 auto [right, left_or_collinear] = partition(point_set, leftmost, rightmost);
5513
5514 DynList<Point> s1 = quick_hull(right, leftmost, rightmost);
5515 DynList<Point> s2 = quick_hull(left_or_collinear, rightmost, leftmost);
5516
5518 convex_set.append(leftmost);
5519 convex_set.concat(s1);
5520 convex_set.append(rightmost);
5521 convex_set.concat(s2);
5522
5523 for (DynList<Point>::Iterator it(convex_set); it.has_curr(); it.next_ne())
5524 ret.add_vertex(it.get_curr());
5525
5526 if (ret.size() >= 3)
5527 ret.close();
5528 return ret;
5529 }
5530};
5531
5532// ============================================================================
5533// Segment-Segment Intersection (Dedicated O(1))
5534// ============================================================================
5535
5557{
5558public:
5562 enum class Kind
5563 {
5564 NONE,
5565 POINT,
5566 OVERLAP
5567 };
5568
5572 struct Result
5573 {
5577 Segment(Point(0, 0), Point(0, 0));
5578
5581 {
5582 return kind != Kind::NONE;
5583 }
5584 };
5585
5586private:
5587 [[nodiscard]] static bool point_less_on_axis(const Point &a, const Point &b, const bool vertical_axis)
5588 {
5589 if (vertical_axis)
5590 {
5591 if (a.get_y() != b.get_y())
5592 return a.get_y() < b.get_y();
5593 return a.get_x() < b.get_x();
5594 }
5595
5596 if (a.get_x() != b.get_x())
5597 return a.get_x() < b.get_x();
5598 return a.get_y() < b.get_y();
5599 }
5600
5601 [[nodiscard]] static Point point_max_on_axis(const Point &a, const Point &b, const bool vertical_axis)
5602 {
5603 return point_less_on_axis(a, b, vertical_axis) ? b : a;
5604 }
5605
5606 [[nodiscard]] static Point point_min_on_axis(const Point &a, const Point &b, const bool vertical_axis)
5607 {
5608 return point_less_on_axis(a, b, vertical_axis) ? a : b;
5609 }
5610
5612 {
5613 const bool vertical_axis = s1.get_src_point().get_x() == s1.get_tgt_point().get_x();
5614
5615 Point a0 = s1.get_src_point();
5616 Point a1 = s1.get_tgt_point();
5617 Point b0 = s2.get_src_point();
5618 Point b1 = s2.get_tgt_point();
5619
5620 if (point_less_on_axis(a1, a0, vertical_axis))
5621 std::swap(a0, a1);
5622 if (point_less_on_axis(b1, b0, vertical_axis))
5623 std::swap(b0, b1);
5624
5625 const Point lo = point_max_on_axis(a0, b0, vertical_axis);
5626 const Point hi = point_min_on_axis(a1, b1, vertical_axis);
5627 return Segment(lo, hi);
5628 }
5629
5630public:
5638 {
5639 Result out;
5640
5642 return out;
5643
5644 if (not s1.is_parallel_with(s2))
5645 {
5647 out.point = s1.intersection_with(s2);
5648 return out;
5649 }
5650
5651 out.overlap = collinear_overlap(s1, s2);
5652 if (out.overlap.get_src_point() == out.overlap.get_tgt_point())
5653 {
5654 out.kind = Kind::POINT;
5655 out.point = out.overlap.get_src_point();
5656 return out;
5657 }
5658
5659 out.kind = Kind::OVERLAP;
5660 return out;
5661 }
5662};
5663
5670
5671// ============================================================================
5672// Line Sweep / Event-Driven Framework
5673// ============================================================================
5674
5718template <typename Event, typename CmpEvent>
5720{
5725 {
5726 Event event;
5727 size_t seq;
5728 };
5729
5734 {
5736
5737 bool operator () (const SeqEvent &a, const SeqEvent &b) const
5738 {
5739 if (cmp(a.event, b.event))
5740 return true;
5741 if (cmp(b.event, a.event))
5742 return false;
5743 return a.seq < b.seq;
5744 }
5745 };
5746
5748 size_t seq_ = 0;
5749
5750public:
5752 void enqueue(const Event &e)
5753 {
5754 queue_.insert(SeqEvent{e, seq_++});
5755 }
5756
5758 void enqueue(Event &&e)
5759 {
5760 queue_.insert(SeqEvent{std::move(e), seq_++});
5761 }
5762
5765 {
5766 return not queue_.is_empty();
5767 }
5768
5771 {
5772 return queue_.size();
5773 }
5774
5776 Event dequeue()
5777 {
5778 SeqEvent se = queue_.min();
5779 queue_.remove(se);
5780 return std::move(se.event);
5781 }
5782
5784 [[nodiscard]] const Event &peek() const
5785 {
5786 return queue_.min().event;
5787 }
5788
5791 {
5792 queue_.empty(); // DynSetTree::empty() is a mutator that removes all elements
5793 seq_ = 0;
5794 }
5795
5808 template <typename Handler>
5809 void run(Handler &&handler)
5810 {
5811 while (has_events())
5812 handler(*this, dequeue());
5813 }
5814
5821 template <typename Handler>
5822 void run(Handler &&handler, Array<Event> &out)
5823 {
5824 while (has_events())
5825 {
5826 Event e = dequeue();
5827 out.append(e);
5828 handler(*this, e);
5829 }
5830 }
5831};
5832
5833// ============================================================================
5834// Sweep Line Segment Intersection (Bentley-Ottmann)
5835// ============================================================================
5836
5862{
5863public:
5866 {
5867 size_t seg_i;
5868 size_t seg_j;
5870 };
5871
5872private:
5883 [[nodiscard]] static Geom_Number y_at_x(const Segment &s, const Geom_Number &x)
5884 {
5885 const Geom_Number &x1 = s.get_src_point().get_x();
5886 const Geom_Number &y1 = s.get_src_point().get_y();
5887 const Geom_Number &x2 = s.get_tgt_point().get_x();
5888 const Geom_Number &y2 = s.get_tgt_point().get_y();
5889
5890 if (x1 == x2)
5891 return (y1 + y2) / 2;
5892
5893 return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
5894 }
5895
5907 {
5908 const Point &a = s.get_src_point();
5909 const Point &b = s.get_tgt_point();
5910
5911 if (a.get_x() < b.get_x())
5912 return s;
5913 if (b.get_x() < a.get_x())
5914 return {b, a};
5915 // Vertical: lower endpoint first.
5916 if (a.get_y() < b.get_y())
5917 return s;
5918 return {b, a};
5919 }
5920
5924 enum class EventType
5925 {
5926 LEFT,
5928 RIGHT
5929 };
5930
5934 struct Event
5935 {
5938 size_t seg_a;
5939 size_t seg_b;
5940 };
5941
5950 [[nodiscard]] static bool event_less(const Event &a, const Event &b)
5951 {
5952 if (a.pt.get_x() != b.pt.get_x())
5953 return a.pt.get_x() < b.pt.get_x();
5954 if (a.pt.get_y() != b.pt.get_y())
5955 return a.pt.get_y() < b.pt.get_y();
5956 // LEFT < INTERSECTION < RIGHT for the same point.
5957 return static_cast<int>(a.type) < static_cast<int>(b.type);
5958 }
5959
5962 {
5963 bool operator () (const Event &a, const Event &b) const
5964 {
5965 return event_less(a, b);
5966 }
5967 };
5968
5969 [[nodiscard]] static bool slope_less(const Segment &a, const Segment &b)
5970 {
5971 const Geom_Number adx = a.get_tgt_point().get_x() - a.get_src_point().get_x();
5972 const Geom_Number ady = a.get_tgt_point().get_y() - a.get_src_point().get_y();
5973 const Geom_Number bdx = b.get_tgt_point().get_x() - b.get_src_point().get_x();
5974 const Geom_Number bdy = b.get_tgt_point().get_y() - b.get_src_point().get_y();
5975
5976 if (adx == 0 and bdx == 0)
5977 return ady < bdy;
5978 if (adx == 0)
5979 return false; // +inf slope
5980 if (bdx == 0)
5981 return true;
5982
5983 const Geom_Number lhs = ady * bdx;
5984 const Geom_Number rhs = bdy * adx;
5985 return lhs < rhs;
5986 }
5987
5988 [[nodiscard]] static bool status_less(const size_t lhs,
5989 const size_t rhs,
5990 const Geom_Number &sx,
5991 const Array<Segment> &segs)
5992 {
5993 if (lhs == rhs)
5994 return false;
5995
5996 const Geom_Number yl = y_at_x(segs(lhs), sx);
5997 const Geom_Number yr = y_at_x(segs(rhs), sx);
5998 if (yl != yr)
5999 return yl < yr;
6000
6001 if (slope_less(segs(lhs), segs(rhs)))
6002 return true;
6003 if (slope_less(segs(rhs), segs(lhs)))
6004 return false;
6005
6006 return lhs < rhs;
6007 }
6008
6018 {
6019 struct Node
6020 {
6021 size_t seg;
6023 unsigned priority;
6024 Node *left = nullptr;
6025 Node *right = nullptr;
6026 };
6027
6028 Node *root_ = nullptr;
6030 std::mt19937 rng_{0x5f3759dfu};
6032
6033 [[nodiscard]] static Node *merge(Node *a, Node *b)
6034 {
6035 if (a == nullptr)
6036 return b;
6037 if (b == nullptr)
6038 return a;
6039 if (a->priority < b->priority)
6040 {
6041 a->right = merge(a->right, b);
6042 return a;
6043 }
6044 b->left = merge(a, b->left);
6045 return b;
6046 }
6047
6049 {
6050 if (root == nullptr)
6051 return node;
6052
6053 if (node->priority < root->priority)
6054 {
6055 Node *left = nullptr;
6056 Node *right = nullptr;
6057 split_by_label(root, node->label, left, right);
6058 node->left = left;
6059 node->right = right;
6060 return node;
6061 }
6062
6063 if (node->label < root->label)
6064 root->left = insert_by_label(root->left, node);
6065 else
6066 root->right = insert_by_label(root->right, node);
6067 return root;
6068 }
6069
6070 static void split_by_label(Node *root, const Geom_Number &label, Node *&left, Node *&right)
6071 {
6072 if (root == nullptr)
6073 {
6074 left = nullptr;
6075 right = nullptr;
6076 return;
6077 }
6078
6079 if (root->label < label)
6080 {
6081 split_by_label(root->right, label, root->right, right);
6082 left = root;
6083 }
6084 else
6085 {
6086 split_by_label(root->left, label, left, root->left);
6087 right = root;
6088 }
6089 }
6090
6092 {
6093 if (root == nullptr)
6094 return nullptr;
6095
6096 if (label < root->label)
6097 {
6098 root->left = erase_by_label(root->left, label, removed);
6099 return root;
6100 }
6101
6102 if (root->label < label)
6103 {
6104 root->right = erase_by_label(root->right, label, removed);
6105 return root;
6106 }
6107
6108 removed = root;
6109 return merge(root->left, root->right);
6110 }
6111
6112 static void destroy(const Node *n)
6113 {
6114 if (n == nullptr)
6115 return;
6116 destroy(n->left);
6117 destroy(n->right);
6118 delete n;
6119 }
6120
6121 [[nodiscard]] size_t predecessor_for_insert(const size_t seg, const Geom_Number &sx) const
6122 {
6123 size_t pred = SIZE_MAX;
6124 const Node *cur = root_;
6125 while (cur != nullptr)
6126 if (status_less(cur->seg, seg, sx, segs_))
6127 {
6128 pred = cur->seg;
6129 cur = cur->right;
6130 }
6131 else
6132 cur = cur->left;
6133 return pred;
6134 }
6135
6136 [[nodiscard]] size_t successor_for_insert(const size_t seg, const Geom_Number &sx) const
6137 {
6138 size_t succ = SIZE_MAX;
6139 const Node *cur = root_;
6140 while (cur != nullptr)
6141 if (status_less(seg, cur->seg, sx, segs_))
6142 {
6143 succ = cur->seg;
6144 cur = cur->left;
6145 }
6146 else
6147 cur = cur->right;
6148 return succ;
6149 }
6150
6151 [[nodiscard]] Geom_Number fresh_label_between(const size_t pred, const size_t succ) const
6152 {
6153 if (pred != SIZE_MAX and succ != SIZE_MAX)
6154 return (nodes_(pred)->label + nodes_(succ)->label) / Geom_Number(2);
6155 if (pred != SIZE_MAX)
6156 return nodes_(pred)->label + Geom_Number(1);
6157 if (succ != SIZE_MAX)
6158 return nodes_(succ)->label - Geom_Number(1);
6159 return Geom_Number(0);
6160 }
6161
6162 public:
6164 {
6165 nodes_.reserve(segs.size());
6166 for (size_t i = 0; i < segs.size(); ++i)
6167 nodes_.append(nullptr);
6168 }
6169
6171 {
6172 destroy(root_);
6173 }
6174
6175 [[nodiscard]] bool contains(const size_t seg) const
6176 {
6177 return nodes_(seg) != nullptr;
6178 }
6179
6180 void insert(const size_t seg, const Geom_Number &sx)
6181 {
6182 if (contains(seg))
6183 return;
6184
6185 const size_t pred = predecessor_for_insert(seg, sx);
6186 const size_t succ = successor_for_insert(seg, sx);
6187 const auto node = new Node{seg, fresh_label_between(pred, succ),
6188 static_cast<unsigned>(rng_()), nullptr, nullptr};
6189 root_ = insert_by_label(root_, node);
6190 nodes_(seg) = node;
6191 }
6192
6193 void erase(const size_t seg)
6194 {
6195 if (not contains(seg))
6196 return;
6197
6198 Node *removed = nullptr;
6199 root_ = erase_by_label(root_, nodes_(seg)->label, removed);
6200 nodes_(seg) = nullptr;
6201 delete removed;
6202 }
6203
6204 [[nodiscard]] size_t predecessor(const size_t seg) const
6205 {
6206 if (not contains(seg))
6207 return SIZE_MAX;
6208
6209 const Geom_Number &label = nodes_(seg)->label;
6210 const Node *pred = nullptr;
6211 Node *cur = root_;
6212 while (cur != nullptr)
6213 if (cur->label < label)
6214 {
6215 pred = cur;
6216 cur = cur->right;
6217 }
6218 else
6219 cur = cur->left;
6220
6221 return pred == nullptr ? SIZE_MAX : pred->seg;
6222 }
6223
6224 [[nodiscard]] size_t successor(const size_t seg) const
6225 {
6226 if (not contains(seg))
6227 return SIZE_MAX;
6228
6229 const Geom_Number &label = nodes_(seg)->label;
6230 const Node *succ = nullptr;
6231 Node *cur = root_;
6232 while (cur != nullptr)
6233 if (label < cur->label)
6234 {
6235 succ = cur;
6236 cur = cur->left;
6237 }
6238 else
6239 cur = cur->right;
6240
6241 return succ == nullptr ? SIZE_MAX : succ->seg;
6242 }
6243 };
6244
6262 const size_t i,
6263 const size_t j,
6264 const Geom_Number &sx,
6267 const size_t n)
6268 {
6269 const size_t lo = i < j ? i : j;
6270 const size_t hi = i < j ? j : i;
6271 const size_t key = lo * n + hi;
6272 if (seen_pairs.search(key) != nullptr)
6273 return;
6274
6275 const Segment &sa = segs(lo);
6276 const Segment &sb = segs(hi);
6277 const auto hit = segment_segment_intersection(sa, sb);
6278 if (not hit.intersects())
6279 return;
6280
6282 {
6283 seen_pairs.insert(key);
6284 const Point overlap_start = hit.overlap.get_src_point();
6285 const Point overlap_end = hit.overlap.get_tgt_point();
6286 if (not (overlap_start.get_x() < sx))
6287 eq.enqueue(Event{overlap_start, EventType::INTERSECTION, lo, hi});
6288 if (not (overlap_end.get_x() < sx))
6289 eq.enqueue(Event{overlap_end, EventType::INTERSECTION, lo, hi});
6290 return;
6291 }
6292
6293 const Point ip = hit.point;
6294 if (ip.get_x() < sx)
6295 return; // Intersection is to the left of the sweep.
6296
6297 seen_pairs.insert(key);
6298 eq.enqueue(Event{ip, EventType::INTERSECTION, lo, hi});
6299 }
6300
6301public:
6311 {
6312 const size_t n = segments.size();
6313 Array<Intersection> result;
6314
6315 if (n < 2)
6316 return result;
6317
6318 // Canonicalize: src is the left endpoint.
6320 segs.reserve(n);
6321 for (size_t i = 0; i < n; ++i)
6322 {
6323 ah_domain_error_if(segments(i).get_src_point() == segments(i).get_tgt_point())
6324 << "Segment " << i << " is degenerate (zero length)";
6325 segs.append(canonicalize(segments(i)));
6326 }
6327
6328 // Build the initial event queue (left + right endpoints).
6330 for (size_t i = 0; i < n; ++i)
6331 {
6332 eq.enqueue(Event{segs(i).get_src_point(), EventType::LEFT, i, 0});
6333 eq.enqueue(Event{segs(i).get_tgt_point(), EventType::RIGHT, i, 0});
6334 }
6335
6336 // Seen intersection pairs (key = lo * n + hi).
6339
6340 auto pair_key = [n](const size_t i, const size_t j)
6341 {
6342 const size_t lo = i < j ? i : j;
6343 const size_t hi = i < j ? j : i;
6344 return lo * n + hi;
6345 };
6346
6347 auto append_intersection =
6348 [&result, &reported_pairs, &pair_key](const size_t i, const size_t j, const Point &p)
6349 {
6350 const size_t key = pair_key(i, j);
6351 if (reported_pairs.search(key) != nullptr)
6352 return;
6353 reported_pairs.insert(key);
6354
6355 const size_t lo = i < j ? i : j;
6356 const size_t hi = i < j ? j : i;
6357 result.append(Intersection{lo, hi, p});
6358 };
6359
6360 // Sweep-line status: balanced tree with O(log n) updates.
6361 StatusTree status(segs);
6362
6363 while (eq.has_events())
6364 {
6365 const Event ev = eq.dequeue();
6366 const Geom_Number sx = ev.pt.get_x();
6367
6368 if (ev.type == EventType::LEFT)
6369 {
6370 const size_t idx = ev.seg_a;
6371 status.insert(idx, sx);
6372
6373 if (const size_t pred = status.predecessor(idx); pred != SIZE_MAX)
6374 check_and_enqueue(segs, pred, idx, sx, eq, seen_pairs, n);
6375 if (const size_t succ = status.successor(idx); succ != SIZE_MAX)
6376 check_and_enqueue(segs, idx, succ, sx, eq, seen_pairs, n);
6377 }
6378 else if (ev.type == EventType::RIGHT)
6379 {
6380 if (const size_t idx = ev.seg_a; status.contains(idx))
6381 {
6382 const size_t pred = status.predecessor(idx);
6383 const size_t succ = status.successor(idx);
6384 if (pred != SIZE_MAX and succ != SIZE_MAX)
6385 check_and_enqueue(segs, pred, succ, sx, eq, seen_pairs, n);
6386 status.erase(idx);
6387 }
6388 }
6389 else // INTERSECTION
6390 {
6391 const size_t a = ev.seg_a;
6392 const size_t b = ev.seg_b;
6393 append_intersection(a, b, ev.pt);
6394
6395 // Walk outward from a (or b) through the status tree to find
6396 // all active segments passing through ev.pt. Segments sharing
6397 // a point are adjacent in sweep-line order, so this is O(k)
6398 // instead of the former O(n) scan.
6399 Array<size_t> block;
6400
6401 // Pick a seed segment that is still in the status tree
6402 size_t seed = SIZE_MAX;
6403 if (status.contains(a) and segs(a).contains(ev.pt))
6404 seed = a;
6405 else if (status.contains(b) and segs(b).contains(ev.pt))
6406 seed = b;
6407
6408 if (seed != SIZE_MAX)
6409 {
6410 block.append(seed);
6411
6412 // Walk predecessors
6413 for (size_t p = status.predecessor(seed); p != SIZE_MAX and segs(p).contains(ev.pt);
6414 p = status.predecessor(p))
6415 block.append(p);
6416
6417 // Walk successors
6418 for (size_t s = status.successor(seed); s != SIZE_MAX and segs(s).contains(ev.pt);
6419 s = status.successor(s))
6420 block.append(s);
6421 }
6422
6423 if (block.size() > 2)
6424 {
6425 for (size_t i = 0; i + 1 < block.size(); ++i)
6426 for (size_t j = i + 1; j < block.size(); ++j)
6427 append_intersection(block(i), block(j), ev.pt);
6428 }
6429
6430 if (block.size() == 0)
6431 {
6432 if (status.contains(a))
6433 block.append(a);
6434 if (status.contains(b) and (block.size() == 0 or block(0) != b))
6435 block.append(b);
6436 }
6437
6438 for (size_t i = 0; i < block.size(); ++i)
6439 status.erase(block(i));
6440 for (size_t i = 0; i < block.size(); ++i)
6441 status.insert(block(i), sx);
6442
6443 for (size_t i = 0; i < block.size(); ++i)
6444 {
6445 const size_t s = block(i);
6446 if (const size_t pred = status.predecessor(s); pred != SIZE_MAX)
6447 check_and_enqueue(segs, pred, s, sx, eq, seen_pairs, n);
6448 if (const size_t succ = status.successor(s); succ != SIZE_MAX)
6449 check_and_enqueue(segs, s, succ, sx, eq, seen_pairs, n);
6450 }
6451 }
6452 }
6453
6454 // Sort results by (x, y).
6455 quicksort_op(result, [](const Intersection &a, const Intersection &b)
6456 {
6457 if (a.point.get_x() != b.point.get_x())
6458 return a.point.get_x() < b.point.get_x();
6459 if (a.point.get_y() != b.point.get_y())
6460 return a.point.get_y() < b.point.get_y();
6461 if (a.seg_i != b.seg_i)
6462 return a.seg_i < b.seg_i;
6463 return a.seg_j < b.seg_j;
6464 });
6465
6466 return result;
6467 }
6468};
6469
6470// ============================================================================
6471// Monotone Polygon Triangulation
6472// ============================================================================
6473
6499{
6500private:
6502 {
6504 }
6505
6510
6511 static void ensure_ccw(Array<Point> &v)
6512 {
6514 }
6515
6519 enum class VertexType
6520 {
6521 START,
6522 END,
6523 SPLIT,
6524 MERGE,
6525 REGULAR
6526 };
6527
6531 [[nodiscard]] static bool is_above(const Point &a, const Point &b)
6532 {
6533 return a.get_y() > b.get_y() or (a.get_y() == b.get_y() and a.get_x() < b.get_x());
6534 }
6535
6539 [[nodiscard]] static bool is_below(const Point &a, const Point &b)
6540 {
6541 return is_above(b, a);
6542 }
6543
6547 [[nodiscard]] static bool edge_goes_down(const Array<Point> &verts, const size_t edge)
6548 {
6549 return is_below(verts((edge + 1) % verts.size()), verts(edge));
6550 }
6551
6553 const size_t edge,
6554 const Geom_Number &y)
6555 {
6556 const Point &a = verts(edge);
6557 const Point &b = verts((edge + 1) % verts.size());
6558 if (a.get_y() == b.get_y())
6559 return a.get_x() < b.get_x() ? a.get_x() : b.get_x();
6560
6561 return a.get_x() + (y - a.get_y()) * (b.get_x() - a.get_x()) / (b.get_y() - a.get_y());
6562 }
6563
6564 [[nodiscard]] static bool edge_status_less(const size_t lhs,
6565 const size_t rhs,
6566 const Geom_Number &sweep_y,
6567 const Array<Point> &verts)
6568 {
6569 if (lhs == rhs)
6570 return false;
6571
6572 const Geom_Number xl = edge_x_at_y(verts, lhs, sweep_y);
6573 const Geom_Number xr = edge_x_at_y(verts, rhs, sweep_y);
6574 if (xl != xr)
6575 return xl < xr;
6576 return lhs < rhs;
6577 }
6578
6580 {
6581 struct Node
6582 {
6583 size_t edge;
6585 unsigned priority;
6586 Node *left = nullptr;
6587 Node *right = nullptr;
6588 };
6589
6590 Node *root_ = nullptr;
6592 std::mt19937 rng_{0x31415926u};
6594
6595 static void split_by_label(Node *root, const Geom_Number &label, Node *&left, Node *&right)
6596 {
6597 if (root == nullptr)
6598 {
6599 left = nullptr;
6600 right = nullptr;
6601 return;
6602 }
6603
6604 if (root->label < label)
6605 {
6606 split_by_label(root->right, label, root->right, right);
6607 left = root;
6608 }
6609 else
6610 {
6611 split_by_label(root->left, label, left, root->left);
6612 right = root;
6613 }
6614 }
6615
6616 [[nodiscard]] static Node *merge(Node *a, Node *b)
6617 {
6618 if (a == nullptr)
6619 return b;
6620 if (b == nullptr)
6621 return a;
6622 if (a->priority < b->priority)
6623 {
6624 a->right = merge(a->right, b);
6625 return a;
6626 }
6627 b->left = merge(a, b->left);
6628 return b;
6629 }
6630
6632 {
6633 if (root == nullptr)
6634 return node;
6635
6636 if (node->priority < root->priority)
6637 {
6638 Node *left = nullptr;
6639 Node *right = nullptr;
6640 split_by_label(root, node->label, left, right);
6641 node->left = left;
6642 node->right = right;
6643 return node;
6644 }
6645
6646 if (node->label < root->label)
6647 root->left = insert_by_label(root->left, node);
6648 else
6649 root->right = insert_by_label(root->right, node);
6650 return root;
6651 }
6652
6654 {
6655 if (root == nullptr)
6656 return nullptr;
6657
6658 if (label < root->label)
6659 {
6660 root->left = erase_by_label(root->left, label, removed);
6661 return root;
6662 }
6663
6664 if (root->label < label)
6665 {
6666 root->right = erase_by_label(root->right, label, removed);
6667 return root;
6668 }
6669
6670 removed = root;
6671 return merge(root->left, root->right);
6672 }
6673
6674 static void destroy(const Node *n)
6675 {
6676 if (n == nullptr)
6677 return;
6678 destroy(n->left);
6679 destroy(n->right);
6680 delete n;
6681 }
6682
6683 [[nodiscard]] size_t predecessor_for_insert(const size_t edge, const Geom_Number &sweep_y) const
6684 {
6685 size_t pred = SIZE_MAX;
6686 const Node *cur = root_;
6687 while (cur != nullptr)
6688 if (edge_status_less(cur->edge, edge, sweep_y, verts_))
6689 {
6690 pred = cur->edge;
6691 cur = cur->right;
6692 }
6693 else
6694 cur = cur->left;
6695 return pred;
6696 }
6697
6698 [[nodiscard]] size_t successor_for_insert(const size_t edge, const Geom_Number &sweep_y) const
6699 {
6700 size_t succ = SIZE_MAX;
6701 const Node *cur = root_;
6702 while (cur != nullptr)
6703 if (edge_status_less(edge, cur->edge, sweep_y, verts_))
6704 {
6705 succ = cur->edge;
6706 cur = cur->left;
6707 }
6708 else
6709 cur = cur->right;
6710 return succ;
6711 }
6712
6713 [[nodiscard]] Geom_Number fresh_label_between(const size_t pred, const size_t succ) const
6714 {
6715 if (pred != SIZE_MAX and succ != SIZE_MAX)
6716 return (nodes_(pred)->label + nodes_(succ)->label) / Geom_Number(2);
6717 if (pred != SIZE_MAX)
6718 return nodes_(pred)->label + Geom_Number(1);
6719 if (succ != SIZE_MAX)
6720 return nodes_(succ)->label - Geom_Number(1);
6721 return {0};
6722 }
6723
6724 public:
6726 {
6727 nodes_.reserve(verts.size());
6728 for (size_t i = 0; i < verts.size(); ++i)
6729 nodes_.append(nullptr);
6730 }
6731
6733 {
6734 destroy(root_);
6735 }
6736
6737 [[nodiscard]] bool contains(const size_t edge) const
6738 {
6739 return nodes_(edge) != nullptr;
6740 }
6741
6742 void insert(const size_t edge, const Geom_Number &sweep_y)
6743 {
6744 if (contains(edge))
6745 return;
6746
6747 const size_t pred = predecessor_for_insert(edge, sweep_y);
6748 const size_t succ = successor_for_insert(edge, sweep_y);
6749 const auto node = new Node{edge, fresh_label_between(pred, succ),
6750 static_cast<unsigned>(rng_()), nullptr, nullptr};
6751 root_ = insert_by_label(root_, node);
6752 nodes_(edge) = node;
6753 }
6754
6755 void erase(const size_t edge)
6756 {
6757 if (not contains(edge))
6758 return;
6759
6760 Node *removed = nullptr;
6761 root_ = erase_by_label(root_, nodes_(edge)->label, removed);
6762 nodes_(edge) = nullptr;
6763 delete removed;
6764 }
6765
6766 [[nodiscard]] size_t left_edge_of_point(const Point &p, const Geom_Number &sweep_y) const
6767 {
6768 size_t best = SIZE_MAX;
6769 const Node *cur = root_;
6770 while (cur != nullptr)
6771 {
6772 const Geom_Number x = edge_x_at_y(verts_, cur->edge, sweep_y);
6773 if (x < p.get_x())
6774 {
6775 best = cur->edge;
6776 cur = cur->right;
6777 }
6778 else
6779 cur = cur->left;
6780 }
6781 return best;
6782 }
6783 };
6784
6787 {
6788 DynList<Triangle> result;
6789 const size_t n = verts.size();
6790 if (n < 3)
6791 return result;
6792 if (n == 3)
6793 {
6794 result.append(Triangle(verts(0), verts(1), verts(2)));
6795 return result;
6796 }
6797
6798 // Sort vertex indices by y descending, x ascending for ties.
6800 sorted.reserve(n);
6801 for (size_t i = 0; i < n; ++i)
6802 sorted.append(i);
6803
6804 quicksort_op(sorted, [&verts](const size_t a, const size_t b)
6805 {
6806 if (verts(a).get_y() != verts(b).get_y())
6807 return verts(a).get_y() > verts(b).get_y();
6808 return verts(a).get_x() < verts(b).get_x();
6809 });
6810
6811 // Identify left and right chains.
6812 // Top vertex = sorted(0), bottom vertex = sorted(n-1).
6813 // Left chain: goes from top to bottom counter-clockwise.
6814 const size_t top = sorted(0);
6815 const size_t bot = sorted(n - 1);
6816
6818 on_left.reserve(n);
6819 for (size_t i = 0; i < n; ++i)
6820 on_left.append(false);
6821
6822 // Walk from top to bottom going forward in the polygon.
6823 for (size_t i = top; i != bot; i = (i + 1) % n)
6824 on_left(i) = true;
6825 on_left(top) = true; // top is on both; mark left.
6826
6827 // Stack-based triangulation.
6828 FixedStack<size_t> stack(n);
6829 stack.push(sorted(0));
6830 stack.push(sorted(1));
6831
6832 for (size_t i = 2; i < n - 1; ++i)
6833 {
6834 const size_t curr = sorted(i);
6835 const size_t stk_top = stack.top();
6836 if (on_left(curr) != on_left(stk_top))
6837 {
6838 // Different chain: fan triangles from curr to all stack vertices.
6839 while (stack.size() > 1)
6840 {
6841 const size_t a = stack.top();
6842 const size_t b = stack.top(1);
6843 result.append(Triangle(verts(curr), verts(a), verts(b)));
6844 stack.pop();
6845 }
6846 stack.pop();
6847 stack.push(sorted(i - 1));
6848 stack.push(curr);
6849 }
6850 else
6851 {
6852 // Same chain: pop vertices while diagonal is inside.
6853 size_t last_popped = stack.top();
6854 stack.pop();
6855
6856 while (not stack.is_empty())
6857 {
6858 const size_t peek = stack.top();
6859 const Geom_Number area =
6861
6862 // On the left chain we need CW turn (area < 0);
6863 // on the right chain we need CCW turn (area > 0).
6864 if ((on_left(curr) and area >= 0) or (not on_left(curr) and area <= 0))
6865 break;
6866
6867 result.append(Triangle(verts(curr), verts(last_popped), verts(peek)));
6868 last_popped = peek;
6869 stack.pop();
6870 }
6871
6872 stack.push(last_popped);
6873 stack.push(curr);
6874 }
6875 }
6876
6877 // Process last vertex (bottom): fan to the remaining stack.
6878 const size_t last = sorted(n - 1);
6879 while (stack.size() > 1)
6880 {
6881 const size_t a = stack.top();
6882 const size_t b = stack.top(1);
6883 result.append(Triangle(verts(last), verts(a), verts(b)));
6884 stack.pop();
6885 }
6886
6887 return result;
6888 }
6889
6890 [[nodiscard]] static VertexType classify_vertex(const Array<Point> &v, const size_t i)
6891 {
6892 const size_t n = v.size();
6893 const Point &prev = v((i + n - 1) % n);
6894 const Point &curr = v(i);
6895 const Point &next = v((i + 1) % n);
6896
6897 const bool prev_below = is_below(prev, curr);
6898 const bool next_below = is_below(next, curr);
6899 const bool prev_above = is_above(prev, curr);
6900 const bool next_above = is_above(next, curr);
6901 const bool reflex = orientation(prev, curr, next) == Orientation::CW;
6902
6907 return VertexType::REGULAR;
6908 }
6909
6910 [[nodiscard]] static bool regular_interior_right(const Array<Point> &v, const size_t i)
6911 {
6912 return is_below(v((i + 1) % v.size()), v(i));
6913 }
6914
6916 const Array<Point> &verts,
6917 const DynSetTree<std::pair<size_t, size_t>> &diagonals)
6918 {
6919 const size_t n = verts.size();
6921 adj.reserve(n);
6922 for (size_t i = 0; i < n; ++i)
6923 adj.append(Array<size_t>());
6924
6925 auto add_undirected_edge = [&adj](const size_t a, const size_t b)
6926 {
6927 adj(a).append(b);
6928 adj(b).append(a);
6929 };
6930
6931 for (size_t i = 0; i < n; ++i)
6932 add_undirected_edge(i, (i + 1) % n);
6933
6934 for (const auto &d : diagonals)
6935 add_undirected_edge(d.first, d.second);
6936
6937 for (size_t v = 0; v < n; ++v)
6938 {
6939 auto &nbrs = adj(v);
6942 in_place_sort(nbrs, [&verts, v](const size_t a, const size_t b)
6943 {
6944 const Geom_Number dax = verts(a).get_x() - verts(v).get_x();
6945 const Geom_Number day = verts(a).get_y() - verts(v).get_y();
6946 const Geom_Number dbx = verts(b).get_x() - verts(v).get_x();
6947 const Geom_Number dby = verts(b).get_y() - verts(v).get_y();
6948
6949 const bool upper_a = day > 0 or (day == 0 and dax >= 0);
6950 const bool upper_b = dby > 0 or (dby == 0 and dbx >= 0);
6951 if (upper_a != upper_b)
6952 return upper_a > upper_b;
6953
6954 if (const Geom_Number cross = dax * dby - day * dbx; cross != 0)
6955 return cross > 0;
6956
6957 const Geom_Number da2 = dax * dax + day * day;
6958 const Geom_Number db2 = dbx * dbx + dby * dby;
6959 return da2 < db2;
6960 });
6961 }
6962
6963 using HalfEdge = std::pair<size_t, size_t>;
6964 std::map<HalfEdge, HalfEdge> next_half;
6965 for (size_t v = 0; v < n; ++v)
6966 {
6967 const auto &nbrs = adj(v);
6968 if (nbrs.is_empty())
6969 continue;
6970 for (size_t k = 0; k < nbrs.size(); ++k)
6971 {
6972 const size_t u = nbrs(k);
6973 const size_t pred = nbrs((k + nbrs.size() - 1) % nbrs.size());
6974 next_half[HalfEdge{u, v}] = HalfEdge{v, pred};
6975 }
6976 }
6977
6978 auto face_area2 = [&verts](const Array<size_t> &face)
6979 {
6980 Geom_Number area2 = 0;
6981 for (size_t i = 0; i < face.size(); ++i)
6982 {
6983 const Point &a = verts(face(i));
6984 const Point &b = verts(face((i + 1) % face.size()));
6985 area2 += a.get_x() * b.get_y() - a.get_y() * b.get_x();
6986 }
6987 return area2;
6988 };
6989
6990 DynSetTree<HalfEdge> visited;
6991 Array<Array<size_t>> faces;
6992 for (const auto &[kv, _] : next_half)
6993 {
6994 const HalfEdge start = kv;
6995 if (visited.search(start) != nullptr)
6996 continue;
6997
6998 HalfEdge cur = start;
6999 Array<size_t> face;
7000 while (visited.insert(cur) != nullptr)
7001 {
7002 face.append(cur.first);
7003 auto it_next = next_half.find(cur);
7004 if (it_next == next_half.end())
7005 {
7006 face.empty();
7007 break;
7008 }
7009 cur = it_next->second;
7010 if (cur == start)
7011 break;
7012 }
7013
7014 if (face.is_empty() or cur != start or face.size() < 3)
7015 continue;
7016 if (face_area2(face) <= 0)
7017 continue; // Ignore outer face / degenerate loops.
7018
7020 poly_idx.reserve(face.size());
7021 for (const size_t idx : face)
7022 poly_idx.append(idx);
7023 faces.append(poly_idx);
7024 }
7025
7026 return faces;
7027 }
7028
7030 {
7031 const size_t n = verts.size();
7032 Array<Array<size_t>> faces;
7033
7034 if (n < 3)
7035 return faces;
7036
7037 if (is_y_monotone(verts))
7038 {
7039 Array<size_t> one;
7040 one.reserve(n);
7041 for (size_t i = 0; i < n; ++i)
7042 one.append(i);
7043 faces.append(one);
7044 return faces;
7045 }
7046
7048 vtype.reserve(n);
7049 for (size_t i = 0; i < n; ++i)
7050 vtype.append(classify_vertex(verts, i));
7051
7052 Array<size_t> order;
7053 order.reserve(n);
7054 for (size_t i = 0; i < n; ++i)
7055 order.append(i);
7056 quicksort_op(order, [&verts](const size_t a, const size_t b)
7057 {
7058 if (verts(a).get_y() != verts(b).get_y())
7059 return verts(a).get_y() > verts(b).get_y();
7060 return verts(a).get_x() < verts(b).get_x();
7061 });
7062
7064 helper.reserve(n);
7065 for (size_t i = 0; i < n; ++i)
7066 helper.append(SIZE_MAX);
7067
7068 EdgeStatusTree status(verts);
7070
7071 auto add_diagonal = [&](const size_t a, const size_t b)
7072 {
7073 if (a == b or b == SIZE_MAX)
7074 return;
7075 if ((a + 1) % n == b or (b + 1) % n == a)
7076 return;
7077
7078 const size_t lo = a < b ? a : b;
7079 const size_t hi = a < b ? b : a;
7080 diagonals.insert({lo, hi});
7081 };
7082
7083 auto helper_is_merge = [&helper, &vtype](const size_t edge)
7084 {
7085 return helper(edge) != SIZE_MAX and vtype(helper(edge)) == VertexType::MERGE;
7086 };
7087
7088 for (size_t oi = 0; oi < n; ++oi)
7089 {
7090 const size_t vi = order(oi);
7091 const size_t prev = (vi + n - 1) % n;
7092 const size_t e_prev = prev; // edge prev -> vi
7093 const size_t e_curr = vi; // edge vi -> next
7094 const Geom_Number sweep_y = verts(vi).get_y();
7095
7096 switch (vtype(vi))
7097 {
7098 case VertexType::START:
7100 {
7101 status.insert(e_curr, sweep_y);
7102 helper(e_curr) = vi;
7103 }
7104 break;
7105
7106 case VertexType::END:
7108 {
7111 status.erase(e_prev);
7112 }
7113 break;
7114
7115 case VertexType::SPLIT:
7116 {
7117 if (const size_t left = status.left_edge_of_point(verts(vi), sweep_y); left != SIZE_MAX)
7118 {
7119 add_diagonal(vi, helper(left));
7120 helper(left) = vi;
7121 }
7122
7124 {
7125 status.insert(e_curr, sweep_y);
7126 helper(e_curr) = vi;
7127 }
7128 }
7129 break;
7130
7131 case VertexType::MERGE:
7133 {
7136 status.erase(e_prev);
7137 }
7138 if (const size_t left = status.left_edge_of_point(verts(vi), sweep_y); left != SIZE_MAX)
7139 {
7140 if (helper_is_merge(left))
7141 add_diagonal(vi, helper(left));
7142 helper(left) = vi;
7143 }
7144 break;
7145
7148 {
7150 {
7153 status.erase(e_prev);
7154 }
7156 {
7157 status.insert(e_curr, sweep_y);
7158 helper(e_curr) = vi;
7159 }
7160 }
7161 else if (const size_t left = status.left_edge_of_point(verts(vi), sweep_y);
7162 left != SIZE_MAX)
7163 {
7164 if (helper_is_merge(left))
7165 add_diagonal(vi, helper(left));
7166 helper(left) = vi;
7167 }
7168 break;
7169 }
7170 }
7171
7173 if (faces.size() == 0)
7174 {
7175 Array<size_t> one;
7176 one.reserve(n);
7177 for (size_t i = 0; i < n; ++i)
7178 one.append(i);
7179 faces.append(one);
7180 }
7181 return faces;
7182 }
7183
7184public:
7203 {
7204 ah_domain_error_if(not p.is_closed()) << "Polygon must be closed";
7205 ah_domain_error_if(p.size() < 3) << "Polygon must have at least 3 vertices";
7206
7208 ah_domain_error_if(signed_double_area(verts) == 0) << "Polygon is degenerate (zero area)";
7209
7211
7212 // Check if the polygon is y-monotone.
7213 if (is_y_monotone(verts))
7215
7217 DynList<Triangle> result;
7218 bool produced_any = false;
7219
7220 for (size_t fi = 0; fi < faces.size(); ++fi)
7221 {
7222 if (faces(fi).size() < 3)
7223 continue;
7224
7226 mono.reserve(faces(fi).size());
7227 for (size_t i = 0; i < faces(fi).size(); ++i)
7228 mono.append(verts(faces(fi)(i)));
7229
7230 if (signed_double_area(mono) == 0)
7231 continue;
7233
7235 if (is_y_monotone(mono))
7237 else
7238 {
7239 // Defensive fallback for degenerate decomposition edge cases.
7240 Polygon piece;
7241 for (size_t i = 0; i < mono.size(); ++i)
7242 piece.add_vertex(mono(i));
7243 if (piece.size() >= 3)
7244 {
7245 piece.close();
7247 tris = ears(piece);
7248 }
7249 }
7250
7251 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
7252 {
7253 result.append(it.get_curr());
7254 produced_any = true;
7255 }
7256 }
7257
7258 if (produced_any)
7259 return result;
7260
7261 // Conservative fallback if decomposition could not produce valid faces.
7263 return ears(p);
7264 }
7265
7266private:
7268 [[nodiscard]] static bool is_y_monotone(const Array<Point> &v)
7269 {
7270 const size_t n = v.size();
7271 if (n <= 3)
7272 return true;
7273
7274 // Find topmost and bottommost vertices.
7275 size_t top_idx = 0;
7276 size_t bot_idx = 0;
7277 for (size_t i = 1; i < n; ++i)
7278 {
7279 if (v(i).get_y() > v(top_idx).get_y() or
7280 (v(i).get_y() == v(top_idx).get_y() and v(i).get_x() < v(top_idx).get_x()))
7281 top_idx = i;
7282
7283 if (v(i).get_y() < v(bot_idx).get_y() or
7284 (v(i).get_y() == v(bot_idx).get_y() and v(i).get_x() > v(bot_idx).get_x()))
7285 bot_idx = i;
7286 }
7287
7288 // Walk from top to bottom along the forward chain — y must be
7289 // non-increasing.
7290 for (size_t i = top_idx;;)
7291 {
7292 const size_t next = (i + 1) % n;
7293 if (next == bot_idx)
7294 break;
7295 if (v(next).get_y() > v(i).get_y())
7296 return false;
7297 i = next;
7298 }
7299
7300 // Walk from top to bottom along the backward chain — y must be
7301 // non-increasing.
7302 for (size_t i = top_idx;;)
7303 {
7304 const size_t prev = (i + n - 1) % n;
7305 if (prev == bot_idx)
7306 break;
7307 if (v(prev).get_y() > v(i).get_y())
7308 return false;
7309 i = prev;
7310 }
7311
7312 return true;
7313 }
7314};
7315
7316// ============================================================================
7317// Minkowski Sum for Convex Polygons — O(n + m)
7318// ============================================================================
7319
7353{
7354private:
7356 {
7358 }
7359
7364
7365 [[nodiscard]] static bool is_convex(const Array<Point> &verts)
7366 {
7367 if (verts.size() < 3)
7368 return false;
7370 }
7371
7374 {
7376
7377 // Find bottom-most vertex (min y, then min x).
7378 size_t bot = 0;
7379 for (size_t i = 1; i < v.size(); ++i)
7380 if (v(i).get_y() < v(bot).get_y() or
7381 (v(i).get_y() == v(bot).get_y() and v(i).get_x() < v(bot).get_x()))
7382 bot = i;
7383
7384 Array<Point> result;
7385 result.reserve(v.size());
7386 for (size_t i = 0; i < v.size(); ++i)
7387 result.append(v((bot + i) % v.size()));
7388
7389 return result;
7390 }
7391
7392 [[nodiscard]] static Point edge_vec(const Array<Point> &v, const size_t i)
7393 {
7394 const size_t j = (i + 1) % v.size();
7395 return {v(j).get_x() - v(i).get_x(), v(j).get_y() - v(i).get_y()};
7396 }
7397
7398 [[nodiscard]] static Geom_Number cross(const Point &a, const Point &b)
7399 {
7400 return a.get_x() * b.get_y() - a.get_y() * b.get_x();
7401 }
7402
7403public:
7414 [[nodiscard]] Polygon operator () (const Polygon &P, const Polygon &Q) const
7415 {
7416 ah_domain_error_if(not P.is_closed()) << "First polygon must be closed";
7417 ah_domain_error_if(not Q.is_closed()) << "Second polygon must be closed";
7418 ah_domain_error_if(P.size() < 3) << "First polygon must have at least 3 vertices";
7419 ah_domain_error_if(Q.size() < 3) << "Second polygon must have at least 3 vertices";
7420
7423
7424 ah_domain_error_if(not is_convex(pv)) << "First polygon must be convex";
7425 ah_domain_error_if(not is_convex(qv)) << "Second polygon must be convex";
7426
7427 pv = normalize(pv);
7428 qv = normalize(qv);
7429
7430 const size_t np = pv.size();
7431 const size_t nq = qv.size();
7432
7433 Array<Point> result;
7434 result.reserve(np + nq);
7435
7436 size_t ip = 0;
7437 size_t iq = 0;
7438
7439 while (ip < np or iq < nq)
7440 {
7441 result.append(Point(pv(ip % np).get_x() + qv(iq % nq).get_x(),
7442 pv(ip % np).get_y() + qv(iq % nq).get_y()));
7443
7444 if (ip >= np)
7445 {
7446 ++iq;
7447 continue;
7448 }
7449 if (iq >= nq)
7450 {
7451 ++ip;
7452 continue;
7453 }
7454
7455 const Point ep = edge_vec(pv, ip % np);
7456 const Point eq = edge_vec(qv, iq % nq);
7457
7458 if (const Geom_Number cr = cross(ep, eq); cr > 0)
7459 ++ip;
7460 else if (cr < 0)
7461 ++iq;
7462 else
7463 {
7464 ++ip;
7465 ++iq;
7466 } // Parallel edges: advance both.
7467 }
7468
7469 // Remove collinear and duplicate vertices.
7471 clean.reserve(result.size());
7472 for (size_t i = 0; i < result.size(); ++i)
7473 {
7474 if (clean.is_empty() or clean.get_last() != result(i))
7475 clean.append(result(i));
7476 }
7477 if (clean.size() > 1 and clean(0) == clean.get_last())
7478 static_cast<void>(clean.remove_last());
7479
7480 Polygon ret;
7481 for (size_t i = 0; i < clean.size(); ++i)
7482 ret.add_vertex(clean(i));
7483
7484 if (ret.size() >= 3)
7485 ret.close();
7486
7487 return ret;
7488 }
7489};
7490
7491// ============================================================================
7492// Convex Polygon Distance (GJK) — 2D
7493// ============================================================================
7494
7512{
7513public:
7526
7527private:
7532 {
7536 double vx = 0.0;
7537 double vy = 0.0;
7538 };
7539
7541 {
7542 std::vector<SupportPoint> reduced_simplex;
7543 double cx = 0.0;
7544 double cy = 0.0;
7547 bool contains_origin = false;
7548 };
7549
7550 static constexpr size_t kMaxIters = 64;
7551 static constexpr double kEps = 1e-12;
7552
7553 [[nodiscard]] static double to_double(const Geom_Number &v)
7554 {
7555 return geom_number_to_double(v);
7556 }
7557
7558 [[nodiscard]] static double dot2(const double ax,
7559 const double ay,
7560 const double bx,
7561 const double by) noexcept
7562 {
7563 return ax * bx + ay * by;
7564 }
7565
7566 [[nodiscard]] static double norm2(const double x, const double y) noexcept
7567 {
7568 return dot2(x, y, x, y);
7569 }
7570
7571 [[nodiscard]] static Point point_from_double(const double x, const double y)
7572 {
7573 return {Geom_Number(x), Geom_Number(y)};
7574 }
7575
7576 [[nodiscard]] static Point lerp_point(const Point &a, const Point &b, const double t)
7577 {
7578 const double ax = to_double(a.get_x());
7579 const double ay = to_double(a.get_y());
7580 const double bx = to_double(b.get_x());
7581 const double by = to_double(b.get_y());
7582 return point_from_double((1.0 - t) * ax + t * bx, (1.0 - t) * ay + t * by);
7583 }
7584
7586 {
7587 Geom_Number sx = 0;
7588 Geom_Number sy = 0;
7589 for (size_t i = 0; i < v.size(); ++i)
7590 {
7591 sx += v(i).get_x();
7592 sy += v(i).get_y();
7593 }
7594 return {sx / Geom_Number(static_cast<long>(v.size())),
7595 sy / Geom_Number(static_cast<long>(v.size()))};
7596 }
7597
7599 const Array<Point> &q,
7600 const double dx,
7601 const double dy)
7602 {
7603 size_t ip = 0;
7604 size_t iq = 0;
7605 double best_p = -std::numeric_limits<double>::infinity();
7606 double best_q = -std::numeric_limits<double>::infinity();
7607
7608 for (size_t i = 0; i < p.size(); ++i)
7609 {
7610 const double proj = dot2(to_double(p(i).get_x()), to_double(p(i).get_y()), dx, dy);
7611 if (proj > best_p)
7612 {
7613 best_p = proj;
7614 ip = i;
7615 }
7616 }
7617
7618 // Support of Minkowski difference uses opposite direction in q.
7619 for (size_t i = 0; i < q.size(); ++i)
7620 {
7621 const double proj = dot2(to_double(q(i).get_x()), to_double(q(i).get_y()), -dx, -dy);
7622 if (proj > best_q)
7623 {
7624 best_q = proj;
7625 iq = i;
7626 }
7627 }
7628
7629 const Point &pa = p(ip);
7630 const Point &pb = q(iq);
7631 const Geom_Number vx = pa.get_x() - pb.get_x();
7632 const Geom_Number vy = pa.get_y() - pb.get_y();
7633 return SupportPoint{pa, pb, Point(vx, vy), to_double(vx), to_double(vy)};
7634 }
7635
7636 [[nodiscard]] static bool polygons_intersect_or_contain(const Polygon &p, const Polygon &q)
7637 {
7638 for (Polygon::Segment_Iterator itp(p); itp.has_curr(); itp.next_ne())
7639 {
7640 const Segment sp = itp.get_current_segment();
7641 for (Polygon::Segment_Iterator itq(q); itq.has_curr(); itq.next_ne())
7642 if (sp.intersects_with(itq.get_current_segment()))
7643 return true;
7644 }
7645
7646 Point vp;
7647 for (Polygon::Vertex_Iterator it(p); it.has_curr(); it.next_ne())
7648 {
7649 vp = it.get_current_vertex().to_point();
7650 break;
7651 }
7652
7653 Point vq;
7654 for (Polygon::Vertex_Iterator it(q); it.has_curr(); it.next_ne())
7655 {
7656 vq = it.get_current_vertex().to_point();
7657 break;
7658 }
7659
7661 }
7662
7663 [[nodiscard]] static ClosestSimplexResult closest_from_simplex(const std::vector<SupportPoint> &simplex)
7664 {
7665 ah_domain_error_if(simplex.empty()) << "GJK simplex cannot be empty";
7666
7668
7669 if (simplex.size() == 1)
7670 {
7672 out.cx = simplex[0].vx;
7673 out.cy = simplex[0].vy;
7674 out.witness_a = simplex[0].a;
7675 out.witness_b = simplex[0].b;
7676 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7677 return out;
7678 }
7679
7680 if (simplex.size() == 2)
7681 {
7682 const SupportPoint &a = simplex[0];
7683 const SupportPoint &b = simplex[1];
7684 const double abx = b.vx - a.vx;
7685 const double aby = b.vy - a.vy;
7686 const double ab2 = norm2(abx, aby);
7687
7688 if (ab2 <= kEps * kEps)
7689 {
7690 out.reduced_simplex = {a};
7691 out.cx = a.vx;
7692 out.cy = a.vy;
7693 out.witness_a = a.a;
7694 out.witness_b = a.b;
7695 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7696 return out;
7697 }
7698
7699 double t = -dot2(a.vx, a.vy, abx, aby) / ab2;
7700 if (t < 0.0)
7701 t = 0.0;
7702 if (t > 1.0)
7703 t = 1.0;
7704
7705 if (t <= kEps)
7706 {
7707 out.reduced_simplex = {a};
7708 out.cx = a.vx;
7709 out.cy = a.vy;
7710 out.witness_a = a.a;
7711 out.witness_b = a.b;
7712 }
7713 else if (t >= 1.0 - kEps)
7714 {
7715 out.reduced_simplex = {b};
7716 out.cx = b.vx;
7717 out.cy = b.vy;
7718 out.witness_a = b.a;
7719 out.witness_b = b.b;
7720 }
7721 else
7722 {
7723 out.reduced_simplex = {a, b};
7724 out.cx = a.vx + t * abx;
7725 out.cy = a.vy + t * aby;
7726 out.witness_a = lerp_point(a.a, b.a, t);
7727 out.witness_b = lerp_point(a.b, b.b, t);
7728 }
7729
7730 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7731 return out;
7732 }
7733
7734 // Triangle case.
7735 const SupportPoint &a = simplex[0];
7736 const SupportPoint &b = simplex[1];
7737 const SupportPoint &c = simplex[2];
7738
7739 const double abx = b.vx - a.vx;
7740 const double aby = b.vy - a.vy;
7741 const double acx = c.vx - a.vx;
7742 const double acy = c.vy - a.vy;
7743 const double apx = -a.vx;
7744 const double apy = -a.vy;
7745
7746 const double d1 = dot2(abx, aby, apx, apy);
7747 const double d2 = dot2(acx, acy, apx, apy);
7748 if (d1 <= 0.0 and d2 <= 0.0)
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 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7756 return out;
7757 }
7758
7759 const double bpx = -b.vx;
7760 const double bpy = -b.vy;
7761 const double d3 = dot2(abx, aby, bpx, bpy);
7762 const double d4 = dot2(acx, acy, bpx, bpy);
7763 if (d3 >= 0.0 and d4 <= d3)
7764 {
7765 out.reduced_simplex = {b};
7766 out.cx = b.vx;
7767 out.cy = b.vy;
7768 out.witness_a = b.a;
7769 out.witness_b = b.b;
7770 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7771 return out;
7772 }
7773
7774 const double vc = d1 * d4 - d3 * d2;
7775 if (vc <= 0.0 and d1 >= 0.0 and d3 <= 0.0)
7776 {
7777 const double t = d1 / (d1 - d3);
7778 out.reduced_simplex = {a, b};
7779 out.cx = a.vx + t * abx;
7780 out.cy = a.vy + t * aby;
7781 out.witness_a = lerp_point(a.a, b.a, t);
7782 out.witness_b = lerp_point(a.b, b.b, t);
7783 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7784 return out;
7785 }
7786
7787 const double cpx = -c.vx;
7788 const double cpy = -c.vy;
7789 const double d5 = dot2(abx, aby, cpx, cpy);
7790 const double d6 = dot2(acx, acy, cpx, cpy);
7791 if (d6 >= 0.0 and d5 <= d6)
7792 {
7793 out.reduced_simplex = {c};
7794 out.cx = c.vx;
7795 out.cy = c.vy;
7796 out.witness_a = c.a;
7797 out.witness_b = c.b;
7798 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7799 return out;
7800 }
7801
7802 const double vb = d5 * d2 - d1 * d6;
7803 if (vb <= 0.0 and d2 >= 0.0 and d6 <= 0.0)
7804 {
7805 const double t = d2 / (d2 - d6);
7806 out.reduced_simplex = {a, c};
7807 out.cx = a.vx + t * acx;
7808 out.cy = a.vy + t * acy;
7809 out.witness_a = lerp_point(a.a, c.a, t);
7810 out.witness_b = lerp_point(a.b, c.b, t);
7811 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7812 return out;
7813 }
7814
7815 const double va = d3 * d6 - d5 * d4;
7816 if (va <= 0.0 and d4 - d3 >= 0.0 and d5 - d6 >= 0.0)
7817 {
7818 const double t = (d4 - d3) / (d4 - d3 + (d5 - d6));
7819 const double bcx = c.vx - b.vx;
7820 const double bcy = c.vy - b.vy;
7821 out.reduced_simplex = {b, c};
7822 out.cx = b.vx + t * bcx;
7823 out.cy = b.vy + t * bcy;
7824 out.witness_a = lerp_point(b.a, c.a, t);
7825 out.witness_b = lerp_point(b.b, c.b, t);
7826 out.contains_origin = norm2(out.cx, out.cy) <= kEps * kEps;
7827 return out;
7828 }
7829
7830 // Origin is inside triangle.
7831 const double denom = va + vb + vc;
7832 if (std::fabs(denom) <= kEps)
7833 {
7834 out.reduced_simplex = {a, b, c};
7835 out.cx = 0.0;
7836 out.cy = 0.0;
7837 out.witness_a = a.a;
7838 out.witness_b = a.b;
7839 out.contains_origin = true;
7840 return out;
7841 }
7842
7843 const double inv = 1.0 / denom;
7844 const double wa = va * inv;
7845 const double wb = vb * inv;
7846 const double wc = vc * inv;
7847 out.reduced_simplex = {a, b, c};
7848 out.cx = 0.0;
7849 out.cy = 0.0;
7850 out.witness_a = point_from_double(
7851 wa * to_double(a.a.get_x()) + wb * to_double(b.a.get_x()) + wc * to_double(c.a.get_x()),
7852 wa * to_double(a.a.get_y()) + wb * to_double(b.a.get_y()) + wc * to_double(c.a.get_y()));
7853 out.witness_b = point_from_double(
7854 wa * to_double(a.b.get_x()) + wb * to_double(b.b.get_x()) + wc * to_double(c.b.get_x()),
7855 wa * to_double(a.b.get_y()) + wb * to_double(b.b.get_y()) + wc * to_double(c.b.get_y()));
7856 out.contains_origin = true;
7857 return out;
7858 }
7859
7861 const Polygon &q,
7862 const Array<Point> &pv,
7863 const Array<Point> &qv)
7864 {
7865 Result out;
7866 out.intersects = false;
7867
7869 {
7870 out.distance_squared = 0;
7871 out.distance = 0;
7872 out.closest_on_first = pv(0);
7873 out.closest_on_second = pv(0);
7874 out.intersects = true;
7875 return out;
7876 }
7877
7878 bool has_best = false;
7879 Geom_Number best_d2 = 0;
7881
7882 for (size_t i = 0; i < pv.size(); ++i)
7883 {
7884 const Point &pnt = pv(i);
7885 for (size_t j = 0; j < qv.size(); ++j)
7886 {
7887 const Point &q0 = qv(j);
7888 const Point &q1 = qv((j + 1) % qv.size());
7889 const Segment edge(q0, q1);
7890 const Point proj = edge.project(pnt);
7891 if (const Geom_Number d2 = pnt.distance_squared_to(proj); not has_best or d2 < best_d2)
7892 {
7893 has_best = true;
7894 best_d2 = d2;
7895 best_a = pnt;
7896 best_b = proj;
7897 }
7898 }
7899 }
7900
7901 for (size_t i = 0; i < qv.size(); ++i)
7902 {
7903 const Point &pnt = qv(i);
7904 for (size_t j = 0; j < pv.size(); ++j)
7905 {
7906 const Point &p0 = pv(j);
7907 const Point &p1 = pv((j + 1) % pv.size());
7908 const Segment edge(p0, p1);
7909 const Point proj = edge.project(pnt);
7910 if (const Geom_Number d2 = pnt.distance_squared_to(proj); not has_best or d2 < best_d2)
7911 {
7912 has_best = true;
7913 best_d2 = d2;
7914 best_a = proj;
7915 best_b = pnt;
7916 }
7917 }
7918 }
7919
7920 ah_domain_error_if(not has_best) << "Could not compute distance between convex polygons";
7921
7922 out.distance_squared = best_d2;
7923 out.distance = square_root(best_d2);
7924 out.closest_on_first = best_a;
7925 out.closest_on_second = best_b;
7926 out.intersects = (best_d2 == 0);
7927 return out;
7928 }
7929
7930public:
7937 [[nodiscard]] Result operator () (const Polygon &p, const Polygon &q) const
7938 {
7939 ah_domain_error_if(not p.is_closed()) << "First polygon must be closed";
7940 ah_domain_error_if(not q.is_closed()) << "Second polygon must be closed";
7941 ah_domain_error_if(p.size() < 3) << "First polygon must have at least 3 vertices";
7942 ah_domain_error_if(q.size() < 3) << "Second polygon must have at least 3 vertices";
7943
7946
7947 ah_domain_error_if(not GeomPolygonUtils::is_convex(pv)) << "First polygon must be convex";
7948 ah_domain_error_if(not GeomPolygonUtils::is_convex(qv)) << "Second polygon must be convex";
7949
7950 // Fast exact overlap check to avoid unnecessary iterations.
7952 {
7953 Result r;
7954 r.distance_squared = 0;
7955 r.distance = 0;
7956 r.closest_on_first = pv(0);
7957 r.closest_on_second = pv(0);
7958 r.intersects = true;
7959 return r;
7960 }
7961
7964 double dirx = to_double(cp.get_x() - cq.get_x());
7965 double diry = to_double(cp.get_y() - cq.get_y());
7966 if (norm2(dirx, diry) <= kEps * kEps)
7967 {
7968 dirx = 1.0;
7969 diry = 0.0;
7970 }
7971
7972 std::vector<SupportPoint> simplex;
7973 simplex.reserve(3);
7974 simplex.push_back(support(pv, qv, dirx, diry));
7975 dirx = -simplex[0].vx;
7976 diry = -simplex[0].vy;
7977
7978 double prev_d2 = std::numeric_limits<double>::infinity();
7979 size_t iters = 0;
7980
7981 for (; iters < kMaxIters; ++iters)
7982 {
7983 if (norm2(dirx, diry) <= kEps * kEps)
7984 break;
7985
7986 simplex.push_back(support(pv, qv, dirx, diry));
7987 if (simplex.size() > 3)
7988 simplex.erase(simplex.begin());
7989
7991 simplex = std::move(step.reduced_simplex);
7992
7993 const double d2 = norm2(step.cx, step.cy);
7994 if (step.contains_origin or d2 <= kEps * kEps)
7995 {
7996 Result r;
7997 r.distance_squared = 0;
7998 r.distance = 0;
7999 r.closest_on_first = step.witness_a;
8000 r.closest_on_second = step.witness_b;
8001 r.intersects = true;
8002 r.gjk_iterations = iters + 1;
8003 return r;
8004 }
8005
8006 if (prev_d2 - d2 <= kEps * std::max(1.0, prev_d2))
8007 break;
8008
8009 prev_d2 = d2;
8010 dirx = -step.cx;
8011 diry = -step.cy;
8012 }
8013
8015 refined.gjk_iterations = iters;
8016 return refined;
8017 }
8018};
8019
8020// ============================================================================
8021// KD-Tree Point Search — O(log n) Nearest Neighbor
8022// ============================================================================
8023
8052{
8056
8057 [[nodiscard]] static bool lexicographic_less(const Point &a, const Point &b)
8058 {
8059 if (a.get_x() < b.get_x())
8060 return true;
8061 if (b.get_x() < a.get_x())
8062 return false;
8063 return a.get_y() < b.get_y();
8064 }
8065
8067 {
8068 quicksort_op(points, [](const Point &a, const Point &b)
8069 {
8070 return lexicographic_less(a, b);
8071 });
8073 uniq.reserve(points.size());
8074 for (size_t i = 0; i < points.size(); ++i)
8075 if (uniq.is_empty() or uniq.get_last() != points(i))
8076 uniq.append(points(i));
8077 return uniq;
8078 }
8079
8080public:
8085 {
8087 bool split_on_x = true;
8089 size_t depth = 0;
8090 bool is_leaf = false;
8092 };
8093
8103
8113 const Geom_Number &ymin,
8114 const Geom_Number &xmax,
8115 const Geom_Number &ymax)
8116 : tree(xmin, ymin, xmax, ymax), bounds_(xmin, ymin, xmax, ymax)
8117 {}
8118
8130 const Geom_Number &xmin,
8131 const Geom_Number &ymin,
8132 const Geom_Number &xmax,
8133 const Geom_Number &ymax)
8134 {
8135 KDTreePointSearch kd(xmin, ymin, xmax, ymax);
8136 kd.tree = K2Tree<>::build(points, Point(xmin, ymin), Point(xmax, ymax));
8137 kd.points_ = unique_points(points);
8138 return kd;
8139 }
8140
8142 bool insert(const Point &p)
8143 {
8144 if (not tree.insert(p))
8145 return false;
8146 points_.append(p);
8147 return true;
8148 }
8149
8151 [[nodiscard]] bool contains(const Point &p) const
8152 {
8153 return tree.contains(p);
8154 }
8155
8157 [[nodiscard]] std::optional<Point> nearest(const Point &p) const
8158 {
8159 return tree.nearest(p);
8160 }
8161
8163 void range(const Geom_Number &xmin,
8164 const Geom_Number &ymin,
8165 const Geom_Number &xmax,
8166 const Geom_Number &ymax,
8167 DynList<Point> *out) const
8168 {
8169 tree.range({xmin, ymin, xmax, ymax}, out);
8170 }
8171
8174 {
8175 return tree.size();
8176 }
8177
8180 {
8181 return tree.is_empty();
8182 }
8183
8185 template <typename Op>
8186 void for_each(Op &&op) const
8187 {
8188 tree.for_each(std::forward<Op>(op));
8189 }
8190
8199 {
8202 snap.points = points_;
8203 if (points_.is_empty())
8204 return snap;
8205
8206 auto recurse = [&](const auto &self, const Array<Point> &pts, const Rectangle &region,
8207 const size_t depth, const bool split_on_x) -> void
8208 {
8209 if (pts.is_empty())
8210 return;
8211
8213 quicksort_op(sorted, [split_on_x](const Point &a, const Point &b)
8214 {
8215 if (split_on_x)
8216 {
8217 if (a.get_x() != b.get_x())
8218 return a.get_x() < b.get_x();
8219 return a.get_y() < b.get_y();
8220 }
8221 if (a.get_y() != b.get_y())
8222 return a.get_y() < b.get_y();
8223 return a.get_x() < b.get_x();
8224 });
8225
8226 size_t mid = sorted.size() / 2;
8227 if (mid == 0)
8228 mid = 1;
8229 if (mid >= sorted.size())
8230 mid = sorted.size() - 1;
8231
8232 const Point pivot = sorted(mid);
8234 part.region = region;
8235 part.split_on_x = split_on_x;
8236 part.split_value = split_on_x ? pivot.get_x() : pivot.get_y();
8237 part.depth = depth;
8238 part.is_leaf = sorted.size() == 1;
8239 part.representative = pivot;
8240 snap.partitions.append(part);
8241
8242 if (sorted.size() == 1)
8243 return;
8244
8248 right_pts.reserve(sorted.size() - mid);
8249 for (size_t i = 0; i < mid; ++i)
8250 left_pts.append(sorted(i));
8251 for (size_t i = mid; i < sorted.size(); ++i)
8252 right_pts.append(sorted(i));
8253
8254 if (split_on_x)
8255 {
8256 Rectangle left(region.get_xmin(), region.get_ymin(), pivot.get_x(), region.get_ymax());
8257 Rectangle right(pivot.get_x(), region.get_ymin(), region.get_xmax(), region.get_ymax());
8258 self(self, left_pts, left, depth + 1, false);
8259 self(self, right_pts, right, depth + 1, false);
8260 }
8261 else
8262 {
8263 Rectangle bottom(region.get_xmin(), region.get_ymin(), region.get_xmax(), pivot.get_y());
8264 Rectangle top(region.get_xmin(), pivot.get_y(), region.get_xmax(), region.get_ymax());
8265 self(self, left_pts, bottom, depth + 1, true);
8266 self(self, right_pts, top, depth + 1, true);
8267 }
8268 };
8269
8270 recurse(recurse, points_, bounds_, 0, true);
8271 return snap;
8272 }
8273};
8274
8275// ============================================================================
8276// Convex Polygon Decomposition — Hertel-Mehlhorn
8277// ============================================================================
8278
8312{
8313 static constexpr size_t NONE = ~static_cast<size_t>(0);
8314
8315 [[nodiscard]] static size_t find_pos(const Array<size_t> &face, size_t v)
8316 {
8317 for (size_t i = 0; i < face.size(); ++i)
8318 if (face(i) == v)
8319 return i;
8320 return NONE;
8321 }
8322
8323 [[nodiscard]] static bool is_polygon_edge(size_t u, size_t v, size_t n)
8324 {
8325 if (u > v)
8326 {
8327 const size_t tmp = u;
8328 u = v;
8329 v = tmp;
8330 }
8331 return v == u + 1 or (u == 0 and v == n - 1);
8332 }
8333
8335 [[nodiscard]] static bool can_merge(const Array<Point> &pts,
8336 const Array<size_t> &f1,
8337 const Array<size_t> &f2,
8338 size_t u,
8339 size_t v)
8340 {
8341 const size_t n1 = f1.size();
8342 const size_t n2 = f2.size();
8343
8344 size_t pu1 = find_pos(f1, u);
8345 size_t pv1 = find_pos(f1, v);
8346
8347 // Ensure u is followed by v in f1 (CCW). If not, swap roles.
8348 if ((pu1 + 1) % n1 != pv1)
8349 {
8350 const size_t tmp = u;
8351 u = v;
8352 v = tmp;
8353 pu1 = find_pos(f1, u);
8354 pv1 = find_pos(f1, v);
8355 }
8356
8357 const size_t prev_u = f1((pu1 + n1 - 1) % n1);
8358 const size_t next_v = f1((pv1 + 1) % n1);
8359
8360 const size_t pu2 = find_pos(f2, u);
8361 const size_t pv2 = find_pos(f2, v);
8362
8363 const size_t next_u = f2((pu2 + 1) % n2);
8364 const size_t prev_v = f2((pv2 + n2 - 1) % n2);
8365
8367 return false;
8369 return false;
8370
8371 return true;
8372 }
8373
8376 const Array<size_t> &f2,
8377 size_t u,
8378 size_t v)
8379 {
8380 const size_t n1 = f1.size();
8381 const size_t n2 = f2.size();
8382
8383 size_t pu1 = find_pos(f1, u);
8384 size_t pv1 = find_pos(f1, v);
8385
8386 if ((pu1 + 1) % n1 != pv1)
8387 {
8388 const size_t tmp = u;
8389 u = v;
8390 v = tmp;
8391 pv1 = find_pos(f1, v);
8392 }
8393
8394 const size_t pu2 = find_pos(f2, u);
8395
8396 Array<size_t> merged;
8397 merged.reserve(n1 + n2 - 2);
8398
8399 // Part 1: f1 vertices from after v around to u (inclusive), skip v.
8400 for (size_t k = 0; k < n1 - 1; ++k)
8401 merged.append(f1((pv1 + 1 + k) % n1));
8402
8403 // Part 2: f2 vertices from after u around to v (inclusive), skip u.
8404 for (size_t k = 0; k < n2 - 1; ++k)
8405 merged.append(f2((pu2 + 1 + k) % n2));
8406
8407 return merged;
8408 }
8409
8411 {
8413 }
8414
8415public:
8423 {
8424 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
8425 ah_domain_error_if(poly.size() < 3) << "Polygon must have >= 3 vertices";
8426
8427 const Array<Point> pts = extract_vertices(poly);
8428 const size_t n = pts.size();
8429
8430 if (n == 3)
8431 {
8432 Array<Polygon> result;
8433 result.append(poly);
8434 return result;
8435 }
8436
8437 // Triangulate.
8440
8441 // Build indexed faces from triangles.
8442 Array<Array<size_t>> faces;
8443 for (DynList<Triangle>::Iterator it(tri_list); it.has_curr(); it.next_ne())
8444 {
8445 const Triangle &t = it.get_curr();
8446 size_t i0 = NONE, i1 = NONE, i2 = NONE;
8447 for (size_t i = 0; i < n; ++i)
8448 {
8449 if (i0 == NONE and pts(i) == t.get_p1())
8450 i0 = i;
8451 if (i1 == NONE and pts(i) == t.get_p2())
8452 i1 = i;
8453 if (i2 == NONE and pts(i) == t.get_p3())
8454 i2 = i;
8455 }
8456 if (i0 == NONE or i1 == NONE or i2 == NONE)
8457 continue;
8458
8460 {
8461 const size_t tmp = i1;
8462 i1 = i2;
8463 i2 = tmp;
8464 }
8465
8466 Array<size_t> face;
8467 face.append(i0);
8468 face.append(i1);
8469 face.append(i2);
8470 faces.append(std::move(face));
8471 }
8472
8473 // Hertel-Mehlhorn: greedily merge across diagonals.
8474 bool changed = true;
8475 while (changed)
8476 {
8477 changed = false;
8478 for (size_t fi = 0; fi < faces.size() and not changed; ++fi)
8479 {
8480 const auto f1 = faces(fi); // copy to avoid dangling when faces mutates
8481 for (size_t k = 0; k < f1.size() and not changed; ++k)
8482 {
8483 const size_t u = f1(k);
8484 const size_t v = f1((k + 1) % f1.size());
8485
8486 if (is_polygon_edge(u, v, n))
8487 continue;
8488
8489 for (size_t fj = fi + 1; fj < faces.size() and not changed; ++fj)
8490 {
8491 const auto f2 = faces(fj); // copy for the same reason
8492 if (find_pos(f2, u) == NONE or find_pos(f2, v) == NONE)
8493 continue;
8494
8495 if (not can_merge(pts, f1, f2, u, v))
8496 continue;
8497
8498 Array<size_t> merged = merge_faces(f1, f2, u, v);
8499
8501 new_faces.reserve(faces.size() - 1);
8502 for (size_t i = 0; i < faces.size(); ++i)
8503 {
8504 if (i == fi)
8505 new_faces.append(std::move(merged));
8506 else if (i != fj)
8507 new_faces.append(std::move(faces(i)));
8508 }
8509 faces = std::move(new_faces);
8510 changed = true;
8511 }
8512 }
8513 }
8514 }
8515
8516 // Convert index faces to Polygons.
8517 Array<Polygon> result;
8518 result.reserve(faces.size());
8519 for (size_t fi = 0; fi < faces.size(); ++fi)
8520 {
8521 Polygon p;
8522 for (size_t k = 0; k < faces(fi).size(); ++k)
8523 p.add_vertex(pts(faces(fi)(k)));
8524 if (p.size() >= 3)
8525 {
8526 p.close();
8527 result.append(std::move(p));
8528 }
8529 }
8530
8531 return result;
8532 }
8533};
8534
8535// ============================================================================
8536// Range Tree 2D — Orthogonal Range Queries
8537// ============================================================================
8538
8564{
8565public:
8570 {
8571 size_t tree_index = 0;
8572 size_t lo = 0;
8573 size_t hi = 0;
8579 size_t y_sorted_size = 0;
8580 bool is_leaf = false;
8581 };
8582
8591
8592private:
8596 struct Node
8597 {
8599 };
8600
8603 size_t n_ = 0;
8604 bool built_ = false;
8605
8607 [[nodiscard]] static size_t lower_bound_y(const Array<Point> &arr, const Geom_Number &ymin)
8608 {
8609 size_t lo = 0, hi = arr.size();
8610 while (lo < hi)
8611 if (const size_t mid = lo + (hi - lo) / 2; arr(mid).get_y() < ymin)
8612 lo = mid + 1;
8613 else
8614 hi = mid;
8615 return lo;
8616 }
8617
8619 [[nodiscard]] static size_t upper_bound_y(const Array<Point> &arr, const Geom_Number &ymax)
8620 {
8621 size_t lo = 0, hi = arr.size();
8622 while (lo < hi)
8623 if (const size_t mid = lo + (hi - lo) / 2; arr(mid).get_y() <= ymax)
8624 lo = mid + 1;
8625 else
8626 hi = mid;
8627 return lo;
8628 }
8629
8631 [[nodiscard]] size_t lower_bound_x(const Geom_Number &xval) const
8632 {
8633 size_t lo = 0, hi = n_;
8634 while (lo < hi)
8635 if (const size_t mid = lo + (hi - lo) / 2; pts_(mid).get_x() < xval)
8636 lo = mid + 1;
8637 else
8638 hi = mid;
8639 return lo;
8640 }
8641
8643 [[nodiscard]] size_t upper_bound_x(const Geom_Number &xval) const
8644 {
8645 size_t lo = 0, hi = n_;
8646 while (lo < hi)
8647 if (const size_t mid = lo + (hi - lo) / 2; pts_(mid).get_x() <= xval)
8648 lo = mid + 1;
8649 else
8650 hi = mid;
8651 return lo;
8652 }
8653
8654 void build_node(const size_t node, const size_t lo, const size_t hi)
8655 {
8656 if (lo == hi)
8657 {
8658 tree_(node).y_sorted.append(pts_(lo));
8659 return;
8660 }
8661
8662 const size_t mid = lo + (hi - lo) / 2;
8663 build_node(2 * node, lo, mid);
8664 build_node(2 * node + 1, mid + 1, hi);
8665
8666 // Merge children's y-sorted arrays.
8667 const auto &left = tree_(2 * node).y_sorted;
8668 const auto &right = tree_(2 * node + 1).y_sorted;
8669 auto &merged = tree_(node).y_sorted;
8670 merged.reserve(left.size() + right.size());
8671
8672 size_t i = 0, j = 0;
8673 while (i < left.size() and j < right.size())
8674 if (left(i).get_y() < right(j).get_y() or
8675 (left(i).get_y() == right(j).get_y() && left(i).get_x() <= right(j).get_x()))
8676 merged.append(left(i++));
8677 else
8678 merged.append(right(j++));
8679 while (i < left.size())
8680 merged.append(left(i++));
8681 while (j < right.size())
8682 merged.append(right(j++));
8683 }
8684
8685 void query_range(const size_t node,
8686 const size_t lo,
8687 const size_t hi,
8688 const size_t qlo,
8689 const size_t qhi,
8690 const Geom_Number &ymin,
8691 const Geom_Number &ymax,
8692 DynList<Point> &out) const
8693 {
8694 if (qlo > qhi or lo > qhi or hi < qlo)
8695 return;
8696
8697 if (qlo <= lo and hi <= qhi)
8698 {
8699 const auto &ys = tree_(node).y_sorted;
8700 const size_t from = lower_bound_y(ys, ymin);
8701 const size_t to = upper_bound_y(ys, ymax);
8702 for (size_t i = from; i < to; ++i)
8703 out.append(ys(i));
8704 return;
8705 }
8706
8707 const size_t mid = lo + (hi - lo) / 2;
8708 query_range(2 * node, lo, mid, qlo, qhi, ymin, ymax, out);
8709 query_range(2 * node + 1, mid + 1, hi, qlo, qhi, ymin, ymax, out);
8710 }
8711
8712public:
8714 void build(const DynList<Point> &points)
8715 {
8716 pts_ = Array<Point>();
8717 for (DynList<Point>::Iterator it(points); it.has_curr(); it.next_ne())
8718 pts_.append(it.get_curr());
8719
8720 n_ = pts_.size();
8721 if (n_ == 0)
8722 {
8723 built_ = true;
8724 return;
8725 }
8726
8727 quicksort_op(pts_, [](const Point &a, const Point &b)
8728 {
8729 return a.get_x() < b.get_x() || (a.get_x() == b.get_x() and a.get_y() < b.get_y());
8730 });
8731
8732 tree_ = Array<Node>();
8733 const size_t tree_sz = 4 * n_ + 4;
8734 tree_.reserve(tree_sz);
8735 for (size_t i = 0; i < tree_sz; ++i)
8736 tree_.append(Node{Array<Point>()});
8737
8738 build_node(1, 0, n_ - 1);
8739 built_ = true;
8740 }
8741
8744 const Geom_Number &xmax,
8745 const Geom_Number &ymin,
8746 const Geom_Number &ymax) const
8747 {
8749 if (not built_ or n_ == 0)
8750 return out;
8751
8752 const size_t qlo = lower_bound_x(xmin);
8753 const size_t qhi = upper_bound_x(xmax);
8754 if (qhi == 0)
8755 return out;
8756
8757 query_range(1, 0, n_ - 1, qlo, qhi - 1, ymin, ymax, out);
8758 return out;
8759 }
8760
8762 {
8763 return n_;
8764 }
8765
8767 {
8768 return n_ == 0;
8769 }
8770
8773 {
8776 if (not built_ or n_ == 0)
8777 return snap;
8778
8779 // A binary range tree with n leaves has exactly 2n - 1 snapshot nodes.
8780 // Reserving also avoids needless reallocations while the snapshot is built.
8781 const size_t max_size = std::numeric_limits<size_t>::max();
8782 if (n_ <= max_size / 2 + 1)
8783 snap.nodes.reserve(n_ + (n_ - 1));
8784
8785 auto build_debug = [&](const auto &self, const size_t tree_idx, const size_t lo,
8786 const size_t hi) -> size_t
8787 {
8788 const size_t out_idx = snap.nodes.size();
8789 snap.nodes.append(DebugNode{});
8790 snap.nodes(out_idx).tree_index = tree_idx;
8791 snap.nodes(out_idx).lo = lo;
8792 snap.nodes(out_idx).hi = hi;
8793 snap.nodes(out_idx).is_leaf = lo == hi;
8794 snap.nodes(out_idx).xmin = pts_(lo).get_x();
8795 snap.nodes(out_idx).xmax = pts_(hi).get_x();
8796 snap.nodes(out_idx).y_sorted_size = tree_(tree_idx).y_sorted.size();
8797 snap.nodes(out_idx).split_x = pts_((lo + hi) / 2).get_x();
8798
8799 if (lo < hi)
8800 {
8801 const size_t mid = lo + (hi - lo) / 2;
8802 const size_t left = self(self, 2 * tree_idx, lo, mid);
8803 const size_t right = self(self, 2 * tree_idx + 1, mid + 1, hi);
8804 snap.nodes(out_idx).left = left;
8805 snap.nodes(out_idx).right = right;
8806 }
8807
8808 return out_idx;
8809 };
8810
8811 build_debug(build_debug, 1, 0, n_ - 1);
8812 return snap;
8813 }
8814};
8815
8816// ============================================================================
8817// Convex Polygon Offset (Inward / Outward)
8818// ============================================================================
8819
8835{
8836 friend class PolygonOffset;
8837
8839 {
8841 }
8842
8847
8848 [[nodiscard]] static bool is_convex(const Array<Point> &v)
8849 {
8850 if (v.size() < 3)
8851 return false;
8853 }
8854
8855 static void ensure_ccw(Array<Point> &v)
8856 {
8858 }
8859
8869 static void offset_edge(const Point &a,
8870 const Point &b,
8871 const Geom_Number &d,
8872 const bool inward,
8873 Point &oa,
8874 Point &ob)
8875 {
8876 const mpfr_class dx(b.get_x() - a.get_x());
8877 const mpfr_class dy(b.get_y() - a.get_y());
8878 const mpfr_class len = hypot(dx, dy);
8879 const mpfr_class md(d);
8880
8881 // For CCW polygon: inward normal = (dy, -dx)/len, outward = (-dy, dx)/len
8882 mpfr_class nx, ny;
8883 if (inward)
8884 {
8885 nx = -dy * md / len;
8886 ny = dx * md / len;
8887 }
8888 else
8889 {
8890 nx = dy * md / len;
8891 ny = -dx * md / len;
8892 }
8893
8896 }
8897
8899 [[nodiscard]] static Point line_intersect(const Point &a1,
8900 const Point &a2,
8901 const Point &b1,
8902 const Point &b2)
8903 {
8904 const Geom_Number &a1x = a1.get_x(), &a1y = a1.get_y();
8905 const Geom_Number &a2x = a2.get_x(), &a2y = a2.get_y();
8906 const Geom_Number &b1x = b1.get_x(), &b1y = b1.get_y();
8907 const Geom_Number &b2x = b2.get_x(), &b2y = b2.get_y();
8908
8909 const Geom_Number dax = a2x - a1x, day = a2y - a1y;
8910 const Geom_Number dbx = b2x - b1x, dby = b2y - b1y;
8911 const Geom_Number denom = dax * dby - day * dbx;
8912 if (denom == 0)
8913 {
8914 // Consecutive offset lines can be parallel when the input has a
8915 // collinear triple. In that case, use the shared shifted vertex.
8916 return a2;
8917 }
8918
8919 const Geom_Number t = ((b1x - a1x) * dby - (b1y - a1y) * dbx) / denom;
8920 return {a1x + t * dax, a1y + t * day};
8921 }
8922
8923public:
8931 static Polygon inward(const Polygon &convex_poly, const Geom_Number &distance)
8932 {
8933 ah_domain_error_if(not convex_poly.is_closed()) << "Polygon must be closed";
8934
8936
8937 ah_domain_error_if(v.size() < 3) << "Polygon must have >= 3 vertices";
8938 ah_domain_error_if(not is_convex(v)) << "Polygon must be convex";
8939
8940 if (distance == 0)
8941 return convex_poly;
8942
8943 ensure_ccw(v);
8944 const size_t n = v.size();
8945
8947 hps.reserve(n);
8948
8949 for (size_t i = 0; i < n; ++i)
8950 {
8951 const size_t j = (i + 1) % n;
8952 Point oa, ob;
8953 offset_edge(v(i), v(j), distance, true, oa, ob);
8955 }
8956
8957 constexpr HalfPlaneIntersection hpi;
8958 return hpi(hps);
8959 }
8960
8968 static Polygon outward(const Polygon &convex_poly, const Geom_Number &distance)
8969 {
8970 ah_domain_error_if(not convex_poly.is_closed()) << "Polygon must be closed";
8971
8973
8974 ah_domain_error_if(v.size() < 3) << "Polygon must have >= 3 vertices";
8975 ah_domain_error_if(not is_convex(v)) << "Polygon must be convex";
8976
8977 if (distance == 0)
8978 return convex_poly;
8979
8980 ensure_ccw(v);
8981 const size_t n = v.size();
8982
8983 // Compute offset lines for each edge.
8984 Array<Point> oa, ob; // offset edge endpoints
8985 oa.reserve(n);
8986 ob.reserve(n);
8987 for (size_t i = 0; i < n; ++i)
8988 {
8989 Point a, b;
8990 offset_edge(v(i), v((i + 1) % n), distance, false, a, b);
8991 oa.append(a);
8992 ob.append(b);
8993 }
8994
8995 // Intersect consecutive offset lines → new vertices.
8996 Polygon result;
8997 for (size_t i = 0; i < n; ++i)
8998 {
8999 const size_t j = (i + 1) % n;
9000 const Geom_Number dax = ob(i).get_x() - oa(i).get_x();
9001 const Geom_Number day = ob(i).get_y() - oa(i).get_y();
9002 const Geom_Number dbx = ob(j).get_x() - oa(j).get_x();
9003 const Geom_Number dby = ob(j).get_y() - oa(j).get_y();
9004 if (const Geom_Number denom = dax * dby - day * dbx; denom == 0)
9005 // Collinear triples produce consecutive parallel offset lines:
9006 // there is no corner vertex to add for that junction.
9007 continue;
9008
9009 result.add_vertex(line_intersect(oa(i), ob(i), oa(j), ob(j)));
9010 }
9011 if (result.size() >= 3)
9012 result.close();
9013 return result;
9014 }
9015};
9016
9017// ============================================================================
9018// General Polygon Offset (non-convex)
9019// ============================================================================
9020
9048{
9049public:
9053 enum class JoinType
9054 {
9055 Miter,
9056 Bevel
9057 };
9058
9062 struct Result
9063 {
9065
9070 {
9071 return polygons.is_empty();
9072 }
9073
9075 {
9076 return polygons.size();
9077 }
9078 };
9079
9091 const Geom_Number &distance,
9093 const Geom_Number &miter_limit = Geom_Number(2)) const
9094 {
9095 ah_domain_error_if(not poly.is_closed()) << "Polygon must be closed";
9096 ah_domain_error_if(poly.size() < 3) << "Polygon must have >= 3 vertices";
9097
9098 if (distance == 0)
9099 {
9100 Result r;
9101 r.polygons.append(poly);
9102 return r;
9103 }
9104
9107
9108 const Array<Point> raw = compute_raw_offset(v, distance, join, miter_limit);
9109
9110 if (raw.size() < 3)
9111 return Result{};
9112
9113 return cleanup(raw);
9114 }
9115
9123 const Geom_Number &distance,
9125 const Geom_Number &miter_limit = Geom_Number(2)) const
9126 {
9127 Result r = (*this)(poly, distance, join, miter_limit);
9128 if (r.is_empty())
9129 return {};
9130
9131 size_t best = 0;
9132 Geom_Number best_area = abs_area(r.polygons(0));
9133 for (size_t i = 1; i < r.polygons.size(); ++i)
9134 if (const Geom_Number a = abs_area(r.polygons(i)); a > best_area)
9135 {
9136 best_area = a;
9137 best = i;
9138 }
9139 return r.polygons(best);
9140 }
9141
9142private:
9143 // ----- helpers -----
9144
9146 {
9148 if (a < 0)
9149 a = -a;
9150 return a;
9151 }
9152
9155 static void offset_edge(const Point &a,
9156 const Point &b,
9157 const Geom_Number &d,
9158 const bool inward,
9159 Point &oa,
9160 Point &ob)
9161 {
9162 ConvexPolygonOffset::offset_edge(a, b, d, inward, oa, ob);
9163 }
9164
9166 [[nodiscard]] static Point line_intersect(const Point &a1,
9167 const Point &a2,
9168 const Point &b1,
9169 const Point &b2)
9170 {
9171 return ConvexPolygonOffset::line_intersect(a1, a2, b1, b2);
9172 }
9173
9174 // ----- Phase 1: raw offset polygon -----
9175
9177 [[nodiscard]] static Geom_Number sq_dist(const Point &a, const Point &b)
9178 {
9179 const Geom_Number dx = b.get_x() - a.get_x();
9180 const Geom_Number dy = b.get_y() - a.get_y();
9181 return dx * dx + dy * dy;
9182 }
9183
9185 const Geom_Number &distance,
9186 const JoinType join,
9187 const Geom_Number &miter_limit)
9188 {
9189 const size_t n = v.size();
9190 const bool inward = distance < 0;
9191 Geom_Number d = distance;
9192 if (inward)
9193 d = -distance;
9194
9195 // Compute offset edge pairs.
9197 oa.reserve(n);
9198 ob.reserve(n);
9199 for (size_t i = 0; i < n; ++i)
9200 {
9201 Point a, b;
9202 offset_edge(v(i), v((i + 1) % n), d, inward, a, b);
9203 oa.append(a);
9204 ob.append(b);
9205 }
9206
9207 // Join consecutive offset edges, tracking junction boundaries.
9208 const Geom_Number miter_sq = miter_limit * miter_limit * d * d;
9209 Array<Point> raw;
9210 raw.reserve(n * 2);
9211
9212 // junction_first[i] = index in raw where junction i's vertices begin.
9213 // junction_count[i] = number of vertices emitted for junction i.
9216 junction_count.reserve(n);
9217
9218 for (size_t i = 0; i < n; ++i)
9219 {
9220 const size_t j = (i + 1) % n;
9221 junction_first.append(raw.size());
9222
9223 const Geom_Number dax = ob(i).get_x() - oa(i).get_x();
9224 const Geom_Number day = ob(i).get_y() - oa(i).get_y();
9225 const Geom_Number dbx = ob(j).get_x() - oa(j).get_x();
9226 const Geom_Number dby = ob(j).get_y() - oa(j).get_y();
9227
9228 if (const Geom_Number denom = dax * dby - day * dbx; denom == 0)
9229 {
9230 junction_count.append(0);
9231 continue;
9232 }
9233
9234 if (const Point miter = line_intersect(oa(i), ob(i), oa(j), ob(j)); join == JoinType::Bevel or
9235 (join == JoinType::Miter && sq_dist(v((i + 1) % n), miter) > miter_sq))
9236 {
9237 raw.append(ob(i));
9238 raw.append(oa(j));
9239 junction_count.append(2);
9240 }
9241 else
9242 {
9243 raw.append(miter);
9244 junction_count.append(1);
9245 }
9246 }
9247
9248 // Validate edge directions: for each span between consecutive
9249 // junctions, the direction must match the corresponding original edge.
9250 // A reversed direction indicates that the offset has "crossed through"
9251 // the polygon (excessive offset).
9252 for (size_t i = 0; i < n; ++i)
9253 {
9254 const size_t j = (i + 1) % n;
9255 if (junction_count(i) == 0 || junction_count(j) == 0)
9256 continue;
9257
9258 // Span runs from the last vertex of junction i to the first
9259 // vertex of junction j, along offset edge (i+1)%n.
9260 const size_t span_start = junction_first(i) + junction_count(i) - 1;
9261 const size_t span_end = junction_first(j);
9262
9263 const Geom_Number sdx = raw(span_end).get_x() - raw(span_start).get_x();
9264 const Geom_Number sdy = raw(span_end).get_y() - raw(span_start).get_y();
9265
9266 // Direction of original edge (i+1)%n: v(j) → v((j+1)%n).
9267 const Geom_Number edx = v((j + 1) % n).get_x() - v(j).get_x();
9268 const Geom_Number edy = v((j + 1) % n).get_y() - v(j).get_y();
9269
9270 if (sdx * edx + sdy * edy < 0)
9271 return {}; // direction reversed → excessive offset
9272 }
9273
9274 return raw;
9275 }
9276
9277 // ----- Phase 2: self-intersection cleanup -----
9278
9281 {
9284 Geom_Number alpha_i, alpha_j; // parameter along each edge
9285 };
9286
9289 {
9291 bool is_intersection = false;
9292 size_t partner = SIZE_MAX; // index of same point on other edge
9293 bool visited = false;
9295 };
9296
9298 [[nodiscard]] static Geom_Number edge_param(const Point &P, const Point &Q, const Point &I)
9299 {
9300 if (const Geom_Number dx = Q.get_x() - P.get_x(); dx != 0)
9301 return (I.get_x() - P.get_x()) / dx;
9302 return (I.get_y() - P.get_y()) / (Q.get_y() - P.get_y());
9303 }
9304
9307 {
9308 const size_t n = raw.size();
9310
9311 for (size_t i = 0; i < n; ++i)
9312 {
9313 const Point &a0 = raw(i);
9314 const Point &a1 = raw((i + 1) % n);
9315 const Segment sa(a0, a1);
9316
9317 for (size_t j = i + 2; j < n; ++j)
9318 {
9319 // Skip adjacent edges (they share a vertex).
9320 if (j == (i + n - 1) % n)
9321 continue;
9322 if (i == 0 && j == n - 1)
9323 continue;
9324
9325 const Point &b0 = raw(j);
9326 const Point &b1 = raw((j + 1) % n);
9327 const Segment sb(b0, b1);
9328
9329 if (not sa.intersects_properly_with(sb))
9330 continue;
9331
9332 const Point ip = sa.intersection_with(sb);
9333 const Geom_Number ai = edge_param(a0, a1, ip);
9334 const Geom_Number aj = edge_param(b0, b1, ip);
9335 isects.append({ip, i, j, ai, aj});
9336 }
9337 }
9338
9339 return isects;
9340 }
9341
9344 {
9345 for (size_t i = 1; i < idx.size(); ++i)
9346 {
9347 const size_t val = idx(i);
9348 const Geom_Number &key = keys(val);
9349 size_t j = i;
9350 while (j > 0 && keys(idx(j - 1)) > key)
9351 {
9352 idx(j) = idx(j - 1);
9353 --j;
9354 }
9355 idx(j) = val;
9356 }
9357 }
9358
9362 {
9363 const size_t n = raw.size();
9364 const size_t m = isects.size();
9365
9366 // Position in the augmented list for each intersection occurrence.
9367 // Each intersection appears twice (once on each edge).
9369 pos_i.reserve(m);
9370 pos_j.reserve(m);
9371 for (size_t k = 0; k < m; ++k)
9372 {
9373 pos_i.append(0);
9374 pos_j.append(0);
9375 }
9376
9378 aug.reserve(n + 2 * m);
9379
9380 for (size_t i = 0; i < n; ++i)
9381 {
9382 // Append original vertex.
9383 AugVertex v;
9384 v.pt = raw(i);
9385 aug.append(v);
9386
9387 // Collect intersections on this edge.
9390 for (size_t k = 0; k < m; ++k)
9391 {
9392 if (isects(k).edge_i == i)
9393 {
9395 edge_alphas.append(isects(k).alpha_i);
9396 }
9397 else if (isects(k).edge_j == i)
9398 {
9399 edge_isects.append(k);
9400 edge_alphas.append(isects(k).alpha_j);
9401 }
9402 }
9403
9404 if (edge_isects.is_empty())
9405 continue;
9406
9407 // Sort by alpha.
9408 Array<size_t> order;
9409 for (size_t p = 0; p < edge_isects.size(); ++p)
9410 order.append(p);
9411 sort_by_alpha(order, edge_alphas);
9412
9413 for (size_t p = 0; p < order.size(); ++p)
9414 {
9415 const size_t k = edge_isects(order(p));
9416 const size_t aug_idx = aug.size();
9417
9418 // Record position for partner linking.
9419 if (isects(k).edge_i == i)
9420 pos_i(k) = aug_idx;
9421 else
9422 pos_j(k) = aug_idx;
9423
9424 AugVertex iv;
9425 iv.pt = isects(k).pt;
9426 iv.is_intersection = true;
9427 iv.alpha = edge_alphas(order(p));
9428 aug.append(iv);
9429 }
9430 }
9431
9432 // Set partner links.
9433 for (size_t k = 0; k < m; ++k)
9434 {
9435 aug(pos_i(k)).partner = pos_j(k);
9436 aug(pos_j(k)).partner = pos_i(k);
9437 }
9438
9439 return aug;
9440 }
9441
9444 {
9445 Polygon p;
9446 for (size_t i = 0; i < pts.size(); ++i)
9447 p.add_vertex(pts(i));
9448 if (pts.size() >= 3)
9449 p.close();
9450 return p;
9451 }
9452
9460 {
9461 Array<Polygon> result;
9462 const size_t N = aug.size();
9463 const size_t safety = N * 2 + 10;
9464
9465 for (size_t s = 0; s < N; ++s)
9466 {
9467 if (not aug(s).is_intersection || aug(s).visited)
9468 continue;
9469
9470 // Trace one contour starting from intersection s.
9471 // Jump to partner first, then walk forward.
9472 Array<Point> boundary;
9473 size_t steps = 0;
9474
9475 aug(s).visited = true;
9476 if (aug(s).partner != SIZE_MAX)
9477 aug(aug(s).partner).visited = true;
9478
9479 // Jump to partner to enter the "other branch".
9480 size_t idx = (aug(s).partner != SIZE_MAX) ? aug(s).partner : s;
9481 const size_t entry = idx; // we'll loop until we return here
9482
9483 do
9484 {
9485 // Append current vertex and walk forward.
9486 boundary.append(aug(idx).pt);
9487 idx = (idx + 1) % N;
9488
9489 while (not aug(idx).is_intersection)
9490 {
9491 boundary.append(aug(idx).pt);
9492 idx = (idx + 1) % N;
9493 if (++steps > safety)
9494 goto done_contour;
9495 }
9496
9497 // At the next intersection — mark visited and jump to partner.
9498 aug(idx).visited = true;
9499 if (aug(idx).partner != SIZE_MAX)
9500 {
9501 aug(aug(idx).partner).visited = true;
9502 idx = aug(idx).partner;
9503 }
9504
9505 if (++steps > safety)
9506 break;
9507 } while (idx != entry);
9508
9510 if (boundary.size() >= 3)
9511 {
9512 // Keep only CCW contours (positive signed area).
9513 if (const Geom_Number sa = GeomPolygonUtils::signed_double_area(boundary); sa > 0)
9514 result.append(build_poly(boundary));
9515 }
9516 }
9517
9518 return result;
9519 }
9520
9522 static Result cleanup(const Array<Point> &raw)
9523 {
9524 const auto isects = find_self_intersections(raw);
9525
9526 if (isects.is_empty())
9527 {
9528 // No self-intersections — check if the raw polygon is valid CCW.
9530 Result r;
9531 if (sa > 0 && raw.size() >= 3)
9532 r.polygons.append(build_poly(raw));
9533 return r;
9534 }
9535
9536 auto aug = build_augmented(raw, isects);
9537 Result r;
9539 return r;
9540 }
9541};
9542
9543// ============================================================================
9544// Visibility Polygon
9545// ============================================================================
9546
9570{
9572 [[nodiscard]] static int angle_quadrant(const Geom_Number &dx, const Geom_Number &dy)
9573 {
9574 if (dx > 0 and dy >= 0)
9575 return 0;
9576 if (dx <= 0 and dy > 0)
9577 return 1;
9578 if (dx < 0 and dy <= 0)
9579 return 2;
9580 return 3; // dx >= 0 and dy < 0
9581 }
9582
9584 [[nodiscard]] static bool angle_less(const Point &q, const Point &a, const Point &b)
9585 {
9586 const Geom_Number dax = a.get_x() - q.get_x();
9587 const Geom_Number day = a.get_y() - q.get_y();
9588 const Geom_Number dbx = b.get_x() - q.get_x();
9589 const Geom_Number dby = b.get_y() - q.get_y();
9590
9591 const int qa = angle_quadrant(dax, day);
9592 const int qb = angle_quadrant(dbx, dby);
9593 if (qa != qb)
9594 return qa < qb;
9595
9596 // Same quadrant: cross product. Positive = a is before b (CCW).
9597 if (const Geom_Number cross = dax * dby - day * dbx; cross != 0)
9598 return cross > 0;
9599
9600 // Same angle: closer point first.
9601 return dax * dax + day * day < (dbx * dbx + dby * dby);
9602 }
9603
9607 const Point &dir,
9608 const Point &e0,
9609 const Point &e1)
9610 {
9611 const Geom_Number rdx = dir.get_x() - q.get_x();
9612 const Geom_Number rdy = dir.get_y() - q.get_y();
9613 const Geom_Number edx = e1.get_x() - e0.get_x();
9614 const Geom_Number edy = e1.get_y() - e0.get_y();
9615 const Geom_Number denom = rdx * edy - rdy * edx;
9616 if (denom == 0)
9617 return {-1};
9618
9619 const Geom_Number dx = e0.get_x() - q.get_x();
9620 const Geom_Number dy = e0.get_y() - q.get_y();
9621 const Geom_Number t = (dx * edy - dy * edx) / denom;
9622 const Geom_Number s = (dx * rdy - dy * rdx) / denom;
9623 if (s < 0 or s > 1 or t < 0)
9624 return {-1};
9625 return t;
9626 }
9627
9629 [[nodiscard]] static Point ray_edge_hit(const Point &q,
9630 const Point &dir,
9631 const Point &e0,
9632 const Point &e1)
9633 {
9634 const Geom_Number t = ray_param(q, dir, e0, e1);
9635 const Geom_Number rdx = dir.get_x() - q.get_x();
9636 const Geom_Number rdy = dir.get_y() - q.get_y();
9637 return {q.get_x() + t * rdx, q.get_y() + t * rdy};
9638 }
9639
9649 {
9650 struct EdgeKey
9651 {
9653 size_t edge;
9654 };
9655
9657 {
9658 bool operator () (const EdgeKey &a, const EdgeKey &b) const
9659 {
9660 if (a.t != b.t)
9661 return a.t < b.t;
9662 return a.edge < b.edge;
9663 }
9664 };
9665
9667 Array<Geom_Number> params_; // edge → ray_param at insertion time
9668 Array<bool> in_tree_; // edge → currently in tree
9670 const size_t n_;
9672
9673 public:
9674 EdgeStatusTree(const Array<Point> &verts, const size_t n, const Point &query)
9675 : verts_(verts), n_(n), query_(query)
9676 {
9677 for (size_t i = 0; i < n; ++i)
9678 {
9679 params_.append(Geom_Number(-1));
9680 in_tree_.append(false);
9681 }
9682 }
9683
9684 void insert(const size_t edge, const Point &dir)
9685 {
9686 if (in_tree_(edge))
9687 return;
9688
9689 const Geom_Number t = ray_param(query_, dir, verts_(edge), verts_((edge + 1) % n_));
9690 params_(edge) = t;
9691 in_tree_(edge) = true;
9692 tree_.insert(EdgeKey{t, edge});
9693 }
9694
9695 void erase(const size_t edge)
9696 {
9697 if (not in_tree_(edge))
9698 return;
9699
9700 tree_.remove(EdgeKey{params_(edge), edge});
9701 in_tree_(edge) = false;
9702 }
9703
9706 [[nodiscard]] size_t min() const
9707 {
9708 if (tree_.is_empty())
9709 return n_;
9710 return tree_.min().edge;
9711 }
9712
9713 [[nodiscard]] bool contains(const size_t edge) const
9714 {
9715 return in_tree_(edge);
9716 }
9717
9718 [[nodiscard]] bool is_empty() const
9719 {
9720 return tree_.is_empty();
9721 }
9722 };
9723
9724public:
9732 [[nodiscard]] Polygon operator () (const Polygon &polygon, const Point &query) const
9733 {
9734 ah_domain_error_if(not polygon.is_closed()) << "Polygon must be closed";
9735 ah_domain_error_if(polygon.size() < 3) << "Polygon must have >= 3 vertices";
9737 << "Query point must be strictly inside the polygon";
9738
9739 // Extract vertices.
9741 for (Polygon::Vertex_Iterator it(polygon); it.has_curr(); it.next_ne())
9742 verts.append(it.get_current_vertex());
9743 const size_t n = verts.size();
9744
9745 // Sort vertex indices by angle from query.
9746 Array<size_t> order;
9747 order.reserve(n);
9748 for (size_t i = 0; i < n; ++i)
9749 order.append(i);
9750 quicksort_op(order, [&](size_t a, size_t b)
9751 {
9752 return angle_less(query, verts(a), verts(b));
9753 });
9754
9755 // Determine which edges cross the initial ray (wrapping edges).
9756 // Their angular span straddles 0°, so their start/end events must be
9757 // swapped relative to non-wrapping edges.
9758 const Point init_dir(query.get_x() + 1, query.get_y());
9760 wrapping.reserve(n);
9761 for (size_t ei = 0; ei < n; ++ei)
9762 {
9763 const Geom_Number t = ray_param(query, init_dir, verts(ei), verts((ei + 1) % n));
9764 wrapping.append(t > 0);
9765 }
9766
9767 // Build edge event tables: for each vertex, which edges start/end there.
9768 // Edge i goes from verts(i) to verts((i+1)%n).
9769 // For non-wrapping edges: insert at the smaller-angle vertex, remove at
9770 // the larger-angle vertex (normal sweep order).
9771 // For wrapping edges (already in the initial status): remove at the
9772 // smaller-angle vertex (ray exits their span), insert at the
9773 // larger-angle vertex (ray re-enters their span).
9775 starts.reserve(n);
9776 ends.reserve(n);
9777 for (size_t i = 0; i < n; ++i)
9778 starts.append(DynList<size_t>());
9779 for (size_t i = 0; i < n; ++i)
9780 ends.append(DynList<size_t>());
9781
9782 for (size_t ei = 0; ei < n; ++ei)
9783 {
9784 const size_t a = ei, b = (ei + 1) % n;
9785 const bool a_less = angle_less(query, verts(a), verts(b));
9786 const size_t small_v = a_less ? a : b;
9787 const size_t large_v = a_less ? b : a;
9788
9789 if (wrapping(ei))
9790 {
9791 ends(small_v).append(ei); // remove when ray exits span
9792 starts(large_v).append(ei); // re-insert when ray re-enters
9793 }
9794 else
9795 {
9796 starts(small_v).append(ei); // insert when ray enters span
9797 ends(large_v).append(ei); // remove when ray exits span
9798 }
9799 }
9800
9801 // Initialize status with wrapping edges.
9802 EdgeStatusTree status(verts, n, query);
9803 for (size_t ei = 0; ei < n; ++ei)
9804 if (wrapping(ei))
9805 status.insert(ei, init_dir);
9806
9807 // Build visibility polygon.
9809
9810 for (size_t oi = 0; oi < n; ++oi)
9811 {
9812 const size_t vi = order(oi);
9813 const Point &v = verts(vi);
9814 const Point dir = v; // ray from query toward v
9815
9816 // Nearest edge BEFORE processing events at this vertex.
9817 const size_t prev_near = status.min();
9818
9819 // Remove ending edges.
9820 for (DynList<size_t>::Iterator it(ends(vi)); it.has_curr(); it.next_ne())
9821 status.erase(it.get_curr());
9822
9823 // Insert starting edges.
9824 for (DynList<size_t>::Iterator it(starts(vi)); it.has_curr(); it.next_ne())
9825 status.insert(it.get_curr(), dir);
9826
9827 // Nearest edge AFTER processing events.
9828 const size_t curr_near = status.min();
9829
9830 // Determine visibility.
9831 bool v_is_on_edge = false;
9832 if (prev_near < n)
9833 {
9834 const size_t ea = prev_near, eb = (prev_near + 1) % n;
9835 if (vi == ea or vi == eb)
9836 v_is_on_edge = true;
9837 }
9838 if (curr_near < n and not v_is_on_edge)
9839 {
9840 const size_t ea = curr_near, eb = (curr_near + 1) % n;
9841 if (vi == ea or vi == eb)
9842 v_is_on_edge = true;
9843 }
9844
9845 if (v_is_on_edge)
9846 {
9847 // Vertex is on the nearest edge — directly visible.
9848 vis_pts.append(v);
9849 }
9850 else if (prev_near != curr_near)
9851 {
9852 // Nearest edge changed — add intersection with old and new.
9853 if (prev_near < n)
9854 vis_pts.append(ray_edge_hit(query, dir, verts(prev_near), verts((prev_near + 1) % n)));
9855 vis_pts.append(v);
9856 if (curr_near < n)
9857 vis_pts.append(ray_edge_hit(query, dir, verts(curr_near), verts((curr_near + 1) % n)));
9858 }
9859 }
9860
9861 // Remove duplicate consecutive points.
9862 Polygon result;
9863 for (size_t i = 0; i < vis_pts.size(); ++i)
9864 {
9865 if (i > 0 and vis_pts(i) == vis_pts(i - 1))
9866 continue;
9867 result.add_vertex(vis_pts(i));
9868 }
9869 if (result.size() >= 3)
9870 {
9871 // Remove last if the same as first.
9873 for (Polygon::Vertex_Iterator it(result); it.has_curr(); it.next_ne())
9874 rv.append(it.get_current_vertex());
9875 if (rv.size() >= 2 and rv(0) == rv(rv.size() - 1))
9876 {
9878 for (size_t i = 0; i + 1 < rv.size(); ++i)
9879 cleaned.add_vertex(rv(i));
9880 if (cleaned.size() >= 3)
9881 cleaned.close();
9882 return cleaned;
9883 }
9884 result.close();
9885 }
9886 return result;
9887 }
9888};
9889
9890// ============================================================================
9891// Shortest Path in Simple Polygon (Lee-Preparata Funnel)
9892// ============================================================================
9893
9918{
9919 static constexpr size_t NONE = ~static_cast<size_t>(0);
9920
9921 struct ITri
9922 {
9923 size_t v[3];
9924 size_t adj[3]; // adj[i] = neighbour sharing edge opposite v[i], or NONE
9925 };
9926
9928 [[nodiscard]] static size_t find_index(const Array<Point> &pts, const Point &p)
9929 {
9930 for (size_t i = 0; i < pts.size(); ++i)
9931 if (pts(i) == p)
9932 return i;
9933 return NONE;
9934 }
9935
9938 {
9939 // Convert to indexed form.
9941 for (DynList<Triangle>::Iterator it(tl); it.has_curr(); it.next_ne())
9942 {
9943 const Triangle &t = it.get_curr();
9944 ITri ti{};
9945 ti.v[0] = find_index(pts, t.get_p1());
9946 ti.v[1] = find_index(pts, t.get_p2());
9947 ti.v[2] = find_index(pts, t.get_p3());
9948 ti.adj[0] = ti.adj[1] = ti.adj[2] = NONE;
9949 tris.append(ti);
9950 }
9951
9952 // Build adjacency via an edge map.
9953 // Key: (min_idx, max_idx), Value: (triangle_index, local_edge_index)
9954 struct EdgeKey
9955 {
9956 size_t u, v;
9957 };
9958
9959 struct CmpEdge
9960 {
9961 bool operator () (const EdgeKey &a, const EdgeKey &b) const
9962 {
9963 if (a.u != b.u)
9964 return a.u < b.u;
9965 return a.v < b.v;
9966 }
9967 };
9968
9969 struct EdgeVal
9970 {
9971 size_t tri;
9972 size_t local;
9973 };
9974
9976
9977 // We need a separate map. Use parallel arrays since DynSetTree doesn't
9978 // store mapped values. Use a simpler approach: flat array scan.
9979 struct EdgeEntry
9980 {
9981 size_t u, v, tri, local;
9982 };
9983 Array<EdgeEntry> edges;
9984 edges.reserve(tris.size() * 3);
9985
9986 for (size_t ti = 0; ti < tris.size(); ++ti)
9987 for (int e = 0; e < 3; ++e)
9988 {
9989 size_t u = tris(ti).v[(e + 1) % 3];
9990 size_t v = tris(ti).v[(e + 2) % 3];
9991 if (u > v)
9992 {
9993 const size_t tmp = u;
9994 u = v;
9995 v = tmp;
9996 }
9997 edges.append(EdgeEntry{u, v, ti, size_t(e)});
9998 }
9999
10000 // Sort edges and find matching pairs.
10001 quicksort_op(edges, [](const EdgeEntry &a, const EdgeEntry &b)
10002 {
10003 if (a.u != b.u)
10004 return a.u < b.u;
10005 if (a.v != b.v)
10006 return a.v < b.v;
10007 return a.tri < b.tri;
10008 });
10009
10010 for (size_t i = 0; i + 1 < edges.size(); ++i)
10011 if (edges(i).u == edges(i + 1).u and edges(i).v == edges(i + 1).v)
10012 {
10013 const size_t t1 = edges(i).tri, l1 = edges(i).local;
10014 const size_t t2 = edges(i + 1).tri, l2 = edges(i + 1).local;
10015 tris(t1).adj[l1] = t2;
10016 tris(t2).adj[l2] = t1;
10017 ++i; // skip the matched pair
10018 }
10019
10020 return tris;
10021 }
10022
10024 [[nodiscard]] static bool point_in_triangle(const Array<Point> &pts, const ITri &t, const Point &p)
10025 {
10026 const Orientation o0 = orientation(pts(t.v[0]), pts(t.v[1]), p);
10027 const Orientation o1 = orientation(pts(t.v[1]), pts(t.v[2]), p);
10028 const Orientation o2 = orientation(pts(t.v[2]), pts(t.v[0]), p);
10031 return not (has_cw and has_ccw);
10032 }
10033
10035 [[nodiscard]] static size_t find_tri(const Array<Point> &pts, const Array<ITri> &tris, const Point &p)
10036 {
10037 for (size_t i = 0; i < tris.size(); ++i)
10038 if (point_in_triangle(pts, tris(i), p))
10039 return i;
10040 return NONE;
10041 }
10042
10045 const size_t src,
10046 const size_t dst)
10047 {
10048 if (src == dst)
10049 {
10050 Array<size_t> s;
10051 s.append(src);
10052 return s;
10053 }
10054
10055 Array<size_t> parent;
10056 parent.reserve(tris.size());
10057 for (size_t i = 0; i < tris.size(); ++i)
10058 parent.append(NONE);
10059 parent(src) = src; // sentinel
10060
10061 DynList<size_t> queue;
10062 queue.append(src);
10063
10064 while (not queue.is_empty())
10065 {
10066 const size_t cur = queue.remove_first();
10067 if (cur == dst)
10068 break;
10069 for (unsigned long nb : tris(cur).adj)
10070 if (nb != NONE and parent(nb) == NONE)
10071 {
10072 parent(nb) = cur;
10073 queue.append(nb);
10074 }
10075 }
10076
10077 // Reconstruct path.
10078 Array<size_t> path;
10079 for (size_t cur = dst; cur != src; cur = parent(cur))
10080 path.append(cur);
10081 path.append(src);
10082
10083 // Reverse.
10084 for (size_t i = 0; i < path.size() / 2; ++i)
10085 {
10086 const size_t tmp = path(i);
10087 path(i) = path(path.size() - 1 - i);
10088 path(path.size() - 1 - i) = tmp;
10089 }
10090 return path;
10091 }
10092
10094 [[nodiscard]] static Geom_Number cross(const Point &a, const Point &b, const Point &c)
10095 {
10096 return (b.get_x() - a.get_x()) * (c.get_y() - a.get_y()) -
10097 (b.get_y() - a.get_y()) * (c.get_x() - a.get_x());
10098 }
10099
10100public:
10110 const Point &source,
10111 const Point &target) const
10112 {
10113 ah_domain_error_if(not polygon.is_closed()) << "Polygon must be closed";
10114 ah_domain_error_if(polygon.size() < 3) << "Polygon must have >= 3 vertices";
10116 << "Source must be inside the polygon";
10118 << "Target must be inside the polygon";
10119
10120 DynList<Point> result;
10121 if (source == target)
10122 {
10123 result.append(source);
10124 return result;
10125 }
10126
10127 // Check direct line of sight.
10128 {
10129 const Segment seg(source, target);
10130 bool blocked = false;
10131 for (Polygon::Segment_Iterator it(polygon); it.has_curr() and not blocked; it.next_ne())
10132 if (const Segment edge = it.get_current_segment(); seg.intersects_properly_with(edge))
10133 blocked = true;
10134 if (not blocked)
10135 {
10136 result.append(source);
10137 result.append(target);
10138 return result;
10139 }
10140 }
10141
10142 // Extract vertices.
10144 for (Polygon::Vertex_Iterator it(polygon); it.has_curr(); it.next_ne())
10145 pts.append(it.get_current_vertex());
10146
10147 // Triangulate.
10150
10151 ah_domain_error_if(tri_list.is_empty()) << "Triangulation failed";
10152
10153 // Build indexed triangulation with adjacency.
10155
10156 // Locate source and target triangles.
10157 const size_t src_t = find_tri(pts, tris, source);
10158 const size_t dst_t = find_tri(pts, tris, target);
10159
10160 ah_domain_error_if(src_t == NONE) << "Could not locate source in triangulation";
10161 ah_domain_error_if(dst_t == NONE) << "Could not locate target in triangulation";
10162
10163 // Find sleeve.
10165
10166 if (sleeve.size() <= 1)
10167 {
10168 result.append(source);
10169 result.append(target);
10170 return result;
10171 }
10172
10173 // ----------------------------------------------------------------
10174 // Simple Stupid Funnel Algorithm (Mononen / Lee-Preparata).
10175 //
10176 // Build a portal list from the sleeve diagonals, then walk it
10177 // maintaining a funnel (apex, left boundary, right boundary).
10178 // ----------------------------------------------------------------
10179
10180 // A portal is a pair (left, right) as seen when walking from source
10181 // toward target. Portal 0 = (source, source), last = (target, target).
10182 struct Portal
10183 {
10184 Point left;
10185 Point right;
10186 };
10187 Array<Portal> portals;
10188 portals.append(Portal{source, source});
10189
10190 // For each sleeve diagonal, find shared vertices and the "opposite"
10191 // vertex in the current triangle (the one NOT shared with the next).
10192 // Orient the portal using cross(V_prev, s0, s1):
10193 // cross < 0 → left = s0, right = s1
10194 // cross > 0 → left = s1, right = s0
10195 // This is robust regardless of triangulation order.
10196
10197 for (size_t i = 0; i + 1 < sleeve.size(); ++i)
10198 {
10199 size_t s0 = 0, s1 = 0;
10200 size_t v_prev = 0;
10201 {
10202 int sc = 0;
10203 bool found[3] = {false, false, false};
10204 for (int a = 0; a < 3; ++a)
10205 for (unsigned long b : tris(sleeve(i + 1)).v)
10206 if (tris(sleeve(i)).v[a] == b)
10207 {
10208 found[a] = true;
10209 break;
10210 }
10211
10212 for (int a = 0; a < 3; ++a)
10213 {
10214 if (found[a])
10215 {
10216 if (sc == 0)
10217 s0 = tris(sleeve(i)).v[a];
10218 else
10219 s1 = tris(sleeve(i)).v[a];
10220 ++sc;
10221 }
10222 else
10223 v_prev = tris(sleeve(i)).v[a];
10224 }
10225 }
10226
10227 const Geom_Number c = cross(pts(v_prev), pts(s0), pts(s1));
10228 if (c < 0)
10229 portals.append(Portal{pts(s0), pts(s1)});
10230 else
10231 portals.append(Portal{pts(s1), pts(s0)});
10232 }
10233
10234 portals.append(Portal{target, target});
10235
10236 // --- SSFA core ---
10237 Point apex = source;
10238 Point fl = source; // funnel left boundary
10239 Point fr = source; // funnel right boundary
10240 size_t ai = 0, li = 0, ri = 0;
10241
10242 result.append(source);
10243
10244 for (size_t i = 1; i < portals.size(); ++i)
10245 {
10246 const Point &pr = portals(i).right;
10247 const Point &pl = portals(i).left;
10248
10249 // --- tighten right boundary ---
10250 if (cross(apex, fr, pr) >= 0)
10251 {
10252 if (apex == fr or cross(apex, fl, pr) < 0)
10253 {
10254 fr = pr;
10255 ri = i;
10256 }
10257 else
10258 {
10259 // Right crossed over left → left becomes new apex.
10260 result.append(fl);
10261 apex = fl;
10262 ai = li;
10263 fl = apex;
10264 fr = apex;
10265 li = ai;
10266 ri = ai;
10267 i = ai; // loop increments to ai+1
10268 continue;
10269 }
10270 }
10271
10272 // --- tighten left boundary ---
10273 if (cross(apex, fl, pl) <= 0)
10274 {
10275 if (apex == fl or cross(apex, fr, pl) > 0)
10276 {
10277 fl = pl;
10278 li = i;
10279 }
10280 else
10281 {
10282 // Left crossed over right → right becomes new apex.
10283 result.append(fr);
10284 apex = fr;
10285 ai = ri;
10286 fl = apex;
10287 fr = apex;
10288 li = ai;
10289 ri = ai;
10290 i = ai;
10291 continue;
10292 }
10293 }
10294 }
10295
10296 if (result.get_last() != target)
10297 result.append(target);
10298 return result;
10299 }
10300};
10301
10302// ============================================================================
10303// Segment Arrangement — Full Planar Subdivision
10304// ============================================================================
10305
10332{
10333public:
10337 struct ArrEdge
10338 {
10339 size_t src;
10340 size_t tgt;
10341 size_t seg_idx;
10342 };
10343
10347 struct ArrFace
10348 {
10351 };
10352
10362
10363private:
10364 static constexpr size_t NONE = ~static_cast<size_t>(0);
10365
10367 [[nodiscard]] static bool pt_less(const Point &a, const Point &b)
10368 {
10369 if (a.get_x() != b.get_x())
10370 return a.get_x() < b.get_x();
10371 return a.get_y() < b.get_y();
10372 }
10373
10375 [[nodiscard]] static size_t find_vertex(const Array<Point> &verts, const Point &p)
10376 {
10377 size_t lo = 0, hi = verts.size();
10378 while (lo < hi)
10379 if (const size_t mid = lo + (hi - lo) / 2; pt_less(verts(mid), p))
10380 lo = mid + 1;
10381 else if (pt_less(p, verts(mid)))
10382 hi = mid;
10383 else
10384 return mid;
10385 return NONE;
10386 }
10387
10389 [[nodiscard]] static int angle_quad(const Geom_Number &dx, const Geom_Number &dy)
10390 {
10391 if (dx > 0 and dy >= 0)
10392 return 0;
10393 if (dx <= 0 and dy > 0)
10394 return 1;
10395 if (dx < 0 and dy <= 0)
10396 return 2;
10397 return 3;
10398 }
10399
10402 [[nodiscard]] static bool angle_lt(const Geom_Number &dx1,
10403 const Geom_Number &dy1,
10404 const Geom_Number &dx2,
10405 const Geom_Number &dy2)
10406 {
10407 const int q1 = angle_quad(dx1, dy1);
10408 const int q2 = angle_quad(dx2, dy2);
10409 if (q1 != q2)
10410 return q1 < q2;
10411 if (const Geom_Number cross = dx1 * dy2 - dy1 * dx2; cross != 0)
10412 return cross > 0;
10413 return dx1 * dx1 + dy1 * dy1 < (dx2 * dx2 + dy2 * dy2);
10414 }
10415
10420 {
10421 size_t origin;
10422 size_t target;
10423 size_t twin;
10424 size_t next;
10425 size_t face;
10426 size_t edge_idx;
10427 };
10428
10429public:
10436 [[nodiscard]] Result operator () (const Array<Segment> &segments) const
10437 {
10438 Result ret;
10439 const size_t n = segments.size();
10440
10441 if (n == 0)
10442 {
10443 // Single unbounded face.
10444 ArrFace uf;
10445 uf.unbounded = true;
10446 ret.faces.append(uf);
10447 return ret;
10448 }
10449
10450 // --- Step 1: Compute all intersections. ---
10452 auto inters = sweep(segments);
10453
10454 // --- Step 2: Build a deduplicated vertex set. ---
10455 // Collect all endpoints + intersection points.
10457 all_pts.reserve(2 * n + inters.size());
10458 for (size_t i = 0; i < n; ++i)
10459 {
10460 all_pts.append(segments(i).get_src_point());
10461 all_pts.append(segments(i).get_tgt_point());
10462 }
10463 for (size_t i = 0; i < inters.size(); ++i)
10464 all_pts.append(inters(i).point);
10465
10466 // Sort lexicographically.
10467 quicksort_op(all_pts, [](const Point &a, const Point &b)
10468 {
10469 return pt_less(a, b);
10470 });
10471
10472 // Deduplicate.
10473 ret.vertices.reserve(all_pts.size());
10474 for (size_t i = 0; i < all_pts.size(); ++i)
10475 if (ret.vertices.is_empty() or ret.vertices.get_last() != all_pts(i))
10476 ret.vertices.append(all_pts(i));
10477
10478 // --- Step 3: Split segments into sub-edges. ---
10479 // For each segment, collect its vertices and sort along the segment.
10480 for (size_t si = 0; si < n; ++si)
10481 {
10482 const Point &sp = segments(si).get_src_point();
10483 const Point &tp = segments(si).get_tgt_point();
10484
10485 // Collect vertices on this segment.
10487 on_seg.append(find_vertex(ret.vertices, sp));
10488 on_seg.append(find_vertex(ret.vertices, tp));
10489
10490 // Add intersection points involving this segment.
10491 for (size_t ki = 0; ki < inters.size(); ++ki)
10492 if (inters(ki).seg_i == si or inters(ki).seg_j == si)
10493 if (const size_t vi = find_vertex(ret.vertices, inters(ki).point); vi != NONE)
10494 on_seg.append(vi);
10495
10496 // Deduplicate.
10497 quicksort_op(on_seg, [](const size_t a, const size_t b)
10498 {
10499 return a < b;
10500 });
10501 {
10503 for (size_t i = 0; i < on_seg.size(); ++i)
10504 if (uniq.is_empty() or uniq.get_last() != on_seg(i))
10505 uniq.append(on_seg(i));
10506 on_seg = uniq;
10507 }
10508
10509 // Sort by parametric position along the segment.
10510 // Use the primary direction (x for non-vertical, y for vertical).
10511 const bool vertical = (sp.get_x() == tp.get_x());
10512 const auto &verts = ret.vertices;
10513 if (vertical)
10514 quicksort_op(on_seg, [&](const size_t a, const size_t b)
10515 {
10516 return verts(a).get_y() < verts(b).get_y();
10517 });
10518 else
10519 quicksort_op(on_seg, [&](const size_t a, const size_t b)
10520 {
10521 return verts(a).get_x() < verts(b).get_x();
10522 });
10523
10524 // Create edges between consecutive vertices.
10525 for (size_t i = 0; i + 1 < on_seg.size(); ++i)
10526 ret.edges.append(ArrEdge{on_seg(i), on_seg(i + 1), si});
10527 }
10528
10529 // --- Step 4 + 5: Build half-edges, compute next pointers, find faces. ---
10530 const size_t ne = ret.edges.size();
10531 const size_t nhe = 2 * ne;
10532 const size_t nv = ret.vertices.size();
10533
10534 if (ne == 0)
10535 {
10536 // Only isolated vertices, no edges → one unbounded face.
10537 ArrFace uf;
10538 uf.unbounded = true;
10539 ret.faces.append(uf);
10540 return ret;
10541 }
10542
10543 // Create half-edges: for edge i, half-edge 2*i goes src→tgt,
10544 // half-edge 2*i+1 goes tgt→src.
10546 he.reserve(nhe);
10547 for (size_t i = 0; i < ne; ++i)
10548 {
10549 he.append(HalfEdge{ret.edges(i).src, ret.edges(i).tgt, 2 * i + 1, NONE, NONE, i});
10550 he.append(HalfEdge{ret.edges(i).tgt, ret.edges(i).src, 2 * i, NONE, NONE, i});
10551 }
10552
10553 // Build incidence: for each vertex, list of outgoing half-edge indices.
10555 inc.reserve(nv);
10556 for (size_t i = 0; i < nv; ++i)
10557 inc.append(Array<size_t>());
10558 for (size_t h = 0; h < nhe; ++h)
10559 inc(he(h).origin).append(h);
10560
10561 // Sort outgoing half-edges at each vertex by angle.
10562 const auto &verts = ret.vertices;
10563 for (size_t v = 0; v < nv; ++v)
10564 {
10565 if (inc(v).size() <= 1)
10566 continue;
10567 quicksort_op(inc(v), [&](size_t a, size_t b)
10568 {
10569 const Geom_Number dxa = verts(he(a).target).get_x() - verts(v).get_x();
10570 const Geom_Number dya = verts(he(a).target).get_y() - verts(v).get_y();
10571 const Geom_Number dxb = verts(he(b).target).get_x() - verts(v).get_x();
10572 const Geom_Number dyb = verts(he(b).target).get_y() - verts(v).get_y();
10573 return angle_lt(dxa, dya, dxb, dyb);
10574 });
10575 }
10576
10577 // Compute "next" pointers.
10578 // For half-edge h = (u→v), the next half-edge around the face is:
10579 // at vertex v, take the twin of h (which is v→u), find its position
10580 // in the sorted incidence list of v, go one step CW (previous in CCW
10581 // order) → that's the next half-edge of the face.
10582 //
10583 // Equivalently: next(h) where h = u→v:
10584 // twin(h) = v→u. Find twin(h) in inc(v). The previous entry in
10585 // inc(v) (wrapping) is the next half-edge of the face.
10586 for (size_t v = 0; v < nv; ++v)
10587 {
10588 const auto &list = inc(v);
10589 const size_t sz = list.size();
10590 if (sz == 0)
10591 continue;
10592
10593 for (size_t i = 0; i < sz; ++i)
10594 {
10595 // Half-edge list(i) leaves v. Its twin arrives at v.
10596 // The twin is the "incoming" half-edge. The face's "next"
10597 // after the twin is the one that comes just before list(i)
10598 // in the CCW angular order, i.e. the one CW from it.
10599 // So: next(twin(list(i))) = list((i+1) % sz)
10600 // wrong — let me re-derive.
10601 //
10602 // We have inc(v) sorted CCW: h0, h1, ..., h_{k-1} (outgoing).
10603 // For outgoing h_i (= v → t_i), twin(h_i) = t_i → v (incoming).
10604 // The face containing twin(h_i) continues with the next outgoing
10605 // half-edge at v going CW from the incoming direction.
10606 //
10607 // Incoming direction of twin(h_i) at v: from t_i toward v.
10608 // This is the reverse of h_i's outgoing direction.
10609 // In the CCW list, h_i is at position i. The CW-next after
10610 // the reversed direction of h_i corresponds to h_{(i-1+k)%k}.
10611 //
10612 // So: next(twin(h_i)) = h_{(i-1+k) % k}.
10613
10614 const size_t prev = (i == 0) ? sz - 1 : i - 1;
10615 he(he(list(i)).twin).next = list(prev);
10616 }
10617 }
10618
10619 // --- Face traversal ---
10620 Array<bool> visited;
10621 visited.reserve(nhe);
10622 for (size_t i = 0; i < nhe; ++i)
10623 visited.append(false);
10624
10625 for (size_t h = 0; h < nhe; ++h)
10626 {
10627 if (visited(h))
10628 continue;
10629
10630 ArrFace face;
10631 face.unbounded = false;
10632
10633 // Trace the face boundary.
10634 size_t cur = h;
10635 Geom_Number signed_area = 0;
10636 do
10637 {
10638 visited(cur) = true;
10639 face.boundary.append(he(cur).origin);
10640
10641 // Accumulate signed area (shoelace).
10642 const Point &p1 = verts(he(cur).origin);
10643 const Point &p2 = verts(he(cur).target);
10644 signed_area += p1.get_x() * p2.get_y() - p2.get_x() * p1.get_y();
10645
10646 cur = he(cur).next;
10647 } while (cur != h and cur != NONE);
10648
10649 // CW winding (negative area) → unbounded face.
10650 if (signed_area < 0)
10651 face.unbounded = true;
10652
10653 ret.faces.append(face);
10654 }
10655
10656 // Ensure exactly one unbounded face is marked.
10657 // If none was found (e.g. all half-edges form CCW cycles, which
10658 // happens when all segments are isolated non-intersecting ones),
10659 // mark the face with the largest absolute signed area as unbounded.
10660 {
10661 bool has_ub = false;
10662 for (size_t i = 0; i < ret.faces.size(); ++i)
10663 if (ret.faces(i).unbounded)
10664 {
10665 has_ub = true;
10666 break;
10667 }
10668 if (not has_ub and not ret.faces.is_empty())
10669 ret.faces(0).unbounded = true;
10670 }
10671
10672 return ret;
10673 }
10674};
10675
10676// ============================================================================
10677// Alpha Shapes — Generalization of Convex Hull
10678// ============================================================================
10679
10713{
10714public:
10725
10737 {
10738 constexpr DelaunayTriangulationBowyerWatson delaunay;
10739 auto [sites, triangles] = delaunay(points);
10740
10741 Result ret;
10742 ret.sites = sites;
10743
10744 if (triangles.size() == 0)
10745 return ret;
10746
10747 // Filter triangles by circumradius².
10749 kept.reserve(triangles.size());
10750 for (size_t t = 0; t < triangles.size(); ++t)
10751 {
10752 const auto &tri = triangles(t);
10753 const Point &a = sites(tri.i);
10754 const Point &b = sites(tri.j);
10755 const Point &c = sites(tri.k);
10756
10757 // Circumradius² = (|AB|²|BC|²|CA|²) / (16 * area²)
10758 const Geom_Number ab2 = a.distance_squared_to(b);
10759 const Geom_Number bc2 = b.distance_squared_to(c);
10760 const Geom_Number ca2 = c.distance_squared_to(a);
10761 const Geom_Number area2x = area_of_parallelogram(a, b, c);
10763
10764 // circumradius² = ab2 * bc2 * ca2 / (4 * four_area_sq)
10765 // Compare: ab2 * bc2 * ca2 ≤ alpha_squared * 4 * four_area_sq
10766 const bool pass = ab2 * bc2 * ca2 <= alpha_squared * four_area_sq * 4;
10767 kept.append(pass);
10768 if (pass)
10769 ret.triangles.append(tri);
10770 }
10771
10772 // Extract boundary edges: edges appearing in exactly one kept triangle.
10773 // Use a simple count map via sorted edge keys.
10777 struct EdgeKey
10778 {
10779 size_t u, v;
10780
10782 bool operator == (const EdgeKey &o) const
10783 {
10784 return u == o.u and v == o.v;
10785 }
10786
10787 bool operator < (const EdgeKey &o) const
10788 {
10789 return u < o.u or (u == o.u and v < o.v);
10790 }
10791 };
10792
10793 auto make_key = [](const size_t a, const size_t b) -> EdgeKey
10794 {
10795 return a < b ? EdgeKey{a, b} : EdgeKey{b, a};
10796 };
10797
10799 all_edges.reserve(ret.triangles.size() * 3);
10800 for (size_t t = 0; t < ret.triangles.size(); ++t)
10801 {
10802 const auto &[i, j, k] = ret.triangles(t);
10803 all_edges.append(make_key(i, j));
10804 all_edges.append(make_key(j, k));
10805 all_edges.append(make_key(k, i));
10806 }
10807
10808 quicksort_op(all_edges, [](const EdgeKey &a, const EdgeKey &b)
10809 {
10810 return a < b;
10811 });
10812
10813 for (size_t i = 0; i < all_edges.size();)
10814 {
10815 size_t j = i + 1;
10816 while (j < all_edges.size() and all_edges(j) == all_edges(i))
10817 ++j;
10818 if (j - i == 1) // appears exactly once → boundary
10819 ret.boundary_edges.append(Segment(ret.sites(all_edges(i).u), ret.sites(all_edges(i).v)));
10820 i = j;
10821 }
10822
10823 return ret;
10824 }
10825};
10826
10827// ============================================================================
10828// Power Diagram — Weighted Voronoi
10829// ============================================================================
10830
10874{
10875public:
10884
10889 {
10890 size_t site_u;
10891 size_t site_v;
10896 };
10897
10909
10920
10927 const WeightedSite &b,
10928 const WeightedSite &c)
10929 {
10930 // Power center solves:
10931 // ||p - a||² - wa = ||p - b||² - wb = ||p - c||² - wc
10932 // Expanding: 2(bx-ax)px + 2(by-ay)py = bx²+by²-ax²-ay² - (wb - wa)
10933 // 2(cx-ax)px + 2(cy-ay)py = cx²+cy²-ax²-ay² - (wc - wa)
10934 const Geom_Number &ax = a.position.get_x();
10935 const Geom_Number &ay = a.position.get_y();
10936 const Geom_Number &bx = b.position.get_x();
10937 const Geom_Number &by = b.position.get_y();
10938 const Geom_Number &cx = c.position.get_x();
10939 const Geom_Number &cy = c.position.get_y();
10940
10941 const Geom_Number d1x = bx - ax, d1y = by - ay;
10942 const Geom_Number d2x = cx - ax, d2y = cy - ay;
10943
10944 const Geom_Number rhs1 = (bx * bx + by * by - ax * ax - ay * ay - (b.weight - a.weight)) / 2;
10945 const Geom_Number rhs2 = (cx * cx + cy * cy - ax * ax - ay * ay - (c.weight - a.weight)) / 2;
10946
10947 const Geom_Number det = d1x * d2y - d1y * d2x;
10948 ah_domain_error_if(det == 0) << "Degenerate configuration";
10949
10950 return {(rhs1 * d2y - rhs2 * d1y) / det, (d1x * rhs2 - d2x * rhs1) / det};
10951 }
10952
10963 {
10964 Result ret;
10965 const size_t n = sites.size();
10966 if (n == 0)
10967 return ret;
10968
10969 ret.sites = sites;
10970
10971 // Compute regular triangulation (weighted Delaunay).
10973 rt_input.reserve(n);
10974 for (size_t i = 0; i < n; ++i)
10975 rt_input.append({sites(i).position, sites(i).weight});
10976
10978 auto rt = reg_tri(rt_input);
10979
10980 if (rt.triangles.size() == 0)
10981 return ret;
10982
10983 // Build a mapping from regular-triangulation site indices to ours.
10984 // rt.sites may have been reordered/deduped, so match by position.
10986 rt_to_ours.reserve(rt.sites.size());
10987 for (size_t ri = 0; ri < rt.sites.size(); ++ri)
10988 {
10989 size_t match = 0;
10990 for (size_t oi = 0; oi < n; ++oi)
10991 if (rt.sites(ri).position == sites(oi).position)
10992 {
10993 match = oi;
10994 break;
10995 }
10996 rt_to_ours.append(match);
10997 }
10998
10999 // Compute power centers for each regular triangle.
11001 pcenters.reserve(rt.triangles.size());
11002 for (size_t t = 0; t < rt.triangles.size(); ++t)
11003 {
11004 const auto &tri = rt.triangles(t);
11005 pcenters.append(power_center(sites(rt_to_ours(tri.i)), sites(rt_to_ours(tri.j)),
11006 sites(rt_to_ours(tri.k))));
11007 }
11008
11009 ret.vertices = pcenters;
11010
11011 // Identify hull sites: sites on boundary edges (edges shared by
11012 // exactly one triangle) have unbounded power cells.
11014
11016 rt.triangles,
11017 [&](const Array<GeomTriangleAdjacencyUtils::EdgeRef> &edge_refs, const size_t first,
11018 const size_t last)
11019 {
11020 if (last - first == 1)
11021 {
11022 // Boundary edge: both sites are on the convex hull.
11023 hull_sites.insert(rt_to_ours(edge_refs(first).u));
11024 hull_sites.insert(rt_to_ours(edge_refs(first).v));
11025 }
11026
11027 // In a manifold triangulation this group has exactly 1 or 2 refs.
11028 // If degeneracies produce >2, connect all pairs defensively.
11029 if (last - first >= 2)
11030 for (size_t a = first; a + 1 < last; ++a)
11031 for (size_t b = a + 1; b < last; ++b)
11032 {
11033 PowerEdge pe;
11034 pe.site_u = rt_to_ours(edge_refs(first).u);
11035 pe.site_v = rt_to_ours(edge_refs(first).v);
11036 pe.src = pcenters(edge_refs(a).tri);
11037 pe.tgt = pcenters(edge_refs(b).tri);
11038 pe.unbounded = false;
11039 pe.direction = Point(0, 0);
11040 ret.edges.append(pe);
11041 }
11042 });
11043
11044 // Build cells: hull sites have unbounded cells.
11045 ret.cells.reserve(n);
11046 for (size_t s = 0; s < n; ++s)
11047 {
11048 PowerCell cell;
11049 cell.site_index = s;
11050 cell.site = sites(s).position;
11051 cell.weight = sites(s).weight;
11052 cell.bounded = hull_sites.search(s) == nullptr;
11053
11054 // Collect power centers of triangles incident to this site.
11055 for (size_t t = 0; t < rt.triangles.size(); ++t)
11056 {
11057 const auto &[i, j, k] = rt.triangles(t);
11058 if (rt_to_ours(i) == s or rt_to_ours(j) == s or rt_to_ours(k) == s)
11059 cell.vertices.append(pcenters(t));
11060 }
11061
11062 ret.cells.append(cell);
11063 }
11064
11065 return ret;
11066 }
11067};
11068
11069// ============================================================================
11070// Bezier Curves
11071// ============================================================================
11072
11093{
11094public:
11096 [[nodiscard]] static Point quadratic(const Point &p0,
11097 const Point &p1,
11098 const Point &p2,
11099 const Geom_Number &t)
11100 {
11101 const Geom_Number s = Geom_Number(1) - t;
11102 const Geom_Number s2 = s * s;
11103 const Geom_Number t2 = t * t;
11104 const Geom_Number st2 = Geom_Number(2) * s * t;
11105
11106 return {s2 * p0.get_x() + st2 * p1.get_x() + t2 * p2.get_x(),
11107 s2 * p0.get_y() + st2 * p1.get_y() + t2 * p2.get_y()};
11108 }
11109
11111 [[nodiscard]] static Point cubic(const Point &p0,
11112 const Point &p1,
11113 const Point &p2,
11114 const Point &p3,
11115 const Geom_Number &t)
11116 {
11117 const Geom_Number s = Geom_Number(1) - t;
11118 const Geom_Number s2 = s * s;
11119 const Geom_Number s3 = s2 * s;
11120 const Geom_Number t2 = t * t;
11121 const Geom_Number t3 = t2 * t;
11122 const Geom_Number c1 = Geom_Number(3) * s2 * t;
11123 const Geom_Number c2 = Geom_Number(3) * s * t2;
11124
11125 return {s3 * p0.get_x() + c1 * p1.get_x() + c2 * p2.get_x() + t3 * p3.get_x(),
11126 s3 * p0.get_y() + c1 * p1.get_y() + c2 * p2.get_y() + t3 * p3.get_y()};
11127 }
11128
11131 const Point &p1,
11132 const Point &p2,
11133 const size_t n)
11134 {
11135 ah_domain_error_if(n == 0) << "Need at least 1 subdivision";
11137 pts.reserve(n + 1);
11138 for (size_t i = 0; i <= n; ++i)
11139 pts.append(quadratic(p0, p1, p2,
11140 Geom_Number(static_cast<long>(i)) / Geom_Number(static_cast<long>(n))));
11141 return pts;
11142 }
11143
11146 const Point &p1,
11147 const Point &p2,
11148 const Point &p3,
11149 const size_t n)
11150 {
11151 ah_domain_error_if(n == 0) << "Need at least 1 subdivision";
11153 pts.reserve(n + 1);
11154 for (size_t i = 0; i <= n; ++i)
11155 pts.append(cubic(p0, p1, p2, p3,
11156 Geom_Number(static_cast<long>(i)) / Geom_Number(static_cast<long>(n))));
11157 return pts;
11158 }
11159
11163 {
11166 };
11167
11169 const Point &p1,
11170 const Point &p2,
11171 const Point &p3,
11172 const Geom_Number &t)
11173 {
11174 auto lerp = [&](const Point &a, const Point &b) -> Point
11175 {
11176 const Geom_Number s = Geom_Number(1) - t;
11177 return {s * a.get_x() + t * b.get_x(), s * a.get_y() + t * b.get_y()};
11178 };
11179
11180 const Point q0 = lerp(p0, p1);
11181 const Point q1 = lerp(p1, p2);
11182 const Point q2 = lerp(p2, p3);
11183 const Point r0 = lerp(q0, q1);
11184 const Point r1 = lerp(q1, q2);
11185 const Point s0 = lerp(r0, r1);
11186
11188 sr.left[0] = p0;
11189 sr.left[1] = q0;
11190 sr.left[2] = r0;
11191 sr.left[3] = s0;
11192 sr.right[0] = s0;
11193 sr.right[1] = r1;
11194 sr.right[2] = q2;
11195 sr.right[3] = p3;
11196 return sr;
11197 }
11198
11201 const Point &p1,
11202 const Point &p2,
11203 const Point &p3)
11204 {
11205 Geom_Number mnx = p0.get_x(), mxx = p0.get_x();
11206 Geom_Number mny = p0.get_y(), mxy = p0.get_y();
11207 auto update = [&](const Point &p)
11208 {
11209 if (p.get_x() < mnx)
11210 mnx = p.get_x();
11211 if (p.get_x() > mxx)
11212 mxx = p.get_x();
11213 if (p.get_y() < mny)
11214 mny = p.get_y();
11215 if (p.get_y() > mxy)
11216 mxy = p.get_y();
11217 };
11218 update(p1);
11219 update(p2);
11220 update(p3);
11221 return {mnx, mny, mxx, mxy};
11222 }
11223
11226 const Point &p1,
11227 const Point &p2,
11228 const size_t n)
11229 {
11230 auto pts = sample_quadratic(p0, p1, p2, n);
11231 Polygon poly;
11232 for (size_t i = 0; i < pts.size(); ++i)
11233 poly.add_vertex(pts(i));
11234 return poly;
11235 }
11236
11239 const Point &p1,
11240 const Point &p2,
11241 const Point &p3,
11242 const size_t n)
11243 {
11244 auto pts = sample_cubic(p0, p1, p2, p3, n);
11245 Polygon poly;
11246 for (size_t i = 0; i < pts.size(); ++i)
11247 poly.add_vertex(pts(i));
11248 return poly;
11249 }
11250};
11251
11252// ============================================================================
11253// Boolean Polygon Operations
11254// ============================================================================
11255
11299{
11300public:
11304 enum class Op
11305 {
11306 INTERSECTION,
11307 UNION,
11308 DIFFERENCE
11309 };
11310
11320 [[nodiscard]] Array<Polygon> operator () (const Polygon &a, const Polygon &b, Op op) const
11321 {
11322 ah_domain_error_if(not a.is_closed()) << "First polygon must be closed";
11323 ah_domain_error_if(not b.is_closed()) << "Second polygon must be closed";
11324 ah_domain_error_if(a.size() < 3) << "First polygon must have >= 3 vertices";
11325 ah_domain_error_if(b.size() < 3) << "Second polygon must have >= 3 vertices";
11326
11327 switch (op)
11328 {
11329 case Op::INTERSECTION:
11330 return compute_intersection(a, b);
11331 case Op::UNION:
11332 return compute_union(a, b);
11333 case Op::DIFFERENCE:
11334 return compute_difference(a, b);
11335 }
11336 return {};
11337 }
11338
11341 {
11342 return (*this)(a, b, Op::INTERSECTION);
11343 }
11344
11347 {
11348 return (*this)(a, b, Op::UNION);
11349 }
11350
11352 [[nodiscard]] Array<Polygon> difference(const Polygon &a, const Polygon &b) const
11353 {
11354 return (*this)(a, b, Op::DIFFERENCE);
11355 }
11356
11357private:
11358 // ----- Greiner-Hormann node for augmented polygon vertex list -----
11359
11360 struct GHNode
11361 {
11363 bool intersect = false;
11364 bool entering = false;
11365 size_t neighbor = SIZE_MAX; // index in the other polygon's list
11366 bool visited = false;
11367 Geom_Number alpha = 0; // parameter along the original edge
11368 };
11369
11370 // ----- helpers -----
11371
11374 {
11376 }
11377
11379 static void ensure_ccw(Array<Point> &v)
11380 {
11382 }
11383
11386 {
11387 for (size_t i = 0, j = v.size() - 1; i < j; ++i, --j)
11388 {
11389 const Point tmp = v(i);
11390 v(i) = v(j);
11391 v(j) = tmp;
11392 }
11393 }
11394
11397 {
11398 Polygon p;
11399 for (size_t i = 0; i < pts.size(); ++i)
11400 p.add_vertex(pts(i));
11401 if (pts.size() >= 3)
11402 p.close();
11403 return p;
11404 }
11405
11407 [[nodiscard]] static Geom_Number edge_param(const Point &P, const Point &Q, const Point &I)
11408 {
11409 const Geom_Number dx = Q.get_x() - P.get_x();
11410 if (dx != 0)
11411 return (I.get_x() - P.get_x()) / dx;
11412 return (I.get_y() - P.get_y()) / (Q.get_y() - P.get_y());
11413 }
11414
11416 [[nodiscard]] static bool point_inside_ccw(const Array<Point> &poly, const Point &p)
11417 {
11418 int winding = 0;
11419 const size_t n = poly.size();
11420 for (size_t i = 0; i < n; ++i)
11421 {
11422 const Point &a = poly(i);
11423 const Point &b = poly((i + 1) % n);
11424 const Segment edge(a, b);
11425 if (edge.contains(p))
11426 return true;
11427 if (a.get_y() <= p.get_y())
11428 {
11429 if (b.get_y() > p.get_y() && area_of_parallelogram(a, b, p) > 0)
11430 ++winding;
11431 }
11432 else
11433 {
11434 if (b.get_y() <= p.get_y() && area_of_parallelogram(a, b, p) < 0)
11435 --winding;
11436 }
11437 }
11438 return winding != 0;
11439 }
11440
11443 {
11444 for (size_t i = 1; i < idx.size(); ++i)
11445 {
11446 const size_t val = idx(i);
11447 const Geom_Number &key = keys(val);
11448 size_t j = i;
11449 while (j > 0 && keys(idx(j - 1)) > key)
11450 {
11451 idx(j) = idx(j - 1);
11452 --j;
11453 }
11454 idx(j) = val;
11455 }
11456 }
11457
11458 // ----- Core Greiner-Hormann algorithm -----
11459
11462 {
11466 };
11467
11474 const Array<Point> &vb,
11475 bool start_entering)
11476 {
11477 const size_t na = va.size();
11478 const size_t nb = vb.size();
11479
11480 // --- Phase 1: find all proper intersection pairs ---
11481
11482 Array<IsectPair> pairs;
11483
11484 for (size_t i = 0; i < na; ++i)
11485 {
11486 const Point &a0 = va(i);
11487 const Point &a1 = va((i + 1) % na);
11488 const Segment sa(a0, a1);
11489
11490 for (size_t j = 0; j < nb; ++j)
11491 {
11492 const Point &b0 = vb(j);
11493 const Point &b1 = vb((j + 1) % nb);
11494 const Segment sb(b0, b1);
11495
11496 if (not sa.intersects_properly_with(sb))
11497 continue;
11498
11499 const Point ip = sa.intersection_with(sb);
11500 const Geom_Number aa = edge_param(a0, a1, ip);
11501 const Geom_Number ab = edge_param(b0, b1, ip);
11502 pairs.append({ip, i, j, aa, ab});
11503 }
11504 }
11505
11506 // --- Phase 2: build augmented lists ---
11507
11509 // pair_idx_a[k] / pair_idx_b[k] = position in list_a / list_b for
11510 // intersection pair k.
11512 pair_idx_a.reserve(pairs.size());
11513 pair_idx_b.reserve(pairs.size());
11514 for (size_t k = 0; k < pairs.size(); ++k)
11515 {
11516 pair_idx_a.append(0);
11517 pair_idx_b.append(0);
11518 }
11519
11520 // Build list_a: for each edge, add original vertex then sorted
11521 // intersections.
11522 for (size_t i = 0; i < na; ++i)
11523 {
11524 GHNode vn;
11525 vn.pt = va(i);
11526 list_a.append(vn);
11527
11528 // Collect pairs on this edge of A.
11531 for (size_t k = 0; k < pairs.size(); ++k)
11532 if (pairs(k).edge_a == i)
11533 {
11535 edge_alphas.append(pairs(k).alpha_a);
11536 }
11537
11538 // Sort by alpha.
11539 Array<size_t> order;
11540 for (size_t p = 0; p < edge_pairs.size(); ++p)
11541 order.append(p);
11543
11544 for (size_t p = 0; p < order.size(); ++p)
11545 {
11546 const size_t k = edge_pairs(order(p));
11547 pair_idx_a(k) = list_a.size();
11548 GHNode in;
11549 in.pt = pairs(k).pt;
11550 in.intersect = true;
11551 in.alpha = pairs(k).alpha_a;
11552 list_a.append(in);
11553 }
11554 }
11555
11556 // Build list_b analogously.
11557 for (size_t j = 0; j < nb; ++j)
11558 {
11559 GHNode vn;
11560 vn.pt = vb(j);
11561 list_b.append(vn);
11562
11565 for (size_t k = 0; k < pairs.size(); ++k)
11566 if (pairs(k).edge_b == j)
11567 {
11569 edge_alphas.append(pairs(k).alpha_b);
11570 }
11571
11572 Array<size_t> order;
11573 for (size_t p = 0; p < edge_pairs.size(); ++p)
11574 order.append(p);
11576
11577 for (size_t p = 0; p < order.size(); ++p)
11578 {
11579 const size_t k = edge_pairs(order(p));
11580 pair_idx_b(k) = list_b.size();
11581 GHNode in;
11582 in.pt = pairs(k).pt;
11583 in.intersect = true;
11584 in.alpha = pairs(k).alpha_b;
11585 list_b.append(in);
11586 }
11587 }
11588
11589 // Set neighbor links.
11590 for (size_t k = 0; k < pairs.size(); ++k)
11591 {
11592 list_a(pair_idx_a(k)).neighbor = pair_idx_b(k);
11593 list_b(pair_idx_b(k)).neighbor = pair_idx_a(k);
11594 }
11595
11596 // --- Phase 3: mark entry / exit ---
11597
11598 // Walk list_a; determine if the first original vertex of A is
11599 // inside B.
11600 {
11601 bool inside = point_inside_ccw(vb, va(0));
11602 for (size_t i = 0; i < list_a.size(); ++i)
11603 if (list_a(i).intersect)
11604 {
11605 list_a(i).entering = !inside;
11606 inside = !inside;
11607 }
11608 }
11609
11610 // Walk list_b similarly w.r.t. A.
11611 {
11612 bool inside = point_inside_ccw(va, vb(0));
11613 for (size_t j = 0; j < list_b.size(); ++j)
11614 if (list_b(j).intersect)
11615 {
11616 list_b(j).entering = !inside;
11617 inside = !inside;
11618 }
11619 }
11620
11621 // --- Phase 4: traverse ---
11622
11623 Array<Polygon> result;
11624 const size_t safety_limit = list_a.size() + list_b.size() + pairs.size() * 2;
11625
11626 for (size_t s = 0; s < list_a.size(); ++s)
11627 {
11628 if (not list_a(s).intersect)
11629 continue;
11630 if (list_a(s).visited)
11631 continue;
11632 if (list_a(s).entering != start_entering)
11633 continue;
11634
11635 // Trace one result polygon.
11636 Array<Point> boundary;
11637 bool on_a = true;
11638 size_t idx = s;
11639 size_t steps = 0;
11640
11641 do
11642 {
11643 auto &cur_list = on_a ? list_a : list_b;
11644
11645 // Mark current intersection visited (and its partner).
11646 cur_list(idx).visited = true;
11647 if (cur_list(idx).neighbor != SIZE_MAX)
11648 {
11649 auto &other = on_a ? list_b : list_a;
11650 other(cur_list(idx).neighbor).visited = true;
11651 }
11652
11653 // Record this intersection point.
11654 boundary.append(cur_list(idx).pt);
11655
11656 // Walk forward until the next intersection node.
11657 idx = (idx + 1) % cur_list.size();
11658 while (not cur_list(idx).intersect)
11659 {
11660 boundary.append(cur_list(idx).pt);
11661 idx = (idx + 1) % cur_list.size();
11662 if (++steps > safety_limit)
11663 goto done_poly;
11664 }
11665
11666 // idx is now at the next intersection — mark it and switch.
11667 cur_list(idx).visited = true;
11668 {
11669 auto &other = on_a ? list_b : list_a;
11670 const size_t nbr = cur_list(idx).neighbor;
11671 other(nbr).visited = true;
11672 on_a = !on_a;
11673 idx = nbr;
11674 }
11675
11676 if (++steps > safety_limit)
11677 break;
11678 } while (!(on_a && idx == s));
11679
11680 done_poly:
11681 if (boundary.size() >= 3)
11682 result.append(build_poly(boundary));
11683 }
11684
11685 return result;
11686 }
11687
11688 // ----- Operation dispatchers -----
11689
11700 {
11701 Array<Point> va = extract(a);
11702 Array<Point> vb = extract(b);
11703 ensure_ccw(va);
11704 ensure_ccw(vb);
11705
11706 Array<Polygon> res = greiner_hormann(va, vb, /*start_entering=*/true);
11707
11708 if (not res.is_empty())
11709 return res;
11710
11711 // No proper intersections — check containment.
11712 if (point_inside_ccw(vb, va(0)))
11713 {
11714 // A entirely inside B → intersection is A.
11715 res.append(a);
11716 return res;
11717 }
11718 if (point_inside_ccw(va, vb(0)))
11719 {
11720 // B entirely inside A → intersection is B.
11721 res.append(b);
11722 return res;
11723 }
11724 // Disjoint.
11725 return res;
11726 }
11727
11737 static Array<Polygon> compute_union(const Polygon &a, const Polygon &b)
11738 {
11739 Array<Point> va = extract(a);
11740 Array<Point> vb = extract(b);
11741 ensure_ccw(va);
11742 ensure_ccw(vb);
11743
11744 Array<Polygon> res = greiner_hormann(va, vb, /*start_entering=*/false);
11745
11746 if (!res.is_empty())
11747 return res;
11748
11749 // No proper intersections — check containment.
11750 if (point_inside_ccw(vb, va(0)))
11751 {
11752 // A inside B → union is B.
11753 res.append(b);
11754 return res;
11755 }
11756 if (point_inside_ccw(va, vb(0)))
11757 {
11758 // B inside A → union is A.
11759 res.append(a);
11760 return res;
11761 }
11762 // Disjoint → both.
11763 res.append(a);
11764 res.append(b);
11765 return res;
11766 }
11767
11778 {
11779 Array<Point> va = extract(a);
11780 Array<Point> vb = extract(b);
11781 ensure_ccw(va);
11782 ensure_ccw(vb);
11783
11784 // A \ B = A ∩ complement(B). Reversing B's winding makes
11785 // "inside reversed-B" = "outside B", so intersection with
11786 // reversed-B gives the difference.
11788
11789 Array<Polygon> res = greiner_hormann(va, vb, /*start_entering=*/true);
11790
11791 if (!res.is_empty())
11792 return res;
11793
11794 // No proper intersections — check containment using ORIGINAL B.
11795 // Reverse vb back to original orientation for the test.
11797
11798 if (point_inside_ccw(vb, va(0)))
11799 {
11800 // A entirely inside B → difference is empty.
11801 return res;
11802 }
11803 // B inside A → difference has a hole (limitation: return A).
11804 // Disjoint → difference is A.
11805 res.append(a);
11806 return res;
11807 }
11808};
11809
11810// ============================================================================
11811// Serialization — WKT and GeoJSON
11812// ============================================================================
11813
11839{
11843 static std::string dbl(const Geom_Number &n)
11844 {
11845 std::ostringstream os;
11846 os << std::setprecision(15) << geom_number_to_double(n);
11847 return os.str();
11848 }
11849
11850public:
11851 // ---- WKT (Well-Known Text) ----
11852
11856 [[nodiscard]] static std::string to_wkt(const Point &p)
11857 {
11858 return "POINT (" + dbl(p.get_x()) + " " + dbl(p.get_y()) + ")";
11859 }
11860
11864 [[nodiscard]] static std::string to_wkt(const Segment &s)
11865 {
11866 return "LINESTRING (" + dbl(s.get_src_point().get_x()) + " " + dbl(s.get_src_point().get_y()) +
11867 ", " + dbl(s.get_tgt_point().get_x()) + " " + dbl(s.get_tgt_point().get_y()) + ")";
11868 }
11869
11873 [[nodiscard]] static std::string to_wkt(const Triangle &t)
11874 {
11875 auto pt = [](const Point &p)
11876 {
11877 return dbl(p.get_x()) + " " + dbl(p.get_y());
11878 };
11879 return "POLYGON ((" + pt(t.get_p1()) + ", " + pt(t.get_p2()) + ", " + pt(t.get_p3()) + ", " +
11880 pt(t.get_p1()) + "))";
11881 }
11882
11887 [[nodiscard]] static std::string to_wkt(const Rectangle &r)
11888 {
11889 return "POLYGON ((" + dbl(r.get_xmin()) + " " + dbl(r.get_ymin()) + ", " + dbl(r.get_xmax()) +
11890 " " + dbl(r.get_ymin()) + ", " + dbl(r.get_xmax()) + " " + dbl(r.get_ymax()) + ", " +
11891 dbl(r.get_xmin()) + " " + dbl(r.get_ymax()) + ", " + dbl(r.get_xmin()) + " " +
11892 dbl(r.get_ymin()) + "))";
11893 }
11894
11898 [[nodiscard]] static std::string to_wkt(const Polygon &poly)
11899 {
11900 std::ostringstream os;
11901 os << std::setprecision(15) << "POLYGON ((";
11902 bool first = true;
11904 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
11905 {
11906 const Point &v = it.get_current_vertex();
11907 if (first)
11908 {
11909 first_pt = v;
11910 first = false;
11911 }
11912 else
11913 os << ", ";
11915 }
11916 if (not first)
11917 os << ", " << geom_number_to_double(first_pt.get_x()) << " "
11918 << geom_number_to_double(first_pt.get_y());
11919 os << "))";
11920 return os.str();
11921 }
11922
11926 [[nodiscard]] static std::string to_wkt(const Point3D &p)
11927 {
11928 return "POINT Z (" + dbl(p.get_x()) + " " + dbl(p.get_y()) + " " + dbl(p.get_z()) + ")";
11929 }
11930
11931 // ---- GeoJSON ----
11932
11936 [[nodiscard]] static std::string to_geojson(const Point &p)
11937 {
11938 return R"({"type":"Point","coordinates":[)" + dbl(p.get_x()) + "," + dbl(p.get_y()) + "]}";
11939 }
11940
11944 [[nodiscard]] static std::string to_geojson(const Segment &s)
11945 {
11946 return R"({"type":"LineString","coordinates":[[)" + dbl(s.get_src_point().get_x()) + "," +
11947 dbl(s.get_src_point().get_y()) + "],[" + dbl(s.get_tgt_point().get_x()) + "," +
11948 dbl(s.get_tgt_point().get_y()) + "]]}";
11949 }
11950
11952 [[nodiscard]] static std::string to_geojson(const Triangle &t)
11953 {
11954 auto pt = [](const Point &p)
11955 {
11956 return "[" + dbl(p.get_x()) + "," + dbl(p.get_y()) + "]";
11957 };
11958 return R"({"type":"Polygon","coordinates":[[)" + pt(t.get_p1()) + "," + pt(t.get_p2()) + "," +
11959 pt(t.get_p3()) + "," + pt(t.get_p1()) + "]]}";
11960 }
11961
11963 [[nodiscard]] static std::string to_geojson(const Polygon &poly)
11964 {
11965 std::ostringstream os;
11966 os << std::setprecision(15);
11967 os << R"({"type":"Polygon","coordinates":[[)";
11968 bool first = true;
11970 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
11971 {
11972 const Point &v = it.get_current_vertex();
11973 if (first)
11974 {
11975 first_pt = v;
11976 first = false;
11977 }
11978 else
11979 os << ",";
11980 os << "[" << geom_number_to_double(v.get_x()) << "," << geom_number_to_double(v.get_y())
11981 << "]";
11982 }
11983 if (not first)
11984 os << ",[" << geom_number_to_double(first_pt.get_x()) << ","
11985 << geom_number_to_double(first_pt.get_y()) << "]";
11986 os << "]]}";
11987 return os.str();
11988 }
11989
11991 [[nodiscard]] static std::string to_geojson(const Point3D &p)
11992 {
11993 return R"({"type":"Point","coordinates":[)" + dbl(p.get_x()) + "," + dbl(p.get_y()) + "," +
11994 dbl(p.get_z()) + "]}";
11995 }
11996};
11997
11998// ============================================================================
11999// AABB Tree — Axis-Aligned Bounding Box Tree
12000// ============================================================================
12001
12036{
12037public:
12041 struct Entry
12042 {
12044 size_t index;
12045 };
12046
12061
12070
12071private:
12075 struct Node
12076 {
12081
12085 [[nodiscard]] bool is_leaf() const
12086 {
12087 return entry_idx != ~static_cast<size_t>(0);
12088 }
12089 };
12090
12093
12094 static constexpr size_t NONE = ~static_cast<size_t>(0);
12095
12099 [[nodiscard]] static Rectangle union_bbox(const Rectangle &a, const Rectangle &b)
12100 {
12101 const Geom_Number xmin = a.get_xmin() < b.get_xmin() ? a.get_xmin() : b.get_xmin();
12102 const Geom_Number ymin = a.get_ymin() < b.get_ymin() ? a.get_ymin() : b.get_ymin();
12103 const Geom_Number xmax = a.get_xmax() > b.get_xmax() ? a.get_xmax() : b.get_xmax();
12104 const Geom_Number ymax = a.get_ymax() > b.get_ymax() ? a.get_ymax() : b.get_ymax();
12105 return {xmin, ymin, xmax, ymax};
12106 }
12107
12111 [[nodiscard]] static bool boxes_overlap(const Rectangle &a, const Rectangle &b)
12112 {
12113 return a.get_xmin() <= b.get_xmax() and a.get_xmax() >= b.get_xmin() and
12114 a.get_ymin() <= b.get_ymax() and a.get_ymax() >= b.get_ymin();
12115 }
12116
12120 [[nodiscard]] static bool box_contains_point(const Rectangle &r, const Point &p)
12121 {
12122 return p.get_x() >= r.get_xmin() and p.get_x() <= r.get_xmax() and p.get_y() >= r.get_ymin() and
12123 p.get_y() <= r.get_ymax();
12124 }
12125
12126 [[nodiscard]] Geom_Number center_coord(const size_t entry_idx, const bool split_x) const
12127 {
12128 return split_x ? entries_(entry_idx).bbox.get_xmin() + entries_(entry_idx).bbox.get_xmax()
12129 : entries_(entry_idx).bbox.get_ymin() + entries_(entry_idx).bbox.get_ymax();
12130 }
12131
12133 const size_t lo,
12134 const size_t hi,
12135 const bool split_x) const
12136 {
12137 for (size_t i = lo + 1; i < hi; ++i)
12138 {
12139 const size_t key = idx(i);
12140 const Geom_Number key_val = center_coord(key, split_x);
12141 size_t j = i;
12142 while (j > lo and center_coord(idx(j - 1), split_x) > key_val)
12143 {
12144 idx(j) = idx(j - 1);
12145 --j;
12146 }
12147 idx(j) = key;
12148 }
12149 }
12150
12151 // Partially reorder idx[lo..hi) so idx[kth] is in its sorted position
12152 // by center along the split axis (expected linear time).
12154 size_t lo,
12155 size_t hi,
12156 const size_t kth,
12157 const bool split_x) const
12158 {
12159 while (hi - lo > 16)
12160 {
12161 const Geom_Number pivot = center_coord(idx(lo + (hi - lo) / 2), split_x);
12162
12163 size_t lt = lo;
12164 size_t i = lo;
12165 size_t gt = hi;
12166 while (i < gt)
12167 {
12168 const Geom_Number val = center_coord(idx(i), split_x);
12169 if (val < pivot)
12170 {
12171 const size_t tmp = idx(lt);
12172 idx(lt) = idx(i);
12173 idx(i) = tmp;
12174 ++lt;
12175 ++i;
12176 }
12177 else if (val > pivot)
12178 {
12179 --gt;
12180 const size_t tmp = idx(gt);
12181 idx(gt) = idx(i);
12182 idx(i) = tmp;
12183 }
12184 else
12185 ++i;
12186 }
12187
12188 if (kth < lt)
12189 hi = lt;
12190 else if (kth >= gt)
12191 lo = gt;
12192 else
12193 return;
12194 }
12195
12196 insertion_sort_by_center(idx, lo, hi, split_x);
12197 }
12198
12199 // Build a subtree for entries[lo..hi) and return its node index.
12200 size_t build(Array<size_t> &idx, const size_t lo, const size_t hi)
12201 {
12202 if (hi - lo == 1)
12203 {
12204 Node n;
12205 n.bbox = entries_(idx(lo)).bbox;
12206 n.entry_idx = idx(lo);
12207 const size_t ni = nodes_.size();
12208 nodes_.append(n);
12209 return ni;
12210 }
12211
12212 // Compute enclosing bbox.
12213 Rectangle total = entries_(idx(lo)).bbox;
12214 for (size_t i = lo + 1; i < hi; ++i)
12215 total = union_bbox(total, entries_(idx(i)).bbox);
12216
12217 // Split along the longest axis.
12218 const Geom_Number dx = total.get_xmax() - total.get_xmin();
12219 const Geom_Number dy = total.get_ymax() - total.get_ymin();
12220 const bool split_x = dx >= dy;
12221
12222 const size_t mid = lo + (hi - lo) / 2;
12223 select_kth_by_center(idx, lo, hi, mid, split_x);
12224
12225 const size_t left = build(idx, lo, mid);
12226 const size_t right = build(idx, mid, hi);
12227
12228 Node n;
12229 n.bbox = union_bbox(nodes_(left).bbox, nodes_(right).bbox);
12230 n.left = left;
12231 n.right = right;
12232 const size_t ni = nodes_.size();
12233 nodes_.append(n);
12234 return ni;
12235 }
12236
12237 void query_impl(const size_t ni, const Rectangle &q, Array<size_t> &results) const
12238 {
12239 if (ni == NONE)
12240 return;
12241 const Node &n = nodes_(ni);
12242 if (not boxes_overlap(n.bbox, q))
12243 return;
12244
12245 if (n.is_leaf())
12246 {
12247 results.append(entries_(n.entry_idx).index);
12248 return;
12249 }
12250
12251 query_impl(n.left, q, results);
12252 query_impl(n.right, q, results);
12253 }
12254
12255 void query_point_impl(const size_t ni, const Point &p, Array<size_t> &results) const
12256 {
12257 if (ni == NONE)
12258 return;
12259 const Node &n = nodes_(ni);
12260 if (not box_contains_point(n.bbox, p))
12261 return;
12262
12263 if (n.is_leaf())
12264 {
12265 if (box_contains_point(entries_(n.entry_idx).bbox, p))
12266 results.append(entries_(n.entry_idx).index);
12267 return;
12268 }
12269
12272 }
12273
12274 size_t root_ = NONE;
12275
12276public:
12277 AABBTree() = default;
12278
12284 void build(const Array<Entry> &entries)
12285 {
12286 entries_ = entries;
12287 nodes_ = Array<Node>();
12288 if (entries_.is_empty())
12289 {
12290 root_ = NONE;
12291 return;
12292 }
12293
12294 Array<size_t> idx;
12295 idx.reserve(entries_.size());
12296 for (size_t i = 0; i < entries_.size(); ++i)
12297 idx.append(i);
12298
12299 root_ = build(idx, 0, idx.size());
12300 }
12301
12309 {
12311 if (root_ != NONE)
12313 return results;
12314 }
12315
12323 {
12325 if (root_ != NONE)
12327 return results;
12328 }
12329
12332 {
12333 ah_domain_error_if(root_ == NONE) << "Empty tree";
12334 return nodes_(root_).bbox;
12335 }
12336
12338 [[nodiscard]] size_t size() const
12339 {
12340 return entries_.size();
12341 }
12342
12344 [[nodiscard]] bool is_empty() const
12345 {
12346 return entries_.is_empty();
12347 }
12348
12351 {
12353 if (root_ == NONE)
12354 return snap;
12355
12356 snap.nodes.reserve(nodes_.size());
12357
12358 auto dfs = [&](const auto &self, const size_t ni, const size_t depth) -> size_t
12359 {
12360 const Node &n = nodes_(ni);
12361 const size_t out_idx = snap.nodes.size();
12362 snap.nodes.append(DebugNode{});
12363 snap.nodes(out_idx).node_index = ni;
12364 snap.nodes(out_idx).bbox = n.bbox;
12365 snap.nodes(out_idx).is_leaf = n.is_leaf();
12366 snap.nodes(out_idx).depth = depth;
12367 if (n.is_leaf())
12368 {
12369 snap.nodes(out_idx).entry_index = n.entry_idx;
12370 snap.nodes(out_idx).user_index = entries_(n.entry_idx).index;
12371 }
12372 else
12373 {
12374 const size_t left = self(self, n.left, depth + 1);
12375 const size_t right = self(self, n.right, depth + 1);
12376 snap.nodes(out_idx).left = left;
12377 snap.nodes(out_idx).right = right;
12378 }
12379 return out_idx;
12380 };
12381
12382 snap.root = dfs(dfs, root_, 0);
12383 return snap;
12384 }
12385};
12386
12387// ============================================================================
12388// Polyline / Polygon Simplification
12389// ============================================================================
12390
12407{
12408 static void dp_recurse(const Array<Point> &pts,
12409 const size_t first,
12410 const size_t last,
12411 const Geom_Number &eps2,
12413 {
12414 if (last <= first + 1)
12415 return;
12416
12417 const Segment seg(pts(first), pts(last));
12419 size_t max_idx = first;
12420
12421 for (size_t i = first + 1; i < last; ++i)
12422 {
12423 Geom_Number d = seg.distance_to(pts(i));
12424 if (Geom_Number d2 = d * d; d2 > max_dist2)
12425 {
12426 max_dist2 = d2;
12427 max_idx = i;
12428 }
12429 }
12430
12431 if (max_dist2 > eps2)
12432 {
12433 keep(max_idx) = true;
12434 dp_recurse(pts, first, max_idx, eps2, keep);
12435 dp_recurse(pts, max_idx, last, eps2, keep);
12436 }
12437 }
12438
12439public:
12450 {
12451 const size_t n = polyline.size();
12452 if (n <= 2)
12453 return polyline;
12454
12455 const Geom_Number eps2 = epsilon * epsilon;
12456 Array<bool> keep(n);
12457 for (size_t i = 0; i < n; ++i)
12458 keep.append(false);
12459 keep(0) = true;
12460 keep(n - 1) = true;
12461
12462 dp_recurse(polyline, 0, n - 1, eps2, keep);
12463
12464 Array<Point> result;
12465 for (size_t i = 0; i < n; ++i)
12466 if (keep(i))
12467 result.append(polyline(i));
12468
12469 return result;
12470 }
12471
12474 const Geom_Number &epsilon) const
12475 {
12476 Array<Point> arr;
12477 for (typename DynList<Point>::Iterator it(polyline); it.has_curr(); it.next_ne())
12478 arr.append(it.get_curr());
12479 return (*this)(arr, epsilon);
12480 }
12481
12492 [[nodiscard]] Polygon simplify_polygon(const Polygon &poly, const Geom_Number &epsilon) const
12493 {
12495 const size_t n = verts.size();
12496 if (n <= 3)
12497 return poly;
12498
12499 // Find vertex farthest from vertex 0
12500 size_t far_idx = 1;
12502 for (size_t i = 1; i < n; ++i)
12503 {
12504 Geom_Number dx = verts(i).get_x() - verts(0).get_x();
12505 Geom_Number dy = verts(i).get_y() - verts(0).get_y();
12506 if (Geom_Number d2 = dx * dx + dy * dy; d2 > far_dist2)
12507 {
12508 far_dist2 = d2;
12509 far_idx = i;
12510 }
12511 }
12512
12513 // Build two open chains sharing endpoints at vertex 0 and far_idx
12514 Array<Point> chain1; // 0 → far_idx
12515 for (size_t i = 0; i <= far_idx; ++i)
12516 chain1.append(verts(i));
12517
12518 Array<Point> chain2; // far_idx → 0 (wrapping around)
12519 for (size_t i = far_idx; i < n; ++i)
12520 chain2.append(verts(i));
12521 chain2.append(verts(0));
12522
12523 auto s1 = (*this)(chain1, epsilon);
12524 auto s2 = (*this)(chain2, epsilon);
12525
12526 // Merge: s1 + s2 without duplicate junction points
12527 Array<Point> merged;
12528 for (size_t i = 0; i < s1.size(); ++i)
12529 merged.append(s1(i));
12530 for (size_t i = 1; i + 1 < s2.size(); ++i) // skip first (dup) & last (dup)
12531 merged.append(s2(i));
12532
12533 if (merged.size() < 3)
12534 return poly;
12535
12536 Polygon result;
12537 for (size_t i = 0; i < merged.size(); ++i)
12538 result.add_vertex(merged(i));
12539 result.close();
12540 return result;
12541 }
12542};
12543
12557{
12561 struct VWEntry
12562 {
12563 size_t idx;
12565
12567 bool operator < (const VWEntry &o) const
12568 {
12569 return area < o.area;
12570 }
12572 bool operator > (const VWEntry &o) const
12573 {
12574 return area > o.area;
12575 }
12576 };
12577
12578 [[nodiscard]] static Geom_Number effective_area(const Point &a, const Point &b, const Point &c)
12579 {
12581 if (ap < 0)
12582 ap = -ap;
12583 return ap / 2;
12584 }
12585
12588 {
12589 const size_t n = polyline.size();
12590 if (n <= 2)
12591 return polyline;
12592
12593 // prev/next linked list (index-based)
12594 Array<size_t> prev(n), next(n);
12595 Array<bool> alive(n);
12596 for (size_t i = 0; i < n; ++i)
12597 {
12598 prev.append(i == 0 ? n : i - 1); // n = sentinel for "no prev"
12599 next.append(i == n - 1 ? n : i + 1);
12600 alive.append(true);
12601 }
12602
12603 // Min-heap
12605
12606 // Compute initial areas for interior vertices
12608 for (size_t i = 0; i < n; ++i)
12609 areas.append(Geom_Number(-1));
12610
12611 for (size_t i = 1; i + 1 < n; ++i)
12612 {
12613 areas(i) = effective_area(polyline(prev(i)), polyline(i), polyline(next(i)));
12614 heap.put({i, areas(i)});
12615 }
12616
12617 while (not heap.is_empty())
12618 {
12619 auto [idx, area] = heap.get();
12620
12621 if (not alive(idx))
12622 continue;
12623 if (area != areas(idx))
12624 continue; // stale entry
12625 if (area >= area_threshold)
12626 break;
12627
12628 // Remove this vertex
12629 alive(idx) = false;
12630 const size_t p = prev(idx);
12631 const size_t nx = next(idx);
12632
12633 if (p < n)
12634 next(p) = nx;
12635 if (nx < n)
12636 prev(nx) = p;
12637
12638 // Recompute neighbor areas
12639 if (p < n and p != 0 and prev(p) < n)
12640 {
12641 areas(p) = effective_area(polyline(prev(p)), polyline(p), polyline(next(p)));
12642 heap.put({p, areas(p)});
12643 }
12644 if (nx < n and nx != n - 1 and next(nx) < n)
12645 {
12647 heap.put({nx, areas(nx)});
12648 }
12649 }
12650
12651 Array<Point> result;
12652 for (size_t i = 0; i < n; ++i)
12653 if (alive(i))
12654 result.append(polyline(i));
12655
12656 return result;
12657 }
12658
12659public:
12674
12677 const Geom_Number &area_threshold) const
12678 {
12679 Array<Point> arr;
12680 for (typename DynList<Point>::Iterator it(polyline); it.has_curr(); it.next_ne())
12681 arr.append(it.get_curr());
12683 }
12684
12695 {
12697 const size_t n = verts.size();
12698 if (n <= 3)
12699 return poly;
12700
12701 // Cyclic prev/next linked list
12702 Array<size_t> prev(n), next(n);
12703 Array<bool> alive(n);
12704 for (size_t i = 0; i < n; ++i)
12705 {
12706 prev.append(i == 0 ? n - 1 : i - 1);
12707 next.append(i == n - 1 ? 0 : i + 1);
12708 alive.append(true);
12709 }
12710
12712
12714 for (size_t i = 0; i < n; ++i)
12715 {
12716 areas.append(effective_area(verts(prev(i)), verts(i), verts(next(i))));
12717 heap.put({i, areas(i)});
12718 }
12719
12720 size_t remaining = n;
12721 while (not heap.is_empty() and remaining > 3)
12722 {
12723 auto [idx, area] = heap.get();
12724
12725 if (not alive(idx))
12726 continue;
12727 if (area != areas(idx))
12728 continue;
12729 if (area >= area_threshold)
12730 break;
12731
12732 alive(idx) = false;
12733 --remaining;
12734 const size_t p = prev(idx);
12735 const size_t nx = next(idx);
12736
12737 next(p) = nx;
12738 prev(nx) = p;
12739
12740 // Recompute neighbor areas
12741 areas(p) = effective_area(verts(prev(p)), verts(p), verts(next(p)));
12742 heap.put({p, areas(p)});
12743 areas(nx) = effective_area(verts(prev(nx)), verts(nx), verts(next(nx)));
12744 heap.put({nx, areas(nx)});
12745 }
12746
12747 Array<Point> result;
12748 for (size_t i = 0; i < n; ++i)
12749 if (alive(i))
12750 result.append(verts(i));
12751
12752 if (result.size() < 3)
12753 return poly;
12754
12755 Polygon rpoly;
12756 for (size_t i = 0; i < result.size(); ++i)
12757 rpoly.add_vertex(result(i));
12758 rpoly.close();
12759 return rpoly;
12760 }
12761};
12762
12763// ============================================================================
12764// Chaikin Corner-Cutting Subdivision (Polygon Smoothing)
12765// ============================================================================
12766
12782{
12784 {
12785 const size_t n = pts.size();
12786 Array<Point> result;
12787 result.append(pts(0));
12788 for (size_t i = 0; i + 1 < n; ++i)
12789 {
12790 const auto &p = pts(i);
12791 const auto &q = pts(i + 1);
12793 result.append(Point(p.get_x() * one_r + q.get_x() * r, p.get_y() * one_r + q.get_y() * r));
12794 result.append(Point(p.get_x() * r + q.get_x() * one_r, p.get_y() * r + q.get_y() * one_r));
12795 }
12796 result.append(pts(n - 1));
12797 return result;
12798 }
12799
12801 {
12802 const size_t n = pts.size();
12803 Array<Point> result;
12805 for (size_t i = 0; i < n; ++i)
12806 {
12807 const auto &p = pts(i);
12808 const auto &q = pts((i + 1) % n);
12809 result.append(Point(p.get_x() * one_r + q.get_x() * r, p.get_y() * one_r + q.get_y() * r));
12810 result.append(Point(p.get_x() * r + q.get_x() * one_r, p.get_y() * r + q.get_y() * one_r));
12811 }
12812 return result;
12813 }
12814
12815public:
12827 size_t iterations,
12828 const Geom_Number &ratio = Geom_Number(1, 4)) const
12829 {
12830 ah_domain_error_if(iterations == 0) << "iterations must be >= 1";
12831 ah_domain_error_if(ratio <= Geom_Number(0) or ratio >= Geom_Number(1, 2))
12832 << "ratio must be in (0, 0.5)";
12833
12835 for (size_t k = 0; k < iterations; ++k)
12836 cur = smooth_open_once(cur, ratio);
12837 return cur;
12838 }
12839
12842 size_t iterations,
12843 const Geom_Number &ratio = Geom_Number(1, 4)) const
12844 {
12845 Array<Point> arr;
12846 for (typename DynList<Point>::Iterator it(polyline); it.has_curr(); it.next_ne())
12847 arr.append(it.get_curr());
12848 return (*this)(arr, iterations, ratio);
12849 }
12850
12861 [[nodiscard]] static Polygon smooth_polygon(const Polygon &poly,
12862 size_t iterations,
12863 const Geom_Number &ratio = Geom_Number(1, 4))
12864 {
12865 ah_domain_error_if(iterations == 0) << "iterations must be >= 1";
12866 ah_domain_error_if(ratio <= Geom_Number(0) or ratio >= Geom_Number(1, 2))
12867 << "ratio must be in (0, 0.5)";
12868
12870 for (size_t k = 0; k < iterations; ++k)
12871 cur = smooth_closed_once(cur, ratio);
12872
12873 Polygon rpoly;
12874 for (size_t i = 0; i < cur.size(); ++i)
12875 rpoly.add_vertex(cur(i));
12876 rpoly.close();
12877 return rpoly;
12878 }
12879};
12880
12881// ============================================================================
12882// Trapezoidal Map Point Location
12883// ============================================================================
12884
12938{
12939public:
12940 static constexpr size_t NONE = ~static_cast<size_t>(0);
12941
12949 {
12950 size_t top;
12951 size_t bottom;
12952 size_t leftp;
12953 size_t rightp;
12954 size_t upper_left;
12955 size_t lower_left;
12958 size_t dag_leaf;
12959 bool active;
12960 };
12961
12965 enum class NodeType
12966 {
12967 X_NODE,
12968 Y_NODE,
12969 LEAF
12970 };
12971
12975 struct DagNode
12976 {
12978 size_t index;
12979 size_t left;
12980 size_t right;
12981 };
12982
12986 enum class LocationType
12987 {
12988 TRAPEZOID,
12989 ON_SEGMENT,
12990 ON_POINT
12991 };
12992
13003
13007 struct Result
13008 {
13013 size_t dag_root;
13016
13029 {
13030 size_t ni = dag_root;
13031 while (true)
13032 {
13033 const auto &[type, index, left, right] = dag(ni);
13034 if (type == NodeType::LEAF)
13035 return {LocationType::TRAPEZOID, index, NONE, NONE};
13036
13037 if (type == NodeType::X_NODE)
13038 {
13039 const Point &xp = points(index);
13040 if (p == xp)
13041 return {LocationType::ON_POINT, NONE, NONE, index};
13042 ni = p < xp ? left : right;
13043 }
13044 else // Y_NODE
13045 {
13046 const Segment &seg = segments(index);
13047 const Geom_Number cross =
13049 if (cross == 0)
13050 {
13051 // the point is on the segment line — check if between endpoints
13052 if (seg.contains(p))
13053 return {LocationType::ON_SEGMENT, NONE, index, NONE};
13054 // collinear but outside the segment extent — go above
13055 ni = left;
13056 }
13057 else if (cross > 0)
13058 ni = left; // above (left of directed segment)
13059 else
13060 ni = right; // below
13061 }
13062 }
13063 }
13064
13075 [[nodiscard]] bool contains(const Point &p) const
13076 {
13077 // Check exact boundary / vertex hits first.
13078 for (size_t i = 0; i < num_input_points; ++i)
13079 if (p == points(i))
13080 return true;
13081 for (size_t i = 0; i < num_input_segments; ++i)
13082 if (segments(i).contains(p))
13083 return true;
13084
13085 // Vertical ray cast downward: count input segments strictly below p
13086 // whose x-range covers p.x (half-open [src.x, tgt.x)).
13087 // Segments are stored left-to-right (src < tgt), so src.x <= tgt.x.
13088 // Vertical segments have empty half-open interval and are skipped.
13089 size_t crossings = 0;
13090 for (size_t i = 0; i < num_input_segments; ++i)
13091 {
13092 const Segment &seg = segments(i);
13093 const Point &a = seg.get_src_point();
13094 const Point &b = seg.get_tgt_point();
13095 if (p.get_x() < a.get_x() or p.get_x() >= b.get_x())
13096 continue;
13097 // y of segment at p.x (b.x != a.x guaranteed by half-open check)
13098 const Geom_Number seg_y =
13099 a.get_y() + (b.get_y() - a.get_y()) * (p.get_x() - a.get_x()) / (b.get_x() - a.get_x());
13100 if (seg_y < p.get_y())
13101 ++crossings;
13102 }
13103 return crossings % 2 == 1;
13104 }
13105 };
13106
13107private:
13109 [[nodiscard]] static bool point_above_segment(const Point &p, const Segment &seg)
13110 {
13111 return area_of_parallelogram(seg.get_src_point(), seg.get_tgt_point(), p) > 0;
13112 }
13113
13115 [[nodiscard]] static size_t dag_locate(const Array<Point> &pts,
13116 const Array<Segment> &segs,
13117 const Array<DagNode> &dag,
13118 const Point &p,
13119 size_t root)
13120 {
13121 size_t ni = root;
13122 while (true)
13123 {
13124 const auto &[type, index, left, right] = dag(ni);
13125 if (type == NodeType::LEAF)
13126 return index;
13127
13128 if (type == NodeType::X_NODE)
13129 {
13130 if (const Point &xp = pts(index); p == xp)
13131 ni = right; // tie-break: go right
13132 else
13133 ni = p < xp ? left : right;
13134 }
13135 else // Y_NODE
13136 {
13137 const Geom_Number cross =
13138 area_of_parallelogram(segs(index).get_src_point(), segs(index).get_tgt_point(), p);
13139 if (cross > 0)
13140 ni = left; // above
13141 else if (cross < 0)
13142 ni = right; // below
13143 else
13144 ni = left; // on segment line → go above
13145 }
13146 }
13147 }
13148
13151 Array<DagNode> &dag,
13152 const size_t top,
13153 const size_t bottom,
13154 const size_t leftp,
13155 const size_t rightp)
13156 {
13157 const size_t ti = traps.size();
13158 const size_t di = dag.size();
13159 traps.append(Trapezoid{top, bottom, leftp, rightp, NONE, NONE, NONE, NONE, di, true});
13161 return ti;
13162 }
13163
13166 const size_t leaf_idx,
13167 const NodeType type,
13168 const size_t index,
13169 const size_t left,
13170 const size_t right)
13171 {
13172 dag(leaf_idx).type = type;
13173 dag(leaf_idx).index = index;
13174 dag(leaf_idx).left = left;
13175 dag(leaf_idx).right = right;
13176 }
13177
13180 const size_t neighbor_idx,
13181 const size_t old_ti,
13182 const size_t new_ti)
13183 {
13184 if (neighbor_idx == NONE)
13185 return;
13187 if (nb.upper_left == old_ti)
13188 nb.upper_left = new_ti;
13189 if (nb.lower_left == old_ti)
13190 nb.lower_left = new_ti;
13191 if (nb.upper_right == old_ti)
13192 nb.upper_right = new_ti;
13193 if (nb.lower_right == old_ti)
13194 nb.lower_right = new_ti;
13195 }
13196
13200 const Array<Segment> &segs,
13201 const Array<Trapezoid> &traps,
13202 const size_t seg_idx,
13203 const size_t start_trap)
13204 {
13207
13208 const Segment &seg = segs(seg_idx);
13209 const Point &rp = seg.get_tgt_point();
13210
13211 size_t ti = start_trap;
13212 while (true)
13213 {
13214 const Trapezoid &t = traps(ti);
13215 const Point &rightp = pts(t.rightp);
13216 // If the right boundary of this trapezoid is at or past the
13217 // segment's right endpoint, we're done.
13218 if (rp < rightp or rp == rightp)
13219 break;
13220
13221 // Go right: the segment exits through the right wall of t.
13222 // If the right endpoint of the segment is above the segment
13223 // at the right boundary x, go to upper_right; else lower_right.
13224 if (t.upper_right != NONE and t.lower_right != NONE)
13225 {
13226 // Test which neighbor the segment enters.
13227 if (point_above_segment(rightp, seg))
13228 ti = t.lower_right;
13229 else
13230 ti = t.upper_right;
13231 }
13232 else if (t.upper_right != NONE)
13233 ti = t.upper_right;
13234 else if (t.lower_right != NONE)
13235 ti = t.lower_right;
13236 else
13237 break; // no right neighbor
13238
13239 crossed.append(ti);
13240 }
13241 return crossed;
13242 }
13243
13248 Array<DagNode> &dag,
13249 const size_t seg_idx,
13250 const size_t trap_idx)
13251 {
13252 const Trapezoid old_t = traps(trap_idx);
13253 const Segment &seg = segs(seg_idx);
13254 const Point &lp = seg.get_src_point();
13255 const Point &rp = seg.get_tgt_point();
13256
13257 // Find point indices.
13258 size_t lp_idx = NONE, rp_idx = NONE;
13259 for (size_t i = 0; i < pts.size(); ++i)
13260 {
13261 if (pts(i) == lp)
13262 lp_idx = i;
13263 if (pts(i) == rp)
13264 rp_idx = i;
13265 }
13266
13267 const bool shared_left = (old_t.leftp == lp_idx);
13268 const bool shared_right = (old_t.rightp == rp_idx);
13269
13270 // Deactivate old trapezoid.
13271 traps(trap_idx).active = false;
13272
13273 // Create new trapezoids:
13274 // A = left of lp (if not shared)
13275 // B = above segment (between lp and rp)
13276 // C = below segment (between lp and rp)
13277 // D = right of rp (if not shared)
13278
13279 const size_t dag_old = old_t.dag_leaf;
13280
13281 size_t a_idx = NONE, d_idx = NONE;
13282
13283 if (not shared_left)
13284 a_idx = make_trapezoid(traps, dag, old_t.top, old_t.bottom, old_t.leftp, lp_idx);
13285 const size_t b_idx = make_trapezoid(traps, dag, old_t.top, seg_idx, lp_idx, rp_idx);
13286 const size_t c_idx = make_trapezoid(traps, dag, seg_idx, old_t.bottom, lp_idx, rp_idx);
13287 if (not shared_right)
13288 d_idx = make_trapezoid(traps, dag, old_t.top, old_t.bottom, rp_idx, old_t.rightp);
13289
13290 // Set neighbor pointers.
13291 // A neighbors:
13292 if (a_idx != NONE)
13293 {
13294 traps(a_idx).upper_left = old_t.upper_left;
13295 traps(a_idx).lower_left = old_t.lower_left;
13296 traps(a_idx).upper_right = b_idx;
13297 traps(a_idx).lower_right = c_idx;
13298 update_neighbor(traps, old_t.upper_left, trap_idx, a_idx);
13299 update_neighbor(traps, old_t.lower_left, trap_idx, a_idx);
13300 }
13301
13302 // B neighbors:
13303 traps(b_idx).upper_left = shared_left ? old_t.upper_left : a_idx;
13304 traps(b_idx).lower_left = shared_left ? old_t.lower_left : a_idx;
13305 traps(b_idx).upper_right = shared_right ? old_t.upper_right : d_idx;
13306 traps(b_idx).lower_right = shared_right ? old_t.lower_right : d_idx;
13307
13308 // C neighbors:
13309 traps(c_idx).upper_left = shared_left ? old_t.upper_left : a_idx;
13310 traps(c_idx).lower_left = shared_left ? old_t.lower_left : a_idx;
13311 traps(c_idx).upper_right = shared_right ? old_t.upper_right : d_idx;
13312 traps(c_idx).lower_right = shared_right ? old_t.lower_right : d_idx;
13313
13314 if (shared_left)
13315 {
13316 update_neighbor(traps, old_t.upper_left, trap_idx, b_idx);
13317 update_neighbor(traps, old_t.lower_left, trap_idx, c_idx);
13318 }
13319
13320 // D neighbors:
13321 if (d_idx != NONE)
13322 {
13323 traps(d_idx).upper_left = b_idx;
13324 traps(d_idx).lower_left = c_idx;
13325 traps(d_idx).upper_right = old_t.upper_right;
13326 traps(d_idx).lower_right = old_t.lower_right;
13327 update_neighbor(traps, old_t.upper_right, trap_idx, d_idx);
13328 update_neighbor(traps, old_t.lower_right, trap_idx, d_idx);
13329 }
13330
13331 if (shared_right)
13332 {
13333 update_neighbor(traps, old_t.upper_right, trap_idx, b_idx);
13334 update_neighbor(traps, old_t.lower_right, trap_idx, c_idx);
13335 }
13336
13337 // Build DAG sub-tree rooted at dag_old.
13338 // Structure depends on shared_left / shared_right.
13340 {
13341 // X(lp) -> left=A, right=X(rp) -> left=Y(seg)->above=B,below=C, right=D
13342 const size_t y_node = dag.size();
13343 dag.append(DagNode{NodeType::Y_NODE, seg_idx, traps(b_idx).dag_leaf, traps(c_idx).dag_leaf});
13344 const size_t x_rp = dag.size();
13347 }
13349 {
13350 // Y(seg) -> above=B, below=C wrapped in X(rp)->left=Y, right=D
13351 const size_t y_node = dag.size();
13352 dag.append(DagNode{NodeType::Y_NODE, seg_idx, traps(b_idx).dag_leaf, traps(c_idx).dag_leaf});
13354 }
13356 {
13357 // X(lp) -> left=A, right=Y(seg)->above=B, below=C
13358 const size_t y_node = dag.size();
13359 dag.append(DagNode{NodeType::Y_NODE, seg_idx, traps(b_idx).dag_leaf, traps(c_idx).dag_leaf});
13361 }
13362 else // both shared
13363 {
13364 // Just Y(seg) -> above=B, below=C
13365 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx, traps(b_idx).dag_leaf,
13366 traps(c_idx).dag_leaf);
13367 }
13368 }
13369
13374 Array<DagNode> &dag,
13375 const size_t seg_idx,
13376 const Array<size_t> &crossed)
13377 {
13378 const Segment &seg = segs(seg_idx);
13379 const Point &lp = seg.get_src_point();
13380 const Point &rp = seg.get_tgt_point();
13381
13382 size_t lp_idx = NONE, rp_idx = NONE;
13383 for (size_t i = 0; i < pts.size(); ++i)
13384 {
13385 if (pts(i) == lp)
13386 lp_idx = i;
13387 if (pts(i) == rp)
13388 rp_idx = i;
13389 }
13390
13391 const size_t nc = crossed.size();
13392
13393 // For each crossed trapezoid, create above/below sub-trapezoids.
13394 // Adjacent above (or below) trapezoids that share the same top (or
13395 // bottom) segment are merged.
13396 Array<size_t> above_traps; // one per crossed, but may be merged
13399 below_traps.reserve(nc);
13400
13401 size_t prev_above = NONE, prev_below = NONE;
13402 size_t left_trap = NONE; // left-of-lp trapezoid
13403
13404 for (size_t ci = 0; ci < nc; ++ci)
13405 {
13406 const size_t ti = crossed(ci);
13407 const Trapezoid old_t = traps(ti);
13408 traps(ti).active = false;
13409
13410 const bool is_first = (ci == 0);
13411 const bool is_last = (ci == nc - 1);
13412 const bool shared_left = is_first and (old_t.leftp == lp_idx);
13413 const bool shared_right = is_last and (old_t.rightp == rp_idx);
13414
13415 // Determine the left/right x-boundaries for the new sub-trapezoids.
13416 const size_t new_leftp = is_first ? lp_idx : old_t.leftp;
13417 const size_t new_rightp = is_last ? rp_idx : old_t.rightp;
13418
13419 // Above trapezoid.
13420 bool merged_above = false;
13421 if (prev_above != NONE and traps(prev_above).top == old_t.top)
13422 {
13423 // Merge: extend prev_above to the right.
13424 traps(prev_above).rightp = new_rightp;
13425 above_traps.append(prev_above);
13426 merged_above = true;
13427 }
13428 else
13429 {
13430 const size_t ai = make_trapezoid(traps, dag, old_t.top, seg_idx, new_leftp, new_rightp);
13431 above_traps.append(ai);
13432 prev_above = ai;
13433 }
13434
13435 // Below trapezoid.
13436 bool merged_below = false;
13437 if (prev_below != NONE and traps(prev_below).bottom == old_t.bottom)
13438 {
13439 traps(prev_below).rightp = new_rightp;
13440 below_traps.append(prev_below);
13441 merged_below = true;
13442 }
13443 else
13444 {
13445 const size_t bi =
13446 make_trapezoid(traps, dag, seg_idx, old_t.bottom, new_leftp, new_rightp);
13447 below_traps.append(bi);
13448 prev_below = bi;
13449 }
13450
13451 const size_t cur_above = above_traps(ci);
13452 const size_t cur_below = below_traps(ci);
13453
13454 // Handle the left trapezoid (only for the first crossed trapezoid).
13456 {
13457 left_trap = make_trapezoid(traps, dag, old_t.top, old_t.bottom, old_t.leftp, lp_idx);
13458 traps(left_trap).upper_left = old_t.upper_left;
13459 traps(left_trap).lower_left = old_t.lower_left;
13460 traps(left_trap).upper_right = cur_above;
13461 traps(left_trap).lower_right = cur_below;
13462 update_neighbor(traps, old_t.upper_left, ti, left_trap);
13463 update_neighbor(traps, old_t.lower_left, ti, left_trap);
13464 }
13465
13466 // Set left neighbors for above/below.
13467 if (is_first)
13468 {
13469 if (not merged_above)
13470 {
13471 if (shared_left)
13472 {
13473 traps(cur_above).upper_left = old_t.upper_left;
13474 traps(cur_above).lower_left = old_t.lower_left;
13475 update_neighbor(traps, old_t.upper_left, ti, cur_above);
13476 }
13477 else
13478 {
13479 traps(cur_above).upper_left = left_trap;
13480 traps(cur_above).lower_left = left_trap;
13481 }
13482 }
13483 if (not merged_below)
13484 {
13485 if (shared_left)
13486 {
13487 traps(cur_below).upper_left = old_t.upper_left;
13488 traps(cur_below).lower_left = old_t.lower_left;
13489 update_neighbor(traps, old_t.lower_left, ti, cur_below);
13490 }
13491 else
13492 {
13493 traps(cur_below).upper_left = left_trap;
13494 traps(cur_below).lower_left = left_trap;
13495 }
13496 }
13497 }
13498 else
13499 {
13500 // Interior crossed trapezoid.
13501 if (not merged_above)
13502 {
13503 // Left neighbor of new above is the previous above.
13504 traps(cur_above).upper_left = above_traps(ci - 1);
13505 traps(cur_above).lower_left = above_traps(ci - 1);
13506 // Also set the previous above's right neighbors.
13507 if (above_traps(ci - 1) != cur_above)
13508 {
13509 traps(above_traps(ci - 1)).upper_right = cur_above;
13510 traps(above_traps(ci - 1)).lower_right = cur_above;
13511 }
13512 }
13513 if (not merged_below)
13514 {
13515 traps(cur_below).upper_left = below_traps(ci - 1);
13516 traps(cur_below).lower_left = below_traps(ci - 1);
13517 if (below_traps(ci - 1) != cur_below)
13518 {
13519 traps(below_traps(ci - 1)).upper_right = cur_below;
13520 traps(below_traps(ci - 1)).lower_right = cur_below;
13521 }
13522 }
13523
13524 // Connect left neighbors from old trapezoid.
13525 if (not merged_above)
13526 if (old_t.upper_left != NONE and old_t.upper_left != crossed(ci - 1))
13527 {
13528 traps(cur_above).upper_left = old_t.upper_left;
13529 update_neighbor(traps, old_t.upper_left, ti, cur_above);
13530 }
13531 if (not merged_below)
13532 if (old_t.lower_left != NONE and old_t.lower_left != crossed(ci - 1))
13533 {
13534 traps(cur_below).lower_left = old_t.lower_left;
13535 update_neighbor(traps, old_t.lower_left, ti, cur_below);
13536 }
13537 }
13538
13539 // Handle right trapezoid (only for last crossed trapezoid).
13540 if (is_last)
13541 {
13542 if (not shared_right)
13543 {
13544 const size_t right_trap =
13545 make_trapezoid(traps, dag, old_t.top, old_t.bottom, rp_idx, old_t.rightp);
13546 traps(right_trap).upper_left = cur_above;
13547 traps(right_trap).lower_left = cur_below;
13548 traps(right_trap).upper_right = old_t.upper_right;
13549 traps(right_trap).lower_right = old_t.lower_right;
13550 update_neighbor(traps, old_t.upper_right, ti, right_trap);
13551 update_neighbor(traps, old_t.lower_right, ti, right_trap);
13552 traps(cur_above).upper_right = right_trap;
13553 traps(cur_above).lower_right = right_trap;
13554 traps(cur_below).upper_right = right_trap;
13555 traps(cur_below).lower_right = right_trap;
13556
13557 // Build DAG for this trapezoid.
13558 const size_t y_node = dag.size();
13559 dag.append(DagNode{NodeType::Y_NODE, seg_idx, traps(cur_above).dag_leaf,
13560 traps(cur_below).dag_leaf});
13561 const size_t dag_old = old_t.dag_leaf;
13563 traps(right_trap).dag_leaf);
13564 }
13565 else
13566 {
13567 traps(cur_above).upper_right = old_t.upper_right;
13568 traps(cur_above).lower_right = old_t.lower_right;
13569 traps(cur_below).upper_right = old_t.upper_right;
13570 traps(cur_below).lower_right = old_t.lower_right;
13571 update_neighbor(traps, old_t.upper_right, ti, cur_above);
13572 update_neighbor(traps, old_t.lower_right, ti, cur_below);
13573
13574 // Build DAG for this trapezoid.
13575 const size_t dag_old = old_t.dag_leaf;
13576 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx, traps(cur_above).dag_leaf,
13577 traps(cur_below).dag_leaf);
13578 }
13579 }
13580 else if (is_first)
13581 {
13582 // First but not last — build DAG with X-node for left endpoint.
13583 const size_t dag_old = old_t.dag_leaf;
13584 const size_t y_node = dag.size();
13585 dag.append(DagNode{NodeType::Y_NODE, seg_idx, traps(cur_above).dag_leaf,
13586 traps(cur_below).dag_leaf});
13587 if (shared_left)
13588 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx, traps(cur_above).dag_leaf,
13589 traps(cur_below).dag_leaf);
13590 else
13592 }
13593 else
13594 {
13595 // Middle trapezoid — just a Y-node.
13596 const size_t dag_old = old_t.dag_leaf;
13597 replace_leaf(dag, dag_old, NodeType::Y_NODE, seg_idx, traps(cur_above).dag_leaf,
13598 traps(cur_below).dag_leaf);
13599 }
13600 }
13601 }
13602
13603public:
13613 {
13614 Result ret;
13615 const size_t n = input_segments.size();
13617
13618 // Collect unique points from segments.
13619 // Orient all segments left-to-right (src < tgt lexicographically).
13620 ret.segments.reserve(n + 2);
13621 for (size_t i = 0; i < n; ++i)
13622 {
13623 const Point &s = input_segments(i).get_src_point();
13624 const Point &t = input_segments(i).get_tgt_point();
13625 ah_domain_error_if(s == t) << "Degenerate segment (src == tgt) at index " << i;
13626 if (s < t)
13627 ret.segments.append(Segment(s, t));
13628 else
13629 ret.segments.append(Segment(t, s));
13630 }
13631
13632 // Collect and deduplicate points.
13633 {
13635 all_pts.reserve(2 * n);
13636 for (size_t i = 0; i < n; ++i)
13637 {
13638 all_pts.append(ret.segments(i).get_src_point());
13639 all_pts.append(ret.segments(i).get_tgt_point());
13640 }
13641 // Sort lexicographically.
13643 // Deduplicate.
13644 for (size_t i = 0; i < all_pts.size(); ++i)
13645 {
13646 if (ret.points.is_empty() or not (ret.points(ret.points.size() - 1) == all_pts(i)))
13647 ret.points.append(all_pts(i));
13648 }
13649 }
13650
13651 ret.num_input_points = ret.points.size();
13652
13653 // Compute bounding box with margin.
13654 if (ret.points.is_empty())
13655 {
13656 // No input segments: create a trivial bounding box.
13657 ret.points.append(Point(-1, -1));
13658 ret.points.append(Point(1, -1));
13659 ret.points.append(Point(1, 1));
13660 ret.points.append(Point(-1, 1));
13661 }
13662
13663 Geom_Number mnx = ret.points(0).get_x();
13665 Geom_Number mny = ret.points(0).get_y();
13667 for (size_t i = 1; i < ret.points.size(); ++i)
13668 {
13669 if (ret.points(i).get_x() < mnx)
13670 mnx = ret.points(i).get_x();
13671 if (ret.points(i).get_x() > mxx)
13672 mxx = ret.points(i).get_x();
13673 if (ret.points(i).get_y() < mny)
13674 mny = ret.points(i).get_y();
13675 if (ret.points(i).get_y() > mxy)
13676 mxy = ret.points(i).get_y();
13677 }
13678
13679 Geom_Number margin = (mxx - mnx > mxy - mny) ? mxx - mnx : mxy - mny;
13680 if (margin == 0)
13681 margin = 1;
13682 margin = margin + 1;
13683
13684 // Bounding box corner points.
13685 const Point bl(mnx - margin, mny - margin);
13686 const Point br(mxx + margin, mny - margin);
13687 const Point tl(mnx - margin, mxy + margin);
13688 const Point tr(mxx + margin, mxy + margin);
13689
13690 const size_t bl_idx = ret.points.size();
13691 ret.points.append(bl);
13692 ret.points.append(br);
13693 ret.points.append(tl);
13694 const size_t tr_idx = ret.points.size();
13695 ret.points.append(tr);
13696
13697 // Bounding box segments: top (tl→tr) and bottom (bl→br).
13698 const size_t top_seg = ret.segments.size();
13699 ret.segments.append(Segment(tl, tr));
13700 const size_t bot_seg = ret.segments.size();
13701 ret.segments.append(Segment(bl, br));
13702
13703 // Initialize: one trapezoid spanning the bounding box.
13704 ret.dag_root = 0;
13705 const size_t t0 = make_trapezoid(ret.trapezoids, ret.dag, top_seg, bot_seg, bl_idx, tr_idx);
13706 (void) t0;
13707
13708 // Randomized insertion order.
13709 if (n == 0)
13710 return ret;
13711
13712 Array<size_t> order;
13713 order.reserve(n);
13714 for (size_t i = 0; i < n; ++i)
13715 order.append(i);
13716
13717 // Fisher-Yates shuffle.
13718 {
13719 std::random_device rd;
13720 std::mt19937 gen(rd());
13721 for (size_t i = n - 1; i > 0; --i)
13722 {
13723 std::uniform_int_distribution<size_t> dis(0, i);
13724 const size_t j = dis(gen);
13725 const size_t tmp = order(i);
13726 order(i) = order(j);
13727 order(j) = tmp;
13728 }
13729 }
13730
13731 // Insert segments one by one.
13732 for (size_t oi = 0; oi < n; ++oi)
13733 {
13734 const size_t si = order(oi);
13735 const Segment &seg = ret.segments(si);
13736
13737 // Locate the trapezoid containing the left endpoint.
13738 const size_t start =
13739 dag_locate(ret.points, ret.segments, ret.dag, seg.get_src_point(), ret.dag_root);
13740
13741 // Find all trapezoids crossed by this segment.
13743 find_crossed_trapezoids(ret.points, ret.segments, ret.trapezoids, si, start);
13744
13745 if (crossed.size() == 1)
13746 split_single_trapezoid(ret.points, ret.segments, ret.trapezoids, ret.dag, si, crossed(0));
13747 else
13748 split_multiple_trapezoids(ret.points, ret.segments, ret.trapezoids, ret.dag, si, crossed);
13749 }
13750
13751 return ret;
13752 }
13753
13760 [[nodiscard]] Result operator () (const Polygon &polygon) const
13761 {
13762 ah_domain_error_if(not polygon.is_closed()) << "Polygon must be closed";
13763 ah_domain_error_if(polygon.size() < 3) << "Polygon must have at least 3 vertices";
13764
13766 for (Polygon::Segment_Iterator it(polygon); it.has_curr(); it.next_ne())
13767 segs.append(it.get_current_segment());
13768 return (*this)(segs);
13769 }
13770
13777 [[nodiscard]] Result operator () (const Array<Polygon> &polygons) const
13778 {
13780 for (size_t i = 0; i < polygons.size(); ++i)
13781 {
13782 const Polygon &poly = polygons(i);
13783 ah_domain_error_if(not poly.is_closed()) << "Polygon " << i << " must be closed";
13784 ah_domain_error_if(poly.size() < 3) << "Polygon " << i << " must have at least 3 vertices";
13785 for (Polygon::Segment_Iterator it(poly); it.has_curr(); it.next_ne())
13786 segs.append(it.get_current_segment());
13787 }
13788 return (*this)(segs);
13789 }
13790};
13791
13792// ============================================================================
13793// GeomNumber Concept (C++20)
13794// ============================================================================
13795
13796#if __cplusplus >= 202002L
13797
13798#include <concepts>
13799
13821template <typename T>
13822concept GeomNumberType = requires(T a, T b, int i) {
13823 { T(i) } -> std::convertible_to<T>;
13824 { a + b } -> std::convertible_to<T>;
13825 { a - b } -> std::convertible_to<T>;
13826 { a * b } -> std::convertible_to<T>;
13827 { a / b } -> std::convertible_to<T>;
13828 { a == b } -> std::convertible_to<bool>;
13829 { a != b } -> std::convertible_to<bool>;
13830 { a < b } -> std::convertible_to<bool>;
13831 { a <= b } -> std::convertible_to<bool>;
13832 { a > b } -> std::convertible_to<bool>;
13833 { a >= b } -> std::convertible_to<bool>;
13834 { -a } -> std::convertible_to<T>;
13835};
13836
13837// Verify that Geom_Number satisfies the concept.
13838static_assert(GeomNumberType<Geom_Number>, "Geom_Number must satisfy GeomNumberType");
13839static_assert(GeomNumberType<double>, "double must satisfy GeomNumberType");
13840static_assert(GeomNumberType<long long>, "long long must satisfy GeomNumberType");
13841
13842#endif // C++20
13843} // namespace Aleph
13844
13845#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:527
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.
long double h
Definition btreepic.C:154
static int cell(const aleph_ca_engine_t *e, size_t r, size_t c)
Definition c_abi_smoke.c:53
size_t steps
Definition ca-c-api.h:126
size_t size_t int32_t * out
Definition ca-c-api.h:120
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:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
void empty() noexcept
Empties the container.
Definition tpl_array.H:341
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & insert(const T &data)
insert a copy of data at the beginning of the array.
Definition tpl_array.H:286
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
T & get_last() noexcept
return a modifiable reference to the last element.
Definition tpl_array.H:392
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
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)
Computes the union of two polygons.
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)
Computes the difference (A \ B) of two polygons.
static void ensure_ccw(Array< Point > &v)
Ensure vertices are in CCW order.
static Array< Polygon > compute_intersection(const Polygon &a, const Polygon &b)
Computes the intersection of two polygons.
Brute force convex hull algorithm.
static SegmentSet extreme_edges(const DynList< Point > &point_set)
Finds the extreme edges of a point set (edges that form the convex hull).
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)
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.
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.
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.
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
Computes the Delaunay triangulation of a set of points.
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 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
Doubly-linked list (defined in tpl_dynList.H).
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.
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:3054
const Geom_Number & get_z() const
Gets the z-coordinate.
Definition point.H:3086
const Geom_Number & get_x() const
Gets the x-coordinate.
Definition point.H:3076
const Geom_Number & get_y() const
Gets the y-coordinate.
Definition point.H:3081
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:221
const Geom_Number & get_x() const noexcept
Gets the x-coordinate value.
Definition point.H:448
const Geom_Number & get_y() const noexcept
Gets the y-coordinate value.
Definition point.H:457
Point midpoint(const Point &other) const
Calculates the midpoint between this point and another.
Definition point.H:439
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:515
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:486
Geom_Number distance_squared_to(const Point &that) const
Calculates the squared Euclidean distance to another point.
Definition point.H:1490
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:521
bool has_curr() const
Check if there is a current segment.
Definition polygon.H:538
A general (irregular) 2D polygon defined by a sequence of vertices.
Definition polygon.H:247
const Vertex & get_next_vertex(const Vertex &v) const
Get the vertex following v in the polygon.
Definition polygon.H:602
const Vertex & get_prev_vertex(const Vertex &v) const
Get the vertex preceding v in the polygon.
Definition polygon.H:616
void remove_vertex(const Vertex &v)
Remove a vertex from the polygon.
Definition polygon.H:778
PointLocation locate_point(const Point &p) const
Classify a point against this closed polygon.
Definition polygon.H:887
const Vertex & get_first_vertex() const
Get the first vertex of the polygon.
Definition polygon.H:579
void add_vertex(const Point &point)
Add a vertex to the polygon.
Definition polygon.H:678
void close()
Close the polygon.
Definition polygon.H:843
const bool & is_closed() const
Check if the polygon is closed.
Definition polygon.H:474
const size_t & size() const
Get the number of vertices.
Definition polygon.H:478
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:1789
const Geom_Number & get_xmin() const
Gets the minimum x-coordinate.
Definition point.H:1815
const Geom_Number & get_ymax() const
Gets the maximum y-coordinate.
Definition point.H:1830
const Geom_Number & get_ymin() const
Gets the minimum y-coordinate.
Definition point.H:1820
const Geom_Number & get_xmax() const
Gets the maximum x-coordinate.
Definition point.H:1825
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)
Deduplicates and sorts an array of weighted sites.
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:837
bool intersects_properly_with(const Segment &s) const
Checks if this segment properly intersects another segment.
Definition point.H:1241
Point intersection_with(const Segment &s) const
Computes the intersection point of the infinite lines defined by two segments.
Definition point.H:1334
bool intersects_with(const Segment &s) const
Checks if this segment intersects another one (including endpoints and collinear overlap).
Definition point.H:1291
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:1161
Geom_Number distance_to(const Point &p) const
Calculates the Euclidean distance from a point to this segment.
Definition point.H:1183
bool contains(const Point &p) const
Checks if a point lies on this segment.
Definition point.H:1259
const Point & get_tgt_point() const noexcept
Gets the target point of the segment.
Definition point.H:933
const Point & get_src_point() const noexcept
Gets the source point of the segment.
Definition point.H:924
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.
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).
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:1512
const Point & get_p3() const
Gets the third vertex.
Definition point.H:1629
const Point & get_p2() const
Gets the second vertex.
Definition point.H:1624
const Point & get_p1() const
Gets the first vertex.
Definition point.H:1619
A vertex in a polygon's doubly linked vertex list.
Definition polygon.H:120
const Vertex & next_vertex() const
Get the next vertex in the polygon.
Definition polygon.H:190
Point to_point() const
Return this vertex as a plain Point value.
Definition polygon.H:151
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)
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_
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)
Validates if a triangulation holds the Delaunay property (empty circumcircle).
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 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:796
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:4123
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_y1_function > > y1(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4114
__gmp_expr< mpfr_t, mpfr_t > mpfr_class
Definition gmpfrxx.h:2457
__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:4071
bool vertical
If true, use vertical layout (default).
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
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
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:2894
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:2923
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:2886
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:2961
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:2967
bool all(Container &container, Operation &operation)
Return true if all elements satisfy a predicate.
and
Check uniqueness with explicit hash + equality functors.
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:2982
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:328
Geom_Number square_root(const Geom_Number &x)
Square root of x (wrapper over mpfr).
Definition point.H:197
double geom_number_to_double(const Geom_Number &n)
Converts a Geom_Number to its double precision representation.
Definition point.H:120
Orientation orientation(const Point &a, const Point &b, const Point &c)
Return the orientation of the triple (a, b, c).
Definition point.H:2902
mpq_class Geom_Number
Numeric type used by the geometry module.
Definition point.H:113
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:490
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:53
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