use crate::compiled::real::{Real, Vec3R};
use glam::Vec3;
const ELLIPSOID_BISECT_STEPS: u32 = 64;
#[inline(always)]
fn ellipsoid_bisector(n: usize, r: [f32; 3], z: [f32; 3], g: f32) -> f32 {
let mut nn = [0.0f32; 3];
let mut norm2 = 0.0f32;
for i in 0..n {
nn[i] = r[i] * z[i];
norm2 = nn[i].mul_add(nn[i], norm2);
}
let mut s0 = z[n - 1] - 1.0;
let mut s1 = if g < 0.0 { 0.0 } else { norm2.sqrt() - 1.0 };
let mut s = 0.0f32;
for _ in 0..ELLIPSOID_BISECT_STEPS {
s = f32::midpoint(s0, s1);
if s == s0 || s == s1 {
break;
}
let mut gs = -1.0f32;
for i in 0..n {
let ratio = nn[i] / (s + r[i]);
gs = ratio.mul_add(ratio, gs);
}
if gs > 0.0 {
s0 = s;
} else if gs < 0.0 {
s1 = s;
} else {
break;
}
}
s
}
fn ellipsoid_sqr_distance(n: usize, e: [f32; 3], y: [f32; 3]) -> f32 {
if n == 1 {
let d = y[0] - e[0];
return d * d;
}
let last = n - 1;
if y[last] > 0.0 {
let mut z = [0.0f32; 3];
let mut g = -1.0f32;
for i in 0..n {
z[i] = y[i] / e[i];
g = z[i].mul_add(z[i], g);
}
if g == 0.0 {
return 0.0;
}
let mut r = [0.0f32; 3];
for i in 0..n {
let q = e[i] / e[last];
r[i] = q * q;
}
let sbar = ellipsoid_bisector(n, r, z, g);
let mut d2 = 0.0f32;
for i in 0..n {
let x = r[i] * y[i] / (sbar + r[i]);
let d = x - y[i];
d2 = d.mul_add(d, d2);
}
return d2;
}
let mut numer = [0.0f32; 3];
let mut denom = [0.0f32; 3];
let mut inside = true;
for i in 0..last {
numer[i] = e[i] * y[i];
denom[i] = e[last].mul_add(-e[last], e[i] * e[i]);
if numer[i] >= denom[i] {
inside = false;
}
}
if inside {
let mut discr = 1.0f32;
let mut xde = [0.0f32; 3];
for i in 0..last {
xde[i] = numer[i] / denom[i];
discr = xde[i].mul_add(-xde[i], discr);
}
if discr > 0.0 {
let mut d2 = 0.0f32;
for i in 0..last {
let d = e[i].mul_add(xde[i], -y[i]);
d2 = d.mul_add(d, d2);
}
let xl = e[last] * discr.sqrt();
return xl.mul_add(xl, d2);
}
}
ellipsoid_sqr_distance(last, e, y)
}
#[inline]
pub fn sdf_ellipsoid_exact(p: Vec3, radii: Vec3) -> f32 {
let safe = [radii.x.max(1e-10), radii.y.max(1e-10), radii.z.max(1e-10)];
let mut y = [p.x.abs(), p.y.abs(), p.z.abs()];
let mut e = safe;
if e[0] < e[1] {
e.swap(0, 1);
y.swap(0, 1);
}
if e[1] < e[2] {
e.swap(1, 2);
y.swap(1, 2);
}
if e[0] < e[1] {
e.swap(0, 1);
y.swap(0, 1);
}
let d = ellipsoid_sqr_distance(3, e, y).sqrt();
let inside = (y[2] / e[2]).mul_add(
y[2] / e[2],
(y[1] / e[1]).mul_add(y[1] / e[1], (y[0] / e[0]).powi(2)),
) < 1.0;
if inside {
-d
} else {
d
}
}
#[inline(always)]
pub fn sdf_ellipsoid_r<R: Real>(p: Vec3R<R>, radii: Vec3) -> R {
R::map3(p.x, p.y, p.z, |q| sdf_ellipsoid_exact(q, radii))
}
#[inline(always)]
pub fn sdf_ellipsoid(p: Vec3, radii: Vec3) -> f32 {
sdf_ellipsoid_exact(p, radii)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ellipsoid_origin() {
let d = sdf_ellipsoid(Vec3::ZERO, Vec3::new(1.0, 2.0, 1.0));
assert!(d < 0.0, "Origin should be inside ellipsoid, got {}", d);
}
#[test]
fn test_ellipsoid_on_surface_x() {
let d = sdf_ellipsoid(Vec3::new(1.0, 0.0, 0.0), Vec3::new(1.0, 2.0, 1.0));
assert!(d.abs() < 0.01, "Should be on surface at x=rx, got {}", d);
}
#[test]
fn test_ellipsoid_on_surface_y() {
let d = sdf_ellipsoid(Vec3::new(0.0, 2.0, 0.0), Vec3::new(1.0, 2.0, 1.0));
assert!(d.abs() < 0.01, "Should be on surface at y=ry, got {}", d);
}
#[test]
fn test_ellipsoid_sphere_equivalence() {
let r = 1.5;
let p = Vec3::new(r, 0.0, 0.0);
let d = sdf_ellipsoid(p, Vec3::splat(r));
assert!(
d.abs() < 0.01,
"Equal radii should be like a sphere, got {}",
d
);
}
fn brute(p: Vec3, r: Vec3) -> f32 {
let mut best = f32::MAX;
let n = 600;
for i in 0..=n {
let th = std::f32::consts::PI * i as f32 / n as f32;
for j in 0..2 * n {
let ph = std::f32::consts::TAU * j as f32 / (2 * n) as f32;
let s = Vec3::new(
r.x * th.sin() * ph.cos(),
r.y * th.sin() * ph.sin(),
r.z * th.cos(),
);
best = best.min((p - s).length());
}
}
let inside = (p.z / r.z).mul_add(
p.z / r.z,
(p.y / r.y).mul_add(p.y / r.y, (p.x / r.x).powi(2)),
) < 1.0;
if inside {
-best
} else {
best
}
}
#[test]
fn test_ellipsoid_exact_matches_brute_force() {
for r in [
Vec3::new(1.2, 0.6, 0.9),
Vec3::new(3.0, 1.0, 0.5),
Vec3::new(1.0, 1.0, 0.1),
] {
for p in [
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(0.3, 0.2, 0.1),
Vec3::new(2.0, 0.0, 0.0),
Vec3::new(0.0, 2.0, 0.0),
Vec3::new(0.0, 0.0, 2.0),
Vec3::new(1.0, 1.0, 1.0),
Vec3::new(-0.7, 0.4, -0.2),
Vec3::new(19.0, 0.0, -0.38),
Vec3::new(0.5, 0.0, 0.0),
Vec3::new(0.0, 0.45, 0.0),
] {
let d = sdf_ellipsoid(p, r);
let b = brute(p, r);
assert!((d - b).abs() < 4e-3, "{r:?} {p:?}: exact {d} vs brute {b}");
}
}
}
#[test]
fn test_ellipsoid_outside() {
let d = sdf_ellipsoid(Vec3::new(5.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0));
assert!(d > 0.0, "Far point should be outside, got {}", d);
}
}