Skip to main content

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()
154            .iter()
155            .map(|p| p.x)
156            .min()
157            .expect("shape has no points")
158    }
159
160    /// x of the right most point
161    #[must_use]
162    fn right(&self) -> isize {
163        self.points()
164            .iter()
165            .map(|p| p.x)
166            .max()
167            .expect("shape has no points")
168    }
169
170    /// y of the top most point
171    #[must_use]
172    fn top(&self) -> isize {
173        self.points()
174            .iter()
175            .map(|p| p.y)
176            .min()
177            .expect("shape has no points")
178    }
179
180    /// y of the bottom most point
181    #[must_use]
182    fn bottom(&self) -> isize {
183        self.points()
184            .iter()
185            .map(|p| p.y)
186            .max()
187            .expect("shape has no points")
188    }
189
190    #[must_use]
191    fn top_left(&self) -> Coord {
192        coord!(self.left(), self.top())
193    }
194
195    #[must_use]
196    fn top_right(&self) -> Coord {
197        coord!(self.right(), self.top())
198    }
199
200    #[must_use]
201    fn bottom_left(&self) -> Coord {
202        coord!(self.left(), self.bottom())
203    }
204
205    #[must_use]
206    fn bottom_right(&self) -> Coord {
207        coord!(self.right(), self.bottom())
208    }
209
210    /// Scale the shape by factor (around the center, so the change will be uniform)
211    #[must_use]
212    fn scale(&self, factor: f32) -> Self
213    where
214        Self: Sized,
215    {
216        self.scale_around(factor, self.center())
217    }
218
219    /// Scale the shape by factor around point
220    #[must_use]
221    fn scale_around(&self, factor: f32, point: Coord) -> Self
222    where
223        Self: Sized,
224    {
225        let points = scale_points(point, &self.points(), factor);
226        self.rebuild(&points)
227    }
228
229    /// The coords for drawing the shape outline, the points may be in any order
230    /// This should be cached rather than called per frame
231    #[must_use]
232    fn outline_pixels(&self) -> Vec<Coord>;
233
234    /// The coords for drawing the filled shape, the points may be in any order
235    /// This should be cached rather than called per frame
236    #[must_use]
237    fn filled_pixels(&self) -> Vec<Coord>;
238
239    /// Convert to [ShapeBox], used to store shapes with type (for bulk drawing, etc)
240    #[must_use]
241    fn to_shape_box(&self) -> ShapeBox;
242}
243
244//Separate so `Shape`s don't have to implement Contains and Intersects
245pub trait IntersectsContains: Shape + ContainsShape + IntersectsShape + Sized {
246    /// Returns
247    /// * Some(true) if `self` contains `other`
248    /// * Some(false) if `self` does not contain `other`
249    /// * None if `other` isn't a supported `Shape`
250    #[must_use]
251    fn contains_shape(&self, other: &dyn Shape) -> Option<bool> {
252        if let Some(line) = other.as_any().downcast_ref::<Line>() {
253            return Some(self.contains_line(line));
254        }
255        if let Some(rect) = other.as_any().downcast_ref::<Rect>() {
256            return Some(self.contains_rect(rect));
257        }
258        if let Some(triangle) = other.as_any().downcast_ref::<Triangle>() {
259            return Some(self.contains_triangle(triangle));
260        }
261        if let Some(polygon) = other.as_any().downcast_ref::<Polygon>() {
262            return Some(self.contains_polygon(polygon));
263        }
264        if let Some(circle) = other.as_any().downcast_ref::<Circle>() {
265            return Some(self.contains_circle(circle));
266        }
267        if let Some(ellipse) = other.as_any().downcast_ref::<Ellipse>() {
268            return Some(self.contains_ellipse(ellipse));
269        }
270        if let Some(shapebox) = other.as_any().downcast_ref::<ShapeBox>() {
271            return Some(match shapebox {
272                ShapeBox::Line(line) => self.contains_line(line),
273                ShapeBox::Rect(rect) => self.contains_rect(rect),
274                ShapeBox::Triangle(triangle) => self.contains_triangle(triangle),
275                ShapeBox::Circle(circle) => self.contains_circle(circle),
276                ShapeBox::Ellipse(ellipse) => self.contains_ellipse(ellipse),
277                ShapeBox::Polygon(polygon) => self.contains_polygon(polygon),
278            });
279        }
280        None
281    }
282
283    /// Returns
284    /// * Some(true) if `self` intersects `other`
285    /// * Some(false) if `self` does not intersects `other`
286    /// * None if `other` isn't a supported `Shape`
287    #[must_use]
288    fn intersects_shape(&self, other: &dyn Shape) -> Option<bool> {
289        if let Some(line) = other.as_any().downcast_ref::<Line>() {
290            return Some(self.intersects_line(line));
291        }
292        if let Some(rect) = other.as_any().downcast_ref::<Rect>() {
293            return Some(self.intersects_rect(rect));
294        }
295        if let Some(triangle) = other.as_any().downcast_ref::<Triangle>() {
296            return Some(self.intersects_triangle(triangle));
297        }
298        if let Some(polygon) = other.as_any().downcast_ref::<Polygon>() {
299            return Some(self.intersects_polygon(polygon));
300        }
301        if let Some(circle) = other.as_any().downcast_ref::<Circle>() {
302            return Some(self.intersects_circle(circle));
303        }
304        if let Some(ellipse) = other.as_any().downcast_ref::<Ellipse>() {
305            return Some(self.intersects_ellipse(ellipse));
306        }
307        if let Some(shapebox) = other.as_any().downcast_ref::<ShapeBox>() {
308            return Some(match shapebox {
309                ShapeBox::Line(line) => self.intersects_line(line),
310                ShapeBox::Rect(rect) => self.intersects_rect(rect),
311                ShapeBox::Triangle(triangle) => self.intersects_triangle(triangle),
312                ShapeBox::Circle(circle) => self.intersects_circle(circle),
313                ShapeBox::Ellipse(ellipse) => self.intersects_ellipse(ellipse),
314                ShapeBox::Polygon(polygon) => self.intersects_polygon(polygon),
315            });
316        }
317        None
318    }
319}
320
321fn new_hash_set() -> FnvHashSet<Coord> {
322    FnvHashSet::default()
323}
324
325#[cfg(test)]
326mod test {
327    use crate::prelude::*;
328
329    pub fn check_points(expected: &[(isize, isize)], actual: &[Coord]) {
330        let mut expected: Vec<Coord> = expected.iter().map(|(x, y)| coord!(*x, *y)).collect();
331        let mut unexpected = vec![];
332        for point in actual {
333            if let Some(i) = expected.iter().position(|p| p == point) {
334                expected.remove(i);
335            } else {
336                unexpected.push(point);
337            }
338        }
339        let mut message = String::new();
340        if !expected.is_empty() {
341            message.push_str(&format!("Points not found: {:?}", expected));
342        }
343        if !unexpected.is_empty() {
344            message.push_str(&format!("Points unexpectedly found: {:?}", unexpected));
345        }
346        if !message.is_empty() {
347            panic!("{message}");
348        }
349    }
350
351    #[test]
352    fn generic_contains() {
353        let outer = Rect::new((0, 0), (10, 10));
354        let inner = Line::new((2, 2), (4, 4));
355
356        assert!(outer.contains_line(&inner));
357        assert_eq!(outer.contains_shape(&inner), Some(true));
358
359        let outside = Line::new((-3, 200), (-1, -1));
360        assert!(!outer.contains_line(&outside));
361        assert_eq!(outer.contains_shape(&outside), Some(false));
362    }
363
364    #[test]
365    fn shapebox_intersects() {
366        let line = Line::new((10, 10), (20, 20));
367        let rect = Rect::new((5, 5), (15, 15));
368        let shape_box = rect.to_shape_box();
369        assert_eq!(line.intersects_shape(&rect), Some(true));
370        assert_eq!(line.intersects_shape(&shape_box), Some(true));
371    }
372}