#[derive(Debug, Clone, PartialEq)]
pub enum SdfPrimitive {
Sphere {
center: [f64; 3],
radius: f64,
},
Box3d {
center: [f64; 3],
half_extents: [f64; 3],
},
Cylinder {
center: [f64; 3],
radius: f64,
height: f64,
},
Torus {
center: [f64; 3],
r_major: f64,
r_minor: f64,
},
}
impl SdfPrimitive {
pub fn evaluate(&self, p: [f64; 3]) -> f64 {
match self {
SdfPrimitive::Sphere { center, radius } => sdf_sphere(p, *center, *radius),
SdfPrimitive::Box3d {
center,
half_extents,
} => sdf_box(p, *center, *half_extents),
SdfPrimitive::Cylinder {
center,
radius,
height,
} => sdf_cylinder(p, *center, *radius, *height),
SdfPrimitive::Torus {
center,
r_major,
r_minor,
} => sdf_torus(p, *center, *r_major, *r_minor),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SdfOp {
Union,
Intersection,
Subtraction,
SmoothUnion {
k: f64,
},
Offset {
d: f64,
},
}
impl SdfOp {
pub fn apply(&self, d1: f64, d2: f64) -> f64 {
match self {
SdfOp::Union => d1.min(d2),
SdfOp::Intersection => d1.max(d2),
SdfOp::Subtraction => d1.max(-d2),
SdfOp::SmoothUnion { k } => sdf_smooth_union(d1, d2, *k),
SdfOp::Offset { d } => d1 + d,
}
}
}
#[derive(Debug, Clone)]
pub enum SdfNode {
Leaf(SdfPrimitive),
Op {
op: SdfOp,
left: Box<SdfNode>,
right: Box<SdfNode>,
},
}
impl SdfNode {
pub fn leaf(prim: SdfPrimitive) -> Self {
SdfNode::Leaf(prim)
}
pub fn op(op: SdfOp, left: SdfNode, right: SdfNode) -> Self {
SdfNode::Op {
op,
left: Box::new(left),
right: Box::new(right),
}
}
pub fn evaluate(&self, p: [f64; 3]) -> f64 {
match self {
SdfNode::Leaf(prim) => prim.evaluate(p),
SdfNode::Op { op, left, right } => {
let d1 = left.evaluate(p);
let d2 = right.evaluate(p);
op.apply(d1, d2)
}
}
}
}
#[derive(Debug, Clone)]
pub struct SdfGrid {
pub dimensions: [usize; 3],
pub spacing: f64,
pub origin: [f64; 3],
pub data: Vec<f32>,
}
impl SdfGrid {
pub fn new(dimensions: [usize; 3], spacing: f64, origin: [f64; 3]) -> Self {
let n = dimensions[0] * dimensions[1] * dimensions[2];
Self {
dimensions,
spacing,
origin,
data: vec![f32::MAX; n],
}
}
pub fn voxel_center(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
[
self.origin[0] + self.spacing * (i as f64 + 0.5),
self.origin[1] + self.spacing * (j as f64 + 0.5),
self.origin[2] + self.spacing * (k as f64 + 0.5),
]
}
pub fn index(&self, i: usize, j: usize, k: usize) -> usize {
i + self.dimensions[0] * (j + self.dimensions[1] * k)
}
pub fn compute_from_sdf(&mut self, sdf: &dyn Fn([f64; 3]) -> f64) {
let [nx, ny, nz] = self.dimensions;
for k in 0..nz {
for j in 0..ny {
for i in 0..nx {
let p = self.voxel_center(i, j, k);
let idx = self.index(i, j, k);
self.data[idx] = sdf(p) as f32;
}
}
}
}
pub fn len(&self) -> usize {
self.dimensions[0] * self.dimensions[1] * self.dimensions[2]
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Default)]
pub struct GpuSdfCompute;
impl GpuSdfCompute {
pub fn new() -> Self {
Self
}
pub fn dispatch_compute(&self, grid: &mut SdfGrid, primitives: &[SdfPrimitive]) {
if primitives.is_empty() {
return;
}
grid.compute_from_sdf(&|p| {
primitives
.iter()
.map(|prim| prim.evaluate(p))
.fold(f64::INFINITY, f64::min)
});
}
}
pub fn sdf_sphere(p: [f64; 3], center: [f64; 3], r: f64) -> f64 {
let dx = p[0] - center[0];
let dy = p[1] - center[1];
let dz = p[2] - center[2];
(dx * dx + dy * dy + dz * dz).sqrt() - r
}
pub fn sdf_box(p: [f64; 3], center: [f64; 3], b: [f64; 3]) -> f64 {
let qx = (p[0] - center[0]).abs() - b[0];
let qy = (p[1] - center[1]).abs() - b[1];
let qz = (p[2] - center[2]).abs() - b[2];
let outer =
(qx.max(0.0) * qx.max(0.0) + qy.max(0.0) * qy.max(0.0) + qz.max(0.0) * qz.max(0.0)).sqrt();
let inner = qx.max(qy).max(qz).min(0.0);
outer + inner
}
#[allow(dead_code)]
pub fn sdf_cylinder(p: [f64; 3], center: [f64; 3], radius: f64, height: f64) -> f64 {
let dx = p[0] - center[0];
let dz = p[2] - center[2];
let radial = (dx * dx + dz * dz).sqrt() - radius;
let axial = (p[1] - center[1]).abs() - height * 0.5;
let outer = (radial.max(0.0) * radial.max(0.0) + axial.max(0.0) * axial.max(0.0)).sqrt();
let inner = radial.max(axial).min(0.0);
outer + inner
}
#[allow(dead_code)]
pub fn sdf_torus(p: [f64; 3], center: [f64; 3], r_major: f64, r_minor: f64) -> f64 {
let dx = p[0] - center[0];
let dy = p[1] - center[1];
let dz = p[2] - center[2];
let xz_dist = (dx * dx + dz * dz).sqrt();
let qx = xz_dist - r_major;
let qy = dy;
(qx * qx + qy * qy).sqrt() - r_minor
}
pub fn sdf_smooth_union(d1: f64, d2: f64, k: f64) -> f64 {
if k <= 0.0 {
return d1.min(d2);
}
let h = (0.5 + 0.5 * (d2 - d1) / k).clamp(0.0, 1.0);
d1 * (1.0 - h) + d2 * h - k * h * (1.0 - h)
}
pub fn sdf_gradient(sdf: &dyn Fn([f64; 3]) -> f64, p: [f64; 3]) -> [f64; 3] {
const EPS: f64 = 1e-4;
let gx = sdf([p[0] + EPS, p[1], p[2]]) - sdf([p[0] - EPS, p[1], p[2]]);
let gy = sdf([p[0], p[1] + EPS, p[2]]) - sdf([p[0], p[1] - EPS, p[2]]);
let gz = sdf([p[0], p[1], p[2] + EPS]) - sdf([p[0], p[1], p[2] - EPS]);
let len = (gx * gx + gy * gy + gz * gz).sqrt();
if len < 1e-15 {
[0.0, 1.0, 0.0]
} else {
[gx / len, gy / len, gz / len]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sphere_surface() {
let d = sdf_sphere([1.0, 0.0, 0.0], [0.0; 3], 1.0);
assert!(d.abs() < 1e-10, "expected ~0, got {d}");
}
#[test]
fn test_sphere_outside() {
let d = sdf_sphere([2.0, 0.0, 0.0], [0.0; 3], 1.0);
assert!((d - 1.0).abs() < 1e-10);
}
#[test]
fn test_sphere_inside() {
let d = sdf_sphere([0.0; 3], [0.0; 3], 1.0);
assert!((d - (-1.0)).abs() < 1e-10);
}
#[test]
fn test_sphere_offset_center() {
let d = sdf_sphere([3.0, 0.0, 0.0], [2.0, 0.0, 0.0], 1.0);
assert!(d.abs() < 1e-10);
}
#[test]
fn test_sphere_diagonal() {
let expected = 3_f64.sqrt() - 1.0;
let d = sdf_sphere([1.0, 1.0, 1.0], [0.0; 3], 1.0);
assert!((d - expected).abs() < 1e-10);
}
#[test]
fn test_box_outside_x() {
let d = sdf_box([1.5, 0.0, 0.0], [0.0; 3], [0.5; 3]);
assert!((d - 1.0).abs() < 1e-10, "got {d}");
}
#[test]
fn test_box_inside() {
let d = sdf_box([0.0; 3], [0.0; 3], [1.0; 3]);
assert!(d < 0.0, "expected negative, got {d}");
}
#[test]
fn test_box_on_face() {
let d = sdf_box([1.0, 0.0, 0.0], [0.0; 3], [1.0; 3]);
assert!(d.abs() < 1e-10, "got {d}");
}
#[test]
fn test_box_corner() {
let d = sdf_box([1.0, 1.0, 1.0], [0.0; 3], [1.0; 3]);
assert!(d.abs() < 1e-10, "got {d}");
}
#[test]
fn test_box_asymmetric_extents() {
let d = sdf_box([2.0, 0.0, 0.0], [0.0; 3], [1.0, 2.0, 3.0]);
assert!((d - 1.0).abs() < 1e-10, "got {d}");
}
#[test]
fn test_smooth_union_zero_k_equals_min() {
let d1 = 1.0_f64;
let d2 = 2.0_f64;
assert!((sdf_smooth_union(d1, d2, 0.0) - d1.min(d2)).abs() < 1e-12);
}
#[test]
fn test_smooth_union_equal_inputs() {
let d = 1.0_f64;
let k = 0.5_f64;
let result = sdf_smooth_union(d, d, k);
let expected = d - k / 4.0;
assert!((result - expected).abs() < 1e-10, "got {result}");
}
#[test]
fn test_smooth_union_approaches_min_for_large_k() {
let d1 = 0.0_f64;
let d2 = 100.0_f64;
let result = sdf_smooth_union(d1, d2, 1.0);
assert!(result <= d2 + 1e-9);
}
#[test]
fn test_smooth_union_negative_k_is_min() {
let result = sdf_smooth_union(1.0, 2.0, -1.0);
assert!((result - 1.0_f64.min(2.0)).abs() < 1e-12);
}
#[test]
fn test_smooth_union_symmetric() {
let a = 0.3_f64;
let b = 0.7_f64;
let k = 0.4_f64;
let ab = sdf_smooth_union(a, b, k);
let ba = sdf_smooth_union(b, a, k);
assert!((ab - ba).abs() < 1e-12, "not symmetric: {ab} vs {ba}");
}
#[test]
fn test_gradient_sphere_outward() {
let sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 1.0);
let g = sdf_gradient(&sdf, [1.0, 0.0, 0.0]);
assert!((g[0] - 1.0).abs() < 1e-3, "gx={}", g[0]);
assert!(g[1].abs() < 1e-3);
assert!(g[2].abs() < 1e-3);
}
#[test]
fn test_gradient_is_unit_length() {
let sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 1.0);
let g = sdf_gradient(&sdf, [2.0, 0.0, 0.0]);
let len = (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt();
assert!((len - 1.0).abs() < 1e-6, "len={len}");
}
#[test]
fn test_gradient_box_face_normal() {
let sdf = |p: [f64; 3]| sdf_box(p, [0.0; 3], [1.0; 3]);
let g = sdf_gradient(&sdf, [2.0, 0.0, 0.0]);
assert!((g[0] - 1.0).abs() < 1e-3, "gx={}", g[0]);
}
#[test]
fn test_gradient_smooth_union() {
let sdf = |p: [f64; 3]| {
let d1 = sdf_sphere(p, [-1.0, 0.0, 0.0], 0.5);
let d2 = sdf_sphere(p, [1.0, 0.0, 0.0], 0.5);
sdf_smooth_union(d1, d2, 0.3)
};
let g = sdf_gradient(&sdf, [0.0, 0.0, 3.0]);
let len = (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt();
assert!((len - 1.0).abs() < 1e-4, "len={len}");
}
#[test]
fn test_primitive_sphere_evaluate() {
let prim = SdfPrimitive::Sphere {
center: [0.0; 3],
radius: 2.0,
};
assert!((prim.evaluate([2.0, 0.0, 0.0])).abs() < 1e-10);
}
#[test]
fn test_primitive_box_evaluate() {
let prim = SdfPrimitive::Box3d {
center: [0.0; 3],
half_extents: [1.0; 3],
};
let d = prim.evaluate([0.0; 3]);
assert!(d < 0.0);
}
#[test]
fn test_primitive_cylinder_evaluate() {
let prim = SdfPrimitive::Cylinder {
center: [0.0; 3],
radius: 1.0,
height: 2.0,
};
let d = prim.evaluate([1.0, 0.0, 0.0]);
assert!(d.abs() < 1e-10, "d={d}");
}
#[test]
fn test_primitive_torus_evaluate() {
let prim = SdfPrimitive::Torus {
center: [0.0; 3],
r_major: 2.0,
r_minor: 0.5,
};
let d = prim.evaluate([2.5, 0.0, 0.0]);
assert!(d.abs() < 1e-10, "d={d}");
}
#[test]
fn test_op_union() {
let op = SdfOp::Union;
assert!((op.apply(1.0, 2.0) - 1.0).abs() < 1e-12);
}
#[test]
fn test_op_intersection() {
let op = SdfOp::Intersection;
assert!((op.apply(1.0, 2.0) - 2.0).abs() < 1e-12);
}
#[test]
fn test_op_subtraction() {
let op = SdfOp::Subtraction;
assert!((op.apply(2.0, 3.0) - 2.0).abs() < 1e-12);
}
#[test]
fn test_op_smooth_union() {
let op = SdfOp::SmoothUnion { k: 0.3 };
let result = op.apply(1.0, 1.0);
assert!((result - (1.0 - 0.3 / 4.0)).abs() < 1e-10);
}
#[test]
fn test_op_offset_positive() {
let op = SdfOp::Offset { d: 0.5 };
assert!((op.apply(1.0, 0.0) - 1.5).abs() < 1e-12);
}
#[test]
fn test_op_offset_negative() {
let op = SdfOp::Offset { d: -0.5 };
assert!((op.apply(1.0, 0.0) - 0.5).abs() < 1e-12);
}
#[test]
fn test_node_leaf_evaluate() {
let node = SdfNode::leaf(SdfPrimitive::Sphere {
center: [0.0; 3],
radius: 1.0,
});
assert!((node.evaluate([1.0, 0.0, 0.0])).abs() < 1e-10);
}
#[test]
fn test_node_op_union() {
let a = SdfNode::leaf(SdfPrimitive::Sphere {
center: [-2.0, 0.0, 0.0],
radius: 1.0,
});
let b = SdfNode::leaf(SdfPrimitive::Sphere {
center: [2.0, 0.0, 0.0],
radius: 1.0,
});
let tree = SdfNode::op(SdfOp::Union, a, b);
let d = tree.evaluate([0.0; 3]);
assert!((d - 1.0).abs() < 1e-10, "d={d}");
}
#[test]
fn test_node_op_intersection() {
let a = SdfNode::leaf(SdfPrimitive::Sphere {
center: [0.0; 3],
radius: 2.0,
});
let b = SdfNode::leaf(SdfPrimitive::Sphere {
center: [0.0; 3],
radius: 3.0,
});
let tree = SdfNode::op(SdfOp::Intersection, a, b);
let d = tree.evaluate([0.0; 3]);
assert!((d - (-2.0)).abs() < 1e-10);
}
#[test]
fn test_node_op_subtraction() {
let a = SdfNode::leaf(SdfPrimitive::Sphere {
center: [0.0; 3],
radius: 2.0,
});
let b = SdfNode::leaf(SdfPrimitive::Sphere {
center: [0.0; 3],
radius: 1.0,
});
let tree = SdfNode::op(SdfOp::Subtraction, a, b);
let d = tree.evaluate([0.0; 3]);
assert!((d - 1.0).abs() < 1e-10, "d={d}");
}
#[test]
fn test_grid_new_size() {
let grid = SdfGrid::new([4, 4, 4], 0.1, [0.0; 3]);
assert_eq!(grid.len(), 64);
assert!(!grid.is_empty());
}
#[test]
fn test_grid_empty_dimensions() {
let grid = SdfGrid::new([0, 4, 4], 0.1, [0.0; 3]);
assert!(grid.is_empty());
}
#[test]
fn test_grid_compute_from_sdf_sphere() {
let mut grid = SdfGrid::new([4, 4, 4], 1.0, [-2.0, -2.0, -2.0]);
let sphere_sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 1.0);
grid.compute_from_sdf(&sphere_sdf);
assert!(
grid.data.iter().all(|&v| v.is_finite()),
"some voxels unfilled"
);
}
#[test]
fn test_grid_index_roundtrip() {
let grid = SdfGrid::new([5, 6, 7], 0.5, [0.0; 3]);
for k in 0..7 {
for j in 0..6 {
for i in 0..5 {
let idx = grid.index(i, j, k);
let expected = i + 5 * (j + 6 * k);
assert_eq!(idx, expected);
}
}
}
}
#[test]
fn test_grid_voxel_center() {
let grid = SdfGrid::new([2, 2, 2], 1.0, [0.0; 3]);
let c = grid.voxel_center(0, 0, 0);
assert!((c[0] - 0.5).abs() < 1e-10);
assert!((c[1] - 0.5).abs() < 1e-10);
assert!((c[2] - 0.5).abs() < 1e-10);
}
#[test]
fn test_grid_center_of_last_voxel() {
let grid = SdfGrid::new([4, 4, 4], 1.0, [0.0; 3]);
let c = grid.voxel_center(3, 3, 3);
assert!((c[0] - 3.5).abs() < 1e-10);
}
#[test]
fn test_grid_inside_outside_counts() {
let mut grid = SdfGrid::new([10, 10, 10], 0.2, [-1.0, -1.0, -1.0]);
let sphere_sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 0.5);
grid.compute_from_sdf(&sphere_sdf);
let inside = grid.data.iter().filter(|&&v| v < 0.0).count();
assert!(inside > 0, "no voxels inside the sphere");
let outside = grid.data.iter().filter(|&&v| v > 0.0).count();
assert!(outside > 0, "no voxels outside the sphere");
}
#[test]
fn test_gpu_sdf_compute_no_primitives() {
let compute = GpuSdfCompute::new();
let mut grid = SdfGrid::new([4, 4, 4], 1.0, [0.0; 3]);
grid.data.iter_mut().for_each(|v| *v = -999.0);
compute.dispatch_compute(&mut grid, &[]);
assert!(grid.data.iter().all(|&v| (v - (-999.0)).abs() < 1e-3));
}
#[test]
fn test_gpu_sdf_compute_sphere_union() {
let compute = GpuSdfCompute::new();
let mut grid = SdfGrid::new([8, 8, 8], 0.25, [-1.0, -1.0, -1.0]);
let primitives = vec![
SdfPrimitive::Sphere {
center: [-0.5, 0.0, 0.0],
radius: 0.3,
},
SdfPrimitive::Sphere {
center: [0.5, 0.0, 0.0],
radius: 0.3,
},
];
compute.dispatch_compute(&mut grid, &primitives);
assert!(grid.data.iter().all(|&v| v.is_finite()));
let inside = grid.data.iter().filter(|&&v| v < 0.0).count();
assert!(inside > 0, "no voxels inside");
}
#[test]
fn test_gpu_sdf_compute_single_box() {
let compute = GpuSdfCompute::new();
let mut grid = SdfGrid::new([4, 4, 4], 1.0, [-2.0, -2.0, -2.0]);
let primitives = vec![SdfPrimitive::Box3d {
center: [0.0; 3],
half_extents: [0.5; 3],
}];
compute.dispatch_compute(&mut grid, &primitives);
assert!(grid.data.iter().all(|&v| v.is_finite()));
}
#[test]
fn test_gpu_sdf_compute_torus() {
let compute = GpuSdfCompute::new();
let mut grid = SdfGrid::new([6, 6, 6], 0.5, [-1.5, -1.5, -1.5]);
let primitives = vec![SdfPrimitive::Torus {
center: [0.0; 3],
r_major: 0.8,
r_minor: 0.2,
}];
compute.dispatch_compute(&mut grid, &primitives);
assert!(grid.data.iter().all(|&v| v.is_finite()));
}
#[test]
fn test_cylinder_inside() {
let d = sdf_cylinder([0.0, 0.0, 0.0], [0.0; 3], 1.0, 2.0);
assert!(d < 0.0, "expected inside, d={d}");
}
#[test]
fn test_cylinder_outside_radially() {
let d = sdf_cylinder([2.0, 0.0, 0.0], [0.0; 3], 1.0, 4.0);
assert!((d - 1.0).abs() < 1e-10, "d={d}");
}
#[test]
fn test_cylinder_on_curved_surface() {
let d = sdf_cylinder([1.0, 0.0, 0.0], [0.0; 3], 1.0, 4.0);
assert!(d.abs() < 1e-10, "d={d}");
}
#[test]
fn test_torus_centre_is_positive() {
let d = sdf_torus([0.0; 3], [0.0; 3], 2.0, 0.5);
assert!(d > 0.0, "d={d}");
}
#[test]
fn test_torus_on_tube_surface() {
let d = sdf_torus([1.5, 0.0, 0.0], [0.0; 3], 2.0, 0.5);
assert!(d.abs() < 1e-10, "d={d}");
}
}