use std::f32::consts::{FRAC_1_SQRT_2, SQRT_2};
#[inline(always)]
fn glsl_mod(a: f32, b: f32) -> f32 {
b.mul_add(-(a / b).floor(), a)
}
#[inline(always)]
pub fn stairs_min(a: f32, b: f32, r: f32, n: f32) -> f32 {
let n = n.max(1.0);
let d = a.min(b);
let mut px = (a + b) * FRAC_1_SQRT_2;
let mut py = (b - a) * FRAC_1_SQRT_2;
std::mem::swap(&mut px, &mut py);
let rn = r / n;
let off = (r - rn) * 0.5 * SQRT_2;
px -= off;
py -= off;
px += 0.5 * SQRT_2 * rn;
let step = r * SQRT_2 / n;
let hs = step * 0.5;
px = glsl_mod(px + hs, step) - hs;
let d = d.min(py);
let npx = (px + py) * FRAC_1_SQRT_2;
let npy = (py - px) * FRAC_1_SQRT_2;
let edge = 0.5 * rn;
d.min((npx - edge).max(npy - edge))
}
#[inline(always)]
pub fn stairs_max(a: f32, b: f32, r: f32, n: f32) -> f32 {
-stairs_min(-a, -b, r, n)
}
#[inline(always)]
pub fn sdf_stairs_union(d1: f32, d2: f32, r: f32, n: f32) -> f32 {
stairs_min(d1, d2, r, n)
}
#[inline(always)]
pub fn sdf_stairs_intersection(d1: f32, d2: f32, r: f32, n: f32) -> f32 {
stairs_max(d1, d2, r, n)
}
#[inline(always)]
pub fn sdf_stairs_subtraction(d1: f32, d2: f32, r: f32, n: f32) -> f32 {
-stairs_min(-d1, d2, r, n)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stairs_union_basic() {
let a = 5.0;
let b = 10.0;
let r = 0.5;
let n = 4.0;
let result = sdf_stairs_union(a, b, r, n);
assert!(
result <= a + 0.01,
"stairs union should approximate min when far apart"
);
}
#[test]
fn test_stairs_union_creates_steps() {
let r = 1.0;
let n = 4.0;
let a = 0.1;
let b = 0.2;
let stairs = sdf_stairs_union(a, b, r, n);
let hard = a.min(b);
assert!(stairs <= hard + 0.01);
}
#[test]
fn test_stairs_intersection_subtraction_duality() {
let a = 0.5;
let b = 0.3;
let r = 0.4;
let n = 3.0;
let int_result = sdf_stairs_intersection(a, b, r, n);
let _sub_result = sdf_stairs_subtraction(a, b, r, n);
let int_alt = -sdf_stairs_union(-a, -b, r, n);
assert!(
(int_result - int_alt).abs() < 1e-6,
"intersection = -union(-a,-b)"
);
}
}