Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
geom_algorithms_test_boolean_3d_serializer_aabb_misc.cc
Go to the documentation of this file.
2
3
4// ---------- Concave polygon boolean operations (Greiner-Hormann) ----------
5
7{
8 // Two overlapping L-shaped (concave) polygons.
9 //
10 // L1: (0,0)-(6,0)-(6,3)-(3,3)-(3,6)-(0,6)
11 // L2: (2,2)-(8,2)-(8,8)-(5,8)-(5,5)-(2,5)
12 //
13 // Their intersection is a non-trivial concave region.
14
15 Polygon L1;
16 L1.add_vertex(Point(0, 0));
17 L1.add_vertex(Point(6, 0));
18 L1.add_vertex(Point(6, 3));
19 L1.add_vertex(Point(3, 3));
20 L1.add_vertex(Point(3, 6));
21 L1.add_vertex(Point(0, 6));
22 L1.close();
23
24 Polygon L2;
25 L2.add_vertex(Point(2, 2));
26 L2.add_vertex(Point(8, 2));
27 L2.add_vertex(Point(8, 8));
28 L2.add_vertex(Point(5, 8));
29 L2.add_vertex(Point(5, 5));
30 L2.add_vertex(Point(2, 5));
31 L2.close();
32
34 auto result = bop.intersection(L1, L2);
35
36 // Should produce at least one polygon.
37 EXPECT_GE(result.size(), 1u);
38
39 // The intersection area must be strictly less than either L-shape's area.
40 // Each L-shape has area 27 (= 6*6 - 3*3). The intersection should be
41 // much smaller.
42 if (result.size() >= 1)
43 EXPECT_GE(result(0).size(), 3u);
44}
45
46
48{
49 // Verify that union of two overlapping concave polygons does NOT return
50 // the convex hull (the original bug).
51
52 Polygon L1;
53 L1.add_vertex(Point(0, 0));
54 L1.add_vertex(Point(6, 0));
55 L1.add_vertex(Point(6, 3));
56 L1.add_vertex(Point(3, 3));
57 L1.add_vertex(Point(3, 6));
58 L1.add_vertex(Point(0, 6));
59 L1.close();
60
61 Polygon sq;
62 sq.add_vertex(Point(1, 1));
63 sq.add_vertex(Point(5, 1));
64 sq.add_vertex(Point(5, 5));
65 sq.add_vertex(Point(1, 5));
66 sq.close();
67
69 auto result = bop.polygon_union(L1, sq);
70
71 EXPECT_GE(result.size(), 1u);
72
73 // The convex hull of L1 ∪ sq would be the bounding box (0,0)-(6,6) with
74 // 4 vertices. The actual union preserves the concavity of L1, so the
75 // result must have MORE than 4 vertices.
76 if (result.size() == 1)
77 EXPECT_GT(result(0).size(), 4u);
78}
79
80
82{
83 // Two overlapping unit squares — verify the union outline is the
84 // 8-vertex L-shaped boundary, not a convex hull.
85
87 sq1.add_vertex(Point(0, 0));
88 sq1.add_vertex(Point(2, 0));
89 sq1.add_vertex(Point(2, 2));
90 sq1.add_vertex(Point(0, 2));
91 sq1.close();
92
94 sq2.add_vertex(Point(1, 1));
95 sq2.add_vertex(Point(3, 1));
96 sq2.add_vertex(Point(3, 3));
97 sq2.add_vertex(Point(1, 3));
98 sq2.close();
99
101 auto result = bop.polygon_union(sq1, sq2);
102
103 EXPECT_EQ(result.size(), 1u);
104 // The union of two overlapping axis-aligned squares yields an
105 // 8-vertex staircase outline.
106 EXPECT_EQ(result(0).size(), 8u);
107}
108
109
111{
112 // Difference: sq1 minus sq2 where they partially overlap.
113 Polygon sq1;
114 sq1.add_vertex(Point(0, 0));
115 sq1.add_vertex(Point(2, 0));
116 sq1.add_vertex(Point(2, 2));
117 sq1.add_vertex(Point(0, 2));
118 sq1.close();
119
120 Polygon sq2;
121 sq2.add_vertex(Point(1, 1));
122 sq2.add_vertex(Point(3, 1));
123 sq2.add_vertex(Point(3, 3));
124 sq2.add_vertex(Point(1, 3));
125 sq2.close();
126
128 auto result = bop.difference(sq1, sq2);
129
130 // Should produce one polygon (the part of sq1 outside sq2).
131 EXPECT_EQ(result.size(), 1u);
132 // The difference is an L-shaped 6-vertex polygon.
133 if (result.size() == 1)
134 EXPECT_EQ(result(0).size(), 6u);
135}
136
137
139{
140 // Small square entirely inside a larger one.
141 Polygon big;
142 big.add_vertex(Point(0, 0));
143 big.add_vertex(Point(10, 0));
144 big.add_vertex(Point(10, 10));
145 big.add_vertex(Point(0, 10));
146 big.close();
147
149 small.add_vertex(Point(2, 2));
150 small.add_vertex(Point(4, 2));
151 small.add_vertex(Point(4, 4));
152 small.add_vertex(Point(2, 4));
153 small.close();
154
156
157 // Intersection = small polygon.
158 auto inter = bop.intersection(big, small);
159 EXPECT_EQ(inter.size(), 1u);
160 if (inter.size() == 1)
161 EXPECT_EQ(inter(0).size(), 4u);
162
163 // Union = big polygon.
164 auto uni = bop.polygon_union(big, small);
165 EXPECT_EQ(uni.size(), 1u);
166 if (uni.size() == 1)
167 EXPECT_EQ(uni(0).size(), 4u);
168
169 // Difference big - small = big (hole not representable as simple polygon).
170 auto diff = bop.difference(big, small);
171 EXPECT_EQ(diff.size(), 1u);
172}
173
174
175// ============================================================================
176// 3D Primitives Tests
177// ============================================================================
178
180{
181 Point3D a(1, 2, 3);
182 Point3D b(4, 5, 6);
183
184 auto sum = a + b;
185 EXPECT_EQ(sum.get_x(), Geom_Number(5));
186 EXPECT_EQ(sum.get_y(), Geom_Number(7));
187 EXPECT_EQ(sum.get_z(), Geom_Number(9));
188
189 auto diff = b - a;
190 EXPECT_EQ(diff.get_x(), Geom_Number(3));
191 EXPECT_EQ(diff.get_y(), Geom_Number(3));
192 EXPECT_EQ(diff.get_z(), Geom_Number(3));
193
194 auto scaled = a * Geom_Number(2);
195 EXPECT_EQ(scaled.get_x(), Geom_Number(2));
196 EXPECT_EQ(scaled.get_y(), Geom_Number(4));
197 EXPECT_EQ(scaled.get_z(), Geom_Number(6));
198}
199
200
202{
203 Point3D i(1, 0, 0);
204 Point3D j(0, 1, 0);
205 Point3D k(0, 0, 1);
206
207 // i · j = 0
208 EXPECT_EQ(i.dot(j), Geom_Number(0));
209 // i · i = 1
210 EXPECT_EQ(i.dot(i), Geom_Number(1));
211
212 // i × j = k
213 auto ixj = i.cross(j);
214 EXPECT_EQ(ixj, k);
215
216 // j × k = i
217 auto jxk = j.cross(k);
218 EXPECT_EQ(jxk, i);
219
220 // k × i = j
221 auto kxi = k.cross(i);
222 EXPECT_EQ(kxi, j);
223}
224
225
227{
228 Point3D a(0, 0, 0);
229 Point3D b(3, 4, 0);
230
233}
234
235
237{
238 Point3D p(3, 4, 5);
239 Point p2d = p.to_2d();
240 EXPECT_EQ(p2d, Point(3, 4));
241
243 EXPECT_EQ(lifted, Point3D(1, 2, 0));
244
246 EXPECT_EQ(lifted_z, Point3D(1, 2, 7));
247}
248
249
251{
252 Point3D a(0, 0, 0), b(3, 4, 0);
253 Segment3D s(a, b);
254
255 EXPECT_EQ(s.get_src(), a);
256 EXPECT_EQ(s.get_tgt(), b);
258
259 auto mid = s.midpoint();
261
262 EXPECT_EQ(s.at(Geom_Number(0)), a);
263 EXPECT_EQ(s.at(Geom_Number(1)), b);
264}
265
267{
268 Segment3D s(Point3D(0, 0, 0), Point3D(10, 0, 0));
269 EXPECT_TRUE(s.contains(Point3D(4, 0, 0)));
270 EXPECT_FALSE(s.contains(Point3D(11, 0, 0)));
271 EXPECT_FALSE(s.contains(Point3D(4, 1, 0)));
272
273 EXPECT_EQ(s.length(), Geom_Number(10));
274 EXPECT_EQ(s.distance_to(Point3D(4, 3, 0)), Geom_Number(3));
275}
276
277
279{
280 Triangle3D t(Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0, 1, 0));
281
282 // Normal should be (0, 0, 1) (z-axis).
283 auto n = t.normal();
284 EXPECT_EQ(n, Point3D(0, 0, 1));
285
287}
288
290{
291 // Right triangle with area = 1/2 -> 2 * area^2 = 1/2.
292 Triangle3D t(Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0, 1, 0));
294}
295
296
298{
299 // Collinear points → degenerate triangle.
300 Triangle3D t(Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(2, 0, 0));
302}
303
304
306{
307 Triangle3D t(Point3D(0, 0, 0), Point3D(3, 0, 0), Point3D(0, 3, 0));
308 auto c = t.centroid();
309 EXPECT_EQ(c, Point3D(1, 1, 0));
310}
311
312
314{
315 Triangle3D t(Point3D(0, 0, 0), Point3D(4, 0, 0), Point3D(0, 4, 0));
316
317 // Centroid should have barycentric coords (1/3, 1/3, 1/3).
318 auto bc = t.barycentric(Point3D(Geom_Number(4, 3), Geom_Number(4, 3), 0));
319 EXPECT_EQ(bc.u, Geom_Number(1, 3));
320 EXPECT_EQ(bc.v, Geom_Number(1, 3));
321 EXPECT_EQ(bc.w, Geom_Number(1, 3));
322
323 // Vertex a should have (1, 0, 0).
324 auto bca = t.barycentric(Point3D(0, 0, 0));
328}
329
330
332{
333 Triangle3D t(Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(2, 0, 0));
334 EXPECT_THROW(t.barycentric(Point3D(0, 0, 0)), std::domain_error);
335}
336
337
339{
340 // Regular tetrahedron with one vertex at origin.
341 Tetrahedron tet(Point3D(0, 0, 0),
342 Point3D(6, 0, 0),
343 Point3D(0, 6, 0),
344 Point3D(0, 0, 6));
345
346 // Volume = |det| / 6 = 6*6*6 / 6 = 36.
347 EXPECT_EQ(tet.volume(), Geom_Number(36));
348
349 EXPECT_FALSE(tet.is_degenerate());
350}
351
352
354{
355 // Four coplanar points.
356 Tetrahedron tet(Point3D(0, 0, 0),
357 Point3D(1, 0, 0),
358 Point3D(0, 1, 0),
359 Point3D(1, 1, 0));
360
361 EXPECT_TRUE(tet.is_degenerate());
362 EXPECT_EQ(tet.volume(), Geom_Number(0));
363}
364
365
367{
368 Tetrahedron tet(Point3D(0, 0, 0),
369 Point3D(4, 0, 0),
370 Point3D(0, 4, 0),
371 Point3D(0, 0, 4));
372
373 // Centroid should be inside.
374 EXPECT_TRUE(tet.contains(Point3D(1, 1, 1)));
375
376 // Origin vertex should be inside (on boundary).
377 EXPECT_TRUE(tet.contains(Point3D(0, 0, 0)));
378
379 // A point far outside.
380 EXPECT_FALSE(tet.contains(Point3D(10, 10, 10)));
381
382 // A point outside but close.
383 EXPECT_FALSE(tet.contains(Point3D(2, 2, 2)));
384}
385
387{
388 Tetrahedron tet(Point3D(0, 0, 0),
389 Point3D(0, 4, 0),
390 Point3D(4, 0, 0),
391 Point3D(0, 0, 4));
392
393 EXPECT_TRUE(tet.contains(Point3D(1, 1, 1)));
394 EXPECT_FALSE(tet.contains(Point3D(5, 1, 1)));
395}
396
397
399{
400 Tetrahedron tet(Point3D(0, 0, 0),
401 Point3D(4, 0, 0),
402 Point3D(0, 4, 0),
403 Point3D(0, 0, 4));
404
405 auto c = tet.centroid();
406 EXPECT_EQ(c, Point3D(1, 1, 1));
407}
408
409
411{
412 Tetrahedron tet(Point3D(0, 0, 0),
413 Point3D(1, 0, 0),
414 Point3D(0, 1, 0),
415 Point3D(0, 0, 1));
416
417 auto f = tet.faces();
418 // Should have 4 faces.
419 for (int i = 0; i < 4; ++i)
420 EXPECT_FALSE(f.f[i].is_degenerate());
421}
422
423
425{
426 Point3D a(1, 0, 0);
427 Point3D b(0, 1, 0);
428 Point3D c(0, 0, 1);
429
430 // a · (b × c) = 1 · (1) = 1
432
433 // Cyclic: b · (c × a) = 1
435
436 // Anti-cyclic: a · (c × b) = -1
438}
439
440
441// ============================================================================
442// operator<< Tests
443// ============================================================================
444
446{
447 std::ostringstream os;
448 os << Point(3, 4);
449 EXPECT_NE(os.str().find("Point("), std::string::npos);
450 EXPECT_NE(os.str().find("3"), std::string::npos);
451 EXPECT_NE(os.str().find("4"), std::string::npos);
452}
453
454
456{
457 std::ostringstream os;
458 os << Segment(Point(1, 2), Point(3, 4));
459 EXPECT_NE(os.str().find("Segment("), std::string::npos);
460}
461
462
464{
465 std::ostringstream os;
466 os << Triangle(Point(0, 0), Point(1, 0), Point(0, 1));
467 EXPECT_NE(os.str().find("Triangle("), std::string::npos);
468}
469
470
472{
473 std::ostringstream os;
474 os << Rectangle(0, 0, 5, 5);
475 EXPECT_NE(os.str().find("Rectangle("), std::string::npos);
476}
477
478
480{
481 std::ostringstream os;
482 os << Ellipse(Point(0, 0), 3, 2);
483 EXPECT_NE(os.str().find("Ellipse("), std::string::npos);
484}
485
486
488{
489 std::ostringstream os;
490 os << RotatedEllipse(Point(0, 0), 3, 2);
491 EXPECT_NE(os.str().find("RotatedEllipse("), std::string::npos);
492}
493
494
496{
497 Polygon sq;
498 sq.add_vertex(Point(0, 0));
499 sq.add_vertex(Point(1, 0));
500 sq.add_vertex(Point(1, 1));
501 sq.add_vertex(Point(0, 1));
502 sq.close();
503
504 std::ostringstream os;
505 os << sq;
506 EXPECT_NE(os.str().find("Polygon("), std::string::npos);
507 EXPECT_NE(os.str().find("n=4"), std::string::npos);
508 EXPECT_NE(os.str().find("closed"), std::string::npos);
509}
510
511
513{
514 {
515 std::ostringstream os;
516 os << Point3D(1, 2, 3);
517 EXPECT_NE(os.str().find("Point3D("), std::string::npos);
518 }
519 {
520 std::ostringstream os;
521 os << Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1));
522 EXPECT_NE(os.str().find("Segment3D("), std::string::npos);
523 }
524 {
525 std::ostringstream os;
526 os << Triangle3D(Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0, 1, 0));
527 EXPECT_NE(os.str().find("Triangle3D("), std::string::npos);
528 }
529 {
530 std::ostringstream os;
531 os << Tetrahedron(Point3D(0, 0, 0), Point3D(1, 0, 0),
532 Point3D(0, 1, 0), Point3D(0, 0, 1));
533 EXPECT_NE(os.str().find("Tetrahedron("), std::string::npos);
534 }
535}
536
537
538// ============================================================================
539// Serialization (WKT, GeoJSON) Tests
540// ============================================================================
541
543{
544 auto wkt = GeomSerializer::to_wkt(Point(3, 4));
545 EXPECT_NE(wkt.find("POINT ("), std::string::npos);
546 EXPECT_NE(wkt.find("3"), std::string::npos);
547 EXPECT_NE(wkt.find("4"), std::string::npos);
548}
549
550
552{
553 auto wkt = GeomSerializer::to_wkt(Segment(Point(1, 2), Point(3, 4)));
554 EXPECT_NE(wkt.find("LINESTRING ("), std::string::npos);
555}
556
557
559{
560 auto wkt = GeomSerializer::to_wkt(Triangle(Point(0, 0), Point(1, 0), Point(0, 1)));
561 EXPECT_NE(wkt.find("POLYGON (("), std::string::npos);
562 // WKT polygon must close: first point repeated at end.
563 // Count occurrences of "0 0" — should appear twice (start and end).
564 size_t pos = 0, count = 0;
565 while ((pos = wkt.find("0 0", pos)) != std::string::npos)
566 { ++count; ++pos; }
567 EXPECT_GE(count, 2u);
568}
569
570
572{
573 auto wkt = GeomSerializer::to_wkt(Rectangle(0, 0, 5, 5));
574 EXPECT_NE(wkt.find("POLYGON (("), std::string::npos);
575}
576
577
579{
580 Polygon sq;
581 sq.add_vertex(Point(0, 0));
582 sq.add_vertex(Point(1, 0));
583 sq.add_vertex(Point(1, 1));
584 sq.add_vertex(Point(0, 1));
585 sq.close();
586
587 auto wkt = GeomSerializer::to_wkt(sq);
588 EXPECT_NE(wkt.find("POLYGON (("), std::string::npos);
589}
590
591
593{
594 auto wkt = GeomSerializer::to_wkt(Point3D(1, 2, 3));
595 EXPECT_NE(wkt.find("POINT Z ("), std::string::npos);
596}
597
598
600{
601 auto json = GeomSerializer::to_geojson(Point(3, 4));
602 EXPECT_NE(json.find("\"type\":\"Point\""), std::string::npos);
603 EXPECT_NE(json.find("\"coordinates\":["), std::string::npos);
604}
605
606
608{
609 auto json = GeomSerializer::to_geojson(Segment(Point(1, 2), Point(3, 4)));
610 EXPECT_NE(json.find("\"type\":\"LineString\""), std::string::npos);
611}
612
613
615{
616 auto json = GeomSerializer::to_geojson(
617 Triangle(Point(0, 0), Point(1, 0), Point(0, 1)));
618 EXPECT_NE(json.find("\"type\":\"Polygon\""), std::string::npos);
619}
620
621
623{
624 Polygon sq;
625 sq.add_vertex(Point(0, 0));
626 sq.add_vertex(Point(1, 0));
627 sq.add_vertex(Point(1, 1));
628 sq.add_vertex(Point(0, 1));
629 sq.close();
630
631 auto json = GeomSerializer::to_geojson(sq);
632 EXPECT_NE(json.find("\"type\":\"Polygon\""), std::string::npos);
633 EXPECT_NE(json.find("\"coordinates\":[["), std::string::npos);
634}
635
636
638{
639 auto json = GeomSerializer::to_geojson(Point3D(1, 2, 3));
640 EXPECT_NE(json.find("\"type\":\"Point\""), std::string::npos);
641}
642
643
644// ============================================================================
645// AABB Tree Tests
646// ============================================================================
647
649{
650 AABBTree tree;
652 tree.build(entries);
653 EXPECT_TRUE(tree.is_empty());
654 EXPECT_EQ(tree.size(), 0u);
655}
656
657
659{
660 AABBTree tree;
662 entries.append({Rectangle(0, 0, 10, 10), 42});
663 tree.build(entries);
664
665 EXPECT_EQ(tree.size(), 1u);
666 EXPECT_FALSE(tree.is_empty());
667
668 // Point inside.
669 auto r = tree.query_point(Point(5, 5));
670 EXPECT_EQ(r.size(), 1u);
671 EXPECT_EQ(r(0), 42u);
672
673 // Point outside.
674 r = tree.query_point(Point(20, 20));
675 EXPECT_EQ(r.size(), 0u);
676}
677
678
680{
681 AABBTree tree;
683 entries.append({Rectangle(0, 0, 5, 5), 0});
684 entries.append({Rectangle(3, 3, 8, 8), 1});
685 entries.append({Rectangle(10, 10, 15, 15), 2});
686 entries.append({Rectangle(12, 0, 17, 5), 3});
687 tree.build(entries);
688
689 EXPECT_EQ(tree.size(), 4u);
690
691 // Query a point in the overlap of boxes 0 and 1.
692 auto r = tree.query_point(Point(4, 4));
693 EXPECT_EQ(r.size(), 2u);
694
695 // Query a point only in box 2.
696 r = tree.query_point(Point(12, 12));
697 EXPECT_EQ(r.size(), 1u);
698 EXPECT_EQ(r(0), 2u);
699
700 // Query a point outside all boxes.
701 r = tree.query_point(Point(50, 50));
702 EXPECT_EQ(r.size(), 0u);
703}
704
705
707{
708 AABBTree tree;
710 entries.append({Rectangle(0, 0, 5, 5), 0});
711 entries.append({Rectangle(3, 3, 8, 8), 1});
712 entries.append({Rectangle(10, 10, 15, 15), 2});
713 tree.build(entries);
714
715 // Query box overlapping entries 0 and 1.
716 auto r = tree.query(Rectangle(2, 2, 6, 6));
717 EXPECT_EQ(r.size(), 2u);
718
719 // Query box overlapping all entries.
720 r = tree.query(Rectangle(0, 0, 20, 20));
721 EXPECT_EQ(r.size(), 3u);
722
723 // Query box overlapping nothing.
724 r = tree.query(Rectangle(50, 50, 60, 60));
725 EXPECT_EQ(r.size(), 0u);
726}
727
728
730{
731 AABBTree tree;
733 entries.append({Rectangle(0, 0, 5, 5), 0});
734 entries.append({Rectangle(10, 10, 15, 15), 1});
735 tree.build(entries);
736
737 auto root = tree.root_bbox();
738 EXPECT_EQ(root.get_xmin(), Geom_Number(0));
739 EXPECT_EQ(root.get_ymin(), Geom_Number(0));
740 EXPECT_EQ(root.get_xmax(), Geom_Number(15));
741 EXPECT_EQ(root.get_ymax(), Geom_Number(15));
742}
743
745{
746 AABBTree tree;
748 entries.append({Rectangle(0, 0, 5, 5), 10});
749 entries.append({Rectangle(3, 3, 8, 8), 11});
750 entries.append({Rectangle(10, 10, 15, 15), 12});
751 tree.build(entries);
752
753 const auto snap = tree.debug_snapshot();
754 EXPECT_GT(snap.nodes.size(), 0u);
755 EXPECT_NE(snap.root, ~static_cast<size_t>(0));
756
757 size_t leaf_count = 0;
758 for (size_t i = 0; i < snap.nodes.size(); ++i)
759 if (snap.nodes(i).is_leaf)
760 ++leaf_count;
761 EXPECT_EQ(leaf_count, tree.size());
762}
763
765{
766 constexpr size_t num_entries = 128;
767 AABBTree tree;
769 entries.reserve(num_entries);
770 for (size_t i = 0; i < num_entries; ++i)
771 {
772 const long x = static_cast<long>(3 * i);
773 const long y = static_cast<long>((41 * i) % 97);
774 entries.append({Rectangle(x, y, x + 2, y + 2), i});
775 }
776 tree.build(entries);
777
778 const auto snap = tree.debug_snapshot();
779 EXPECT_EQ(snap.nodes.size(), 2 * num_entries - 1);
780 EXPECT_LT(snap.root, snap.nodes.size());
781
782 size_t leaf_count = 0;
783 for (size_t i = 0; i < snap.nodes.size(); ++i)
784 if (snap.nodes(i).is_leaf)
785 ++leaf_count;
786 else
787 {
788 EXPECT_LT(snap.nodes(i).left, snap.nodes.size());
789 EXPECT_LT(snap.nodes(i).right, snap.nodes.size());
790 }
791
793}
794
795
796// ============================================================================
797// GeomNumberType Concept Test (compile-time)
798// ============================================================================
799
800#if __cplusplus >= 202002L
801
803{
804 // These are compile-time checks; if we get here, the static_asserts passed.
808}
809
810#endif
811
812
813// ============================================================================
814// std::format Tests
815// ============================================================================
816
817#if __cplusplus >= 202002L && __has_include(<format>)
818
819# include <format>
820
821# if defined(__cpp_lib_format)
822
823
825{
826 auto s = std::format("{}", Point(3, 4));
827 EXPECT_NE(s.find("Point("), std::string::npos);
828 EXPECT_NE(s.find("3"), std::string::npos);
829 EXPECT_NE(s.find("4"), std::string::npos);
830}
831
832
834{
835 auto s = std::format("{}", Segment(Point(1, 2), Point(3, 4)));
836 EXPECT_NE(s.find("Segment("), std::string::npos);
837}
838
839
841{
842 auto s = std::format("{}", Triangle(Point(0, 0), Point(1, 0), Point(0, 1)));
843 EXPECT_NE(s.find("Triangle("), std::string::npos);
844}
845
846
848{
849 auto s = std::format("{}", Rectangle(0, 0, 5, 5));
850 EXPECT_NE(s.find("Rectangle("), std::string::npos);
851}
852
853
855{
856 auto s = std::format("{}", Point3D(1, 2, 3));
857 EXPECT_NE(s.find("Point3D("), std::string::npos);
858}
859
861{
862 auto s = std::format("{}", Polar_Point(Point(3, 4)));
863 EXPECT_NE(s.find("PolarPoint("), std::string::npos);
864}
865
867{
868 auto s = std::format("{}", Ellipse(Point(0, 0), 3, 2));
869 EXPECT_NE(s.find("Ellipse("), std::string::npos);
870}
871
873{
874 auto s = std::format("{}", RotatedEllipse(Point(0, 0), 3, 2));
875 EXPECT_NE(s.find("RotatedEllipse("), std::string::npos);
876}
877
879{
880 auto s = std::format("{}", Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1)));
881 EXPECT_NE(s.find("Segment3D("), std::string::npos);
882}
883
885{
886 auto s = std::format("{}", Triangle3D(Point3D(0, 0, 0),
887 Point3D(1, 0, 0),
888 Point3D(0, 1, 0)));
889 EXPECT_NE(s.find("Triangle3D("), std::string::npos);
890}
891
893{
894 auto s = std::format("{}", Tetrahedron(Point3D(0, 0, 0),
895 Point3D(1, 0, 0),
896 Point3D(0, 1, 0),
897 Point3D(0, 0, 1)));
898 EXPECT_NE(s.find("Tetrahedron("), std::string::npos);
899}
900
901
902# else
903
905{
906 GTEST_SKIP() << "std::format header exists but __cpp_lib_format is not enabled.";
907}
908
909# endif
910
911#endif // C++20 format
912
913
914// ============================================================================
915// Polygon Aleph Patterns Tests (Section 6.3)
916// ============================================================================
917
919{
920 Polygon sq;
921 sq.add_vertex(Point(0, 0));
922 sq.add_vertex(Point(4, 0));
923 sq.add_vertex(Point(4, 4));
924 sq.add_vertex(Point(0, 4));
925 sq.close();
926
927 // Range-based for via StlAlephIterator begin()/end().
928 size_t count = 0;
929 for (const auto & pt : sq)
930 {
931 (void)pt;
932 ++count;
933 }
934 EXPECT_EQ(count, 4u);
935}
936
937
939{
940 Polygon sq;
941 sq.add_vertex(Point(0, 0));
942 sq.add_vertex(Point(4, 0));
943 sq.add_vertex(Point(2, 3));
944 sq.close();
945
946 // Use the new Polygon::Iterator directly.
947 Polygon::Iterator it(sq);
948 EXPECT_TRUE(it.has_curr());
949 EXPECT_EQ(it.get_curr(), Point(0, 0));
950 it.next();
951 EXPECT_EQ(it.get_curr(), Point(4, 0));
952 it.next();
953 EXPECT_EQ(it.get_curr(), Point(2, 3));
954 it.next();
956}
957
958
960{
961 Polygon sq;
962 sq.add_vertex(Point(0, 0));
963 sq.add_vertex(Point(1, 0));
964 sq.add_vertex(Point(1, 1));
965 sq.add_vertex(Point(0, 1));
966 sq.close();
967
968 // FunctionalMethods::for_each
969 Geom_Number sum_x = 0;
970 sq.for_each([&sum_x](const Point & p) { sum_x += p.get_x(); });
971 EXPECT_EQ(sum_x, Geom_Number(2)); // 0 + 1 + 1 + 0
972}
973
974
976{
977 Polygon sq;
978 sq.add_vertex(Point(0, 0));
979 sq.add_vertex(Point(1, 0));
980 sq.add_vertex(Point(1, 1));
981 sq.add_vertex(Point(0, 1));
982 sq.close();
983
984 // GenericTraverse::traverse — stop early.
985 size_t visited = 0;
986 bool completed = sq.traverse([&visited](const Point &) {
987 ++visited;
988 return visited < 2; // stop after 2
989 });
991 EXPECT_EQ(visited, 2u);
992}
993
994
996{
997 Polygon sq;
998 sq.add_vertex(Point(0, 0));
999 sq.add_vertex(Point(1, 0));
1000 sq.add_vertex(Point(1, 1));
1001 sq.add_vertex(Point(0, 1));
1002 sq.close();
1003
1004 EXPECT_TRUE(sq.exists([](const Point & p) {
1005 return p.get_x() == 1 && p.get_y() == 1;
1006 }));
1007
1008 EXPECT_FALSE(sq.exists([](const Point & p) {
1009 return p.get_x() == 99;
1010 }));
1011}
1012
1013
1015{
1016 Polygon sq;
1017 sq.add_vertex(Point(0, 0));
1018 sq.add_vertex(Point(1, 0));
1019 sq.add_vertex(Point(1, 1));
1020 sq.add_vertex(Point(0, 1));
1021 sq.close();
1022
1023 EXPECT_TRUE(sq.all([](const Point & p) {
1024 return p.get_x() >= 0 && p.get_y() >= 0;
1025 }));
1026
1027 EXPECT_FALSE(sq.all([](const Point & p) {
1028 return p.get_x() > 0;
1029 }));
1030}
1031
1032
1034{
1035 Polygon sq;
1036 sq.add_vertex(Point(0, 0));
1037 sq.add_vertex(Point(4, 0));
1038 sq.add_vertex(Point(2, 3));
1039 sq.close();
1040
1041 auto xs = sq.maps<Geom_Number>([](const Point & p) { return p.get_x(); });
1042 EXPECT_EQ(xs.size(), 3u);
1043}
1044
1045
1047{
1048 Polygon sq;
1049 sq.add_vertex(Point(0, 0));
1050 sq.add_vertex(Point(1, 0));
1051 sq.add_vertex(Point(1, 1));
1052 sq.add_vertex(Point(0, 1));
1053 sq.close();
1054
1055 auto filtered = sq.filter([](const Point & p) { return p.get_x() > 0; });
1056 EXPECT_EQ(filtered.size(), 2u);
1057}
1058
1059
1061{
1062 // Special_Ctors: construct from initializer_list<Point>.
1063 Polygon poly = {Point(0, 0), Point(2, 0), Point(2, 2), Point(0, 2)};
1064
1065 EXPECT_EQ(poly.size(), 4u);
1066 EXPECT_FALSE(poly.is_closed()); // Special_Ctors doesn't close
1067}
1068
1069
1071{
1072 Polygon sq;
1073 sq.add_vertex(Point(0, 0));
1074 sq.add_vertex(Point(4, 0));
1075 sq.add_vertex(Point(2, 3));
1076 sq.close();
1077
1078 auto it = sq.get_it();
1079 EXPECT_TRUE(it.has_curr());
1080 EXPECT_EQ(it.get_curr(), Point(0, 0));
1081
1082 auto it2 = sq.get_it(2);
1083 EXPECT_EQ(it2.get_curr(), Point(2, 3));
1084}
1085
1086
1087// ============================================================================
1088// Section 7.1: Missing Correctness Tests
1089// ============================================================================
1090
1092{
1093 // All convex hull algorithms should produce the same result.
1095 pts.append(Point(0, 0));
1096 pts.append(Point(10, 0));
1097 pts.append(Point(10, 10));
1098 pts.append(Point(0, 10));
1099 pts.append(Point(5, 5)); // interior
1100 pts.append(Point(3, 2)); // interior
1101 pts.append(Point(7, 8)); // interior
1102 pts.append(Point(1, 9)); // interior
1103
1106 QuickHull qh;
1107
1108 auto hull_gw = gift(pts);
1109 auto hull_gm = graham(pts);
1110 auto hull_qh = qh(pts);
1111
1112 // All should have same number of hull vertices (the 4 corners).
1113 EXPECT_EQ(hull_gw.size(), 4u);
1114 EXPECT_EQ(hull_gm.size(), 4u);
1115 EXPECT_EQ(hull_qh.size(), 4u);
1116}
1117
1118
1120{
1121 // L-shaped polygon (non-convex).
1122 Polygon L;
1123 L.add_vertex(Point(0, 0));
1124 L.add_vertex(Point(6, 0));
1125 L.add_vertex(Point(6, 3));
1126 L.add_vertex(Point(3, 3));
1127 L.add_vertex(Point(3, 6));
1128 L.add_vertex(Point(0, 6));
1129 L.close();
1130
1132 auto tris = cet(L);
1133 // An n-vertex polygon yields n-2 triangles.
1134 EXPECT_EQ(tris.size(), 4u); // 6 vertices -> 4 triangles
1135}
1136
1137
1139{
1140 // U-shaped polygon.
1141 Polygon U;
1142 U.add_vertex(Point(0, 0));
1143 U.add_vertex(Point(6, 0));
1144 U.add_vertex(Point(6, 6));
1145 U.add_vertex(Point(5, 6));
1146 U.add_vertex(Point(5, 1));
1147 U.add_vertex(Point(1, 1));
1148 U.add_vertex(Point(1, 6));
1149 U.add_vertex(Point(0, 6));
1150 U.close();
1151
1153 auto tris = cet(U);
1154 EXPECT_EQ(tris.size(), 6u); // 8 vertices -> 6 triangles
1155}
1156
1157
1159{
1160 // Create a regular-ish polygon with many vertices (circle approximation).
1161 Polygon circle;
1162 const int N = 32;
1163 for (int i = 0; i < N; ++i)
1164 {
1166 Geom_Number y(Geom_Number(1000) * Geom_Number((i * 7 + 3) % N) / Geom_Number(N));
1167 // Use a convex polygon: just use a large square with many vertices on edges
1168 if (i < N/4)
1169 circle.add_vertex(Point(i * 4, 0));
1170 else if (i < N/2)
1171 circle.add_vertex(Point((N/4) * 4, (i - N/4) * 4));
1172 else if (i < 3*N/4)
1173 circle.add_vertex(Point((3*N/4 - i) * 4, (N/4) * 4));
1174 else
1175 circle.add_vertex(Point(0, (N - i) * 4));
1176 }
1177 circle.close();
1178
1179 // Center should be inside.
1180 EXPECT_TRUE(circle.contains(Point(16, 16)));
1181 // Far away point should be outside.
1182 EXPECT_FALSE(circle.contains(Point(1000, 1000)));
1183}
1184
1185
1186// ============================================================================
1187// Section 7.2: Missing Robustness Tests
1188// ============================================================================
1189
1191{
1192 // Three nearly collinear points — exact arithmetic should handle this.
1193 // p3 deviates by 1/10^15 from the line p1-p2.
1194 Point p1(0, 0);
1195 Point p2(Geom_Number(1000000), 0);
1196 // Tiny deviation from collinear.
1197 Point p3(Geom_Number(500000), Geom_Number(1, 1000000000));
1198
1199 // Should NOT be collinear (exact rational arithmetic).
1200 EXPECT_FALSE(p3.is_colinear_with(p1, p2));
1201
1202 // But if deviation is exactly 0, it IS collinear.
1203 Point p4(Geom_Number(500000), 0);
1204 EXPECT_TRUE(p4.is_colinear_with(p1, p2));
1205}
1206
1207
1209{
1210 // Very large coordinates.
1211 Geom_Number big("1000000000000000000"); // 10^18
1212 Point p1(big, big);
1213 Point p2(-big, -big);
1214 Point p3(big, -big);
1215
1216 // Distance should be exact.
1219
1220 // Triangle should work.
1221 Triangle t(p1, p2, p3);
1222 EXPECT_FALSE(t.contains(Point(0, 0))); // origin outside this triangle
1223
1224 // Very small coordinates.
1225 Geom_Number tiny(1, 1000000000);
1226 Point q1(0, 0);
1227 Point q2(tiny, 0);
1228 Point q3(0, tiny);
1229 Triangle t2(q1, q2, q3);
1230 // A point at (tiny/3, tiny/3) should be inside.
1231 EXPECT_TRUE(t2.contains(Point(tiny / 3, tiny / 3)));
1232}
1233
1234
1236{
1237 // Two segments that are nearly parallel but do intersect.
1238 Segment s1(Point(0, 0), Point(Geom_Number(1000000), Geom_Number(1)));
1239 Segment s2(Point(0, Geom_Number(1, 2)),
1240 Point(Geom_Number(1000000), 0));
1241
1242 // They should intersect (they cross at some point).
1243 EXPECT_TRUE(s1.intersects_with(s2));
1244}
1245
1246
1248{
1249 // 4 points on a circle of radius 5 centered at origin.
1250 // (3,4), (-3,4), (-3,-4), (3,-4) all on circle r=5.
1251 Point a(3, 4);
1252 Point b(-3, 4);
1253 Point c(-3, -4);
1254 Point d(3, -4);
1255
1256 // d should be ON the circumcircle of a,b,c (not inside).
1257 auto result = in_circle(a, b, c, d);
1258 EXPECT_EQ(result, InCircleResult::ON_CIRCLE);
1259}
1260
1261
1262// ============================================================================
1263// Section 7.4: Missing Primitive Tests
1264// ============================================================================
1265
1267{
1268 // Test the intersects_properly_with predicate with near-collinear segments.
1269 Segment s1(Point(0, 0), Point(10, 0));
1270 Segment s2(Point(5, -1), Point(5, 1));
1271
1272 EXPECT_TRUE(s1.intersects_properly_with(s2));
1273
1274 // Collinear overlapping segments should NOT intersect properly.
1275 Segment s3(Point(0, 0), Point(6, 0));
1276 Segment s4(Point(4, 0), Point(10, 0));
1277 EXPECT_FALSE(s3.intersects_properly_with(s4));
1278}
1279
1280
1282{
1283 // Vertical segment through the center of an ellipse.
1284 Ellipse e(Point(0, 0), 5, 3);
1285
1286 // Vertical segment x=0 from y=-10 to y=10.
1287 Segment vert(Point(0, -10), Point(0, 10));
1289}
1290
1291
1293{
1294 // Enlarge a diagonal segment in both directions.
1295 Segment s(Point(0, 0), Point(3, 4)); // length = 5
1296
1297 Segment s_copy = s;
1299 // Source should have moved away from target.
1300 EXPECT_TRUE(s_copy.length() > s.length());
1301
1302 Segment s_copy2 = s;
1304 EXPECT_TRUE(s_copy2.length() > s.length());
1305}
1306
1307
1309{
1310 // CCW triangle.
1311 Triangle ccw(Point(0, 0), Point(4, 0), Point(2, 3));
1312 EXPECT_TRUE(ccw.contains(Point(2, 1)));
1313
1314 // CW triangle (reversed vertex order).
1315 Triangle cw(Point(0, 0), Point(2, 3), Point(4, 0));
1316 EXPECT_TRUE(cw.contains(Point(2, 1)));
1317}
1318
1319
1321{
1322 // Two rectangles sharing exactly one corner.
1323 Rectangle r1(0, 0, 5, 5);
1324 Rectangle r2(5, 5, 10, 10);
1325
1326 // They touch at (5,5) — xmin of r2 == xmax of r1.
1327 // Depending on the definition, they may or may not intersect.
1328 // The point (5,5) is on the boundary of both.
1329 Point corner(5, 5);
1330 EXPECT_TRUE(corner.get_x() >= r1.get_xmin() &&
1331 corner.get_x() <= r1.get_xmax() &&
1332 corner.get_y() >= r1.get_ymin() &&
1333 corner.get_y() <= r1.get_ymax());
1334 EXPECT_TRUE(corner.get_x() >= r2.get_xmin() &&
1335 corner.get_x() <= r2.get_xmax() &&
1336 corner.get_y() >= r2.get_ymin() &&
1337 corner.get_y() <= r2.get_ymax());
1338}
1339
1340
1342{
1343 // contains() should return true for endpoints.
1344 Segment s(Point(1, 2), Point(5, 6));
1347
1348 // Midpoint should also be contained.
1350}
1351
1352
1354{
1355 // Verify the new contains() method on Polygon works.
1356 Polygon sq;
1357 sq.add_vertex(Point(0, 0));
1358 sq.add_vertex(Point(10, 0));
1359 sq.add_vertex(Point(10, 10));
1360 sq.add_vertex(Point(0, 10));
1361 sq.close();
1362
1363 EXPECT_TRUE(sq.contains(Point(5, 5)));
1364 EXPECT_FALSE(sq.contains(Point(20, 20)));
1365 // Boundary point.
1366 EXPECT_TRUE(sq.contains(Point(0, 5)));
1367}
1368
1369
1371{
1372 Triangle t(Point(0, 0), Point(10, 0), Point(0, 10));
1373 EXPECT_TRUE(t.contains(Point(1, 1)));
1374 EXPECT_FALSE(t.contains(Point(8, 8)));
1375}
1376
1377
1379{
1380 Ellipse e(Point(0, 0), 5, 3);
1381 EXPECT_TRUE(e.contains(Point(0, 0))); // center
1382 EXPECT_TRUE(e.contains(Point(4, 0))); // inside
1383 EXPECT_FALSE(e.contains(Point(10, 10))); // outside
1384}
1385
1386
1395
1396
1406
1407
1408// ---------- BooleanPolygonOperations: new critical tests ----------
1409
1411{
1412 // L-shaped polygon intersected with a rectangle.
1413 // L: (0,0)-(6,0)-(6,3)-(3,3)-(3,6)-(0,6), area = 27
1414 // Rect: (1,1)-(5,1)-(5,5)-(1,5), area = 16
1415 //
1416 // Geometric intersection:
1417 // The rect clips to the L interior. The rect's top-right corner (5,5)
1418 // is outside the L (the L only extends to x=3 above y=3). So the
1419 // intersection is the rect minus the rectangle (3,3)-(5,3)-(5,5)-(3,5),
1420 // giving area = 16 - 4 = 12.
1421 Polygon L;
1422 L.add_vertex(Point(0, 0));
1423 L.add_vertex(Point(6, 0));
1424 L.add_vertex(Point(6, 3));
1425 L.add_vertex(Point(3, 3));
1426 L.add_vertex(Point(3, 6));
1427 L.add_vertex(Point(0, 6));
1428 L.close();
1429
1430 Polygon rect;
1431 rect.add_vertex(Point(1, 1));
1432 rect.add_vertex(Point(5, 1));
1433 rect.add_vertex(Point(5, 5));
1434 rect.add_vertex(Point(1, 5));
1435 rect.close();
1436
1438 auto result = bop.intersection(L, rect);
1439
1440 EXPECT_GE(result.size(), 1u);
1441
1443 for (size_t i = 0; i < result.size(); ++i)
1444 total_area += polygon_area(result(i));
1445
1446 // Must be strictly positive and less than both inputs.
1449
1450 // Correct intersection area should be exactly 12.
1452 << "Intersection area = " << total_area.get_d()
1453 << ", expected 12 (concave polygon intersection bug?)";
1454}
1455
1456
1458{
1459 // Two overlapping L-shapes.
1460 // L1: (0,0)-(6,0)-(6,3)-(3,3)-(3,6)-(0,6), area = 27
1461 // L2: (2,2)-(8,2)-(8,5)-(5,5)-(5,8)-(2,8), area = 27
1462 // Union area = area(L1) + area(L2) - area(intersection).
1463 Polygon L1;
1464 L1.add_vertex(Point(0, 0));
1465 L1.add_vertex(Point(6, 0));
1466 L1.add_vertex(Point(6, 3));
1467 L1.add_vertex(Point(3, 3));
1468 L1.add_vertex(Point(3, 6));
1469 L1.add_vertex(Point(0, 6));
1470 L1.close();
1471
1472 Polygon L2;
1473 L2.add_vertex(Point(2, 2));
1474 L2.add_vertex(Point(8, 2));
1475 L2.add_vertex(Point(8, 5));
1476 L2.add_vertex(Point(5, 5));
1477 L2.add_vertex(Point(5, 8));
1478 L2.add_vertex(Point(2, 8));
1479 L2.close();
1480
1482 auto result = bop.polygon_union(L1, L2);
1483
1484 EXPECT_GE(result.size(), 1u);
1485
1487 for (size_t i = 0; i < result.size(); ++i)
1488 union_area += polygon_area(result(i));
1489
1490 // Union area must be >= max(area(L1), area(L2)) = 27 and <= area(L1)+area(L2) = 54.
1493
1494 // The result must NOT be the convex hull (which would have 4 vertices
1495 // and area 64 = 8*8). It must preserve concavity.
1496 if (result.size() == 1)
1497 EXPECT_GT(result(0).size(), 4u)
1498 << "Union collapsed to convex hull — concave shape lost";
1499}
1500
1501
1503{
1504 // Rectangle minus overlapping rectangle at corner.
1505 // big: (0,0)-(10,0)-(10,10)-(0,10), area = 100
1506 // corner: (5,5)-(15,5)-(15,15)-(5,15), area = 100
1507 // overlap area = 25, so difference area = 100 - 25 = 75.
1508 Polygon big;
1509 big.add_vertex(Point(0, 0));
1510 big.add_vertex(Point(10, 0));
1511 big.add_vertex(Point(10, 10));
1512 big.add_vertex(Point(0, 10));
1513 big.close();
1514
1516 corner.add_vertex(Point(5, 5));
1517 corner.add_vertex(Point(15, 5));
1518 corner.add_vertex(Point(15, 15));
1519 corner.add_vertex(Point(5, 15));
1520 corner.close();
1521
1523 auto result = bop.difference(big, corner);
1524
1525 EXPECT_GE(result.size(), 1u);
1526
1527 // Difference area = 100 - 25 = 75.
1529 for (size_t i = 0; i < result.size(); ++i)
1530 diff_area += polygon_area(result(i));
1531
1533}
1534
1535
1537{
1538 // Two non-overlapping polygons.
1539 Polygon sq1;
1540 sq1.add_vertex(Point(0, 0));
1541 sq1.add_vertex(Point(1, 0));
1542 sq1.add_vertex(Point(1, 1));
1543 sq1.add_vertex(Point(0, 1));
1544 sq1.close();
1545
1546 Polygon sq2;
1547 sq2.add_vertex(Point(10, 10));
1548 sq2.add_vertex(Point(11, 10));
1549 sq2.add_vertex(Point(11, 11));
1550 sq2.add_vertex(Point(10, 11));
1551 sq2.close();
1552
1554 auto result = bop.intersection(sq1, sq2);
1555
1556 // No overlap → empty result.
1557 EXPECT_EQ(result.size(), 0u);
1558}
1559
1560
1561// ---------- PowerDiagram: new critical tests ----------
1562
1564{
1565 // 4 sites with weights 0, 1, 4, 9.
1567 sites.append({Point(0, 0), Geom_Number(0)});
1568 sites.append({Point(10, 0), Geom_Number(1)});
1569 sites.append({Point(10, 10), Geom_Number(4)});
1570 sites.append({Point(0, 10), Geom_Number(9)});
1571
1573 auto result = pd(sites);
1574
1575 EXPECT_EQ(result.sites.size(), 4u);
1576 EXPECT_EQ(result.cells.size(), 4u);
1577
1578 // Each power vertex must satisfy the equi-power-distance property for
1579 // at least one triple of sites.
1580 for (size_t v = 0; v < result.vertices.size(); ++v)
1581 {
1582 const Point & pc = result.vertices(v);
1584 for (size_t s = 0; s < result.sites.size(); ++s)
1585 pds.append(pc.distance_squared_to(result.sites(s).position)
1586 - result.sites(s).weight);
1587
1588 bool found_triple = false;
1589 for (size_t a = 0; a < pds.size() and not found_triple; ++a)
1590 for (size_t b = a + 1; b < pds.size() and not found_triple; ++b)
1591 for (size_t c = b + 1; c < pds.size() and not found_triple; ++c)
1592 if (pds(a) == pds(b) and pds(b) == pds(c))
1593 found_triple = true;
1594
1596 << "Power vertex " << v << " is not equi-power-distant to any triple";
1597 }
1598}
1599
1600
1602{
1603 // All weights = 0 → result should match standard Voronoi.
1605 wsites.append({Point(0, 0), Geom_Number(0)});
1606 wsites.append({Point(6, 0), Geom_Number(0)});
1607 wsites.append({Point(3, 5), Geom_Number(0)});
1608
1610 auto pr = pd(wsites);
1611
1612 // Standard Voronoi via Delaunay.
1614 auto vr = voronoi({Point(0, 0), Point(6, 0), Point(3, 5)});
1615
1616 // Same number of sites and vertices.
1617 EXPECT_EQ(pr.sites.size(), vr.sites.size());
1618 EXPECT_EQ(pr.vertices.size(), vr.vertices.size());
1619
1620 // The power vertex should be the circumcenter (same as Voronoi vertex).
1621 if (pr.vertices.size() >= 1 and vr.vertices.size() >= 1)
1622 {
1623 const Point & pv = pr.vertices(0);
1624 const Point & vv = vr.vertices(0);
1625 // They should be the same point (or very close).
1626 Geom_Number d2 = pv.distance_squared_to(vv);
1627 EXPECT_LT(d2, Geom_Number(1, 100))
1628 << "Power vertex and Voronoi vertex differ";
1629 }
1630}
1631
1632
1633// ---------- ConvexPolygonOffset: new critical tests ----------
1634
1636{
1637 // Square with extra collinear point on bottom edge.
1638 // (0,0)-(5,0)-(10,0)-(10,10)-(0,10) — 5 vertices, 3 collinear on bottom.
1639 // Original area = 100. Inward offset by 1 should give (10-2)*(10-2) = 64.
1640 Polygon p;
1641 p.add_vertex(Point(0, 0));
1642 p.add_vertex(Point(5, 0));
1643 p.add_vertex(Point(10, 0));
1644 p.add_vertex(Point(10, 10));
1645 p.add_vertex(Point(0, 10));
1646 p.close();
1647
1648 // Should not crash (this tests the collinear vertex handling path).
1650
1651 EXPECT_TRUE(result.is_closed());
1652 EXPECT_GE(result.size(), 3u);
1653
1656
1657 // BUG: collinear consecutive vertices cause incorrect offset geometry.
1658 // The offset area should be 64 (8x8) and strictly less than 100.
1659 // Currently produces area > 100 due to the collinear vertex handling.
1661 << "Inward offset area (" << offset_area.get_d()
1662 << ") >= original area (" << orig_area.get_d()
1663 << ") — collinear vertex bug in ConvexPolygonOffset";
1664}
1665
1666
1668{
1669 // Square 10x10. Inward offset of 6 is larger than half the minimum
1670 // dimension (5), so the offset polygon should be empty or degenerate.
1671 Polygon sq;
1672 sq.add_vertex(Point(0, 0));
1673 sq.add_vertex(Point(10, 0));
1674 sq.add_vertex(Point(10, 10));
1675 sq.add_vertex(Point(0, 10));
1676 sq.close();
1677
1679
1680 // BUG: the half-plane intersection approach produces a non-degenerate
1681 // polygon even when the offset exceeds half the minimum dimension.
1682 // Correct behavior: result should be empty (0 vertices) or < 3 vertices.
1683 EXPECT_LT(result.size(), 3u)
1684 << "Inward offset of 6 on a 10x10 square should produce empty result, "
1685 << "but got " << result.size() << " vertices";
1686}
long double vr
Definition btreepic.C:150
Axis-aligned bounding box tree for spatial queries.
Rectangle root_bbox() const
Return the root bounding box (union of all entries).
size_t build(Array< size_t > &idx, const size_t lo, const size_t hi)
bool is_empty() const
Whether the tree is empty.
size_t size() const
Number of entries.
DebugSnapshot debug_snapshot() const
Return the full tree structure for visualization/debug.
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.
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Boolean operations on simple polygons (union, intersection, difference) using the Greiner-Hormann alg...
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.
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.
Doubly-linked list (defined in tpl_dynList.H).
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
An axis-aligned ellipse.
Definition point.H:2076
const Geom_Number & get_vradius() const
Gets the vertical radius.
Definition point.H:2153
bool intersects_with(const Segment &s) const
Checks if a segment intersects the ellipse.
Definition point.H:2298
const Geom_Number & get_hradius() const
Gets the horizontal radius.
Definition point.H:2148
bool contains(const Point &p) const
Checks if a point lies inside or on the boundary of this ellipse.
Definition point.H:2363
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)".
Gift wrapping (Jarvis march) convex hull algorithm.
Graham scan convex hull algorithm.
Represents a point in 3D space with exact rational coordinates.
Definition point.H:3054
static Point3D from_2d(const Point &p)
Lifts a 2D point to 3D, setting its z-coordinate to 0.
Definition point.H:3273
Geom_Number distance_squared_to(const Point3D &p) const
Squared Euclidean distance to another point.
Definition point.H:3211
Point3D cross(const Point3D &p) const
Cross product.
Definition point.H:3201
Geom_Number norm_squared() const
Squared Euclidean norm (magnitude).
Definition point.H:3223
Point to_2d() const
Projects this 3D point to a 2D point by dropping the z-coordinate.
Definition point.H:3263
Geom_Number dot(const Point3D &p) const
Dot product.
Definition point.H:3191
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
Geom_Number distance_squared_to(const Point &that) const
Calculates the squared Euclidean distance to another point.
Definition point.H:1490
bool is_colinear_with(const Point &p1, const Point &p2) const
Checks if this point is collinear with two other points.
Definition point.H:468
Polar representation of a 2D point.
Definition point.H:728
STL/Aleph-compatible iterator over polygon vertices as Points.
Definition polygon.H:318
const Point & get_curr() const
Definition polygon.H:341
bool has_curr() const noexcept
Definition polygon.H:327
A general (irregular) 2D polygon defined by a sequence of vertices.
Definition polygon.H:247
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
bool contains(const Point &p) const
Check if a point is inside the polygon (or on its boundary).
Definition polygon.H:929
Power diagram (weighted Voronoi diagram).
QuickHull convex hull algorithm.
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
An ellipse with arbitrary rotation.
Definition point.H:2473
bool contains(const Point &p) const
Checks if a point lies inside or on the ellipse.
Definition point.H:2646
const Geom_Number & get_sin() const
Gets the sine of the rotation angle.
Definition point.H:2612
bool on_boundary(const Point &p) const
Checks if a point lies exactly on the ellipse boundary.
Definition point.H:2666
const Geom_Number & get_cos() const
Gets the cosine of the rotation angle.
Definition point.H:2607
Represents a line segment in 3D space.
Definition point.H:3302
Point3D midpoint() const
Calculates the midpoint of the segment.
Definition point.H:3379
bool contains(const Point3D &p) const
Checks if a point lies on the segment.
Definition point.H:3411
const Point3D & get_src() const noexcept
Gets the source point of the segment.
Definition point.H:3317
Geom_Number distance_to(const Point3D &p) const
Calculates the shortest distance from a point to this segment.
Definition point.H:3433
const Point3D & get_tgt() const noexcept
Gets the target point of the segment.
Definition point.H:3322
Point3D at(const Geom_Number &t) const
Evaluates a point on the segment via linear interpolation.
Definition point.H:3369
Geom_Number length_squared() const
Calculates the squared length of the segment.
Definition point.H:3350
Geom_Number length() const
Calculates the length of the segment.
Definition point.H:3359
Represents a line segment between two points.
Definition point.H:837
Point mid_point() const
Returns the midpoint of this segment.
Definition point.H:1128
void enlarge_src(const Geom_Number &dist)
Extends the segment by a given distance from the source endpoint.
Definition point.H:1392
void enlarge_tgt(const Geom_Number &dist)
Extends the segment by a given distance from the target endpoint.
Definition point.H:1403
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
Geom_Number length() const
Returns the Euclidean length of the segment.
Definition point.H:1069
Represents a tetrahedron in 3D space defined by four points.
Definition point.H:3572
Represents a triangle in 3D space defined by three points.
Definition point.H:3461
BaryCoords barycentric(const Point3D &p) const
Computes the barycentric coordinates of a point p with respect to this triangle.
Definition point.H:3548
Point3D centroid() const
Computes the centroid (center of mass) of the triangle.
Definition point.H:3517
Point3D normal() const
Computes the normal vector of the triangle's plane.
Definition point.H:3499
Geom_Number double_area_squared() const
Calculates twice the squared area of the triangle.
Definition point.H:3508
bool is_degenerate() const
Checks if the triangle is degenerate (i.e., its vertices are collinear).
Definition point.H:3526
A non-degenerate triangle defined by three points.
Definition point.H:1512
bool contains(const Point &p) const
Checks if a point lies strictly inside this triangle.
Definition point.H:1744
Voronoi diagram derived as the dual of a Delaunay triangulation.
Aleph::DynList< T > filter(Operation &operation) const
Filter the elements of a container according to a matching criterion.
Definition ah-dry.H:1437
bool exists(Operation &op) const
Test for existence in the container of an element satisfying a criterion.
Definition ah-dry.H:1022
Aleph::DynList< __T > maps(Operation &op) const
Map the elements of the container.
Definition ah-dry.H:1090
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:796
bool all(Operation &operation) const
Check if all the elements of the container satisfy a condition.
Definition ah-dry.H:984
auto get_it() const
Return a properly initialized iterator positioned at the first item on the container.
Definition ah-dry.H:228
#define N
Definition fib.C:294
TEST_F(GeomAlgorithmsTest, BooleanIntersectionConcaveLShapes)
__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
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
static mpfr_t y
Definition mpfr_mul_d.c:3
Geom_Number scalar_triple_product(const Point3D &a, const Point3D &b, const Point3D &c)
Scalar triple product: a · (b × c).
Definition point.H:3291
bool completed() const noexcept
Return true if all underlying iterators are finished.
Definition ah-zip.H:136
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
InCircleResult in_circle(const Point &a, const Point &b, const Point &c, const Point &p)
Classify point p against circumcircle of triangle (a,b,c), exactly.
Definition point.H:2943
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
mpq_class Geom_Number
Numeric type used by the geometry module.
Definition point.H:113
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
static Geom_Number polygon_area(const Polygon &poly)
bool traverse(Operation &operation) noexcept(traverse_is_noexcept< Operation >())
Traverse the container via its iterator and performs a conditioned operation on each item.
Definition ah-dry.H:101
static int * k
gsl_rng * r