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#[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#[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#[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#[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#[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#[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#[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#[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#[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
357pub type Triangle = NGonC<3>;
359
360pub type Pentagon = NGonC<5>;
362
363pub type Hexagon = NGonC<6>;
365
366pub type Heptagon = NGonC<7>;
368
369pub 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};