1use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::types::{
8 AlphaShapeBuilder, BentleyOttmannEvents, ConvexHull2D, ConvexLayerPeeler,
9 DelaunayTriangulation, FrechetDistanceApprox, KdNode, MinkowskiSum, Orientation,
10 PlaneSubdivision, Point2D, RangeTree1D, RangeTree2D, SpatialHash, Triangle, VoronoiDiagram,
11};
12
13pub fn app(f: Expr, a: Expr) -> Expr {
14 Expr::App(Box::new(f), Box::new(a))
15}
16pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
17 app(app(f, a), b)
18}
19pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
20 app(app2(f, a, b), c)
21}
22pub fn cst(s: &str) -> Expr {
23 Expr::Const(Name::str(s), vec![])
24}
25pub fn prop() -> Expr {
26 Expr::Sort(Level::zero())
27}
28pub fn type0() -> Expr {
29 Expr::Sort(Level::succ(Level::zero()))
30}
31pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
32 Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
33}
34pub fn arrow(a: Expr, b: Expr) -> Expr {
35 pi(BinderInfo::Default, "_", a, b)
36}
37pub fn nat_ty() -> Expr {
38 cst("Nat")
39}
40pub fn real_ty() -> Expr {
41 cst("Real")
42}
43pub fn bool_ty() -> Expr {
44 cst("Bool")
45}
46pub fn list_ty(elem: Expr) -> Expr {
47 app(cst("List"), elem)
48}
49pub fn pair_ty(a: Expr, b: Expr) -> Expr {
50 app2(cst("Prod"), a, b)
51}
52pub fn point2d_ty() -> Expr {
53 pair_ty(real_ty(), real_ty())
54}
55pub fn list_point2d_ty() -> Expr {
56 list_ty(point2d_ty())
57}
58pub fn list_nat_ty() -> Expr {
59 list_ty(nat_ty())
60}
61pub fn point2d_kernel_ty() -> Expr {
63 pair_ty(real_ty(), real_ty())
64}
65pub fn point3d_kernel_ty() -> Expr {
67 pair_ty(real_ty(), pair_ty(real_ty(), real_ty()))
68}
69pub fn segment2d_ty() -> Expr {
71 pair_ty(point2d_ty(), point2d_ty())
72}
73pub fn triangle2d_ty() -> Expr {
75 pair_ty(point2d_ty(), pair_ty(point2d_ty(), point2d_ty()))
76}
77pub fn polygon2d_ty() -> Expr {
79 arrow(list_point2d_ty(), type0())
80}
81pub fn convex_hull_fn_ty() -> Expr {
84 arrow(list_point2d_ty(), list_point2d_ty())
85}
86pub fn is_convex_ty() -> Expr {
88 arrow(list_point2d_ty(), prop())
89}
90pub fn is_on_convex_hull_ty() -> Expr {
93 arrow(point2d_ty(), arrow(list_point2d_ty(), prop()))
94}
95pub fn triangulation_ty() -> Expr {
98 let triple = pair_ty(nat_ty(), pair_ty(nat_ty(), nat_ty()));
99 arrow(list_point2d_ty(), list_ty(triple))
100}
101pub fn delaunay_triangulation_ty() -> Expr {
104 triangulation_ty()
105}
106pub fn is_delaunay_ty() -> Expr {
109 let triple = pair_ty(nat_ty(), pair_ty(nat_ty(), nat_ty()));
110 arrow(list_point2d_ty(), arrow(list_ty(triple), prop()))
111}
112pub fn voronoi_cell_ty() -> Expr {
115 arrow(
116 list_point2d_ty(),
117 arrow(nat_ty(), arrow(point2d_ty(), prop())),
118 )
119}
120pub fn voronoi_diagram_ty() -> Expr {
123 arrow(list_point2d_ty(), list_ty(list_point2d_ty()))
124}
125pub fn circumcircle_center_ty() -> Expr {
128 arrow(
129 point2d_ty(),
130 arrow(point2d_ty(), arrow(point2d_ty(), point2d_ty())),
131 )
132}
133pub fn point_location_ty() -> Expr {
136 arrow(list_point2d_ty(), arrow(point2d_ty(), nat_ty()))
137}
138pub fn kd_tree_ty() -> Expr {
140 arrow(nat_ty(), type0())
141}
142pub fn kd_tree_query_ty() -> Expr {
145 arrow(
146 type0(),
147 arrow(point2d_ty(), arrow(real_ty(), list_point2d_ty())),
148 )
149}
150pub fn nearest_neighbour_ty() -> Expr {
153 arrow(list_point2d_ty(), arrow(point2d_ty(), point2d_ty()))
154}
155pub fn closest_pair_fn_ty() -> Expr {
158 arrow(list_point2d_ty(), pair_ty(point2d_ty(), point2d_ty()))
159}
160pub fn segments_intersect_ty() -> Expr {
163 arrow(segment2d_ty(), arrow(segment2d_ty(), prop()))
164}
165pub fn segment_intersection_point_ty() -> Expr {
168 arrow(segment2d_ty(), arrow(segment2d_ty(), point2d_ty()))
169}
170pub fn any_segments_intersect_ty() -> Expr {
173 arrow(list_ty(segment2d_ty()), prop())
174}
175pub fn polygon_area_ty() -> Expr {
178 arrow(list_point2d_ty(), real_ty())
179}
180pub fn polygon_centroid_ty() -> Expr {
183 arrow(list_point2d_ty(), point2d_ty())
184}
185pub fn point_in_polygon_ty() -> Expr {
188 arrow(point2d_ty(), arrow(list_point2d_ty(), prop()))
189}
190pub fn polygon_perimeter_ty() -> Expr {
192 arrow(list_point2d_ty(), real_ty())
193}
194pub fn spatial_hash_ty() -> Expr {
196 arrow(nat_ty(), type0())
197}
198pub fn spatial_hash_insert_ty() -> Expr {
200 arrow(type0(), arrow(point2d_ty(), type0()))
201}
202pub fn hyperplane_ty() -> Expr {
204 arrow(nat_ty(), type0())
205}
206pub fn hyperplane_arrangement_ty() -> Expr {
209 arrow(list_ty(type0()), type0())
210}
211pub fn zone_theorem_bound_ty() -> Expr {
215 arrow(nat_ty(), nat_ty())
216}
217pub fn arrangement_face_count_ty() -> Expr {
221 arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
222}
223pub fn cell_of_arrangement_ty() -> Expr {
226 arrow(type0(), arrow(point2d_ty(), nat_ty()))
227}
228pub fn alpha_complex_ty() -> Expr {
231 arrow(list_point2d_ty(), arrow(real_ty(), type0()))
232}
233pub fn cech_complex_ty() -> Expr {
236 arrow(list_point2d_ty(), arrow(real_ty(), type0()))
237}
238pub fn persistence_diagram_ty() -> Expr {
241 list_ty(pair_ty(real_ty(), real_ty()))
242}
243pub fn compute_persistence_ty() -> Expr {
246 arrow(arrow(real_ty(), type0()), persistence_diagram_ty())
247}
248pub fn alpha_shape_filtration_ty() -> Expr {
251 arrow(list_point2d_ty(), list_ty(pair_ty(real_ty(), type0())))
252}
253pub fn kinetic_certificate_ty() -> Expr {
256 type0()
257}
258pub fn kinetic_tournament_ty() -> Expr {
261 arrow(nat_ty(), type0())
262}
263pub fn kinetic_heap_ty() -> Expr {
266 arrow(nat_ty(), type0())
267}
268pub fn event_queue_ty() -> Expr {
271 arrow(type0(), type0())
272}
273pub fn kinetic_convex_hull_ty() -> Expr {
276 arrow(nat_ty(), type0())
277}
278pub fn parametric_search_problem_ty() -> Expr {
282 arrow(real_ty(), prop())
283}
284pub fn cole_parallel_binary_search_ty() -> Expr {
287 arrow(arrow(real_ty(), prop()), real_ty())
288}
289pub fn parallel_binary_search_ty() -> Expr {
292 arrow(list_ty(pair_ty(real_ty(), real_ty())), list_ty(real_ty()))
293}
294pub fn point_hyperplane_dual_ty() -> Expr {
297 arrow(point2d_ty(), arrow(real_ty(), prop()))
298}
299pub fn gale_transform_ty() -> Expr {
302 arrow(list_point2d_ty(), list_point2d_ty())
303}
304pub fn dual_arrangement_ty() -> Expr {
307 arrow(type0(), list_point2d_ty())
308}
309pub fn sweep_line_state_ty() -> Expr {
312 list_ty(pair_ty(point2d_ty(), point2d_ty()))
313}
314pub fn shamos_hoey_algorithm_ty() -> Expr {
317 arrow(list_ty(segment2d_ty()), bool_ty())
318}
319pub fn bentley_ottmann_output_ty() -> Expr {
322 arrow(list_ty(segment2d_ty()), list_point2d_ty())
323}
324pub fn bentley_ottmann_correctness_ty() -> Expr {
327 arrow(list_ty(segment2d_ty()), prop())
328}
329pub fn steiner_point_ty() -> Expr {
332 arrow(list_point2d_ty(), point2d_ty())
333}
334pub fn delaunay_refinement_ty() -> Expr {
338 arrow(list_point2d_ty(), arrow(real_ty(), list_point2d_ty()))
339}
340pub fn min_angle_guarantee_ty() -> Expr {
343 arrow(list_point2d_ty(), arrow(real_ty(), prop()))
344}
345pub fn triangulation_quality_ty() -> Expr {
348 arrow(list_point2d_ty(), real_ty())
349}
350pub fn vc_dimension_ty() -> Expr {
353 arrow(arrow(point2d_ty(), prop()), nat_ty())
354}
355pub fn epsilon_net_ty() -> Expr {
358 arrow(list_point2d_ty(), arrow(real_ty(), list_point2d_ty()))
359}
360pub fn haussler_packing_lemma_ty() -> Expr {
363 arrow(nat_ty(), arrow(nat_ty(), nat_ty()))
364}
365pub fn epsilon_net_bound_ty() -> Expr {
368 arrow(nat_ty(), arrow(real_ty(), nat_ty()))
369}
370pub fn range_tree_ty() -> Expr {
373 arrow(nat_ty(), type0())
374}
375pub fn range_tree_query_ty() -> Expr {
378 arrow(
379 type0(),
380 arrow(
381 pair_ty(real_ty(), real_ty()),
382 arrow(pair_ty(real_ty(), real_ty()), list_point2d_ty()),
383 ),
384 )
385}
386pub fn fractional_cascading_ty() -> Expr {
389 arrow(list_ty(list_ty(real_ty())), arrow(real_ty(), list_nat_ty()))
390}
391pub fn orthogonal_range_searching_ty() -> Expr {
394 arrow(
395 list_point2d_ty(),
396 arrow(
397 pair_ty(real_ty(), real_ty()),
398 arrow(pair_ty(real_ty(), real_ty()), list_point2d_ty()),
399 ),
400 )
401}
402pub fn segment_tree_ty() -> Expr {
405 type0()
406}
407pub fn stabbing_query_ty() -> Expr {
410 arrow(
411 type0(),
412 arrow(real_ty(), list_ty(pair_ty(real_ty(), real_ty()))),
413 )
414}
415pub fn interval_intersection_query_ty() -> Expr {
418 arrow(
419 type0(),
420 arrow(
421 pair_ty(real_ty(), real_ty()),
422 list_ty(pair_ty(real_ty(), real_ty())),
423 ),
424 )
425}
426pub fn convex_layers_ty() -> Expr {
429 arrow(list_point2d_ty(), list_ty(list_point2d_ty()))
430}
431pub fn convex_layer_depth_ty() -> Expr {
434 arrow(list_point2d_ty(), arrow(point2d_ty(), nat_ty()))
435}
436pub fn tukey_depth_ty() -> Expr {
439 arrow(list_point2d_ty(), arrow(point2d_ty(), nat_ty()))
440}
441pub fn k_center_clustering_ty() -> Expr {
444 arrow(list_point2d_ty(), arrow(nat_ty(), list_point2d_ty()))
445}
446pub fn k_median_clustering_ty() -> Expr {
449 arrow(list_point2d_ty(), arrow(nat_ty(), list_point2d_ty()))
450}
451pub fn facility_location_ty() -> Expr {
454 arrow(list_point2d_ty(), arrow(real_ty(), list_point2d_ty()))
455}
456pub fn k_center_approx_ratio_ty() -> Expr {
459 nat_ty()
460}
461pub fn hausdorff_distance_ty() -> Expr {
464 arrow(list_point2d_ty(), arrow(list_point2d_ty(), real_ty()))
465}
466pub fn directed_hausdorff_ty() -> Expr {
469 arrow(list_point2d_ty(), arrow(list_point2d_ty(), real_ty()))
470}
471pub fn frechet_distance_ty() -> Expr {
474 arrow(list_point2d_ty(), arrow(list_point2d_ty(), real_ty()))
475}
476pub fn discrete_frechet_distance_ty() -> Expr {
479 arrow(list_point2d_ty(), arrow(list_point2d_ty(), real_ty()))
480}
481pub fn shape_matching_optimal_ty() -> Expr {
484 arrow(list_point2d_ty(), arrow(list_point2d_ty(), real_ty()))
485}
486pub fn configuration_space_ty() -> Expr {
489 arrow(type0(), type0())
490}
491pub fn free_space_ty() -> Expr {
494 arrow(type0(), arrow(type0(), prop()))
495}
496pub fn probabilistic_roadmap_ty() -> Expr {
499 arrow(type0(), arrow(nat_ty(), type0()))
500}
501pub fn rrt_path_ty() -> Expr {
504 arrow(
505 type0(),
506 arrow(point2d_ty(), arrow(point2d_ty(), list_point2d_ty())),
507 )
508}
509pub fn motion_planning_completeness_ty() -> Expr {
512 arrow(type0(), prop())
513}
514pub fn build_computational_geometry_env(env: &mut Environment) {
516 let axioms: &[(&str, Expr)] = &[
517 ("Point2D", point2d_kernel_ty()),
518 ("Point3D", point3d_kernel_ty()),
519 ("Segment2D", segment2d_ty()),
520 ("Triangle2D", triangle2d_ty()),
521 ("Polygon2D", polygon2d_ty()),
522 ("ConvexHullFn", convex_hull_fn_ty()),
523 ("IsConvex", is_convex_ty()),
524 ("IsOnConvexHull", is_on_convex_hull_ty()),
525 ("TriangulationFn", triangulation_ty()),
526 ("DelaunayTriangulationFn", delaunay_triangulation_ty()),
527 ("IsDelaunay", is_delaunay_ty()),
528 ("VoronoiCell", voronoi_cell_ty()),
529 ("VoronoiDiagramFn", voronoi_diagram_ty()),
530 ("CircumcircleCenter", circumcircle_center_ty()),
531 ("PointLocationFn", point_location_ty()),
532 ("KdTree", kd_tree_ty()),
533 ("KdTreeQuery", kd_tree_query_ty()),
534 ("NearestNeighbourFn", nearest_neighbour_ty()),
535 ("ClosestPairFn", closest_pair_fn_ty()),
536 ("SegmentsIntersect", segments_intersect_ty()),
537 ("SegmentIntersectionPoint", segment_intersection_point_ty()),
538 ("AnySegmentsIntersect", any_segments_intersect_ty()),
539 ("PolygonArea", polygon_area_ty()),
540 ("PolygonCentroid", polygon_centroid_ty()),
541 ("PointInPolygon", point_in_polygon_ty()),
542 ("PolygonPerimeter", polygon_perimeter_ty()),
543 ("SpatialHash", spatial_hash_ty()),
544 ("SpatialHashInsert", spatial_hash_insert_ty()),
545 ("ConvexHullCorrectness", arrow(list_point2d_ty(), prop())),
546 ("DelaunayMaxMinAngle", arrow(list_point2d_ty(), prop())),
547 ("DelaunayUniqueness", arrow(list_point2d_ty(), prop())),
548 ("VoronoiDelaunayDuality", arrow(list_point2d_ty(), prop())),
549 (
550 "ShamosHoeyCorrectness",
551 arrow(list_ty(segment2d_ty()), prop()),
552 ),
553 (
554 "ClosestPairOptimality",
555 arrow(list_point2d_ty(), arrow(real_ty(), prop())),
556 ),
557 ("GrahamScanComplexity", nat_ty()),
558 ("DelaunayComplexity", nat_ty()),
559 ("FortuneAlgorithmComplexity", nat_ty()),
560 ("HyperplaneTy", hyperplane_ty()),
561 ("HyperplaneArrangementTy", hyperplane_arrangement_ty()),
562 ("ZoneTheoremBound", zone_theorem_bound_ty()),
563 ("ArrangementFaceCount", arrangement_face_count_ty()),
564 ("CellOfArrangement", cell_of_arrangement_ty()),
565 ("AlphaComplex", alpha_complex_ty()),
566 ("CechComplex", cech_complex_ty()),
567 ("PersistenceDiagramTy", persistence_diagram_ty()),
568 ("ComputePersistence", compute_persistence_ty()),
569 ("AlphaShapeFiltration", alpha_shape_filtration_ty()),
570 ("KineticCertificate", kinetic_certificate_ty()),
571 ("KineticTournament", kinetic_tournament_ty()),
572 ("KineticHeap", kinetic_heap_ty()),
573 ("EventQueue", event_queue_ty()),
574 ("KineticConvexHull", kinetic_convex_hull_ty()),
575 ("ParametricSearchProblem", parametric_search_problem_ty()),
576 ("ColeParallelBinarySearch", cole_parallel_binary_search_ty()),
577 ("ParallelBinarySearch", parallel_binary_search_ty()),
578 ("PointHyperplaneDual", point_hyperplane_dual_ty()),
579 ("GaleTransform", gale_transform_ty()),
580 ("DualArrangement", dual_arrangement_ty()),
581 ("SweepLineState", sweep_line_state_ty()),
582 ("ShamosHoeyAlgorithm", shamos_hoey_algorithm_ty()),
583 ("BentleyOttmannOutput", bentley_ottmann_output_ty()),
584 (
585 "BentleyOttmannCorrectness",
586 bentley_ottmann_correctness_ty(),
587 ),
588 ("SteinerPoint", steiner_point_ty()),
589 ("DelaunayRefinement", delaunay_refinement_ty()),
590 ("MinAngleGuarantee", min_angle_guarantee_ty()),
591 ("TriangulationQuality", triangulation_quality_ty()),
592 ("VCDimension", vc_dimension_ty()),
593 ("EpsilonNet", epsilon_net_ty()),
594 ("HausslerPackingLemma", haussler_packing_lemma_ty()),
595 ("EpsilonNetBound", epsilon_net_bound_ty()),
596 ("RangeTree", range_tree_ty()),
597 ("RangeTreeQuery", range_tree_query_ty()),
598 ("FractionalCascading", fractional_cascading_ty()),
599 ("OrthogonalRangeSearching", orthogonal_range_searching_ty()),
600 ("SegmentTree", segment_tree_ty()),
601 ("StabbingQuery", stabbing_query_ty()),
602 (
603 "IntervalIntersectionQuery",
604 interval_intersection_query_ty(),
605 ),
606 ("ConvexLayers", convex_layers_ty()),
607 ("ConvexLayerDepth", convex_layer_depth_ty()),
608 ("TukeyDepth", tukey_depth_ty()),
609 ("KCenterClustering", k_center_clustering_ty()),
610 ("KMedianClustering", k_median_clustering_ty()),
611 ("FacilityLocation", facility_location_ty()),
612 ("KCenterApproxRatio", k_center_approx_ratio_ty()),
613 ("HausdorffDistance", hausdorff_distance_ty()),
614 ("DirectedHausdorff", directed_hausdorff_ty()),
615 ("FrechetDistance", frechet_distance_ty()),
616 ("DiscreteFrechetDistance", discrete_frechet_distance_ty()),
617 ("ShapeMatchingOptimal", shape_matching_optimal_ty()),
618 ("ConfigurationSpace", configuration_space_ty()),
619 ("FreeSpace", free_space_ty()),
620 ("ProbabilisticRoadmap", probabilistic_roadmap_ty()),
621 ("RRTPath", rrt_path_ty()),
622 (
623 "MotionPlanningCompleteness",
624 motion_planning_completeness_ty(),
625 ),
626 ];
627 for (name, ty) in axioms {
628 env.add(Declaration::Axiom {
629 name: Name::str(*name),
630 univ_params: vec![],
631 ty: ty.clone(),
632 })
633 .ok();
634 }
635}
636pub fn cross(p0: &Point2D, p1: &Point2D, p2: &Point2D) -> f64 {
641 (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x)
642}
643pub fn orientation(p: &Point2D, q: &Point2D, r: &Point2D) -> Orientation {
645 let val = cross(p, q, r);
646 if val > 1e-12 {
647 Orientation::CounterClockwise
648 } else if val < -1e-12 {
649 Orientation::Clockwise
650 } else {
651 Orientation::Collinear
652 }
653}
654pub fn graham_scan(points: &[Point2D]) -> Vec<Point2D> {
657 let n = points.len();
658 if n < 3 {
659 return points.to_vec();
660 }
661 let pivot_idx = points
662 .iter()
663 .enumerate()
664 .min_by(|(_, a), (_, b)| {
665 a.y.partial_cmp(&b.y)
666 .unwrap_or(std::cmp::Ordering::Equal)
667 .then(a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal))
668 })
669 .map(|(i, _)| i)
670 .expect("points is non-empty: checked by caller");
671 let pivot = points[pivot_idx];
672 let mut sorted: Vec<Point2D> = points.to_vec();
673 sorted.swap(0, pivot_idx);
674 let p0 = sorted[0];
675 sorted[1..].sort_by(|a, b| {
676 let ca = cross(&p0, a, b);
677 if ca.abs() < 1e-12 {
678 p0.dist_sq(a)
679 .partial_cmp(&p0.dist_sq(b))
680 .unwrap_or(std::cmp::Ordering::Equal)
681 } else if ca > 0.0 {
682 std::cmp::Ordering::Less
683 } else {
684 std::cmp::Ordering::Greater
685 }
686 });
687 let mut hull: Vec<Point2D> = Vec::with_capacity(n);
688 hull.push(sorted[0]);
689 hull.push(sorted[1]);
690 for i in 2..n {
691 while hull.len() >= 2 {
692 let m = hull.len();
693 let cr = cross(&hull[m - 2], &hull[m - 1], &sorted[i]);
694 if cr <= 0.0 {
695 hull.pop();
696 } else {
697 break;
698 }
699 }
700 hull.push(sorted[i]);
701 }
702 let _ = pivot;
703 hull
704}
705pub fn jarvis_march(points: &[Point2D]) -> Vec<Point2D> {
708 let n = points.len();
709 if n < 3 {
710 return points.to_vec();
711 }
712 let start = points
713 .iter()
714 .enumerate()
715 .min_by(|(_, a), (_, b)| {
716 a.x.partial_cmp(&b.x)
717 .unwrap_or(std::cmp::Ordering::Equal)
718 .then(a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal))
719 })
720 .map(|(i, _)| i)
721 .expect("points is non-empty: checked by n < 3 guard");
722 let mut hull = Vec::new();
723 let mut current = start;
724 loop {
725 hull.push(points[current]);
726 let mut next = 0usize;
727 for i in 1..n {
728 if next == current {
729 next = i;
730 continue;
731 }
732 let c = cross(&points[current], &points[next], &points[i]);
733 if c < -1e-12 {
734 next = i;
735 } else if c.abs() <= 1e-12 {
736 if points[current].dist_sq(&points[i]) > points[current].dist_sq(&points[next]) {
737 next = i;
738 }
739 }
740 }
741 current = next;
742 if current == start {
743 break;
744 }
745 if hull.len() > n {
746 break;
747 }
748 }
749 hull
750}
751pub fn closest_pair(points: &[Point2D]) -> Option<(f64, Point2D, Point2D)> {
754 if points.len() < 2 {
755 return None;
756 }
757 let mut sorted_x: Vec<Point2D> = points.to_vec();
758 sorted_x.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
759 let result = closest_pair_rec(&sorted_x);
760 Some(result)
761}
762pub fn closest_pair_rec(pts: &[Point2D]) -> (f64, Point2D, Point2D) {
763 let n = pts.len();
764 if n <= 3 {
765 return brute_force_closest(pts);
766 }
767 let mid = n / 2;
768 let mid_x = pts[mid].x;
769 let (d_left, pa_l, pb_l) = closest_pair_rec(&pts[..mid]);
770 let (d_right, pa_r, pb_r) = closest_pair_rec(&pts[mid..]);
771 let (mut best_d, mut best_a, mut best_b) = if d_left <= d_right {
772 (d_left, pa_l, pb_l)
773 } else {
774 (d_right, pa_r, pb_r)
775 };
776 let strip: Vec<Point2D> = pts
777 .iter()
778 .filter(|p| (p.x - mid_x).abs() < best_d)
779 .cloned()
780 .collect();
781 let mut strip_y = strip.clone();
782 strip_y.sort_by(|a, b| a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal));
783 for i in 0..strip_y.len() {
784 for j in (i + 1)..strip_y.len() {
785 if strip_y[j].y - strip_y[i].y >= best_d {
786 break;
787 }
788 let d = strip_y[i].dist(&strip_y[j]);
789 if d < best_d {
790 best_d = d;
791 best_a = strip_y[i];
792 best_b = strip_y[j];
793 }
794 }
795 }
796 (best_d, best_a, best_b)
797}
798pub fn brute_force_closest(pts: &[Point2D]) -> (f64, Point2D, Point2D) {
799 let mut best_d = f64::INFINITY;
800 let mut best_a = pts[0];
801 let mut best_b = pts[1.min(pts.len() - 1)];
802 for i in 0..pts.len() {
803 for j in (i + 1)..pts.len() {
804 let d = pts[i].dist(&pts[j]);
805 if d < best_d {
806 best_d = d;
807 best_a = pts[i];
808 best_b = pts[j];
809 }
810 }
811 }
812 (best_d, best_a, best_b)
813}
814pub fn polygon_signed_area(vertices: &[Point2D]) -> f64 {
817 let n = vertices.len();
818 if n < 3 {
819 return 0.0;
820 }
821 let mut area = 0.0f64;
822 for i in 0..n {
823 let j = (i + 1) % n;
824 area += vertices[i].x * vertices[j].y;
825 area -= vertices[j].x * vertices[i].y;
826 }
827 area / 2.0
828}
829pub fn polygon_area(vertices: &[Point2D]) -> f64 {
831 polygon_signed_area(vertices).abs()
832}
833pub fn polygon_centroid(vertices: &[Point2D]) -> Option<Point2D> {
835 let n = vertices.len();
836 if n < 3 {
837 return None;
838 }
839 let area = polygon_signed_area(vertices);
840 if area.abs() < 1e-14 {
841 return None;
842 }
843 let mut cx = 0.0f64;
844 let mut cy = 0.0f64;
845 for i in 0..n {
846 let j = (i + 1) % n;
847 let cross = vertices[i].x * vertices[j].y - vertices[j].x * vertices[i].y;
848 cx += (vertices[i].x + vertices[j].x) * cross;
849 cy += (vertices[i].y + vertices[j].y) * cross;
850 }
851 let inv = 1.0 / (6.0 * area);
852 Some(Point2D::new(cx * inv, cy * inv))
853}
854pub fn polygon_perimeter(vertices: &[Point2D]) -> f64 {
856 let n = vertices.len();
857 if n < 2 {
858 return 0.0;
859 }
860 (0..n)
861 .map(|i| vertices[i].dist(&vertices[(i + 1) % n]))
862 .sum()
863}
864pub fn point_in_polygon(p: &Point2D, polygon: &[Point2D]) -> bool {
866 let n = polygon.len();
867 if n < 3 {
868 return false;
869 }
870 let mut inside = false;
871 let mut j = n - 1;
872 for i in 0..n {
873 let vi = &polygon[i];
874 let vj = &polygon[j];
875 let crosses_y = (vi.y > p.y) != (vj.y > p.y);
876 if crosses_y {
877 let x_intersect = (vj.x - vi.x) * (p.y - vi.y) / (vj.y - vi.y) + vi.x;
878 if p.x < x_intersect {
879 inside = !inside;
880 }
881 }
882 j = i;
883 }
884 inside
885}
886pub fn is_convex_polygon(vertices: &[Point2D]) -> bool {
888 let n = vertices.len();
889 if n < 3 {
890 return true;
891 }
892 let mut sign = 0i32;
893 for i in 0..n {
894 let j = (i + 1) % n;
895 let k = (i + 2) % n;
896 let c = cross(&vertices[i], &vertices[j], &vertices[k]);
897 if c > 1e-12 {
898 if sign == -1 {
899 return false;
900 }
901 sign = 1;
902 } else if c < -1e-12 {
903 if sign == 1 {
904 return false;
905 }
906 sign = -1;
907 }
908 }
909 true
910}
911pub fn segments_intersect(p1: &Point2D, p2: &Point2D, p3: &Point2D, p4: &Point2D) -> bool {
914 let d1 = cross(p3, p4, p1);
915 let d2 = cross(p3, p4, p2);
916 let d3 = cross(p1, p2, p3);
917 let d4 = cross(p1, p2, p4);
918 if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
919 && ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
920 {
921 return true;
922 }
923 if d1.abs() < 1e-12 && on_segment(p3, p4, p1) {
924 return true;
925 }
926 if d2.abs() < 1e-12 && on_segment(p3, p4, p2) {
927 return true;
928 }
929 if d3.abs() < 1e-12 && on_segment(p1, p2, p3) {
930 return true;
931 }
932 if d4.abs() < 1e-12 && on_segment(p1, p2, p4) {
933 return true;
934 }
935 false
936}
937pub fn on_segment(p: &Point2D, q: &Point2D, r: &Point2D) -> bool {
939 r.x >= p.x.min(q.x) - 1e-12
940 && r.x <= p.x.max(q.x) + 1e-12
941 && r.y >= p.y.min(q.y) - 1e-12
942 && r.y <= p.y.max(q.y) + 1e-12
943}
944pub fn segment_intersection(
947 p1: &Point2D,
948 p2: &Point2D,
949 p3: &Point2D,
950 p4: &Point2D,
951) -> Option<Point2D> {
952 let denom = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x);
953 if denom.abs() < 1e-12 {
954 return None;
955 }
956 let t_num = (p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x);
957 let t = t_num / denom;
958 Some(Point2D::new(
959 p1.x + t * (p2.x - p1.x),
960 p1.y + t * (p2.y - p1.y),
961 ))
962}
963pub fn in_circumcircle(pa: &Point2D, pb: &Point2D, pc: &Point2D, p: &Point2D) -> bool {
966 let ax = pa.x - p.x;
967 let ay = pa.y - p.y;
968 let bx = pb.x - p.x;
969 let by = pb.y - p.y;
970 let cx = pc.x - p.x;
971 let cy = pc.y - p.y;
972 let det = ax * (by * (cx * cx + cy * cy) - cy * (bx * bx + by * by))
973 - ay * (bx * (cx * cx + cy * cy) - cx * (bx * bx + by * by))
974 + (ax * ax + ay * ay) * (bx * cy - by * cx);
975 det > 1e-12
976}
977pub fn circumcenter(a: &Point2D, b: &Point2D, c: &Point2D) -> Option<Point2D> {
979 let ax = b.x - a.x;
980 let ay = b.y - a.y;
981 let bx = c.x - a.x;
982 let by = c.y - a.y;
983 let d = 2.0 * (ax * by - ay * bx);
984 if d.abs() < 1e-14 {
985 return None;
986 }
987 let ux = (by * (ax * ax + ay * ay) - ay * (bx * bx + by * by)) / d;
988 let uy = (ax * (bx * bx + by * by) - bx * (ax * ax + ay * ay)) / d;
989 Some(Point2D::new(a.x + ux, a.y + uy))
990}
991pub fn delaunay_triangulation(points: &[Point2D]) -> Vec<Triangle> {
994 let n = points.len();
995 if n < 3 {
996 return vec![];
997 }
998 let min_x = points.iter().map(|p| p.x).fold(f64::INFINITY, f64::min);
999 let max_x = points.iter().map(|p| p.x).fold(f64::NEG_INFINITY, f64::max);
1000 let min_y = points.iter().map(|p| p.y).fold(f64::INFINITY, f64::min);
1001 let max_y = points.iter().map(|p| p.y).fold(f64::NEG_INFINITY, f64::max);
1002 let dx = max_x - min_x;
1003 let dy = max_y - min_y;
1004 let delta_max = dx.max(dy);
1005 let mid_x = (min_x + max_x) / 2.0;
1006 let mid_y = (min_y + max_y) / 2.0;
1007 let mut pts: Vec<Point2D> = points.to_vec();
1008 let st0 = Point2D::new(mid_x - 20.0 * delta_max, mid_y - delta_max);
1009 let st1 = Point2D::new(mid_x, mid_y + 20.0 * delta_max);
1010 let st2 = Point2D::new(mid_x + 20.0 * delta_max, mid_y - delta_max);
1011 pts.push(st0);
1012 pts.push(st1);
1013 pts.push(st2);
1014 let si0 = n;
1015 let si1 = n + 1;
1016 let si2 = n + 2;
1017 let mut triangulation: Vec<Triangle> = vec![Triangle::new(si0, si1, si2)];
1018 for i in 0..n {
1019 let p = pts[i];
1020 let mut bad: Vec<Triangle> = Vec::new();
1021 let mut good: Vec<Triangle> = Vec::new();
1022 for &tri in &triangulation {
1023 let ta = pts[tri.a];
1024 let tb = pts[tri.b];
1025 let tc = pts[tri.c];
1026 if in_circumcircle(&ta, &tb, &tc, &p) {
1027 bad.push(tri);
1028 } else {
1029 good.push(tri);
1030 }
1031 }
1032 let mut edges: Vec<(usize, usize)> = Vec::new();
1033 for tri in &bad {
1034 let tri_edges = [(tri.a, tri.b), (tri.b, tri.c), (tri.c, tri.a)];
1035 for &(u, v) in &tri_edges {
1036 let already = edges
1037 .iter()
1038 .any(|&(eu, ev)| (eu == u && ev == v) || (eu == v && ev == u));
1039 if already {
1040 edges.retain(|&(eu, ev)| !((eu == u && ev == v) || (eu == v && ev == u)));
1041 } else {
1042 edges.push((u, v));
1043 }
1044 }
1045 }
1046 triangulation = good;
1047 for (u, v) in edges {
1048 let pa = pts[u];
1049 let pb = pts[v];
1050 if cross(&pa, &pb, &p) > 0.0 {
1051 triangulation.push(Triangle::new(u, v, i));
1052 } else {
1053 triangulation.push(Triangle::new(v, u, i));
1054 }
1055 }
1056 }
1057 triangulation
1058 .retain(|t| !t.contains_vertex(si0) && !t.contains_vertex(si1) && !t.contains_vertex(si2));
1059 triangulation
1060}
1061pub fn build_kd_tree(points: &[Point2D]) -> KdNode {
1063 build_kd_tree_rec(&mut points.to_vec(), 0)
1064}
1065pub fn build_kd_tree_rec(points: &mut Vec<Point2D>, depth: usize) -> KdNode {
1066 if points.is_empty() {
1067 return KdNode::Leaf;
1068 }
1069 let axis = (depth % 2) as u8;
1070 if axis == 0 {
1071 points.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
1072 } else {
1073 points.sort_by(|a, b| a.y.partial_cmp(&b.y).unwrap_or(std::cmp::Ordering::Equal));
1074 }
1075 let median = points.len() / 2;
1076 let point = points[median];
1077 let mut left_pts: Vec<Point2D> = points[..median].to_vec();
1078 let mut right_pts: Vec<Point2D> = points[(median + 1)..].to_vec();
1079 KdNode::Node {
1080 point,
1081 left: Box::new(build_kd_tree_rec(&mut left_pts, depth + 1)),
1082 right: Box::new(build_kd_tree_rec(&mut right_pts, depth + 1)),
1083 axis,
1084 }
1085}
1086pub fn kd_nearest(tree: &KdNode, query: &Point2D) -> Option<Point2D> {
1088 let mut best: Option<(f64, Point2D)> = None;
1089 kd_nearest_rec(tree, query, &mut best);
1090 best.map(|(_, p)| p)
1091}
1092pub fn kd_nearest_rec(node: &KdNode, query: &Point2D, best: &mut Option<(f64, Point2D)>) {
1093 match node {
1094 KdNode::Leaf => {}
1095 KdNode::Node {
1096 point,
1097 left,
1098 right,
1099 axis,
1100 } => {
1101 let d = query.dist(point);
1102 if best.map_or(true, |(bd, _)| d < bd) {
1103 *best = Some((d, *point));
1104 }
1105 let (near, far) =
1106 if (*axis == 0 && query.x < point.x) || (*axis == 1 && query.y < point.y) {
1107 (left.as_ref(), right.as_ref())
1108 } else {
1109 (right.as_ref(), left.as_ref())
1110 };
1111 kd_nearest_rec(near, query, best);
1112 let split_diff = if *axis == 0 {
1113 (query.x - point.x).abs()
1114 } else {
1115 (query.y - point.y).abs()
1116 };
1117 if best.map_or(true, |(bd, _)| split_diff < bd) {
1118 kd_nearest_rec(far, query, best);
1119 }
1120 }
1121 }
1122}
1123pub fn kd_range_query(tree: &KdNode, query: &Point2D, r: f64) -> Vec<Point2D> {
1125 let mut results = Vec::new();
1126 kd_range_rec(tree, query, r, &mut results);
1127 results
1128}
1129pub fn kd_range_rec(node: &KdNode, query: &Point2D, r: f64, results: &mut Vec<Point2D>) {
1130 match node {
1131 KdNode::Leaf => {}
1132 KdNode::Node {
1133 point,
1134 left,
1135 right,
1136 axis,
1137 } => {
1138 if query.dist(point) <= r {
1139 results.push(*point);
1140 }
1141 let split_diff = if *axis == 0 {
1142 (query.x - point.x).abs()
1143 } else {
1144 (query.y - point.y).abs()
1145 };
1146 kd_range_rec(left, query, r, results);
1147 if split_diff <= r {
1148 kd_range_rec(right, query, r, results);
1149 }
1150 }
1151 }
1152}
1153pub fn build_range_tree_1d(pts: &[(f64, Point2D)]) -> RangeTree1D {
1155 if pts.is_empty() {
1156 return RangeTree1D::Empty;
1157 }
1158 let mid = pts.len() / 2;
1159 let (key, point) = pts[mid];
1160 RangeTree1D::Node {
1161 key,
1162 point,
1163 left: Box::new(build_range_tree_1d(&pts[..mid])),
1164 right: Box::new(build_range_tree_1d(&pts[(mid + 1)..])),
1165 }
1166}
1167pub fn query_range_tree_1d(node: &RangeTree1D, lo: f64, hi: f64, result: &mut Vec<Point2D>) {
1169 match node {
1170 RangeTree1D::Empty => {}
1171 RangeTree1D::Node {
1172 key,
1173 point,
1174 left,
1175 right,
1176 } => {
1177 if *key >= lo && *key <= hi {
1178 result.push(*point);
1179 }
1180 if lo < *key {
1181 query_range_tree_1d(left, lo, hi, result);
1182 }
1183 if hi > *key {
1184 query_range_tree_1d(right, lo, hi, result);
1185 }
1186 }
1187 }
1188}
1189#[cfg(test)]
1190mod tests {
1191 use super::*;
1192 fn pts(coords: &[(f64, f64)]) -> Vec<Point2D> {
1193 coords.iter().map(|&(x, y)| Point2D::new(x, y)).collect()
1194 }
1195 #[test]
1196 fn test_convex_hull_square() {
1197 let points = pts(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.5, 0.5)]);
1198 let hull = graham_scan(&points);
1199 assert_eq!(
1200 hull.len(),
1201 4,
1202 "square hull should have 4 points, got {}",
1203 hull.len()
1204 );
1205 }
1206 #[test]
1207 fn test_convex_hull_collinear() {
1208 let points = pts(&[(0.0, 0.0), (1.0, 0.0), (2.0, 0.0), (1.0, 1.0)]);
1209 let hull = graham_scan(&points);
1210 let has_middle = hull
1211 .iter()
1212 .any(|p| (p.x - 1.0).abs() < 1e-9 && p.y.abs() < 1e-9);
1213 assert!(!has_middle, "collinear middle point should not be on hull");
1214 }
1215 #[test]
1216 fn test_polygon_area_unit_square() {
1217 let square = pts(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]);
1218 let area = polygon_area(&square);
1219 assert!(
1220 (area - 1.0).abs() < 1e-10,
1221 "unit square area = 1, got {area}"
1222 );
1223 }
1224 #[test]
1225 fn test_polygon_centroid_square() {
1226 let square = pts(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]);
1227 let c = polygon_centroid(&square).expect("operation should succeed");
1228 assert!(
1229 (c.x - 0.5).abs() < 1e-10 && (c.y - 0.5).abs() < 1e-10,
1230 "centroid of unit square should be (0.5, 0.5), got ({}, {})",
1231 c.x,
1232 c.y
1233 );
1234 }
1235 #[test]
1236 fn test_point_in_polygon() {
1237 let square = pts(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)]);
1238 assert!(
1239 point_in_polygon(&Point2D::new(2.0, 2.0), &square),
1240 "center should be inside"
1241 );
1242 assert!(
1243 !point_in_polygon(&Point2D::new(5.0, 2.0), &square),
1244 "outside point should not be inside"
1245 );
1246 }
1247 #[test]
1248 fn test_segments_intersect_crossing() {
1249 let p1 = Point2D::new(0.0, 0.0);
1250 let p2 = Point2D::new(2.0, 2.0);
1251 let p3 = Point2D::new(0.0, 2.0);
1252 let p4 = Point2D::new(2.0, 0.0);
1253 assert!(
1254 segments_intersect(&p1, &p2, &p3, &p4),
1255 "crossing segments should intersect"
1256 );
1257 }
1258 #[test]
1259 fn test_segments_no_intersect_parallel() {
1260 let p1 = Point2D::new(0.0, 0.0);
1261 let p2 = Point2D::new(1.0, 0.0);
1262 let p3 = Point2D::new(0.0, 1.0);
1263 let p4 = Point2D::new(1.0, 1.0);
1264 assert!(
1265 !segments_intersect(&p1, &p2, &p3, &p4),
1266 "parallel segments should not intersect"
1267 );
1268 }
1269 #[test]
1270 fn test_closest_pair() {
1271 let points = pts(&[(0.0, 0.0), (5.0, 5.0), (1.0, 0.1), (10.0, 10.0)]);
1272 let (d, _a, _b) = closest_pair(&points).expect("operation should succeed");
1273 let expected = ((0.0 - 1.0f64).powi(2) + (0.0 - 0.1f64).powi(2)).sqrt();
1274 assert!(
1275 (d - expected).abs() < 1e-9,
1276 "closest pair distance: expected {expected}, got {d}"
1277 );
1278 }
1279 #[test]
1280 fn test_kd_tree_nearest() {
1281 let points = pts(&[(0.0, 0.0), (3.0, 4.0), (1.0, 1.0), (7.0, 2.0)]);
1282 let tree = build_kd_tree(&points);
1283 let query = Point2D::new(1.1, 1.1);
1284 let nearest = kd_nearest(&tree, &query).expect("Point2D::new should succeed");
1285 assert!(
1286 (nearest.x - 1.0).abs() < 1e-9 && (nearest.y - 1.0).abs() < 1e-9,
1287 "nearest to (1.1,1.1) should be (1.0,1.0), got ({},{})",
1288 nearest.x,
1289 nearest.y
1290 );
1291 }
1292 #[test]
1293 fn test_bentley_ottmann_events_no_intersection() {
1294 let segs = vec![
1295 (Point2D::new(0.0, 0.0), Point2D::new(2.0, 0.0)),
1296 (Point2D::new(0.0, 1.0), Point2D::new(2.0, 1.0)),
1297 ];
1298 let eq = BentleyOttmannEvents::new(&segs);
1299 let intersections = eq.all_intersections();
1300 assert!(
1301 intersections.is_empty(),
1302 "parallel segments should not intersect"
1303 );
1304 }
1305 #[test]
1306 fn test_bentley_ottmann_events_crossing() {
1307 let segs = vec![
1308 (Point2D::new(0.0, 0.0), Point2D::new(2.0, 2.0)),
1309 (Point2D::new(0.0, 2.0), Point2D::new(2.0, 0.0)),
1310 ];
1311 let eq = BentleyOttmannEvents::new(&segs);
1312 let intersections = eq.all_intersections();
1313 assert_eq!(intersections.len(), 1, "exactly one intersection expected");
1314 let p = &intersections[0];
1315 assert!(
1316 (p.x - 1.0).abs() < 1e-9 && (p.y - 1.0).abs() < 1e-9,
1317 "intersection should be (1,1), got ({},{})",
1318 p.x,
1319 p.y
1320 );
1321 }
1322 #[test]
1323 fn test_bentley_ottmann_event_queue_ordering() {
1324 let segs = vec![
1325 (Point2D::new(3.0, 0.0), Point2D::new(5.0, 2.0)),
1326 (Point2D::new(0.0, 1.0), Point2D::new(2.0, 1.0)),
1327 ];
1328 let eq = BentleyOttmannEvents::new(&segs);
1329 assert_eq!(eq.len(), 4, "4 endpoint events expected");
1330 }
1331 #[test]
1332 fn test_alpha_shape_builder_trivial() {
1333 let pts_data = pts(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (2.0, 2.0)]);
1334 let builder = AlphaShapeBuilder::new(&pts_data);
1335 let alphas = builder.critical_alphas();
1336 let max_finite = alphas
1337 .iter()
1338 .filter(|a| a.is_finite())
1339 .copied()
1340 .fold(f64::NEG_INFINITY, f64::max);
1341 if max_finite > 0.0 {
1342 let step = builder.at_alpha(max_finite);
1343 assert!(
1344 !step.triangles.is_empty(),
1345 "should have at least one triangle at max finite alpha={max_finite}"
1346 );
1347 }
1348 let step_inf = builder.at_alpha(f64::INFINITY);
1349 let _ = step_inf;
1350 }
1351 #[test]
1352 fn test_alpha_shape_builder_filtration_monotone() {
1353 let pts_data = pts(&[(0.0, 0.0), (1.0, 0.0), (0.5, 1.0), (0.5, 0.3)]);
1354 let builder = AlphaShapeBuilder::new(&pts_data);
1355 let filt = builder.filtration();
1356 let mut prev_count = 0usize;
1357 for step in &filt {
1358 assert!(
1359 step.triangles.len() >= prev_count,
1360 "filtration must be monotone: at alpha={} have {} triangles, prev {}",
1361 step.alpha,
1362 step.triangles.len(),
1363 prev_count
1364 );
1365 prev_count = step.triangles.len();
1366 }
1367 }
1368 #[test]
1369 fn test_range_tree_2d_basic() {
1370 let points = pts(&[(1.0, 1.0), (2.0, 3.0), (3.0, 2.0), (5.0, 5.0), (4.0, 1.0)]);
1371 let tree = RangeTree2D::new(&points);
1372 let mut result = tree.query(1.5, 3.5, 1.5, 3.5);
1373 result.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
1374 assert_eq!(
1375 result.len(),
1376 2,
1377 "should find 2 points in [1.5,3.5]x[1.5,3.5], got {:?}",
1378 result
1379 );
1380 }
1381 #[test]
1382 fn test_range_tree_2d_via_tree() {
1383 let points = pts(&[(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (2.0, 1.0)]);
1384 let tree = RangeTree2D::new(&points);
1385 let result = tree.query_via_tree(1.0, 3.0, 1.0, 4.0);
1386 assert!(!result.is_empty(), "should find points in range");
1387 }
1388 #[test]
1389 fn test_range_tree_2d_empty_result() {
1390 let points = pts(&[(10.0, 10.0), (20.0, 20.0)]);
1391 let tree = RangeTree2D::new(&points);
1392 let result = tree.query(0.0, 5.0, 0.0, 5.0);
1393 assert!(result.is_empty(), "no points in small range");
1394 }
1395 #[test]
1396 fn test_frechet_distance_identical_curves() {
1397 let curve = pts(&[(0.0, 0.0), (1.0, 0.0), (2.0, 0.0)]);
1398 let d = FrechetDistanceApprox::compute(&curve, &curve);
1399 assert!(
1400 d < 1e-9,
1401 "Fréchet distance between identical curves should be 0, got {d}"
1402 );
1403 }
1404 #[test]
1405 fn test_frechet_distance_parallel_curves() {
1406 let p = pts(&[(0.0, 0.0), (1.0, 0.0), (2.0, 0.0)]);
1407 let q = pts(&[(0.0, 1.0), (1.0, 1.0), (2.0, 1.0)]);
1408 let d = FrechetDistanceApprox::compute(&p, &q);
1409 assert!(
1410 (d - 1.0).abs() < 1e-9,
1411 "parallel curves at distance 1.0 should have Fréchet distance 1.0, got {d}"
1412 );
1413 }
1414 #[test]
1415 fn test_frechet_decision_true() {
1416 let p = pts(&[(0.0, 0.0), (1.0, 0.0)]);
1417 let q = pts(&[(0.0, 0.5), (1.0, 0.5)]);
1418 assert!(
1419 FrechetDistanceApprox::decide(&p, &q, 1.0),
1420 "Fréchet distance should be <= 1.0"
1421 );
1422 }
1423 #[test]
1424 fn test_frechet_decision_false() {
1425 let p = pts(&[(0.0, 0.0), (1.0, 0.0)]);
1426 let q = pts(&[(0.0, 2.0), (1.0, 2.0)]);
1427 assert!(
1428 !FrechetDistanceApprox::decide(&p, &q, 1.5),
1429 "Fréchet distance 2.0 should not be <= 1.5"
1430 );
1431 }
1432 #[test]
1433 fn test_convex_layer_peeler_basic() {
1434 let points = pts(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (2.0, 2.0)]);
1435 let mut peeler = ConvexLayerPeeler::new(&points);
1436 peeler.peel_all();
1437 assert!(
1438 peeler.num_layers() >= 2,
1439 "should have at least 2 layers, got {}",
1440 peeler.num_layers()
1441 );
1442 }
1443 #[test]
1444 fn test_convex_layer_peeler_all_hull() {
1445 let points = pts(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]);
1446 let mut peeler = ConvexLayerPeeler::new(&points);
1447 peeler.peel_all();
1448 assert_eq!(peeler.num_layers(), 1, "all hull points = 1 layer");
1449 }
1450 #[test]
1451 fn test_convex_layer_peeler_depth() {
1452 let points = pts(&[(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0), (2.0, 2.0)]);
1453 let mut peeler = ConvexLayerPeeler::new(&points);
1454 peeler.peel_all();
1455 let inner = Point2D::new(2.0, 2.0);
1456 let d = peeler.depth_of(&inner);
1457 assert!(
1458 d.map_or(false, |depth| depth >= 1),
1459 "interior point should have depth >= 1"
1460 );
1461 }
1462 #[test]
1463 fn test_build_computational_geometry_env_extended() {
1464 use oxilean_kernel::Environment;
1465 let mut env = Environment::new();
1466 build_computational_geometry_env(&mut env);
1467 for name in [
1468 "AlphaComplex",
1469 "CechComplex",
1470 "KineticTournament",
1471 "BentleyOttmannOutput",
1472 "VCDimension",
1473 "EpsilonNet",
1474 "RangeTree",
1475 "FractionalCascading",
1476 "HausdorffDistance",
1477 "FrechetDistance",
1478 "ConfigurationSpace",
1479 "ConvexLayers",
1480 "KCenterClustering",
1481 "DelaunayRefinement",
1482 ] {
1483 assert!(
1484 env.get(&oxilean_kernel::Name::str(name)).is_some(),
1485 "axiom '{}' should be registered",
1486 name
1487 );
1488 }
1489 }
1490}
1491#[allow(dead_code)]
1492pub fn cross_2d(o: (f64, f64), a: (f64, f64), b: (f64, f64)) -> f64 {
1493 (a.0 - o.0) * (b.1 - o.1) - (a.1 - o.1) * (b.0 - o.0)
1494}
1495#[cfg(test)]
1496mod tests_cg_extra {
1497 use super::*;
1498 #[test]
1499 fn test_voronoi_nearest_site() {
1500 let v = VoronoiDiagram::new(vec![(0.0, 0.0), (5.0, 0.0), (2.5, 5.0)]);
1501 let nearest = v
1502 .nearest_site((1.0, 0.0))
1503 .expect("VoronoiDiagram::new should succeed");
1504 assert_eq!(nearest, 0, "Nearest to (1,0) should be site 0 at origin");
1505 }
1506 #[test]
1507 fn test_convex_hull() {
1508 let points = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.5, 0.5)];
1509 let hull = ConvexHull2D::compute(points);
1510 assert_eq!(hull.n_hull_points(), 4, "Square should have 4 hull points");
1511 let area = hull.area();
1512 assert!((area - 1.0).abs() < 1e-9, "Area should be 1.0, got {area}");
1513 }
1514 #[test]
1515 fn test_minkowski_sum() {
1516 let square = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
1517 let ms = MinkowskiSum::new(square.clone(), square.clone());
1518 assert_eq!(ms.result_size_upper_bound(), 8);
1519 assert!(ms.is_convex_if_inputs_convex());
1520 }
1521 #[test]
1522 fn test_plane_subdivision_euler() {
1523 let mut sub = PlaneSubdivision::new(vec![(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)]);
1524 sub.add_edge(0, 1);
1525 sub.add_edge(1, 2);
1526 sub.add_edge(2, 0);
1527 assert_eq!(sub.n_faces(), 2);
1528 }
1529}