1#[inline]
26fn len3(v: [f64; 3]) -> f64 {
27 (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
28}
29
30#[inline]
31fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
32 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
33}
34
35#[inline]
36fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
37 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
38}
39
40#[inline]
41fn scale3(v: [f64; 3], s: f64) -> [f64; 3] {
42 [v[0] * s, v[1] * s, v[2] * s]
43}
44
45#[inline]
46fn normalize3(v: [f64; 3]) -> [f64; 3] {
47 let l = len3(v).max(1e-300);
48 [v[0] / l, v[1] / l, v[2] / l]
49}
50
51#[inline]
53fn clamp(v: f64, lo: f64, hi: f64) -> f64 {
54 v.max(lo).min(hi)
55}
56
57pub trait ImplicitSurface {
68 fn eval(&self, p: [f64; 3]) -> f64;
70
71 fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
76 let eps = 1e-5;
77 let dx = self.eval([p[0] + eps, p[1], p[2]]) - self.eval([p[0] - eps, p[1], p[2]]);
78 let dy = self.eval([p[0], p[1] + eps, p[2]]) - self.eval([p[0], p[1] - eps, p[2]]);
79 let dz = self.eval([p[0], p[1], p[2] + eps]) - self.eval([p[0], p[1], p[2] - eps]);
80 [dx / (2.0 * eps), dy / (2.0 * eps), dz / (2.0 * eps)]
81 }
82}
83
84#[derive(Debug, Clone)]
97pub struct SphereSDF {
98 pub center: [f64; 3],
100 pub radius: f64,
102}
103
104impl SphereSDF {
105 pub fn new(center: [f64; 3], radius: f64) -> Self {
107 Self { center, radius }
108 }
109}
110
111impl ImplicitSurface for SphereSDF {
112 fn eval(&self, p: [f64; 3]) -> f64 {
113 len3(sub3(p, self.center)) - self.radius
114 }
115
116 fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
117 normalize3(sub3(p, self.center))
118 }
119}
120
121#[derive(Debug, Clone)]
134pub struct BoxSDF {
135 pub half_extents: [f64; 3],
137}
138
139impl BoxSDF {
140 pub fn new(half_extents: [f64; 3]) -> Self {
142 Self { half_extents }
143 }
144}
145
146impl ImplicitSurface for BoxSDF {
147 fn eval(&self, p: [f64; 3]) -> f64 {
148 let q = [
150 p[0].abs() - self.half_extents[0],
151 p[1].abs() - self.half_extents[1],
152 p[2].abs() - self.half_extents[2],
153 ];
154 let pos = [q[0].max(0.0), q[1].max(0.0), q[2].max(0.0)];
156 len3(pos) + q[0].max(q[1]).max(q[2]).min(0.0)
157 }
158}
159
160#[derive(Debug, Clone)]
174pub struct TorusSDF {
175 pub major_r: f64,
177 pub minor_r: f64,
179}
180
181impl TorusSDF {
182 pub fn new(major_r: f64, minor_r: f64) -> Self {
184 Self { major_r, minor_r }
185 }
186}
187
188impl ImplicitSurface for TorusSDF {
189 fn eval(&self, p: [f64; 3]) -> f64 {
190 let q_xz = ((p[0] * p[0] + p[2] * p[2]).sqrt() - self.major_r, p[1]);
192 (q_xz.0 * q_xz.0 + q_xz.1 * q_xz.1).sqrt() - self.minor_r
193 }
194}
195
196#[derive(Debug, Clone)]
210pub struct CylinderSDF {
211 pub radius: f64,
213 pub half_height: f64,
215}
216
217impl CylinderSDF {
218 pub fn new(radius: f64, half_height: f64) -> Self {
220 Self {
221 radius,
222 half_height,
223 }
224 }
225}
226
227impl ImplicitSurface for CylinderSDF {
228 fn eval(&self, p: [f64; 3]) -> f64 {
229 let d = [
230 (p[0] * p[0] + p[2] * p[2]).sqrt() - self.radius,
231 p[1].abs() - self.half_height,
232 ];
233 d[0].max(d[1]).min(0.0)
234 + [d[0].max(0.0), d[1].max(0.0)]
235 .iter()
236 .map(|v| v * v)
237 .sum::<f64>()
238 .sqrt()
239 }
240}
241
242#[derive(Debug, Clone)]
250pub struct UnionSDF<A, B> {
251 pub a: A,
253 pub b: B,
255}
256
257impl<A: ImplicitSurface, B: ImplicitSurface> UnionSDF<A, B> {
258 pub fn new(a: A, b: B) -> Self {
260 Self { a, b }
261 }
262}
263
264impl<A: ImplicitSurface, B: ImplicitSurface> ImplicitSurface for UnionSDF<A, B> {
265 fn eval(&self, p: [f64; 3]) -> f64 {
266 self.a.eval(p).min(self.b.eval(p))
267 }
268}
269
270#[derive(Debug, Clone)]
274pub struct IntersectionSDF<A, B> {
275 pub a: A,
277 pub b: B,
279}
280
281impl<A: ImplicitSurface, B: ImplicitSurface> IntersectionSDF<A, B> {
282 pub fn new(a: A, b: B) -> Self {
284 Self { a, b }
285 }
286}
287
288impl<A: ImplicitSurface, B: ImplicitSurface> ImplicitSurface for IntersectionSDF<A, B> {
289 fn eval(&self, p: [f64; 3]) -> f64 {
290 self.a.eval(p).max(self.b.eval(p))
291 }
292}
293
294#[derive(Debug, Clone)]
298pub struct DifferenceSDF<A, B> {
299 pub a: A,
301 pub b: B,
303}
304
305impl<A: ImplicitSurface, B: ImplicitSurface> DifferenceSDF<A, B> {
306 pub fn new(a: A, b: B) -> Self {
308 Self { a, b }
309 }
310}
311
312impl<A: ImplicitSurface, B: ImplicitSurface> ImplicitSurface for DifferenceSDF<A, B> {
313 fn eval(&self, p: [f64; 3]) -> f64 {
314 self.a.eval(p).max(-self.b.eval(p))
315 }
316}
317
318#[derive(Debug, Clone)]
327pub struct SmoothUnionSDF<A, B> {
328 pub a: A,
330 pub b: B,
332 pub k: f64,
334}
335
336impl<A: ImplicitSurface, B: ImplicitSurface> SmoothUnionSDF<A, B> {
337 pub fn new(a: A, b: B, k: f64) -> Self {
339 Self { a, b, k }
340 }
341}
342
343impl<A: ImplicitSurface, B: ImplicitSurface> ImplicitSurface for SmoothUnionSDF<A, B> {
344 fn eval(&self, p: [f64; 3]) -> f64 {
345 let da = self.a.eval(p);
346 let db = self.b.eval(p);
347 let h = clamp(0.5 + 0.5 * (db - da) / self.k, 0.0, 1.0);
348 da * h + db * (1.0 - h) - self.k * h * (1.0 - h)
349 }
350}
351
352#[derive(Debug, Clone)]
362pub struct OffsetSDF<S> {
363 pub inner: S,
365 pub offset: f64,
367}
368
369impl<S: ImplicitSurface> OffsetSDF<S> {
370 pub fn new(inner: S, offset: f64) -> Self {
372 Self { inner, offset }
373 }
374}
375
376impl<S: ImplicitSurface> ImplicitSurface for OffsetSDF<S> {
377 fn eval(&self, p: [f64; 3]) -> f64 {
378 self.inner.eval(p) - self.offset
379 }
380
381 fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
382 self.inner.gradient(p)
383 }
384}
385
386const EDGE_TABLE: [u16; 256] = [
396 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a,
397 0xd03, 0xe09, 0xf00, 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895,
398 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435,
399 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0x0aa,
400 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460,
401 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963,
402 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff,
403 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c,
404 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6,
405 0x2cf, 0x1c5, 0x0cc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9,
406 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9,
407 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x055, 0x35f, 0x256,
408 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc,
409 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f,
410 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3,
411 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
412 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x835, 0xb3f, 0xa36, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96,
414 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190, 0xf00, 0xe09,
415 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109,
416 0x000,
417];
418
419const CUBE_CORNERS: [[f64; 3]; 8] = [
421 [0.0, 0.0, 0.0],
422 [1.0, 0.0, 0.0],
423 [1.0, 1.0, 0.0],
424 [0.0, 1.0, 0.0],
425 [0.0, 0.0, 1.0],
426 [1.0, 0.0, 1.0],
427 [1.0, 1.0, 1.0],
428 [0.0, 1.0, 1.0],
429];
430
431const CUBE_EDGES: [[usize; 2]; 12] = [
433 [0, 1],
434 [1, 2],
435 [2, 3],
436 [3, 0],
437 [4, 5],
438 [5, 6],
439 [6, 7],
440 [7, 4],
441 [0, 4],
442 [1, 5],
443 [2, 6],
444 [3, 7],
445];
446
447fn interp_edge(p0: [f64; 3], v0: f64, p1: [f64; 3], v1: f64) -> [f64; 3] {
449 let dv = v1 - v0;
450 if dv.abs() < 1e-30 {
451 return [
452 (p0[0] + p1[0]) / 2.0,
453 (p0[1] + p1[1]) / 2.0,
454 (p0[2] + p1[2]) / 2.0,
455 ];
456 }
457 let t = -v0 / dv;
458 [
459 p0[0] + t * (p1[0] - p0[0]),
460 p0[1] + t * (p1[1] - p0[1]),
461 p0[2] + t * (p1[2] - p0[2]),
462 ]
463}
464
465pub fn marching_cubes(
476 sdf: &dyn ImplicitSurface,
477 bounds: [[f64; 2]; 3],
478 resolution: usize,
479) -> (Vec<[f64; 3]>, Vec<[usize; 3]>) {
480 let n = resolution.max(1);
481 let mut vertices: Vec<[f64; 3]> = Vec::new();
482 let mut triangles: Vec<[usize; 3]> = Vec::new();
483
484 let dx = (bounds[0][1] - bounds[0][0]) / n as f64;
485 let dy = (bounds[1][1] - bounds[1][0]) / n as f64;
486 let dz = (bounds[2][1] - bounds[2][0]) / n as f64;
487
488 for iz in 0..n {
489 for iy in 0..n {
490 for ix in 0..n {
491 let ox = bounds[0][0] + ix as f64 * dx;
492 let oy = bounds[1][0] + iy as f64 * dy;
493 let oz = bounds[2][0] + iz as f64 * dz;
494
495 let corner_pts: [[f64; 3]; 8] = std::array::from_fn(|c| {
497 [
498 ox + CUBE_CORNERS[c][0] * dx,
499 oy + CUBE_CORNERS[c][1] * dy,
500 oz + CUBE_CORNERS[c][2] * dz,
501 ]
502 });
503 let vals: [f64; 8] = std::array::from_fn(|c| sdf.eval(corner_pts[c]));
504
505 let mut cube_idx: usize = 0;
507 for (c, &val) in vals.iter().enumerate() {
508 if val < 0.0 {
509 cube_idx |= 1 << c;
510 }
511 }
512
513 let edge_flags = EDGE_TABLE[cube_idx];
514 if edge_flags == 0 {
515 continue;
516 }
517
518 let mut edge_verts: [Option<[f64; 3]>; 12] = [None; 12];
520 for e in 0..12 {
521 if edge_flags & (1 << e) != 0 {
522 let [i0, i1] = CUBE_EDGES[e];
523 edge_verts[e] = Some(interp_edge(
524 corner_pts[i0],
525 vals[i0],
526 corner_pts[i1],
527 vals[i1],
528 ));
529 }
530 }
531
532 let active: Vec<[f64; 3]> = (0..12).filter_map(|e| edge_verts[e]).collect();
535 if active.len() >= 3 {
536 let base = vertices.len();
537 for v in &active {
538 vertices.push(*v);
539 }
540 for k in 1..(active.len() - 1) {
541 triangles.push([base, base + k, base + k + 1]);
542 }
543 }
544 }
545 }
546 }
547
548 (vertices, triangles)
549}
550
551#[derive(Debug, Clone)]
563pub struct RBFImplicit {
564 pub centers: Vec<[f64; 3]>,
566 pub weights: Vec<f64>,
568}
569
570impl RBFImplicit {
571 pub fn new(centers: Vec<[f64; 3]>) -> Self {
573 let n = centers.len();
574 Self {
575 centers,
576 weights: vec![0.0; n],
577 }
578 }
579
580 pub fn fit(&mut self, pts: &[[f64; 3]], targets: &[f64], tol: f64, max_iter: usize) {
588 assert_eq!(pts.len(), targets.len());
589 assert_eq!(pts.len(), self.centers.len());
590 let n = pts.len();
591 let phi: Vec<Vec<f64>> = (0..n)
593 .map(|i| {
594 (0..n)
595 .map(|j| rbf_kernel(len3(sub3(pts[i], self.centers[j]))))
596 .collect()
597 })
598 .collect();
599 self.weights = rbf_cg(&phi, targets, tol, max_iter);
601 }
602
603 pub fn eval(&self, p: [f64; 3]) -> f64 {
605 self.centers
606 .iter()
607 .zip(self.weights.iter())
608 .map(|(c, w)| w * rbf_kernel(len3(sub3(p, *c))))
609 .sum()
610 }
611}
612
613fn rbf_kernel(r: f64) -> f64 {
615 r * r * (r + 1e-30).ln()
616}
617
618fn rbf_cg(a: &[Vec<f64>], b: &[f64], tol: f64, max_iter: usize) -> Vec<f64> {
620 let n = b.len();
621 let mut x = vec![0.0_f64; n];
622 let mut r: Vec<f64> = b.to_vec();
623 let mut p = r.clone();
624 let mut rsold: f64 = r.iter().map(|v| v * v).sum();
625
626 for _ in 0..max_iter {
627 let ap: Vec<f64> = (0..n)
628 .map(|i| a[i].iter().zip(p.iter()).map(|(aij, pj)| aij * pj).sum())
629 .collect();
630 let pap: f64 = p.iter().zip(ap.iter()).map(|(pi, api)| pi * api).sum();
631 if pap.abs() < 1e-300 {
632 break;
633 }
634 let alpha = rsold / pap;
635 for i in 0..n {
636 x[i] += alpha * p[i];
637 r[i] -= alpha * ap[i];
638 }
639 let rsnew: f64 = r.iter().map(|v| v * v).sum();
640 if rsnew.sqrt() < tol {
641 break;
642 }
643 let beta = rsnew / rsold;
644 for i in 0..n {
645 p[i] = r[i] + beta * p[i];
646 }
647 rsold = rsnew;
648 }
649 x
650}
651
652pub fn ray_march(
671 sdf: &dyn ImplicitSurface,
672 origin: [f64; 3],
673 direction: [f64; 3],
674 max_steps: usize,
675) -> Option<f64> {
676 let dir = normalize3(direction);
677 let max_dist = 1e6_f64;
678 let mut t = 0.0_f64;
679
680 for _ in 0..max_steps {
681 let p = add3(origin, scale3(dir, t));
682 let d = sdf.eval(p);
683 if d.abs() < 1e-5 {
684 return Some(t);
685 }
686 if t > max_dist {
687 break;
688 }
689 t += d.abs().max(1e-7);
691 }
692 None
693}
694
695pub fn surface_normal(sdf: &dyn ImplicitSurface, p: [f64; 3], eps: f64) -> [f64; 3] {
707 let gx = sdf.eval([p[0] + eps, p[1], p[2]]) - sdf.eval([p[0] - eps, p[1], p[2]]);
708 let gy = sdf.eval([p[0], p[1] + eps, p[2]]) - sdf.eval([p[0], p[1] - eps, p[2]]);
709 let gz = sdf.eval([p[0], p[1], p[2] + eps]) - sdf.eval([p[0], p[1], p[2] - eps]);
710 normalize3([gx, gy, gz])
711}
712
713pub fn is_inside(sdf: &dyn ImplicitSurface, p: [f64; 3]) -> bool {
725 sdf.eval(p) < 0.0
726}
727
728pub fn surface_bbox(
738 sdf: &dyn ImplicitSurface,
739 bounds: [[f64; 2]; 3],
740 res: usize,
741 threshold: f64,
742) -> [[f64; 2]; 3] {
743 let n = res.max(2);
744 let mut bbox = [
745 [f64::INFINITY, f64::NEG_INFINITY],
746 [f64::INFINITY, f64::NEG_INFINITY],
747 [f64::INFINITY, f64::NEG_INFINITY],
748 ];
749
750 let step: [f64; 3] = std::array::from_fn(|k| (bounds[k][1] - bounds[k][0]) / (n - 1) as f64);
751
752 for iz in 0..n {
753 for iy in 0..n {
754 for ix in 0..n {
755 let p = [
756 bounds[0][0] + ix as f64 * step[0],
757 bounds[1][0] + iy as f64 * step[1],
758 bounds[2][0] + iz as f64 * step[2],
759 ];
760 if sdf.eval(p).abs() < threshold {
761 for k in 0..3 {
762 bbox[k][0] = bbox[k][0].min(p[k]);
763 bbox[k][1] = bbox[k][1].max(p[k]);
764 }
765 }
766 }
767 }
768 }
769 bbox
770}
771
772#[cfg(test)]
777mod tests {
778 use super::*;
779
780 #[test]
785 fn test_sphere_eval_center_negative() {
786 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
787 assert!(s.eval([0.0, 0.0, 0.0]) < 0.0);
788 }
789
790 #[test]
791 fn test_sphere_eval_on_surface() {
792 let s = SphereSDF::new([0.0, 0.0, 0.0], 2.0);
793 let d = s.eval([2.0, 0.0, 0.0]);
794 assert!(d.abs() < 1e-12, "d = {d}");
795 }
796
797 #[test]
798 fn test_sphere_eval_outside_positive() {
799 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
800 assert!(s.eval([3.0, 0.0, 0.0]) > 0.0);
801 }
802
803 #[test]
804 fn test_sphere_gradient_unit_normal() {
805 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
806 let g = s.gradient([1.0, 0.0, 0.0]);
807 let l = len3(g);
808 assert!((l - 1.0).abs() < 1e-12, "gradient not unit: |g| = {l}");
809 }
810
811 #[test]
812 fn test_sphere_gradient_direction() {
813 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
814 let g = s.gradient([0.0, 1.0, 0.0]);
815 assert!(g[1] > 0.9);
817 assert!(g[0].abs() < 1e-12);
818 assert!(g[2].abs() < 1e-12);
819 }
820
821 #[test]
822 fn test_sphere_translated() {
823 let s = SphereSDF::new([1.0, 2.0, 3.0], 1.5);
824 assert!((s.eval([1.0, 2.0, 3.0]) + 1.5).abs() < 1e-12);
826 }
827
828 #[test]
833 fn test_box_eval_inside_negative() {
834 let b = BoxSDF::new([1.0, 1.0, 1.0]);
835 assert!(b.eval([0.0, 0.0, 0.0]) < 0.0);
836 }
837
838 #[test]
839 fn test_box_eval_on_face() {
840 let b = BoxSDF::new([1.0, 2.0, 3.0]);
841 let d = b.eval([1.0, 0.0, 0.0]);
842 assert!(d.abs() < 1e-12, "d = {d}");
843 }
844
845 #[test]
846 fn test_box_eval_outside_positive() {
847 let b = BoxSDF::new([1.0, 1.0, 1.0]);
848 assert!(b.eval([2.0, 0.0, 0.0]) > 0.0);
849 }
850
851 #[test]
852 fn test_box_eval_corner() {
853 let b = BoxSDF::new([1.0, 1.0, 1.0]);
854 let d = b.eval([2.0, 2.0, 2.0]);
856 let expected = (3.0_f64).sqrt();
857 assert!((d - expected).abs() < 1e-12);
858 }
859
860 #[test]
865 fn test_torus_eval_on_surface() {
866 let t = TorusSDF::new(2.0, 0.5);
867 let d = t.eval([2.5, 0.0, 0.0]);
869 assert!(d.abs() < 1e-12, "d = {d}");
870 }
871
872 #[test]
873 fn test_torus_eval_center_positive() {
874 let t = TorusSDF::new(2.0, 0.5);
875 assert!(t.eval([0.0, 0.0, 0.0]) > 0.0);
877 }
878
879 #[test]
880 fn test_torus_inside_tube_negative() {
881 let t = TorusSDF::new(2.0, 0.5);
882 assert!(t.eval([2.0, 0.0, 0.0]) < 0.0);
884 }
885
886 #[test]
891 fn test_cylinder_lateral_surface() {
892 let c = CylinderSDF::new(1.0, 2.0);
893 let d = c.eval([1.0, 0.0, 0.0]);
894 assert!(d.abs() < 1e-12, "d = {d}");
895 }
896
897 #[test]
898 fn test_cylinder_inside_negative() {
899 let c = CylinderSDF::new(1.0, 2.0);
900 assert!(c.eval([0.0, 0.0, 0.0]) < 0.0);
901 }
902
903 #[test]
904 fn test_cylinder_outside_positive() {
905 let c = CylinderSDF::new(1.0, 2.0);
906 assert!(c.eval([3.0, 0.0, 0.0]) > 0.0);
907 }
908
909 #[test]
914 fn test_union_inside_either() {
915 let a = SphereSDF::new([-1.0, 0.0, 0.0], 1.5);
916 let b = SphereSDF::new([1.0, 0.0, 0.0], 1.5);
917 let u = UnionSDF::new(a, b);
918 assert!(u.eval([-1.0, 0.0, 0.0]) < 0.0);
920 assert!(u.eval([1.0, 0.0, 0.0]) < 0.0);
921 }
922
923 #[test]
924 fn test_intersection_inside_both() {
925 let a = SphereSDF::new([0.0, 0.0, 0.0], 2.0);
926 let b = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
927 let i = IntersectionSDF::new(a, b);
928 assert!(i.eval([0.0, 0.0, 0.0]) < 0.0);
930 }
931
932 #[test]
933 fn test_intersection_outside_one() {
934 let a = SphereSDF::new([0.0, 0.0, 0.0], 2.0);
935 let b = SphereSDF::new([5.0, 0.0, 0.0], 1.0);
936 let i = IntersectionSDF::new(a, b);
937 assert!(i.eval([0.0, 0.0, 0.0]) > 0.0);
939 }
940
941 #[test]
942 fn test_difference_removes_region() {
943 let a = SphereSDF::new([0.0, 0.0, 0.0], 2.0);
944 let b = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
945 let d = DifferenceSDF::new(a, b);
946 assert!(d.eval([0.5, 0.0, 0.0]) > 0.0);
948 assert!(d.eval([1.5, 0.0, 0.0]) < 0.0);
950 }
951
952 #[test]
957 fn test_smooth_union_less_than_regular_union() {
958 let a = SphereSDF::new([-0.5, 0.0, 0.0], 1.0);
959 let b = SphereSDF::new([0.5, 0.0, 0.0], 1.0);
960 let su = SmoothUnionSDF::new(a.clone(), b.clone(), 0.5);
961 let u = UnionSDF::new(a, b);
962 let p = [0.0, 0.0, 0.0];
963 assert!(su.eval(p) <= u.eval(p) + 1e-12);
965 }
966
967 #[test]
972 fn test_offset_expands_sphere() {
973 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
974 let os = OffsetSDF::new(s, 0.5);
975 let d = os.eval([1.5, 0.0, 0.0]);
977 assert!(d.abs() < 1e-12, "d = {d}");
978 }
979
980 #[test]
981 fn test_offset_shrinks_sphere() {
982 let s = SphereSDF::new([0.0, 0.0, 0.0], 2.0);
983 let os = OffsetSDF::new(s, -0.5);
984 let d = os.eval([1.5, 0.0, 0.0]);
986 assert!(d.abs() < 1e-12, "d = {d}");
987 }
988
989 #[test]
994 fn test_is_inside_sphere() {
995 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
996 assert!(is_inside(&s, [0.0, 0.0, 0.0]));
997 assert!(!is_inside(&s, [2.0, 0.0, 0.0]));
998 }
999
1000 #[test]
1001 fn test_surface_normal_sphere_unit_length() {
1002 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
1003 let n = surface_normal(&s, [1.0, 0.0, 0.0], 1e-5);
1004 let l = len3(n);
1005 assert!((l - 1.0).abs() < 1e-3, "normal length = {l}");
1006 }
1007
1008 #[test]
1013 fn test_marching_cubes_sphere_produces_vertices() {
1014 let s = SphereSDF::new([0.0, 0.0, 0.0], 0.4);
1015 let bounds = [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]];
1016 let (verts, tris) = marching_cubes(&s, bounds, 8);
1017 assert!(!verts.is_empty(), "no vertices produced");
1018 assert!(!tris.is_empty(), "no triangles produced");
1019 }
1020
1021 #[test]
1022 fn test_marching_cubes_indices_in_bounds() {
1023 let s = SphereSDF::new([0.0, 0.0, 0.0], 0.4);
1024 let bounds = [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]];
1025 let (verts, tris) = marching_cubes(&s, bounds, 6);
1026 for tri in &tris {
1027 for &idx in tri {
1028 assert!(idx < verts.len(), "index {idx} out of range");
1029 }
1030 }
1031 }
1032
1033 #[test]
1034 fn test_marching_cubes_empty_for_no_surface() {
1035 let s = SphereSDF::new([100.0, 100.0, 100.0], 0.1);
1037 let bounds = [[-1.0, 1.0], [-1.0, 1.0], [-1.0, 1.0]];
1038 let (verts, tris) = marching_cubes(&s, bounds, 4);
1039 assert!(verts.is_empty());
1040 assert!(tris.is_empty());
1041 }
1042
1043 #[test]
1048 fn test_rbf_fit_interpolates_at_centers() {
1049 let centers = vec![[0.0, 0.0, 0.0_f64], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
1050 let targets = vec![0.0, 1.0, -1.0];
1051 let mut rbf = RBFImplicit::new(centers.clone());
1052 rbf.fit(¢ers, &targets, 1e-10, 200);
1053 for (c, &t) in centers.iter().zip(targets.iter()) {
1055 let v = rbf.eval(*c);
1056 assert!((v - t).abs() < 0.5, "RBF({c:?}) = {v}, expected {t}");
1057 }
1058 }
1059
1060 #[test]
1061 fn test_rbf_zero_weights_before_fit() {
1062 let centers = vec![[0.0, 0.0, 0.0_f64]];
1063 let rbf = RBFImplicit::new(centers);
1064 assert_eq!(rbf.weights, vec![0.0]);
1065 }
1066
1067 #[test]
1072 fn test_ray_march_sphere_hit() {
1073 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
1074 let hit = ray_march(&s, [-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], 200);
1075 assert!(hit.is_some(), "ray should hit sphere");
1076 let t = hit.unwrap();
1077 let px = -5.0 + t;
1079 assert!((px + 1.0).abs() < 0.01, "hit at x = {px}, expected -1");
1080 }
1081
1082 #[test]
1083 fn test_ray_march_sphere_miss() {
1084 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
1085 let hit = ray_march(&s, [0.0, 100.0, 0.0], [1.0, 0.0, 0.0], 50);
1087 assert!(hit.is_none(), "ray should miss sphere");
1088 }
1089
1090 #[test]
1091 fn test_ray_march_returns_positive_distance() {
1092 let s = SphereSDF::new([0.0, 0.0, 0.0], 1.0);
1093 let hit = ray_march(&s, [-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], 200);
1094 if let Some(t) = hit {
1095 assert!(t > 0.0, "distance must be positive, got {t}");
1096 }
1097 }
1098}