1const SQRT_3_F32: f32 = 1.7320508_f32;
8
9#[derive(Debug, Clone)]
15#[repr(C)]
16pub struct SnapResult {
17 pub snap_a: i32,
18 pub snap_b: i32,
19 pub error: f32,
20 pub chamber: i32,
21}
22
23pub fn snap(x: f32, y: f32) -> SnapResult {
28 let b_approx = (2.0 * y) / SQRT_3_F32;
29 let a_approx = x + y / SQRT_3_F32;
30
31 let b0 = b_approx.floor() as i32;
32 let a0 = a_approx.floor() as i32;
33
34 let mut best_a = a0;
35 let mut best_b = b0;
36 let mut best_err = f32::MAX;
37
38 for da in -1i32..=1 {
39 for db in -1i32..=1 {
40 let a = a0 + da;
41 let b = b0 + db;
42 let px = a as f32 - b as f32 * 0.5;
43 let py = b as f32 * SQRT_3_F32 * 0.5;
44 let dx = x - px;
45 let dy = y - py;
46 let err = dx * dx + dy * dy;
47 if err < best_err {
48 best_err = err;
49 best_a = a;
50 best_b = b;
51 }
52 }
53 }
54
55 let best_err = best_err.sqrt();
56
57 let px = best_a as f32 - best_b as f32 * 0.5;
58 let py = best_b as f32 * SQRT_3_F32 * 0.5;
59 let dx = x - px;
60 let dy = y - py;
61
62 let chamber = if best_err < 1e-6 {
63 0
64 } else {
65 let angle = dy.atan2(dx);
66 let sector = ((angle + std::f32::consts::PI) / (std::f32::consts::PI / 3.0)).floor() as i32;
67 (sector % 6).min(5).max(0)
68 };
69
70 SnapResult {
71 snap_a: best_a,
72 snap_b: best_b,
73 error: best_err,
74 chamber,
75 }
76}
77
78pub fn batch_snap(flat: &[f32]) -> Vec<SnapResult> {
82 assert!(flat.len() % 2 == 0, "flat must have even length");
83 flat.chunks_exact(2)
84 .map(|chunk| snap(chunk[0], chunk[1]))
85 .collect()
86}
87
88#[no_mangle]
96pub extern "C" fn fleet_eisenstein_norm(a: i32, b: i32) -> i64 {
97 let a = a as i64;
98 let b = b as i64;
99 a * a - a * b + b * b
100}
101
102#[no_mangle]
110pub extern "C" fn fleet_laman_edges(vertices: i32) -> i32 {
111 if vertices < 2 {
112 return 0;
113 }
114 2 * vertices - 3
115}
116
117#[no_mangle]
122pub extern "C" fn fleet_is_rigid(vertices: i32, edges: i32) -> bool {
123 if vertices < 2 {
124 return edges >= 0;
125 }
126 edges >= 2 * vertices - 3
127}
128
129#[no_mangle]
141pub extern "C" fn fleet_holonomy_check(transforms: *const i64, len: usize) -> bool {
142 if transforms.is_null() || len == 0 {
143 return true;
145 }
146 let slice = unsafe { std::slice::from_raw_parts(transforms, len) };
147 let product: i64 = slice.iter().product();
148 product == 1
149}
150
151#[no_mangle]
162pub extern "C" fn fleet_manhattan_distance(a: *const i32, b: *const i32, len: usize) -> i64 {
163 if a.is_null() || b.is_null() || len == 0 {
164 return 0;
165 }
166 let sa = unsafe { std::slice::from_raw_parts(a, len) };
167 let sb = unsafe { std::slice::from_raw_parts(b, len) };
168 sa.iter()
169 .zip(sb.iter())
170 .map(|(&x, &y)| (x as i64 - y as i64).abs())
171 .sum()
172}
173
174pub const PYTHAGOREAN_48_DIRECTIONS: usize = 48;
180
181#[no_mangle]
189pub extern "C" fn fleet_pythagorean48_encode(x: f64, y: f64) -> i32 {
190 if x == 0.0 && y == 0.0 {
191 return 0;
192 }
193 let angle = y.atan2(x); let angle = if angle < 0.0 { angle + 2.0 * std::f64::consts::PI } else { angle };
196 let sector = (angle / (2.0 * std::f64::consts::PI / 48.0)).floor() as i32;
197 sector.min(47).max(0)
198}
199
200#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
211 fn test_snap_origin() {
212 let r = snap(0.0, 0.0);
213 assert_eq!(r.snap_a, 0);
214 assert_eq!(r.snap_b, 0);
215 assert!(r.error < 0.001);
216 }
217
218 #[test]
219 fn test_batch_snap() {
220 let flat = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0];
221 let results = batch_snap(&flat);
222 assert_eq!(results.len(), 3);
223 assert_eq!(results[0].snap_a, 0);
224 assert_eq!(results[0].snap_b, 0);
225 }
226
227 #[test]
230 fn test_eisenstein_norm_zero() {
231 assert_eq!(fleet_eisenstein_norm(0, 0), 0);
232 }
233
234 #[test]
235 fn test_eisenstein_norm_unit() {
236 assert_eq!(fleet_eisenstein_norm(1, 0), 1);
238 assert_eq!(fleet_eisenstein_norm(0, 1), 1);
240 assert_eq!(fleet_eisenstein_norm(1, 1), 1);
242 }
243
244 #[test]
245 fn test_eisenstein_norm_larger() {
246 assert_eq!(fleet_eisenstein_norm(2, 3), 7);
248 assert_eq!(fleet_eisenstein_norm(-2, 3), 19);
250 }
251
252 #[test]
253 fn test_eisenstein_norm_symmetry() {
254 assert_eq!(fleet_eisenstein_norm(3, 5), fleet_eisenstein_norm(5, 3));
256 assert_eq!(fleet_eisenstein_norm(3, 5), fleet_eisenstein_norm(-3, -5));
257 }
258
259 #[test]
262 fn test_laman_edges_basic() {
263 assert_eq!(fleet_laman_edges(2), 1); assert_eq!(fleet_laman_edges(3), 3); assert_eq!(fleet_laman_edges(4), 5); assert_eq!(fleet_laman_edges(10), 17); }
268
269 #[test]
270 fn test_laman_edges_degenerate() {
271 assert_eq!(fleet_laman_edges(0), 0);
272 assert_eq!(fleet_laman_edges(1), 0);
273 assert_eq!(fleet_laman_edges(-5), 0);
274 }
275
276 #[test]
279 fn test_is_rigid_true() {
280 assert!(fleet_is_rigid(3, 3)); assert!(fleet_is_rigid(4, 6)); assert!(fleet_is_rigid(2, 1)); }
284
285 #[test]
286 fn test_is_rigid_false() {
287 assert!(!fleet_is_rigid(3, 2)); assert!(!fleet_is_rigid(4, 4)); assert!(!fleet_is_rigid(10, 10)); }
291
292 #[test]
293 fn test_is_rigid_degenerate() {
294 assert!(fleet_is_rigid(0, 0));
296 assert!(fleet_is_rigid(1, 0));
297 assert!(!fleet_is_rigid(0, -1)); }
299
300 #[test]
303 fn test_holonomy_identity() {
304 let t: [i64; 1] = [1];
305 assert!(fleet_holonomy_check(t.as_ptr(), 1));
306 }
307
308 #[test]
309 fn test_holonomy_pair() {
310 let t: [i64; 2] = [2, -2];
311 assert!(!fleet_holonomy_check(t.as_ptr(), 2));
313 }
314
315 #[test]
316 fn test_holonomy_product_one() {
317 let _t: [i64; 3] = [2, 3, -6]; let t2: [i64; 4] = [1, 1, 1, 1];
320 assert!(fleet_holonomy_check(t2.as_ptr(), 4));
321 }
322
323 #[test]
324 fn test_holonomy_null() {
325 assert!(fleet_holonomy_check(std::ptr::null(), 0));
326 assert!(fleet_holonomy_check(std::ptr::null(), 5)); }
328
329 #[test]
330 fn test_holonomy_non_identity() {
331 let t: [i64; 2] = [1, 2];
332 assert!(!fleet_holonomy_check(t.as_ptr(), 2));
333 }
334
335 #[test]
338 fn test_manhattan_same() {
339 let a: [i32; 3] = [1, 2, 3];
340 let b: [i32; 3] = [1, 2, 3];
341 assert_eq!(fleet_manhattan_distance(a.as_ptr(), b.as_ptr(), 3), 0);
342 }
343
344 #[test]
345 fn test_manhattan_simple() {
346 let a: [i32; 3] = [0, 0, 0];
347 let b: [i32; 3] = [1, 2, 3];
348 assert_eq!(fleet_manhattan_distance(a.as_ptr(), b.as_ptr(), 3), 6);
349 }
350
351 #[test]
352 fn test_manhattan_negative() {
353 let a: [i32; 2] = [-1, -2];
354 let b: [i32; 2] = [1, 2];
355 assert_eq!(fleet_manhattan_distance(a.as_ptr(), b.as_ptr(), 2), 6);
356 }
357
358 #[test]
361 fn test_pythag48_zero() {
362 assert_eq!(fleet_pythagorean48_encode(0.0, 0.0), 0);
363 }
364
365 #[test]
366 fn test_pythag48_positive_x() {
367 assert_eq!(fleet_pythagorean48_encode(1.0, 0.0), 0);
369 }
370
371 #[test]
372 fn test_pythag48_positive_y() {
373 assert_eq!(fleet_pythagorean48_encode(0.0, 1.0), 12);
375 }
376
377 #[test]
378 fn test_pythag48_negative_x() {
379 assert_eq!(fleet_pythagorean48_encode(-1.0, 0.0), 24);
381 }
382
383 #[test]
384 fn test_pythag48_negative_y() {
385 assert_eq!(fleet_pythagorean48_encode(0.0, -1.0), 36);
387 }
388
389 #[test]
390 fn test_pythag48_45deg() {
391 assert_eq!(fleet_pythagorean48_encode(1.0, 1.0), 6);
393 }
394}