1use std::collections::BTreeSet;
15
16use axiolid_core::{Point3, Scalar};
17use axiolid_mesh::{EdgeAdjacency, TriMesh};
18
19use crate::{RefineError, SmoothReport};
20
21#[derive(Debug, Clone, Copy, PartialEq)]
27pub struct SmoothOptions {
28 pub factor: Scalar,
30 pub passes: u32,
32 pub fix_boundary: bool,
34}
35
36impl Default for SmoothOptions {
37 fn default() -> Self {
38 Self {
39 factor: 0.5,
40 passes: 1,
41 fix_boundary: true,
42 }
43 }
44}
45
46pub fn smooth(
53 mesh: &TriMesh,
54 options: SmoothOptions,
55) -> Result<(TriMesh, SmoothReport), RefineError> {
56 if mesh.indices.len() % 3 != 0 {
57 return Err(RefineError::RaggedIndices(mesh.indices.len()));
58 }
59 let vertex_count = mesh.positions.len();
60 for (triangle, chunk) in mesh.indices.chunks_exact(3).enumerate() {
61 for &index in chunk {
62 if index as usize >= vertex_count {
63 return Err(RefineError::IndexOutOfRange(triangle, index));
64 }
65 }
66 }
67 if !(options.factor > 0.0 && options.factor <= 1.0) {
68 return Err(RefineError::InvalidTarget(options.factor));
69 }
70
71 let adjacency = EdgeAdjacency::build(mesh);
75 let boundary: BTreeSet<u32> = adjacency.boundary_vertices().into_iter().collect();
76 let neighbours = adjacency.vertex_neighbours();
77
78 let mut positions = mesh.positions.clone();
79 let mut moved = 0usize;
80 let mut max_movement: Scalar = 0.0;
81
82 for _ in 0..options.passes {
83 let source = positions.clone();
84 for (index, position) in positions.iter_mut().enumerate() {
85 let vertex = index as u32;
86 if options.fix_boundary && boundary.contains(&vertex) {
87 continue;
88 }
89 let adjacent = &neighbours[index];
90 if adjacent.is_empty() {
91 continue;
92 }
93 let mut sum = Point3::ZERO;
94 for &other in adjacent {
95 sum += source[other as usize];
96 }
97 let average = sum / (adjacent.len() as Scalar);
98 let target = source[index] + (average - source[index]) * options.factor;
99 let movement = (target - source[index]).length();
100 if movement > 0.0 {
101 moved += 1;
102 max_movement = max_movement.max(movement);
103 }
104 *position = target;
105 }
106 }
107
108 let attribute_fates = crate::carry_attributes(mesh);
109 let out = TriMesh {
110 positions,
111 indices: mesh.indices.clone(),
112 normals: None,
113 attributes: Vec::new(),
114 };
115 let report = SmoothReport {
116 vertices_moved: moved,
117 boundary_vertices: boundary.len(),
118 max_movement,
119 attribute_fates,
120 };
121 Ok((out, report))
122}