space_filling/geometry/
shapes.rs

1#![allow(non_upper_case_globals)]
2use {
3  super::{Shape, BoundingBox, WorldSpace, Translation},
4  crate::sdf::{SDF, Union},
5  euclid::{Box2D, Point2D, Vector2D as V2},
6  num_traits::{Float, Signed, FloatConst},
7  std::marker::PhantomData
8};
9
10fn clamp<T: Float>(mut x: T, min: T, max: T) -> T {
11  if x < min { x = min; }
12  if x > max { x = max; }
13  x
14}
15
16/// Unit circle
17#[derive(Debug, Copy, Clone)]
18pub struct Circle;
19
20impl<T: Float> BoundingBox<T> for Circle {
21  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
22    Box2D::new(
23      Point2D::splat(-T::one()),
24      Point2D::splat(T::one())
25    )}}
26
27impl <T: Float> SDF<T> for Circle {
28  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
29    pixel.to_vector().length() - T::one()
30  }
31}
32
33/// Rectangle with center at `[0, 0]`
34#[derive(Debug, Copy, Clone)]
35pub struct Rect<T, S> {
36  pub size: Point2D<T, S>
37}
38
39impl<T: Float> BoundingBox<T> for Rect<T, WorldSpace> {
40  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
41    let two = T::one() + T::one();
42    Box2D::new(
43      -self.size / two,
44      self.size / two
45    )}}
46
47impl<T> SDF<T> for Rect<T, WorldSpace>
48  where T: Float + Signed
49{
50  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
51    let two = T::one() + T::one();
52    let dist = pixel.to_vector().abs() - (self.size.to_vector() / two);
53    let outside_dist = dist
54      .max(V2::splat(T::zero()))
55      .length();
56    let inside_dist = dist.x
57      .max(dist.y)
58      .min(T::zero());
59    outside_dist + inside_dist
60  }}
61
62#[derive(Debug, Copy, Clone)]
63pub struct Line<T> {
64  pub a: Point2D<T, WorldSpace>,
65  pub b: Point2D<T, WorldSpace>,
66  pub thickness: T,
67}
68
69impl<T: Float> BoundingBox<T> for Line<T> {
70  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
71    let two = T::one() + T::one();
72    let ret = Box2D::from_points([self.a, self.b]);
73    let t = V2::splat(self.thickness / two);
74    Box2D::new(ret.min - t, ret.max + t)
75  }}
76
77impl<T: Float> SDF<T> for Line<T> {
78  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
79    let ba = self.b - self.a;
80    let pa = pixel - self.a;
81    let h = clamp(pa.dot(ba) / ba.dot(ba), T::zero(), T::one());
82    (pa - ba * h).length() - self.thickness / (T::one() + T::one())
83  }
84}
85
86/// Regular polygon with N sides, inscribed in a unit circle. Partially evaluated at compile-time.
87#[derive(Debug, Copy, Clone)]
88pub struct NGonC<const N: usize>;
89
90impl<T: Float, const N: usize> BoundingBox<T> for NGonC<N> {
91  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
92    Box2D::new(
93      Point2D::splat(-T::one()),
94      Point2D::splat(T::one())
95    )}}
96
97impl<T: Float + FloatConst, const N: usize> SDF<T> for NGonC<N> {
98  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
99    let p5 = T::one() / (T::one() + T::one());
100    let n = T::from(N).unwrap();
101    let angle = pixel.y.atan2(pixel.x) + T::FRAC_PI_2();
102    let split = T::TAU() / n;
103    let r = (T::PI() / n).cos();
104    pixel.to_vector().length() * (split * (angle / split + p5).floor() - angle).cos() - r
105  }
106}
107
108/// Regular polygon with N sides, inscribed in a unit circle. Evaluated at runtime.
109#[derive(Debug, Copy, Clone)]
110pub struct NGonR {
111  pub n: u64
112}
113
114impl<T: Float> BoundingBox<T> for NGonR {
115  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
116    Box2D::new(
117      Point2D::splat(-T::one()),
118      Point2D::splat(T::one())
119    )}}
120
121impl<T: Float + FloatConst> SDF<T> for NGonR {
122  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
123    let p5 = T::one() / (T::one() + T::one());
124    let n = T::from(self.n).unwrap();
125    let angle = pixel.y.atan2(pixel.x) + T::FRAC_PI_2();
126    let split = T::TAU() / n;
127    let r = (T::PI() / n).cos();
128    pixel.to_vector().length() * (split * (angle / split + p5).floor() - angle).cos() - r
129  }
130}
131
132/// N-pointed regular star polygon, inscibed in a unit circle.
133/// `m` is density, must be between `2..=n`
134#[derive(Debug, Copy, Clone)]
135pub struct Star<T> {
136  pub n: u64,
137  pub m: T
138}
139
140impl<T: Float> BoundingBox<T> for Star<T> {
141  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
142    Box2D::new(
143      Point2D::splat(-T::one()),
144      Point2D::splat(T::one())
145    )}}
146
147impl<T: Float + FloatConst> SDF<T> for Star<T> {
148  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
149    let module = |x: T, y: T| x - y * (x / y).floor();
150    let n = T::from(self.n).unwrap();
151    let an = T::PI() / n;
152    let en = T::PI()  / self.m;
153    let acs = V2::<_, WorldSpace>::new(an.cos(), an.sin());
154    let ecs = V2::new(en.cos(), en.sin());
155
156    let bn = module(pixel.x.atan2(pixel.y), (T::one() + T::one()) * an) - an;
157    let mut p = V2::new(bn.cos(), bn.sin().abs())
158      * pixel.to_vector().length()
159      - acs;
160    p += ecs * clamp(-p.dot(ecs), T::zero(), acs.y / ecs.y);
161    p.length() * p.x.signum()
162  }
163}
164
165/// `phase` in `-1..=1`.
166#[derive(Debug, Copy, Clone)]
167pub struct Moon<T> {
168  pub phase: T
169}
170
171impl<T: Float> BoundingBox<T> for Moon<T> {
172  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
173    Box2D::new(
174      Point2D::splat(-T::one()),
175      Point2D::splat(T::one())
176    )}}
177
178impl<T: Float> SDF<T> for Moon<T> {
179  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
180    let two = T::one() + T::one();
181    let pixel = V2::<_, WorldSpace>::new(pixel.x, pixel.y.abs());
182    let d = self.phase * two;
183    let a = (d * d) / (two * d);
184    let b = (T::one() - a * a).max(T::zero()).sqrt();
185
186    if d * (pixel.x * b - pixel.y * a) > d * d * (b - pixel.y).max(T::zero()) {
187      (pixel - V2::new(a, b)).length()
188    } else {
189      (pixel.length() - T::one()).max(
190        -((pixel - V2::new(d, T::zero())).length() - T::one())
191      )
192    }
193  }
194}
195
196#[derive(Debug, Copy, Clone)]
197pub struct Kakera<T> {
198  pub width: T
199}
200
201impl<T: Float> BoundingBox<T> for Kakera<T> {
202  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
203    Box2D::new(
204      Point2D::new(-self.width, -T::one()),
205      Point2D::new(self.width, T::one())
206    )}}
207
208impl<T: Float + Signed> SDF<T> for Kakera<T> {
209  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
210    let two = T::one() + T::one();
211    let ndot = |a: V2<_, _,>, b: V2<_, _,>| a.x*b.x - a.y*b.y;
212    let b = V2::new(self.width, T::one());
213    let q = pixel.to_vector().abs();
214    let mut h = (-two * ndot(q, b) + ndot(b, b)) / b.dot(b);
215    h = clamp(h, -T::one(), T::one());
216    let d = (q - V2::new(T::one() - h, T::one() + h).component_mul(b) / two).length();
217    d * (q.x * b.y + q.y * b.x - b.x * b.y).signum()
218  }
219}
220
221#[derive(Debug, Copy, Clone)]
222pub struct Cross<T> {
223  pub thickness: T
224}
225
226impl<T: Float> BoundingBox<T> for Cross<T> {
227  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
228    Box2D::new(
229      Point2D::splat(-T::one()),
230      Point2D::splat(T::one())
231    )}}
232
233impl<T: Float + Signed> SDF<T> for Cross<T>  {
234  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
235    let mut pixel = pixel.to_vector().abs();
236    pixel = if pixel.y > pixel.x { pixel.yx() } else { pixel };
237    let q = pixel - V2::new(T::one(), self.thickness);
238    let k = q.x.max(q.y);
239    let w = if k > T::zero() { q } else { V2::new(self.thickness - pixel.x, -k) };
240    k.signum() * w.max(V2::splat(T::zero())).length()
241  }
242}
243
244#[derive(Debug, Copy, Clone)]
245pub struct Ring<T> {
246  pub inner_r: T
247}
248
249impl<T: Float> BoundingBox<T> for Ring<T> {
250  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
251    Box2D::new(
252      Point2D::splat(-T::one()),
253      Point2D::splat(T::one())
254    )}}
255
256impl<T: Float> SDF<T> for Ring<T>  {
257  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
258    Shape::<T>::subtraction(Circle,Circle.scale(self.inner_r))
259      .sdf(pixel)
260  }
261}
262
263#[derive(Debug, Copy, Clone)]
264pub struct Polygon<T> {
265  pub vertices: T
266}
267
268impl<T, U> BoundingBox<T> for Polygon<U>
269  where T: Float,
270        U: AsRef<[Point2D<T, WorldSpace>]> {
271  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
272    Box2D::from_points(self.vertices.as_ref())
273  }}
274
275impl<T, U> SDF<T> for Polygon<U>
276  where T: Float,
277        U: AsRef<[Point2D<T, WorldSpace>]> {
278  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
279    let v = self.vertices.as_ref();
280    let mut d = match v.get(0) {
281      Some(&v) => (pixel - v).dot(pixel - v),
282      None => return T::max_value() / (T::one() + T::one())
283    };
284    let mut s = T::one();
285    let n = v.len();
286    (0..n).zip(std::iter::once(n - 1).chain(0..n - 1))
287      .for_each(|(i, j)| {
288        let e = v[j] - v[i];
289        let w = pixel - v[i];
290        let b = w - e * clamp(w.dot(e) / e.dot(e), T::zero(), T::one());
291        d = d.min(b.dot(b));
292        let c = euclid::BoolVector3D {
293          x: pixel.y >= v[i].y,
294          y: pixel.y < v[j].y,
295          z: e.x * w.y > e.y * w.x
296        };
297        if c.all() || c.none() {
298          s = s.neg();
299        }
300      });
301    s * d.sqrt()
302  }
303}
304
305/// `= Rect { size: [2.0, 2.0] }`
306#[derive(Debug, Copy, Clone)]
307pub struct Square;
308
309impl<T: Float> BoundingBox<T> for Square {
310  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
311    let two = T::one() + T::one();
312    Rect { size: Point2D::splat(two) }.bounding_box()}}
313
314impl<T> SDF<T> for Square
315  where T: Float + Signed {
316  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
317    let two = T::one() + T::one();
318    Rect { size: Point2D::splat(two) }.sdf(pixel)
319  }}
320
321/// `= Star { n: 5, m: 10.0 / 3.0 }`
322#[derive(Debug, Copy, Clone)]
323pub struct Pentagram;
324
325impl<T: Float> BoundingBox<T> for Pentagram {
326  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
327    let two = T::one() + T::one();
328    let three = two + T::one();
329    let ten = three * three + T::one();
330    Star { n: 5, m: ten / three }.bounding_box()}}
331
332impl<T> SDF<T> for Pentagram
333  where T: Float + FloatConst {
334  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
335    let two = T::one() + T::one();
336    let three = two + T::one();
337    let ten = three * three + T::one();
338    Star { n: 5, m: ten / three }.sdf(pixel)
339  }}
340
341/// `= Star { n: 6, m: 3.0 }`
342#[derive(Debug, Copy, Clone)]
343pub struct Hexagram;
344
345impl<T: Float> BoundingBox<T> for Hexagram {
346  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
347    let three = T::one() + T::one() + T::one();
348    Star { n: 6, m: three }.bounding_box()}}
349
350impl<T> SDF<T> for Hexagram
351  where T: Float + FloatConst {
352  fn sdf(&self, pixel: Point2D<T, WorldSpace>) -> T {
353    let three = T::one() + T::one() + T::one();
354    Star { n: 6, m: three }.sdf(pixel)
355  }}
356
357/// `= NGonC::<3>`
358pub type Triangle = NGonC<3>;
359
360/// `= NGonC::<5>`
361pub type Pentagon = NGonC<5>;
362
363/// `= NGonC::<6>`
364pub type Hexagon = NGonC<6>;
365
366/// `= NGonC::<7>`
367pub type Heptagon = NGonC<7>;
368
369/// `= NGonC::<8>`
370pub type Octagon = NGonC<8>;
371
372pub static HolyCross: Union <
373  Rect<f64, WorldSpace> ,
374  Translation < Rect<f64, WorldSpace>, f64 >
375> = Union {
376  s1: Rect { size: Point2D { x: 0.4, y: 2.0, _unit: PhantomData::<WorldSpace> } },
377  s2: Translation {
378    shape: Rect { size: Point2D {  x: 1.432, y: 0.4, _unit: PhantomData::<WorldSpace> } },
379    offset: V2 { x: 0.0, y: -0.3, _unit: PhantomData::<WorldSpace> }
380  }
381};