use alloc::boxed::Box;
use crate::Real;
pub trait Sdf {
type Scalar: Real;
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar;
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
let scale = p[0]
.abs()
.max(p[1].abs())
.max(p[2].abs())
.max(Self::Scalar::ONE);
let h = Self::Scalar::DIFF_STEP * scale;
let inv = (Self::Scalar::TWO * h).recip();
[
(self.sample([p[0] + h, p[1], p[2]]) - self.sample([p[0] - h, p[1], p[2]])) * inv,
(self.sample([p[0], p[1] + h, p[2]]) - self.sample([p[0], p[1] - h, p[2]])) * inv,
(self.sample([p[0], p[1], p[2] + h]) - self.sample([p[0], p[1], p[2] - h])) * inv,
]
}
}
impl<S: Sdf + ?Sized> Sdf for &S {
type Scalar = S::Scalar;
#[inline]
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
S::sample(self, p)
}
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
S::gradient(self, p)
}
}
impl<S: Sdf + ?Sized> Sdf for Box<S> {
type Scalar = S::Scalar;
#[inline]
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
S::sample(self, p)
}
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
S::gradient(self, p)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use super::*;
struct Plane<R: Real> {
n: [R; 3],
d: R,
}
impl<R: Real> Sdf for Plane<R> {
type Scalar = R;
fn sample(&self, p: [R; 3]) -> R {
self.n[0] * p[0] + self.n[1] * p[1] + self.n[2] * p[2] - self.d
}
}
struct Sentinel;
const SENTINEL: [f32; 3] = [42.0, -17.0, 3.5];
impl Sdf for Sentinel {
type Scalar = f32;
fn sample(&self, p: [f32; 3]) -> f32 {
p[0]
}
fn gradient(&self, _p: [f32; 3]) -> [f32; 3] {
SENTINEL
}
}
fn norm(v: [f32; 3]) -> f32 {
(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt()
}
#[test]
fn sdf_is_dyn_compatible() {
const _: Option<&dyn Sdf<Scalar = f32>> = None;
let field = Sentinel;
let dynamic: &dyn Sdf<Scalar = f32> = &field;
assert_eq!(dynamic.sample([2.0, 0.0, 0.0]), 2.0);
assert_eq!(dynamic.gradient([0.0; 3]), SENTINEL);
let boxed: Box<dyn Sdf<Scalar = f32>> = Box::new(Sentinel);
assert_eq!(boxed.gradient([0.0; 3]), SENTINEL);
}
#[test]
fn dyn_sdf_satisfies_the_generic_bound() {
fn takes_generic<S: Sdf<Scalar = f32>>(s: S) -> f32 {
s.sample([3.0, 0.0, 0.0])
}
let field = Sentinel;
let dynamic: &dyn Sdf<Scalar = f32> = &field;
assert_eq!(takes_generic(dynamic), 3.0);
}
#[test]
fn ref_impl_forwards_analytic_gradient() {
let field = Sentinel;
let by_ref: &Sentinel = &field;
assert_eq!(
<&Sentinel as Sdf>::gradient(&by_ref, [1.0, 2.0, 3.0]),
SENTINEL
);
let boxed: Box<Sentinel> = Box::new(Sentinel);
assert_eq!(
<Box<Sentinel> as Sdf>::gradient(&boxed, [1.0, 2.0, 3.0]),
SENTINEL
);
fn through_generic<S: Sdf<Scalar = f32>>(s: S) -> [f32; 3] {
s.gradient([1.0, 2.0, 3.0])
}
assert_eq!(through_generic(&field), SENTINEL);
assert_eq!(through_generic(Box::new(Sentinel)), SENTINEL);
}
#[test]
fn default_gradient_is_exact_on_a_plane() {
let n = [0.267_261_24_f32, 0.534_522_5, 0.801_783_7]; let plane = Plane { n, d: 0.5 };
let g = plane.gradient([0.25, -0.5, 0.75]);
for i in 0..3 {
assert!(
(g[i] - n[i]).abs() <= 16.0 * f32::EPSILON,
"axis {i}: {g:?}"
);
}
}
#[test]
fn default_gradient_matches_analytic_on_a_sphere() {
struct Sphere;
impl Sdf for Sphere {
type Scalar = f64;
fn sample(&self, p: [f64; 3]) -> f64 {
(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt() - 1.0
}
}
let p = [0.3, -0.7, 0.5];
let len = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
let analytic = [p[0] / len, p[1] / len, p[2] / len];
let g = Sphere.gradient(p);
for i in 0..3 {
assert!((g[i] - analytic[i]).abs() < 1e-9, "axis {i}: {g:?}");
}
}
#[test]
fn default_gradient_survives_large_coordinates() {
let plane = Plane {
n: [1.0f32, 0.0, 0.0],
d: 0.0,
};
let p = [1.0e6f32, 0.0, 0.0];
assert_eq!(
p[0] + f32::DIFF_STEP,
p[0],
"premise: a fixed step vanishes here"
);
let g = plane.gradient(p);
assert!(g.iter().all(|c| c.is_finite()));
assert!((g[0] - 1.0).abs() < 1e-3, "{g:?}");
}
#[test]
fn default_gradient_is_not_normalised() {
let plane = Plane {
n: [3.0f32, 0.0, 0.0],
d: 0.0,
};
let g = plane.gradient([0.5, 0.5, 0.5]);
assert!((norm(g) - 3.0).abs() < 1e-3, "{g:?}");
}
}