space_filling/
sdf.rs

1use {
2  euclid::{Point2D, Vector2D as V2, Rotation2D, Box2D},
3  crate::{
4    geometry::{self, WorldSpace, Shape, Rotation, Scale, Translation, BoundingBox},
5  },
6  num_traits::{Float, Signed},
7  std::ops::{Neg, Sub}
8};
9
10/// Signed distance function
11pub trait SDF<T> {
12  fn sdf(&self, p: Point2D<T, WorldSpace>) -> T;
13}
14
15impl <S, P: Float> SDF<P> for Translation<S, P>
16  where S: Shape<P>,
17        P: Clone + Sub<Output = P>  {
18  fn sdf(&self, pixel: Point2D<P, WorldSpace>) -> P {
19    self.shape.sdf(pixel - self.offset)
20  }
21}
22
23impl <S, P> SDF<P> for Rotation<S, P>
24  where S: Shape<P>,
25        P: Float {
26  fn sdf(&self, pixel: Point2D<P, WorldSpace>) -> P {
27    let pivot = self.shape.bounding_box().center();
28    let pixel = Rotation2D::new(self.angle)
29      .transform_point( (pixel - pivot).to_point())
30      + pivot.to_vector();
31
32    self.shape.sdf(pixel)
33  }
34}
35
36impl <S, P> SDF<P> for Scale<S, P>
37  where S: Shape<P>,
38        P: Float {
39  fn sdf(&self, pixel: Point2D<P, WorldSpace>) -> P {
40    let c = self.shape.bounding_box().center();
41    let pixel = ((pixel - c) / self.scale + c.to_vector())
42      .to_point();
43    self.shape.sdf(pixel) * self.scale
44  }
45}
46
47/// Distance to the edges of image.
48pub fn boundary_rect<T: Float + Signed>(pixel: Point2D<T, WorldSpace>) -> T {
49  let p5 = T::one() / (T::one() + T::one());
50  -geometry::Rect { size: Point2D::splat(T::one()) }
51    .translate(V2::splat(p5))
52    .sdf(pixel)
53}
54
55/// Union of two SDFs.
56#[derive(Clone, Copy, Debug)]
57pub struct Union<S1, S2> {
58  pub s1: S1,
59  pub s2: S2,
60}
61
62impl<T, S1, S2> SDF<T> for Union<S1, S2>
63  where T: Float,
64        S1: SDF<T>,
65        S2: SDF<T> {
66  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
67    self.s1.sdf(pixel).min(self.s2.sdf(pixel))
68  }}
69
70impl<T, S1, S2> BoundingBox<T> for Union<S1, S2>
71  where T: Copy + PartialOrd,
72        S1: BoundingBox<T>,
73        S2: BoundingBox<T> {
74  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
75    self.s1.bounding_box().union(&self.s2.bounding_box())
76  }}
77
78/// Subtracion of two SDFs. Note that this operation is *not* commutative,
79/// i.e. `Subtraction {a, b} =/= Subtraction {b, a}`.
80#[derive(Clone, Copy, Debug)]
81pub struct Subtraction<S1, S2> {
82  pub s1: S1,
83  pub s2: S2,
84}
85
86impl<T, S1, S2> SDF<T> for Subtraction<S1, S2>
87  where T: Float,
88    S1: SDF<T>,
89    S2: SDF<T> {
90  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
91    (-self.s2.sdf(pixel)).max(self.s1.sdf(pixel))
92  }}
93
94impl<T, S1, S2> BoundingBox<T> for Subtraction<S1, S2>
95  where T: Copy + PartialOrd,
96    S1: BoundingBox<T>,
97    S2: BoundingBox<T> {
98  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
99    self.s1.bounding_box().union(&self.s2.bounding_box())
100  }}
101
102/// Intersection of two SDFs.
103#[derive(Clone, Copy, Debug)]
104pub struct Intersection<S1, S2> {
105  pub s1: S1,
106  pub s2: S2,
107}
108
109impl<T, S1, S2> SDF<T> for Intersection<S1, S2>
110  where T: Float,
111        S1: SDF<T>,
112        S2: SDF<T> {
113  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
114    self.s1.sdf(pixel).max(self.s2.sdf(pixel))
115  }}
116
117impl<T, S1, S2> BoundingBox<T> for Intersection<S1, S2>
118  where T: Copy + PartialOrd + num_traits::One + Neg<Output = T>,
119        S1: BoundingBox<T>,
120        S2: BoundingBox<T> {
121  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
122    self.s1.bounding_box()
123      .intersection(&self.s2.bounding_box())
124      .unwrap_or(Box2D {
125        min: Point2D::splat(-T::one()),
126        max: Point2D::splat(-T::one())
127      })
128  }}
129
130/// Takes the minimum of two SDFs, smoothing between them when they are close.
131///
132/// `k` controls the radius/distance of the smoothing. 32 is a good default value.
133#[derive(Clone, Copy, Debug)]
134pub struct SmoothMin<T, S1, S2> {
135  pub s1: S1,
136  pub s2: S2,
137  pub k: T
138}
139
140impl<T, S1, S2> SDF<T> for SmoothMin<T, S1, S2>
141  where T: Float,
142        S1: SDF<T>,
143        S2: SDF<T> {
144  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
145    let (s1, s2) = (self.s1.sdf(pixel), self.s2.sdf(pixel));
146    let res = (-self.k * s1).exp2() + (-self.k * s2).exp2();
147    -res.log2() / self.k
148  }}
149
150impl<T, S1, S2> BoundingBox<T> for SmoothMin<T, S1, S2>
151  where T: Copy + PartialOrd,
152        S1: BoundingBox<T>,
153        S2: BoundingBox<T> {
154  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
155    self.s1.bounding_box().union(&self.s2.bounding_box())
156  }}