use glam::Vec3;
#[inline]
pub fn sdf_bezier(pos: Vec3, a: Vec3, b: Vec3, c: Vec3, radius: f32) -> f32 {
let ab = b - a;
let ba2c = a - 2.0 * b + c;
let cv = ab * 2.0;
let dv = a - pos;
let ba2c_dot = ba2c.dot(ba2c);
if ba2c_dot < 1e-10 {
let ac = c - a;
let ac_dot = ac.dot(ac);
if ac_dot < 1e-10 {
return (pos - a).length() - radius;
}
let t = ((pos - a).dot(ac) / ac_dot).clamp(0.0, 1.0);
return (pos - a - ac * t).length() - radius;
}
let kk = 1.0 / ba2c_dot;
let kx = kk * ab.dot(ba2c);
let ky = kk * (2.0f32 * ab.dot(ab) + dv.dot(ba2c)) / 3.0;
let kz = kk * dv.dot(ab);
let p2 = ky - kx * kx;
let p3 = p2 * p2 * p2;
let q2 = kx * ((2.0 * kx) * kx + -(3.0 * ky)) + kz;
let h = 4.0f32 * p3 + (q2 * q2);
let res = if h >= 0.0 {
let h_sqrt = h.sqrt();
let x0 = (h_sqrt - q2) * 0.5;
let x1 = (-h_sqrt - q2) * 0.5;
let uv_x = x0.signum() * alice_det_math::cbrt(x0.abs());
let uv_y = x1.signum() * alice_det_math::cbrt(x1.abs());
let t = (uv_x + uv_y - kx).clamp(0.0, 1.0);
(dv + (cv + ba2c * t) * t).length()
} else {
let z = (-p2).sqrt();
let v = alice_det_math::acos(q2 / (p2 * z * 2.0)) / 3.0;
let m = alice_det_math::cos(v);
let n = alice_det_math::sin(v) * 1.732_050_8;
let t0 = ((m + m) * z + -kx).clamp(0.0, 1.0);
let t1 = ((-n - m) * z + -kx).clamp(0.0, 1.0);
let d0 = (dv + (cv + ba2c * t0) * t0).length();
let d1 = (dv + (cv + ba2c * t1) * t1).length();
d0.min(d1)
};
res - radius
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bezier_on_start() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(0.5, 1.0, 0.0);
let c = Vec3::new(1.0, 0.0, 0.0);
let radius = 0.1;
let d = sdf_bezier(a, a, b, c, radius);
assert!((d + radius).abs() < 0.01, "At start: {}", d);
}
#[test]
fn test_bezier_on_end() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(0.5, 1.0, 0.0);
let c = Vec3::new(1.0, 0.0, 0.0);
let radius = 0.1;
let d = sdf_bezier(c, a, b, c, radius);
assert!((d + radius).abs() < 0.01, "At end: {}", d);
}
#[test]
fn test_bezier_far_away() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(0.5, 1.0, 0.0);
let c = Vec3::new(1.0, 0.0, 0.0);
let radius = 0.1;
let d = sdf_bezier(Vec3::new(0.0, 10.0, 0.0), a, b, c, radius);
assert!(d > 5.0, "Far away: {}", d);
}
#[test]
fn test_bezier_straight_line() {
let a = Vec3::new(0.0, 0.0, 0.0);
let b = Vec3::new(0.5, 0.0, 0.0);
let c = Vec3::new(1.0, 0.0, 0.0);
let radius = 0.2;
let d = sdf_bezier(Vec3::new(0.5, 0.5, 0.0), a, b, c, radius);
assert!((d - 0.3).abs() < 0.05, "Perpendicular: {}", d);
}
}