1use super::functions::*;
6use std::collections::HashMap;
7
8pub struct PointCloudFilter;
10impl PointCloudFilter {
11 pub fn voxel_downsample(cloud: &PointCloud, voxel_size: f64) -> PointCloud {
13 if cloud.points.is_empty() || voxel_size <= 0.0 {
14 return PointCloud::new();
15 }
16 let inv = 1.0 / voxel_size;
17 let mut voxels: HashMap<(i64, i64, i64), Vec<usize>> = HashMap::new();
18 for (i, &p) in cloud.points.iter().enumerate() {
19 let key = (
20 (p[0] * inv).floor() as i64,
21 (p[1] * inv).floor() as i64,
22 (p[2] * inv).floor() as i64,
23 );
24 voxels.entry(key).or_default().push(i);
25 }
26 let mut out = PointCloud::new();
27 for indices in voxels.values() {
28 let n = indices.len() as f64;
29 let mut sum = [0.0f64; 3];
30 for &i in indices {
31 let p = cloud.points[i];
32 for k in 0..3 {
33 sum[k] += p[k];
34 }
35 }
36 out.add_point([sum[0] / n, sum[1] / n, sum[2] / n]);
37 }
38 out
39 }
40 pub fn statistical_outlier_removal(
43 cloud: &PointCloud,
44 k: usize,
45 std_factor: f64,
46 ) -> PointCloud {
47 if cloud.points.is_empty() {
48 return PointCloud::new();
49 }
50 let tree = KdTree3D::build(&cloud.points);
51 let mean_dists: Vec<f64> = cloud
52 .points
53 .iter()
54 .map(|&p| {
55 let neighbors = tree.k_nearest(p, k + 1);
56 let dists: Vec<f64> = neighbors
57 .iter()
58 .filter(|&&(idx, _)| cloud.points[idx] != p || idx != 0)
59 .map(|&(_, d)| d.sqrt())
60 .collect();
61 if dists.is_empty() {
62 0.0
63 } else {
64 dists.iter().sum::<f64>() / dists.len() as f64
65 }
66 })
67 .collect();
68 let mean = mean_dists.iter().sum::<f64>() / mean_dists.len() as f64;
69 let variance = mean_dists
70 .iter()
71 .map(|&d| (d - mean) * (d - mean))
72 .sum::<f64>()
73 / mean_dists.len() as f64;
74 let std = variance.sqrt();
75 let threshold = mean + std_factor * std;
76 let mut out = PointCloud::new();
77 for (i, &md) in mean_dists.iter().enumerate() {
78 if md <= threshold {
79 out.add_point(cloud.points[i]);
80 if !cloud.normals.is_empty() {
81 out.normals.push(cloud.normals[i]);
82 }
83 if !cloud.colors.is_empty() {
84 out.colors.push(cloud.colors[i]);
85 }
86 }
87 }
88 out
89 }
90 pub fn radius_outlier_removal(
92 cloud: &PointCloud,
93 radius: f64,
94 min_neighbors: usize,
95 ) -> PointCloud {
96 if cloud.points.is_empty() {
97 return PointCloud::new();
98 }
99 let tree = KdTree3D::build(&cloud.points);
100 let mut out = PointCloud::new();
101 for (i, &p) in cloud.points.iter().enumerate() {
102 let count = tree.range_search(p, radius).len();
103 if count.saturating_sub(1) >= min_neighbors {
104 out.add_point(p);
105 if !cloud.normals.is_empty() {
106 out.normals.push(cloud.normals[i]);
107 }
108 if !cloud.colors.is_empty() {
109 out.colors.push(cloud.colors[i]);
110 }
111 }
112 }
113 out
114 }
115}
116pub struct KdTree3D {
118 pub(super) points: Vec<[f64; 3]>,
119 pub(super) root: Option<Box<KdNode3D>>,
120}
121impl KdTree3D {
122 pub fn build(points: &[[f64; 3]]) -> Self {
124 let mut indices: Vec<usize> = (0..points.len()).collect();
125 let root = Self::build_recursive(points, &mut indices, 0);
126 Self {
127 points: points.to_vec(),
128 root,
129 }
130 }
131 fn build_recursive(
132 points: &[[f64; 3]],
133 indices: &mut [usize],
134 depth: usize,
135 ) -> Option<Box<KdNode3D>> {
136 if indices.is_empty() {
137 return None;
138 }
139 let axis = depth % 3;
140 indices.sort_unstable_by(|&a, &b| {
141 points[a][axis]
142 .partial_cmp(&points[b][axis])
143 .unwrap_or(std::cmp::Ordering::Equal)
144 });
145 let mid = indices.len() / 2;
146 let idx = indices[mid];
147 let left = Self::build_recursive(points, &mut indices[..mid], depth + 1);
148 let right = Self::build_recursive(points, &mut indices[mid + 1..], depth + 1);
149 Some(Box::new(KdNode3D {
150 point_idx: idx,
151 split_axis: axis,
152 left,
153 right,
154 }))
155 }
156 pub fn nearest_neighbor(&self, query: [f64; 3]) -> (usize, f64) {
158 let mut best = (0, f64::MAX);
159 if let Some(ref root) = self.root {
160 Self::nn_search(&self.points, root, query, &mut best);
161 }
162 best
163 }
164 fn nn_search(points: &[[f64; 3]], node: &KdNode3D, query: [f64; 3], best: &mut (usize, f64)) {
165 let p = points[node.point_idx];
166 let d2 = dist2(p, query);
167 if d2 < best.1 {
168 *best = (node.point_idx, d2);
169 }
170 let axis = node.split_axis;
171 let diff = query[axis] - p[axis];
172 let (near, far) = if diff <= 0.0 {
173 (node.left.as_deref(), node.right.as_deref())
174 } else {
175 (node.right.as_deref(), node.left.as_deref())
176 };
177 if let Some(near_node) = near {
178 Self::nn_search(points, near_node, query, best);
179 }
180 if diff * diff < best.1
181 && let Some(far_node) = far
182 {
183 Self::nn_search(points, far_node, query, best);
184 }
185 }
186 pub fn k_nearest(&self, query: [f64; 3], k: usize) -> Vec<(usize, f64)> {
188 if k == 0 || self.points.is_empty() {
189 return Vec::new();
190 }
191 let mut heap: Vec<(usize, f64)> = Vec::with_capacity(k + 1);
192 if let Some(ref root) = self.root {
193 Self::knn_search(&self.points, root, query, k, &mut heap);
194 }
195 heap.sort_unstable_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
196 heap
197 }
198 fn knn_search(
199 points: &[[f64; 3]],
200 node: &KdNode3D,
201 query: [f64; 3],
202 k: usize,
203 heap: &mut Vec<(usize, f64)>,
204 ) {
205 let p = points[node.point_idx];
206 let d2 = dist2(p, query);
207 heap.push((node.point_idx, d2));
208 if heap.len() > k {
209 let worst_pos = heap
210 .iter()
211 .enumerate()
212 .max_by(|a, b| {
213 a.1.1
214 .partial_cmp(&b.1.1)
215 .unwrap_or(std::cmp::Ordering::Equal)
216 })
217 .map(|(i, _)| i)
218 .unwrap_or(0);
219 heap.swap_remove(worst_pos);
220 }
221 let worst_dist2 = heap.iter().map(|e| e.1).fold(0.0_f64, f64::max);
222 let axis = node.split_axis;
223 let diff = query[axis] - p[axis];
224 let (near, far) = if diff <= 0.0 {
225 (node.left.as_deref(), node.right.as_deref())
226 } else {
227 (node.right.as_deref(), node.left.as_deref())
228 };
229 if let Some(near_node) = near {
230 Self::knn_search(points, near_node, query, k, heap);
231 }
232 let current_worst = heap.iter().map(|e| e.1).fold(0.0_f64, f64::max);
233 if (heap.len() < k || diff * diff < current_worst)
234 && let Some(far_node) = far
235 {
236 Self::knn_search(points, far_node, query, k, heap);
237 }
238 let _ = worst_dist2;
239 }
240 pub fn range_search(&self, center: [f64; 3], radius: f64) -> Vec<usize> {
242 let mut result = Vec::new();
243 let r2 = radius * radius;
244 if let Some(ref root) = self.root {
245 Self::range_recursive(&self.points, root, center, r2, &mut result);
246 }
247 result
248 }
249 fn range_recursive(
250 points: &[[f64; 3]],
251 node: &KdNode3D,
252 center: [f64; 3],
253 r2: f64,
254 result: &mut Vec<usize>,
255 ) {
256 let p = points[node.point_idx];
257 if dist2(p, center) <= r2 {
258 result.push(node.point_idx);
259 }
260 let axis = node.split_axis;
261 let diff = center[axis] - p[axis];
262 if (diff * diff <= r2 || diff <= 0.0)
263 && let Some(ref left) = node.left
264 {
265 Self::range_recursive(points, left, center, r2, result);
266 }
267 if (diff * diff <= r2 || diff > 0.0)
268 && let Some(ref right) = node.right
269 {
270 Self::range_recursive(points, right, center, r2, result);
271 }
272 }
273}
274pub struct KdNode3D {
276 pub point_idx: usize,
278 pub split_axis: usize,
280 pub left: Option<Box<KdNode3D>>,
282 pub right: Option<Box<KdNode3D>>,
284}
285pub struct NormalEstimation {
287 pub k_neighbors: usize,
289}
290impl NormalEstimation {
291 pub fn new(k_neighbors: usize) -> Self {
293 Self { k_neighbors }
294 }
295 pub fn estimate(&self, cloud: &PointCloud) -> Vec<[f64; 3]> {
297 let tree = KdTree3D::build(&cloud.points);
298 cloud
299 .points
300 .iter()
301 .map(|&p| {
302 let neighbors = tree.k_nearest(p, self.k_neighbors);
303 let pts: Vec<[f64; 3]> = neighbors
304 .iter()
305 .map(|&(idx, _)| cloud.points[idx])
306 .collect();
307 if pts.len() < 3 {
308 return [0.0, 0.0, 1.0];
309 }
310 let cov = Self::covariance_matrix(&pts);
311 Self::smallest_eigenvector_3x3_sym(&cov)
312 })
313 .collect()
314 }
315 pub fn covariance_matrix(pts: &[[f64; 3]]) -> [[f64; 3]; 3] {
317 if pts.is_empty() {
318 return [[0.0; 3]; 3];
319 }
320 let n = pts.len() as f64;
321 let mut centroid = [0.0f64; 3];
322 for &p in pts {
323 for (c, p_i) in centroid.iter_mut().zip(p.iter()) {
324 *c += p_i;
325 }
326 }
327 for c in centroid.iter_mut() {
328 *c /= n;
329 }
330 let mut cov = [[0.0f64; 3]; 3];
331 for &p in pts {
332 let d = sub(p, centroid);
333 for (i, cov_row) in cov.iter_mut().enumerate() {
334 for (j, cov_ij) in cov_row.iter_mut().enumerate() {
335 *cov_ij += d[i] * d[j];
336 }
337 }
338 }
339 cov
340 }
341 pub fn smallest_eigenvector_3x3_sym(m: &[[f64; 3]; 3]) -> [f64; 3] {
344 let mut v = [1.0f64, 1.0, 1.0];
345 for _ in 0..20 {
346 v = mat_vec_3(m, v);
347 let len = length(v);
348 if len < 1e-15 {
349 break;
350 }
351 v = scale(v, 1.0 / len);
352 }
353 let lambda_max = dot(mat_vec_3(m, v), v);
354 let shifted = [
355 [lambda_max - m[0][0], -m[0][1], -m[0][2]],
356 [-m[1][0], lambda_max - m[1][1], -m[1][2]],
357 [-m[2][0], -m[2][1], lambda_max - m[2][2]],
358 ];
359 let mut u = [1.0f64, 0.0, 0.0];
360 for _ in 0..50 {
361 u = mat_vec_3(&shifted, u);
362 let len = length(u);
363 if len < 1e-15 {
364 break;
365 }
366 u = scale(u, 1.0 / len);
367 }
368 normalize(u)
369 }
370}
371pub struct RansacPlaneResult {
373 pub normal: [f64; 3],
375 pub point_on_plane: [f64; 3],
377 pub inliers: Vec<usize>,
379 pub n_inliers: usize,
381}
382pub struct IcpResult {
384 pub rotation: [[f64; 3]; 3],
386 pub translation: [f64; 3],
388 pub final_error: f64,
390 pub iterations: usize,
392}
393impl IcpResult {
394 pub fn apply_to(&self, cloud: &PointCloud) -> PointCloud {
396 let mut out = PointCloud::new();
397 for &p in &cloud.points {
398 let rp = mat_vec_3(&self.rotation, p);
399 out.add_point(add(rp, self.translation));
400 }
401 if !cloud.normals.is_empty() {
402 for &n in &cloud.normals {
403 out.normals.push(mat_vec_3(&self.rotation, n));
404 }
405 }
406 out.colors = cloud.colors.clone();
407 out
408 }
409}
410pub struct PointCloud {
412 pub points: Vec<[f64; 3]>,
414 pub normals: Vec<[f64; 3]>,
416 pub colors: Vec<[f32; 3]>,
418}
419impl PointCloud {
420 pub fn new() -> Self {
422 Self {
423 points: Vec::new(),
424 normals: Vec::new(),
425 colors: Vec::new(),
426 }
427 }
428 pub fn add_point(&mut self, p: [f64; 3]) {
430 self.points.push(p);
431 }
432 pub fn len(&self) -> usize {
434 self.points.len()
435 }
436 pub fn is_empty(&self) -> bool {
438 self.points.is_empty()
439 }
440 pub fn bounding_box(&self) -> ([f64; 3], [f64; 3]) {
442 if self.points.is_empty() {
443 return ([0.0; 3], [0.0; 3]);
444 }
445 let mut mn = self.points[0];
446 let mut mx = self.points[0];
447 for &p in &self.points {
448 for i in 0..3 {
449 if p[i] < mn[i] {
450 mn[i] = p[i];
451 }
452 if p[i] > mx[i] {
453 mx[i] = p[i];
454 }
455 }
456 }
457 (mn, mx)
458 }
459 pub fn centroid(&self) -> [f64; 3] {
461 if self.points.is_empty() {
462 return [0.0; 3];
463 }
464 let n = self.points.len() as f64;
465 let mut sum = [0.0f64; 3];
466 for &p in &self.points {
467 for i in 0..3 {
468 sum[i] += p[i];
469 }
470 }
471 [sum[0] / n, sum[1] / n, sum[2] / n]
472 }
473 pub fn translate(&mut self, offset: [f64; 3]) {
475 for p in &mut self.points {
476 *p = add(*p, offset);
477 }
478 }
479 pub fn scale_uniform(&mut self, s: f64) {
481 for p in &mut self.points {
482 *p = scale(*p, s);
483 }
484 }
485 pub fn from_grid(nx: usize, ny: usize, height_fn: impl Fn(f64, f64) -> f64, dx: f64) -> Self {
489 let mut cloud = Self::new();
490 for i in 0..nx {
491 for j in 0..ny {
492 let x = i as f64 * dx;
493 let y = j as f64 * dx;
494 let z = height_fn(x, y);
495 cloud.add_point([x, y, z]);
496 }
497 }
498 cloud
499 }
500}
501impl PointCloud {
502 pub fn from_points(points: Vec<[f64; 3]>) -> Self {
504 Self {
505 points,
506 normals: Vec::new(),
507 colors: Vec::new(),
508 }
509 }
510 pub fn estimate_normals_pca(&mut self, k: usize) {
513 let tree = KdTree3D::build(&self.points);
514 self.normals = self
515 .points
516 .iter()
517 .map(|&p| {
518 let neighbors = tree.k_nearest(p, k);
519 let pts: Vec<[f64; 3]> =
520 neighbors.iter().map(|&(idx, _)| self.points[idx]).collect();
521 if pts.len() < 3 {
522 return [0.0, 0.0, 1.0];
523 }
524 let cov = NormalEstimation::covariance_matrix(&pts);
525 NormalEstimation::smallest_eigenvector_3x3_sym(&cov)
526 })
527 .collect();
528 }
529 pub fn voxel_downsample(&self, voxel_size: f64) -> Self {
531 PointCloudFilter::voxel_downsample(self, voxel_size)
532 }
533 pub fn statistical_outlier_removal(&self, k: usize, std_ratio: f64) -> Self {
535 PointCloudFilter::statistical_outlier_removal(self, k, std_ratio)
536 }
537}
538impl PointCloud {
539 pub fn farthest_point_sample(&self, k: usize) -> Self {
544 let indices = farthest_point_sampling(&self.points, k, 0);
545 let mut out = PointCloud::new();
546 for &i in &indices {
547 out.add_point(self.points[i]);
548 if !self.normals.is_empty() {
549 out.normals.push(self.normals[i]);
550 }
551 if !self.colors.is_empty() {
552 out.colors.push(self.colors[i]);
553 }
554 }
555 out
556 }
557 pub fn fit_plane_ransac(&self, n_iter: usize, threshold: f64) -> Option<RansacPlaneResult> {
561 ransac_fit_plane(&self.points, n_iter, threshold)
562 }
563 pub fn pca_obb(&self) -> ([[f64; 3]; 3], [f64; 3], [f64; 3]) {
567 pca_obb(&self.points)
568 }
569 pub fn compute_principal_curvatures(&self, k: usize) -> Vec<(f64, f64)> {
577 if self.points.is_empty() {
578 return Vec::new();
579 }
580 let tree = KdTree3D::build(&self.points);
581 self.points
582 .iter()
583 .map(|&p| {
584 let neighbors = tree.k_nearest(p, k + 1);
585 let pts: Vec<[f64; 3]> =
586 neighbors.iter().map(|&(idx, _)| self.points[idx]).collect();
587 if pts.len() < 3 {
588 return (0.0, 0.0);
589 }
590 let n = pts.len() as f64;
591 let mut centroid = [0.0f64; 3];
592 for &q in &pts {
593 for (c, q_i) in centroid.iter_mut().zip(q.iter()) {
594 *c += q_i;
595 }
596 }
597 for c in centroid.iter_mut() {
598 *c /= n;
599 }
600 let mut cov = [[0.0f64; 3]; 3];
601 for &q in &pts {
602 let d = sub(q, centroid);
603 for (i, cov_row) in cov.iter_mut().enumerate() {
604 for (j, cov_ij) in cov_row.iter_mut().enumerate() {
605 *cov_ij += d[i] * d[j];
606 }
607 }
608 }
609 for cov_row in cov.iter_mut() {
610 for cov_ij in cov_row.iter_mut() {
611 *cov_ij /= n;
612 }
613 }
614 let eigenvalues = jacobi_eigenvalues_3x3(cov);
615 let mut ev = eigenvalues;
616 ev.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
617 (ev[0], ev[1])
618 })
619 .collect()
620 }
621 pub fn icp_register(&self, target: &PointCloud) -> IcpResult {
626 IcpRegistration::align(self, target)
627 }
628}
629pub struct IcpRegistration {
631 pub max_iterations: usize,
633 pub convergence_tol: f64,
635}
636impl IcpRegistration {
637 pub fn new(max_iterations: usize, convergence_tol: f64) -> Self {
639 Self {
640 max_iterations,
641 convergence_tol,
642 }
643 }
644 pub fn align(source: &PointCloud, target: &PointCloud) -> IcpResult {
646 let icp = Self::new(50, 1e-6);
647 icp.run(source, target)
648 }
649 pub fn run(&self, source: &PointCloud, target: &PointCloud) -> IcpResult {
651 let mut rot = identity_3x3();
652 let mut trans = [0.0f64; 3];
653 let mut current: Vec<[f64; 3]> = source.points.clone();
654 let target_tree = KdTree3D::build(&target.points);
655 let mut prev_error = f64::MAX;
656 let mut iters = 0;
657 for iter in 0..self.max_iterations {
658 iters = iter + 1;
659 let pairs: Vec<(usize, usize)> = current
660 .iter()
661 .enumerate()
662 .map(|(i, &p)| {
663 let (ti, _) = target_tree.nearest_neighbor(p);
664 (i, ti)
665 })
666 .collect();
667 let error: f64 = pairs
668 .iter()
669 .map(|&(si, ti)| dist2(current[si], target.points[ti]))
670 .sum::<f64>()
671 / pairs.len() as f64;
672 if (prev_error - error).abs() < self.convergence_tol {
673 break;
674 }
675 prev_error = error;
676 let src_centroid =
677 mean_points(&pairs.iter().map(|&(si, _)| current[si]).collect::<Vec<_>>());
678 let tgt_centroid = mean_points(
679 &pairs
680 .iter()
681 .map(|&(_, ti)| target.points[ti])
682 .collect::<Vec<_>>(),
683 );
684 let mut h = [[0.0f64; 3]; 3];
685 for &(si, ti) in &pairs {
686 let s = sub(current[si], src_centroid);
687 let t = sub(target.points[ti], tgt_centroid);
688 for i in 0..3 {
689 for j in 0..3 {
690 h[i][j] += s[i] * t[j];
691 }
692 }
693 }
694 let (u, _s_vals, v) = svd_3x3_sym_jacobi(h);
695 let det = mat_det_3(&mat_mul_3(&v, &mat_transpose_3(&u)));
696 let mut d = identity_3x3();
697 if det < 0.0 {
698 d[2][2] = -1.0;
699 }
700 let r_step = mat_mul_3(&v, &mat_mul_3(&d, &mat_transpose_3(&u)));
701 let t_step = sub(tgt_centroid, mat_vec_3(&r_step, src_centroid));
702 for p in &mut current {
703 *p = add(mat_vec_3(&r_step, *p), t_step);
704 }
705 rot = mat_mul_3(&r_step, &rot);
706 trans = add(mat_vec_3(&r_step, trans), t_step);
707 }
708 let target_tree2 = KdTree3D::build(&target.points);
709 let final_error = if current.is_empty() {
710 0.0
711 } else {
712 current
713 .iter()
714 .map(|&p| {
715 let (ti, _) = target_tree2.nearest_neighbor(p);
716 dist2(p, target.points[ti])
717 })
718 .sum::<f64>()
719 / current.len() as f64
720 };
721 IcpResult {
722 rotation: rot,
723 translation: trans,
724 final_error,
725 iterations: iters,
726 }
727 }
728 pub fn closest_point_pairs(source: &PointCloud, target: &PointCloud) -> Vec<(usize, usize)> {
730 let tree = KdTree3D::build(&target.points);
731 source
732 .points
733 .iter()
734 .enumerate()
735 .map(|(i, &p)| {
736 let (ti, _) = tree.nearest_neighbor(p);
737 (i, ti)
738 })
739 .collect()
740 }
741}