use glam::{Vec2, Vec3};
#[inline(always)]
fn step_box(lx: f32, ly: f32, s: f32, sw: f32, sh: f32) -> f32 {
let cx = s.mul_add(sw, sw * 0.5);
let hy = (s + 1.0) * sh * 0.5;
let dx = sw.mul_add(-0.5, (lx - cx).abs());
let dy = (ly - hy).abs() - hy;
Vec2::new(dx.max(0.0), dy.max(0.0)).length() + dx.max(dy).min(0.0)
}
#[inline(always)]
pub fn sdf_stairs(
p: Vec3,
step_width: f32,
step_height: f32,
n_steps: f32,
half_depth: f32,
) -> f32 {
let sw = step_width;
let sh = step_height;
let n = n_steps.max(1.0);
let tw = n * sw;
let th = n * sh;
let lx = p.x + tw * 0.5;
let ly = p.y + th * 0.5;
let si = (lx / sw).floor().clamp(0.0, n - 1.0);
let sj = ((ly / sh).ceil() - 1.0).clamp(0.0, n - 1.0);
let mut d_2d = step_box(lx, ly, si, sw, sh);
if si > 0.0 {
d_2d = d_2d.min(step_box(lx, ly, si - 1.0, sw, sh));
}
if si < n - 1.0 {
d_2d = d_2d.min(step_box(lx, ly, si + 1.0, sw, sh));
}
if sj != si && sj != si - 1.0 && sj != si + 1.0 {
d_2d = d_2d.min(step_box(lx, ly, sj, sw, sh));
}
let d_z = p.z.abs() - half_depth;
let w = Vec2::new(d_2d.max(0.0), d_z.max(0.0));
d_2d.max(d_z).min(0.0) + w.length()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stairs_center_inside() {
let d = sdf_stairs(Vec3::new(0.1, -0.3, 0.0), 0.5, 0.5, 4.0, 0.5);
assert!(d < 0.0, "Point inside step should be negative, got {}", d);
}
#[test]
fn test_stairs_far_outside() {
let d = sdf_stairs(Vec3::new(5.0, 0.0, 0.0), 0.5, 0.5, 4.0, 0.5);
assert!(d > 0.0, "Far point should be outside, got {}", d);
}
#[test]
fn test_stairs_symmetry_z() {
let d1 = sdf_stairs(Vec3::new(0.1, 0.1, 0.2), 0.5, 0.5, 4.0, 0.5);
let d2 = sdf_stairs(Vec3::new(0.1, 0.1, -0.2), 0.5, 0.5, 4.0, 0.5);
assert!((d1 - d2).abs() < 0.001, "Should be symmetric in Z");
}
#[test]
fn test_stairs_step_shape() {
let d = sdf_stairs(Vec3::new(-0.8, -0.8, 0.0), 0.5, 0.5, 4.0, 0.5);
assert!(d < 0.0, "Point on first step should be inside, got {}", d);
}
}