use crate::crispy::round_half_up;
use crate::types::SdfNode;
use glam::{Quat, Vec3};
use std::ops::{Add, Mul, Neg, Sub};
#[derive(Clone, Copy, Debug)]
pub struct Interval {
pub lo: f32,
pub hi: f32,
}
#[inline(always)]
#[must_use]
pub fn next_down(x: f32) -> f32 {
if x.is_nan() || x == f32::NEG_INFINITY {
return x;
}
if x == 0.0 {
return -f32::from_bits(1);
}
let bits = x.to_bits();
f32::from_bits(if x > 0.0 { bits - 1 } else { bits + 1 })
}
#[inline(always)]
#[must_use]
pub fn next_up(x: f32) -> f32 {
if x.is_nan() || x == f32::INFINITY {
return x;
}
if x == 0.0 {
return f32::from_bits(1);
}
let bits = x.to_bits();
f32::from_bits(if x > 0.0 { bits + 1 } else { bits - 1 })
}
impl Interval {
#[inline(always)]
#[must_use]
pub const fn entire() -> Self {
Self {
lo: f32::NEG_INFINITY,
hi: f32::INFINITY,
}
}
#[inline(always)]
pub fn new(lo: f32, hi: f32) -> Self {
debug_assert!(lo <= hi + 1e-6, "lo ({}) > hi ({})", lo, hi);
Self::outward(lo, hi)
}
#[inline(always)]
#[must_use]
pub fn outward(lo: f32, hi: f32) -> Self {
Self {
lo: next_down(lo),
hi: next_up(hi),
}
}
#[inline(always)]
pub const fn point(v: f32) -> Self {
Self { lo: v, hi: v }
}
pub const EVERYTHING: Self = Self {
lo: f32::NEG_INFINITY,
hi: f32::INFINITY,
};
pub const ZERO: Self = Self { lo: 0.0, hi: 0.0 };
#[inline(always)]
pub fn is_positive(self) -> bool {
self.lo > 0.0
}
#[inline(always)]
pub fn is_negative(self) -> bool {
self.hi < 0.0
}
#[inline(always)]
pub fn width(self) -> f32 {
self.hi - self.lo
}
#[inline(always)]
pub const fn midpoint(self) -> f32 {
f32::midpoint(self.lo, self.hi)
}
#[inline(always)]
pub fn contains(self, v: f32) -> bool {
v >= self.lo && v <= self.hi
}
#[inline(always)]
pub fn overlaps(self, other: Self) -> bool {
self.lo <= other.hi && other.lo <= self.hi
}
#[inline(always)]
pub const fn intersect(self, other: Self) -> Self {
Self {
lo: self.lo.max(other.lo),
hi: self.hi.min(other.hi),
}
}
#[inline(always)]
pub const fn hull(self, other: Self) -> Self {
Self {
lo: self.lo.min(other.lo),
hi: self.hi.max(other.hi),
}
}
#[inline(always)]
pub fn abs(self) -> Self {
if self.lo >= 0.0 {
self
} else if self.hi <= 0.0 {
Self {
lo: -self.hi,
hi: -self.lo,
}
} else {
Self {
lo: 0.0,
hi: self.hi.max(-self.lo),
}
}
}
#[inline(always)]
pub fn sqrt(self) -> Self {
Self::outward(self.lo.max(0.0).sqrt(), self.hi.max(0.0).sqrt())
}
#[inline(always)]
pub fn sqr(self) -> Self {
if self.lo >= 0.0 {
Self::outward(self.lo * self.lo, self.hi * self.hi)
} else if self.hi <= 0.0 {
Self::outward(self.hi * self.hi, self.lo * self.lo)
} else {
Self {
lo: 0.0,
hi: next_up((self.lo * self.lo).max(self.hi * self.hi)),
}
}
}
#[inline(always)]
pub const fn min(self, other: Self) -> Self {
Self {
lo: self.lo.min(other.lo),
hi: self.hi.min(other.hi),
}
}
#[inline(always)]
pub const fn max(self, other: Self) -> Self {
Self {
lo: self.lo.max(other.lo),
hi: self.hi.max(other.hi),
}
}
#[inline(always)]
pub const fn clamp(self, lo: f32, hi: f32) -> Self {
Self {
lo: self.lo.clamp(lo, hi),
hi: self.hi.clamp(lo, hi),
}
}
#[inline(always)]
pub fn expand(self, amount: f32) -> Self {
Self::outward(self.lo - amount, self.hi + amount)
}
}
impl Add for Interval {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Self::outward(self.lo + rhs.lo, self.hi + rhs.hi)
}
}
impl Sub for Interval {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Self::outward(self.lo - rhs.hi, self.hi - rhs.lo)
}
}
impl Mul for Interval {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: Self) -> Self {
let a = self.lo * rhs.lo;
let b = self.lo * rhs.hi;
let c = self.hi * rhs.lo;
let d = self.hi * rhs.hi;
Self::outward(a.min(b).min(c).min(d), a.max(b).max(c).max(d))
}
}
impl Neg for Interval {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
Self {
lo: -self.hi,
hi: -self.lo,
}
}
}
impl Mul<f32> for Interval {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: f32) -> Self {
if rhs >= 0.0 {
Self::outward(self.lo * rhs, self.hi * rhs)
} else {
Self::outward(self.hi * rhs, self.lo * rhs)
}
}
}
impl Sub<f32> for Interval {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: f32) -> Self {
Self::outward(self.lo - rhs, self.hi - rhs)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Vec3Interval {
pub x: Interval,
pub y: Interval,
pub z: Interval,
}
impl Vec3Interval {
#[inline]
pub fn from_bounds(min: Vec3, max: Vec3) -> Self {
Self {
x: Interval::new(min.x, max.x),
y: Interval::new(min.y, max.y),
z: Interval::new(min.z, max.z),
}
}
#[inline(always)]
pub fn translate(self, offset: Vec3) -> Self {
Self {
x: self.x - Interval::point(offset.x),
y: self.y - Interval::point(offset.y),
z: self.z - Interval::point(offset.z),
}
}
pub fn affine(self, a: &[f32; 9], b: Vec3) -> Self {
let corners = [
Vec3::new(self.x.lo, self.y.lo, self.z.lo),
Vec3::new(self.x.lo, self.y.lo, self.z.hi),
Vec3::new(self.x.lo, self.y.hi, self.z.lo),
Vec3::new(self.x.lo, self.y.hi, self.z.hi),
Vec3::new(self.x.hi, self.y.lo, self.z.lo),
Vec3::new(self.x.hi, self.y.lo, self.z.hi),
Vec3::new(self.x.hi, self.y.hi, self.z.lo),
Vec3::new(self.x.hi, self.y.hi, self.z.hi),
];
let map = |c: Vec3| {
Vec3::new(
a[6].mul_add(c.z, a[3].mul_add(c.y, a[0] * c.x)) + b.x,
a[7].mul_add(c.z, a[4].mul_add(c.y, a[1] * c.x)) + b.y,
a[8].mul_add(c.z, a[5].mul_add(c.y, a[2] * c.x)) + b.z,
)
};
let first = map(corners[0]);
let mut min = first;
let mut max = first;
for &c in &corners[1..] {
let r = map(c);
min = min.min(r);
max = max.max(r);
}
Self::from_bounds(min, max)
}
pub const fn hull(&self, other: &Self) -> Self {
Self {
x: self.x.hull(other.x),
y: self.y.hull(other.y),
z: self.z.hull(other.z),
}
}
#[inline]
pub fn rotate(self, rotation: Quat) -> Self {
let inv_rot = rotation.conjugate();
let corners = [
Vec3::new(self.x.lo, self.y.lo, self.z.lo),
Vec3::new(self.x.lo, self.y.lo, self.z.hi),
Vec3::new(self.x.lo, self.y.hi, self.z.lo),
Vec3::new(self.x.lo, self.y.hi, self.z.hi),
Vec3::new(self.x.hi, self.y.lo, self.z.lo),
Vec3::new(self.x.hi, self.y.lo, self.z.hi),
Vec3::new(self.x.hi, self.y.hi, self.z.lo),
Vec3::new(self.x.hi, self.y.hi, self.z.hi),
];
let first = inv_rot * corners[0];
let mut min = first;
let mut max = first;
for &c in &corners[1..] {
let r = inv_rot * c;
min = min.min(r);
max = max.max(r);
}
Self::from_bounds(min, max)
}
#[inline]
pub fn length(self) -> Interval {
(self.x.sqr() + self.y.sqr() + self.z.sqr()).sqrt()
}
#[inline]
pub fn length_xz(self) -> Interval {
(self.x.sqr() + self.z.sqr()).sqrt()
}
#[inline]
pub fn mirror(self, axes: Vec3) -> Self {
Self {
x: if axes.x != 0.0 { self.x.abs() } else { self.x },
y: if axes.y != 0.0 { self.y.abs() } else { self.y },
z: if axes.z != 0.0 { self.z.abs() } else { self.z },
}
}
}
pub fn eval_interval(node: &SdfNode, bounds: Vec3Interval) -> Interval {
match node {
SdfNode::Sphere { radius } => bounds.length() - Interval::point(*radius),
SdfNode::MetricBall {
radius,
w_l1,
w_l2,
w_linf,
} => {
let ax = bounds.x.abs();
let ay = bounds.y.abs();
let az = bounds.z.abs();
let l1 = ax + ay + az;
let l2 = bounds.length();
let linf = ax.max(ay).max(az);
l1 * Interval::point(*w_l1)
+ l2 * Interval::point(*w_l2)
+ linf * Interval::point(*w_linf)
- Interval::point(*radius)
}
SdfNode::MetricBlend { inner, outer, .. } => {
let a = eval_interval(inner, bounds);
let b = eval_interval(outer, bounds);
Interval::new(a.lo.min(b.lo), a.hi.max(b.hi))
}
SdfNode::Box3d { half_extents } => {
let h = *half_extents;
let qx = bounds.x.abs() - Interval::point(h.x);
let qy = bounds.y.abs() - Interval::point(h.y);
let qz = bounds.z.abs() - Interval::point(h.z);
let outer = Vec3Interval {
x: qx.max(Interval::ZERO),
y: qy.max(Interval::ZERO),
z: qz.max(Interval::ZERO),
}
.length();
let inner = qx.max(qy).max(qz).min(Interval::ZERO);
outer + inner
}
SdfNode::Cylinder {
radius,
half_height,
} => {
let dx = bounds.length_xz() - Interval::point(*radius);
let dy = bounds.y.abs() - Interval::point(*half_height);
let ox = dx.max(Interval::ZERO);
let oy = dy.max(Interval::ZERO);
(ox.sqr() + oy.sqr()).sqrt() + dx.max(dy).min(Interval::ZERO)
}
SdfNode::Torus {
major_radius,
minor_radius,
} => {
let qx = bounds.length_xz() - Interval::point(*major_radius);
(qx.sqr() + bounds.y.sqr()).sqrt() - Interval::point(*minor_radius)
}
SdfNode::Plane { normal, distance } => {
bounds.x * Interval::point(normal.x)
+ bounds.y * Interval::point(normal.y)
+ bounds.z * Interval::point(normal.z)
- Interval::point(*distance)
}
SdfNode::Capsule {
point_a,
point_b,
radius,
} => {
let ab = *point_b - *point_a;
let ab_sq = ab.length_squared();
if ab_sq < 1e-10 {
let p = bounds.translate(*point_a);
return p.length() - Interval::point(*radius);
}
let pa = Vec3Interval {
x: bounds.x - Interval::point(point_a.x),
y: bounds.y - Interval::point(point_a.y),
z: bounds.z - Interval::point(point_a.z),
};
let dot = pa.x * Interval::point(ab.x)
+ pa.y * Interval::point(ab.y)
+ pa.z * Interval::point(ab.z);
let t = (dot * (1.0 / ab_sq)).clamp(0.0, 1.0);
let cx = pa.x - Interval::point(ab.x) * t;
let cy = pa.y - Interval::point(ab.y) * t;
let cz = pa.z - Interval::point(ab.z) * t;
Vec3Interval {
x: cx,
y: cy,
z: cz,
}
.length()
- Interval::point(*radius)
}
SdfNode::InfiniteCylinder { radius } => bounds.length_xz() - Interval::point(*radius),
SdfNode::RoundedBox {
half_extents,
round_radius,
} => {
let h = *half_extents;
let qx = bounds.x.abs() - Interval::point(h.x);
let qy = bounds.y.abs() - Interval::point(h.y);
let qz = bounds.z.abs() - Interval::point(h.z);
let outer = Vec3Interval {
x: qx.max(Interval::ZERO),
y: qy.max(Interval::ZERO),
z: qz.max(Interval::ZERO),
}
.length();
let inner = qx.max(qy).max(qz).min(Interval::ZERO);
outer + inner - Interval::point(*round_radius)
}
SdfNode::Cone {
radius,
half_height,
} => ia_bsphere(bounds, (2.0 * half_height).hypot(*radius)),
SdfNode::Ellipsoid { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::RoundedCone {
r1,
r2,
half_height,
} => ia_bsphere(bounds, (2.0 * half_height).hypot(r1.max(*r2))),
SdfNode::Pyramid { half_height } => ia_bsphere(
bounds,
(2.0 * half_height).mul_add(2.0 * half_height, 0.25).sqrt(),
),
SdfNode::Octahedron { size } => ia_bsphere(bounds, *size),
SdfNode::HexPrism { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::Link {
half_length,
r1,
r2,
} => ia_bsphere(bounds, half_length + r1 + r2),
SdfNode::Triangle { .. } | SdfNode::Bezier { .. } => Interval::EVERYTHING,
SdfNode::CappedCone {
half_height,
r1,
r2,
} => ia_bsphere(bounds, (2.0 * half_height).hypot(r1.max(*r2))),
SdfNode::CappedTorus {
major_radius,
minor_radius,
..
} => ia_bsphere(bounds, major_radius + minor_radius),
SdfNode::RoundedCylinder { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::TriangularPrism { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::CutSphere { radius, .. } => ia_bsphere(bounds, *radius),
SdfNode::CutHollowSphere { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::DeathStar { ra, rb, .. } => ia_bsphere(bounds, ra.max(*rb)),
SdfNode::SolidAngle { radius, .. } => ia_bsphere(bounds, *radius),
SdfNode::Rhombus {
la,
lb,
half_height,
round_radius,
} => ia_bsphere(bounds, (la.max(*lb) + round_radius).hypot(*half_height)),
SdfNode::Horseshoe {
radius,
half_length,
width,
thickness,
..
} => ia_bsphere(bounds, radius + half_length + width + thickness),
SdfNode::Vesica { radius, .. } => ia_bsphere(bounds, *radius),
SdfNode::InfiniteCone { .. } => Interval::EVERYTHING,
SdfNode::Gyroid { .. }
| SdfNode::SchwarzP { .. }
| SdfNode::DiamondSurface { .. }
| SdfNode::Neovius { .. }
| SdfNode::Lidinoid { .. }
| SdfNode::IWP { .. }
| SdfNode::FRD { .. }
| SdfNode::FischerKochS { .. }
| SdfNode::PMY { .. } => ia_lipschitz(node, bounds, eval_lipschitz(node)),
SdfNode::Heart { size } => ia_bsphere(bounds, *size * 2.0),
SdfNode::Tube { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::Barrel {
radius,
half_height,
bulge,
} => ia_bsphere(bounds, (radius + bulge.abs()).hypot(*half_height)),
SdfNode::Diamond {
radius,
half_height,
} => ia_bsphere(bounds, radius.hypot(*half_height)),
SdfNode::ChamferedCube { half_extents, .. } => ia_bsphere(bounds, half_extents.length()),
SdfNode::Superellipsoid {
half_extents,
e1,
e2,
} => {
let e1 = e1.max(0.02);
let e2 = e2.max(0.02);
let q = |iv: Interval, h: f32| {
let a = (iv * Interval::point(1.0 / h)).abs();
Interval::new(a.lo.max(1e-10), a.hi.max(1e-10))
};
let (qx, qy, qz) = (
q(bounds.x, half_extents.x),
q(bounds.y, half_extents.y),
q(bounds.z, half_extents.z),
);
let m1 = 2.0 / e2;
let m2 = 2.0 / e1;
let w = powf_nonneg(qx, m1) + powf_nonneg(qz, m1);
let v = powf_nonneg(w, e2 / e1) + powf_nonneg(qy, m2);
let f = powf_nonneg(v, e1 * 0.5);
let min_extent = half_extents.x.min(half_extents.y.min(half_extents.z));
(f - Interval::point(1.0)) * Interval::point(min_extent * 0.5)
}
SdfNode::RoundedX {
width,
round_radius,
half_height,
} => ia_bsphere(bounds, (width + round_radius).max(*half_height)),
SdfNode::Pie {
radius,
half_height,
..
} => ia_bsphere(bounds, radius.hypot(*half_height)),
SdfNode::Trapezoid {
r1,
r2,
trap_height,
half_depth,
} => ia_bsphere(
bounds,
(r1.max(*r2).mul_add(r1.max(*r2), trap_height.powi(2)) + half_depth.powi(2)).sqrt(),
),
SdfNode::Parallelogram {
width,
para_height,
skew,
half_depth,
} => ia_bsphere(
bounds,
((width + skew.abs()).mul_add(width + skew.abs(), para_height.powi(2))
+ half_depth.powi(2))
.sqrt(),
),
SdfNode::Tunnel {
width,
height_2d,
half_depth,
} => {
let px = bounds.x.abs();
let py = bounds.y;
let dx = px - Interval::point(*width);
let dy = py.abs() - Interval::point(*height_2d);
let d_rect = len2(dx.max(Interval::ZERO), dy.max(Interval::ZERO))
+ dx.max(dy).min(Interval::ZERO);
let d_circle = len2(px, py - Interval::point(*height_2d)) - Interval::point(*width);
let d_2d = Interval::new(d_rect.min(d_circle).lo, d_rect.hi);
let d_z = bounds.z.abs() - Interval::point(*half_depth);
d_2d.max(d_z).min(Interval::ZERO)
+ len2(d_2d.max(Interval::ZERO), d_z.max(Interval::ZERO))
}
SdfNode::UnevenCapsule {
r1,
r2,
cap_height,
half_depth,
} => ia_bsphere(
bounds,
r1.max(2.0f32.mul_add(*cap_height, *r2)).hypot(*half_depth),
),
SdfNode::Egg { ra, rb } => ia_bsphere(bounds, 1.732_050_8f32.mul_add(ra - rb, *ra)),
SdfNode::ArcShape {
radius,
thickness,
half_height,
..
} => ia_bsphere(bounds, (radius + thickness).hypot(*half_height)),
SdfNode::Moon {
ra,
rb,
half_height,
..
} => ia_bsphere(bounds, ra.max(*rb).hypot(*half_height)),
SdfNode::CrossShape { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::BlobbyCross { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::ParabolaSegment {
width,
para_height,
half_depth,
} => ia_bsphere(
bounds,
(width.powi(2) + para_height.powi(2) + half_depth.powi(2)).sqrt(),
),
SdfNode::RegularPolygon {
radius,
half_height,
..
} => ia_bsphere(bounds, radius.hypot(*half_height)),
SdfNode::StarPolygon {
radius,
half_height,
..
} => ia_bsphere(bounds, radius.hypot(*half_height)),
SdfNode::Stairs {
step_width,
step_height,
n_steps,
half_depth,
} => {
let extent = ((step_width * n_steps)
.mul_add(step_width * n_steps, (step_height * n_steps).powi(2))
+ half_depth.powi(2))
.sqrt();
ia_bsphere(bounds, extent)
}
SdfNode::Helix {
major_r,
minor_r,
pitch,
half_height,
} => {
let d_radial = bounds.length_xz() - Interval::point(*major_r);
let dy = Interval::new(0.0, pitch.abs() * 0.5);
let d_tube =
Interval::new(d_radial.abs().lo - minor_r, len2(d_radial, dy).hi - minor_r);
let d_cap = bounds.y.abs() - Interval::point(*half_height);
d_tube.max(d_cap)
}
SdfNode::Tetrahedron { .. }
| SdfNode::Dodecahedron { .. }
| SdfNode::Icosahedron { .. }
| SdfNode::TruncatedOctahedron { .. }
| SdfNode::TruncatedIcosahedron { .. }
| SdfNode::BoxFrame { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::Circle2D { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::Rect2D { .. } => ia_lipschitz(node, bounds, 1.0),
SdfNode::Segment2D { .. } | SdfNode::Polygon2D { .. } => Interval::EVERYTHING,
SdfNode::RoundedRect2D { .. } | SdfNode::Annular2D { .. } => {
ia_lipschitz(node, bounds, 1.0)
}
SdfNode::Terrain { amplitude, .. } => {
Interval::new(bounds.y.lo - amplitude, bounds.y.hi + amplitude)
}
SdfNode::Union { a, b } => eval_interval(a, bounds).min(eval_interval(b, bounds)),
SdfNode::Intersection { a, b } => eval_interval(a, bounds).max(eval_interval(b, bounds)),
SdfNode::Subtraction { a, b } => eval_interval(a, bounds).max(-eval_interval(b, bounds)),
SdfNode::SmoothUnion { a, b, k } => {
let sharp = eval_interval(a, bounds).min(eval_interval(b, bounds));
Interval::new(sharp.lo - k * 0.25, sharp.hi)
}
SdfNode::SmoothIntersection { a, b, k } => {
let sharp = eval_interval(a, bounds).max(eval_interval(b, bounds));
Interval::new(sharp.lo, sharp.hi + k * 0.25)
}
SdfNode::SmoothSubtraction { a, b, k } => {
let sharp = eval_interval(a, bounds).max(-eval_interval(b, bounds));
Interval::new(sharp.lo, sharp.hi + k * 0.25)
}
SdfNode::ChamferUnion { a, b, r } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
ia.min(ib).min(
(ia + ib) * Interval::point(std::f32::consts::FRAC_1_SQRT_2) - Interval::point(*r),
)
}
SdfNode::ChamferIntersection { a, b, r } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
ia.max(ib).max(
(ia + ib) * Interval::point(std::f32::consts::FRAC_1_SQRT_2) + Interval::point(*r),
)
}
SdfNode::ChamferSubtraction { a, b, r } => {
let ia = eval_interval(a, bounds);
let ib = Interval::ZERO - eval_interval(b, bounds);
ia.max(ib).max(
(ia + ib) * Interval::point(std::f32::consts::FRAC_1_SQRT_2) + Interval::point(*r),
)
}
SdfNode::StairsUnion { a, b, r, n } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
if !(ia.lo.is_finite() && ia.hi.is_finite() && ib.lo.is_finite() && ib.hi.is_finite()) {
return Interval::EVERYTHING;
}
ia_stairs_min(ia, ib, *r, *n)
}
SdfNode::StairsIntersection { a, b, r, n } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
if !(ia.lo.is_finite() && ia.hi.is_finite() && ib.lo.is_finite() && ib.hi.is_finite()) {
return Interval::EVERYTHING;
}
-ia_stairs_min(-ia, -ib, *r, *n)
}
SdfNode::StairsSubtraction { a, b, r, n } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
if !(ia.lo.is_finite() && ia.hi.is_finite() && ib.lo.is_finite() && ib.hi.is_finite()) {
return Interval::EVERYTHING;
}
-ia_stairs_min(-ia, ib, *r, *n)
}
SdfNode::XOR { a, b } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
let min_ab = ia.min(ib);
let neg_max = -(ia.max(ib));
min_ab.max(neg_max)
}
SdfNode::Morph { a, b, t } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
ia * (1.0 - *t) + ib * *t
}
SdfNode::ColumnsUnion { a, b, r, .. } => {
let sharp = eval_interval(a, bounds).min(eval_interval(b, bounds));
Interval::new(sharp.lo - r, sharp.hi)
}
SdfNode::ColumnsIntersection { a, b, r, .. } => {
let sharp = eval_interval(a, bounds).max(eval_interval(b, bounds));
Interval::new(sharp.lo, sharp.hi + r)
}
SdfNode::ColumnsSubtraction { a, b, r, .. } => {
let sharp = eval_interval(a, bounds).max(-eval_interval(b, bounds));
Interval::new(sharp.lo, sharp.hi + r)
}
SdfNode::Pipe { a, b, r } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
let sq = ia * ia + ib * ib;
Interval::new(sq.lo.max(0.0).sqrt() - r, sq.hi.max(0.0).sqrt() + r.abs())
}
SdfNode::Engrave { a, b, r } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
let abs_b = ib.abs();
let inner = Interval::new(
(ia.lo + r - abs_b.hi) * std::f32::consts::FRAC_1_SQRT_2,
(ia.hi + r - abs_b.lo).max(0.0) * std::f32::consts::FRAC_1_SQRT_2,
);
ia.max(inner)
}
SdfNode::Groove { a, b, ra, rb } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
let abs_b = ib.abs();
let arm1 = Interval::new(ia.lo + ra, ia.hi + ra);
let arm2 = Interval::new(rb - abs_b.hi, rb - abs_b.lo);
ia.max(arm1.min(arm2))
}
SdfNode::Tongue { a, b, ra, rb } => {
let ia = eval_interval(a, bounds);
let ib = eval_interval(b, bounds);
let abs_b = ib.abs();
let arm1 = Interval::new(ia.lo - ra, ia.hi - ra);
let arm2 = Interval::new(abs_b.lo - rb, abs_b.hi - rb);
ia.min(arm1.max(arm2))
}
SdfNode::ExpSmoothUnion { a, b, k } => {
let sharp = eval_interval(a, bounds).min(eval_interval(b, bounds));
Interval::new(
k.max(1e-6).mul_add(-std::f32::consts::LN_2, sharp.lo),
sharp.hi,
)
}
SdfNode::ExpSmoothIntersection { a, b, k } => {
let sharp = eval_interval(a, bounds).max(eval_interval(b, bounds));
Interval::new(
sharp.lo,
k.max(1e-6).mul_add(std::f32::consts::LN_2, sharp.hi),
)
}
SdfNode::ExpSmoothSubtraction { a, b, k } => {
let sharp = eval_interval(a, bounds).max(-eval_interval(b, bounds));
Interval::new(
sharp.lo,
k.max(1e-6).mul_add(std::f32::consts::LN_2, sharp.hi),
)
}
SdfNode::Shear { child, shear } => {
let x = bounds.x;
let y = bounds.y - x * Interval::point(shear.x);
let z = bounds.z - x * Interval::point(shear.y) - bounds.y * Interval::point(shear.z);
eval_interval(child, Vec3Interval { x, y, z })
}
SdfNode::Animated { child, .. } => eval_interval(child, bounds),
SdfNode::Translate { child, offset } => eval_interval(child, bounds.translate(*offset)),
SdfNode::Rotate { child, rotation } => eval_interval(child, bounds.rotate(*rotation)),
SdfNode::Scale { child, factor } => {
let inv = 1.0 / *factor;
if !(factor.is_finite() && inv.is_finite()) {
return Interval::entire();
}
let scaled = Vec3Interval {
x: bounds.x * inv,
y: bounds.y * inv,
z: bounds.z * inv,
};
eval_interval(child, scaled) * *factor
}
SdfNode::ScaleNonUniform { child, factors } => {
if !(factors.x.is_finite()
&& factors.y.is_finite()
&& factors.z.is_finite()
&& (1.0 / factors.x).is_finite()
&& (1.0 / factors.y).is_finite()
&& (1.0 / factors.z).is_finite())
{
return Interval::entire();
}
let min_f = factors.x.min(factors.y).min(factors.z);
let scaled = Vec3Interval {
x: bounds.x * (1.0 / factors.x),
y: bounds.y * (1.0 / factors.y),
z: bounds.z * (1.0 / factors.z),
};
let d = eval_interval(child, scaled);
let (lo, hi) = if min_f >= 0.0 {
(d.lo * min_f, d.hi * min_f)
} else {
(d.hi * min_f, d.lo * min_f)
};
Interval::new(lo, hi)
}
SdfNode::ProjectiveTransform {
child,
lipschitz_bound,
..
} => {
let child_interval = eval_interval(child, bounds);
Interval::new(
child_interval.lo / lipschitz_bound,
child_interval.hi / lipschitz_bound,
)
}
SdfNode::LatticeDeform {
child,
bbox_min,
bbox_max,
..
} => {
let outside = bounds.x.hi < bbox_min.x
|| bounds.x.lo > bbox_max.x
|| bounds.y.hi < bbox_min.y
|| bounds.y.lo > bbox_max.y
|| bounds.z.hi < bbox_min.z
|| bounds.z.lo > bbox_max.z;
if outside {
eval_interval(child, bounds)
} else {
Interval::EVERYTHING
}
}
SdfNode::SdfSkinning { child, bones } => match skinning_affine(bones) {
Some((a, b)) => eval_interval(child, bounds.affine(&a, b)),
None => eval_interval(child, bounds),
},
SdfNode::Twist { child, strength: _ } => {
let max_xz = bounds.length_xz().hi;
let expanded = Vec3Interval {
x: Interval::new(-max_xz, max_xz),
y: bounds.y,
z: Interval::new(-max_xz, max_xz),
};
eval_interval(child, expanded)
}
SdfNode::Bend { child, .. } => {
let max_r = bounds.length().hi;
eval_interval(
child,
Vec3Interval::from_bounds(Vec3::splat(-max_r), Vec3::splat(max_r)),
)
}
SdfNode::RepeatInfinite { child, spacing } => {
let hs = *spacing * 0.5;
let cell = Vec3Interval {
x: if spacing.x > 0.0 {
Interval::new(-hs.x, hs.x)
} else {
bounds.x
},
y: if spacing.y > 0.0 {
Interval::new(-hs.y, hs.y)
} else {
bounds.y
},
z: if spacing.z > 0.0 {
Interval::new(-hs.z, hs.z)
} else {
bounds.z
},
};
eval_interval(child, cell)
}
SdfNode::RepeatFinite {
child,
count,
spacing,
} => {
let axis = |iv: Interval, s: f32, cnt: u32| {
if s <= 0.0 {
return iv;
}
let limit = cnt as f32 * 0.5;
let inv = 1.0 / s;
let cmin = round_half_up(iv.lo * inv).clamp(-limit, limit);
let cmax = round_half_up(iv.hi * inv).clamp(-limit, limit);
Interval::new(iv.lo - cmax * s, iv.hi - cmin * s)
};
eval_interval(
child,
Vec3Interval {
x: axis(bounds.x, spacing.x, count[0]),
y: axis(bounds.y, spacing.y, count[1]),
z: axis(bounds.z, spacing.z, count[2]),
},
)
}
SdfNode::Noise {
child, amplitude, ..
} => eval_interval(child, bounds).expand(*amplitude),
SdfNode::Round { child, radius } => eval_interval(child, bounds) - Interval::point(*radius),
SdfNode::Onion { child, thickness } => {
eval_interval(child, bounds).abs() - Interval::point(*thickness)
}
SdfNode::Elongate { child, amount } => {
let qx = bounds.x - bounds.x.clamp(-amount.x, amount.x);
let qy = bounds.y - bounds.y.clamp(-amount.y, amount.y);
let qz = bounds.z - bounds.z.clamp(-amount.z, amount.z);
eval_interval(
child,
Vec3Interval {
x: qx,
y: qy,
z: qz,
},
)
}
SdfNode::Mirror { child, axes } => eval_interval(child, bounds.mirror(*axes)),
SdfNode::OctantMirror { child } => {
let (ax, ay, az) = (bounds.x.abs(), bounds.y.abs(), bounds.z.abs());
let lo_min = ax.lo.min(ay.lo).min(az.lo);
let lo_max = ax.lo.max(ay.lo).max(az.lo);
let hi_min = ax.hi.min(ay.hi).min(az.hi);
let hi_max = ax.hi.max(ay.hi).max(az.hi);
eval_interval(
child,
Vec3Interval {
x: Interval::new(lo_max, hi_max),
y: Interval::new(lo_min, hi_max),
z: Interval::new(lo_min, hi_min),
},
)
}
SdfNode::Revolution { child, offset } => {
let qx = bounds.length_xz() - Interval::point(*offset);
eval_interval(
child,
Vec3Interval {
x: qx,
y: bounds.y,
z: Interval::ZERO,
},
)
}
SdfNode::Extrude { child, half_height } => {
let flat = Vec3Interval {
x: bounds.x,
y: bounds.y,
z: Interval::ZERO,
};
let d = eval_interval(child, flat);
let wy = bounds.z.abs() - Interval::point(*half_height);
let ox = d.max(Interval::ZERO);
let oy = wy.max(Interval::ZERO);
(ox.sqr() + oy.sqr()).sqrt() + d.max(wy).min(Interval::ZERO)
}
SdfNode::Taper {
child,
factor,
reach,
} => {
let denom = Interval::point(1.0) - bounds.y * Interval::point(*factor);
if denom.lo <= 0.0 && denom.hi >= 0.0 {
return Interval::EVERYTHING;
}
let s = Interval::new(
(1.0 / denom.lo).min(1.0 / denom.hi),
(1.0 / denom.lo).max(1.0 / denom.hi),
);
let d = eval_interval(
child,
Vec3Interval {
x: bounds.x * s,
y: bounds.y,
z: bounds.z * s,
},
);
taper_bound_interval(d, bounds, *factor, *reach)
}
SdfNode::Displacement { child, strength } => {
eval_interval(child, bounds).expand(strength.abs())
}
SdfNode::SineDisplacement {
child, amplitude, ..
} => eval_interval(child, bounds).expand(amplitude.abs()),
SdfNode::PolarRepeat { child, count } => {
let sector = std::f32::consts::TAU / (*count as f32);
let ha = sector * 0.5;
let max_r = bounds.length_xz().hi;
let (x_lo, z_abs) = if ha >= std::f32::consts::FRAC_PI_2 {
(-max_r, max_r)
} else {
(0.0, max_r * ha.sin())
};
eval_interval(
child,
Vec3Interval {
x: Interval::new(x_lo, max_r),
y: bounds.y,
z: Interval::new(-z_abs, z_abs),
},
)
}
SdfNode::SweepBezier { child, p0, p1, p2 } => {
let bmin_x = p0.x.min(p1.x).min(p2.x);
let bmax_x = p0.x.max(p1.x).max(p2.x);
let bmin_z = p0.y.min(p1.y).min(p2.y);
let bmax_z = p0.y.max(p1.y).max(p2.y);
let corners = [
(bounds.x.lo, bounds.z.lo),
(bounds.x.lo, bounds.z.hi),
(bounds.x.hi, bounds.z.lo),
(bounds.x.hi, bounds.z.hi),
];
let curve_corners = [
(bmin_x, bmin_z),
(bmin_x, bmax_z),
(bmax_x, bmin_z),
(bmax_x, bmax_z),
];
let mut max_d2: f32 = 0.0;
for &(px, pz) in &corners {
for &(cx, cz) in &curve_corners {
let dx = px - cx;
let dz = pz - cz;
max_d2 = max_d2.max(dx.mul_add(dx, dz * dz));
}
}
let min_perp = {
let dx = if bounds.x.hi < bmin_x {
bmin_x - bounds.x.hi
} else if bounds.x.lo > bmax_x {
bounds.x.lo - bmax_x
} else {
0.0
};
let dz = if bounds.z.hi < bmin_z {
bmin_z - bounds.z.hi
} else if bounds.z.lo > bmax_z {
bounds.z.lo - bmax_z
} else {
0.0
};
dx.hypot(dz)
};
eval_interval(
child,
Vec3Interval {
x: Interval::new(min_perp, max_d2.sqrt()),
y: bounds.y,
z: Interval::ZERO,
},
)
}
SdfNode::IcosahedralSymmetry { child } => {
let l = bounds.length().hi;
let cube = Interval::new(-l, l);
eval_interval(
child,
Vec3Interval {
x: cube,
y: cube,
z: cube,
},
)
}
SdfNode::IFS {
child,
transforms,
iterations,
} => {
let mut hull = bounds;
let (mut s_lo, mut s_hi) = (1.0f32, 1.0f32);
for t in transforms {
let sx = t[2].mul_add(t[2], t[1].mul_add(t[1], t[0] * t[0])).sqrt();
s_lo = s_lo.min(sx);
s_hi = s_hi.max(sx);
}
for _ in 0..*iterations {
let mut next = hull;
for t in transforms {
let a = [t[0], t[1], t[2], t[4], t[5], t[6], t[8], t[9], t[10]];
let img = hull.affine(&a, Vec3::new(t[12], t[13], t[14]));
next = next.hull(&img);
}
hull = next;
}
let d = eval_interval(child, hull);
let n = *iterations as i32;
let scale = Interval::new(s_lo.powi(n).max(1e-6), s_hi.powi(n).max(1e-6));
let recip = Interval::new(1.0 / scale.hi, 1.0 / scale.lo);
d * recip
}
SdfNode::HeightmapDisplacement {
child, amplitude, ..
} => {
let child_iv = eval_interval(child, bounds);
let amp = *amplitude;
Interval::new(child_iv.lo - amp.abs(), child_iv.hi + amp.abs())
}
SdfNode::SurfaceRoughness {
child,
amplitude,
octaves,
..
} => {
let child_iv = eval_interval(child, bounds);
let amp = amplitude.abs() * crate::modifiers::fbm_bound(*octaves);
Interval::new(child_iv.lo - amp, child_iv.hi + amp)
}
SdfNode::WithMaterial { child, .. } => eval_interval(child, bounds),
#[allow(unreachable_patterns)]
_ => Interval::EVERYTHING,
}
}
#[inline]
fn centre_and_half_diag(bounds: Vec3Interval) -> (Vec3, f32) {
let c = Vec3::new(
bounds.x.midpoint(),
bounds.y.midpoint(),
bounds.z.midpoint(),
);
let h = Vec3::new(bounds.x.width(), bounds.y.width(), bounds.z.width()) * 0.5;
(c, h.length())
}
#[inline]
fn ia_lipschitz(node: &SdfNode, bounds: Vec3Interval, l: f32) -> Interval {
let (c, rho) = centre_and_half_diag(bounds);
let d = crate::eval::eval(node, c);
if !d.is_finite() {
return Interval::EVERYTHING;
}
Interval::new(l.mul_add(-rho, d), l.mul_add(rho, d))
}
#[inline]
fn powf_nonneg(iv: Interval, m: f32) -> Interval {
let lo = iv.lo.max(0.0);
let hi = iv.hi.max(0.0);
Interval::new(lo.powf(m), hi.powf(m))
}
#[inline]
fn len2(a: Interval, b: Interval) -> Interval {
(a.sqr() + b.sqr()).sqrt()
}
#[inline]
fn mod_interval(x: Interval, step: f32) -> Interval {
if step <= 0.0 || !step.is_finite() || !(x.lo.is_finite() && x.hi.is_finite()) {
return Interval::new(0.0, step.max(0.0));
}
let k_lo = (x.lo / step).floor();
let k_hi = (x.hi / step).floor();
if k_lo == k_hi {
Interval::outward(x.lo - step * k_lo, x.hi - step * k_lo)
} else {
Interval::new(0.0, step)
}
}
fn ia_stairs_min(ia: Interval, ib: Interval, r: f32, n: f32) -> Interval {
let n = n.max(1.0);
let s = std::f32::consts::FRAC_1_SQRT_2;
let sqrt2 = std::f32::consts::SQRT_2;
let d = ia.min(ib);
let py = (ia + ib) * s;
let px = (ib - ia) * s;
let rn = r / n;
let off = (r - rn) * 0.5 * sqrt2;
let px = px - (off - 0.5 * sqrt2 * rn);
let py = py - off;
let step = r * sqrt2 / n;
let hs = step * 0.5;
let px = mod_interval(px + Interval::point(hs), step) - hs;
let d = d.min(py);
let npx = (px + py) * s;
let npy = (py - px) * s;
let edge = 0.5 * rn;
d.min((npx - edge).max(npy - edge))
}
#[inline]
fn ia_bsphere(bounds: Vec3Interval, radius: f32) -> Interval {
let l = bounds.length();
Interval::new(l.lo - radius, l.hi + radius)
}
pub fn eval_lipschitz(node: &SdfNode) -> f32 {
match node {
SdfNode::Sphere { .. }
| SdfNode::Box3d { .. }
| SdfNode::Cylinder { .. }
| SdfNode::Torus { .. }
| SdfNode::Plane { .. }
| SdfNode::Capsule { .. }
| SdfNode::Cone { .. }
| SdfNode::RoundedCone { .. }
| SdfNode::Pyramid { .. }
| SdfNode::Octahedron { .. }
| SdfNode::HexPrism { .. }
| SdfNode::Link { .. }
| SdfNode::Triangle { .. }
| SdfNode::Bezier { .. }
| SdfNode::RoundedBox { .. }
| SdfNode::CappedCone { .. }
| SdfNode::CappedTorus { .. }
| SdfNode::RoundedCylinder { .. }
| SdfNode::TriangularPrism { .. }
| SdfNode::CutSphere { .. }
| SdfNode::CutHollowSphere { .. }
| SdfNode::DeathStar { .. }
| SdfNode::SolidAngle { .. }
| SdfNode::Rhombus { .. }
| SdfNode::Vesica { .. }
| SdfNode::InfiniteCylinder { .. }
| SdfNode::InfiniteCone { .. }
| SdfNode::Heart { .. }
| SdfNode::Tube { .. }
| SdfNode::Diamond { .. }
| SdfNode::ChamferedCube { .. }
| SdfNode::Superellipsoid { .. }
| SdfNode::RoundedX { .. }
| SdfNode::Pie { .. }
| SdfNode::Trapezoid { .. }
| SdfNode::Parallelogram { .. }
| SdfNode::Tunnel { .. }
| SdfNode::UnevenCapsule { .. }
| SdfNode::ArcShape { .. }
| SdfNode::Moon { .. }
| SdfNode::CrossShape { .. }
| SdfNode::ParabolaSegment { .. }
| SdfNode::RegularPolygon { .. }
| SdfNode::StarPolygon { .. }
| SdfNode::Tetrahedron { .. }
| SdfNode::Dodecahedron { .. }
| SdfNode::Icosahedron { .. }
| SdfNode::TruncatedOctahedron { .. }
| SdfNode::TruncatedIcosahedron { .. }
| SdfNode::BoxFrame { .. }
| SdfNode::Circle2D { .. }
| SdfNode::Rect2D { .. }
| SdfNode::Segment2D { .. }
| SdfNode::Polygon2D { .. }
| SdfNode::RoundedRect2D { .. }
| SdfNode::Annular2D { .. }
| SdfNode::Egg { .. }
| SdfNode::Horseshoe { .. }
| SdfNode::BlobbyCross { .. }
| SdfNode::Stairs { .. }
| SdfNode::Helix { .. }
| SdfNode::Ellipsoid { .. } => 1.0,
SdfNode::Gyroid { .. } | SdfNode::SchwarzP { .. } | SdfNode::DiamondSurface { .. } => {
TPMS_SQRT3
}
SdfNode::MetricBall {
w_l1, w_l2, w_linf, ..
} => alice_det_math::metric::MetricWeights::new(*w_l1, *w_l2, *w_linf)
.map_or(f32::INFINITY, |w| w.lipschitz()),
SdfNode::MetricBlend { .. } => f32::INFINITY,
SdfNode::Neovius { .. } => TPMS_NEOVIUS,
SdfNode::Lidinoid { .. } => TPMS_LIDINOID,
SdfNode::IWP { .. } => TPMS_IWP,
SdfNode::FRD { .. } | SdfNode::FischerKochS { .. } => TPMS_FRD,
SdfNode::PMY { .. } => TPMS_PMY,
SdfNode::Barrel {
half_height, bulge, ..
} => shear_singular_value(2.0 * bulge.abs() / half_height.abs().max(1e-6)),
SdfNode::Terrain { scale, amplitude } => {
TERRAIN_FBM_GRAD.mul_add((scale * amplitude).abs(), 1.0)
}
SdfNode::Union { a, b }
| SdfNode::Intersection { a, b }
| SdfNode::Subtraction { a, b }
| SdfNode::SmoothUnion { a, b, .. }
| SdfNode::SmoothIntersection { a, b, .. }
| SdfNode::SmoothSubtraction { a, b, .. }
| SdfNode::XOR { a, b }
| SdfNode::Groove { a, b, .. }
| SdfNode::Tongue { a, b, .. }
| SdfNode::ExpSmoothUnion { a, b, .. }
| SdfNode::ExpSmoothIntersection { a, b, .. }
| SdfNode::ExpSmoothSubtraction { a, b, .. } => eval_lipschitz(a).max(eval_lipschitz(b)),
SdfNode::ChamferUnion { a, b, .. }
| SdfNode::ChamferIntersection { a, b, .. }
| SdfNode::ChamferSubtraction { a, b, .. }
| SdfNode::StairsUnion { a, b, .. }
| SdfNode::StairsIntersection { a, b, .. }
| SdfNode::StairsSubtraction { a, b, .. }
| SdfNode::Engrave { a, b, .. } => {
std::f32::consts::SQRT_2 * eval_lipschitz(a).max(eval_lipschitz(b))
}
SdfNode::Pipe { a, b, .. } => eval_lipschitz(a).hypot(eval_lipschitz(b)),
SdfNode::Morph { a, b, t } => t
.abs()
.mul_add(eval_lipschitz(b), (1.0 - t).abs() * eval_lipschitz(a)),
SdfNode::ColumnsUnion { .. }
| SdfNode::ColumnsIntersection { .. }
| SdfNode::ColumnsSubtraction { .. } => f32::INFINITY,
SdfNode::Translate { child, .. } | SdfNode::Rotate { child, .. } => eval_lipschitz(child),
SdfNode::Scale { child, .. } => eval_lipschitz(child),
SdfNode::ScaleNonUniform { child, factors } => {
let s = [factors.x.abs(), factors.y.abs(), factors.z.abs()];
let min_s = s[0].min(s[1]).min(s[2]).max(1e-6);
let max_s = s[0].max(s[1]).max(s[2]);
eval_lipschitz(child) * max_s / min_s
}
SdfNode::ProjectiveTransform {
child,
lipschitz_bound,
..
} => eval_lipschitz(child) * lipschitz_bound,
SdfNode::LatticeDeform { .. } => f32::INFINITY,
SdfNode::SdfSkinning { child, bones } => match skinning_affine(bones) {
Some((a, _)) => eval_lipschitz(child) * a.iter().map(|v| v * v).sum::<f32>().sqrt(),
None => eval_lipschitz(child),
},
SdfNode::Round { child, .. }
| SdfNode::Onion { child, .. }
| SdfNode::Elongate { child, .. }
| SdfNode::Mirror { child, .. }
| SdfNode::OctantMirror { child, .. }
| SdfNode::Revolution { child, .. }
| SdfNode::Extrude { child, .. } => eval_lipschitz(child),
SdfNode::SweepBezier { child, .. } => eval_lipschitz(child),
SdfNode::RepeatInfinite { .. }
| SdfNode::RepeatFinite { .. }
| SdfNode::PolarRepeat { .. } => f32::INFINITY,
SdfNode::Twist { child, strength } => {
eval_lipschitz(child)
* shear_singular_value(strength.abs() * child_radius(child, Axis::Y))
}
SdfNode::Bend { child, curvature } => {
eval_lipschitz(child) * curvature.abs().mul_add(child_radius(child, Axis::Z), 1.0)
}
SdfNode::Noise {
child,
amplitude,
frequency,
..
} => (amplitude * frequency)
.abs()
.mul_add(PERLIN_GRAD, eval_lipschitz(child)),
SdfNode::Taper { .. } => f32::INFINITY,
SdfNode::Displacement { child, strength } => {
strength.abs().mul_add(5.0, eval_lipschitz(child))
}
SdfNode::SineDisplacement {
child,
amplitude,
frequency,
} => {
let f = frequency
.x
.abs()
.max(frequency.y.abs())
.max(frequency.z.abs());
(amplitude * f).abs() + eval_lipschitz(child)
}
SdfNode::Shear { child, shear } => {
let max_shear = shear.x.abs().max(shear.y.abs()).max(shear.z.abs());
eval_lipschitz(child) * (1.0 + max_shear)
}
SdfNode::Animated { child, .. } => eval_lipschitz(child),
SdfNode::IcosahedralSymmetry { child } => eval_lipschitz(child),
SdfNode::IFS { .. } => f32::INFINITY,
SdfNode::HeightmapDisplacement { .. } => f32::INFINITY,
SdfNode::SurfaceRoughness {
child,
amplitude,
frequency,
octaves,
} => (amplitude * frequency * *octaves as f32)
.abs()
.mul_add(VALUE_NOISE_GRAD, eval_lipschitz(child)),
SdfNode::WithMaterial { child, .. } => eval_lipschitz(child),
}
}
const TPMS_SQRT3: f32 = 1.732_051;
const TPMS_NEOVIUS: f32 = 7.05;
const TPMS_LIDINOID: f32 = 2.62;
const TPMS_IWP: f32 = 5.25;
const TPMS_FRD: f32 = 2.47;
const TPMS_PMY: f32 = 4.17;
const PERLIN_GRAD: f32 = 3.5;
const VALUE_NOISE_GRAD: f32 = 5.196_153;
const TERRAIN_FBM_GRAD: f32 = 6.42;
const UNBOUNDED_CHILD_RADIUS: f32 = 10.0;
const UNBOUNDED_CHILD_SENTINEL: f32 = 1e6;
#[inline]
fn shear_singular_value(v: f32) -> f32 {
0.5f32.mul_add(v, (v * 0.25).mul_add(v, 1.0).sqrt())
}
#[derive(Clone, Copy)]
enum Axis {
Y,
Z,
}
fn child_radius(child: &SdfNode, axis: Axis) -> f32 {
use crate::compiled::{get_scene_aabb, CompiledSdfBvh};
let Ok(bvh) = CompiledSdfBvh::try_compile(child) else {
return UNBOUNDED_CHILD_RADIUS;
};
let aabb = get_scene_aabb(&bvh);
let (lo, hi) = (aabb.min(), aabb.max());
if !aabb.is_valid() || !lo.is_finite() || !hi.is_finite() {
return UNBOUNDED_CHILD_RADIUS;
}
let reach = |a: f32, b: f32| a.abs().max(b.abs());
let r = match axis {
Axis::Y => reach(lo.x, hi.x).hypot(reach(lo.z, hi.z)),
Axis::Z => reach(lo.x, hi.x).hypot(reach(lo.y, hi.y)),
};
if r > UNBOUNDED_CHILD_SENTINEL {
UNBOUNDED_CHILD_RADIUS
} else {
r
}
}
fn taper_bound_interval(d: Interval, p: Vec3Interval, factor: f32, reach: [f32; 2]) -> Interval {
let f_abs = factor.abs();
if f_abs == 0.0 || d.lo == f32::NEG_INFINITY || d.hi == f32::INFINITY {
return d;
}
let one = Interval::point(1.0);
let eps = Interval::point(1e-6);
let recip = |a: Interval| Interval::new(1.0 / a.hi, 1.0 / a.lo);
let div = |a: Interval, b: Interval| a * recip(b);
let den_abs = (one - p.y * Interval::point(factor)).abs().max(eps);
let s = recip(den_abs);
let rho = (p.x.sqr() + p.z.sqr()).sqrt();
let d_abs = d.abs();
let j0 = s.max(one) + s.sqr() * rho * f_abs;
let r = div(d_abs, j0).min(den_abs * (0.5 / f_abs));
let den1 = (den_abs - r * f_abs).max(eps);
let s1 = recip(den1);
let j1 = s1.max(one) + s1.sqr() * (rho + r) * f_abs;
let mag = div(d_abs, j1).min(r);
let d_j = if d.hi < 0.0 {
-mag
} else if d.lo >= 0.0 {
mag
} else {
(-mag).hull(mag)
};
if reach[0] >= 1e30 || reach[1] >= 1e30 {
return d_j;
}
let k = reach[0] * f_abs;
let inv_n = 1.0 / k.mul_add(k, 1.0).sqrt();
let big_y = p.y - 1.0 / factor;
let d_cone = (rho - big_y.abs() * k) * inv_n;
let d_slab = p.y.abs() - reach[1];
let d_region = d_cone.max(d_slab);
if d.hi < 0.0 {
d_j
} else if d.lo >= 0.0 {
d_j.max(d_region)
} else {
d_j.hull(d_j.max(d_region))
}
}
fn skinning_affine(
bones: &[crate::transforms::skinning::BoneTransform],
) -> Option<([f32; 9], Vec3)> {
use glam::Mat4;
let active: Vec<_> = bones.iter().filter(|b| b.weight >= 1e-6).collect();
let total: f32 = active.iter().map(|b| b.weight).sum();
if active.is_empty() || total <= 1e-6 {
return None;
}
let mut m = Mat4::ZERO;
for b in &active {
let combined =
Mat4::from_cols_array(&b.inv_bind_pose) * Mat4::from_cols_array(&b.current_pose);
m += combined * (b.weight / total);
}
let c = m.to_cols_array();
Some((
[c[0], c[1], c[2], c[4], c[5], c[6], c[8], c[9], c[10]],
Vec3::new(c[12], c[13], c[14]),
))
}
pub(crate) fn taper_reach(child: &SdfNode) -> [f32; 2] {
use crate::compiled::{get_scene_aabb, CompiledSdfBvh};
let Ok(bvh) = CompiledSdfBvh::try_compile(child) else {
return [f32::INFINITY; 2];
};
let aabb = get_scene_aabb(&bvh);
let (lo, hi) = (aabb.min(), aabb.max());
if !aabb.is_valid() || !lo.is_finite() || !hi.is_finite() {
return [f32::INFINITY; 2];
}
let reach = |a: f32, b: f32| a.abs().max(b.abs());
let r_xz = reach(lo.x, hi.x).hypot(reach(lo.z, hi.z));
let r_y = reach(lo.y, hi.y);
if r_xz > UNBOUNDED_CHILD_SENTINEL || r_y > UNBOUNDED_CHILD_SENTINEL {
[f32::INFINITY; 2]
} else {
[r_xz, r_y]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eval::eval;
use std::cmp::Ordering;
fn check(node: &SdfNode, min: Vec3, max: Vec3, n: usize) {
let iv = eval_interval(node, Vec3Interval::from_bounds(min, max));
let step = (max - min) / ((n - 1) as f32);
for ix in 0..n {
for iy in 0..n {
for iz in 0..n {
let p = min + Vec3::new(ix as f32, iy as f32, iz as f32) * step;
let d = eval(node, p);
assert!(
d >= iv.lo - 1e-4 && d <= iv.hi + 1e-4,
"d={d} not in [{lo}, {hi}] at {p:?}",
lo = iv.lo,
hi = iv.hi,
);
}
}
}
}
#[track_caller]
fn encloses(bound: f32, exact: f32, side: Ordering) {
let ulps = 8.0 * f32::EPSILON * exact.abs().max(1.0);
match side {
Ordering::Less => assert!(
bound <= exact && bound >= exact - ulps,
"lower bound {bound} must be at or just below {exact}"
),
_ => assert!(
bound >= exact && bound <= exact + ulps,
"upper bound {bound} must be at or just above {exact}"
),
}
}
#[test]
fn test_interval_ops() {
let a = Interval::new(1.0, 3.0);
let b = Interval::new(2.0, 5.0);
encloses((a + b).lo, 3.0, Ordering::Less);
encloses((a + b).hi, 8.0, Ordering::Greater);
encloses((a - b).lo, -4.0, Ordering::Less);
encloses((a - b).hi, 1.0, Ordering::Greater);
assert_eq!((-a).lo, -a.hi);
assert_eq!((-a).hi, -a.lo);
}
#[test]
fn test_interval_abs() {
let a = Interval::new(1.0, 3.0);
assert_eq!(a.abs().lo, a.lo);
let b = Interval::new(-3.0, -1.0);
assert_eq!(b.abs().hi, -b.lo);
assert_eq!(Interval::new(-2.0, 3.0).abs().lo, 0.0);
}
#[test]
fn test_interval_sqr() {
let s = Interval::new(-2.0, 3.0).sqr();
assert_eq!(s.lo, 0.0);
encloses(s.hi, 9.0, Ordering::Greater);
}
#[test]
fn test_sphere() {
check(
&SdfNode::sphere(1.0),
Vec3::splat(-2.0),
Vec3::splat(2.0),
5,
);
}
#[test]
fn test_box3d() {
check(
&SdfNode::box3d(1.0, 0.5, 0.75),
Vec3::splat(-2.0),
Vec3::splat(2.0),
5,
);
}
#[test]
fn test_cylinder() {
check(
&SdfNode::cylinder(0.5, 1.0),
Vec3::splat(-2.0),
Vec3::splat(2.0),
5,
);
}
#[test]
fn test_torus() {
check(
&SdfNode::torus(1.0, 0.25),
Vec3::splat(-2.0),
Vec3::splat(2.0),
5,
);
}
#[test]
fn test_plane() {
check(
&SdfNode::plane(Vec3::Y, 0.0),
Vec3::splat(-2.0),
Vec3::splat(2.0),
5,
);
}
#[test]
fn test_rounded_box() {
check(
&SdfNode::rounded_box(1.0, 0.5, 0.75, 0.1),
Vec3::splat(-2.0),
Vec3::splat(2.0),
4,
);
}
#[test]
fn test_union() {
let node = SdfNode::sphere(1.0)
.translate(1.0, 0.0, 0.0)
.union(SdfNode::sphere(1.0).translate(-1.0, 0.0, 0.0));
check(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 4);
}
#[test]
fn test_smooth_union() {
let node = SdfNode::sphere(1.0)
.translate(0.5, 0.0, 0.0)
.smooth_union(SdfNode::sphere(1.0).translate(-0.5, 0.0, 0.0), 0.5);
check(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 4);
}
#[test]
fn test_subtraction() {
let node = SdfNode::sphere(1.5).subtract(SdfNode::sphere(1.0).translate(0.5, 0.0, 0.0));
check(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 4);
}
#[test]
fn test_translate() {
check(
&SdfNode::sphere(1.0).translate(2.0, 0.0, 0.0),
Vec3::new(0.0, -2.0, -2.0),
Vec3::new(4.0, 2.0, 2.0),
4,
);
}
#[test]
fn test_rotate() {
let node = SdfNode::box3d(1.0, 0.5, 0.3).rotate(Quat::from_rotation_y(0.7));
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_scale() {
check(
&SdfNode::sphere(1.0).scale(2.0),
Vec3::splat(-4.0),
Vec3::splat(4.0),
4,
);
}
#[test]
fn test_round() {
let node = SdfNode::box3d(1.0, 1.0, 1.0).round(0.2);
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_onion() {
let node = SdfNode::sphere(1.0).onion(0.1);
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_revolution() {
let node = SdfNode::Revolution {
child: std::sync::Arc::new(SdfNode::sphere(0.3)),
offset: 1.0,
};
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_extrude() {
let node = SdfNode::Extrude {
child: std::sync::Arc::new(SdfNode::sphere(0.5)),
half_height: 1.0,
};
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_mirror() {
let node = SdfNode::box3d(1.0, 0.5, 0.5).mirror(true, false, false);
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_pruning() {
let sphere = SdfNode::sphere(1.0);
let far = Vec3Interval::from_bounds(Vec3::splat(5.0), Vec3::splat(6.0));
assert!(eval_interval(&sphere, far).is_positive());
let inside = Vec3Interval::from_bounds(Vec3::splat(-0.1), Vec3::splat(0.1));
assert!(eval_interval(&sphere, inside).is_negative());
}
#[test]
fn test_chamfer_blend() {
let node = SdfNode::sphere(1.0).chamfer_union(SdfNode::box3d(0.8, 0.8, 0.8), 0.3);
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_stairs_blend() {
let node = SdfNode::sphere(1.0).stairs_union(SdfNode::box3d(0.8, 0.8, 0.8), 0.3, 4.0);
check(&node, Vec3::splat(-2.0), Vec3::splat(2.0), 4);
}
#[test]
fn test_conservative_primitives() {
check(
&SdfNode::cone(0.5, 1.0),
Vec3::splat(-3.0),
Vec3::splat(3.0),
4,
);
check(
&SdfNode::octahedron(1.0),
Vec3::splat(-2.0),
Vec3::splat(2.0),
4,
);
check(
&SdfNode::pyramid(1.0),
Vec3::splat(-3.0),
Vec3::splat(3.0),
4,
);
}
fn check_lipschitz(node: &SdfNode, min: Vec3, max: Vec3, n: usize) {
let lip = eval_lipschitz(node);
assert!(lip >= 1.0, "Lipschitz should be >= 1.0, got {lip}");
let eps = 1e-3;
let step = (max - min) / ((n - 1) as f32);
let mut max_grad: f32 = 0.0;
for ix in 0..n {
for iy in 0..n {
for iz in 0..n {
let p = min + step * Vec3::new(ix as f32, iy as f32, iz as f32);
let d = eval(node, p);
let gx = (eval(node, p + Vec3::X * eps) - eval(node, p - Vec3::X * eps))
/ (2.0 * eps);
let gy = (eval(node, p + Vec3::Y * eps) - eval(node, p - Vec3::Y * eps))
/ (2.0 * eps);
let gz = (eval(node, p + Vec3::Z * eps) - eval(node, p - Vec3::Z * eps))
/ (2.0 * eps);
let grad_mag = (gx * gx + gy * gy + gz * gz).sqrt();
max_grad = max_grad.max(grad_mag);
assert!(
grad_mag <= lip.mul_add(1.05, 0.01),
"|∇SDF|={grad_mag:.4} > L={lip:.4} at {p:?} (d={d:.4})"
);
}
}
}
}
#[test]
fn test_lipschitz_sphere() {
check_lipschitz(
&SdfNode::sphere(1.0),
Vec3::splat(-3.0),
Vec3::splat(3.0),
6,
);
}
#[test]
fn test_lipschitz_box() {
check_lipschitz(
&SdfNode::box3d(1.0, 0.5, 0.75),
Vec3::splat(-3.0),
Vec3::splat(3.0),
6,
);
}
#[test]
fn test_lipschitz_union() {
let node = SdfNode::sphere(1.0)
.translate(1.0, 0.0, 0.0)
.union(SdfNode::sphere(1.0).translate(-1.0, 0.0, 0.0));
assert_eq!(eval_lipschitz(&node), 1.0);
check_lipschitz(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 5);
}
#[test]
fn test_lipschitz_smooth_union() {
let node = SdfNode::sphere(1.0)
.translate(0.5, 0.0, 0.0)
.smooth_union(SdfNode::sphere(1.0).translate(-0.5, 0.0, 0.0), 0.5);
assert_eq!(eval_lipschitz(&node), 1.0);
check_lipschitz(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 5);
}
#[test]
fn test_lipschitz_scale() {
let node = SdfNode::sphere(1.0).scale(2.0);
assert_eq!(eval_lipschitz(&node), 1.0);
}
#[test]
fn test_lipschitz_non_uniform_scale() {
let node = SdfNode::ScaleNonUniform {
child: std::sync::Arc::new(SdfNode::sphere(1.0)),
factors: Vec3::new(2.0, 1.0, 1.0),
};
assert_eq!(eval_lipschitz(&node), 2.0);
}
#[test]
fn test_lipschitz_noise() {
let node = SdfNode::Noise {
child: std::sync::Arc::new(SdfNode::sphere(1.0)),
amplitude: 0.2,
frequency: 3.0,
seed: 0,
};
assert!((eval_lipschitz(&node) - 0.6f32.mul_add(PERLIN_GRAD, 1.0)).abs() < 1e-5);
}
#[test]
fn test_lipschitz_twist() {
let node = SdfNode::Twist {
child: std::sync::Arc::new(SdfNode::sphere(1.0)),
strength: 0.5,
};
let v = 0.5 * 2.0_f32.sqrt();
let expected = 0.5f32.mul_add(v, (0.25 * v).mul_add(v, 1.0).sqrt());
assert!((eval_lipschitz(&node) - expected).abs() < 1e-3);
}
#[test]
fn test_lipschitz_round() {
let node = SdfNode::box3d(1.0, 1.0, 1.0).round(0.2);
assert_eq!(eval_lipschitz(&node), 1.0);
check_lipschitz(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 5);
}
#[test]
fn interval_width() {
let iv = Interval::new(1.0, 3.0);
assert!((iv.width() - 2.0).abs() < 1e-6);
}
#[test]
fn interval_midpoint() {
let iv = Interval::new(1.0, 5.0);
assert!((iv.midpoint() - 3.0).abs() < 1e-6);
}
#[test]
fn interval_contains_value() {
let iv = Interval::new(-1.0, 2.0);
assert!(iv.contains(0.0));
assert!(iv.contains(-1.0));
assert!(iv.contains(2.0));
assert!(!iv.contains(3.0));
assert!(!iv.contains(-2.0));
}
#[test]
fn interval_overlaps() {
let a = Interval::new(0.0, 2.0);
let b = Interval::new(1.0, 3.0);
let c = Interval::new(3.0, 5.0);
assert!(a.overlaps(b));
assert!(!a.overlaps(c));
}
#[test]
fn interval_intersection() {
let a = Interval::new(0.0, 3.0);
let b = Interval::new(1.0, 5.0);
let c = a.intersect(b);
assert!((c.lo - 1.0).abs() < 1e-6);
assert!((c.hi - 3.0).abs() < 1e-6);
}
#[test]
fn interval_hull() {
let a = Interval::new(1.0, 2.0);
let b = Interval::new(4.0, 5.0);
let h = a.hull(b);
assert!((h.lo - 1.0).abs() < 1e-6);
assert!((h.hi - 5.0).abs() < 1e-6);
}
#[test]
fn test_lipschitz_subtraction() {
let node = SdfNode::sphere(1.0).subtract(SdfNode::box3d(0.5, 0.5, 0.5));
assert_eq!(eval_lipschitz(&node), 1.0);
}
#[test]
fn test_lipschitz_intersection() {
let node = SdfNode::sphere(1.0).intersection(SdfNode::box3d(0.5, 0.5, 0.5));
assert_eq!(eval_lipschitz(&node), 1.0);
}
#[test]
fn test_lipschitz_onion() {
let node = SdfNode::Onion {
child: std::sync::Arc::new(SdfNode::sphere(1.0)),
thickness: 0.1,
};
assert_eq!(eval_lipschitz(&node), 1.0);
}
#[test]
fn test_interval_morph() {
let node = SdfNode::Morph {
a: std::sync::Arc::new(SdfNode::sphere(1.0)),
b: std::sync::Arc::new(SdfNode::box3d(1.0, 1.0, 1.0)),
t: 0.5,
};
check(&node, Vec3::splat(-3.0), Vec3::splat(3.0), 4);
}
}