symbios_tensor/
geometry.rs1use glam::Vec2;
4
5const DEGENERATE_LEN_SQ: f32 = 1e-6;
7
8const PARALLEL_REL_EPS: f32 = 1e-6;
12
13pub fn closest_point_on_segment(p: Vec2, a: Vec2, b: Vec2) -> Vec2 {
15 let ab = b - a;
16 let len_sq = ab.length_squared();
17 if len_sq < DEGENERATE_LEN_SQ {
18 return a;
19 }
20 let t = ((p - a).dot(ab) / len_sq).clamp(0.0, 1.0);
21 a + t * ab
22}
23
24pub fn segment_intersection(a1: Vec2, a2: Vec2, b1: Vec2, b2: Vec2) -> Option<Vec2> {
26 let s1 = a2 - a1;
27 let s2 = b2 - b1;
28
29 let denom = -s2.x * s1.y + s1.x * s2.y;
30 let len_product = s1.length() * s2.length();
32 if denom.abs() <= PARALLEL_REL_EPS * len_product {
33 return None;
34 }
35
36 let d = a1 - b1;
37 let s = (-s1.y * d.x + s1.x * d.y) / denom;
38 let t = (s2.x * d.y - s2.y * d.x) / denom;
39
40 if (0.0..=1.0).contains(&s) && (0.0..=1.0).contains(&t) {
41 Some(a1 + t * s1)
42 } else {
43 None
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 #[test]
52 fn closest_point_midpoint() {
53 let p = Vec2::new(1.0, 1.0);
54 let a = Vec2::new(0.0, 0.0);
55 let b = Vec2::new(2.0, 0.0);
56 let c = closest_point_on_segment(p, a, b);
57 assert!((c - Vec2::new(1.0, 0.0)).length() < 1e-5);
58 }
59
60 #[test]
61 fn closest_point_clamped_start() {
62 let p = Vec2::new(-5.0, 0.0);
63 let a = Vec2::new(0.0, 0.0);
64 let b = Vec2::new(2.0, 0.0);
65 let c = closest_point_on_segment(p, a, b);
66 assert!((c - a).length() < 1e-5);
67 }
68
69 #[test]
70 fn closest_point_degenerate() {
71 let p = Vec2::new(3.0, 4.0);
72 let a = Vec2::new(1.0, 1.0);
73 let c = closest_point_on_segment(p, a, a);
74 assert!((c - a).length() < 1e-5);
75 }
76
77 #[test]
78 fn segments_cross() {
79 let hit = segment_intersection(
80 Vec2::new(0.0, 0.0),
81 Vec2::new(2.0, 2.0),
82 Vec2::new(0.0, 2.0),
83 Vec2::new(2.0, 0.0),
84 );
85 assert!(hit.is_some());
86 let h = hit.unwrap();
87 assert!((h - Vec2::new(1.0, 1.0)).length() < 1e-5);
88 }
89
90 #[test]
91 fn parallel_segments_no_intersection() {
92 let hit = segment_intersection(
93 Vec2::new(0.0, 0.0),
94 Vec2::new(2.0, 0.0),
95 Vec2::new(0.0, 1.0),
96 Vec2::new(2.0, 1.0),
97 );
98 assert!(hit.is_none());
99 }
100
101 #[test]
102 fn non_overlapping_segments() {
103 let hit = segment_intersection(
104 Vec2::new(0.0, 0.0),
105 Vec2::new(1.0, 0.0),
106 Vec2::new(2.0, -1.0),
107 Vec2::new(2.0, 1.0),
108 );
109 assert!(hit.is_none());
110 }
111}