pub(crate) mod noise;
use crate::vec3::{length, scale, sub};
use crate::{Real, Sdf};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FieldBound {
Exact,
Lipschitz {
l: f64,
},
Underestimate {
q: f64,
},
Unbounded,
}
impl FieldBound {
#[must_use]
pub const fn is_exact(self) -> bool {
matches!(self, Self::Exact)
}
#[must_use]
pub const fn lipschitz(self) -> Option<f64> {
match self {
Self::Exact | Self::Underestimate { .. } => Some(1.0),
Self::Lipschitz { l } => Some(l),
Self::Unbounded => None,
}
}
}
pub trait ReferenceField: Sdf {
const NAME: &'static str;
fn domain(&self) -> ([Self::Scalar; 3], [Self::Scalar; 3]);
fn closed_in_domain(&self) -> bool;
fn expected_euler(&self) -> Option<i64>;
fn bound(&self) -> FieldBound;
}
#[macro_export]
macro_rules! for_each_reference_field {
($scalar:ty, |$name:ident, $field:ident| $body:block) => {{
{
let $name = "sphere";
let $field = $crate::fields::Sphere::<$scalar>::canonical();
$body
}
{
let $name = "torus";
let $field = $crate::fields::Torus::<$scalar>::canonical();
$body
}
{
let $name = "box_exact";
let $field = $crate::fields::BoxExact::<$scalar>::canonical();
$body
}
{
let $name = "csg_difference";
let $field = $crate::fields::csg_difference::<$scalar>();
$body
}
{
let $name = "thin_plate";
let $field = $crate::fields::ThinPlate::<$scalar>::canonical();
$body
}
{
let $name = "gyroid";
let $field = $crate::fields::capped_gyroid::<$scalar>();
$body
}
{
let $name = "fbm_terrain";
let $field = $crate::fields::FbmTerrain::<$scalar>::canonical();
$body
}
{
let $name = "noise_cavity";
let $field = $crate::fields::noise_cavity::<$scalar>();
$body
}
}};
}
const COMPACT_DOMAIN: f64 = 2.0;
#[inline]
fn cube_domain<R: Real>(half: f64) -> ([R; 3], [R; 3]) {
let h = R::from_f64(half);
([-h, -h, -h], [h, h, h])
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Sphere<R: Real> {
pub center: [R; 3],
pub radius: R,
}
impl<R: Real> Sphere<R> {
#[must_use]
pub fn canonical() -> Self {
Self {
center: [R::ZERO; 3],
radius: R::ONE,
}
}
}
impl<R: Real> Default for Sphere<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for Sphere<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
let d = sub(p, self.center);
length(d) - self.radius
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
let d = sub(p, self.center);
let len = length(d);
scale(d, len.recip())
}
}
impl<R: Real> ReferenceField for Sphere<R> {
const NAME: &'static str = "sphere";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(COMPACT_DOMAIN)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
Some(2)
}
fn bound(&self) -> FieldBound {
FieldBound::Exact
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Torus<R: Real> {
pub center: [R; 3],
pub major: R,
pub minor: R,
}
impl<R: Real> Torus<R> {
#[must_use]
pub fn canonical() -> Self {
Self {
center: [R::ZERO; 3],
major: R::ONE,
minor: R::from_f64(0.3),
}
}
}
impl<R: Real> Default for Torus<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for Torus<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
let d = sub(p, self.center);
let s = (d[0] * d[0] + d[2] * d[2]).sqrt();
let q = [s - self.major, d[1]];
(q[0] * q[0] + q[1] * q[1]).sqrt() - self.minor
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
let d = sub(p, self.center);
let s = (d[0] * d[0] + d[2] * d[2]).sqrt();
let q = [s - self.major, d[1]];
let qlen = (q[0] * q[0] + q[1] * q[1]).sqrt();
let radial = q[0] / qlen; [radial * (d[0] / s), q[1] / qlen, radial * (d[2] / s)]
}
}
impl<R: Real> ReferenceField for Torus<R> {
const NAME: &'static str = "torus";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(COMPACT_DOMAIN)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
Some(0) }
fn bound(&self) -> FieldBound {
FieldBound::Exact
}
}
#[inline]
fn box_q<R: Real>(p: [R; 3], center: [R; 3], half: [R; 3]) -> ([R; 3], [R; 3]) {
let d = sub(p, center);
let q = [
d[0].abs() - half[0],
d[1].abs() - half[1],
d[2].abs() - half[2],
];
(d, q)
}
#[inline]
fn box_sample<R: Real>(p: [R; 3], center: [R; 3], half: [R; 3]) -> R {
let (_, q) = box_q(p, center, half);
let outside = [q[0].max(R::ZERO), q[1].max(R::ZERO), q[2].max(R::ZERO)];
let inside = q[0].max(q[1]).max(q[2]).min(R::ZERO);
length(outside) + inside
}
#[inline]
fn box_gradient<R: Real>(p: [R; 3], center: [R; 3], half: [R; 3]) -> [R; 3] {
let (d, q) = box_q(p, center, half);
let max_q = q[0].max(q[1]).max(q[2]);
if max_q > R::ZERO {
let m = [q[0].max(R::ZERO), q[1].max(R::ZERO), q[2].max(R::ZERO)];
let inv = length(m).recip();
[
d[0].signum() * m[0] * inv,
d[1].signum() * m[1] * inv,
d[2].signum() * m[2] * inv,
]
} else {
let j = if q[0] >= q[1] && q[0] >= q[2] {
0
} else if q[1] >= q[2] {
1
} else {
2
};
let mut g = [R::ZERO; 3];
g[j] = d[j].signum();
g
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxExact<R: Real> {
pub center: [R; 3],
pub half_extents: [R; 3],
}
impl<R: Real> BoxExact<R> {
#[must_use]
pub fn canonical() -> Self {
Self {
center: [R::ZERO; 3],
half_extents: [R::ONE; 3],
}
}
}
impl<R: Real> Default for BoxExact<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for BoxExact<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
box_sample(p, self.center, self.half_extents)
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
box_gradient(p, self.center, self.half_extents)
}
}
impl<R: Real> ReferenceField for BoxExact<R> {
const NAME: &'static str = "box_exact";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(COMPACT_DOMAIN)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
Some(2)
}
fn bound(&self) -> FieldBound {
FieldBound::Exact
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ThinPlate<R: Real> {
pub center: [R; 3],
pub half_extents: [R; 3],
}
impl<R: Real> ThinPlate<R> {
pub const THICKNESS_IN_CELLS: f64 = 0.4;
#[must_use]
pub fn for_cell_size(h: R) -> Self {
let half_thickness = h * R::from_f64(Self::THICKNESS_IN_CELLS * 0.5);
Self {
center: [R::ZERO; 3],
half_extents: [R::ONE, half_thickness, R::ONE],
}
}
pub const CANONICAL_CELL_SIZE: f64 = 2.0 * COMPACT_DOMAIN / 64.0;
#[must_use]
pub fn canonical() -> Self {
Self::for_cell_size(R::from_f64(Self::CANONICAL_CELL_SIZE))
}
}
impl<R: Real> Default for ThinPlate<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for ThinPlate<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
box_sample(p, self.center, self.half_extents)
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
box_gradient(p, self.center, self.half_extents)
}
}
impl<R: Real> ReferenceField for ThinPlate<R> {
const NAME: &'static str = "thin_plate";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(COMPACT_DOMAIN)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
Some(2)
}
fn bound(&self) -> FieldBound {
FieldBound::Exact
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Difference<A, B> {
pub a: A,
pub b: B,
}
impl<A: Sdf, B: Sdf<Scalar = A::Scalar>> Sdf for Difference<A, B> {
type Scalar = A::Scalar;
#[inline]
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
let (fa, fb) = (self.a.sample(p), self.b.sample(p));
if fa >= -fb { fa } else { -fb }
}
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
let (fa, fb) = (self.a.sample(p), self.b.sample(p));
if fa >= -fb {
self.a.gradient(p)
} else {
let g = self.b.gradient(p);
[-g[0], -g[1], -g[2]]
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Intersection<A, B> {
pub a: A,
pub b: B,
}
impl<A: Sdf, B: Sdf<Scalar = A::Scalar>> Sdf for Intersection<A, B> {
type Scalar = A::Scalar;
#[inline]
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
let (fa, fb) = (self.a.sample(p), self.b.sample(p));
if fa >= fb { fa } else { fb }
}
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
let (fa, fb) = (self.a.sample(p), self.b.sample(p));
if fa >= fb {
self.a.gradient(p)
} else {
self.b.gradient(p)
}
}
}
pub trait BoundedSdf: Sdf {
fn value_bound(&self) -> FieldBound;
}
#[must_use]
pub fn composed_bound(a: FieldBound, b: FieldBound) -> FieldBound {
match (a.lipschitz(), b.lipschitz()) {
(Some(la), Some(lb)) => FieldBound::Lipschitz {
l: if la > lb { la } else { lb },
},
_ => FieldBound::Unbounded,
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Union<A, B> {
pub a: A,
pub b: B,
}
impl<A: Sdf, B: Sdf<Scalar = A::Scalar>> Sdf for Union<A, B> {
type Scalar = A::Scalar;
#[inline]
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
let (fa, fb) = (self.a.sample(p), self.b.sample(p));
if fa <= fb { fa } else { fb }
}
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
let (fa, fb) = (self.a.sample(p), self.b.sample(p));
if fa <= fb {
self.a.gradient(p)
} else {
self.b.gradient(p)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SmoothUnion<A, B, R> {
pub a: A,
pub b: B,
pub k: R,
}
impl<A: Sdf, B: Sdf<Scalar = A::Scalar>> Sdf for SmoothUnion<A, B, A::Scalar> {
type Scalar = A::Scalar;
#[inline]
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
crate::brush::smooth_min(self.a.sample(p), self.b.sample(p), self.k)
}
#[inline]
fn gradient(&self, p: [Self::Scalar; 3]) -> [Self::Scalar; 3] {
let e = Self::Scalar::from_f64(1e-4);
let at = |q: [Self::Scalar; 3]| self.sample(q);
let two = Self::Scalar::ONE + Self::Scalar::ONE;
[
(at([p[0] + e, p[1], p[2]]) - at([p[0] - e, p[1], p[2]])) / (two * e),
(at([p[0], p[1] + e, p[2]]) - at([p[0], p[1] - e, p[2]])) / (two * e),
(at([p[0], p[1], p[2] + e]) - at([p[0], p[1], p[2] - e])) / (two * e),
]
}
}
pub type CsgDifference<R> = Difference<BoxExact<R>, Sphere<R>>;
#[must_use]
pub fn csg_difference<R: Real>() -> CsgDifference<R> {
Difference {
a: BoxExact::canonical(),
b: Sphere {
center: [R::from_f64(0.6); 3],
radius: R::from_f64(0.75),
},
}
}
impl<R: Real> ReferenceField for CsgDifference<R> {
const NAME: &'static str = "csg_difference";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(COMPACT_DOMAIN)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
Some(2)
}
fn bound(&self) -> FieldBound {
FieldBound::Underestimate { q: 0.5 }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Gyroid<R: Real> {
pub scale: R,
pub iso: R,
}
impl<R: Real> Gyroid<R> {
#[must_use]
pub fn canonical() -> Self {
Self {
scale: R::ONE,
iso: R::ZERO,
}
}
}
impl<R: Real> Default for Gyroid<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for Gyroid<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
let (a, b, c) = (self.scale * p[0], self.scale * p[1], self.scale * p[2]);
a.sin() * b.cos() + b.sin() * c.cos() + c.sin() * a.cos() - self.iso
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
let (a, b, c) = (self.scale * p[0], self.scale * p[1], self.scale * p[2]);
let (sa, ca) = (a.sin(), a.cos());
let (sb, cb) = (b.sin(), b.cos());
let (sc, cc) = (c.sin(), c.cos());
[
self.scale * (ca * cb - sc * sa),
self.scale * (cb * cc - sa * sb),
self.scale * (cc * ca - sb * sc),
]
}
}
pub type CappedGyroid<R> = Intersection<Gyroid<R>, Sphere<R>>;
#[must_use]
pub fn capped_gyroid<R: Real>() -> CappedGyroid<R> {
Intersection {
a: Gyroid::canonical(),
b: Sphere {
center: [R::ZERO; 3],
radius: R::from_f64(6.0),
},
}
}
impl<R: Real> ReferenceField for CappedGyroid<R> {
const NAME: &'static str = "gyroid";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(7.0)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
None }
fn bound(&self) -> FieldBound {
FieldBound::Lipschitz {
l: 3.464_101_615_137_754_6,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct NoiseVolume<R: Real> {
pub seed: u32,
pub frequency: R,
pub iso: R,
}
impl<R: Real> NoiseVolume<R> {
#[must_use]
pub fn canonical() -> Self {
Self {
seed: 0x5EED_1234,
frequency: R::from_f64(3.45),
iso: R::from_f64(0.25),
}
}
}
impl<R: Real> Default for NoiseVolume<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for NoiseVolume<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
let (n, _) = noise::perlin(scale(p, self.frequency), self.seed);
n - self.iso
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
let (_, g) = noise::perlin(scale(p, self.frequency), self.seed);
scale(g, self.frequency)
}
}
pub type NoiseCavity<R> = Intersection<NoiseVolume<R>, Sphere<R>>;
#[must_use]
pub fn noise_cavity<R: Real>() -> NoiseCavity<R> {
Intersection {
a: NoiseVolume::canonical(),
b: Sphere {
center: [R::ZERO; 3],
radius: R::from_f64(1.5),
},
}
}
impl<R: Real> ReferenceField for NoiseCavity<R> {
const NAME: &'static str = "noise_cavity";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(2.0)
}
fn closed_in_domain(&self) -> bool {
true
}
fn expected_euler(&self) -> Option<i64> {
None }
fn bound(&self) -> FieldBound {
FieldBound::Unbounded
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FbmTerrain<R: Real> {
pub seed: u32,
pub octaves: u32,
pub lacunarity: R,
pub gain: R,
pub frequency: R,
pub amplitude: R,
pub base_height: R,
}
impl<R: Real> FbmTerrain<R> {
#[must_use]
pub fn canonical() -> Self {
Self {
seed: 0x5EED_1234,
octaves: 4,
lacunarity: R::from_f64(2.0),
gain: R::from_f64(0.5),
frequency: R::from_f64(0.25),
amplitude: R::from_f64(2.0),
base_height: R::ZERO,
}
}
#[must_use]
pub fn height_bound(&self) -> R {
self.amplitude.abs() * noise::fbm_bound::<R>(self.octaves, self.gain)
}
}
impl<R: Real> Default for FbmTerrain<R> {
fn default() -> Self {
Self::canonical()
}
}
impl<R: Real> Sdf for FbmTerrain<R> {
type Scalar = R;
#[inline]
fn sample(&self, p: [R; 3]) -> R {
let (n, _) = noise::fbm(
[p[0], R::ZERO, p[2]],
self.seed,
self.octaves,
self.lacunarity,
self.gain,
self.frequency,
);
p[1] - (self.base_height + self.amplitude * n)
}
#[inline]
fn gradient(&self, p: [R; 3]) -> [R; 3] {
let (_, g) = noise::fbm(
[p[0], R::ZERO, p[2]],
self.seed,
self.octaves,
self.lacunarity,
self.gain,
self.frequency,
);
[-self.amplitude * g[0], R::ONE, -self.amplitude * g[2]]
}
}
impl<R: Real> ReferenceField for FbmTerrain<R> {
const NAME: &'static str = "fbm_terrain";
fn domain(&self) -> ([R; 3], [R; 3]) {
cube_domain(8.0)
}
fn closed_in_domain(&self) -> bool {
false }
fn expected_euler(&self) -> Option<i64> {
None }
fn bound(&self) -> FieldBound {
FieldBound::Unbounded
}
}
#[cfg(test)]
mod tests;
impl<R: Real> BoundedSdf for Sphere<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Exact
}
}
impl<R: Real> BoundedSdf for Torus<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Exact
}
}
impl<R: Real> BoundedSdf for BoxExact<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Exact
}
}
impl<R: Real> BoundedSdf for ThinPlate<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Exact
}
}
impl<R: Real> BoundedSdf for Gyroid<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Lipschitz {
l: 3.464_101_615_137_754_6 * self.scale.as_f64().abs(),
}
}
}
impl<A: BoundedSdf, B: BoundedSdf<Scalar = A::Scalar>> BoundedSdf for Union<A, B> {
fn value_bound(&self) -> FieldBound {
composed_bound(self.a.value_bound(), self.b.value_bound())
}
}
impl<A: BoundedSdf, B: BoundedSdf<Scalar = A::Scalar>> BoundedSdf for Intersection<A, B> {
fn value_bound(&self) -> FieldBound {
composed_bound(self.a.value_bound(), self.b.value_bound())
}
}
impl<A: BoundedSdf, B: BoundedSdf<Scalar = A::Scalar>> BoundedSdf for Difference<A, B> {
fn value_bound(&self) -> FieldBound {
composed_bound(self.a.value_bound(), self.b.value_bound())
}
}
impl<A: BoundedSdf, B: BoundedSdf<Scalar = A::Scalar>> BoundedSdf for SmoothUnion<A, B, A::Scalar> {
fn value_bound(&self) -> FieldBound {
composed_bound(self.a.value_bound(), self.b.value_bound())
}
}
impl<R: Real> BoundedSdf for NoiseVolume<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Unbounded
}
}
impl<R: Real> BoundedSdf for FbmTerrain<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Unbounded
}
}
impl<R: Real> BoundedSdf for crate::brush::Capsule<R> {
fn value_bound(&self) -> FieldBound {
FieldBound::Exact
}
}