1use super::types::{
6 IcpRegistration, KdTree3D, NormalEstimation, PointCloud, PointCloudFilter, RansacPlaneResult,
7};
8
9pub(super) fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
10 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
11}
12pub(super) fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
13 [
14 a[1] * b[2] - a[2] * b[1],
15 a[2] * b[0] - a[0] * b[2],
16 a[0] * b[1] - a[1] * b[0],
17 ]
18}
19pub(super) fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
20 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
21}
22pub(super) fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
23 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
24}
25pub(super) fn scale(a: [f64; 3], s: f64) -> [f64; 3] {
26 [a[0] * s, a[1] * s, a[2] * s]
27}
28pub(super) fn length(a: [f64; 3]) -> f64 {
29 dot(a, a).sqrt()
30}
31pub(super) fn normalize(a: [f64; 3]) -> [f64; 3] {
32 let len = length(a);
33 if len < 1e-15 {
34 [0.0, 0.0, 1.0]
35 } else {
36 scale(a, 1.0 / len)
37 }
38}
39pub(super) fn dist2(a: [f64; 3], b: [f64; 3]) -> f64 {
40 let d = sub(a, b);
41 dot(d, d)
42}
43pub(super) fn mat_vec_3(m: &[[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
44 [
45 m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
46 m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
47 m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
48 ]
49}
50pub(super) fn identity_3x3() -> [[f64; 3]; 3] {
51 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
52}
53pub(super) fn mat_transpose_3(m: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
54 let mut t = [[0.0f64; 3]; 3];
55 for i in 0..3 {
56 for j in 0..3 {
57 t[i][j] = m[j][i];
58 }
59 }
60 t
61}
62pub(super) fn mat_mul_3(a: &[[f64; 3]; 3], b: &[[f64; 3]; 3]) -> [[f64; 3]; 3] {
63 let mut c = [[0.0f64; 3]; 3];
64 for i in 0..3 {
65 for j in 0..3 {
66 for k in 0..3 {
67 c[i][j] += a[i][k] * b[k][j];
68 }
69 }
70 }
71 c
72}
73pub(super) fn mat_det_3(m: &[[f64; 3]; 3]) -> f64 {
74 m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
75 - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
76 + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
77}
78pub(super) fn mean_points(pts: &[[f64; 3]]) -> [f64; 3] {
79 if pts.is_empty() {
80 return [0.0; 3];
81 }
82 let n = pts.len() as f64;
83 let mut s = [0.0f64; 3];
84 for &p in pts {
85 for i in 0..3 {
86 s[i] += p[i];
87 }
88 }
89 [s[0] / n, s[1] / n, s[2] / n]
90}
91pub(super) fn svd_3x3_sym_jacobi(a: [[f64; 3]; 3]) -> ([[f64; 3]; 3], [f64; 3], [[f64; 3]; 3]) {
94 let ata = mat_mul_3(&mat_transpose_3(&a), &a);
95 let (v, eigenvalues) = jacobi_eigen_3x3(ata);
96 let s = [
97 eigenvalues[0].max(0.0).sqrt(),
98 eigenvalues[1].max(0.0).sqrt(),
99 eigenvalues[2].max(0.0).sqrt(),
100 ];
101 let mut u = [[0.0f64; 3]; 3];
102 for j in 0..3 {
103 let vj = [v[0][j], v[1][j], v[2][j]];
104 let avj = mat_vec_3(&a, vj);
105 let col = if s[j] > 1e-12 {
106 scale(avj, 1.0 / s[j])
107 } else {
108 gram_schmidt_fallback(&u, j)
109 };
110 u[0][j] = col[0];
111 u[1][j] = col[1];
112 u[2][j] = col[2];
113 }
114 (u, s, v)
115}
116pub(super) fn gram_schmidt_fallback(u: &[[f64; 3]; 3], col: usize) -> [f64; 3] {
117 let candidates: [[f64; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
118 for &cand in &candidates {
119 let mut v = cand;
120 for (j, _) in u[0].iter().enumerate().take(col) {
121 let uj = [u[0][j], u[1][j], u[2][j]];
122 let proj = dot(v, uj);
123 v = sub(v, scale(uj, proj));
124 }
125 let len = length(v);
126 if len > 1e-12 {
127 return scale(v, 1.0 / len);
128 }
129 }
130 [0.0, 0.0, 1.0]
131}
132pub(super) fn jacobi_eigen_3x3(mut a: [[f64; 3]; 3]) -> ([[f64; 3]; 3], [f64; 3]) {
135 let mut v = identity_3x3();
136 for _ in 0..50 {
137 let mut max_val = 0.0f64;
138 let mut p = 0;
139 let mut q = 1;
140 for (i, a_row) in a.iter().enumerate() {
141 for (j, a_ij) in a_row.iter().enumerate().skip(i + 1) {
142 if a_ij.abs() > max_val {
143 max_val = a_ij.abs();
144 p = i;
145 q = j;
146 }
147 }
148 }
149 if max_val < 1e-12 {
150 break;
151 }
152 let theta = 0.5 * (a[q][q] - a[p][p]) / a[p][q];
153 let t = if theta >= 0.0 {
154 1.0 / (theta + (1.0 + theta * theta).sqrt())
155 } else {
156 -1.0 / (-theta + (1.0 + theta * theta).sqrt())
157 };
158 let c = 1.0 / (1.0 + t * t).sqrt();
159 let s = t * c;
160 let app = a[p][p];
161 let aqq = a[q][q];
162 let apq = a[p][q];
163 a[p][p] = c * c * app - 2.0 * s * c * apq + s * s * aqq;
164 a[q][q] = s * s * app + 2.0 * s * c * apq + c * c * aqq;
165 a[p][q] = 0.0;
166 a[q][p] = 0.0;
167 for (r, a_row) in a.iter_mut().enumerate() {
168 if r != p && r != q {
169 let arp = a_row[p];
170 let arq = a_row[q];
171 a_row[p] = c * arp - s * arq;
172 a_row[q] = s * arp + c * arq;
173 }
174 }
175 let sym_updates: Vec<(usize, f64, f64)> = (0..3)
177 .filter(|&r| r != p && r != q)
178 .map(|r| (r, a[r][p], a[r][q]))
179 .collect();
180 for (r, arp, arq) in sym_updates {
181 a[p][r] = arp;
182 a[q][r] = arq;
183 }
184 for (r, v_row) in v.iter_mut().enumerate() {
185 let _ = r;
186 let vrp = v_row[p];
187 let vrq = v_row[q];
188 v_row[p] = c * vrp - s * vrq;
189 v_row[q] = s * vrp + c * vrq;
190 }
191 }
192 let eigenvalues = [a[0][0], a[1][1], a[2][2]];
193 (v, eigenvalues)
194}
195pub(super) fn jacobi_eigenvalues_3x3(a: [[f64; 3]; 3]) -> [f64; 3] {
197 let (_vecs, vals) = jacobi_eigen_3x3(a);
198 vals
199}
200pub fn estimate_normals(cloud: &PointCloud, k_neighbors: usize) -> Vec<[f64; 3]> {
204 NormalEstimation::new(k_neighbors).estimate(cloud)
205}
206pub fn voxel_downsample(cloud: &PointCloud, voxel_size: f64) -> PointCloud {
210 PointCloudFilter::voxel_downsample(cloud, voxel_size)
211}
212pub fn statistical_outlier_removal(cloud: &PointCloud, k: usize, std_ratio: f64) -> PointCloud {
218 PointCloudFilter::statistical_outlier_removal(cloud, k, std_ratio)
219}
220pub fn compute_bounding_box(cloud: &PointCloud) -> ([f64; 3], [f64; 3]) {
225 cloud.bounding_box()
226}
227pub fn icp_align(source: &PointCloud, target: &PointCloud, max_iter: usize) -> ([f64; 16], f64) {
233 let icp = IcpRegistration::new(max_iter, 1e-8);
234 let result = icp.run(source, target);
235 let r = result.rotation;
236 let t = result.translation;
237 let m = [
238 r[0][0], r[0][1], r[0][2], t[0], r[1][0], r[1][1], r[1][2], t[1], r[2][0], r[2][1],
239 r[2][2], t[2], 0.0, 0.0, 0.0, 1.0,
240 ];
241 (m, result.final_error)
242}
243pub fn icp_point_to_point(
247 source: &[[f64; 3]],
248 target: &[[f64; 3]],
249 max_iter: usize,
250) -> [[f64; 3]; 4] {
251 let src_cloud = PointCloud::from_points(source.to_vec());
252 let tgt_cloud = PointCloud::from_points(target.to_vec());
253 let icp = IcpRegistration::new(max_iter, 1e-8);
254 let result = icp.run(&src_cloud, &tgt_cloud);
255 let r = result.rotation;
256 [r[0], r[1], r[2], result.translation]
257}
258pub fn compute_point_cloud_normals(points: &[[f64; 3]], k: usize) -> Vec<[f64; 3]> {
260 let cloud = PointCloud::from_points(points.to_vec());
261 NormalEstimation::new(k).estimate(&cloud)
262}
263pub fn fpfh_feature(points: &[[f64; 3]], normals: &[[f64; 3]], idx: usize, r: f64) -> Vec<f64> {
268 let tree = KdTree3D::build(points);
269 let neighbors = tree.range_search(points[idx], r);
270 let n_bins = 33usize;
271 let mut hist = vec![0.0f64; n_bins];
272 let ni = normals[idx];
273 let pi = points[idx];
274 for &j in &neighbors {
275 if j == idx {
276 continue;
277 }
278 let nj = normals[j];
279 let pj = points[j];
280 let d = sub(pj, pi);
281 let d_len = length(d);
282 if d_len < 1e-12 {
283 continue;
284 }
285 let cos_alpha = dot(ni, nj).clamp(-1.0, 1.0);
286 let d_norm = scale(d, 1.0 / d_len);
287 let cos_phi = dot(ni, d_norm).clamp(-1.0, 1.0);
288 let bin_a = ((cos_alpha + 1.0) / 2.0 * (n_bins / 3) as f64) as usize;
289 let bin_b = ((cos_phi + 1.0) / 2.0 * (n_bins / 3) as f64) as usize;
290 let bin_c = (dot(nj, d_norm).clamp(-1.0, 1.0) + 1.0) / 2.0 * (n_bins / 3) as f64;
291 let bin_c = bin_c as usize;
292 hist[(bin_a).min(n_bins / 3 - 1)] += 1.0;
293 hist[(n_bins / 3 + bin_b).min(2 * n_bins / 3 - 1)] += 1.0;
294 hist[(2 * n_bins / 3 + bin_c).min(n_bins - 1)] += 1.0;
295 }
296 let total: f64 = hist.iter().sum();
297 if total > 0.0 {
298 for v in &mut hist {
299 *v /= total;
300 }
301 }
302 hist
303}
304pub fn farthest_point_sampling(points: &[[f64; 3]], k: usize, seed_idx: usize) -> Vec<usize> {
312 let n = points.len();
313 if n == 0 || k == 0 {
314 return Vec::new();
315 }
316 let k = k.min(n);
317 let mut selected = Vec::with_capacity(k);
318 let mut min_dist = vec![f64::MAX; n];
319 let seed = seed_idx % n;
320 selected.push(seed);
321 for i in 0..n {
322 min_dist[i] = dist2_pts(points[i], points[seed]);
323 }
324 for _ in 1..k {
325 let next = min_dist
326 .iter()
327 .enumerate()
328 .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
329 .map(|(i, _)| i)
330 .unwrap_or(0);
331 selected.push(next);
332 for i in 0..n {
333 let d = dist2_pts(points[i], points[next]);
334 if d < min_dist[i] {
335 min_dist[i] = d;
336 }
337 }
338 }
339 selected
340}
341pub(super) fn dist2_pts(a: [f64; 3], b: [f64; 3]) -> f64 {
342 let d = sub(a, b);
343 dot(d, d)
344}
345pub fn ransac_fit_plane(
357 points: &[[f64; 3]],
358 n_iter: usize,
359 threshold: f64,
360) -> Option<RansacPlaneResult> {
361 let n = points.len();
362 if n < 3 {
363 return None;
364 }
365 let mut best_inliers: Vec<usize> = Vec::new();
366 let mut best_normal = [0.0f64; 3];
367 let mut best_d = 0.0_f64;
368 let step1 = 1usize;
369 let step2 = n / 3 + 1;
370 let step3 = 2 * n / 3 + 1;
371 for iter in 0..n_iter {
372 let i0 = (iter * step1) % n;
373 let i1 = (iter * step2 + 1) % n;
374 let i2 = (iter * step3 + 2) % n;
375 if i0 == i1 || i1 == i2 || i0 == i2 {
376 continue;
377 }
378 let p0 = points[i0];
379 let p1 = points[i1];
380 let p2 = points[i2];
381 let e1 = sub(p1, p0);
382 let e2 = sub(p2, p0);
383 let normal_raw = cross(e1, e2);
384 let normal = normalize(normal_raw);
385 if length(normal) < 0.5 {
386 continue;
387 }
388 let d = dot(normal, p0);
389 let inliers: Vec<usize> = points
390 .iter()
391 .enumerate()
392 .filter(|(_, p)| (dot(normal, **p) - d).abs() <= threshold)
393 .map(|(i, _)| i)
394 .collect();
395 if inliers.len() > best_inliers.len() {
396 best_inliers = inliers;
397 best_normal = normal;
398 best_d = d;
399 }
400 }
401 if best_inliers.is_empty() {
402 return None;
403 }
404 let n_in = best_inliers.len() as f64;
405 let mut centroid = [0.0f64; 3];
406 for &i in &best_inliers {
407 for (c, p) in centroid.iter_mut().zip(points[i].iter()) {
408 *c += p;
409 }
410 }
411 for c in centroid.iter_mut() {
412 *c /= n_in;
413 }
414 let n_inliers = best_inliers.len();
415 let point_on_plane = sub(
416 centroid,
417 scale(best_normal, dot(best_normal, centroid) - best_d),
418 );
419 Some(RansacPlaneResult {
420 normal: best_normal,
421 point_on_plane,
422 inliers: best_inliers,
423 n_inliers,
424 })
425}
426pub fn aabb_extent(points: &[[f64; 3]]) -> ([f64; 3], [f64; 3]) {
430 if points.is_empty() {
431 return ([0.0; 3], [0.0; 3]);
432 }
433 let mut mn = points[0];
434 let mut mx = points[0];
435 for &p in points.iter().skip(1) {
436 for i in 0..3 {
437 if p[i] < mn[i] {
438 mn[i] = p[i];
439 }
440 if p[i] > mx[i] {
441 mx[i] = p[i];
442 }
443 }
444 }
445 let dims = [mx[0] - mn[0], mx[1] - mn[1], mx[2] - mn[2]];
446 let center = [
447 (mn[0] + mx[0]) * 0.5,
448 (mn[1] + mx[1]) * 0.5,
449 (mn[2] + mx[2]) * 0.5,
450 ];
451 (dims, center)
452}
453pub fn pca_obb(points: &[[f64; 3]]) -> ([[f64; 3]; 3], [f64; 3], [f64; 3]) {
458 if points.is_empty() {
459 return (
460 [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
461 [0.0; 3],
462 [0.0; 3],
463 );
464 }
465 let n = points.len() as f64;
466 let mut centroid = [0.0f64; 3];
467 for &p in points {
468 for (c, p_i) in centroid.iter_mut().zip(p.iter()) {
469 *c += p_i;
470 }
471 }
472 for c in centroid.iter_mut() {
473 *c /= n;
474 }
475 let mut cov = [[0.0f64; 3]; 3];
476 for &p in points {
477 let d = sub(p, centroid);
478 for i in 0..3 {
479 for j in 0..3 {
480 cov[i][j] += d[i] * d[j];
481 }
482 }
483 }
484 let axis0 = power_iteration_largest(&cov);
485 let deflated = deflate_matrix(&cov, &axis0);
486 let axis1 = power_iteration_largest(&deflated);
487 let axis2 = normalize(cross(axis0, axis1));
488 let axes = [axis0, axis1, axis2];
489 let mut min_proj = [f64::MAX; 3];
490 let mut max_proj = [f64::MIN; 3];
491 for &p in points {
492 let d = sub(p, centroid);
493 for k in 0..3 {
494 let proj = dot(d, axes[k]);
495 if proj < min_proj[k] {
496 min_proj[k] = proj;
497 }
498 if proj > max_proj[k] {
499 max_proj[k] = proj;
500 }
501 }
502 }
503 let half_extents = [
504 (max_proj[0] - min_proj[0]) * 0.5,
505 (max_proj[1] - min_proj[1]) * 0.5,
506 (max_proj[2] - min_proj[2]) * 0.5,
507 ];
508 (axes, half_extents, centroid)
509}
510pub(super) fn power_iteration_largest(m: &[[f64; 3]; 3]) -> [f64; 3] {
511 let mut v = [1.0f64, 0.0, 0.0];
512 for _ in 0..30 {
513 let mv = mat_vec_3_pca(m, v);
514 let len = length(mv);
515 if len < 1e-15 {
516 break;
517 }
518 v = scale(mv, 1.0 / len);
519 }
520 normalize(v)
521}
522pub(super) fn deflate_matrix(m: &[[f64; 3]; 3], axis: &[f64; 3]) -> [[f64; 3]; 3] {
523 let mv = mat_vec_3_pca(m, *axis);
524 let lambda = dot(mv, *axis);
525 let mut result = *m;
526 for i in 0..3 {
527 for j in 0..3 {
528 result[i][j] -= lambda * axis[i] * axis[j];
529 }
530 }
531 result
532}
533pub(super) fn mat_vec_3_pca(m: &[[f64; 3]; 3], v: [f64; 3]) -> [f64; 3] {
534 [
535 m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
536 m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
537 m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
538 ]
539}