1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! Stairs SDF (Deep Fried Edition)
//!
//! Staircase profile in XY plane, extruded along Z-axis.
//! The staircase is a union of progressively taller rectangular steps.
//!
//! Author: Moroya Sakamoto
use glam::{Vec2, Vec3};
/// SDF of a single step box in local coordinates
#[inline(always)]
fn step_box(lx: f32, ly: f32, s: f32, sw: f32, sh: f32) -> f32 {
let cx = s * sw + (sw * 0.5);
let hy = (s + 1.0) * sh * 0.5;
let dx = sw * -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)
}
/// SDF for a staircase shape, extruded along Z-axis
///
/// The staircase has n_steps, each step_width wide and step_height tall.
/// Step i has height (i+1)*step_height. Centered at origin.
/// - `step_width`: width of each step
/// - `step_height`: height of each step
/// - `n_steps`: number of steps (as f32)
/// - `half_depth`: half the extrusion depth along Z
#[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;
// Center coordinates: local x in [0, tw], y in [0, th]
let lx = p.x + tw * 0.5;
let ly = p.y + th * 0.5;
// Union of the n step boxes: the exact exterior distance is the minimum
// over every box. The pre-1.11.0 candidate set {si − 1, si, si + 1, sj}
// missed the nearest box for points above or beside the staircase and
// jumped where the candidates changed (Lipschitz property test).
let mut d_2d = f32::MAX;
for si in 0..n as u32 {
d_2d = d_2d.min(step_box(lx, ly, si as f32, sw, sh));
}
// Extrude along Z
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() {
// Point clearly inside a step (offset from step edges)
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() {
// Point on the first step (low-left) should be inside
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);
}
}