1use crate::{Mesh, Vertex};
31use nalgebra::{Point3, Vector3};
32use std::collections::{HashMap, HashSet};
33
34#[derive(Debug, Clone)]
36pub struct ScanCleanupParams {
37 pub remove_isolated: bool,
39
40 pub min_component_size: usize,
42
43 pub remove_spikes: bool,
45
46 pub spike_threshold: f64,
48
49 pub fill_small_holes: bool,
51
52 pub max_hole_size: usize,
54
55 pub smooth: bool,
57
58 pub smooth_iterations: usize,
60
61 pub smooth_strength: f64,
63
64 pub preserve_features: bool,
66
67 pub feature_angle_threshold: f64,
69}
70
71impl Default for ScanCleanupParams {
72 fn default() -> Self {
73 Self {
74 remove_isolated: true,
75 min_component_size: 100,
76 remove_spikes: true,
77 spike_threshold: 3.0,
78 fill_small_holes: true,
79 max_hole_size: 50,
80 smooth: true,
81 smooth_iterations: 2,
82 smooth_strength: 0.5,
83 preserve_features: true,
84 feature_angle_threshold: 0.5, }
86 }
87}
88
89impl ScanCleanupParams {
90 pub fn for_body_scan() -> Self {
92 Self {
93 remove_isolated: true,
94 min_component_size: 500,
95 remove_spikes: true,
96 spike_threshold: 2.5,
97 fill_small_holes: true,
98 max_hole_size: 100,
99 smooth: true,
100 smooth_iterations: 3,
101 smooth_strength: 0.4,
102 preserve_features: true,
103 feature_angle_threshold: 0.7, }
105 }
106
107 pub fn for_object_scan() -> Self {
109 Self {
110 remove_isolated: true,
111 min_component_size: 50,
112 remove_spikes: true,
113 spike_threshold: 3.5,
114 fill_small_holes: true,
115 max_hole_size: 30,
116 smooth: true,
117 smooth_iterations: 2,
118 smooth_strength: 0.3,
119 preserve_features: true,
120 feature_angle_threshold: 0.4, }
122 }
123
124 pub fn minimal() -> Self {
126 Self {
127 remove_isolated: true,
128 min_component_size: 10,
129 remove_spikes: false,
130 spike_threshold: 4.0,
131 fill_small_holes: false,
132 max_hole_size: 10,
133 smooth: false,
134 smooth_iterations: 1,
135 smooth_strength: 0.2,
136 preserve_features: true,
137 feature_angle_threshold: 0.3,
138 }
139 }
140
141 pub fn aggressive() -> Self {
143 Self {
144 remove_isolated: true,
145 min_component_size: 1000,
146 remove_spikes: true,
147 spike_threshold: 2.0,
148 fill_small_holes: true,
149 max_hole_size: 200,
150 smooth: true,
151 smooth_iterations: 5,
152 smooth_strength: 0.7,
153 preserve_features: false,
154 feature_angle_threshold: 0.5,
155 }
156 }
157}
158
159#[derive(Debug)]
161pub struct ScanCleanupResult {
162 pub mesh: Mesh,
164
165 pub components_removed: usize,
167
168 pub spikes_removed: usize,
170
171 pub holes_filled: usize,
173
174 pub smoothing_applied: bool,
176}
177
178pub fn cleanup_scan(mesh: &Mesh, params: &ScanCleanupParams) -> ScanCleanupResult {
180 let mut result_mesh = mesh.clone();
181 let mut components_removed = 0;
182 let mut spikes_removed = 0;
183 let mut holes_filled = 0;
184
185 if params.remove_isolated {
187 components_removed = remove_small_components(&mut result_mesh, params.min_component_size);
188 }
189
190 if params.remove_spikes {
192 spikes_removed = remove_spikes(&mut result_mesh, params.spike_threshold);
193 }
194
195 if params.fill_small_holes {
197 holes_filled = fill_small_holes(&mut result_mesh, params.max_hole_size);
198 }
199
200 if params.smooth {
202 if params.preserve_features {
203 smooth_preserve_features(
204 &mut result_mesh,
205 params.smooth_iterations,
206 params.smooth_strength,
207 params.feature_angle_threshold,
208 );
209 } else {
210 smooth_laplacian(
211 &mut result_mesh,
212 params.smooth_iterations,
213 params.smooth_strength,
214 );
215 }
216 }
217
218 ScanCleanupResult {
219 mesh: result_mesh,
220 components_removed,
221 spikes_removed,
222 holes_filled,
223 smoothing_applied: params.smooth,
224 }
225}
226
227#[derive(Debug, Clone)]
229pub struct DenoiseParams {
230 pub method: DenoiseMethod,
232
233 pub iterations: usize,
235
236 pub strength: f64,
238
239 pub preserve_features: bool,
241
242 pub feature_threshold: f64,
244}
245
246impl Default for DenoiseParams {
247 fn default() -> Self {
248 Self {
249 method: DenoiseMethod::Bilateral,
250 iterations: 3,
251 strength: 0.5,
252 preserve_features: true,
253 feature_threshold: 0.5,
254 }
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub enum DenoiseMethod {
261 Laplacian,
263
264 Bilateral,
266
267 Taubin,
269
270 MeanCurvatureFlow,
272}
273
274#[derive(Debug)]
276pub struct DenoiseResult {
277 pub mesh: Mesh,
279
280 pub average_displacement: f64,
282
283 pub max_displacement: f64,
285
286 pub iterations_performed: usize,
288}
289
290pub fn denoise_mesh(mesh: &Mesh, params: &DenoiseParams) -> DenoiseResult {
292 let mut result = mesh.clone();
293 let mut total_displacement = 0.0;
294 let mut max_displacement = 0.0f64;
295
296 for _ in 0..params.iterations {
297 let displacements = match params.method {
298 DenoiseMethod::Laplacian => compute_laplacian_displacements(&result, params.strength),
299 DenoiseMethod::Bilateral => {
300 compute_bilateral_displacements(&result, params.strength, params.feature_threshold)
301 }
302 DenoiseMethod::Taubin => compute_taubin_displacements(&result, params.strength),
303 DenoiseMethod::MeanCurvatureFlow => compute_mcf_displacements(&result, params.strength),
304 };
305
306 for (i, disp) in displacements.iter().enumerate() {
308 if params.preserve_features && is_feature_vertex(&result, i, params.feature_threshold) {
309 continue;
310 }
311
312 let disp_mag = disp.norm();
313 total_displacement += disp_mag;
314 max_displacement = max_displacement.max(disp_mag);
315 result.vertices[i].position += disp;
316 }
317 }
318
319 let vertex_count = result.vertices.len().max(1);
320 DenoiseResult {
321 mesh: result,
322 average_displacement: total_displacement / (vertex_count * params.iterations) as f64,
323 max_displacement,
324 iterations_performed: params.iterations,
325 }
326}
327
328#[derive(Debug, Clone)]
330pub struct HoleFillParams {
331 pub strategy: HoleFillStrategy,
333
334 pub max_size: usize,
336
337 pub smooth_iterations: usize,
339}
340
341impl Default for HoleFillParams {
342 fn default() -> Self {
343 Self {
344 strategy: HoleFillStrategy::Smooth,
345 max_size: 100,
346 smooth_iterations: 2,
347 }
348 }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
353pub enum HoleFillStrategy {
354 Planar,
356
357 Smooth,
359
360 CurvatureBased,
362
363 MinimalArea,
365}
366
367#[derive(Debug)]
369pub struct HoleFillResult {
370 pub mesh: Mesh,
372
373 pub holes_filled: usize,
375
376 pub faces_added: usize,
378
379 pub hole_sizes: Vec<usize>,
381}
382
383pub fn fill_holes_advanced(mesh: &Mesh, params: &HoleFillParams) -> HoleFillResult {
385 let mut result = mesh.clone();
386 let holes = detect_boundary_loops(&result);
387
388 let mut holes_filled = 0;
389 let mut faces_added = 0;
390 let mut hole_sizes = Vec::new();
391
392 for hole in holes {
393 if hole.len() > params.max_size {
394 continue;
395 }
396
397 let added = match params.strategy {
398 HoleFillStrategy::Planar => fill_hole_planar(&mut result, &hole),
399 HoleFillStrategy::Smooth => {
400 fill_hole_smooth(&mut result, &hole, params.smooth_iterations)
401 }
402 HoleFillStrategy::CurvatureBased => fill_hole_curvature(&mut result, &hole),
403 HoleFillStrategy::MinimalArea => fill_hole_minimal_area(&mut result, &hole),
404 };
405
406 if added > 0 {
407 holes_filled += 1;
408 faces_added += added;
409 hole_sizes.push(hole.len());
410 }
411 }
412
413 HoleFillResult {
414 mesh: result,
415 holes_filled,
416 faces_added,
417 hole_sizes,
418 }
419}
420
421#[derive(Debug, Clone)]
423pub struct OutlierRemovalParams {
424 pub k_neighbors: usize,
426
427 pub std_dev_threshold: f64,
429}
430
431impl Default for OutlierRemovalParams {
432 fn default() -> Self {
433 Self {
434 k_neighbors: 10,
435 std_dev_threshold: 2.0,
436 }
437 }
438}
439
440pub fn remove_outliers(mesh: &Mesh, params: &OutlierRemovalParams) -> Mesh {
442 if mesh.vertices.len() < params.k_neighbors {
443 return mesh.clone();
444 }
445
446 let mut avg_distances: Vec<f64> = Vec::with_capacity(mesh.vertices.len());
448
449 for (i, v) in mesh.vertices.iter().enumerate() {
450 let mut distances: Vec<f64> = mesh
451 .vertices
452 .iter()
453 .enumerate()
454 .filter(|(j, _)| *j != i)
455 .map(|(_, other)| (v.position - other.position).norm())
456 .collect();
457
458 distances.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
459
460 let k = params.k_neighbors.min(distances.len());
461 let avg: f64 = distances[..k].iter().sum::<f64>() / k as f64;
462 avg_distances.push(avg);
463 }
464
465 let mean: f64 = avg_distances.iter().sum::<f64>() / avg_distances.len() as f64;
467 let variance: f64 = avg_distances
468 .iter()
469 .map(|d| (d - mean).powi(2))
470 .sum::<f64>()
471 / avg_distances.len() as f64;
472 let std_dev = variance.sqrt();
473
474 let threshold = mean + params.std_dev_threshold * std_dev;
475
476 let keep: HashSet<u32> = avg_distances
478 .iter()
479 .enumerate()
480 .filter(|(_, d)| **d <= threshold)
481 .map(|(i, _)| i as u32)
482 .collect();
483
484 let mut result = Mesh::new();
486 let mut vertex_map: HashMap<u32, u32> = HashMap::new();
487
488 for (i, v) in mesh.vertices.iter().enumerate() {
489 if keep.contains(&(i as u32)) {
490 vertex_map.insert(i as u32, result.vertices.len() as u32);
491 result.vertices.push(v.clone());
492 }
493 }
494
495 for face in &mesh.faces {
497 if let (Some(&i0), Some(&i1), Some(&i2)) = (
498 vertex_map.get(&face[0]),
499 vertex_map.get(&face[1]),
500 vertex_map.get(&face[2]),
501 ) {
502 result.faces.push([i0, i1, i2]);
503 }
504 }
505
506 result
507}
508
509fn remove_small_components(mesh: &mut Mesh, min_size: usize) -> usize {
515 let components = find_connected_components(mesh);
516 let mut removed = 0;
517
518 let mut faces_to_keep: HashSet<usize> = HashSet::new();
520 for component in &components {
521 if component.len() >= min_size {
522 faces_to_keep.extend(component.iter());
523 } else {
524 removed += 1;
525 }
526 }
527
528 if removed == 0 {
529 return 0;
530 }
531
532 let new_faces: Vec<[u32; 3]> = mesh
534 .faces
535 .iter()
536 .enumerate()
537 .filter(|(i, _)| faces_to_keep.contains(i))
538 .map(|(_, f)| *f)
539 .collect();
540
541 mesh.faces = new_faces;
542
543 let mut referenced: HashSet<u32> = HashSet::new();
545 for face in &mesh.faces {
546 referenced.insert(face[0]);
547 referenced.insert(face[1]);
548 referenced.insert(face[2]);
549 }
550
551 let mut vertex_map: HashMap<u32, u32> = HashMap::new();
552 let mut new_vertices = Vec::new();
553
554 for (i, v) in mesh.vertices.iter().enumerate() {
555 if referenced.contains(&(i as u32)) {
556 vertex_map.insert(i as u32, new_vertices.len() as u32);
557 new_vertices.push(v.clone());
558 }
559 }
560
561 mesh.vertices = new_vertices;
562
563 for face in &mut mesh.faces {
564 face[0] = vertex_map[&face[0]];
565 face[1] = vertex_map[&face[1]];
566 face[2] = vertex_map[&face[2]];
567 }
568
569 removed
570}
571
572fn find_connected_components(mesh: &Mesh) -> Vec<Vec<usize>> {
574 let mut visited: HashSet<usize> = HashSet::new();
575 let mut components: Vec<Vec<usize>> = Vec::new();
576
577 let mut edge_to_faces: HashMap<(u32, u32), Vec<usize>> = HashMap::new();
579 for (fi, face) in mesh.faces.iter().enumerate() {
580 for i in 0..3 {
581 let v0 = face[i];
582 let v1 = face[(i + 1) % 3];
583 let edge = if v0 < v1 { (v0, v1) } else { (v1, v0) };
584 edge_to_faces.entry(edge).or_default().push(fi);
585 }
586 }
587
588 for fi in 0..mesh.faces.len() {
589 if visited.contains(&fi) {
590 continue;
591 }
592
593 let mut component = Vec::new();
594 let mut stack = vec![fi];
595
596 while let Some(current) = stack.pop() {
597 if visited.contains(¤t) {
598 continue;
599 }
600 visited.insert(current);
601 component.push(current);
602
603 let face = &mesh.faces[current];
604 for i in 0..3 {
605 let v0 = face[i];
606 let v1 = face[(i + 1) % 3];
607 let edge = if v0 < v1 { (v0, v1) } else { (v1, v0) };
608
609 if let Some(neighbors) = edge_to_faces.get(&edge) {
610 for &neighbor in neighbors {
611 if !visited.contains(&neighbor) {
612 stack.push(neighbor);
613 }
614 }
615 }
616 }
617 }
618
619 components.push(component);
620 }
621
622 components
623}
624
625fn remove_spikes(mesh: &mut Mesh, threshold: f64) -> usize {
627 if mesh.faces.is_empty() {
628 return 0;
629 }
630
631 let mut edge_lengths: Vec<f64> = Vec::new();
633 for face in &mesh.faces {
634 for i in 0..3 {
635 let v0 = &mesh.vertices[face[i] as usize].position;
636 let v1 = &mesh.vertices[face[(i + 1) % 3] as usize].position;
637 edge_lengths.push((v1 - v0).norm());
638 }
639 }
640
641 if edge_lengths.is_empty() {
642 return 0;
643 }
644
645 let mean: f64 = edge_lengths.iter().sum::<f64>() / edge_lengths.len() as f64;
647 let variance: f64 =
648 edge_lengths.iter().map(|l| (l - mean).powi(2)).sum::<f64>() / edge_lengths.len() as f64;
649 let std_dev = variance.sqrt();
650
651 let max_length = mean + threshold * std_dev;
652
653 let mut spike_vertices: HashSet<u32> = HashSet::new();
655 for face in &mesh.faces {
656 for i in 0..3 {
657 let v0 = &mesh.vertices[face[i] as usize].position;
658 let v1 = &mesh.vertices[face[(i + 1) % 3] as usize].position;
659 if (v1 - v0).norm() > max_length {
660 spike_vertices.insert(face[i]);
661 spike_vertices.insert(face[(i + 1) % 3]);
662 }
663 }
664 }
665
666 if spike_vertices.is_empty() {
667 return 0;
668 }
669
670 let original_face_count = mesh.faces.len();
672 mesh.faces.retain(|face| {
673 !spike_vertices.contains(&face[0])
674 && !spike_vertices.contains(&face[1])
675 && !spike_vertices.contains(&face[2])
676 });
677
678 original_face_count - mesh.faces.len()
679}
680
681fn fill_small_holes(mesh: &mut Mesh, max_size: usize) -> usize {
683 let holes = detect_boundary_loops(mesh);
684 let mut filled = 0;
685
686 for hole in holes {
687 if hole.len() <= max_size && hole.len() >= 3 {
688 fill_hole_planar(mesh, &hole);
689 filled += 1;
690 }
691 }
692
693 filled
694}
695
696fn detect_boundary_loops(mesh: &Mesh) -> Vec<Vec<u32>> {
698 let mut edge_count: HashMap<(u32, u32), usize> = HashMap::new();
700
701 for face in &mesh.faces {
702 for i in 0..3 {
703 let v0 = face[i];
704 let v1 = face[(i + 1) % 3];
705 let edge = if v0 < v1 { (v0, v1) } else { (v1, v0) };
706 *edge_count.entry(edge).or_insert(0) += 1;
707 }
708 }
709
710 let boundary_edges: HashSet<(u32, u32)> = edge_count
711 .into_iter()
712 .filter(|(_, count)| *count == 1)
713 .map(|(edge, _)| edge)
714 .collect();
715
716 if boundary_edges.is_empty() {
717 return Vec::new();
718 }
719
720 let mut boundary_adj: HashMap<u32, Vec<u32>> = HashMap::new();
722 for (v0, v1) in &boundary_edges {
723 boundary_adj.entry(*v0).or_default().push(*v1);
724 boundary_adj.entry(*v1).or_default().push(*v0);
725 }
726
727 let mut visited_edges: HashSet<(u32, u32)> = HashSet::new();
729 let mut loops: Vec<Vec<u32>> = Vec::new();
730
731 for (&start_v, neighbors) in &boundary_adj {
732 for &next_v in neighbors {
733 let edge = if start_v < next_v {
734 (start_v, next_v)
735 } else {
736 (next_v, start_v)
737 };
738
739 if visited_edges.contains(&edge) {
740 continue;
741 }
742
743 let mut current_loop = vec![start_v];
744 let mut prev = start_v;
745 let mut current = next_v;
746
747 loop {
748 let edge = if prev < current {
749 (prev, current)
750 } else {
751 (current, prev)
752 };
753 visited_edges.insert(edge);
754 current_loop.push(current);
755
756 if current == start_v {
757 current_loop.pop(); break;
759 }
760
761 if let Some(adj) = boundary_adj.get(¤t) {
763 let next = adj.iter().find(|&&v| v != prev);
764 if let Some(&n) = next {
765 prev = current;
766 current = n;
767 } else {
768 break;
769 }
770 } else {
771 break;
772 }
773 }
774
775 if current_loop.len() >= 3 {
776 loops.push(current_loop);
777 }
778 }
779 }
780
781 loops
782}
783
784fn fill_hole_planar(mesh: &mut Mesh, hole: &[u32]) -> usize {
786 if hole.len() < 3 {
787 return 0;
788 }
789
790 let first = hole[0];
792 let mut faces_added = 0;
793
794 for i in 1..hole.len() - 1 {
795 mesh.faces.push([first, hole[i], hole[i + 1]]);
796 faces_added += 1;
797 }
798
799 faces_added
800}
801
802fn fill_hole_smooth(mesh: &mut Mesh, hole: &[u32], smooth_iterations: usize) -> usize {
804 let faces_added = fill_hole_planar(mesh, hole);
805
806 if faces_added > 0 && smooth_iterations > 0 {
807 let affected: HashSet<u32> = hole.iter().copied().collect();
809
810 for _ in 0..smooth_iterations {
811 smooth_vertices_subset(mesh, &affected, 0.5);
812 }
813 }
814
815 faces_added
816}
817
818fn fill_hole_curvature(mesh: &mut Mesh, hole: &[u32]) -> usize {
820 let faces_added = fill_hole_planar(mesh, hole);
822
823 if faces_added == 0 || hole.is_empty() {
824 return 0;
825 }
826
827 let centroid = compute_hole_centroid(mesh, hole);
829
830 let _normal = estimate_boundary_normal(mesh, hole);
832
833 if hole.len() > 4 {
835 let start_face_idx = mesh.faces.len() - faces_added;
837 mesh.faces.truncate(start_face_idx);
838
839 let center_idx = mesh.vertices.len() as u32;
841 mesh.vertices.push(Vertex::new(centroid));
842
843 for i in 0..hole.len() {
845 let v0 = hole[i];
846 let v1 = hole[(i + 1) % hole.len()];
847 mesh.faces.push([center_idx, v0, v1]);
848 }
849
850 return hole.len();
851 }
852
853 faces_added
854}
855
856fn fill_hole_minimal_area(mesh: &mut Mesh, hole: &[u32]) -> usize {
858 fill_hole_planar(mesh, hole)
861}
862
863fn smooth_laplacian(mesh: &mut Mesh, iterations: usize, strength: f64) {
865 for _ in 0..iterations {
866 let displacements = compute_laplacian_displacements(mesh, strength);
867 for (i, disp) in displacements.iter().enumerate() {
868 mesh.vertices[i].position += disp;
869 }
870 }
871}
872
873fn smooth_preserve_features(
875 mesh: &mut Mesh,
876 iterations: usize,
877 strength: f64,
878 feature_threshold: f64,
879) {
880 for _ in 0..iterations {
881 let displacements = compute_bilateral_displacements(mesh, strength, feature_threshold);
882 for (i, disp) in displacements.iter().enumerate() {
883 if !is_feature_vertex(mesh, i, feature_threshold) {
884 mesh.vertices[i].position += disp;
885 }
886 }
887 }
888}
889
890fn smooth_vertices_subset(mesh: &mut Mesh, vertices: &HashSet<u32>, strength: f64) {
892 let adjacency = build_vertex_adjacency(mesh);
893 let mut displacements: HashMap<u32, Vector3<f64>> = HashMap::new();
894
895 for &vi in vertices {
896 if let Some(neighbors) = adjacency.get(&vi) {
897 if neighbors.is_empty() {
898 continue;
899 }
900
901 let current = mesh.vertices[vi as usize].position;
902 let centroid: Point3<f64> = Point3::from(
903 neighbors
904 .iter()
905 .map(|&ni| mesh.vertices[ni as usize].position.coords)
906 .sum::<Vector3<f64>>()
907 / neighbors.len() as f64,
908 );
909
910 displacements.insert(vi, (centroid - current) * strength);
911 }
912 }
913
914 for (vi, disp) in displacements {
915 mesh.vertices[vi as usize].position += disp;
916 }
917}
918
919fn build_vertex_adjacency(mesh: &Mesh) -> HashMap<u32, Vec<u32>> {
921 let mut adjacency: HashMap<u32, Vec<u32>> = HashMap::new();
922
923 for face in &mesh.faces {
924 for i in 0..3 {
925 let v0 = face[i];
926 let v1 = face[(i + 1) % 3];
927 adjacency.entry(v0).or_default().push(v1);
928 adjacency.entry(v1).or_default().push(v0);
929 }
930 }
931
932 for neighbors in adjacency.values_mut() {
934 neighbors.sort();
935 neighbors.dedup();
936 }
937
938 adjacency
939}
940
941fn compute_laplacian_displacements(mesh: &Mesh, strength: f64) -> Vec<Vector3<f64>> {
943 let adjacency = build_vertex_adjacency(mesh);
944 let mut displacements = vec![Vector3::zeros(); mesh.vertices.len()];
945
946 for (vi, v) in mesh.vertices.iter().enumerate() {
947 if let Some(neighbors) = adjacency.get(&(vi as u32)) {
948 if neighbors.is_empty() {
949 continue;
950 }
951
952 let centroid: Point3<f64> = Point3::from(
953 neighbors
954 .iter()
955 .map(|&ni| mesh.vertices[ni as usize].position.coords)
956 .sum::<Vector3<f64>>()
957 / neighbors.len() as f64,
958 );
959
960 displacements[vi] = (centroid - v.position) * strength;
961 }
962 }
963
964 displacements
965}
966
967fn compute_bilateral_displacements(
969 mesh: &Mesh,
970 strength: f64,
971 feature_threshold: f64,
972) -> Vec<Vector3<f64>> {
973 let adjacency = build_vertex_adjacency(mesh);
974 let normals = compute_vertex_normals(mesh);
975 let mut displacements = vec![Vector3::zeros(); mesh.vertices.len()];
976
977 for (vi, v) in mesh.vertices.iter().enumerate() {
978 if let Some(neighbors) = adjacency.get(&(vi as u32)) {
979 if neighbors.is_empty() {
980 continue;
981 }
982
983 let normal = normals.get(vi).copied().unwrap_or(Vector3::z());
984 let mut weighted_sum = Vector3::zeros();
985 let mut weight_sum = 0.0;
986
987 for &ni in neighbors {
988 let neighbor_pos = mesh.vertices[ni as usize].position;
989 let diff = neighbor_pos - v.position;
990
991 let spatial_weight = (-diff.norm_squared() / (2.0 * strength * strength)).exp();
993
994 let neighbor_normal = normals.get(ni as usize).copied().unwrap_or(Vector3::z());
995 let normal_sim = normal.dot(&neighbor_normal).max(0.0);
996 let range_weight = if normal_sim > feature_threshold.cos() {
997 1.0
998 } else {
999 0.1
1000 };
1001
1002 let weight = spatial_weight * range_weight;
1003 weighted_sum += diff * weight;
1004 weight_sum += weight;
1005 }
1006
1007 if weight_sum > 1e-10 {
1008 displacements[vi] = weighted_sum / weight_sum * strength;
1009 }
1010 }
1011 }
1012
1013 displacements
1014}
1015
1016fn compute_taubin_displacements(mesh: &Mesh, strength: f64) -> Vec<Vector3<f64>> {
1018 let lambda = strength;
1020 let mu = -strength * 1.02; let laplacian = compute_laplacian_displacements(mesh, lambda);
1023
1024 let mut temp_mesh = mesh.clone();
1026 for (i, disp) in laplacian.iter().enumerate() {
1027 temp_mesh.vertices[i].position += disp;
1028 }
1029
1030 let mut inverse = compute_laplacian_displacements(&temp_mesh, mu);
1032
1033 for (i, disp) in inverse.iter_mut().enumerate() {
1035 *disp = laplacian[i] + *disp;
1036 }
1037
1038 inverse
1039}
1040
1041fn compute_mcf_displacements(mesh: &Mesh, strength: f64) -> Vec<Vector3<f64>> {
1043 let adjacency = build_vertex_adjacency(mesh);
1044 let normals = compute_vertex_normals(mesh);
1045 let mut displacements = vec![Vector3::zeros(); mesh.vertices.len()];
1046
1047 for (vi, v) in mesh.vertices.iter().enumerate() {
1048 if let Some(neighbors) = adjacency.get(&(vi as u32)) {
1049 if neighbors.is_empty() {
1050 continue;
1051 }
1052
1053 let normal = normals.get(vi).copied().unwrap_or(Vector3::z());
1054
1055 let centroid: Point3<f64> = Point3::from(
1057 neighbors
1058 .iter()
1059 .map(|&ni| mesh.vertices[ni as usize].position.coords)
1060 .sum::<Vector3<f64>>()
1061 / neighbors.len() as f64,
1062 );
1063
1064 let laplacian = centroid - v.position;
1065 let mean_curvature = laplacian.dot(&normal);
1066
1067 displacements[vi] = normal * mean_curvature * strength;
1069 }
1070 }
1071
1072 displacements
1073}
1074
1075fn compute_vertex_normals(mesh: &Mesh) -> Vec<Vector3<f64>> {
1077 let mut normals = vec![Vector3::zeros(); mesh.vertices.len()];
1078
1079 for face in &mesh.faces {
1080 let v0 = mesh.vertices[face[0] as usize].position;
1081 let v1 = mesh.vertices[face[1] as usize].position;
1082 let v2 = mesh.vertices[face[2] as usize].position;
1083
1084 let edge1 = v1 - v0;
1085 let edge2 = v2 - v0;
1086 let face_normal = edge1.cross(&edge2);
1087
1088 normals[face[0] as usize] += face_normal;
1089 normals[face[1] as usize] += face_normal;
1090 normals[face[2] as usize] += face_normal;
1091 }
1092
1093 for normal in &mut normals {
1094 let len = normal.norm();
1095 if len > 1e-10 {
1096 *normal /= len;
1097 }
1098 }
1099
1100 normals
1101}
1102
1103fn is_feature_vertex(mesh: &Mesh, vertex_idx: usize, threshold: f64) -> bool {
1105 let mut face_normals: Vec<Vector3<f64>> = Vec::new();
1107
1108 for face in &mesh.faces {
1109 if face[0] as usize == vertex_idx
1110 || face[1] as usize == vertex_idx
1111 || face[2] as usize == vertex_idx
1112 {
1113 let v0 = mesh.vertices[face[0] as usize].position;
1114 let v1 = mesh.vertices[face[1] as usize].position;
1115 let v2 = mesh.vertices[face[2] as usize].position;
1116
1117 let edge1 = v1 - v0;
1118 let edge2 = v2 - v0;
1119 let normal = edge1.cross(&edge2);
1120 let len = normal.norm();
1121 if len > 1e-10 {
1122 face_normals.push(normal / len);
1123 }
1124 }
1125 }
1126
1127 for i in 0..face_normals.len() {
1129 for j in (i + 1)..face_normals.len() {
1130 let dot = face_normals[i].dot(&face_normals[j]).clamp(-1.0, 1.0);
1131 let angle = dot.acos();
1132 if angle > threshold {
1133 return true;
1134 }
1135 }
1136 }
1137
1138 false
1139}
1140
1141fn compute_hole_centroid(mesh: &Mesh, hole: &[u32]) -> Point3<f64> {
1143 if hole.is_empty() {
1144 return Point3::origin();
1145 }
1146
1147 let sum: Vector3<f64> = hole
1148 .iter()
1149 .map(|&vi| mesh.vertices[vi as usize].position.coords)
1150 .sum();
1151
1152 Point3::from(sum / hole.len() as f64)
1153}
1154
1155fn estimate_boundary_normal(mesh: &Mesh, hole: &[u32]) -> Vector3<f64> {
1157 if hole.len() < 3 {
1158 return Vector3::z();
1159 }
1160
1161 let mut normal: Vector3<f64> = Vector3::zeros();
1163
1164 for i in 0..hole.len() {
1165 let current = mesh.vertices[hole[i] as usize].position;
1166 let next = mesh.vertices[hole[(i + 1) % hole.len()] as usize].position;
1167
1168 normal.x += (current.y - next.y) * (current.z + next.z);
1169 normal.y += (current.z - next.z) * (current.x + next.x);
1170 normal.z += (current.x - next.x) * (current.y + next.y);
1171 }
1172
1173 let len = normal.norm();
1174 if len > 1e-10 {
1175 normal / len
1176 } else {
1177 Vector3::z()
1178 }
1179}
1180
1181impl Mesh {
1186 pub fn cleanup_scan(&self) -> ScanCleanupResult {
1188 cleanup_scan(self, &ScanCleanupParams::default())
1189 }
1190
1191 pub fn cleanup_scan_with_params(&self, params: &ScanCleanupParams) -> ScanCleanupResult {
1193 cleanup_scan(self, params)
1194 }
1195
1196 pub fn denoise(&self) -> DenoiseResult {
1198 denoise_mesh(self, &DenoiseParams::default())
1199 }
1200
1201 pub fn denoise_with_params(&self, params: &DenoiseParams) -> DenoiseResult {
1203 denoise_mesh(self, params)
1204 }
1205
1206 pub fn fill_holes_advanced(&self) -> HoleFillResult {
1208 fill_holes_advanced(self, &HoleFillParams::default())
1209 }
1210
1211 pub fn fill_holes_advanced_with_params(&self, params: &HoleFillParams) -> HoleFillResult {
1213 fill_holes_advanced(self, params)
1214 }
1215
1216 pub fn remove_outliers(&self) -> Mesh {
1218 remove_outliers(self, &OutlierRemovalParams::default())
1219 }
1220
1221 pub fn remove_outliers_with_params(&self, params: &OutlierRemovalParams) -> Mesh {
1223 remove_outliers(self, params)
1224 }
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229 use super::*;
1230
1231 fn create_test_mesh() -> Mesh {
1232 let mut mesh = Mesh::new();
1233 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
1235 mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
1236 mesh.vertices.push(Vertex::from_coords(10.0, 10.0, 0.0));
1237 mesh.vertices.push(Vertex::from_coords(0.0, 10.0, 0.0));
1238 mesh.vertices.push(Vertex::from_coords(5.0, 5.0, 10.0));
1239
1240 mesh.faces.push([0, 1, 4]);
1241 mesh.faces.push([1, 2, 4]);
1242 mesh.faces.push([2, 3, 4]);
1243 mesh.faces.push([3, 0, 4]);
1244 mesh
1245 }
1246
1247 #[test]
1248 fn test_cleanup_params_default() {
1249 let params = ScanCleanupParams::default();
1250 assert!(params.remove_isolated);
1251 assert!(params.remove_spikes);
1252 assert!(params.fill_small_holes);
1253 assert!(params.smooth);
1254 }
1255
1256 #[test]
1257 fn test_cleanup_params_presets() {
1258 let body = ScanCleanupParams::for_body_scan();
1259 assert!(body.min_component_size > 100);
1260
1261 let object = ScanCleanupParams::for_object_scan();
1262 assert!(object.min_component_size < body.min_component_size);
1263
1264 let minimal = ScanCleanupParams::minimal();
1265 assert!(!minimal.remove_spikes);
1266
1267 let aggressive = ScanCleanupParams::aggressive();
1268 assert!(aggressive.smooth_iterations > 3);
1269 }
1270
1271 #[test]
1272 fn test_cleanup_scan() {
1273 let mesh = create_test_mesh();
1274 let mut params = ScanCleanupParams::minimal();
1276 params.min_component_size = 1; let result = cleanup_scan(&mesh, ¶ms);
1278
1279 assert!(!result.mesh.vertices.is_empty());
1280 assert!(!result.mesh.faces.is_empty());
1281 }
1282
1283 #[test]
1284 fn test_denoise_params() {
1285 let params = DenoiseParams::default();
1286 assert_eq!(params.method, DenoiseMethod::Bilateral);
1287 assert!(params.preserve_features);
1288 }
1289
1290 #[test]
1291 fn test_denoise_mesh() {
1292 let mesh = create_test_mesh();
1293 let result = denoise_mesh(&mesh, &DenoiseParams::default());
1294
1295 assert!(!result.mesh.vertices.is_empty());
1296 assert_eq!(result.iterations_performed, 3);
1297 }
1298
1299 #[test]
1300 fn test_outlier_removal() {
1301 let mut mesh = create_test_mesh();
1302 mesh.vertices.push(Vertex::from_coords(1000.0, 0.0, 0.0));
1304
1305 let params = OutlierRemovalParams {
1306 k_neighbors: 3,
1307 std_dev_threshold: 1.5,
1308 };
1309 let result = remove_outliers(&mesh, ¶ms);
1310
1311 assert!(result.vertices.len() <= mesh.vertices.len());
1313 }
1314
1315 #[test]
1316 fn test_mesh_cleanup_method() {
1317 let mesh = create_test_mesh();
1318 let mut params = ScanCleanupParams::minimal();
1320 params.min_component_size = 1;
1321 let result = mesh.cleanup_scan_with_params(¶ms);
1322
1323 assert!(!result.mesh.vertices.is_empty());
1324 }
1325
1326 #[test]
1327 fn test_mesh_denoise_method() {
1328 let mesh = create_test_mesh();
1329 let result = mesh.denoise();
1330
1331 assert!(!result.mesh.vertices.is_empty());
1332 }
1333
1334 #[test]
1335 fn test_vertex_adjacency() {
1336 let mesh = create_test_mesh();
1337 let adjacency = build_vertex_adjacency(&mesh);
1338
1339 assert!(adjacency.get(&4).map(|v| v.len() >= 4).unwrap_or(false));
1341 }
1342
1343 #[test]
1344 fn test_vertex_normals() {
1345 let mesh = create_test_mesh();
1346 let normals = compute_vertex_normals(&mesh);
1347
1348 assert_eq!(normals.len(), mesh.vertices.len());
1349 for normal in &normals {
1351 let len = normal.norm();
1352 assert!(len > 0.9 && len < 1.1, "Normal length: {}", len);
1353 }
1354 }
1355}