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