1#![allow(clippy::needless_range_loop)]
6use rayon::prelude::*;
7
8use super::types::{SdfGrid, SphereTraceResult, Triangle};
9
10pub fn fast_sweeping_update(grid: &mut SdfGrid) {
12 let nx = grid.nx;
13 let ny = grid.ny;
14 let nz = grid.nz;
15 let dx = grid.dx;
16 for i in 0..nx {
17 for j in 0..ny {
18 for k in 0..nz {
19 let cur = grid.get(i, j, k);
20 let mut best = cur;
21 if i > 0 {
22 let candidate = grid.get(i - 1, j, k) + dx;
23 if candidate < best {
24 best = candidate;
25 }
26 }
27 if i + 1 < nx {
28 let candidate = grid.get(i + 1, j, k) + dx;
29 if candidate < best {
30 best = candidate;
31 }
32 }
33 if j > 0 {
34 let candidate = grid.get(i, j - 1, k) + dx;
35 if candidate < best {
36 best = candidate;
37 }
38 }
39 if j + 1 < ny {
40 let candidate = grid.get(i, j + 1, k) + dx;
41 if candidate < best {
42 best = candidate;
43 }
44 }
45 if k > 0 {
46 let candidate = grid.get(i, j, k - 1) + dx;
47 if candidate < best {
48 best = candidate;
49 }
50 }
51 if k + 1 < nz {
52 let candidate = grid.get(i, j, k + 1) + dx;
53 if candidate < best {
54 best = candidate;
55 }
56 }
57 if best < cur {
58 grid.set(i, j, k, best);
59 }
60 }
61 }
62 }
63}
64pub fn union_sdf(a: &SdfGrid, b: &SdfGrid) -> SdfGrid {
66 assert_eq!(a.nx, b.nx, "union_sdf: nx mismatch");
67 assert_eq!(a.ny, b.ny, "union_sdf: ny mismatch");
68 assert_eq!(a.nz, b.nz, "union_sdf: nz mismatch");
69 let values: Vec<f64> = a
70 .values
71 .par_iter()
72 .zip(b.values.par_iter())
73 .map(|(&av, &bv)| av.min(bv))
74 .collect();
75 SdfGrid {
76 nx: a.nx,
77 ny: a.ny,
78 nz: a.nz,
79 dx: a.dx,
80 origin: a.origin,
81 values,
82 }
83}
84pub fn intersection_sdf(a: &SdfGrid, b: &SdfGrid) -> SdfGrid {
86 assert_eq!(a.nx, b.nx, "intersection_sdf: nx mismatch");
87 assert_eq!(a.ny, b.ny, "intersection_sdf: ny mismatch");
88 assert_eq!(a.nz, b.nz, "intersection_sdf: nz mismatch");
89 let values: Vec<f64> = a
90 .values
91 .par_iter()
92 .zip(b.values.par_iter())
93 .map(|(&av, &bv)| av.max(bv))
94 .collect();
95 SdfGrid {
96 nx: a.nx,
97 ny: a.ny,
98 nz: a.nz,
99 dx: a.dx,
100 origin: a.origin,
101 values,
102 }
103}
104pub fn subtraction_sdf(a: &SdfGrid, b: &SdfGrid) -> SdfGrid {
108 assert_eq!(a.nx, b.nx, "subtraction_sdf: nx mismatch");
109 assert_eq!(a.ny, b.ny, "subtraction_sdf: ny mismatch");
110 assert_eq!(a.nz, b.nz, "subtraction_sdf: nz mismatch");
111 let values: Vec<f64> = a
112 .values
113 .par_iter()
114 .zip(b.values.par_iter())
115 .map(|(&av, &bv)| av.max(-bv))
116 .collect();
117 SdfGrid {
118 nx: a.nx,
119 ny: a.ny,
120 nz: a.nz,
121 dx: a.dx,
122 origin: a.origin,
123 values,
124 }
125}
126pub fn shell_sdf(grid: &SdfGrid, thickness: f64) -> SdfGrid {
128 let half = thickness / 2.0;
129 let values: Vec<f64> = grid.values.par_iter().map(|&v| v.abs() - half).collect();
130 SdfGrid {
131 nx: grid.nx,
132 ny: grid.ny,
133 nz: grid.nz,
134 dx: grid.dx,
135 origin: grid.origin,
136 values,
137 }
138}
139pub fn smooth_union_sdf(a: &SdfGrid, b: &SdfGrid, k: f64) -> SdfGrid {
143 assert_eq!(a.nx, b.nx);
144 assert_eq!(a.ny, b.ny);
145 assert_eq!(a.nz, b.nz);
146 let values: Vec<f64> = a
147 .values
148 .par_iter()
149 .zip(b.values.par_iter())
150 .map(|(&av, &bv)| {
151 let h = (0.5 + 0.5 * (bv - av) / k).clamp(0.0, 1.0);
152 bv * (1.0 - h) + av * h - k * h * (1.0 - h)
153 })
154 .collect();
155 SdfGrid {
156 nx: a.nx,
157 ny: a.ny,
158 nz: a.nz,
159 dx: a.dx,
160 origin: a.origin,
161 values,
162 }
163}
164pub fn sphere_trace(
169 grid: &SdfGrid,
170 ray_origin: [f64; 3],
171 ray_direction: [f64; 3],
172 max_t: f64,
173 max_iterations: usize,
174 surface_threshold: f64,
175) -> SphereTraceResult {
176 let mut t = 0.0;
177 let mut pos = ray_origin;
178 for iter in 0..max_iterations {
179 let sdf_val = match grid.sample(pos) {
180 Some(v) => v,
181 None => {
182 return SphereTraceResult {
183 hit: false,
184 position: pos,
185 t,
186 iterations: iter,
187 };
188 }
189 };
190 if sdf_val < surface_threshold {
191 return SphereTraceResult {
192 hit: true,
193 position: pos,
194 t,
195 iterations: iter,
196 };
197 }
198 t += sdf_val;
199 if t > max_t {
200 return SphereTraceResult {
201 hit: false,
202 position: pos,
203 t,
204 iterations: iter,
205 };
206 }
207 pos = [
208 ray_origin[0] + ray_direction[0] * t,
209 ray_origin[1] + ray_direction[1] * t,
210 ray_origin[2] + ray_direction[2] * t,
211 ];
212 }
213 SphereTraceResult {
214 hit: false,
215 position: pos,
216 t,
217 iterations: max_iterations,
218 }
219}
220pub fn mesh_to_sdf(grid: &mut SdfGrid, vertices: &[[f64; 3]], triangles: &[[usize; 3]]) {
229 let ny = grid.ny;
230 let nz = grid.nz;
231 let dx = grid.dx;
232 let origin = grid.origin;
233 grid.values.par_iter_mut().enumerate().for_each(|(idx, v)| {
234 let i = idx / (ny * nz);
235 let j = (idx / nz) % ny;
236 let k = idx % nz;
237 let p = [
238 origin[0] + (i as f64 + 0.5) * dx,
239 origin[1] + (j as f64 + 0.5) * dx,
240 origin[2] + (k as f64 + 0.5) * dx,
241 ];
242 let mut min_dist = f64::MAX;
243 for tri in triangles {
244 let a = vertices[tri[0]];
245 let b = vertices[tri[1]];
246 let c = vertices[tri[2]];
247 let dist = point_triangle_distance(&p, &a, &b, &c);
248 if dist < min_dist {
249 min_dist = dist;
250 }
251 }
252 *v = min_dist;
253 });
254}
255pub(super) fn point_triangle_distance(
257 p: &[f64; 3],
258 a: &[f64; 3],
259 b: &[f64; 3],
260 c: &[f64; 3],
261) -> f64 {
262 let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
263 let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
264 let ap = [p[0] - a[0], p[1] - a[1], p[2] - a[2]];
265 let d1 = dot3(&ab, &ap);
266 let d2 = dot3(&ac, &ap);
267 if d1 <= 0.0 && d2 <= 0.0 {
268 return dist3(p, a);
269 }
270 let bp = [p[0] - b[0], p[1] - b[1], p[2] - b[2]];
271 let d3 = dot3(&ab, &bp);
272 let d4 = dot3(&ac, &bp);
273 if d3 >= 0.0 && d4 <= d3 {
274 return dist3(p, b);
275 }
276 let vc = d1 * d4 - d3 * d2;
277 if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
278 let v = d1 / (d1 - d3);
279 let proj = [a[0] + ab[0] * v, a[1] + ab[1] * v, a[2] + ab[2] * v];
280 return dist3(p, &proj);
281 }
282 let cp = [p[0] - c[0], p[1] - c[1], p[2] - c[2]];
283 let d5 = dot3(&ab, &cp);
284 let d6 = dot3(&ac, &cp);
285 if d6 >= 0.0 && d5 <= d6 {
286 return dist3(p, c);
287 }
288 let vb = d5 * d2 - d1 * d6;
289 if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
290 let w = d2 / (d2 - d6);
291 let proj = [a[0] + ac[0] * w, a[1] + ac[1] * w, a[2] + ac[2] * w];
292 return dist3(p, &proj);
293 }
294 let va = d3 * d6 - d5 * d4;
295 if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
296 let w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
297 let bc = [c[0] - b[0], c[1] - b[1], c[2] - b[2]];
298 let proj = [b[0] + bc[0] * w, b[1] + bc[1] * w, b[2] + bc[2] * w];
299 return dist3(p, &proj);
300 }
301 let denom = 1.0 / (va + vb + vc);
302 let v = vb * denom;
303 let w = vc * denom;
304 let proj = [
305 a[0] + ab[0] * v + ac[0] * w,
306 a[1] + ab[1] * v + ac[1] * w,
307 a[2] + ab[2] * v + ac[2] * w,
308 ];
309 dist3(p, &proj)
310}
311pub(super) fn dot3(a: &[f64; 3], b: &[f64; 3]) -> f64 {
312 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
313}
314pub(super) fn dist3(a: &[f64; 3], b: &[f64; 3]) -> f64 {
315 let dx = a[0] - b[0];
316 let dy = a[1] - b[1];
317 let dz = a[2] - b[2];
318 (dx * dx + dy * dy + dz * dz).sqrt()
319}
320pub fn evaluate_sdf_batch(grid: &SdfGrid, points: &[[f64; 3]]) -> Vec<f64> {
324 points
325 .par_iter()
326 .map(|&p| grid.sample(p).unwrap_or(f64::MAX))
327 .collect()
328}
329pub fn gradient_sdf_batch(grid: &SdfGrid, points: &[[f64; 3]]) -> Vec<[f64; 3]> {
331 points
332 .par_iter()
333 .map(|&p| grid.gradient_at_point(p).unwrap_or([0.0; 3]))
334 .collect()
335}
336pub fn count_interior_cells(grid: &SdfGrid) -> usize {
338 grid.values.par_iter().filter(|&&v| v < 0.0).count()
339}
340pub fn approximate_volume(grid: &SdfGrid) -> f64 {
344 let count = count_interior_cells(grid);
345 count as f64 * grid.dx * grid.dx * grid.dx
346}
347#[rustfmt::skip]
350pub(super) const MC_EDGE_TABLE: [u16; 256] = [
351 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06,
352 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
353 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x033, 0x13a,
354 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
355 0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6,
356 0xfaa, 0xea3, 0xda9, 0xca0, 0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c,
357 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa,
358 0x1f6, 0x0ff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
359 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56,
360 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc,
361 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca,
362 0xcc6, 0xdcf, 0xec5, 0xfcc, 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
363 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x055, 0x35f, 0x256,
364 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
365 0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a,
366 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460,
367 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6,
368 0x0aa, 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x835, 0xb3f, 0xa36,
369 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a,
370 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190,
371 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406,
372 0x30a, 0x203, 0x109, 0x000,
373];
374#[inline]
376pub(super) fn interpolate_vertex(
377 p1: [f64; 3],
378 p2: [f64; 3],
379 val1: f64,
380 val2: f64,
381 iso: f64,
382) -> [f64; 3] {
383 if (val2 - val1).abs() < 1e-15 {
384 return p1;
385 }
386 let t = (iso - val1) / (val2 - val1);
387 [
388 p1[0] + t * (p2[0] - p1[0]),
389 p1[1] + t * (p2[1] - p1[1]),
390 p1[2] + t * (p2[2] - p1[2]),
391 ]
392}
393pub fn marching_cubes(grid: &SdfGrid, isovalue: f64) -> Vec<Triangle> {
399 let nx = grid.nx;
400 let ny = grid.ny;
401 let nz = grid.nz;
402 if nx < 2 || ny < 2 || nz < 2 {
403 return Vec::new();
404 }
405 let mut triangles = Vec::new();
406 for i in 0..nx - 1 {
407 for j in 0..ny - 1 {
408 for k in 0..nz - 1 {
409 let corners: [[f64; 3]; 8] = [
410 grid.world_pos(i, j, k),
411 grid.world_pos(i + 1, j, k),
412 grid.world_pos(i + 1, j + 1, k),
413 grid.world_pos(i, j + 1, k),
414 grid.world_pos(i, j, k + 1),
415 grid.world_pos(i + 1, j, k + 1),
416 grid.world_pos(i + 1, j + 1, k + 1),
417 grid.world_pos(i, j + 1, k + 1),
418 ];
419 let vals: [f64; 8] = [
420 grid.get(i, j, k),
421 grid.get(i + 1, j, k),
422 grid.get(i + 1, j + 1, k),
423 grid.get(i, j + 1, k),
424 grid.get(i, j, k + 1),
425 grid.get(i + 1, j, k + 1),
426 grid.get(i + 1, j + 1, k + 1),
427 grid.get(i, j + 1, k + 1),
428 ];
429 let mut cube_idx = 0u8;
430 for c in 0..8 {
431 if vals[c] < isovalue {
432 cube_idx |= 1 << c;
433 }
434 }
435 let edge_flags = MC_EDGE_TABLE[cube_idx as usize];
436 if edge_flags == 0 {
437 continue;
438 }
439 let mut verts = [[0.0f64; 3]; 12];
440 if edge_flags & 0x001 != 0 {
441 verts[0] =
442 interpolate_vertex(corners[0], corners[1], vals[0], vals[1], isovalue);
443 }
444 if edge_flags & 0x002 != 0 {
445 verts[1] =
446 interpolate_vertex(corners[1], corners[2], vals[1], vals[2], isovalue);
447 }
448 if edge_flags & 0x004 != 0 {
449 verts[2] =
450 interpolate_vertex(corners[2], corners[3], vals[2], vals[3], isovalue);
451 }
452 if edge_flags & 0x008 != 0 {
453 verts[3] =
454 interpolate_vertex(corners[3], corners[0], vals[3], vals[0], isovalue);
455 }
456 if edge_flags & 0x010 != 0 {
457 verts[4] =
458 interpolate_vertex(corners[4], corners[5], vals[4], vals[5], isovalue);
459 }
460 if edge_flags & 0x020 != 0 {
461 verts[5] =
462 interpolate_vertex(corners[5], corners[6], vals[5], vals[6], isovalue);
463 }
464 if edge_flags & 0x040 != 0 {
465 verts[6] =
466 interpolate_vertex(corners[6], corners[7], vals[6], vals[7], isovalue);
467 }
468 if edge_flags & 0x080 != 0 {
469 verts[7] =
470 interpolate_vertex(corners[7], corners[4], vals[7], vals[4], isovalue);
471 }
472 if edge_flags & 0x100 != 0 {
473 verts[8] =
474 interpolate_vertex(corners[0], corners[4], vals[0], vals[4], isovalue);
475 }
476 if edge_flags & 0x200 != 0 {
477 verts[9] =
478 interpolate_vertex(corners[1], corners[5], vals[1], vals[5], isovalue);
479 }
480 if edge_flags & 0x400 != 0 {
481 verts[10] =
482 interpolate_vertex(corners[2], corners[6], vals[2], vals[6], isovalue);
483 }
484 if edge_flags & 0x800 != 0 {
485 verts[11] =
486 interpolate_vertex(corners[3], corners[7], vals[3], vals[7], isovalue);
487 }
488 let active: Vec<[f64; 3]> = (0..12)
489 .filter(|&e| edge_flags & (1 << e) != 0)
490 .map(|e| verts[e])
491 .collect();
492 if active.len() >= 3 {
493 for tri_idx in 1..active.len() - 1 {
494 triangles.push(Triangle {
495 v: [active[0], active[tri_idx], active[tri_idx + 1]],
496 });
497 }
498 }
499 }
500 }
501 }
502 triangles
503}
504pub fn mesh_triangle_count(grid: &SdfGrid, isovalue: f64) -> usize {
506 marching_cubes(grid, isovalue).len()
507}
508pub fn sdf_gaussian_blur(grid: &SdfGrid, sigma: f64) -> SdfGrid {
513 let nx = grid.nx;
514 let ny = grid.ny;
515 let nz = grid.nz;
516 let mut kernel = [[[0.0f64; 3]; 3]; 3];
517 let mut kernel_sum = 0.0;
518 let s2 = 2.0 * sigma * sigma;
519 for di in -1i32..=1 {
520 for dj in -1i32..=1 {
521 for dk in -1i32..=1 {
522 let r2 = (di * di + dj * dj + dk * dk) as f64;
523 let w = (-r2 / s2).exp();
524 kernel[(di + 1) as usize][(dj + 1) as usize][(dk + 1) as usize] = w;
525 kernel_sum += w;
526 }
527 }
528 }
529 let mut out = SdfGrid::new(nx, ny, nz, grid.dx, grid.origin);
530 for i in 0..nx {
531 for j in 0..ny {
532 for k in 0..nz {
533 let mut acc = 0.0;
534 let mut wt = 0.0;
535 for di in -1i32..=1 {
536 for dj in -1i32..=1 {
537 for dk in -1i32..=1 {
538 let ni = i as i32 + di;
539 let nj = j as i32 + dj;
540 let nk = k as i32 + dk;
541 if ni >= 0
542 && ni < nx as i32
543 && nj >= 0
544 && nj < ny as i32
545 && nk >= 0
546 && nk < nz as i32
547 {
548 let w =
549 kernel[(di + 1) as usize][(dj + 1) as usize][(dk + 1) as usize];
550 acc += w * grid.get(ni as usize, nj as usize, nk as usize);
551 wt += w;
552 }
553 }
554 }
555 }
556 let v = if wt > 1e-15 {
557 acc / wt
558 } else {
559 grid.get(i, j, k)
560 };
561 out.set(i, j, k, v);
562 }
563 }
564 }
565 let _ = kernel_sum;
566 out
567}
568pub fn sdf_laplacian_sharpen(grid: &SdfGrid, amount: f64) -> SdfGrid {
572 let nx = grid.nx;
573 let ny = grid.ny;
574 let nz = grid.nz;
575 let inv_dx2 = 1.0 / (grid.dx * grid.dx);
576 let mut out = SdfGrid::new(nx, ny, nz, grid.dx, grid.origin);
577 for i in 0..nx {
578 for j in 0..ny {
579 for k in 0..nz {
580 let v = grid.get(i, j, k);
581 let lx = if i > 0 && i + 1 < nx {
582 (grid.get(i + 1, j, k) - 2.0 * v + grid.get(i - 1, j, k)) * inv_dx2
583 } else {
584 0.0
585 };
586 let ly = if j > 0 && j + 1 < ny {
587 (grid.get(i, j + 1, k) - 2.0 * v + grid.get(i, j - 1, k)) * inv_dx2
588 } else {
589 0.0
590 };
591 let lz = if k > 0 && k + 1 < nz {
592 (grid.get(i, j, k + 1) - 2.0 * v + grid.get(i, j, k - 1)) * inv_dx2
593 } else {
594 0.0
595 };
596 out.set(i, j, k, v + amount * (lx + ly + lz));
597 }
598 }
599 }
600 out
601}
602pub fn sdf_dilate(grid: &SdfGrid, offset: f64) -> SdfGrid {
607 let values: Vec<f64> = grid.values.par_iter().map(|&v| v - offset).collect();
608 SdfGrid {
609 nx: grid.nx,
610 ny: grid.ny,
611 nz: grid.nz,
612 dx: grid.dx,
613 origin: grid.origin,
614 values,
615 }
616}
617pub fn sdf_erode(grid: &SdfGrid, offset: f64) -> SdfGrid {
621 sdf_dilate(grid, -offset)
622}
623pub fn sdf_open(grid: &SdfGrid, offset: f64) -> SdfGrid {
627 let eroded = sdf_erode(grid, offset);
628 sdf_dilate(&eroded, offset)
629}
630pub fn sdf_close(grid: &SdfGrid, offset: f64) -> SdfGrid {
634 let dilated = sdf_dilate(grid, offset);
635 sdf_erode(&dilated, offset)
636}
637pub fn sdf_offset_surface(grid: &SdfGrid, offset: f64) -> SdfGrid {
641 let values: Vec<f64> = grid.values.par_iter().map(|&v| v - offset).collect();
642 SdfGrid {
643 nx: grid.nx,
644 ny: grid.ny,
645 nz: grid.nz,
646 dx: grid.dx,
647 origin: grid.origin,
648 values,
649 }
650}
651pub fn sdf_laplacian_smooth(grid: &SdfGrid, n_iterations: usize, dt: f64) -> SdfGrid {
656 let mut current = SdfGrid {
657 nx: grid.nx,
658 ny: grid.ny,
659 nz: grid.nz,
660 dx: grid.dx,
661 origin: grid.origin,
662 values: grid.values.clone(),
663 };
664 for _ in 0..n_iterations {
665 let sharpened = sdf_laplacian_sharpen(¤t, -dt);
666 current = sharpened;
667 }
668 current
669}
670pub fn sdf_mean_curvature_smooth(grid: &SdfGrid, step: f64) -> SdfGrid {
675 sdf_laplacian_smooth(grid, 1, step)
676}
677#[cfg(test)]
678mod tests {
679 use super::*;
680 fn make_sphere_grid(nx: usize, dx: f64, center: [f64; 3], radius: f64) -> SdfGrid {
681 let origin = [0.0; 3];
682 let mut g = SdfGrid::new(nx, nx, nx, dx, origin);
683 g.compute_sphere_sdf(center, radius);
684 g
685 }
686 #[test]
687 fn test_sphere_center_is_negative_radius() {
688 let n = 21usize;
689 let dx = 0.1;
690 let radius = 0.4;
691 let mid = (n / 2) as f64 + 0.5;
692 let center = [mid * dx, mid * dx, mid * dx];
693 let g = make_sphere_grid(n, dx, center, radius);
694 let c = n / 2;
695 let sdf_val = g.get(c, c, c);
696 assert!(
697 (sdf_val - (-radius)).abs() < dx,
698 "centre value {sdf_val} should be close to -{radius}"
699 );
700 }
701 #[test]
702 fn test_box_far_outside_positive() {
703 let n = 11usize;
704 let dx = 0.1;
705 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
706 let box_center = [0.55, 0.55, 0.55];
707 let half_extents = [0.1, 0.1, 0.1];
708 g.compute_box_sdf(box_center, half_extents);
709 let v = g.get(0, 0, 0);
710 assert!(
711 v > 0.0,
712 "far-outside cell should have positive SDF, got {v}"
713 );
714 }
715 #[test]
716 fn test_gradient_on_sphere_surface_is_unit() {
717 let n = 41usize;
718 let dx = 0.05;
719 let radius = 0.5;
720 let mid = (n / 2) as f64 + 0.5;
721 let center = [mid * dx, mid * dx, mid * dx];
722 let g = make_sphere_grid(n, dx, center, radius);
723 let c = n / 2;
724 let surface_i = c + (radius / dx) as usize;
725 let grad = g.gradient_at(surface_i, c, c);
726 let mag = (grad[0].powi(2) + grad[1].powi(2) + grad[2].powi(2)).sqrt();
727 assert!(
728 (mag - 1.0).abs() < 0.1,
729 "gradient magnitude should be close to 1.0, got {mag}"
730 );
731 assert!(grad[0] > 0.5, "gradient should point outward, got {grad:?}");
732 }
733 #[test]
734 fn test_union_sdf_inside_either() {
735 let n = 21usize;
736 let dx = 0.1;
737 let radius = 0.3;
738 let origin = [0.0; 3];
739 let c_float = (n / 2) as f64 + 0.5;
740 let center_a = [(c_float - 3.0) * dx, c_float * dx, c_float * dx];
741 let center_b = [(c_float + 3.0) * dx, c_float * dx, c_float * dx];
742 let mut ga = SdfGrid::new(n, n, n, dx, origin);
743 ga.compute_sphere_sdf(center_a, radius);
744 let mut gb = SdfGrid::new(n, n, n, dx, origin);
745 gb.compute_sphere_sdf(center_b, radius);
746 let u = union_sdf(&ga, &gb);
747 let cy = n / 2;
748 let cz = n / 2;
749 let ia = n / 2 - 3;
750 assert!(
751 u.get(ia, cy, cz) < 0.0,
752 "inside sphere A should be negative in union"
753 );
754 let ib = n / 2 + 3;
755 assert!(
756 u.get(ib, cy, cz) < 0.0,
757 "inside sphere B should be negative in union"
758 );
759 }
760 #[test]
761 fn test_total_cells() {
762 let g = SdfGrid::new(4, 5, 6, 0.1, [0.0; 3]);
763 assert_eq!(g.total_cells(), 4 * 5 * 6);
764 }
765 #[test]
768 fn test_subtraction_sdf() {
769 let n = 11usize;
770 let dx = 0.2;
771 let origin = [0.0; 3];
772 let center = [1.1, 1.1, 1.1];
773 let mut small = SdfGrid::new(n, n, n, dx, origin);
774 small.compute_sphere_sdf(center, 0.3);
775 let mut large = SdfGrid::new(n, n, n, dx, origin);
776 large.compute_sphere_sdf(center, 0.5);
777 let result = subtraction_sdf(&small, &large);
778 let c = n / 2;
779 assert!(
780 result.get(c, c, c) > 0.0,
781 "subtraction centre should be positive, got {}",
782 result.get(c, c, c)
783 );
784 }
785 #[test]
787 fn test_intersection_sdf() {
788 let n = 21usize;
789 let dx = 0.1;
790 let origin = [0.0; 3];
791 let radius = 0.5;
792 let c = (n / 2) as f64 + 0.5;
793 let center_a = [(c - 1.0) * dx, c * dx, c * dx];
794 let center_b = [(c + 1.0) * dx, c * dx, c * dx];
795 let mut ga = SdfGrid::new(n, n, n, dx, origin);
796 ga.compute_sphere_sdf(center_a, radius);
797 let mut gb = SdfGrid::new(n, n, n, dx, origin);
798 gb.compute_sphere_sdf(center_b, radius);
799 let inter = intersection_sdf(&ga, &gb);
800 let mid = n / 2;
801 let val = inter.get(mid, mid, mid);
802 assert!(
803 val < 0.0,
804 "midpoint of intersection should be inside, got {val}"
805 );
806 }
807 #[test]
809 fn test_smooth_union() {
810 let n = 11usize;
811 let dx = 0.2;
812 let origin = [0.0; 3];
813 let radius = 0.3;
814 let c = (n / 2) as f64 + 0.5;
815 let center_a = [(c - 2.0) * dx, c * dx, c * dx];
816 let center_b = [(c + 2.0) * dx, c * dx, c * dx];
817 let mut ga = SdfGrid::new(n, n, n, dx, origin);
818 ga.compute_sphere_sdf(center_a, radius);
819 let mut gb = SdfGrid::new(n, n, n, dx, origin);
820 gb.compute_sphere_sdf(center_b, radius);
821 let su = smooth_union_sdf(&ga, &gb, 0.5);
822 let u = union_sdf(&ga, &gb);
823 let mid = n / 2;
824 assert!(
825 su.get(mid, mid, mid) <= u.get(mid, mid, mid) + 0.1,
826 "smooth union should not be much larger than union"
827 );
828 }
829 #[test]
831 fn test_sample_at_cell_center() {
832 let n = 11usize;
833 let dx = 0.1;
834 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
835 g.compute_sphere_sdf([0.55, 0.55, 0.55], 0.3);
836 let c = n / 2;
837 let pos = g.world_pos(c, c, c);
838 let sampled = g.sample(pos);
839 assert!(sampled.is_some());
840 let cell_val = g.get(c, c, c);
841 assert!(
842 (sampled.unwrap() - cell_val).abs() < 0.05,
843 "sampled = {:?}, cell = {cell_val}",
844 sampled
845 );
846 }
847 #[test]
849 fn test_sample_outside_grid() {
850 let g = SdfGrid::new(5, 5, 5, 0.1, [0.0; 3]);
851 assert!(g.sample([-1.0, -1.0, -1.0]).is_none());
852 }
853 #[test]
855 fn test_sphere_trace_hit() {
856 let n = 41usize;
857 let dx = 0.05;
858 let center = [1.0, 1.0, 1.0];
859 let radius = 0.3;
860 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
861 g.compute_sphere_sdf(center, radius);
862 let result = sphere_trace(&g, [0.1, 1.0, 1.0], [1.0, 0.0, 0.0], 5.0, 100, dx * 0.5);
863 assert!(result.hit, "ray should hit the sphere");
864 assert!(result.t > 0.0, "t should be positive");
865 }
866 #[test]
868 fn test_sphere_trace_miss() {
869 let n = 21usize;
870 let dx = 0.1;
871 let center = [1.0, 1.0, 1.0];
872 let radius = 0.3;
873 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
874 g.compute_sphere_sdf(center, radius);
875 let result = sphere_trace(&g, [0.1, 1.0, 1.0], [-1.0, 0.0, 0.0], 5.0, 100, dx * 0.5);
876 assert!(!result.hit, "ray should miss the sphere");
877 }
878 #[test]
880 fn test_point_triangle_distance_at_vertex() {
881 let a = [0.0, 0.0, 0.0];
882 let b = [1.0, 0.0, 0.0];
883 let c = [0.0, 1.0, 0.0];
884 let p = [-1.0, 0.0, 0.0];
885 let d = point_triangle_distance(&p, &a, &b, &c);
886 assert!((d - 1.0).abs() < 1e-10, "distance = {d}, expected 1.0");
887 }
888 #[test]
890 fn test_point_triangle_distance_above() {
891 let a = [0.0, 0.0, 0.0];
892 let b = [1.0, 0.0, 0.0];
893 let c = [0.0, 1.0, 0.0];
894 let p = [0.2, 0.2, 1.0];
895 let d = point_triangle_distance(&p, &a, &b, &c);
896 assert!((d - 1.0).abs() < 1e-10, "distance = {d}, expected 1.0");
897 }
898 #[test]
900 fn test_evaluate_sdf_batch() {
901 let n = 11usize;
902 let dx = 0.2;
903 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
904 g.compute_sphere_sdf([1.1, 1.1, 1.1], 0.5);
905 let points = vec![[1.1, 1.1, 1.1], [0.1, 0.1, 0.1]];
906 let vals = evaluate_sdf_batch(&g, &points);
907 assert_eq!(vals.len(), 2);
908 assert!(vals[0] < 0.0, "centre should be negative, got {}", vals[0]);
909 }
910 #[test]
912 fn test_approximate_volume_sphere() {
913 let n = 41usize;
914 let dx = 0.05;
915 let radius = 0.5;
916 let center = [1.0, 1.0, 1.0];
917 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
918 g.compute_sphere_sdf(center, radius);
919 let vol = approximate_volume(&g);
920 let expected = 4.0 / 3.0 * std::f64::consts::PI * radius.powi(3);
921 assert!(
922 (vol - expected).abs() / expected < 0.2,
923 "volume = {vol}, expected ~{expected}"
924 );
925 }
926 #[test]
928 fn test_cylinder_sdf_center_inside() {
929 let n = 21usize;
930 let dx = 0.1;
931 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
932 g.compute_cylinder_sdf([1.0, 1.0], 0.3, 1.2);
933 let c = n / 2;
934 let val = g.get(10, 10, c);
935 assert!(val < 0.0, "cylinder centre should be inside, got {val}");
936 }
937 #[test]
939 fn test_torus_sdf() {
940 let n = 21usize;
941 let dx = 0.1;
942 let center = [1.0, 1.0, 1.0];
943 let mut g = SdfGrid::new(n, n, n, dx, [0.0; 3]);
944 g.compute_torus_sdf(center, 0.4, 0.1);
945 let ring_x = ((center[0] + 0.4) / dx - 0.5) as usize;
946 let ring_y = (center[1] / dx - 0.5) as usize;
947 let ring_z = (center[2] / dx - 0.5) as usize;
948 if ring_x < n && ring_y < n && ring_z < n {
949 let val = g.get(ring_x, ring_y, ring_z);
950 assert!(val < 0.1, "ring point should be near surface, got {val}");
951 }
952 }
953}