proof_engine/topology/
projective.rs1use glam::Vec2;
4
5pub struct ProjectivePlane {
11 pub size: f32,
12}
13
14impl ProjectivePlane {
15 pub fn new(size: f32) -> Self {
16 Self { size }
17 }
18
19 pub fn projective_wrap(&self, pos: Vec2) -> Vec2 {
22 projective_wrap(pos, self.size)
23 }
24}
25
26pub fn projective_wrap(pos: Vec2, size: f32) -> Vec2 {
29 let mut x = pos.x;
30 let mut y = pos.y;
31
32 let double = 2.0 * size;
34 x = ((x % double) + double) % double;
35 y = ((y % double) + double) % double;
36
37 if x >= size && y >= size {
39 x -= size;
40 y -= size;
41 } else if x >= size {
42 x -= size;
44 y += size;
45 if y >= double {
46 y -= double;
47 }
48 if x >= size || y >= size {
50 x = ((x % size) + size) % size;
51 y = ((y % size) + size) % size;
52 }
53 } else if y >= size {
54 y -= size;
55 x += size;
56 if x >= double {
57 x -= double;
58 }
59 if x >= size || y >= size {
60 x = ((x % size) + size) % size;
61 y = ((y % size) + size) % size;
62 }
63 }
64
65 x = ((x % size) + size) % size;
67 y = ((y % size) + size) % size;
68
69 Vec2::new(x, y)
70}
71
72pub fn homogeneous_coords(x: f32, y: f32) -> [f32; 3] {
75 [x, y, 1.0]
76}
77
78pub fn projective_line(p1: [f32; 3], p2: [f32; 3]) -> [f32; 3] {
81 cross3(p1, p2)
82}
83
84pub fn line_intersection(l1: [f32; 3], l2: [f32; 3]) -> Option<Vec2> {
88 let p = cross3(l1, l2);
89 if p[2].abs() < 1e-10 {
90 if p[0].abs() < 1e-10 && p[1].abs() < 1e-10 {
92 return None; }
94 let scale = 1e6;
97 let len = (p[0] * p[0] + p[1] * p[1]).sqrt();
98 return Some(Vec2::new(p[0] / len * scale, p[1] / len * scale));
99 }
100 Some(Vec2::new(p[0] / p[2], p[1] / p[2]))
101}
102
103fn cross3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
105 [
106 a[1] * b[2] - a[2] * b[1],
107 a[2] * b[0] - a[0] * b[2],
108 a[0] * b[1] - a[1] * b[0],
109 ]
110}
111
112pub fn cross_ratio(a: f32, b: f32, c: f32, d: f32) -> f32 {
118 let ac = c - a;
119 let bd = d - b;
120 let bc = c - b;
121 let ad = d - a;
122
123 if bc.abs() < 1e-10 || ad.abs() < 1e-10 {
124 return f32::INFINITY;
125 }
126
127 (ac * bd) / (bc * ad)
128}
129
130pub fn cross_ratio_homogeneous(a: [f32; 2], b: [f32; 2], c: [f32; 2], d: [f32; 2]) -> f32 {
132 let ac = a[0] * c[1] - a[1] * c[0];
134 let bd = b[0] * d[1] - b[1] * d[0];
135 let bc = b[0] * c[1] - b[1] * c[0];
136 let ad = a[0] * d[1] - a[1] * d[0];
137
138 if bc.abs() < 1e-10 || ad.abs() < 1e-10 {
139 return f32::INFINITY;
140 }
141
142 (ac * bd) / (bc * ad)
143}
144
145#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_projective_wrap_inside() {
153 let p = projective_wrap(Vec2::new(3.0, 4.0), 10.0);
154 assert!((p.x - 3.0).abs() < 1e-4);
155 assert!((p.y - 4.0).abs() < 1e-4);
156 }
157
158 #[test]
159 fn test_projective_wrap_result_in_domain() {
160 for ix in -20..20 {
161 for iy in -20..20 {
162 let pos = Vec2::new(ix as f32 * 3.7, iy as f32 * 2.3);
163 let w = projective_wrap(pos, 10.0);
164 assert!(w.x >= -1e-4 && w.x < 10.0 + 1e-4, "x out of range: {}", w.x);
165 assert!(w.y >= -1e-4 && w.y < 10.0 + 1e-4, "y out of range: {}", w.y);
166 }
167 }
168 }
169
170 #[test]
171 fn test_homogeneous_coords() {
172 let h = homogeneous_coords(3.0, 4.0);
173 assert_eq!(h, [3.0, 4.0, 1.0]);
174 }
175
176 #[test]
177 fn test_projective_line_through_points() {
178 let p1 = homogeneous_coords(0.0, 0.0);
179 let p2 = homogeneous_coords(1.0, 1.0);
180 let line = projective_line(p1, p2);
181 let dot1 = line[0] * p1[0] + line[1] * p1[1] + line[2] * p1[2];
184 let dot2 = line[0] * p2[0] + line[1] * p2[1] + line[2] * p2[2];
185 assert!(dot1.abs() < 1e-6);
186 assert!(dot2.abs() < 1e-6);
187 }
188
189 #[test]
190 fn test_line_intersection() {
191 let l1 = projective_line(homogeneous_coords(0.0, 0.0), homogeneous_coords(1.0, 0.0));
194 let l2 = projective_line(homogeneous_coords(0.5, -1.0), homogeneous_coords(0.5, 1.0));
195 let inter = line_intersection(l1, l2);
196 assert!(inter.is_some());
197 let p = inter.unwrap();
198 assert!((p.x - 0.5).abs() < 1e-3, "x = {}", p.x);
199 assert!(p.y.abs() < 1e-3, "y = {}", p.y);
200 }
201
202 #[test]
203 fn test_line_intersection_parallel() {
204 let l1 = [0.0_f32, 1.0, -1.0]; let l2 = [0.0_f32, 1.0, -2.0]; let inter = line_intersection(l1, l2);
208 assert!(inter.is_some());
210 }
211
212 #[test]
213 fn test_cross_ratio_basic() {
214 let cr = cross_ratio(0.0, 1.0, 2.0, 3.0);
216 assert!((cr - 4.0 / 3.0).abs() < 1e-4, "Cross ratio = {}", cr);
218 }
219
220 #[test]
221 fn test_cross_ratio_projective_invariant() {
222 let transform = |x: f32| (2.0 * x + 1.0) / (x + 1.0);
225
226 let a = 0.0_f32;
227 let b = 1.0;
228 let c = 2.0;
229 let d = 3.0;
230
231 let cr1 = cross_ratio(a, b, c, d);
232 let cr2 = cross_ratio(transform(a), transform(b), transform(c), transform(d));
233 assert!((cr1 - cr2).abs() < 1e-3, "Cross-ratio not invariant: {} vs {}", cr1, cr2);
234 }
235
236 #[test]
237 fn test_cross_ratio_homogeneous() {
238 let cr1 = cross_ratio(0.0, 1.0, 2.0, 3.0);
239 let cr2 = cross_ratio_homogeneous([0.0, 1.0], [1.0, 1.0], [2.0, 1.0], [3.0, 1.0]);
240 assert!((cr1 - cr2).abs() < 1e-4);
241 }
242}