1use std::time::Instant;
4use tracing::{debug, info, warn};
5
6use mesh_repair::Mesh;
7
8use crate::error::{ShellError, ShellResult};
9
10use super::extract::extract_isosurface;
11use super::grid::{SdfGrid, SdfOffsetParams};
12use super::transfer::transfer_vertex_data;
13
14#[derive(Debug, Clone)]
16pub struct SdfOffsetStats {
17 pub grid_dims: [usize; 3],
19 pub total_voxels: usize,
21 pub sdf_time_ms: u64,
23 pub extraction_time_ms: u64,
25 pub transfer_time_ms: u64,
27 pub input_vertices: usize,
29 pub output_vertices: usize,
31 pub output_faces: usize,
33}
34
35#[derive(Debug)]
37pub struct SdfOffsetResult {
38 pub mesh: Mesh,
40 pub stats: SdfOffsetStats,
42}
43
44pub fn apply_sdf_offset(mesh: &Mesh, params: &SdfOffsetParams) -> ShellResult<SdfOffsetResult> {
64 let total_start = Instant::now();
65
66 if mesh.vertices.is_empty() {
67 return Err(ShellError::EmptyMesh);
68 }
69
70 let missing_offset = mesh
72 .vertices
73 .iter()
74 .filter(|v| v.offset.is_none())
75 .count();
76
77 if missing_offset > 0 {
78 warn!(
79 missing = missing_offset,
80 total = mesh.vertices.len(),
81 "Some vertices missing offset values, using 0.0"
82 );
83 }
84
85 let input_vertices = mesh.vertices.len();
86
87 info!(
88 vertices = input_vertices,
89 faces = mesh.faces.len(),
90 voxel_size_mm = params.voxel_size_mm,
91 padding_mm = params.padding_mm,
92 "Starting SDF offset"
93 );
94
95 let mut grid = SdfGrid::from_mesh_bounds(
97 mesh,
98 params.voxel_size_mm,
99 params.padding_mm,
100 params.max_voxels,
101 )?;
102
103 info!(
104 dims = ?grid.dims,
105 total_voxels = grid.total_voxels(),
106 "Grid created"
107 );
108
109 let sdf_start = Instant::now();
111 grid.compute_sdf(mesh);
112 let sdf_time_ms = sdf_start.elapsed().as_millis() as u64;
113
114 debug!(sdf_time_ms, "SDF computation complete");
115
116 grid.interpolate_offsets(mesh, params.offset_neighbors);
118
119 grid.apply_variable_offset();
121
122 let extract_start = Instant::now();
124 let mut output_mesh = extract_isosurface(&grid)?;
125 let extraction_time_ms = extract_start.elapsed().as_millis() as u64;
126
127 debug!(
128 extraction_time_ms,
129 vertices = output_mesh.vertices.len(),
130 faces = output_mesh.faces.len(),
131 "Surface extraction complete"
132 );
133
134 let transfer_start = Instant::now();
136 transfer_vertex_data(mesh, &mut output_mesh)?;
137 let transfer_time_ms = transfer_start.elapsed().as_millis() as u64;
138
139 debug!(transfer_time_ms, "Vertex data transfer complete");
140
141 let total_time_ms = total_start.elapsed().as_millis();
142
143 let stats = SdfOffsetStats {
144 grid_dims: grid.dims,
145 total_voxels: grid.total_voxels(),
146 sdf_time_ms,
147 extraction_time_ms,
148 transfer_time_ms,
149 input_vertices,
150 output_vertices: output_mesh.vertices.len(),
151 output_faces: output_mesh.faces.len(),
152 };
153
154 info!(
155 total_time_ms,
156 input_vertices = stats.input_vertices,
157 output_vertices = stats.output_vertices,
158 output_faces = stats.output_faces,
159 "SDF offset complete"
160 );
161
162 Ok(SdfOffsetResult {
163 mesh: output_mesh,
164 stats,
165 })
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use mesh_repair::Vertex;
172
173 fn create_unit_cube() -> Mesh {
174 let mut mesh = Mesh::new();
175
176 for z in [0.0, 10.0] {
178 for y in [0.0, 10.0] {
179 for x in [0.0, 10.0] {
180 let mut v = Vertex::from_coords(x, y, z);
181 v.offset = Some(1.0); v.tag = Some(1);
183 mesh.vertices.push(v);
184 }
185 }
186 }
187
188 mesh.faces.push([0, 1, 3]);
190 mesh.faces.push([0, 3, 2]);
191 mesh.faces.push([4, 7, 5]);
192 mesh.faces.push([4, 6, 7]);
193 mesh.faces.push([0, 5, 1]);
194 mesh.faces.push([0, 4, 5]);
195 mesh.faces.push([2, 3, 7]);
196 mesh.faces.push([2, 7, 6]);
197 mesh.faces.push([0, 2, 6]);
198 mesh.faces.push([0, 6, 4]);
199 mesh.faces.push([1, 5, 7]);
200 mesh.faces.push([1, 7, 3]);
201
202 mesh
203 }
204
205 #[test]
206 fn test_sdf_offset_cube() {
207 let mesh = create_unit_cube();
208
209 let params = SdfOffsetParams {
210 voxel_size_mm: 1.0,
211 padding_mm: 5.0,
212 max_voxels: 1_000_000,
213 offset_neighbors: 4,
214 };
215
216 let result = apply_sdf_offset(&mesh, ¶ms).unwrap();
217
218 assert!(!result.mesh.vertices.is_empty());
220 assert!(!result.mesh.faces.is_empty());
221
222 let input_bounds = mesh.bounds().unwrap();
224 let output_bounds = result.mesh.bounds().unwrap();
225
226 let input_extent = input_bounds.1 - input_bounds.0;
227 let output_extent = output_bounds.1 - output_bounds.0;
228
229 assert!(
231 output_extent.x > input_extent.x,
232 "Output should be wider: {} vs {}",
233 output_extent.x,
234 input_extent.x
235 );
236 }
237
238 #[test]
239 fn test_sdf_offset_empty_mesh() {
240 let mesh = Mesh::new();
241 let params = SdfOffsetParams::default();
242
243 let result = apply_sdf_offset(&mesh, ¶ms);
244 assert!(result.is_err());
245 }
246}