use crate::{Real, Sdf, vec3};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Capsule<R: Real> {
pub a: [R; 3],
pub b: [R; 3],
pub radius: R,
}
impl<R: Real> Sdf for Capsule<R> {
type Scalar = R;
fn sample(&self, p: [R; 3]) -> R {
let ab = vec3::sub(self.b, self.a);
let ap = vec3::sub(p, self.a);
let denom = vec3::dot(ab, ab);
let t = if denom > R::ZERO {
(vec3::dot(ap, ab) / denom).clamp(R::ZERO, R::ONE)
} else {
R::ZERO
};
vec3::length(vec3::sub(ap, vec3::scale(ab, t))) - self.radius
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BrushOp {
Add,
Subtract,
SmoothAdd {
k: f64,
},
}
impl BrushOp {
#[must_use]
pub fn commutes_with(self, other: Self) -> bool {
matches!(
(self, other),
(Self::Add, Self::Add) | (Self::Subtract, Self::Subtract)
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Brush<S> {
pub shape: S,
pub op: BrushOp,
}
impl<S> Brush<S> {
pub const fn add(shape: S) -> Self {
Self {
shape,
op: BrushOp::Add,
}
}
pub const fn subtract(shape: S) -> Self {
Self {
shape,
op: BrushOp::Subtract,
}
}
pub const fn smooth_add(shape: S, k: f64) -> Self {
Self {
shape,
op: BrushOp::SmoothAdd { k },
}
}
}
#[must_use]
pub fn apply<R: Real>(op: BrushOp, field: R, shape: R) -> R {
match op {
BrushOp::Add => field.min(shape),
BrushOp::Subtract => field.max(-shape),
BrushOp::SmoothAdd { k } => smooth_min(field, shape, R::from_f64(k)),
}
}
#[must_use]
pub fn smooth_min<R: Real>(a: R, b: R, k: R) -> R {
if k <= R::ZERO {
return a.min(b);
}
let h = (R::HALF + R::HALF * (b - a) / k).clamp(R::ZERO, R::ONE);
(b + (a - b) * h) - k * h * (R::ONE - h)
}
#[derive(Clone, Debug)]
pub struct BrushStack<'a, F, S> {
pub base: F,
pub brushes: &'a [Brush<S>],
}
impl<F, S> Sdf for BrushStack<'_, F, S>
where
F: Sdf,
S: Sdf<Scalar = F::Scalar>,
{
type Scalar = F::Scalar;
fn sample(&self, p: [Self::Scalar; 3]) -> Self::Scalar {
let mut value = self.base.sample(p);
for brush in self.brushes {
value = apply(brush.op, value, brush.shape.sample(p));
}
value
}
}
#[cfg(test)]
mod tests;