renderling/
sdf.rs

1//! SDF functions for use in shaders.
2//!
3//! For more info, see these great articles:
4//! - <https://iquilezles.org/articles/distfunctions2d/>
5use crabslab::SlabItem;
6use glam::Vec2;
7// use spirv_std::spirv;
8
9// #[spirv(vertex)]
10// pub fn vertex_sdf_circle(
11//     #[spirv(instance_index)] circle_id: Id<CircleDescriptor>,
12//     #[spirv(vertex_index)] vertex_index: u32,
13// )
14
15#[derive(Clone, Copy, SlabItem)]
16pub struct CircleDescriptor {
17    pub center: Vec2,
18    pub radius: f32,
19}
20
21impl CircleDescriptor {
22    pub fn distance(&self, point: Vec2) -> f32 {
23        let p = point - self.center;
24        p.length() - self.radius
25    }
26}
27
28#[derive(Clone, Copy, SlabItem)]
29pub struct Box {
30    pub center: Vec2,
31    pub half_extent: Vec2,
32}
33
34impl Box {
35    pub fn distance(&self, point: Vec2) -> f32 {
36        let p = point - self.center;
37        let component_edge_distance = p.abs() - self.half_extent;
38        let outside = component_edge_distance.max(Vec2::ZERO).length();
39        let inside = component_edge_distance
40            .x
41            .max(component_edge_distance.y)
42            .min(0.0);
43        inside + outside
44    }
45}
46
47#[cfg(test)]
48mod test {
49    use super::*;
50
51    #[test]
52    fn sdf_circle_sanity() {
53        let mut img = image::ImageBuffer::<image::Luma<f32>, Vec<f32>>::new(32, 32);
54
55        let circle = CircleDescriptor {
56            center: Vec2::new(12.0, 12.0),
57            radius: 4.0,
58        };
59
60        img.enumerate_pixels_mut().for_each(|(x, y, p)| {
61            let distance = circle.distance(Vec2::new(x as f32 + 0.5, y as f32 + 0.5));
62            p.0[0] = distance / circle.radius;
63        });
64
65        img_diff::assert_img_eq(
66            "sdf/circle_sanity.png",
67            image::DynamicImage::from(img).into_rgb8(),
68        );
69    }
70
71    #[test]
72    fn sdf_box_sanity() {
73        let mut img = image::ImageBuffer::<image::Luma<f32>, Vec<f32>>::new(32, 32);
74
75        let bx = Box {
76            center: Vec2::new(12.0, 12.0),
77            half_extent: Vec2::new(4.0, 6.0),
78        };
79
80        img.enumerate_pixels_mut().for_each(|(x, y, p)| {
81            let distance = bx.distance(Vec2::new(x as f32 + 0.5, y as f32 + 0.5));
82            p.0[0] = distance / bx.half_extent.max_element();
83        });
84
85        img_diff::assert_img_eq(
86            "sdf/box_sanity.png",
87            image::DynamicImage::from(img).into_rgb8(),
88        );
89    }
90}