use crate::compiled::real::LawMinMax;
use crate::compiled::real::Real;
#[inline(always)]
pub fn smooth_min(a: f32, b: f32, k: f32) -> f32 {
let k = k.law_max(1e-10);
smooth_min_rk(a, b, k, 1.0 / k)
}
#[inline(always)]
pub fn smooth_min_rk(a: f32, b: f32, k: f32, rk: f32) -> f32 {
smooth_min_rk_r::<f32>(a, b, k, rk)
}
#[inline(always)]
pub fn smooth_max(a: f32, b: f32, k: f32) -> f32 {
let k = k.law_max(1e-10);
smooth_max_rk(a, b, k, 1.0 / k)
}
#[inline(always)]
pub fn smooth_max_rk(a: f32, b: f32, k: f32, rk: f32) -> f32 {
smooth_max_rk_r::<f32>(a, b, k, rk)
}
#[inline(always)]
pub fn sdf_smooth_union(d1: f32, d2: f32, k: f32) -> f32 {
smooth_min(d1, d2, k)
}
#[inline(always)]
pub fn sdf_smooth_intersection(d1: f32, d2: f32, k: f32) -> f32 {
smooth_max(d1, d2, k)
}
#[inline(always)]
pub fn sdf_smooth_subtraction(d1: f32, d2: f32, k: f32) -> f32 {
smooth_max(d1, -d2, k)
}
#[inline(always)]
pub fn sdf_smooth_union_rk(d1: f32, d2: f32, k: f32, rk: f32) -> f32 {
smooth_min_rk(d1, d2, k, rk)
}
#[inline(always)]
pub fn sdf_smooth_intersection_rk(d1: f32, d2: f32, k: f32, rk: f32) -> f32 {
smooth_max_rk(d1, d2, k, rk)
}
#[inline(always)]
pub fn sdf_smooth_subtraction_rk(d1: f32, d2: f32, k: f32, rk: f32) -> f32 {
smooth_max_rk(d1, -d2, k, rk)
}
#[inline(always)]
pub fn smooth_min_exp(a: f32, b: f32, k: f32) -> f32 {
let k = k.law_max(1e-10);
let delta = if a == b { 0.0 } else { (a - b).abs() };
a.law_min(b) - alice_det_math::ln(1.0 + alice_det_math::exp(-k * delta)) / k
}
#[inline(always)]
pub fn sdf_exp_smooth_union(d1: f32, d2: f32, k: f32) -> f32 {
sdf_exp_smooth_union_r::<f32>(d1, d2, k)
}
#[inline(always)]
pub fn sdf_exp_smooth_intersection(d1: f32, d2: f32, k: f32) -> f32 {
sdf_exp_smooth_intersection_r::<f32>(d1, d2, k)
}
#[inline(always)]
pub fn sdf_exp_smooth_subtraction(d1: f32, d2: f32, k: f32) -> f32 {
sdf_exp_smooth_subtraction_r::<f32>(d1, d2, k)
}
#[inline(always)]
pub fn smooth_min_cubic(a: f32, b: f32, k: f32) -> f32 {
let k = k.law_max(1e-10);
let h = (k - (a - b).abs()).law_max(0.0) / k;
(h * h * h * k) * -(1.0 / 6.0) + a.law_min(b)
}
#[inline(always)]
pub fn smooth_min_root(a: f32, b: f32, k: f32) -> f32 {
let x = b - a;
0.5 * (a + b - alice_det_math::hypot(x, k))
}
#[inline(always)]
pub fn smooth_min_rk_r<R: Real>(a: R, b: R, k: f32, rk: f32) -> R {
let h = (R::one() - (a - b).abs() * R::splat(rk)).max(R::zero());
a.min(b) - h * h * R::splat(k * 0.25)
}
#[inline(always)]
pub fn smooth_max_rk_r<R: Real>(a: R, b: R, k: f32, rk: f32) -> R {
let h = (R::one() - (a - b).abs() * R::splat(rk)).max(R::zero());
a.max(b) + h * h * R::splat(k * 0.25)
}
#[inline(always)]
pub fn sdf_smooth_union_rk_r<R: Real>(d1: R, d2: R, k: f32, rk: f32) -> R {
smooth_min_rk_r(d1, d2, k, rk)
}
#[inline(always)]
pub fn sdf_smooth_intersection_rk_r<R: Real>(d1: R, d2: R, k: f32, rk: f32) -> R {
smooth_max_rk_r(d1, d2, k, rk)
}
#[inline(always)]
pub fn sdf_smooth_subtraction_rk_r<R: Real>(d1: R, d2: R, k: f32, rk: f32) -> R {
smooth_max_rk_r(d1, -d2, k, rk)
}
#[inline(always)]
pub fn sdf_exp_smooth_union_r<R: Real>(d1: R, d2: R, k: f32) -> R {
let k = R::splat(k.law_max(1e-6));
let m = d1.min(d2);
let delta = (d1 - d2).abs();
m - (R::one() + (-delta / k).exp()).ln() * k
}
#[inline(always)]
pub fn sdf_exp_smooth_intersection_r<R: Real>(d1: R, d2: R, k: f32) -> R {
let k = R::splat(k.law_max(1e-6));
let m = d1.max(d2);
let delta = (d1 - d2).abs();
m + (R::one() + (-delta / k).exp()).ln() * k
}
#[inline(always)]
pub fn sdf_exp_smooth_subtraction_r<R: Real>(d1: R, d2: R, k: f32) -> R {
sdf_exp_smooth_intersection_r(d1, -d2, k)
}
#[allow(
clippy::disallowed_methods,
reason = "test code: the platform libm and fused mul_add serve as independent references"
)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_smooth_min_basic() {
let a = 1.0;
let b = 3.0;
let k = 0.5;
let result = smooth_min(a, b, k);
assert!(result <= a);
}
#[test]
fn test_smooth_union_symmetry() {
let d1 = 0.5;
let d2 = 0.8;
let k = 0.3;
assert!((sdf_smooth_union(d1, d2, k) - sdf_smooth_union(d2, d1, k)).abs() < 0.0001);
}
#[test]
fn test_smooth_intersection() {
let d1 = -0.5;
let d2 = -0.3;
let k = 0.2;
let result = sdf_smooth_intersection(d1, d2, k);
assert!(result >= d1.max(d2));
}
#[test]
fn test_smooth_min_exp() {
let result = smooth_min_exp(1.0, 1.0, 10.0);
assert!(result < 1.0);
}
#[test]
fn test_smooth_min_cubic() {
let result = smooth_min_cubic(1.0, 1.0, 0.5);
assert!(result < 1.0);
}
#[test]
fn test_smooth_min_root_basic() {
let result = smooth_min_root(1.0, 3.0, 0.5);
assert!(result <= 1.0, "Should be <= min(a,b), got {}", result);
}
#[test]
fn test_smooth_min_root_symmetry() {
let k = 0.3;
let r1 = smooth_min_root(0.5, 0.8, k);
let r2 = smooth_min_root(0.8, 0.5, k);
assert!(
(r1 - r2).abs() < 1e-6,
"Should be symmetric: {} vs {}",
r1,
r2
);
}
#[test]
fn test_smooth_min_root_k_zero() {
let result = smooth_min_root(2.0, 5.0, 0.0);
assert!(
(result - 2.0).abs() < 1e-6,
"k=0 should be min, got {}",
result
);
}
#[test]
fn test_smooth_min_root_equal() {
let result = smooth_min_root(1.0, 1.0, 0.5);
assert!(
result < 1.0,
"Equal inputs with k>0 should blend below, got {}",
result
);
}
#[test]
fn exp_min_forms_are_finite_for_finite_arguments_far_from_the_surface() {
for &(a, b, k) in &[
(100.0_f32, 100.0, 10.0),
(-100.0, -100.0, 10.0),
(100.0, -100.0, 10.0),
] {
let want = a.min(b) - (1.0 + (-k * (a - b).abs()).exp()).ln() / k;
let e = smooth_min_exp(a, b, k);
assert!(
e.is_finite() && (e - want).abs() < 1e-3,
"smooth_min_exp({a}, {b}, {k}) = {e} (want {want})"
);
}
assert_eq!(
smooth_min_exp(f32::INFINITY, f32::INFINITY, 2.0),
f32::INFINITY
);
}
}