space_filling/geometry/
mod.rs

1//! .
2//!
3//! The origin of coordinate system is in top-left corner. Most of shapes are represented in the
4//! interval `[-1, 1]`, and center in the origin.
5
6use {
7  std::ops::Add,
8  euclid::{Point2D, Box2D, Vector2D as V2, Rotation2D, Angle},
9  num_traits::Float,
10  crate::sdf::{SDF, Union, Subtraction, Intersection, SmoothMin}
11};
12
13pub mod shapes;
14pub use shapes::*;
15
16/// Pixel coordinate basis
17#[derive(Debug, Copy, Clone)]
18pub struct PixelSpace;
19/// Normalized coordinate basis
20#[derive(Debug, Copy, Clone)]
21pub struct WorldSpace;
22
23pub type P2<P> = Point2D<P, WorldSpace>;
24
25pub trait BoundingBox<T> {
26  fn bounding_box(&self) -> Box2D<T, WorldSpace>;
27}
28
29/// Something inside a rectangular area.
30pub trait Shape<T>: SDF<T> + BoundingBox<T> {
31  fn translate(self, offset: V2<T, WorldSpace>) -> Translation<Self, T> where Self: Sized {
32    Translation { shape: self, offset }
33  }
34  /// Rotate around the center of shape's bounding box
35  fn rotate(self, angle: Angle<T>) -> Rotation<Self, T> where Self: Sized {
36    Rotation { shape: self, angle }
37  }
38  /// Scale around the center of shape's bounding box
39  fn scale(self, scale: T) -> Scale<Self, T> where Self: Sized {
40    Scale { shape: self, scale }
41  }
42  /// Union of two SDFs.
43  fn union<U>(self, other: U) -> Union<Self, U> where Self: Sized {
44    Union { s1: self, s2: other }
45  }
46  /// Subtracion of two SDFs. Note that this operation is *not* commutative,
47  /// i.e. `Subtraction {a, b} =/= Subtraction {b, a}`.
48  fn subtraction<U>(self, other: U) -> Subtraction<Self, U> where Self: Sized {
49    Subtraction { s1: self, s2: other }
50  }
51  /// Intersection of two SDFs.
52  fn intersection<U>(self, other: U) -> Intersection<Self, U> where Self: Sized {
53    Intersection { s1: self, s2: other }
54  }
55  /// Takes the minimum of two SDFs, smoothing between them when they are close.
56  ///
57  /// `k` controls the radius/distance of the smoothing. 32 is a good default value.
58  fn smooth_min<U>(self, other: U, k: T) -> SmoothMin<T, Self, U> where Self: Sized {
59    SmoothMin { s1: self, s2: other, k }
60  }
61  #[cfg(feature = "drawing")]
62  #[cfg_attr(doc, doc(cfg(feature = "drawing")))]
63  fn texture<Tex>(self, texture: Tex) -> crate::drawing::Texture<Self, Tex> where Self: Sized {
64    crate::drawing::Texture { shape: self, texture }
65  }
66}
67impl <T, Sh> Shape<T> for Sh where Sh: SDF<T> + BoundingBox<T> {}
68
69#[derive(Debug, Copy, Clone)]
70pub struct Translation<S, T> {
71  pub shape: S,
72  pub offset: V2<T, WorldSpace>
73}
74impl <S, P> BoundingBox<P> for Translation<S, P>
75  where S: BoundingBox<P>,
76        P: Copy + Add<Output = P> {
77  fn bounding_box(&self) -> Box2D<P, WorldSpace> {
78    self.shape.bounding_box().translate(self.offset)
79  }
80}
81
82/// Rotate around the center of shape's bounding box
83#[derive(Debug, Copy, Clone)]
84pub struct Rotation<S, T> {
85  pub shape: S,
86  pub angle: Angle<T>
87}
88impl <T, S> BoundingBox<T> for Rotation<S, T>
89  where S: BoundingBox<T>,
90        T: Float
91{
92  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
93    let bounding = self.shape.bounding_box();
94    let pivot = bounding.center();
95    let rot = |point: Point2D<_, _>| Rotation2D::new(self.angle)
96      .transform_point( (point - pivot).to_point())
97      + pivot.to_vector();
98    update_bounding_box(bounding, rot)
99  }
100}
101
102/// Scale around the center of shape's bounding box
103#[derive(Debug, Copy, Clone)]
104pub struct Scale<S, T> {
105  pub shape: S,
106  pub scale: T
107}
108impl <T, S> BoundingBox<T> for Scale<S, T>
109  where S: BoundingBox<T>,
110        T: Float
111{
112  fn bounding_box(&self) -> Box2D<T, WorldSpace> {
113    let c = self.shape.bounding_box().center().to_vector();
114    self.shape.bounding_box()
115      .translate(-c)
116      .scale(self.scale, self.scale)
117      .translate(c)
118  }
119}
120
121fn update_bounding_box<T>(
122  bounding: Box2D<T, WorldSpace>,
123  morphism: impl Fn(Point2D<T, WorldSpace>) -> Point2D<T, WorldSpace>
124) -> Box2D<T, WorldSpace>
125  where T: Float
126{
127  let pts = [
128    [bounding.min.x, bounding.min.y],
129    [bounding.max.x, bounding.min.y],
130    [bounding.max.x, bounding.max.y],
131    [bounding.min.x, bounding.max.y],
132  ];
133  let pts = pts.iter().cloned()
134    .map(|p| morphism(p.into()));
135  Box2D::from_points(pts)
136}
137
138#[derive(Copy, Clone, Debug)]
139pub struct DistPoint<D, P, Space> {
140  pub distance: D,
141  pub point: Point2D<P, Space>
142}
143
144impl<F: Float, P: Default, S> Default for DistPoint<F, P, S> {
145  fn default() -> Self {
146    Self {
147      distance: F::max_value() / (F::one() + F::one()),
148      point: Point2D::default()
149    }
150  }
151}
152
153impl<D: PartialEq, P, S> PartialEq for DistPoint<D, P, S> {
154  fn eq(&self, other: &Self) -> bool {
155    self.distance.eq(&other.distance)
156  }
157}
158
159impl<D: PartialOrd, P, S> PartialOrd for DistPoint<D, P, S> {
160  fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
161    self.distance.partial_cmp(&other.distance)
162  }
163}
164
165impl<D: PartialEq, P, S> Eq for DistPoint<D, P, S> {}
166
167impl<P, S> std::cmp::Ord for DistPoint<f32, P, S> {
168  fn cmp(&self, other: &Self) -> std::cmp::Ordering {
169    // waiting for #![feature(total_cmp)]
170    fn total_cmp(left: f32, right: f32) -> std::cmp::Ordering {
171      let mut left = left.to_bits() as i32;
172      let mut right = right.to_bits() as i32;
173      left ^= (((left >> 31) as u32) >> 1) as i32;
174      right ^= (((right >> 31) as u32) >> 1) as i32;
175
176      left.cmp(&right)
177    }
178    total_cmp(self.distance, other.distance)
179  }
180}