1use nalgebra::Point3;
4use rayon::prelude::*;
5use tracing::{debug, info};
6
7use mesh_repair::Mesh;
8
9use crate::error::{ShellError, ShellResult};
10
11#[derive(Debug, Clone)]
13pub struct SdfOffsetParams {
14 pub voxel_size_mm: f64,
17 pub padding_mm: f64,
19 pub max_voxels: usize,
21 pub offset_neighbors: usize,
23 pub adaptive_resolution: bool,
26 pub coarse_voxel_multiplier: f64,
30 pub refinement_distance_mm: f64,
34 pub use_gpu: bool,
39}
40
41impl Default for SdfOffsetParams {
42 fn default() -> Self {
43 Self {
44 voxel_size_mm: 0.75,
45 padding_mm: 12.0,
46 max_voxels: 50_000_000,
47 offset_neighbors: 8,
48 adaptive_resolution: false,
49 coarse_voxel_multiplier: 4.0,
50 refinement_distance_mm: 5.0,
51 use_gpu: false,
52 }
53 }
54}
55
56impl SdfOffsetParams {
57 pub fn adaptive() -> Self {
62 Self {
63 voxel_size_mm: 0.5,
64 padding_mm: 10.0,
65 max_voxels: 50_000_000,
66 offset_neighbors: 8,
67 adaptive_resolution: true,
68 coarse_voxel_multiplier: 4.0,
69 refinement_distance_mm: 5.0,
70 use_gpu: false,
71 }
72 }
73
74 pub fn adaptive_high_quality() -> Self {
76 Self {
77 voxel_size_mm: 0.4,
78 padding_mm: 12.0,
79 max_voxels: 80_000_000,
80 offset_neighbors: 12,
81 adaptive_resolution: true,
82 coarse_voxel_multiplier: 3.0,
83 refinement_distance_mm: 8.0,
84 use_gpu: false,
85 }
86 }
87
88 pub fn adaptive_large_mesh() -> Self {
90 Self {
91 voxel_size_mm: 0.75,
92 padding_mm: 8.0,
93 max_voxels: 30_000_000,
94 offset_neighbors: 6,
95 adaptive_resolution: true,
96 coarse_voxel_multiplier: 5.0,
97 refinement_distance_mm: 4.0,
98 use_gpu: false,
99 }
100 }
101
102 pub fn with_gpu(mut self) -> Self {
107 self.use_gpu = true;
108 self
109 }
110
111 pub fn coarse_voxel_size_mm(&self) -> f64 {
113 self.voxel_size_mm * self.coarse_voxel_multiplier
114 }
115}
116
117#[derive(Debug)]
119pub struct SdfGrid {
120 pub dims: [usize; 3],
122 pub origin: Point3<f64>,
124 pub voxel_size: f64,
126 pub values: Vec<f32>,
128 pub offsets: Vec<f32>,
130}
131
132#[allow(dead_code)] impl SdfGrid {
134 pub fn from_mesh_bounds(
136 mesh: &Mesh,
137 voxel_size: f64,
138 padding: f64,
139 max_voxels: usize,
140 ) -> ShellResult<Self> {
141 let (min, max) = mesh.bounds().ok_or(ShellError::EmptyMesh)?;
142
143 let origin = Point3::new(min.x - padding, min.y - padding, min.z - padding);
145 let max_padded = Point3::new(max.x + padding, max.y + padding, max.z + padding);
146
147 let extent = max_padded - origin;
149 let dims = [
150 ((extent.x / voxel_size).ceil() as usize).max(1),
151 ((extent.y / voxel_size).ceil() as usize).max(1),
152 ((extent.z / voxel_size).ceil() as usize).max(1),
153 ];
154
155 let total_voxels = dims[0] * dims[1] * dims[2];
156 if total_voxels > max_voxels {
157 return Err(ShellError::GridTooLarge {
158 dims,
159 total: total_voxels,
160 max: max_voxels,
161 });
162 }
163
164 info!(
165 dims = ?dims,
166 total = total_voxels,
167 voxel_size_mm = voxel_size,
168 "Creating SDF grid"
169 );
170
171 Ok(Self {
172 dims,
173 origin,
174 voxel_size,
175 values: vec![0.0; total_voxels],
176 offsets: vec![0.0; total_voxels],
177 })
178 }
179
180 #[inline]
182 pub fn total_voxels(&self) -> usize {
183 self.dims[0] * self.dims[1] * self.dims[2]
184 }
185
186 #[inline]
188 pub fn linearize(&self, x: usize, y: usize, z: usize) -> usize {
189 x + y * self.dims[0] + z * self.dims[0] * self.dims[1]
190 }
191
192 #[inline]
194 pub fn delinearize(&self, idx: usize) -> [usize; 3] {
195 let z = idx / (self.dims[0] * self.dims[1]);
196 let rem = idx % (self.dims[0] * self.dims[1]);
197 let y = rem / self.dims[0];
198 let x = rem % self.dims[0];
199 [x, y, z]
200 }
201
202 #[inline]
204 pub fn voxel_center(&self, x: usize, y: usize, z: usize) -> Point3<f64> {
205 Point3::new(
206 self.origin.x + (x as f64 + 0.5) * self.voxel_size,
207 self.origin.y + (y as f64 + 0.5) * self.voxel_size,
208 self.origin.z + (z as f64 + 0.5) * self.voxel_size,
209 )
210 }
211
212 pub fn compute_sdf(&mut self, mesh: &Mesh) {
214 self.compute_sdf_cpu(mesh);
215 }
216
217 pub fn compute_sdf_with_params(&mut self, mesh: &Mesh, params: &SdfOffsetParams) {
223 #[cfg(feature = "gpu")]
224 if params.use_gpu {
225 if self.try_compute_sdf_gpu(mesh) {
226 return;
227 }
228 info!("GPU unavailable or failed, falling back to CPU");
229 }
230
231 #[cfg(not(feature = "gpu"))]
232 if params.use_gpu {
233 info!("GPU feature not enabled, using CPU");
234 }
235
236 self.compute_sdf_cpu(mesh);
237 }
238
239 fn compute_sdf_cpu(&mut self, mesh: &Mesh) {
241 use mesh_to_sdf::{Grid, SignMethod, Topology, generate_grid_sdf};
242
243 info!(vertices = mesh.vertices.len(), "Computing SDF (CPU)");
244
245 let vertices: Vec<[f32; 3]> = mesh
247 .vertices
248 .iter()
249 .map(|v| {
250 [
251 v.position.x as f32,
252 v.position.y as f32,
253 v.position.z as f32,
254 ]
255 })
256 .collect();
257
258 let indices: Vec<u32> = mesh.faces.iter().flat_map(|f| f.iter().copied()).collect();
259
260 let grid = Grid::from_bounding_box(
262 &[
263 self.origin.x as f32,
264 self.origin.y as f32,
265 self.origin.z as f32,
266 ],
267 &[
268 (self.origin.x + self.dims[0] as f64 * self.voxel_size) as f32,
269 (self.origin.y + self.dims[1] as f64 * self.voxel_size) as f32,
270 (self.origin.z + self.dims[2] as f64 * self.voxel_size) as f32,
271 ],
272 [self.dims[0], self.dims[1], self.dims[2]],
273 );
274
275 let sdf_values = generate_grid_sdf(
277 &vertices,
278 Topology::TriangleList(Some(&indices)),
279 &grid,
280 SignMethod::Raycast,
281 );
282
283 self.values = sdf_values;
284
285 debug!(
286 min_sdf = self.values.iter().copied().fold(f32::INFINITY, f32::min),
287 max_sdf = self
288 .values
289 .iter()
290 .copied()
291 .fold(f32::NEG_INFINITY, f32::max),
292 "SDF computed (CPU)"
293 );
294 }
295
296 #[cfg(feature = "gpu")]
300 fn try_compute_sdf_gpu(&mut self, mesh: &Mesh) -> bool {
301 use mesh_gpu::{GpuSdfParams, try_compute_sdf_gpu};
302
303 let params = GpuSdfParams {
304 dims: self.dims,
305 origin: [
306 self.origin.x as f32,
307 self.origin.y as f32,
308 self.origin.z as f32,
309 ],
310 voxel_size: self.voxel_size as f32,
311 };
312
313 match try_compute_sdf_gpu(mesh, ¶ms) {
314 Some(result) => {
315 info!(time_ms = result.compute_time_ms, "SDF computed (GPU)");
316 self.values = result.values;
317 true
318 }
319 None => false,
320 }
321 }
322
323 pub fn interpolate_offsets(&mut self, mesh: &Mesh, k_neighbors: usize) {
327 use kiddo::KdTree;
328
329 info!(
330 voxels = self.total_voxels(),
331 vertices = mesh.vertices.len(),
332 k = k_neighbors,
333 "Interpolating offsets (building KD-tree)"
334 );
335
336 let mut kdtree: KdTree<f64, 3> = KdTree::new();
338 for (i, v) in mesh.vertices.iter().enumerate() {
339 kdtree.add(&[v.position.x, v.position.y, v.position.z], i as u64);
340 }
341
342 let vertex_offsets: Vec<f32> = mesh
344 .vertices
345 .iter()
346 .map(|v| v.offset.unwrap_or(0.0))
347 .collect();
348
349 info!(
350 "KD-tree built, interpolating {} voxels",
351 self.total_voxels()
352 );
353
354 let [_dim_x, dim_y, dim_z] = self.dims;
355 let voxel_size = self.voxel_size;
356 let origin = self.origin;
357
358 let offsets: Vec<f32> = (0..self.total_voxels())
361 .into_par_iter()
362 .map(|idx| {
363 let z = idx % dim_z;
365 let y = (idx / dim_z) % dim_y;
366 let x = idx / (dim_y * dim_z);
367
368 let voxel_pos = nalgebra::Point3::new(
370 origin.x + (x as f64 + 0.5) * voxel_size,
371 origin.y + (y as f64 + 0.5) * voxel_size,
372 origin.z + (z as f64 + 0.5) * voxel_size,
373 );
374
375 let query = [voxel_pos.x, voxel_pos.y, voxel_pos.z];
377 let nearest = kdtree.nearest_n::<kiddo::SquaredEuclidean>(&query, k_neighbors);
378
379 let mut total_weight = 0.0;
381 let mut weighted_offset = 0.0;
382 for neighbor in nearest {
383 let dist = neighbor.distance.sqrt();
384 let w = 1.0 / (dist + 0.001);
385 total_weight += w;
386 weighted_offset += w * vertex_offsets[neighbor.item as usize] as f64;
387 }
388
389 if total_weight > 0.0 {
390 (weighted_offset / total_weight) as f32
391 } else {
392 0.0
393 }
394 })
395 .collect();
396
397 self.offsets = offsets;
398
399 debug!(
400 min_offset = self.offsets.iter().copied().fold(f32::INFINITY, f32::min),
401 max_offset = self
402 .offsets
403 .iter()
404 .copied()
405 .fold(f32::NEG_INFINITY, f32::max),
406 "Offsets interpolated"
407 );
408 }
409
410 pub fn apply_variable_offset(&mut self) {
414 info!("Applying variable offset to SDF");
415
416 self.values
418 .par_iter_mut()
419 .zip(self.offsets.par_iter())
420 .for_each(|(sdf, offset)| {
421 *sdf -= *offset;
422 });
423
424 debug!(
425 min_adjusted = self.values.iter().copied().fold(f32::INFINITY, f32::min),
426 max_adjusted = self
427 .values
428 .iter()
429 .copied()
430 .fold(f32::NEG_INFINITY, f32::max),
431 "Variable offset applied"
432 );
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use mesh_repair::Vertex;
440
441 fn create_unit_cube() -> Mesh {
442 let mut mesh = Mesh::new();
443
444 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
446 mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
447 mesh.vertices.push(Vertex::from_coords(10.0, 10.0, 0.0));
448 mesh.vertices.push(Vertex::from_coords(0.0, 10.0, 0.0));
449 mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 10.0));
450 mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 10.0));
451 mesh.vertices.push(Vertex::from_coords(10.0, 10.0, 10.0));
452 mesh.vertices.push(Vertex::from_coords(0.0, 10.0, 10.0));
453
454 mesh.faces.push([0, 1, 2]);
456 mesh.faces.push([0, 2, 3]);
457 mesh.faces.push([4, 6, 5]);
458 mesh.faces.push([4, 7, 6]);
459 mesh.faces.push([0, 5, 1]);
460 mesh.faces.push([0, 4, 5]);
461 mesh.faces.push([2, 7, 3]);
462 mesh.faces.push([2, 6, 7]);
463 mesh.faces.push([0, 3, 7]);
464 mesh.faces.push([0, 7, 4]);
465 mesh.faces.push([1, 5, 6]);
466 mesh.faces.push([1, 6, 2]);
467
468 mesh
469 }
470
471 #[test]
472 fn test_grid_construction() {
473 let mesh = create_unit_cube();
474 let grid = SdfGrid::from_mesh_bounds(&mesh, 1.0, 5.0, 1_000_000).unwrap();
475
476 assert_eq!(grid.dims[0], 20);
478 assert_eq!(grid.dims[1], 20);
479 assert_eq!(grid.dims[2], 20);
480 assert_eq!(grid.total_voxels(), 8000);
481 }
482
483 #[test]
484 fn test_grid_too_large() {
485 let mesh = create_unit_cube();
486 let result = SdfGrid::from_mesh_bounds(&mesh, 0.1, 5.0, 1000);
487 assert!(result.is_err());
488 }
489
490 #[test]
491 fn test_sdf_offset_params_default() {
492 let params = SdfOffsetParams::default();
493 assert!(!params.adaptive_resolution);
494 assert_eq!(params.voxel_size_mm, 0.75);
495 }
496
497 #[test]
498 fn test_sdf_offset_params_adaptive() {
499 let params = SdfOffsetParams::adaptive();
500 assert!(params.adaptive_resolution);
501 assert!(params.coarse_voxel_size_mm() > params.voxel_size_mm);
502 }
503
504 #[test]
505 fn test_sdf_offset_params_presets() {
506 let hq = SdfOffsetParams::adaptive_high_quality();
507 let large = SdfOffsetParams::adaptive_large_mesh();
508
509 assert!(hq.voxel_size_mm < large.voxel_size_mm);
511
512 assert!(hq.adaptive_resolution);
514 assert!(large.adaptive_resolution);
515 }
516
517 #[test]
518 fn test_coarse_voxel_size() {
519 let params = SdfOffsetParams {
520 voxel_size_mm: 1.0,
521 coarse_voxel_multiplier: 4.0,
522 ..Default::default()
523 };
524 assert_eq!(params.coarse_voxel_size_mm(), 4.0);
525 }
526
527 #[test]
528 fn test_sdf_offset_params_with_gpu() {
529 let params = SdfOffsetParams::default().with_gpu();
530 assert!(params.use_gpu);
531 assert!(!params.adaptive_resolution); }
533
534 #[test]
535 fn test_sdf_offset_params_gpu_default() {
536 let params = SdfOffsetParams::default();
537 assert!(!params.use_gpu); }
539}