Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
geom_algorithms_test_edgecases_sweepline_minkowski_kdtree.cc
Go to the documentation of this file.
2#include <random>
3#include <cmath>
4
5
6// ---------- Edge cases: ClosestPair ----------
7
14
15
17{
19 one.append(Point(1, 1));
21 EXPECT_THROW((void) cp(one), std::domain_error);
22}
23
24
26{
28 dups.append(Point(7, 7));
29 dups.append(Point(7, 7));
30 dups.append(Point(7, 7));
31 dups.append(Point(7, 7));
32
34 auto res = cp(dups);
35
36 EXPECT_EQ(res.distance_squared, 0);
37 EXPECT_EQ(res.first, Point(7, 7));
38 EXPECT_EQ(res.second, Point(7, 7));
39}
40
41
42// ---------- Edge cases: CuttingEarsTriangulation ----------
43
45{
46 // A polygon with only 2 vertices cannot be closed (requires >= 3)
47 // so the exception is thrown at close() time, not at triangulation time
49 {
50 Polygon p;
51 p.add_vertex(Point(0, 0));
52 p.add_vertex(Point(1, 0));
53 p.close();
54 },
55 std::domain_error);
56}
57
58
59// ---------- Edge cases: RotatingCalipers ----------
60
62{
63 Polygon p;
64 p.add_vertex(Point(1, 1));
65 // Not closed — should throw.
66
68 EXPECT_THROW((void) calipers.diameter(p), std::domain_error);
69 EXPECT_THROW((void) calipers.minimum_width(p), std::domain_error);
70}
71
72
73// ---------- Edge cases: PointInPolygon ----------
74
76{
77 // A polygon with only 2 vertices cannot be closed (requires >= 3)
78 // so the exception is thrown at close() time
80 {
81 Polygon p;
82 p.add_vertex(Point(0, 0));
83 p.add_vertex(Point(5, 5));
84 p.close();
85 },
86 std::domain_error);
87}
88
89
90// ---------- Edge cases: Convex hull algorithms with 2 collinear points ----------
91
93{
94 DynList<Point> points;
95 points.append(Point(0, 0));
96 points.append(Point(5, 5));
97
99 Polygon hull = andrew(points);
100
101 // Degenerate 2-point hull cannot be closed (requires >= 3 vertices)
102 EXPECT_EQ(hull.size(), 2u);
103 EXPECT_FALSE(hull.is_closed());
106}
107
108
115
116
126
127
139
140
147
148
157
158
160{
161 DynList<Point> points;
162 points.append(Point(0, 0));
163 points.append(Point(5, 5));
164
166 Polygon hull = graham(points);
167 // Degenerate 2-point hull cannot be closed (requires >= 3 vertices)
168 EXPECT_EQ(hull.size(), 2u);
169 EXPECT_FALSE(hull.is_closed());
170}
171
172
174{
176 dups.append(Point(7, 7));
177 dups.append(Point(7, 7));
178 dups.append(Point(7, 7));
179
182 EXPECT_EQ(hull.size(), 1u);
183}
184
185
186// ---------- Cross-algorithm consistency ----------
187
189{
190 // All five hull algorithms should produce the same vertex set.
191 DynList<Point> points;
192 // Deterministic "random" set avoiding cocircular degeneracies.
193 int seed = 12345;
194 for (int i = 0; i < 50; ++i)
195 {
196 seed = (seed * 1103515245 + 12345) & 0x7fffffff;
197 int x = seed % 1000;
198 seed = (seed * 1103515245 + 12345) & 0x7fffffff;
199 int y = seed % 1000;
200 points.append(Point(x, y));
201 }
202
208
209 Polygon h_andrew = andrew(points);
210 Polygon h_graham = graham(points);
211 Polygon h_qh = qh(points);
212 Polygon h_gw = gw(points);
213 Polygon h_bf = bf(points);
214
215 EXPECT_EQ(h_andrew.size(), h_graham.size());
216 EXPECT_EQ(h_andrew.size(), h_qh.size());
217 EXPECT_EQ(h_andrew.size(), h_gw.size());
218 EXPECT_EQ(h_andrew.size(), h_bf.size());
219
220 // Every vertex of Andrew's hull should appear in every other hull.
221 for (Polygon::Vertex_Iterator it(h_andrew); it.has_curr(); it.next_ne())
222 {
223 const Point p = it.get_current_vertex();
228 }
229}
230
231
232// ---------- Delaunay: as_triangles helper ----------
233
235{
237 auto r = delaunay({Point(0, 0), Point(6, 0), Point(3, 5), Point(6, 5),
238 Point(0, 5)});
239
241
242 size_t count = 0;
243 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
244 ++count;
245
246 EXPECT_EQ(count, r.triangles.size());
247}
248
249
250// ============================================================================
251// Phase 4 — New Algorithms Tests
252// ============================================================================
253
254// ---------- SegmentSegmentIntersection (Dedicated O(1)) ----------
255
257{
259 const auto result = pair_ix(Segment(Point(0, 0), Point(2, 0)),
260 Segment(Point(0, 2), Point(2, 2)));
261
262 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::NONE);
263 EXPECT_FALSE(result.intersects());
264}
265
266
268{
270 const auto result = pair_ix(Segment(Point(0, 0), Point(4, 4)),
271 Segment(Point(0, 4), Point(4, 0)));
272
273 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::POINT);
274 EXPECT_TRUE(result.intersects());
275 EXPECT_EQ(result.point, Point(2, 2));
276}
277
278
280{
282 const auto result = pair_ix(Segment(Point(0, 0), Point(4, 0)),
283 Segment(Point(4, 0), Point(4, 3)));
284
285 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::POINT);
286 EXPECT_EQ(result.point, Point(4, 0));
287}
288
289
291{
293 const Segment a(Point(0, 0), Point(6, 0));
294 const Segment b(Point(2, 0), Point(4, 0));
295 const auto result = pair_ix(a, b);
296
299 scene.segments.append(b);
300 scene.highlighted_points.append(Point(2, 0));
301 scene.highlighted_points.append(Point(4, 0));
303 "case_segment_segment_overlap_o1", scene,
304 "Dedicated O(1) segment-segment overlap");
305
306 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::OVERLAP);
307 EXPECT_EQ(result.overlap.get_src_point(), Point(2, 0));
308 EXPECT_EQ(result.overlap.get_tgt_point(), Point(4, 0));
309}
310
311
313{
315 const auto result = pair_ix(Segment(Point(2, 5), Point(2, 1)),
316 Segment(Point(2, 3), Point(2, 7)));
317
318 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::OVERLAP);
319 EXPECT_EQ(result.overlap.get_src_point(), Point(2, 3));
320 EXPECT_EQ(result.overlap.get_tgt_point(), Point(2, 5));
321}
322
323
325{
327 const auto result = pair_ix(Segment(Point(3, 3), Point(3, 3)),
328 Segment(Point(0, 0), Point(6, 6)));
329
330 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::POINT);
331 EXPECT_EQ(result.point, Point(3, 3));
332}
333
334
336{
338 const auto result = pair_ix(Segment(Point(7, -2), Point(7, -2)),
339 Segment(Point(7, -2), Point(7, -2)));
340
341 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::POINT);
342 EXPECT_EQ(result.point, Point(7, -2));
343}
344
345
347{
349 const auto result = pair_ix(Segment(Point(1, 1), Point(1, 1)),
350 Segment(Point(2, 2), Point(2, 2)));
351
352 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::NONE);
353 EXPECT_FALSE(result.intersects());
354}
355
356
358{
360 const auto result = pair_ix(Segment(Point(0, 0), Point(2, 0)),
361 Segment(Point(2, 0), Point(5, 0)));
362
363 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::POINT);
364 EXPECT_EQ(result.point, Point(2, 0));
365}
366
367
369{
370 const auto result = segment_segment_intersection(Segment(Point(0, 0), Point(3, 3)),
371 Segment(Point(0, 3), Point(3, 0)));
372
373 EXPECT_EQ(result.kind, SegmentSegmentIntersection::Kind::POINT);
374 EXPECT_EQ(result.point, Point(Geom_Number(3, 2), Geom_Number(3, 2)));
375}
376
377
378// ---------- SweepLineSegmentIntersection ----------
379
387
388
390{
393 segs.append(Segment(Point(0, 0), Point(5, 5)));
394 auto result = sweep(segs);
395 EXPECT_EQ(result.size(), 0u);
396}
397
398
400{
403 segs.append(Segment(Point(0, 0), Point(5, 0)));
404 segs.append(Segment(Point(0, 1), Point(5, 1)));
405 auto result = sweep(segs);
406 EXPECT_EQ(result.size(), 0u);
407}
408
409
411{
414 segs.append(Segment(Point(0, 0), Point(4, 4)));
415 segs.append(Segment(Point(0, 4), Point(4, 0)));
416 auto result = sweep(segs);
417 ASSERT_EQ(result.size(), 1u);
418 EXPECT_EQ(result(0).point, Point(2, 2));
419 EXPECT_EQ(result(0).seg_i, 0u);
420 EXPECT_EQ(result(0).seg_j, 1u);
421}
422
423
425{
426 // Three segments forming a triangle of intersections.
429 segs.append(Segment(Point(0, 0), Point(6, 6))); // s0: diagonal up
430 segs.append(Segment(Point(0, 6), Point(6, 0))); // s1: diagonal down
431 segs.append(Segment(Point(0, 3), Point(6, 3))); // s2: horizontal
432
433 auto result = sweep(segs);
434
436 for (size_t i = 0; i < segs.size(); ++i)
438 for (size_t i = 0; i < result.size(); ++i)
439 scene.highlighted_points.append(result(i).point);
441 "case_sweepline_multiple_intersections", scene,
442 "Sweep-line / multi-intersection degeneracy");
443
444 // s0 x s1 at (3,3), s0 x s2 at (3,3), s1 x s2 at (3,3)
445 // Actually: s0 x s2 at (3,3), s1 x s2 at (3,3), s0 x s1 at (3,3)
446 // All three intersect at (3,3).
447 EXPECT_EQ(result.size(), 3u);
448 for (size_t i = 0; i < result.size(); ++i)
449 EXPECT_EQ(result(i).point, Point(3, 3));
450}
451
452
454{
457 segs.append(Segment(Point(0, 0), Point(1, 0)));
458 segs.append(Segment(Point(3, 3), Point(4, 3)));
459 auto result = sweep(segs);
460 EXPECT_EQ(result.size(), 0u);
461}
462
463
465{
468 segs.append(Segment(Point(0, 2), Point(4, 2))); // horizontal
469 segs.append(Segment(Point(2, 0), Point(2, 2))); // vertical, touching
470 auto result = sweep(segs);
471 ASSERT_EQ(result.size(), 1u);
472 EXPECT_EQ(result(0).point, Point(2, 2));
473}
474
475
477{
480 segs.append(Segment(Point(1, 1), Point(1, 1))); // zero length
481 segs.append(Segment(Point(0, 0), Point(2, 2)));
482 EXPECT_THROW((void) sweep(segs), std::domain_error);
483}
484
485
487{
488 // Four segments through center (2,2).
491 segs.append(Segment(Point(0, 2), Point(4, 2))); // horizontal
492 segs.append(Segment(Point(2, 0), Point(2, 4))); // vertical
493 segs.append(Segment(Point(0, 0), Point(4, 4))); // diagonal up
494 segs.append(Segment(Point(0, 4), Point(4, 0))); // diagonal down
495
496 auto result = sweep(segs);
497
498 // C(4,2) = 6 pairs, all intersecting at (2,2).
499 EXPECT_EQ(result.size(), 6u);
500 for (size_t i = 0; i < result.size(); ++i)
501 EXPECT_EQ(result(i).point, Point(2, 2));
502}
503
504
505// ---------- MonotonePolygonTriangulation ----------
506
508{
509 Polygon p;
510 p.add_vertex(Point(0, 0));
511 p.add_vertex(Point(4, 0));
512 p.add_vertex(Point(2, 3));
513 p.close();
514
517
518 size_t count = 0;
519 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
520 ++count;
521 EXPECT_EQ(count, 1u);
522}
523
524
526{
527 Polygon p;
528 p.add_vertex(Point(0, 0));
529 p.add_vertex(Point(4, 0));
530 p.add_vertex(Point(4, 4));
531 p.add_vertex(Point(0, 4));
532 p.close();
533
536
537 size_t count = 0;
538 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
539 ++count;
540 EXPECT_EQ(count, 2u);
541}
542
543
545{
546 Polygon p;
547 p.add_vertex(Point(0, 0));
548 p.add_vertex(Point(0, 4));
549 p.add_vertex(Point(4, 4));
550 p.add_vertex(Point(4, 0));
551 p.close();
552
555
556 size_t count = 0;
557 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
558 ++count;
559 EXPECT_EQ(count, 2u);
560}
561
562
564{
565 Polygon p;
566 p.add_vertex(Point(2, 0));
567 p.add_vertex(Point(4, 1.5));
568 p.add_vertex(Point(3, 4));
569 p.add_vertex(Point(1, 4));
570 p.add_vertex(Point(0, 1.5));
571 p.close();
572
575
576 size_t count = 0;
577 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
578 ++count;
579 EXPECT_EQ(count, 3u);
580}
581
582
584{
585 Polygon p;
586 p.add_vertex(Point(1, 0));
587 p.add_vertex(Point(2, 0));
588 p.add_vertex(Point(3, 1));
589 p.add_vertex(Point(2, 2));
590 p.add_vertex(Point(1, 2));
591 p.add_vertex(Point(0, 1));
592 p.close();
593
596
597 size_t count = 0;
598 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
599 ++count;
600 EXPECT_EQ(count, 4u);
601}
602
603
605{
606 Polygon p;
607 p.add_vertex(Point(0, 0));
608 p.add_vertex(Point(4, 0));
609 p.add_vertex(Point(2, 3));
610
612 EXPECT_THROW((void) mt(p), std::domain_error);
613}
614
615
617{
618 // Degenerate (collinear) polygon should throw - either at close() time
619 // if vertices are reduced, or at triangulation time for zero area
621 {
622 Polygon p;
623 p.add_vertex(Point(0, 0));
624 p.add_vertex(Point(1, 0));
625 p.add_vertex(Point(2, 0));
626 p.close();
628 (void) mt(p);
629 },
630 std::domain_error);
631}
632
633
635{
636 // L-shaped polygon (non-monotone): both methods should produce n-2 triangles.
637 Polygon p;
638 p.add_vertex(Point(0, 0));
639 p.add_vertex(Point(4, 0));
640 p.add_vertex(Point(4, 2));
641 p.add_vertex(Point(2, 2));
642 p.add_vertex(Point(2, 4));
643 p.add_vertex(Point(0, 4));
644 p.close();
645
648
651
652 size_t mt_count = 0;
653 for (DynList<Triangle>::Iterator it(mt_tris); it.has_curr(); it.next_ne())
654 ++mt_count;
655
656 size_t ear_count = 0;
657 for (DynList<Triangle>::Iterator it(ear_tris); it.has_curr(); it.next_ne())
658 ++ear_count;
659
660 EXPECT_EQ(mt_count, 4u);
662}
663
664
665// ---------- MinkowskiSumConvex ----------
666
668{
669 // Square [0,1]^2 ⊕ Square [0,1]^2 = Square [0,2]^2.
670 Polygon sq;
671 sq.add_vertex(Point(0, 0));
672 sq.add_vertex(Point(1, 0));
673 sq.add_vertex(Point(1, 1));
674 sq.add_vertex(Point(0, 1));
675 sq.close();
676
678 Polygon result = mink(sq, sq);
679
680 EXPECT_EQ(result.size(), 4u);
681 EXPECT_TRUE(result.is_closed());
686}
687
688
690{
691 Polygon sq;
692 sq.add_vertex(Point(0, 0));
693 sq.add_vertex(Point(2, 0));
694 sq.add_vertex(Point(2, 2));
695 sq.add_vertex(Point(0, 2));
696 sq.close();
697
698 Polygon tri;
699 tri.add_vertex(Point(0, 0));
700 tri.add_vertex(Point(1, 0));
701 tri.add_vertex(Point(0, 1));
702 tri.close();
703
705 Polygon result = mink(sq, tri);
706
707 // Square (4 edges) + Triangle (3 edges) = up to 7 vertices.
708 EXPECT_TRUE(result.is_closed());
709 EXPECT_GE(result.size(), 3u);
710 EXPECT_LE(result.size(), 7u);
711
712 // The sum must contain the extreme vertices.
713 EXPECT_TRUE(polygon_contains_vertex(result, Point(0, 0))); // (0,0)+(0,0)
714 EXPECT_TRUE(polygon_contains_vertex(result, Point(3, 0))); // (2,0)+(1,0)
715 EXPECT_TRUE(polygon_contains_vertex(result, Point(0, 3))); // (0,2)+(0,1)
716}
717
718
720{
721 // CW square ⊕ CW square should still work.
723 sq_cw.add_vertex(Point(0, 0));
724 sq_cw.add_vertex(Point(0, 1));
725 sq_cw.add_vertex(Point(1, 1));
726 sq_cw.add_vertex(Point(1, 0));
727 sq_cw.close();
728
730 Polygon result = mink(sq_cw, sq_cw);
731
732 EXPECT_EQ(result.size(), 4u);
733 EXPECT_TRUE(result.is_closed());
738}
739
740
742{
744 convex.add_vertex(Point(0, 0));
745 convex.add_vertex(Point(2, 0));
746 convex.add_vertex(Point(2, 2));
747 convex.add_vertex(Point(0, 2));
748 convex.close();
749
751 concave.add_vertex(Point(0, 0));
752 concave.add_vertex(Point(4, 0));
753 concave.add_vertex(Point(2, 1));
754 concave.add_vertex(Point(4, 4));
755 concave.add_vertex(Point(0, 4));
756 concave.close();
757
759 EXPECT_THROW((void) mink(convex, concave), std::domain_error);
760 EXPECT_THROW((void) mink(concave, convex), std::domain_error);
761}
762
763
765{
766 Polygon open;
767 open.add_vertex(Point(0, 0));
768 open.add_vertex(Point(1, 0));
769 open.add_vertex(Point(1, 1));
770
771 Polygon closed;
772 closed.add_vertex(Point(0, 0));
773 closed.add_vertex(Point(1, 0));
774 closed.add_vertex(Point(0, 1));
775 closed.close();
776
778 EXPECT_THROW((void) mink(open, closed), std::domain_error);
779}
780
781
783{
784 // Pentagon ⊕ Triangle — result must be convex.
786 pent.add_vertex(Point(2, 0));
787 pent.add_vertex(Point(4, 1));
788 pent.add_vertex(Point(3, 3));
789 pent.add_vertex(Point(1, 3));
790 pent.add_vertex(Point(0, 1));
791 pent.close();
792
793 Polygon tri;
794 tri.add_vertex(Point(0, 0));
795 tri.add_vertex(Point(1, 0));
796 tri.add_vertex(Point(0, 1));
797 tri.close();
798
800 Polygon result = mink(pent, tri);
801 EXPECT_TRUE(result.is_closed());
802 EXPECT_GE(result.size(), 3u);
803
804 // Verify convexity: all turns should be consistent.
806 for (Polygon::Vertex_Iterator it(result); it.has_curr(); it.next_ne())
807 rv.append(it.get_current_vertex());
808
809 int sign = 0;
810 for (size_t i = 0; i < rv.size(); ++i)
811 {
812 Geom_Number turn =
813 area_of_parallelogram(rv(i), rv((i + 1) % rv.size()),
814 rv((i + 2) % rv.size()));
815 if (turn == 0) continue;
816 int s = turn > 0 ? 1 : -1;
817 if (sign == 0) sign = s;
818 else EXPECT_EQ(sign, s);
819 }
820}
821
822static Point first_vertex_of(const Polygon & poly)
823{
824 for (Polygon::Vertex_Iterator it(poly); it.has_curr(); it.next_ne())
825 return it.get_current_vertex();
826 return Point(0, 0);
827}
828
830 Point & out_p, Point & out_q)
831{
834 {
836 out_q = out_p;
837 return 0;
838 }
839
840 for (Polygon::Segment_Iterator itp(p); itp.has_curr(); itp.next_ne())
841 {
842 const Segment sp = itp.get_current_segment();
843 for (Polygon::Segment_Iterator itq(q); itq.has_curr(); itq.next_ne())
844 if (sp.intersects_with(itq.get_current_segment()))
845 {
847 out_q = out_p;
848 return 0;
849 }
850 }
851
854
855 bool has_best = false;
857
858 for (size_t i = 0; i < pv.size(); ++i)
859 for (size_t j = 0; j < qv.size(); ++j)
860 {
861 const Segment e(qv(j), qv((j + 1) % qv.size()));
862 const Point proj = e.project(pv(i));
863 const Geom_Number d2 = pv(i).distance_squared_to(proj);
864 if (not has_best or d2 < best_d2)
865 {
866 has_best = true;
867 best_d2 = d2;
868 out_p = pv(i);
869 out_q = proj;
870 }
871 }
872
873 for (size_t i = 0; i < qv.size(); ++i)
874 for (size_t j = 0; j < pv.size(); ++j)
875 {
876 const Segment e(pv(j), pv((j + 1) % pv.size()));
877 const Point proj = e.project(qv(i));
878 const Geom_Number d2 = qv(i).distance_squared_to(proj);
879 if (not has_best or d2 < best_d2)
880 {
881 has_best = true;
882 best_d2 = d2;
883 out_p = proj;
884 out_q = qv(i);
885 }
886 }
887
888 return best_d2;
889}
890
891// ---------- ConvexPolygonDistanceGJK ----------
892
894{
895 Polygon a;
896 a.add_vertex(Point(0, 0));
897 a.add_vertex(Point(1, 0));
898 a.add_vertex(Point(1, 1));
899 a.add_vertex(Point(0, 1));
900 a.close();
901
902 Polygon b;
903 b.add_vertex(Point(2, 0));
904 b.add_vertex(Point(3, 0));
905 b.add_vertex(Point(3, 1));
906 b.add_vertex(Point(2, 1));
907 b.close();
908
910 const auto r = gjk(a, b);
911
912 EXPECT_FALSE(r.intersects);
913 EXPECT_EQ(r.distance_squared, Geom_Number(1));
914 EXPECT_EQ(r.closest_on_first.distance_squared_to(r.closest_on_second),
915 r.distance_squared);
916 EXPECT_LE(r.gjk_iterations, 64u);
917}
918
920{
921 Polygon a;
922 a.add_vertex(Point(0, 0));
923 a.add_vertex(Point(3, 0));
924 a.add_vertex(Point(3, 3));
925 a.add_vertex(Point(0, 3));
926 a.close();
927
928 Polygon b;
929 b.add_vertex(Point(2, 2));
930 b.add_vertex(Point(4, 2));
931 b.add_vertex(Point(4, 4));
932 b.add_vertex(Point(2, 4));
933 b.close();
934
936 const auto r = gjk(a, b);
937
938 EXPECT_TRUE(r.intersects);
939 EXPECT_EQ(r.distance_squared, Geom_Number(0));
940 EXPECT_EQ(r.distance, Geom_Number(0));
941}
942
944{
945 Polygon a;
946 a.add_vertex(Point(0, 0));
947 a.add_vertex(Point(1, 0));
948 a.add_vertex(Point(1, 1));
949 a.add_vertex(Point(0, 1));
950 a.close();
951
952 Polygon b;
953 b.add_vertex(Point(1, 0));
954 b.add_vertex(Point(2, 0));
955 b.add_vertex(Point(2, 1));
956 b.add_vertex(Point(1, 1));
957 b.close();
958
960 const auto r = gjk(a, b);
961
962 EXPECT_TRUE(r.intersects);
963 EXPECT_EQ(r.distance_squared, Geom_Number(0));
964}
965
967{
968 Polygon a;
969 a.add_vertex(Point(0, 0));
970 a.add_vertex(Point(4, 0));
971 a.add_vertex(Point(2, 2));
972 a.close();
973
974 Polygon b;
975 b.add_vertex(Point(6, 1));
976 b.add_vertex(Point(9, 1));
977 b.add_vertex(Point(9, 4));
978 b.add_vertex(Point(6, 4));
979 b.close();
980
982 const auto ab = gjk(a, b);
983 const auto ba = gjk(b, a);
984
985 EXPECT_EQ(ab.distance_squared, ba.distance_squared);
986 EXPECT_EQ(ab.distance, ba.distance);
987 EXPECT_EQ(ab.intersects, ba.intersects);
988}
989
991{
993 convex.add_vertex(Point(0, 0));
994 convex.add_vertex(Point(2, 0));
995 convex.add_vertex(Point(2, 2));
996 convex.add_vertex(Point(0, 2));
997 convex.close();
998
1000 concave.add_vertex(Point(0, 0));
1001 concave.add_vertex(Point(4, 0));
1002 concave.add_vertex(Point(2, 1));
1003 concave.add_vertex(Point(4, 4));
1004 concave.add_vertex(Point(0, 4));
1005 concave.close();
1006
1007 Polygon open;
1008 open.add_vertex(Point(0, 0));
1009 open.add_vertex(Point(1, 0));
1010 open.add_vertex(Point(1, 1));
1011
1013 EXPECT_THROW((void) gjk(open, convex), std::domain_error);
1014 EXPECT_THROW((void) gjk(convex, open), std::domain_error);
1015 EXPECT_THROW((void) gjk(concave, convex), std::domain_error);
1016 EXPECT_THROW((void) gjk(convex, concave), std::domain_error);
1017}
1018
1020{
1023
1024 int seed = 424242;
1025 for (int tc = 0; tc < 12; ++tc)
1026 {
1027 Polygon a, b;
1028 while (a.size() < 3)
1029 {
1031 for (int i = 0; i < 16; ++i)
1032 {
1033 seed = (seed * 1103515245 + 12345) & 0x7fffffff;
1034 const int x = (seed % 51) - 25;
1035 seed = (seed * 1103515245 + 12345) & 0x7fffffff;
1036 const int y = (seed % 51) - 25;
1037 pts.append(Point(x, y));
1038 }
1039 a = hull(pts);
1040 }
1041
1042 while (b.size() < 3)
1043 {
1045 for (int i = 0; i < 16; ++i)
1046 {
1047 seed = (seed * 1103515245 + 12345) & 0x7fffffff;
1048 const int x = (seed % 51) - 25;
1049 seed = (seed * 1103515245 + 12345) & 0x7fffffff;
1050 const int y = (seed % 51) - 25;
1051 pts.append(Point(x + 10, y + 7));
1052 }
1053 b = hull(pts);
1054 }
1055
1056 const auto r = gjk(a, b);
1057 Point bp, bq;
1059
1060 EXPECT_EQ(r.distance_squared, brute_d2);
1061 EXPECT_EQ(r.intersects, brute_d2 == 0);
1062 }
1063}
1064
1065
1066// ---------- KDTreePointSearch ----------
1067
1069{
1070 KDTreePointSearch kd(0, 0, 100, 100);
1071 EXPECT_TRUE(kd.is_empty());
1072
1073 EXPECT_TRUE(kd.insert(Point(10, 20)));
1074 EXPECT_TRUE(kd.insert(Point(50, 50)));
1075 EXPECT_FALSE(kd.insert(Point(10, 20))); // duplicate
1076
1077 EXPECT_EQ(kd.size(), 2u);
1078 EXPECT_TRUE(kd.contains(Point(10, 20)));
1079 EXPECT_TRUE(kd.contains(Point(50, 50)));
1080 EXPECT_FALSE(kd.contains(Point(30, 30)));
1081}
1082
1083
1085{
1086 KDTreePointSearch kd(0, 0, 100, 100);
1087 kd.insert(Point(10, 10));
1088 kd.insert(Point(50, 50));
1089 kd.insert(Point(90, 90));
1090
1091 auto n = kd.nearest(Point(12, 12));
1092 ASSERT_TRUE(n.has_value());
1093 EXPECT_EQ(*n, Point(10, 10));
1094
1095 auto n2 = kd.nearest(Point(48, 52));
1096 ASSERT_TRUE(n2.has_value());
1097 EXPECT_EQ(*n2, Point(50, 50));
1098}
1099
1100
1102{
1103 KDTreePointSearch kd(0, 0, 100, 100);
1104 auto n = kd.nearest(Point(50, 50));
1105 EXPECT_FALSE(n.has_value());
1106}
1107
1108
1110{
1111 Array<Point> points;
1112 for (int x = 0; x < 10; ++x)
1113 for (int y = 0; y < 10; ++y)
1114 points.append(Point(x, y));
1115
1116 auto kd = KDTreePointSearch::build(points, 0, 0, 10, 10);
1117
1118 EXPECT_EQ(kd.size(), 100u);
1119
1120 for (int x = 0; x < 10; ++x)
1121 for (int y = 0; y < 10; ++y)
1122 EXPECT_TRUE(kd.contains(Point(x, y)));
1123
1124 auto n = kd.nearest(Point(5, 5));
1125 ASSERT_TRUE(n.has_value());
1126 EXPECT_EQ(*n, Point(5, 5));
1127}
1128
1129
1131{
1132 KDTreePointSearch kd(0, 0, 100, 100);
1133 kd.insert(Point(10, 10));
1134 kd.insert(Point(20, 20));
1135 kd.insert(Point(50, 50));
1136 kd.insert(Point(80, 80));
1137
1139 kd.range(5, 5, 25, 25, &out);
1140
1141 size_t count = 0;
1142 for (DynList<Point>::Iterator it(out); it.has_curr(); it.next_ne())
1143 ++count;
1144 EXPECT_EQ(count, 2u); // (10,10) and (20,20)
1145}
1146
1147
1149{
1150 KDTreePointSearch kd(0, 0, 100, 100);
1151 kd.insert(Point(1, 1));
1152 kd.insert(Point(2, 2));
1153 kd.insert(Point(3, 3));
1154
1155 size_t visited = 0;
1156 kd.for_each([&visited](const Point &) { ++visited; });
1157 EXPECT_EQ(visited, 3u);
1158}
1159
1161{
1162 KDTreePointSearch kd(0, 0, 100, 100);
1163 kd.insert(Point(10, 10));
1164 kd.insert(Point(25, 40));
1165 kd.insert(Point(70, 20));
1166 kd.insert(Point(80, 90));
1167
1168 const auto snap = kd.debug_snapshot();
1169 EXPECT_EQ(snap.points.size(), kd.size());
1170 EXPECT_GT(snap.partitions.size(), 0u);
1171 EXPECT_EQ(snap.bounds.get_xmin(), Geom_Number(0));
1172 EXPECT_EQ(snap.bounds.get_ymin(), Geom_Number(0));
1173 EXPECT_EQ(snap.bounds.get_xmax(), Geom_Number(100));
1174 EXPECT_EQ(snap.bounds.get_ymax(), Geom_Number(100));
1175
1176 size_t leaves = 0;
1177 for (size_t i = 0; i < snap.partitions.size(); ++i)
1178 if (snap.partitions(i).is_leaf)
1179 ++leaves;
1180 EXPECT_EQ(leaves, kd.size());
1181}
1182
1183
1184// ============================================================================
1185// Phase 5 — Rigorous Tests
1186// ============================================================================
1187
1188// ---------- 5.1 Property tests: Delaunay empty-circumcircle ----------
1189
1190// Helper: squared distance between two points (exact).
1191static Geom_Number dist2(const Point & a, const Point & b)
1192
1193{
1194
1195 return a.distance_squared_to(b);
1196
1197}
1198
1199
1200// Helper: extract sorted vertex set from polygon for comparison.
1202
1203{
1204
1205 Array<Point> v;
1206
1207 for (Polygon::Vertex_Iterator it(p); it.has_curr(); it.next_ne())
1208
1209 v.append(it.get_current_vertex());
1210
1211 quicksort_op(v, [](const Point & a, const Point & b)
1212
1213 {
1214
1215 if (a.get_x() != b.get_x())
1216
1217 return a.get_x() < b.get_x();
1218
1219 return a.get_y() < b.get_y();
1220
1221 });
1222
1223 return v;
1224
1225}
1226
1227
1229{
1230 // The Delaunay property: for every triangle, no other site is strictly
1231 // inside its circumcircle.
1233 auto r = delaunay({Point(0, 0), Point(6, 0), Point(3, 5), Point(6, 5),
1234 Point(0, 5), Point(3, 2), Point(1, 3), Point(5, 1)});
1235
1236 ASSERT_GE(r.triangles.size(), 1u);
1237
1238 for (size_t t = 0; t < r.triangles.size(); ++t)
1239 {
1240 const auto & tri = r.triangles(t);
1241 const Point & a = r.sites(tri.i);
1242 const Point & b = r.sites(tri.j);
1243 const Point & c = r.sites(tri.k);
1244
1245 // Compute circumcenter and squared circumradius.
1246 const Point cc = circumcenter_of(a, b, c);
1247 const Geom_Number r2 = dist2(cc, a);
1248
1249 for (size_t s = 0; s < r.sites.size(); ++s)
1250 {
1251 if (s == tri.i or s == tri.j or s == tri.k)
1252 continue;
1253
1254 const Geom_Number d2 = dist2(cc, r.sites(s));
1255 // d2 must be >= r2 (no site strictly inside the circumcircle).
1256 EXPECT_GE(d2, r2)
1257 << "Site " << s << " violates empty-circumcircle for triangle "
1258 << t;
1259 }
1260 }
1261}
1262
1263
1265{
1266 // Grid of 5x5 points — a stress test of the circumcircle property.
1268 for (int x = 0; x < 5; ++x)
1269 for (int y = 0; y < 5; ++y)
1270 pts.append(Point(x, y));
1271
1273 auto r = delaunay(pts);
1274
1275 ASSERT_GE(r.triangles.size(), 1u);
1276
1277 for (size_t t = 0; t < r.triangles.size(); ++t)
1278 {
1279 const auto & tri = r.triangles(t);
1280 const Point & a = r.sites(tri.i);
1281 const Point & b = r.sites(tri.j);
1282 const Point & c = r.sites(tri.k);
1283 const Point cc = circumcenter_of(a, b, c);
1284 const Geom_Number r2 = dist2(cc, a);
1285
1286 for (size_t s = 0; s < r.sites.size(); ++s)
1287 {
1288 if (s == tri.i or s == tri.j or s == tri.k)
1289 continue;
1290 EXPECT_GE(dist2(cc, r.sites(s)), r2);
1291 }
1292 }
1293}
1294
1295
1296// ---------- 5.1 Property tests: Voronoi equidistance ----------
1297
1299{
1300 // Each bounded Voronoi edge connects two circumcenters.
1301 // Each circumcenter (Voronoi vertex) is equidistant to the 3 sites
1302 // of its Delaunay triangle.
1304 auto dt = delaunay({Point(0, 0), Point(5, 0), Point(6, 3), Point(0, 4),
1305 Point(2, 2), Point(4, 4)});
1306 ASSERT_GE(dt.triangles.size(), 1u);
1307
1308 for (size_t t = 0; t < dt.triangles.size(); ++t)
1309 {
1310 const auto & tri = dt.triangles(t);
1311 const Point & a = dt.sites(tri.i);
1312 const Point & b = dt.sites(tri.j);
1313 const Point & c = dt.sites(tri.k);
1314 const Point cc = circumcenter_of(a, b, c);
1315
1316 const Geom_Number da = dist2(cc, a);
1317 const Geom_Number db = dist2(cc, b);
1318 const Geom_Number dc = dist2(cc, c);
1319
1320 EXPECT_EQ(da, db) << "Triangle " << t << ": circumcenter not equidistant";
1321 EXPECT_EQ(db, dc) << "Triangle " << t << ": circumcenter not equidistant";
1322 }
1323}
1324
1325
1327{
1328 // For each bounded Voronoi edge (connecting two circumcenters c0 and c1),
1329 // the two adjacent sites u,v should be equidistant from the edge midpoint.
1331 auto dt = delaunay({Point(0, 0), Point(5, 0), Point(6, 3), Point(0, 4),
1332 Point(2, 2), Point(4, 4)});
1333
1335 auto r = voronoi(dt);
1336
1337 for (size_t e = 0; e < r.edges.size(); ++e)
1338 {
1339 const auto & edge = r.edges(e);
1340 if (edge.unbounded)
1341 continue;
1342
1343 // Both endpoints are circumcenters equidistant to sites u and v.
1344 const Geom_Number d_src_u = dist2(edge.src, r.sites(edge.site_u));
1345 const Geom_Number d_src_v = dist2(edge.src, r.sites(edge.site_v));
1347 << "Edge " << e << " src not equidistant to sites";
1348
1349 const Geom_Number d_tgt_u = dist2(edge.tgt, r.sites(edge.site_u));
1350 const Geom_Number d_tgt_v = dist2(edge.tgt, r.sites(edge.site_v));
1352 << "Edge " << e << " tgt not equidistant to sites";
1353 }
1354}
1355
1356
1357// ---------- 5.2 Numerical robustness: near-collinear ----------
1358
1360{
1361 // Points almost collinear but with tiny deviation — exact arithmetic
1362 // should handle this correctly.
1363 // Using rational offsets like 1/1000000 instead of floating-point.
1364 Geom_Number tiny(1, 1000000); // 10^-6 as exact rational
1365
1367 pts.append(Point(0, 0));
1368 pts.append(Point(1, tiny));
1369 pts.append(Point(2, -tiny));
1370 pts.append(Point(3, tiny));
1371 pts.append(Point(4, 0));
1372 pts.append(Point(2, 1)); // clearly off-axis to guarantee non-collinear set
1373
1375 auto r = delaunay(pts);
1376
1378 for (DynList<Point>::Iterator it(pts); it.has_curr(); it.next_ne())
1379 scene.points.append(it.get_curr());
1380 for (size_t i = 0; i < r.triangles.size(); ++i)
1381 {
1382 const auto & t = r.triangles(i);
1383 scene.segments.append(Segment(r.sites(t.i), r.sites(t.j)));
1384 scene.segments.append(Segment(r.sites(t.j), r.sites(t.k)));
1385 scene.segments.append(Segment(r.sites(t.k), r.sites(t.i)));
1386 }
1388 "case_robust_near_collinear_delaunay", scene,
1389 "Delaunay robustness / near-collinear");
1390
1391 // Should produce a valid triangulation.
1392 EXPECT_GE(r.triangles.size(), 1u);
1393
1394 // Verify circumcircle property.
1395 for (size_t t = 0; t < r.triangles.size(); ++t)
1396 {
1397 const auto & tri = r.triangles(t);
1398 const Point cc = circumcenter_of(r.sites(tri.i), r.sites(tri.j),
1399 r.sites(tri.k));
1400 const Geom_Number r2 = dist2(cc, r.sites(tri.i));
1401 for (size_t s = 0; s < r.sites.size(); ++s)
1402 {
1403 if (s == tri.i or s == tri.j or s == tri.k)
1404 continue;
1405 EXPECT_GE(dist2(cc, r.sites(s)), r2);
1406 }
1407 }
1408}
1409
1410
1412{
1413 // Near-collinear points should still produce a valid hull.
1414 Geom_Number tiny(1, 10000000); // 10^-7
1415
1417 pts.append(Point(0, 0));
1418 pts.append(Point(1, tiny));
1419 pts.append(Point(2, 0));
1420 pts.append(Point(3, -tiny));
1421 pts.append(Point(4, 0));
1422 pts.append(Point(2, 1)); // off-line to make non-degenerate
1423
1426
1428 for (DynList<Point>::Iterator it(pts); it.has_curr(); it.next_ne())
1429 scene.points.append(it.get_curr());
1430 scene.polygons.append(hull);
1433 "case_robust_near_collinear_hull", scene,
1434 "Convex hull robustness / near-collinear");
1435
1436 EXPECT_TRUE(hull.is_closed());
1437 EXPECT_GE(hull.size(), 3u);
1438
1439 // Hull must contain the extremes.
1443}
1444
1445
1446// ---------- 5.2 Numerical robustness: near-parallel segments ----------
1447
1449{
1450 // Two segments that are nearly parallel — they intersect at a very
1451 // distant point. The sweep line should either find 0 or 1 intersection
1452 // depending on whether the segments actually overlap.
1453 Geom_Number tiny(1, 100000000); // 10^-8
1454
1457 segs.append(Segment(Point(0, 0), Point(10, 0)));
1458 segs.append(Segment(Point(0, tiny), Point(10, tiny))); // almost parallel
1459
1460 auto result = sweep(segs);
1461 EXPECT_EQ(result.size(), 0u); // truly parallel, no intersection
1462}
1463
1464
1466{
1467 // Two segments that converge at a nearly-parallel angle.
1468 Geom_Number tiny(1, 1000000);
1469
1472 segs.append(Segment(Point(0, 0), Point(10, 0)));
1473 segs.append(Segment(Point(0, tiny), Point(10, -tiny))); // slight converge
1474
1475 auto result = sweep(segs);
1476
1478 for (size_t i = 0; i < segs.size(); ++i)
1480 for (size_t i = 0; i < result.size(); ++i)
1481 scene.highlighted_points.append(result(i).point);
1483 "case_robust_near_parallel_converging", scene,
1484 "Near-parallel segments / converging intersection");
1485
1486 ASSERT_EQ(result.size(), 1u);
1487 // Intersection must be exact.
1488 EXPECT_EQ(result(0).point.get_y(), Geom_Number(0)); // should be on y=0 plane
1489}
1490
1491
1492// ---------- 5.2 Numerical robustness: extreme coordinates ----------
1493
1495{
1496 // Points with very large coordinates — exact arithmetic handles this.
1497 Geom_Number big(1000000000); // 10^9
1498
1500 pts.append(Point(big, big));
1501 pts.append(Point(-big, big));
1502 pts.append(Point(-big, -big));
1503 pts.append(Point(big, -big));
1504 pts.append(Point(0, 0));
1505
1507 auto r = delaunay(pts);
1508 EXPECT_GE(r.triangles.size(), 1u);
1509
1510 // Verify circumcircle property with big coords.
1511 for (size_t t = 0; t < r.triangles.size(); ++t)
1512 {
1513 const auto & tri = r.triangles(t);
1514 const Point cc = circumcenter_of(r.sites(tri.i), r.sites(tri.j),
1515 r.sites(tri.k));
1516 const Geom_Number r2 = dist2(cc, r.sites(tri.i));
1517 for (size_t s = 0; s < r.sites.size(); ++s)
1518 {
1519 if (s == tri.i or s == tri.j or s == tri.k)
1520 continue;
1521 EXPECT_GE(dist2(cc, r.sites(s)), r2);
1522 }
1523 }
1524}
1525
1526
1528{
1529 // Points with very small coordinates.
1530 Geom_Number eps(1, 1000000000); // 10^-9
1531
1533 pts.append(Point(0, 0));
1534 pts.append(Point(eps, 0));
1535 pts.append(Point(0, eps));
1536 pts.append(Point(eps, eps));
1537
1539 auto r = delaunay(pts);
1540 EXPECT_GE(r.triangles.size(), 2u);
1541}
1542
1543
1544// ---------- 5.2 Numerical robustness: cocircular points ----------
1545
1547{
1548 // 8 points on a circle — a degenerate case for Delaunay.
1549 // The triangulation should still be valid and complete.
1550 // Use rational approximations of circle points:
1551 // (2,0), (0,2), (-2,0), (0,-2), and 4 diagonal points.
1553 pts.append(Point(2, 0));
1554 pts.append(Point(0, 2));
1555 pts.append(Point(-2, 0));
1556 pts.append(Point(0, -2));
1557
1558 // Points at 45-degree offsets (rational approximation on circle r=2).
1559 // Use exact: sqrt(2) ≈ 1414/1000 (close enough for testing cocircularity).
1560 // Actually, for true cocircularity use x^2+y^2 = 4.
1561 // (1, sqrt(3)) is on circle r=2: 1+3=4. Use Geom_Number for sqrt(3).
1562 // Instead, let's use: (8/5, 6/5) since (8/5)^2+(6/5)^2 = 64/25+36/25 = 100/25 = 4.
1563 pts.append(Point(Geom_Number(8, 5), Geom_Number(6, 5)));
1564 pts.append(Point(Geom_Number(-8, 5), Geom_Number(6, 5)));
1565 pts.append(Point(Geom_Number(-8, 5), Geom_Number(-6, 5)));
1566 pts.append(Point(Geom_Number(8, 5), Geom_Number(-6, 5)));
1567
1569 auto r = delaunay(pts);
1570
1572 for (DynList<Point>::Iterator it(pts); it.has_curr(); it.next_ne())
1573 scene.points.append(it.get_curr());
1574 for (size_t i = 0; i < r.triangles.size(); ++i)
1575 {
1576 const auto & t = r.triangles(i);
1577 scene.segments.append(Segment(r.sites(t.i), r.sites(t.j)));
1578 scene.segments.append(Segment(r.sites(t.j), r.sites(t.k)));
1579 scene.segments.append(Segment(r.sites(t.k), r.sites(t.i)));
1580 }
1582 "case_robust_cocircular_delaunay", scene,
1583 "Delaunay robustness / cocircular points");
1584
1585 // Must produce a triangulation.
1586 EXPECT_GE(r.triangles.size(), 6u); // at least 6 triangles for 8 cocircular pts
1587
1588 // All sites should participate.
1589 EXPECT_EQ(r.sites.size(), 8u);
1590}
1591
1592
1593// ---------- 5.3 Determinism: permuted inputs produce same results ----------
1594
1596{
1597 // The Delaunay output should be the same regardless of input order.
1599 pts1.append(Point(0, 0));
1600 pts1.append(Point(5, 0));
1601 pts1.append(Point(6, 3));
1602 pts1.append(Point(0, 4));
1603 pts1.append(Point(3, 2));
1604
1605 DynList<Point> pts2; // reverse order
1606 pts2.append(Point(3, 2));
1607 pts2.append(Point(0, 4));
1608 pts2.append(Point(6, 3));
1609 pts2.append(Point(5, 0));
1610 pts2.append(Point(0, 0));
1611
1612 DynList<Point> pts3; // shuffled
1613 pts3.append(Point(6, 3));
1614 pts3.append(Point(0, 0));
1615 pts3.append(Point(3, 2));
1616 pts3.append(Point(5, 0));
1617 pts3.append(Point(0, 4));
1618
1620 auto r1 = delaunay(pts1);
1621 auto r2 = delaunay(pts2);
1622 auto r3 = delaunay(pts3);
1623
1624 // Same number of sites and triangles.
1625 EXPECT_EQ(r1.sites.size(), r2.sites.size());
1626 EXPECT_EQ(r1.sites.size(), r3.sites.size());
1627 EXPECT_EQ(r1.triangles.size(), r2.triangles.size());
1628 EXPECT_EQ(r1.triangles.size(), r3.triangles.size());
1629
1630 // Canonical triangle sets should match.
1631 auto ct1 = canonical_triangles(r1);
1632 auto ct2 = canonical_triangles(r2);
1633 auto ct3 = canonical_triangles(r3);
1634
1635 ASSERT_EQ(ct1.size(), ct2.size());
1636 ASSERT_EQ(ct1.size(), ct3.size());
1637
1638 for (size_t i = 0; i < ct1.size(); ++i)
1639 {
1640 EXPECT_EQ(ct1(i).a, ct2(i).a);
1641 EXPECT_EQ(ct1(i).b, ct2(i).b);
1642 EXPECT_EQ(ct1(i).c, ct2(i).c);
1643 EXPECT_EQ(ct1(i).a, ct3(i).a);
1644 EXPECT_EQ(ct1(i).b, ct3(i).b);
1645 EXPECT_EQ(ct1(i).c, ct3(i).c);
1646 }
1647}
1648
1649
1651{
1653 pts1.append(Point(0, 0));
1654 pts1.append(Point(5, 0));
1655 pts1.append(Point(6, 3));
1656 pts1.append(Point(0, 4));
1657 pts1.append(Point(3, 1)); // interior point
1658
1660 pts2.append(Point(3, 1));
1661 pts2.append(Point(0, 4));
1662 pts2.append(Point(6, 3));
1663 pts2.append(Point(5, 0));
1664 pts2.append(Point(0, 0));
1665
1667 Polygon h1 = andrew(pts1);
1668 Polygon h2 = andrew(pts2);
1669
1670 auto v1 = sorted_hull_vertices(h1);
1671 auto v2 = sorted_hull_vertices(h2);
1672
1673 ASSERT_EQ(v1.size(), v2.size());
1674 for (size_t i = 0; i < v1.size(); ++i)
1675 EXPECT_EQ(v1(i), v2(i));
1676}
1677
1678
1680{
1682 pts1.append(Point(0, 0));
1683 pts1.append(Point(10, 10));
1684 pts1.append(Point(1, 0)); // closest pair: (0,0)-(1,0)
1685 pts1.append(Point(5, 5));
1686
1688 pts2.append(Point(5, 5));
1689 pts2.append(Point(1, 0));
1690 pts2.append(Point(0, 0));
1691 pts2.append(Point(10, 10));
1692
1694 auto r1 = cp(pts1);
1695 auto r2 = cp(pts2);
1696
1697 EXPECT_EQ(r1.distance_squared, r2.distance_squared);
1698 // Same pair (possibly swapped).
1699 EXPECT_TRUE(matches_unordered_pair(r1.first, r1.second, r2.first, r2.second));
1700}
1701
1702
1703// ---------- 5.4 Performance: large datasets ----------
1704
1706{
1707 // 10000 points on a grid — convex hull should return the boundary.
1709 for (int x = 0; x < 100; ++x)
1710 for (int y = 0; y < 100; ++y)
1711 pts.append(Point(x, y));
1712
1715
1716 EXPECT_TRUE(hull.is_closed());
1717 // The hull of a grid is the bounding rectangle.
1718 EXPECT_EQ(hull.size(), 4u);
1723}
1724
1725
1727{
1728 // 5000 points on a grid; minimum distance = 1.
1730 for (int x = 0; x < 50; ++x)
1731 for (int y = 0; y < 100; ++y)
1732 pts.append(Point(x, y));
1733
1735 auto r = cp(pts);
1736
1737 EXPECT_EQ(r.distance_squared, Geom_Number(1));
1738}
1739
1740
1742{
1743 // 500 points on a grid — verify valid Delaunay.
1745 for (int x = 0; x < 25; ++x)
1746 for (int y = 0; y < 20; ++y)
1747 pts.append(Point(x, y));
1748
1750 auto r = delaunay(pts);
1751
1752 EXPECT_GE(r.triangles.size(), 1u);
1753
1754 // Spot-check a few triangles for circumcircle property.
1755 const size_t check_limit = r.triangles.size() < 50 ? r.triangles.size() : 50;
1756 for (size_t t = 0; t < check_limit; ++t)
1757 {
1758 const auto & tri = r.triangles(t);
1759 const Point cc = circumcenter_of(r.sites(tri.i), r.sites(tri.j),
1760 r.sites(tri.k));
1761 const Geom_Number r2 = dist2(cc, r.sites(tri.i));
1762 for (size_t s = 0; s < r.sites.size(); ++s)
1763 {
1764 if (s == tri.i or s == tri.j or s == tri.k)
1765 continue;
1766 EXPECT_GE(dist2(cc, r.sites(s)), r2);
1767 }
1768 }
1769}
1770
1771
1773{
1774 // Build a simple polygon with ~100 vertices (zigzag) — no collinear edges.
1775 // Triangulation should produce n-2 triangles.
1776 Polygon p;
1777
1778 // Bottom zigzag: (0,0), (1,1), (2,0), (3,1), ..., (48,0), (49,1), (50,0)
1779 for (int x = 0; x <= 50; ++x)
1780 p.add_vertex(Point(x, (x % 2 == 0) ? 0 : 1));
1781
1782 // Top zigzag going back: (50,10), (49,9), (48,10), ..., (1,9), (0,10)
1783 for (int x = 50; x >= 0; --x)
1784 p.add_vertex(Point(x, (x % 2 == 0) ? 10 : 9));
1785
1786 p.close();
1787
1788 const size_t nv = p.size();
1789 ASSERT_GE(nv, 50u);
1790
1793
1794 size_t count = 0;
1795 for (DynList<Triangle>::Iterator it(tris); it.has_curr(); it.next_ne())
1796 ++count;
1797
1798 EXPECT_EQ(count, nv - 2);
1799}
1800
1801
1802// ---------- 5.5 Cross-algorithm comparison: 5 convex hulls ----------
1803
1805{
1807 pts.append(Point(0, 0));
1808 pts.append(Point(5, 0));
1809 pts.append(Point(6, 3));
1810 pts.append(Point(3, 6));
1811 pts.append(Point(0, 4));
1812 pts.append(Point(2, 1)); // interior
1813 pts.append(Point(3, 2)); // interior
1814
1820
1826
1832
1833 // All must have the same vertex count.
1834 ASSERT_EQ(v_andrew.size(), v_graham.size())
1835 << "Andrew vs Graham vertex count mismatch";
1836 ASSERT_EQ(v_andrew.size(), v_brute.size())
1837 << "Andrew vs BruteForce vertex count mismatch";
1838 ASSERT_EQ(v_andrew.size(), v_gift.size())
1839 << "Andrew vs GiftWrapping vertex count mismatch";
1840 ASSERT_EQ(v_andrew.size(), v_quick.size())
1841 << "Andrew vs QuickHull vertex count mismatch";
1842
1843 // All must have the same vertices.
1844 for (size_t i = 0; i < v_andrew.size(); ++i)
1845 {
1847 << "Andrew vs Graham mismatch at index " << i;
1848 EXPECT_EQ(v_andrew(i), v_brute(i))
1849 << "Andrew vs BruteForce mismatch at index " << i;
1850 EXPECT_EQ(v_andrew(i), v_gift(i))
1851 << "Andrew vs GiftWrapping mismatch at index " << i;
1852 EXPECT_EQ(v_andrew(i), v_quick(i))
1853 << "Andrew vs QuickHull mismatch at index " << i;
1854 }
1855}
1856
1857
1859{
1860 // 100 points, mix of grid + interior + boundary.
1862 for (int x = 0; x <= 10; ++x)
1863 for (int y = 0; y <= 10; ++y)
1864 pts.append(Point(x, y));
1865
1866 // Add some extra interior points.
1867 pts.append(Point(5, 5));
1868 pts.append(Point(3, 7));
1869 pts.append(Point(8, 2));
1870
1876
1882
1888
1889 ASSERT_EQ(v_andrew.size(), v_graham.size());
1890 ASSERT_EQ(v_andrew.size(), v_brute.size());
1891 ASSERT_EQ(v_andrew.size(), v_gift.size());
1892 ASSERT_EQ(v_andrew.size(), v_quick.size());
1893
1894 for (size_t i = 0; i < v_andrew.size(); ++i)
1895 {
1896 EXPECT_EQ(v_andrew(i), v_graham(i));
1897 EXPECT_EQ(v_andrew(i), v_brute(i));
1898 EXPECT_EQ(v_andrew(i), v_gift(i));
1899 EXPECT_EQ(v_andrew(i), v_quick(i));
1900 }
1901}
1902
1903
1905{
1906 // Many collinear points on the hull boundary.
1908 for (int x = 0; x <= 20; ++x)
1909 {
1910 pts.append(Point(x, 0)); // bottom
1911 pts.append(Point(x, 10)); // top
1912 }
1913 pts.append(Point(0, 5)); // left
1914 pts.append(Point(20, 5)); // right
1915
1919
1923
1927
1928 // For collinear points, algorithms may differ on whether they include
1929 // intermediate points. Compare just the extreme corners.
1934
1939
1944}
1945
1946
1948{
1949 // All points on hull (triangle) — all algorithms must agree.
1951 pts.append(Point(0, 0));
1952 pts.append(Point(10, 0));
1953 pts.append(Point(5, 8));
1954
1960
1966
1967 ASSERT_EQ(v_andrew.size(), 3u);
1968 ASSERT_EQ(v_graham.size(), 3u);
1969 ASSERT_EQ(v_brute.size(), 3u);
1970 ASSERT_EQ(v_gift.size(), 3u);
1971 ASSERT_EQ(v_quick.size(), 3u);
1972
1973 for (size_t i = 0; i < 3; ++i)
1974 {
1975 EXPECT_EQ(v_andrew(i), v_graham(i));
1976 EXPECT_EQ(v_andrew(i), v_brute(i));
1977 EXPECT_EQ(v_andrew(i), v_gift(i));
1978 EXPECT_EQ(v_andrew(i), v_quick(i));
1979 }
1980}
1981
1982
1983// ============================================================================
1984// Section 5.1 — Tests for new algorithms
1985// ============================================================================
1986
1987// ---------- Delaunay O(n log n) — randomized incremental ----------
1988
1990{
1992 pts.append(Point(0, 0));
1993 pts.append(Point(4, 0));
1994 pts.append(Point(4, 4));
1995 pts.append(Point(0, 4));
1996
1998 auto r = delaunay(pts);
1999
2000 EXPECT_EQ(r.sites.size(), 4u);
2001 EXPECT_EQ(r.triangles.size(), 2u);
2002}
2003
2004
2006{
2008 pts.append(Point(0, 0));
2009 pts.append(Point(5, 0));
2010 pts.append(Point(5, 5));
2011 pts.append(Point(0, 5));
2012 pts.append(Point(2, 3));
2013 pts.append(Point(3, 1));
2014
2016 auto r = delaunay(pts);
2017
2018 EXPECT_GE(r.triangles.size(), 4u);
2019
2020 for (size_t t = 0; t < r.triangles.size(); ++t)
2021 {
2022 const auto & tri = r.triangles(t);
2023 const Point cc = circumcenter_of(r.sites(tri.i), r.sites(tri.j),
2024 r.sites(tri.k));
2025 const Geom_Number r2 = dist2(cc, r.sites(tri.i));
2026 for (size_t s = 0; s < r.sites.size(); ++s)
2027 {
2028 if (s == tri.i || s == tri.j || s == tri.k)
2029 continue;
2030 EXPECT_GE(dist2(cc, r.sites(s)), r2)
2031 << "Delaunay incremental: site " << s
2032 << " violates circumcircle of triangle " << t;
2033 }
2034 }
2035}
2036
2037
2039{
2041 pts.append(Point(0, 0));
2042 pts.append(Point(10, 0));
2043 pts.append(Point(10, 10));
2044 pts.append(Point(0, 10));
2045 pts.append(Point(5, 5));
2046 pts.append(Point(3, 7));
2047 pts.append(Point(7, 2));
2048 pts.append(Point(1, 3));
2049
2051 auto rbw = bw(pts);
2052
2054 auto rinc = inc(pts);
2055
2056 EXPECT_EQ(rbw.sites.size(), rinc.sites.size());
2057 EXPECT_EQ(rbw.triangles.size(), rinc.triangles.size());
2058}
2059
2060
2062{
2064 auto r = delaunay({Point(0, 0), Point(1, 0), Point(0, 1)});
2065 EXPECT_EQ(r.sites.size(), 3u);
2066 EXPECT_EQ(r.triangles.size(), 1u);
2067}
2068
2069
2071{
2073 pts.append(Point(0, 0));
2074 pts.append(Point(1, 0));
2075 pts.append(Point(2, 0));
2076 pts.append(Point(3, 0));
2077
2079 auto r = delaunay(pts);
2080
2081 EXPECT_EQ(r.triangles.size(), 0u);
2082}
2083
2084
2086{
2088 pts.append(Point(0, 0));
2089 pts.append(Point(1, 0));
2090 pts.append(Point(0, 1));
2091 pts.append(Point(0, 0));
2092 pts.append(Point(1, 0));
2093
2095 auto r = delaunay(pts);
2096 EXPECT_EQ(r.sites.size(), 3u);
2097 EXPECT_EQ(r.triangles.size(), 1u);
2098}
2099
2100
2102{
2104 for (int x = 0; x <= 4; ++x)
2105 for (int y = 0; y <= 4; ++y)
2106 pts.append(Point(x, y));
2107
2109 auto r = delaunay(pts);
2110
2111 EXPECT_EQ(r.sites.size(), 25u);
2112 EXPECT_GE(r.triangles.size(), 32u);
2113
2114 for (size_t t = 0; t < r.triangles.size(); ++t)
2115 {
2116 const auto & tri = r.triangles(t);
2117 const Point cc = circumcenter_of(r.sites(tri.i), r.sites(tri.j),
2118 r.sites(tri.k));
2119 const Geom_Number cr2 = dist2(cc, r.sites(tri.i));
2120 for (size_t s = 0; s < r.sites.size(); ++s)
2121 {
2122 if (s == tri.i || s == tri.j || s == tri.k)
2123 continue;
2124 EXPECT_GE(dist2(cc, r.sites(s)), cr2);
2125 }
2126 }
2127}
2128
2129
2130// ---------- VoronoiDiagramFortune ----------
2131
2133{
2135 auto r = voronoi({Point(0, 0), Point(4, 0), Point(4, 4), Point(0, 4)});
2136
2137 EXPECT_EQ(r.sites.size(), 4u);
2138 EXPECT_GE(r.vertices.size(), 1u);
2139 EXPECT_GE(r.edges.size(), 1u);
2140}
2141
2142
2144{
2146 pts.append(Point(0, 0));
2147 pts.append(Point(6, 0));
2148 pts.append(Point(3, 5));
2149 pts.append(Point(6, 5));
2150 pts.append(Point(0, 5));
2151
2153 auto r = voronoi(pts);
2154
2155 for (size_t e = 0; e < r.edges.size(); ++e)
2156 {
2157 const auto & edge = r.edges(e);
2158 if (edge.unbounded) continue;
2159
2160 const Geom_Number d_u = dist2(edge.src, r.sites(edge.site_u));
2161 const Geom_Number d_v = dist2(edge.src, r.sites(edge.site_v));
2162 EXPECT_EQ(d_u, d_v) << "Voronoi edge src not equidistant for edge " << e;
2163 }
2164}
2165
2166
2168{
2170 pts.append(Point(1, 1));
2171 pts.append(Point(3, 1));
2172 pts.append(Point(2, 3));
2173
2174 Polygon clip;
2175 clip.add_vertex(Point(0, 0));
2176 clip.add_vertex(Point(4, 0));
2177 clip.add_vertex(Point(4, 4));
2178 clip.add_vertex(Point(0, 4));
2179 clip.close();
2180
2182 auto cells = voronoi.clipped_cells(pts, clip);
2183
2184 EXPECT_EQ(cells.size(), 3u);
2185 for (size_t i = 0; i < cells.size(); ++i)
2186 EXPECT_TRUE(cells(i).polygon.is_closed());
2187}
2188
2189
2191{
2193 pts.append(Point(0, 0));
2194 pts.append(Point(6, 0));
2195 pts.append(Point(7, 3));
2196 pts.append(Point(2, 7));
2197 pts.append(Point(-1, 4));
2198
2201
2202 const auto dual = dual_voronoi(pts);
2203 const auto fortune = fortune_voronoi(pts);
2204
2205 EXPECT_EQ(fortune.sites.size(), dual.sites.size());
2206 EXPECT_EQ(fortune.vertices.size(), dual.vertices.size());
2207 EXPECT_EQ(fortune.edges.size(), dual.edges.size());
2208 EXPECT_EQ(fortune.cells.size(), dual.cells.size());
2209}
2210
2211
2212// ---------- ConvexPolygonDecomposition ----------
2213
2215{
2216 Polygon p;
2217 p.add_vertex(Point(0, 0));
2218 p.add_vertex(Point(4, 0));
2219 p.add_vertex(Point(2, 3));
2220 p.close();
2221
2223 auto parts = decomp(p);
2224
2225 EXPECT_EQ(parts.size(), 1u);
2226 EXPECT_TRUE(parts(0).is_closed());
2227}
2228
2229
2230// ---------- SweepLineSegmentIntersection: new critical tests ----------
2231
2233{
2234 // Two collinear overlapping segments on the x-axis.
2235 // Segments [0,4] and [2,6] overlap on [2,4].
2238 segs.append(Segment(Point(0, 0), Point(4, 0)));
2239 segs.append(Segment(Point(2, 0), Point(6, 0)));
2240
2241 auto result = sweep(segs);
2242
2243 // Overlap must be reported at least at one overlap boundary point.
2244 EXPECT_GE(result.size(), 1u)
2245 << "Collinear overlapping segments were not reported";
2246}
2247
2248
2250{
2251 // 10 segments all sharing endpoint (5,5), fanning out.
2254 const size_t N = 10;
2255 for (size_t i = 0; i < N; ++i)
2256 {
2258 / Geom_Number(N);
2259 Point far(Geom_Number(5) + Geom_Number(10) * Geom_Number(cos(angle.get_d())),
2260 Geom_Number(5) + Geom_Number(10) * Geom_Number(sin(angle.get_d())));
2261 segs.append(Segment(Point(5, 5), far));
2262 }
2263
2264 auto result = sweep(segs);
2265
2266 // C(10,2) = 45 pairwise intersections, all at (5,5).
2267 EXPECT_EQ(result.size(), N * (N - 1) / 2);
2268 for (size_t i = 0; i < result.size(); ++i)
2269 EXPECT_EQ(result(i).point, Point(5, 5));
2270}
2271
2272
2274{
2275 // Mix of vertical, horizontal, and diagonal segments.
2278
2279 segs.append(Segment(Point(3, 0), Point(3, 6))); // vertical
2280 segs.append(Segment(Point(0, 3), Point(6, 3))); // horizontal
2281 segs.append(Segment(Point(0, 0), Point(6, 6))); // diagonal
2282
2283 auto result = sweep(segs);
2284
2285 // vertical x horizontal at (3,3), vertical x diagonal at (3,3),
2286 // horizontal x diagonal at (3,3).
2287 EXPECT_EQ(result.size(), 3u);
2288 for (size_t i = 0; i < result.size(); ++i)
2289 EXPECT_EQ(result(i).point, Point(3, 3));
2290}
2291
2292
2294{
2295 // 10K random short segments. Verify each reported intersection is real.
2298
2299 std::mt19937 rng(999);
2300 std::uniform_real_distribution<double> coord(-100.0, 100.0);
2301 std::uniform_real_distribution<double> delta(-5.0, 5.0);
2302
2303 const size_t N = 10000;
2304 for (size_t i = 0; i < N; ++i)
2305 {
2306 double x = coord(rng), y = coord(rng);
2307 double dx = delta(rng), dy = delta(rng);
2308 if (dx == 0 and dy == 0) dx = 1;
2310 Point(Geom_Number(x + dx), Geom_Number(y + dy))));
2311 }
2312
2313 auto result = sweep(segs);
2314
2315 // Spot-check first 100 intersections: the reported point must lie on
2316 // both participating segments (within tolerance).
2317 const size_t check = std::min(result.size(), (size_t) 100);
2318 for (size_t i = 0; i < check; ++i)
2319 {
2320 const auto & ix = result(i);
2321 const Segment & s1 = segs(ix.seg_i);
2322 const Segment & s2 = segs(ix.seg_j);
2323
2324 // Verify the two segments actually intersect.
2325 EXPECT_TRUE(s1.intersects_with(s2))
2326 << "seg " << ix.seg_i << " and seg " << ix.seg_j
2327 << " reported as intersecting but don't";
2328 }
2329}
Andrew's monotonic chain convex hull algorithm.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
Brute force convex hull algorithm.
Closest pair of points via divide and conquer.
Decompose a simple polygon into convex parts using Hertel-Mehlhorn.
Distance between two closed convex polygons using GJK.
Polygon triangulation using the ear-cutting algorithm.
Exact Delaunay triangulation using the Bowyer-Watson incremental algorithm.
static DynList< Triangle > as_triangles(const Result &result)
Convert indexed triangulation to geometric triangles.
O(n log n) expected-time Delaunay's triangulation.
Iterator on the items of list.
Definition htlist.H:1420
Dynamic singly linked list with functional programming support.
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
static Array< Point > extract_vertices(const Polygon &poly)
Extract vertices from a polygon into an array for indexed access.
Gift wrapping (Jarvis march) convex hull algorithm.
Graham scan convex hull algorithm.
bool has_curr() const noexcept
Definition htlist.H:930
Spatial point index for O(log n) nearest-neighbor queries.
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.
Exact Minkowski sum of two closed convex polygons.
O(n log n) triangulation of simple polygons via y-monotone partition + linear-time monotone triangula...
static bool contains(const Polygon &poly, const Point &p)
Return true if the point is inside or on the boundary.
Represents a point with rectangular coordinates in a 2D plane.
Definition point.H:229
const Geom_Number & get_x() const noexcept
Gets the x-coordinate value.
Definition point.H:457
const Geom_Number & get_y() const noexcept
Gets the y-coordinate value.
Definition point.H:466
Geom_Number distance_squared_to(const Point &that) const
Calculates the squared Euclidean distance to another point.
Definition point.H:1454
Iterator over the edges (segments) of a polygon.
Definition polygon.H:520
bool has_curr() const
Check if there is a current segment.
Definition polygon.H:537
A general (irregular) 2D polygon defined by a sequence of vertices.
Definition polygon.H:246
void add_vertex(const Point &point)
Add a vertex to the polygon.
Definition polygon.H:677
void close()
Close the polygon.
Definition polygon.H:840
const bool & is_closed() const
Check if the polygon is closed.
Definition polygon.H:473
const size_t & size() const
Get the number of vertices.
Definition polygon.H:477
QuickHull convex hull algorithm.
Rotating calipers metrics for convex polygons.
Dedicated exact intersection for a single pair of segments.
Represents a line segment between two points.
Definition point.H:827
Point project(const Point &p) const
Orthogonal projection of a point onto this segment's infinite line, clamped to the segment's endpoint...
Definition point.H:1147
Report all pairwise intersection points among a set of segments.
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.
Voronoi diagram derived as the dual of a Delaunay triangulation.
O(n log n) Voronoi diagram construction.
static mt19937 rng
#define N
Definition fib.C:294
static Array< Point > sorted_hull_vertices(const Polygon &p)
static Point first_vertex_of(const Polygon &poly)
static Geom_Number brute_convex_distance_squared(const Polygon &p, const Polygon &q, Point &out_p, Point &out_q)
static Geom_Number dist2(const Point &a, const Point &b)
TEST_F(GeomAlgorithmsTest, ClosestPairEmptyInputThrows)
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_cos_function > > cos(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4069
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_sin_function > > sin(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4070
static mpfr_t y
Definition mpfr_mul_d.c:3
void add_polygon_vertices(SvgScene &scene, const ::Polygon &poly, const bool as_highlight=false)
std::filesystem::path emit_case_svg(const std::string &case_id, const SvgScene &scene, const std::string &note="")
and
Check uniqueness with explicit hash + equality functors.
Geom_Number area_of_parallelogram(const Point &a, const Point &b, const Point &c)
Compute the signed area of the parallelogram defined by vectors a->b and a->c.
Definition point.H:2803
size_t size(Node *root) noexcept
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
mpq_class Geom_Number
Numeric type used by the geometry module.
Definition point.H:115
SegmentSegmentIntersection::Result segment_segment_intersection(const Segment &s1, const Segment &s2)
Convenience free-function wrapper for SegmentSegmentIntersection.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
void quicksort_op(C< T > &a, const Compare &cmp=Compare(), const size_t threshold=Quicksort_Threshold)
Optimized quicksort for containers using operator().
Iterator over the vertices of a polygon.
Definition polygon.H:489
bool check(GT &g)
ValueArg< size_t > seed
Definition testHash.C:48
gsl_rng * r