rusty_systems/
geometry.rs

1//! This provides very basic support for 2D geometry.
2//!
3//! Since there is a large body of 2D l-system examples, this
4//! module makes it much easier to implement interpretations
5//! for these examples and see your output.
6//!
7//! 
8//! <div class="warning">
9//! 
10//! Note that this is not meant to be a *complete* or *performant* implementation
11//! of 2D geometry and transformations. It does not even have an implementation of
12//! a transformation matrix — if you need transformation matrices, your needs have moved
13//! beyond the ability of this crate, and you should look elsewhere. More full-featured
14//! alternatives include [nalgebra][nalgebra], and if you specifically want
15//! a computer graphics related package focusing on 2D and 3D operations using
16//! homogenous coordinates, consider their [nalgebra-glm][nalgebra-glm] crate.
17//! 
18//! </div>
19//!
20//! [nalgebra]: https://nalgebra.org/
21//! [nalgebra-glm]: https://nalgebra.org/docs/user_guide/nalgebra_glm
22
23use std::fmt::{Display, Formatter};
24use std::iter::FromIterator;
25use std::ops::{Add, Div, Index, Neg, Sub};
26use std::slice::Iter;
27use std::vec::IntoIter;
28
29/// Represents an immutable point in 2-space.
30#[derive(Debug, Copy, Clone, PartialEq)]
31pub struct Point {
32    x: f64,
33    y: f64
34}
35
36impl Point {
37    #[inline]
38    pub fn new(x: f64, y: f64) -> Self {
39        Point { x, y }
40    }
41    
42    #[inline]
43    pub fn zero() -> Self {
44        Point { x: 0.0, y: 0.0 }
45    }
46
47    #[inline]
48    pub fn x(&self) -> f64 {
49        self.x
50    }
51
52    #[inline]
53    pub fn y(&self) -> f64 {
54        self.y
55    }
56}
57
58
59/// An immutable vector in 2-space. This represents *size* and *direction*.
60/// See [`Point`]
61#[derive(Debug, Copy, Clone, PartialEq)]
62pub struct Vector {
63    x: f64,
64    y: f64
65}
66
67impl Vector {
68    #[inline]
69    pub fn new(x: f64, y: f64) -> Self {
70        Vector { x, y }
71    }
72
73    #[inline]
74    pub fn up() -> Self {
75        Vector::new(0.0, 1.0)
76    }
77
78    #[inline]
79    pub fn down() -> Self {
80        Vector::new(0.0, -1.0)
81    }
82
83    #[inline]
84    pub fn zero() -> Self {
85        Vector::new(0.0, 0.0)
86    }
87
88    pub fn norm(&self) -> f64 {
89        (self.x.powi(2) + self.y.powi(2)).sqrt()
90    }
91
92    #[inline]
93    pub fn x(&self) -> f64 {
94        self.x
95    }
96
97    #[inline]
98    pub fn y(&self) -> f64 {
99        self.y
100    }
101
102    /// Returns a vector rotated by the given degrees.
103    ///
104    /// Here is an example of how to make use of rotations. Note
105    /// that rust has built in support for converting between degrees and radians.
106    /// See [`f64::to_radians`] and [`f64::to_degrees`].
107    ///
108    /// ```
109    /// use rusty_systems::geometry::Vector;
110    /// let up = Vector::up();
111    /// let left = up.rotate(90.0);
112    ///
113    /// assert!((up - Vector::new(0.0, 1.0)).norm() < 0.001);   // The up vector is (0, 1)
114    /// assert!((left - Vector::new(-1.0, 0.0)).norm() < 0.001) // rotated by 90º, it points (-1, 0)
115    /// ```
116    pub fn rotate(&self, degrees: f64) -> Self {
117        let cos = degrees.to_radians().cos();
118        let sin = degrees.to_radians().sin();
119
120        Vector::new(
121            cos * self.x() - sin * self.y(),
122            sin * self.x() + cos * self.y()
123        )
124    }
125}
126
127impl Default for Vector {
128    #[inline]
129    fn default() -> Self {
130        Vector::zero()
131    }
132}
133
134impl Default for Point {
135    #[inline]
136    fn default() -> Self {
137        Point::zero()
138    }
139}
140
141impl From<Point> for Vector {
142    #[inline]
143    fn from(value: Point) -> Self {
144        Vector::new(value.x, value.y)
145    }
146}
147
148impl From<Vector> for Point {
149    #[inline]
150    fn from(value: Vector) -> Self {
151        Point::new(value.x, value.y)
152    }
153}
154
155
156impl Add for Vector {
157    type Output = Vector;
158
159    fn add(self, rhs: Self) -> Self::Output {
160        Vector::new(self.x() + rhs.x(), self.y() + rhs.y())
161    }
162}
163
164impl Sub for Vector {
165    type Output = Vector;
166
167    fn sub(self, rhs: Self) -> Self::Output {
168        Vector::new(self.x() - rhs.x(), self.y() - rhs.y())
169    }
170}
171
172impl Neg for Vector {
173    type Output = Vector;
174
175    fn neg(self) -> Self::Output {
176        Vector::new(-self.x(), -self.y())
177    }
178}
179
180impl Add<Vector> for Point {
181    type Output = Point;
182
183    fn add(self, rhs: Vector) -> Self::Output {
184        Point::new(self.x + rhs.x, self.y + rhs.y)
185    }
186}
187
188impl Add for Point {
189    type Output = Point;
190
191    fn add(self, rhs: Self) -> Self::Output {
192        Point::new(self.x + rhs.x, self.y + rhs.y)
193    }
194}
195
196impl Sub<Vector> for Point {
197    type Output = Point;
198
199    fn sub(self, rhs: Vector) -> Self::Output {
200        Point::new(self.x - rhs.x, self.y - rhs.y)
201    }
202}
203
204impl Sub for Point {
205    type Output = Point;
206
207    fn sub(self, rhs: Self) -> Self::Output {
208        Point::new(self.x - rhs.x, self.y - rhs.y)
209    }
210}
211
212impl Div<f64> for Point {
213    type Output = Point;
214
215    fn div(self, rhs: f64) -> Self::Output {
216        Point::new(self.x() / rhs, self.y() / rhs)
217    }
218}
219
220impl Neg for Point {
221    type Output = Point;
222
223    fn neg(self) -> Self::Output {
224        Point::new(-self.x, -self.y)
225    }
226}
227
228impl Display for Point {
229    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
230        write!(f, "({},{})", self.x, self.y)
231    }
232}
233
234impl Display for Vector {
235    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
236        write!(f, "({},{})", self.x, self.y)
237    }
238}
239
240/// A path is a sequence of points. These can represent
241/// a line.
242#[derive(Debug, Clone)]
243pub struct Path {
244    points: Vec<Point>
245}
246
247impl Path {
248    #[inline]
249    pub fn new() -> Self {
250        Path { points: Vec::new() }
251    }
252
253    #[inline]
254    pub fn len(&self) -> usize {
255        self.points.len()
256    }
257
258    #[inline]
259    pub fn is_empty(&self) -> bool {
260        self.points.is_empty()
261    }
262
263    #[inline]
264    pub fn push<T: Into<Point>>(&mut self, point: T) {
265        self.points.push(point.into())
266    }
267
268    #[inline]
269    pub fn get(&self, index: usize) -> Option<&Point> {
270        self.points.get(index)
271    }
272
273    #[inline]
274    pub fn iter(&self) -> Iter<'_, Point> {
275        self.points.iter()
276    }
277
278    /// Returns the starting point of the path.
279    #[inline]
280    pub fn get_start(&self) -> Option<&Point> {
281        self.points.first()
282    }
283}
284
285impl Index<usize> for Path {
286    type Output = Point;
287
288    fn index(&self, index: usize) -> &Self::Output {
289        &self.points[index]
290    }
291}
292
293impl Default for Path {
294    fn default() -> Self {
295        Path::new()
296    }
297}
298
299impl IntoIterator for Path {
300    type Item = Point;
301    type IntoIter = IntoIter<Point>;
302
303    fn into_iter(self) -> Self::IntoIter {
304        self.points.into_iter()
305    }
306}
307
308/// Create a [`Path`] from a collection of [`Point`] objects.
309impl FromIterator<Point> for Path {
310    fn from_iter<T: IntoIterator<Item=Point>>(iter: T) -> Self {
311        Path { points: iter.into_iter().collect() }
312    }
313}
314
315/// Offset all points in the path by the given vector.
316impl Add<Vector> for Path {
317    type Output = Path;
318
319    fn add(self, rhs: Vector) -> Self::Output {
320        self.into_iter().map(|p| p + rhs).collect()
321    }
322}
323
324/// The bounds of a geometric object, defined as a square. The object is contained in these bounds.
325#[derive(Debug, Clone, Default)]
326pub struct BoundingBox {
327    pub min_x: f64,
328    pub max_x: f64,
329    pub min_y: f64,
330    pub max_y: f64,
331    /// The "center of mass" of object.
332    pub com: Point
333}
334
335impl BoundingBox {
336    #[inline]
337    pub fn width(&self) -> f64 {
338        (self.max_x - self.min_x).abs()
339    }
340
341    #[inline]
342    pub fn height(&self) -> f64 {
343        (self.max_y - self.min_y).abs()
344    }
345
346    /// The center of the box.
347    #[inline]
348    pub fn center(&self) -> Point {
349        Point::new((self.max_x + self.min_x) / 2.0, (self.max_y + self.min_y) / 2.0)
350    }
351
352    /// Returns a bounding box set up to have its values updated.
353    fn initial_infinite() -> Self {
354        BoundingBox {
355            min_x: f64::INFINITY,
356            max_x: f64::NEG_INFINITY,
357            min_y: f64::INFINITY,
358            max_y: f64::NEG_INFINITY,
359            ..Default::default()
360        }
361    }
362
363    /// Returns a bounding box that only contains 0.
364    ///
365    /// This is a synonym for [`BoundingBox::default`].
366    pub fn zero() -> Self {
367        Self::default()
368    }
369}
370
371/// Represents an item that potentially has bounds.
372///
373/// Note that empty collections, such as a [`Path`] that
374/// contains no points, will return [`None`].
375pub trait Bounds {
376    fn bounds(&self) -> Option<BoundingBox>;
377}
378
379impl Bounds for Path {
380    /// Returns the bounds for the path.
381
382    fn bounds(&self) -> Option<BoundingBox> {
383        let mut bounds = BoundingBox::initial_infinite();
384
385        let mut center = Point::zero();
386
387        for point in &self.points {
388            center = center + *point;
389
390            if point.x < bounds.min_x {
391                bounds.min_x = point.x;
392            }
393            if point.x > bounds.max_x {
394                bounds.max_x = point.x;
395            }
396            if point.y < bounds.min_y {
397                bounds.min_y = point.y;
398            }
399            if point.y > bounds.max_y {
400                bounds.max_y = point.y;
401            }
402        }
403
404        bounds.com = center / self.len() as f64;
405
406        Some(bounds)
407    }
408}
409
410impl Bounds for Vec<Path> {
411    fn bounds(&self) -> Option<BoundingBox> {
412        let mut min_x = f64::INFINITY;
413        let mut max_x = f64::NEG_INFINITY;
414        let mut min_y = f64::INFINITY;
415        let mut max_y = f64::NEG_INFINITY;
416
417        let mut center = Point::zero();
418        let mut len = 0_f64;
419
420        for path in self {
421            len += path.len() as f64;
422
423            for point in &path.points {
424                center = center + *point;
425
426                if point.x < min_x {
427                    min_x = point.x;
428                }
429                if point.x > max_x {
430                    max_x = point.x;
431                }
432                if point.y < min_y {
433                    min_y = point.y;
434                }
435                if point.y > max_y {
436                    max_y = point.y;
437                }
438            }
439        }
440
441        if min_x.is_infinite() {
442            return None
443        }
444
445        Some(BoundingBox {
446            min_x,
447            max_x,
448            min_y,
449            max_y,
450            com: center / len
451        })
452    }
453}
454
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    #[test]
461    fn testing_rotations() {
462        let up = Vector::up();
463        let one = up.rotate(90.0);
464
465        assert_eq!(up, Vector::new(0.0, 1.0));
466
467        assert!((one.x - -1.0).abs() < 0.001);
468        assert!((one.y - 0.0).abs() < 0.001);
469    }
470
471    #[test]
472    fn bounding_box() {
473        assert_eq!(BoundingBox::zero().width(), 0.0);
474        assert_eq!(BoundingBox::zero().height(), 0.0);
475        assert_eq!(BoundingBox { min_y: -12.0, max_y: 100.0, ..Default::default()}.height(), 112.0);
476        assert_eq!(BoundingBox { min_x: -10.0, max_x: 100.0, ..Default::default()}.width(), 110.0);
477
478        let b = BoundingBox { min_x: -22.0, max_x:-2.0, min_y: 100.0, max_y: 101.0, ..BoundingBox::default()};
479        assert_eq!(b.width(), 20.0);
480        assert_eq!(b.height(), 1.0);
481        assert_eq!(b.center().x, -12.0);
482        assert_eq!(b.center().y, 100.5);
483    }
484}