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
764
765// ============================================================================
766// GeomNumberType Concept Test (compile-time)
767// ============================================================================
768
769#if __cplusplus >= 202002L
770
772{
773 // These are compile-time checks; if we get here, the static_asserts passed.
777}
778
779#endif
780
781
782// ============================================================================
783// std::format Tests
784// ============================================================================
785
786#if __cplusplus >= 202002L && __has_include(<format>)
787
788# include <format>
789
790# if defined(__cpp_lib_format)
791
792
794{
795 auto s = std::format("{}", Point(3, 4));
796 EXPECT_NE(s.find("Point("), std::string::npos);
797 EXPECT_NE(s.find("3"), std::string::npos);
798 EXPECT_NE(s.find("4"), std::string::npos);
799}
800
801
803{
804 auto s = std::format("{}", Segment(Point(1, 2), Point(3, 4)));
805 EXPECT_NE(s.find("Segment("), std::string::npos);
806}
807
808
810{
811 auto s = std::format("{}", Triangle(Point(0, 0), Point(1, 0), Point(0, 1)));
812 EXPECT_NE(s.find("Triangle("), std::string::npos);
813}
814
815
817{
818 auto s = std::format("{}", Rectangle(0, 0, 5, 5));
819 EXPECT_NE(s.find("Rectangle("), std::string::npos);
820}
821
822
824{
825 auto s = std::format("{}", Point3D(1, 2, 3));
826 EXPECT_NE(s.find("Point3D("), std::string::npos);
827}
828
830{
831 auto s = std::format("{}", Polar_Point(Point(3, 4)));
832 EXPECT_NE(s.find("PolarPoint("), std::string::npos);
833}
834
836{
837 auto s = std::format("{}", Ellipse(Point(0, 0), 3, 2));
838 EXPECT_NE(s.find("Ellipse("), std::string::npos);
839}
840
842{
843 auto s = std::format("{}", RotatedEllipse(Point(0, 0), 3, 2));
844 EXPECT_NE(s.find("RotatedEllipse("), std::string::npos);
845}
846
848{
849 auto s = std::format("{}", Segment3D(Point3D(0, 0, 0), Point3D(1, 1, 1)));
850 EXPECT_NE(s.find("Segment3D("), std::string::npos);
851}
852
854{
855 auto s = std::format("{}", Triangle3D(Point3D(0, 0, 0),
856 Point3D(1, 0, 0),
857 Point3D(0, 1, 0)));
858 EXPECT_NE(s.find("Triangle3D("), std::string::npos);
859}
860
862{
863 auto s = std::format("{}", Tetrahedron(Point3D(0, 0, 0),
864 Point3D(1, 0, 0),
865 Point3D(0, 1, 0),
866 Point3D(0, 0, 1)));
867 EXPECT_NE(s.find("Tetrahedron("), std::string::npos);
868}
869
870
871# else
872
874{
875 GTEST_SKIP() << "std::format header exists but __cpp_lib_format is not enabled.";
876}
877
878# endif
879
880#endif // C++20 format
881
882
883// ============================================================================
884// Polygon Aleph Patterns Tests (Section 6.3)
885// ============================================================================
886
888{
889 Polygon sq;
890 sq.add_vertex(Point(0, 0));
891 sq.add_vertex(Point(4, 0));
892 sq.add_vertex(Point(4, 4));
893 sq.add_vertex(Point(0, 4));
894 sq.close();
895
896 // Range-based for via StlAlephIterator begin()/end().
897 size_t count = 0;
898 for (const auto & pt : sq)
899 {
900 (void)pt;
901 ++count;
902 }
903 EXPECT_EQ(count, 4u);
904}
905
906
908{
909 Polygon sq;
910 sq.add_vertex(Point(0, 0));
911 sq.add_vertex(Point(4, 0));
912 sq.add_vertex(Point(2, 3));
913 sq.close();
914
915 // Use the new Polygon::Iterator directly.
916 Polygon::Iterator it(sq);
917 EXPECT_TRUE(it.has_curr());
918 EXPECT_EQ(it.get_curr(), Point(0, 0));
919 it.next();
920 EXPECT_EQ(it.get_curr(), Point(4, 0));
921 it.next();
922 EXPECT_EQ(it.get_curr(), Point(2, 3));
923 it.next();
925}
926
927
929{
930 Polygon sq;
931 sq.add_vertex(Point(0, 0));
932 sq.add_vertex(Point(1, 0));
933 sq.add_vertex(Point(1, 1));
934 sq.add_vertex(Point(0, 1));
935 sq.close();
936
937 // FunctionalMethods::for_each
938 Geom_Number sum_x = 0;
939 sq.for_each([&sum_x](const Point & p) { sum_x += p.get_x(); });
940 EXPECT_EQ(sum_x, Geom_Number(2)); // 0 + 1 + 1 + 0
941}
942
943
945{
946 Polygon sq;
947 sq.add_vertex(Point(0, 0));
948 sq.add_vertex(Point(1, 0));
949 sq.add_vertex(Point(1, 1));
950 sq.add_vertex(Point(0, 1));
951 sq.close();
952
953 // GenericTraverse::traverse — stop early.
954 size_t visited = 0;
955 bool completed = sq.traverse([&visited](const Point &) {
956 ++visited;
957 return visited < 2; // stop after 2
958 });
960 EXPECT_EQ(visited, 2u);
961}
962
963
965{
966 Polygon sq;
967 sq.add_vertex(Point(0, 0));
968 sq.add_vertex(Point(1, 0));
969 sq.add_vertex(Point(1, 1));
970 sq.add_vertex(Point(0, 1));
971 sq.close();
972
973 EXPECT_TRUE(sq.exists([](const Point & p) {
974 return p.get_x() == 1 && p.get_y() == 1;
975 }));
976
977 EXPECT_FALSE(sq.exists([](const Point & p) {
978 return p.get_x() == 99;
979 }));
980}
981
982
984{
985 Polygon sq;
986 sq.add_vertex(Point(0, 0));
987 sq.add_vertex(Point(1, 0));
988 sq.add_vertex(Point(1, 1));
989 sq.add_vertex(Point(0, 1));
990 sq.close();
991
992 EXPECT_TRUE(sq.all([](const Point & p) {
993 return p.get_x() >= 0 && p.get_y() >= 0;
994 }));
995
996 EXPECT_FALSE(sq.all([](const Point & p) {
997 return p.get_x() > 0;
998 }));
999}
1000
1001
1003{
1004 Polygon sq;
1005 sq.add_vertex(Point(0, 0));
1006 sq.add_vertex(Point(4, 0));
1007 sq.add_vertex(Point(2, 3));
1008 sq.close();
1009
1010 auto xs = sq.maps<Geom_Number>([](const Point & p) { return p.get_x(); });
1011 EXPECT_EQ(xs.size(), 3u);
1012}
1013
1014
1016{
1017 Polygon sq;
1018 sq.add_vertex(Point(0, 0));
1019 sq.add_vertex(Point(1, 0));
1020 sq.add_vertex(Point(1, 1));
1021 sq.add_vertex(Point(0, 1));
1022 sq.close();
1023
1024 auto filtered = sq.filter([](const Point & p) { return p.get_x() > 0; });
1025 EXPECT_EQ(filtered.size(), 2u);
1026}
1027
1028
1030{
1031 // Special_Ctors: construct from initializer_list<Point>.
1032 Polygon poly = {Point(0, 0), Point(2, 0), Point(2, 2), Point(0, 2)};
1033
1034 EXPECT_EQ(poly.size(), 4u);
1035 EXPECT_FALSE(poly.is_closed()); // Special_Ctors doesn't close
1036}
1037
1038
1040{
1041 Polygon sq;
1042 sq.add_vertex(Point(0, 0));
1043 sq.add_vertex(Point(4, 0));
1044 sq.add_vertex(Point(2, 3));
1045 sq.close();
1046
1047 auto it = sq.get_it();
1048 EXPECT_TRUE(it.has_curr());
1049 EXPECT_EQ(it.get_curr(), Point(0, 0));
1050
1051 auto it2 = sq.get_it(2);
1052 EXPECT_EQ(it2.get_curr(), Point(2, 3));
1053}
1054
1055
1056// ============================================================================
1057// Section 7.1: Missing Correctness Tests
1058// ============================================================================
1059
1061{
1062 // All convex hull algorithms should produce the same result.
1064 pts.append(Point(0, 0));
1065 pts.append(Point(10, 0));
1066 pts.append(Point(10, 10));
1067 pts.append(Point(0, 10));
1068 pts.append(Point(5, 5)); // interior
1069 pts.append(Point(3, 2)); // interior
1070 pts.append(Point(7, 8)); // interior
1071 pts.append(Point(1, 9)); // interior
1072
1075 QuickHull qh;
1076
1077 auto hull_gw = gift(pts);
1078 auto hull_gm = graham(pts);
1079 auto hull_qh = qh(pts);
1080
1081 // All should have same number of hull vertices (the 4 corners).
1082 EXPECT_EQ(hull_gw.size(), 4u);
1083 EXPECT_EQ(hull_gm.size(), 4u);
1084 EXPECT_EQ(hull_qh.size(), 4u);
1085}
1086
1087
1089{
1090 // L-shaped polygon (non-convex).
1091 Polygon L;
1092 L.add_vertex(Point(0, 0));
1093 L.add_vertex(Point(6, 0));
1094 L.add_vertex(Point(6, 3));
1095 L.add_vertex(Point(3, 3));
1096 L.add_vertex(Point(3, 6));
1097 L.add_vertex(Point(0, 6));
1098 L.close();
1099
1101 auto tris = cet(L);
1102 // An n-vertex polygon yields n-2 triangles.
1103 EXPECT_EQ(tris.size(), 4u); // 6 vertices -> 4 triangles
1104}
1105
1106
1108{
1109 // U-shaped polygon.
1110 Polygon U;
1111 U.add_vertex(Point(0, 0));
1112 U.add_vertex(Point(6, 0));
1113 U.add_vertex(Point(6, 6));
1114 U.add_vertex(Point(5, 6));
1115 U.add_vertex(Point(5, 1));
1116 U.add_vertex(Point(1, 1));
1117 U.add_vertex(Point(1, 6));
1118 U.add_vertex(Point(0, 6));
1119 U.close();
1120
1122 auto tris = cet(U);
1123 EXPECT_EQ(tris.size(), 6u); // 8 vertices -> 6 triangles
1124}
1125
1126
1128{
1129 // Create a regular-ish polygon with many vertices (circle approximation).
1130 Polygon circle;
1131 const int N = 32;
1132 for (int i = 0; i < N; ++i)
1133 {
1135 Geom_Number y(Geom_Number(1000) * Geom_Number((i * 7 + 3) % N) / Geom_Number(N));
1136 // Use a convex polygon: just use a large square with many vertices on edges
1137 if (i < N/4)
1138 circle.add_vertex(Point(i * 4, 0));
1139 else if (i < N/2)
1140 circle.add_vertex(Point((N/4) * 4, (i - N/4) * 4));
1141 else if (i < 3*N/4)
1142 circle.add_vertex(Point((3*N/4 - i) * 4, (N/4) * 4));
1143 else
1144 circle.add_vertex(Point(0, (N - i) * 4));
1145 }
1146 circle.close();
1147
1148 // Center should be inside.
1149 EXPECT_TRUE(circle.contains(Point(16, 16)));
1150 // Far away point should be outside.
1151 EXPECT_FALSE(circle.contains(Point(1000, 1000)));
1152}
1153
1154
1155// ============================================================================
1156// Section 7.2: Missing Robustness Tests
1157// ============================================================================
1158
1160{
1161 // Three nearly collinear points — exact arithmetic should handle this.
1162 // p3 deviates by 1/10^15 from the line p1-p2.
1163 Point p1(0, 0);
1164 Point p2(Geom_Number(1000000), 0);
1165 // Tiny deviation from collinear.
1166 Point p3(Geom_Number(500000), Geom_Number(1, 1000000000));
1167
1168 // Should NOT be collinear (exact rational arithmetic).
1169 EXPECT_FALSE(p3.is_colinear_with(p1, p2));
1170
1171 // But if deviation is exactly 0, it IS collinear.
1172 Point p4(Geom_Number(500000), 0);
1173 EXPECT_TRUE(p4.is_colinear_with(p1, p2));
1174}
1175
1176
1178{
1179 // Very large coordinates.
1180 Geom_Number big("1000000000000000000"); // 10^18
1181 Point p1(big, big);
1182 Point p2(-big, -big);
1183 Point p3(big, -big);
1184
1185 // Distance should be exact.
1188
1189 // Triangle should work.
1190 Triangle t(p1, p2, p3);
1191 EXPECT_FALSE(t.contains(Point(0, 0))); // origin outside this triangle
1192
1193 // Very small coordinates.
1194 Geom_Number tiny(1, 1000000000);
1195 Point q1(0, 0);
1196 Point q2(tiny, 0);
1197 Point q3(0, tiny);
1198 Triangle t2(q1, q2, q3);
1199 // A point at (tiny/3, tiny/3) should be inside.
1200 EXPECT_TRUE(t2.contains(Point(tiny / 3, tiny / 3)));
1201}
1202
1203
1205{
1206 // Two segments that are nearly parallel but do intersect.
1207 Segment s1(Point(0, 0), Point(Geom_Number(1000000), Geom_Number(1)));
1208 Segment s2(Point(0, Geom_Number(1, 2)),
1209 Point(Geom_Number(1000000), 0));
1210
1211 // They should intersect (they cross at some point).
1212 EXPECT_TRUE(s1.intersects_with(s2));
1213}
1214
1215
1217{
1218 // 4 points on a circle of radius 5 centered at origin.
1219 // (3,4), (-3,4), (-3,-4), (3,-4) all on circle r=5.
1220 Point a(3, 4);
1221 Point b(-3, 4);
1222 Point c(-3, -4);
1223 Point d(3, -4);
1224
1225 // d should be ON the circumcircle of a,b,c (not inside).
1226 auto result = in_circle(a, b, c, d);
1227 EXPECT_EQ(result, InCircleResult::ON_CIRCLE);
1228}
1229
1230
1231// ============================================================================
1232// Section 7.4: Missing Primitive Tests
1233// ============================================================================
1234
1236{
1237 // Test the intersects_properly_with predicate with near-collinear segments.
1238 Segment s1(Point(0, 0), Point(10, 0));
1239 Segment s2(Point(5, -1), Point(5, 1));
1240
1241 EXPECT_TRUE(s1.intersects_properly_with(s2));
1242
1243 // Collinear overlapping segments should NOT intersect properly.
1244 Segment s3(Point(0, 0), Point(6, 0));
1245 Segment s4(Point(4, 0), Point(10, 0));
1246 EXPECT_FALSE(s3.intersects_properly_with(s4));
1247}
1248
1249
1251{
1252 // Vertical segment through the center of an ellipse.
1253 Ellipse e(Point(0, 0), 5, 3);
1254
1255 // Vertical segment x=0 from y=-10 to y=10.
1256 Segment vert(Point(0, -10), Point(0, 10));
1258}
1259
1260
1262{
1263 // Enlarge a diagonal segment in both directions.
1264 Segment s(Point(0, 0), Point(3, 4)); // length = 5
1265
1266 Segment s_copy = s;
1268 // Source should have moved away from target.
1269 EXPECT_TRUE(s_copy.length() > s.length());
1270
1271 Segment s_copy2 = s;
1273 EXPECT_TRUE(s_copy2.length() > s.length());
1274}
1275
1276
1278{
1279 // CCW triangle.
1280 Triangle ccw(Point(0, 0), Point(4, 0), Point(2, 3));
1281 EXPECT_TRUE(ccw.contains(Point(2, 1)));
1282
1283 // CW triangle (reversed vertex order).
1284 Triangle cw(Point(0, 0), Point(2, 3), Point(4, 0));
1285 EXPECT_TRUE(cw.contains(Point(2, 1)));
1286}
1287
1288
1290{
1291 // Two rectangles sharing exactly one corner.
1292 Rectangle r1(0, 0, 5, 5);
1293 Rectangle r2(5, 5, 10, 10);
1294
1295 // They touch at (5,5) — xmin of r2 == xmax of r1.
1296 // Depending on the definition, they may or may not intersect.
1297 // The point (5,5) is on the boundary of both.
1298 Point corner(5, 5);
1299 EXPECT_TRUE(corner.get_x() >= r1.get_xmin() &&
1300 corner.get_x() <= r1.get_xmax() &&
1301 corner.get_y() >= r1.get_ymin() &&
1302 corner.get_y() <= r1.get_ymax());
1303 EXPECT_TRUE(corner.get_x() >= r2.get_xmin() &&
1304 corner.get_x() <= r2.get_xmax() &&
1305 corner.get_y() >= r2.get_ymin() &&
1306 corner.get_y() <= r2.get_ymax());
1307}
1308
1309
1311{
1312 // contains() should return true for endpoints.
1313 Segment s(Point(1, 2), Point(5, 6));
1316
1317 // Midpoint should also be contained.
1319}
1320
1321
1323{
1324 // Verify the new contains() method on Polygon works.
1325 Polygon sq;
1326 sq.add_vertex(Point(0, 0));
1327 sq.add_vertex(Point(10, 0));
1328 sq.add_vertex(Point(10, 10));
1329 sq.add_vertex(Point(0, 10));
1330 sq.close();
1331
1332 EXPECT_TRUE(sq.contains(Point(5, 5)));
1333 EXPECT_FALSE(sq.contains(Point(20, 20)));
1334 // Boundary point.
1335 EXPECT_TRUE(sq.contains(Point(0, 5)));
1336}
1337
1338
1340{
1341 Triangle t(Point(0, 0), Point(10, 0), Point(0, 10));
1342 EXPECT_TRUE(t.contains(Point(1, 1)));
1343 EXPECT_FALSE(t.contains(Point(8, 8)));
1344}
1345
1346
1348{
1349 Ellipse e(Point(0, 0), 5, 3);
1350 EXPECT_TRUE(e.contains(Point(0, 0))); // center
1351 EXPECT_TRUE(e.contains(Point(4, 0))); // inside
1352 EXPECT_FALSE(e.contains(Point(10, 10))); // outside
1353}
1354
1355
1364
1365
1375
1376
1377// ---------- BooleanPolygonOperations: new critical tests ----------
1378
1380{
1381 // L-shaped polygon intersected with a rectangle.
1382 // L: (0,0)-(6,0)-(6,3)-(3,3)-(3,6)-(0,6), area = 27
1383 // Rect: (1,1)-(5,1)-(5,5)-(1,5), area = 16
1384 //
1385 // Geometric intersection:
1386 // The rect clips to the L interior. The rect's top-right corner (5,5)
1387 // is outside the L (the L only extends to x=3 above y=3). So the
1388 // intersection is the rect minus the rectangle (3,3)-(5,3)-(5,5)-(3,5),
1389 // giving area = 16 - 4 = 12.
1390 Polygon L;
1391 L.add_vertex(Point(0, 0));
1392 L.add_vertex(Point(6, 0));
1393 L.add_vertex(Point(6, 3));
1394 L.add_vertex(Point(3, 3));
1395 L.add_vertex(Point(3, 6));
1396 L.add_vertex(Point(0, 6));
1397 L.close();
1398
1399 Polygon rect;
1400 rect.add_vertex(Point(1, 1));
1401 rect.add_vertex(Point(5, 1));
1402 rect.add_vertex(Point(5, 5));
1403 rect.add_vertex(Point(1, 5));
1404 rect.close();
1405
1407 auto result = bop.intersection(L, rect);
1408
1409 EXPECT_GE(result.size(), 1u);
1410
1412 for (size_t i = 0; i < result.size(); ++i)
1413 total_area += polygon_area(result(i));
1414
1415 // Must be strictly positive and less than both inputs.
1418
1419 // Correct intersection area should be exactly 12.
1421 << "Intersection area = " << total_area.get_d()
1422 << ", expected 12 (concave polygon intersection bug?)";
1423}
1424
1425
1427{
1428 // Two overlapping L-shapes.
1429 // L1: (0,0)-(6,0)-(6,3)-(3,3)-(3,6)-(0,6), area = 27
1430 // L2: (2,2)-(8,2)-(8,5)-(5,5)-(5,8)-(2,8), area = 27
1431 // Union area = area(L1) + area(L2) - area(intersection).
1432 Polygon L1;
1433 L1.add_vertex(Point(0, 0));
1434 L1.add_vertex(Point(6, 0));
1435 L1.add_vertex(Point(6, 3));
1436 L1.add_vertex(Point(3, 3));
1437 L1.add_vertex(Point(3, 6));
1438 L1.add_vertex(Point(0, 6));
1439 L1.close();
1440
1441 Polygon L2;
1442 L2.add_vertex(Point(2, 2));
1443 L2.add_vertex(Point(8, 2));
1444 L2.add_vertex(Point(8, 5));
1445 L2.add_vertex(Point(5, 5));
1446 L2.add_vertex(Point(5, 8));
1447 L2.add_vertex(Point(2, 8));
1448 L2.close();
1449
1451 auto result = bop.polygon_union(L1, L2);
1452
1453 EXPECT_GE(result.size(), 1u);
1454
1456 for (size_t i = 0; i < result.size(); ++i)
1457 union_area += polygon_area(result(i));
1458
1459 // Union area must be >= max(area(L1), area(L2)) = 27 and <= area(L1)+area(L2) = 54.
1462
1463 // The result must NOT be the convex hull (which would have 4 vertices
1464 // and area 64 = 8*8). It must preserve concavity.
1465 if (result.size() == 1)
1466 EXPECT_GT(result(0).size(), 4u)
1467 << "Union collapsed to convex hull — concave shape lost";
1468}
1469
1470
1472{
1473 // Rectangle minus overlapping rectangle at corner.
1474 // big: (0,0)-(10,0)-(10,10)-(0,10), area = 100
1475 // corner: (5,5)-(15,5)-(15,15)-(5,15), area = 100
1476 // overlap area = 25, so difference area = 100 - 25 = 75.
1477 Polygon big;
1478 big.add_vertex(Point(0, 0));
1479 big.add_vertex(Point(10, 0));
1480 big.add_vertex(Point(10, 10));
1481 big.add_vertex(Point(0, 10));
1482 big.close();
1483
1485 corner.add_vertex(Point(5, 5));
1486 corner.add_vertex(Point(15, 5));
1487 corner.add_vertex(Point(15, 15));
1488 corner.add_vertex(Point(5, 15));
1489 corner.close();
1490
1492 auto result = bop.difference(big, corner);
1493
1494 EXPECT_GE(result.size(), 1u);
1495
1496 // Difference area = 100 - 25 = 75.
1498 for (size_t i = 0; i < result.size(); ++i)
1499 diff_area += polygon_area(result(i));
1500
1502}
1503
1504
1506{
1507 // Two non-overlapping polygons.
1508 Polygon sq1;
1509 sq1.add_vertex(Point(0, 0));
1510 sq1.add_vertex(Point(1, 0));
1511 sq1.add_vertex(Point(1, 1));
1512 sq1.add_vertex(Point(0, 1));
1513 sq1.close();
1514
1515 Polygon sq2;
1516 sq2.add_vertex(Point(10, 10));
1517 sq2.add_vertex(Point(11, 10));
1518 sq2.add_vertex(Point(11, 11));
1519 sq2.add_vertex(Point(10, 11));
1520 sq2.close();
1521
1523 auto result = bop.intersection(sq1, sq2);
1524
1525 // No overlap → empty result.
1526 EXPECT_EQ(result.size(), 0u);
1527}
1528
1529
1530// ---------- PowerDiagram: new critical tests ----------
1531
1533{
1534 // 4 sites with weights 0, 1, 4, 9.
1536 sites.append({Point(0, 0), Geom_Number(0)});
1537 sites.append({Point(10, 0), Geom_Number(1)});
1538 sites.append({Point(10, 10), Geom_Number(4)});
1539 sites.append({Point(0, 10), Geom_Number(9)});
1540
1542 auto result = pd(sites);
1543
1544 EXPECT_EQ(result.sites.size(), 4u);
1545 EXPECT_EQ(result.cells.size(), 4u);
1546
1547 // Each power vertex must satisfy the equi-power-distance property for
1548 // at least one triple of sites.
1549 for (size_t v = 0; v < result.vertices.size(); ++v)
1550 {
1551 const Point & pc = result.vertices(v);
1553 for (size_t s = 0; s < result.sites.size(); ++s)
1554 pds.append(pc.distance_squared_to(result.sites(s).position)
1555 - result.sites(s).weight);
1556
1557 bool found_triple = false;
1558 for (size_t a = 0; a < pds.size() and not found_triple; ++a)
1559 for (size_t b = a + 1; b < pds.size() and not found_triple; ++b)
1560 for (size_t c = b + 1; c < pds.size() and not found_triple; ++c)
1561 if (pds(a) == pds(b) and pds(b) == pds(c))
1562 found_triple = true;
1563
1565 << "Power vertex " << v << " is not equi-power-distant to any triple";
1566 }
1567}
1568
1569
1571{
1572 // All weights = 0 → result should match standard Voronoi.
1574 wsites.append({Point(0, 0), Geom_Number(0)});
1575 wsites.append({Point(6, 0), Geom_Number(0)});
1576 wsites.append({Point(3, 5), Geom_Number(0)});
1577
1579 auto pr = pd(wsites);
1580
1581 // Standard Voronoi via Delaunay.
1583 auto vr = voronoi({Point(0, 0), Point(6, 0), Point(3, 5)});
1584
1585 // Same number of sites and vertices.
1586 EXPECT_EQ(pr.sites.size(), vr.sites.size());
1587 EXPECT_EQ(pr.vertices.size(), vr.vertices.size());
1588
1589 // The power vertex should be the circumcenter (same as Voronoi vertex).
1590 if (pr.vertices.size() >= 1 and vr.vertices.size() >= 1)
1591 {
1592 const Point & pv = pr.vertices(0);
1593 const Point & vv = vr.vertices(0);
1594 // They should be the same point (or very close).
1595 Geom_Number d2 = pv.distance_squared_to(vv);
1596 EXPECT_LT(d2, Geom_Number(1, 100))
1597 << "Power vertex and Voronoi vertex differ";
1598 }
1599}
1600
1601
1602// ---------- ConvexPolygonOffset: new critical tests ----------
1603
1605{
1606 // Square with extra collinear point on bottom edge.
1607 // (0,0)-(5,0)-(10,0)-(10,10)-(0,10) — 5 vertices, 3 collinear on bottom.
1608 // Original area = 100. Inward offset by 1 should give (10-2)*(10-2) = 64.
1609 Polygon p;
1610 p.add_vertex(Point(0, 0));
1611 p.add_vertex(Point(5, 0));
1612 p.add_vertex(Point(10, 0));
1613 p.add_vertex(Point(10, 10));
1614 p.add_vertex(Point(0, 10));
1615 p.close();
1616
1617 // Should not crash (this tests the collinear vertex handling path).
1619
1620 EXPECT_TRUE(result.is_closed());
1621 EXPECT_GE(result.size(), 3u);
1622
1625
1626 // BUG: collinear consecutive vertices cause incorrect offset geometry.
1627 // The offset area should be 64 (8x8) and strictly less than 100.
1628 // Currently produces area > 100 due to the collinear vertex handling.
1630 << "Inward offset area (" << offset_area.get_d()
1631 << ") >= original area (" << orig_area.get_d()
1632 << ") — collinear vertex bug in ConvexPolygonOffset";
1633}
1634
1635
1637{
1638 // Square 10x10. Inward offset of 6 is larger than half the minimum
1639 // dimension (5), so the offset polygon should be empty or degenerate.
1640 Polygon sq;
1641 sq.add_vertex(Point(0, 0));
1642 sq.add_vertex(Point(10, 0));
1643 sq.add_vertex(Point(10, 10));
1644 sq.add_vertex(Point(0, 10));
1645 sq.close();
1646
1648
1649 // BUG: the half-plane intersection approach produces a non-degenerate
1650 // polygon even when the offset exceeds half the minimum dimension.
1651 // Correct behavior: result should be empty (0 vertices) or < 3 vertices.
1652 EXPECT_LT(result.size(), 3u)
1653 << "Inward offset of 6 on a 10x10 square should produce empty result, "
1654 << "but got " << result.size() << " vertices";
1655}
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:139
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
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.
Dynamic singly linked list with functional programming support.
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
An axis-aligned ellipse.
Definition point.H:2006
const Geom_Number & get_vradius() const
Gets the vertical radius.
Definition point.H:2080
bool intersects_with(const Segment &s) const
Checks if a segment intersects the ellipse.
Definition point.H:2224
const Geom_Number & get_hradius() const
Gets the horizontal radius.
Definition point.H:2078
bool contains(const Point &p) const
Checks if a point lies inside or on the boundary of this ellipse.
Definition point.H:2285
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:2973
static Point3D from_2d(const Point &p)
Lifts a 2D point to 3D, setting its z-coordinate to 0.
Definition point.H:3185
Geom_Number distance_squared_to(const Point3D &p) const
Squared Euclidean distance to another point.
Definition point.H:3126
Point3D cross(const Point3D &p) const
Cross product.
Definition point.H:3112
Geom_Number norm_squared() const
Squared Euclidean norm (magnitude).
Definition point.H:3138
Point to_2d() const
Projects this 3D point to a 2D point by dropping the z-coordinate.
Definition point.H:3178
Geom_Number dot(const Point3D &p) const
Dot product.
Definition point.H:3102
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
Geom_Number distance_squared_to(const Point &that) const
Calculates the squared Euclidean distance to another point.
Definition point.H:1454
bool is_colinear_with(const Point &p1, const Point &p2) const
Checks if this point is collinear with two other points.
Definition point.H:477
Polar representation of a 2D point.
Definition point.H:726
STL/Aleph-compatible iterator over polygon vertices as Points.
Definition polygon.H:317
const Point & get_curr() const
Definition polygon.H:340
bool has_curr() const noexcept
Definition polygon.H:326
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
bool contains(const Point &p) const
Check if a point is inside the polygon (or on its boundary).
Definition polygon.H:926
Power diagram (weighted Voronoi diagram).
QuickHull convex hull algorithm.
An axis-aligned rectangle.
Definition point.H:1737
const Geom_Number & get_xmin() const
Gets the minimum x-coordinate.
Definition point.H:1764
const Geom_Number & get_ymax() const
Gets the maximum y-coordinate.
Definition point.H:1770
const Geom_Number & get_ymin() const
Gets the minimum y-coordinate.
Definition point.H:1766
const Geom_Number & get_xmax() const
Gets the maximum x-coordinate.
Definition point.H:1768
An ellipse with arbitrary rotation.
Definition point.H:2404
bool contains(const Point &p) const
Checks if a point lies inside or on the ellipse.
Definition point.H:2573
const Geom_Number & get_sin() const
Gets the sine of the rotation angle.
Definition point.H:2541
bool on_boundary(const Point &p) const
Checks if a point lies exactly on the ellipse boundary.
Definition point.H:2593
const Geom_Number & get_cos() const
Gets the cosine of the rotation angle.
Definition point.H:2539
Represents a line segment in 3D space.
Definition point.H:3214
Point3D midpoint() const
Calculates the midpoint of the segment.
Definition point.H:3276
bool contains(const Point3D &p) const
Checks if a point lies on the segment.
Definition point.H:3309
const Point3D & get_src() const noexcept
Gets the source point of the segment.
Definition point.H:3229
Geom_Number distance_to(const Point3D &p) const
Calculates the shortest distance from a point to this segment.
Definition point.H:3331
const Point3D & get_tgt() const noexcept
Gets the target point of the segment.
Definition point.H:3231
Point3D at(const Geom_Number &t) const
Evaluates a point on the segment via linear interpolation.
Definition point.H:3266
Geom_Number length_squared() const
Calculates the squared length of the segment.
Definition point.H:3247
Geom_Number length() const
Calculates the length of the segment.
Definition point.H:3256
Represents a line segment between two points.
Definition point.H:827
Point mid_point() const
Returns the midpoint of this segment.
Definition point.H:1115
void enlarge_src(const Geom_Number &dist)
Extends the segment by a given distance from the source endpoint.
Definition point.H:1361
void enlarge_tgt(const Geom_Number &dist)
Extends the segment by a given distance from the target endpoint.
Definition point.H:1371
bool contains(const Point &p) const
Checks if a point lies on this segment.
Definition point.H:1246
const Point & get_tgt_point() const noexcept
Gets the target point of the segment.
Definition point.H:921
const Point & get_src_point() const noexcept
Gets the source point of the segment.
Definition point.H:915
Geom_Number length() const
Returns the Euclidean length of the segment.
Definition point.H:1056
Represents a tetrahedron in 3D space defined by four points.
Definition point.H:3463
Represents a triangle in 3D space defined by three points.
Definition point.H:3359
BaryCoords barycentric(const Point3D &p) const
Computes the barycentric coordinates of a point p with respect to this triangle.
Definition point.H:3438
Point3D centroid() const
Computes the centroid (center of mass) of the triangle.
Definition point.H:3407
Point3D normal() const
Computes the normal vector of the triangle's plane.
Definition point.H:3389
Geom_Number double_area_squared() const
Calculates twice the squared area of the triangle.
Definition point.H:3398
bool is_degenerate() const
Checks if the triangle is degenerate (i.e., its vertices are collinear).
Definition point.H:3416
A non-degenerate triangle defined by three points.
Definition point.H:1478
bool contains(const Point &p) const
Checks if a point lies strictly inside this triangle.
Definition point.H:1706
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:1319
bool exists(Operation &op) const
Test for existence in the container of an element satisfying a criterion.
Definition ah-dry.H:993
bool all(Operation &operation) const
Check if all the elements of the container satisfy a condition.
Definition ah-dry.H:957
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:779
Aleph::DynList< __T > maps(Operation &op) const
Map the elements of the container.
Definition ah-dry.H:1057
auto get_it() const
Return a properly initialized iterator positioned at the first item on the container.
Definition ah-dry.H:222
#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:4060
static mpfr_t y
Definition mpfr_mul_d.c:3
and
Check uniqueness with explicit hash + equality functors.
Geom_Number scalar_triple_product(const Point3D &a, const Point3D &b, const Point3D &c)
Scalar triple product: a · (b × c).
Definition point.H:3204
bool completed() const noexcept
Return true if all underlying iterators are finished.
Definition ah-zip.H:136
size_t size(Node *root) noexcept
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:2857
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.
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:115
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:97
static int * k
gsl_rng * r