graphics_shapes/
lib.rs

1//! Shapes for Graphics
2//!
3//! Provides shapes for simple graphics
4//!
5//! ```rust
6//! # use graphics_shapes::coord::Coord;
7//! # use graphics_shapes::rect::Rect;
8//! # use graphics_shapes::{coord, Shape};
9//! # use graphics_shapes::triangle::Triangle;
10//! let rect = Rect::new((10,10),(20,20));
11//! assert!(rect.contains(coord!(15,15)));
12//! let triangle = Triangle::new((34,5),(12,30),(9,10));
13//! let rotated = triangle.rotate(45);
14//!
15//! let start = coord!(20,130);
16//! let dist = start.distance((30,130));
17//!```
18
19#![forbid(unsafe_code)]
20
21use crate::coord::Coord;
22use crate::general_math::{rotate_points, scale_points};
23use crate::prelude::*;
24use crate::shape_box::ShapeBox;
25use fnv::FnvHashSet;
26use std::any::Any;
27
28pub mod circle;
29#[macro_use]
30pub mod coord;
31pub mod contains;
32pub mod ellipse;
33pub mod general_math;
34pub mod intersection;
35pub mod lerp;
36pub mod line;
37pub mod polygon;
38pub mod rect;
39pub mod shape_box;
40pub mod triangle;
41
42pub mod prelude {
43    pub use crate::circle::*;
44    pub use crate::contains::ContainsShape;
45    pub use crate::coord;
46    pub use crate::coord::*;
47    pub use crate::ellipse::*;
48    pub use crate::intersection::IntersectsShape;
49    pub use crate::lerp::*;
50    pub use crate::line::*;
51    pub use crate::polygon::*;
52    pub use crate::rect::*;
53    pub use crate::triangle::*;
54    pub use crate::IntersectsContains;
55    pub use crate::Shape;
56}
57
58pub trait AnyToAny: 'static {
59    fn as_any(&self) -> &dyn Any;
60}
61
62impl<T: 'static> AnyToAny for T {
63    fn as_any(&self) -> &dyn Any {
64        self
65    }
66}
67
68pub trait Shape: AnyToAny {
69    /// create this shape from a list of points (corners of a shape or tips of a line)
70    #[must_use]
71    fn from_points(points: &[Coord]) -> Self
72    where
73        Self: Sized;
74
75    /// Used internally
76    #[must_use]
77    fn rebuild(&self, points: &[Coord]) -> Self
78    where
79        Self: Sized;
80
81    /// change every point by +`delta`
82    #[must_use]
83    fn translate_by(&self, delta: Coord) -> Self
84    where
85        Self: Sized,
86    {
87        let points: Vec<Coord> = self.points().iter().map(|p| *p + delta).collect();
88        self.rebuild(&points)
89    }
90
91    /// moves the shapes first point to `point`
92    /// (and changes every other point to match their original distance and angle)
93    ///
94    /// As this moves self.points()[0] the result might be unexpected if the shape was created
95    /// right to left and/or bottom to top
96    #[must_use]
97    fn move_to(&self, point: Coord) -> Self
98    where
99        Self: Sized,
100    {
101        let diff = (point) - self.points()[0];
102        self.translate_by(diff)
103    }
104
105    /// Moves the shapes center to `point`
106    /// (and changes every other point to match their original distance and angle)
107    ///
108    /// As this moves relative to self.points()[0] the result might be unexpected if the shape was created
109    /// right to left and/or bottom to top
110    #[must_use]
111    fn move_center_to(&self, point: Coord) -> Self
112    where
113        Self: Sized,
114    {
115        let diff = point - self.center();
116        self.translate_by(diff)
117    }
118
119    /// Returns true if the shape contains point
120    #[must_use]
121    fn contains(&self, point: Coord) -> bool;
122
123    /// Points(corners/ends) the shape is made of
124    #[must_use]
125    fn points(&self) -> Vec<Coord>;
126
127    /// Rotate shape around it's center
128    #[must_use]
129    fn rotate(&self, degrees: isize) -> Self
130    where
131        Self: Sized,
132    {
133        self.rotate_around(degrees, self.center())
134    }
135
136    /// Rotate shape around a point
137    #[must_use]
138    fn rotate_around(&self, degrees: isize, point: Coord) -> Self
139    where
140        Self: Sized,
141    {
142        let points = rotate_points(point, &self.points(), degrees);
143        self.rebuild(&points)
144    }
145
146    /// Center of shape
147    #[must_use]
148    fn center(&self) -> Coord;
149
150    /// x of the left most point
151    #[must_use]
152    fn left(&self) -> isize {
153        self.points().iter().map(|p| p.x).min().unwrap()
154    }
155
156    /// x of the right most point
157    #[must_use]
158    fn right(&self) -> isize {
159        self.points().iter().map(|p| p.x).max().unwrap()
160    }
161
162    /// y of the top most point
163    #[must_use]
164    fn top(&self) -> isize {
165        self.points().iter().map(|p| p.y).min().unwrap()
166    }
167
168    /// y of the bottom most point
169    #[must_use]
170    fn bottom(&self) -> isize {
171        self.points().iter().map(|p| p.y).max().unwrap()
172    }
173
174    #[must_use]
175    fn top_left(&self) -> Coord {
176        coord!(self.left(), self.top())
177    }
178
179    #[must_use]
180    fn top_right(&self) -> Coord {
181        coord!(self.right(), self.top())
182    }
183
184    #[must_use]
185    fn bottom_left(&self) -> Coord {
186        coord!(self.left(), self.bottom())
187    }
188
189    #[must_use]
190    fn bottom_right(&self) -> Coord {
191        coord!(self.right(), self.bottom())
192    }
193
194    /// Scale the shape by factor (around the center, so the change will be uniform)
195    #[must_use]
196    fn scale(&self, factor: f32) -> Self
197    where
198        Self: Sized,
199    {
200        self.scale_around(factor, self.center())
201    }
202
203    /// Scale the shape by factor around point
204    #[must_use]
205    fn scale_around(&self, factor: f32, point: Coord) -> Self
206    where
207        Self: Sized,
208    {
209        let points = scale_points(point, &self.points(), factor);
210        self.rebuild(&points)
211    }
212
213    /// The coords for drawing the shape outline, the points may be in any order
214    /// This should be cached rather than called per frame
215    #[must_use]
216    fn outline_pixels(&self) -> Vec<Coord>;
217
218    /// The coords for drawing the filled shape, the points may be in any order
219    /// This should be cached rather than called per frame
220    #[must_use]
221    fn filled_pixels(&self) -> Vec<Coord>;
222
223    /// Convert to [ShapeBox], used to store shapes with type (for bulk drawing, etc)
224    #[must_use]
225    fn to_shape_box(&self) -> ShapeBox;
226}
227
228//Separate so `Shape`s don't have to implement Contains and Intersects
229pub trait IntersectsContains: Shape + ContainsShape + IntersectsShape + Sized {
230    /// Returns
231    /// * Some(true) if `self` contains `other`
232    /// * Some(false) if `self` does not contain `other`
233    /// * None if `other` isn't a supported `Shape`
234    #[must_use]
235    fn contains_shape(&self, other: &dyn Shape) -> Option<bool> {
236        if let Some(line) = other.as_any().downcast_ref::<Line>() {
237            return Some(self.contains_line(line));
238        }
239        if let Some(rect) = other.as_any().downcast_ref::<Rect>() {
240            return Some(self.contains_rect(rect));
241        }
242        if let Some(triangle) = other.as_any().downcast_ref::<Triangle>() {
243            return Some(self.contains_triangle(triangle));
244        }
245        if let Some(polygon) = other.as_any().downcast_ref::<Polygon>() {
246            return Some(self.contains_polygon(polygon));
247        }
248        if let Some(circle) = other.as_any().downcast_ref::<Circle>() {
249            return Some(self.contains_circle(circle));
250        }
251        if let Some(ellipse) = other.as_any().downcast_ref::<Ellipse>() {
252            return Some(self.contains_ellipse(ellipse));
253        }
254        if let Some(shapebox) = other.as_any().downcast_ref::<ShapeBox>() {
255            return Some(match shapebox {
256                ShapeBox::Line(line) => self.contains_line(line),
257                ShapeBox::Rect(rect) => self.contains_rect(rect),
258                ShapeBox::Triangle(triangle) => self.contains_triangle(triangle),
259                ShapeBox::Circle(circle) => self.contains_circle(circle),
260                ShapeBox::Ellipse(ellipse) => self.contains_ellipse(ellipse),
261                ShapeBox::Polygon(polygon) => self.contains_polygon(polygon),
262            });
263        }
264        None
265    }
266
267    /// Returns
268    /// * Some(true) if `self` intersects `other`
269    /// * Some(false) if `self` does not intersects `other`
270    /// * None if `other` isn't a supported `Shape`
271    #[must_use]
272    fn intersects_shape(&self, other: &dyn Shape) -> Option<bool> {
273        if let Some(line) = other.as_any().downcast_ref::<Line>() {
274            return Some(self.intersects_line(line));
275        }
276        if let Some(rect) = other.as_any().downcast_ref::<Rect>() {
277            return Some(self.intersects_rect(rect));
278        }
279        if let Some(triangle) = other.as_any().downcast_ref::<Triangle>() {
280            return Some(self.intersects_triangle(triangle));
281        }
282        if let Some(polygon) = other.as_any().downcast_ref::<Polygon>() {
283            return Some(self.intersects_polygon(polygon));
284        }
285        if let Some(circle) = other.as_any().downcast_ref::<Circle>() {
286            return Some(self.intersects_circle(circle));
287        }
288        if let Some(ellipse) = other.as_any().downcast_ref::<Ellipse>() {
289            return Some(self.intersects_ellipse(ellipse));
290        }
291        if let Some(shapebox) = other.as_any().downcast_ref::<ShapeBox>() {
292            return Some(match shapebox {
293                ShapeBox::Line(line) => self.intersects_line(line),
294                ShapeBox::Rect(rect) => self.intersects_rect(rect),
295                ShapeBox::Triangle(triangle) => self.intersects_triangle(triangle),
296                ShapeBox::Circle(circle) => self.intersects_circle(circle),
297                ShapeBox::Ellipse(ellipse) => self.intersects_ellipse(ellipse),
298                ShapeBox::Polygon(polygon) => self.intersects_polygon(polygon),
299            });
300        }
301        None
302    }
303}
304
305fn new_hash_set() -> FnvHashSet<Coord> {
306    FnvHashSet::default()
307}
308
309#[cfg(test)]
310mod test {
311    use crate::prelude::*;
312
313    pub fn check_points(expected: &[(isize, isize)], actual: &[Coord]) {
314        let mut expected: Vec<Coord> = expected.iter().map(|(x, y)| coord!(*x, *y)).collect();
315        let mut unexpected = vec![];
316        for point in actual {
317            if let Some(i) = expected.iter().position(|p| p == point) {
318                expected.remove(i);
319            } else {
320                unexpected.push(point);
321            }
322        }
323        let mut message = String::new();
324        if !expected.is_empty() {
325            message.push_str(&format!("Points not found: {:?}", expected));
326        }
327        if !unexpected.is_empty() {
328            message.push_str(&format!("Points unexpectedly found: {:?}", unexpected));
329        }
330        if !message.is_empty() {
331            panic!("{message}");
332        }
333    }
334
335    #[test]
336    fn generic_contains() {
337        let outer = Rect::new((0, 0), (10, 10));
338        let inner = Line::new((2, 2), (4, 4));
339
340        assert!(outer.contains_line(&inner));
341        assert_eq!(outer.contains_shape(&inner), Some(true));
342
343        let outside = Line::new((-3, 200), (-1, -1));
344        assert!(!outer.contains_line(&outside));
345        assert_eq!(outer.contains_shape(&outside), Some(false));
346    }
347
348    #[test]
349    fn shapebox_intersects() {
350        let line = Line::new((10, 10), (20, 20));
351        let rect = Rect::new((5, 5), (15, 15));
352        let shape_box = rect.to_shape_box();
353        assert_eq!(line.intersects_shape(&rect), Some(true));
354        assert_eq!(line.intersects_shape(&shape_box), Some(true));
355    }
356}