graphics_shapes/
coord.rs

1use crate::coord;
2#[cfg(feature = "mint")]
3use mint::Point2;
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6use std::ops::{Add, Div, Mul, Neg, Sub};
7
8/// Represents a 2D point
9///
10/// Supports basic math functions
11///
12/// # Usage
13/// ```rust
14///# use std::ops::Neg;
15///# use graphics_shapes::coord;
16///# use graphics_shapes::coord::Coord;
17/// let point = coord!(10,10);
18/// assert_eq!(point + coord!(1.0,1.0), coord!(11, 11));
19/// assert_eq!(point / 2.0, coord!(5,5));
20///
21/// let pos: Coord = (2.0, 3.0).into();
22/// assert_eq!(pos.neg(), coord!(-2, -3));
23/// ```
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
26pub struct Coord {
27    pub x: isize,
28    pub y: isize,
29}
30
31impl Coord {
32    /// Create a new Coord, use `(<number>, <number>).into()` if not using `isize`
33    #[inline]
34    #[must_use]
35    pub const fn new(x: isize, y: isize) -> Self {
36        Self { x, y }
37    }
38
39    /// Calculate a point on a circle (where 0 is the top of the circle)
40    #[must_use]
41    pub fn from_angle<P: Into<Coord>>(center: P, distance: usize, degrees: isize) -> Self {
42        let center = center.into();
43        let distance = distance as f32;
44        let rads = (degrees as f32 - 90.0).to_radians();
45        let x = (distance * rads.cos()).round() as isize;
46        let y = (distance * rads.sin()).round() as isize;
47        coord!(center.x + x, center.y + y)
48    }
49}
50
51impl Coord {
52    /// Distance between `self` and `rhs`
53    #[must_use]
54    pub fn distance<P: Into<Coord>>(self, rhs: P) -> usize {
55        let rhs = rhs.into();
56        let x = (rhs.x - self.x) as f64;
57        let y = (rhs.y - self.y) as f64;
58        x.hypot(y).round().abs() as usize
59    }
60
61    /// Returns true if `self`, `b` and `c` are collinear (all on a straight line)
62    #[must_use]
63    pub fn are_collinear<P1: Into<Coord>, P2: Into<Coord>>(self, b: P1, c: P2) -> bool {
64        let b = b.into();
65        let c = c.into();
66        (b.x - self.x) * (c.y - self.y) == (c.x - self.x) * (b.y - self.y)
67    }
68
69    /// Returns true if `self` is on a line between `a` and `b`
70    #[must_use]
71    pub fn is_between<P1: Into<Coord>, P2: Into<Coord>>(self, a: P1, b: P2) -> bool {
72        let a = a.into();
73        let b = b.into();
74        ((a.x <= self.x && self.x <= b.x) && (a.y <= self.y && self.y <= b.y))
75            || ((b.x <= self.x && self.x <= a.x) && (b.y <= self.y && self.y <= a.y))
76    }
77
78    /// Point midway in between self and rhs
79    /// Use lerp for other positions
80    #[must_use]
81    pub fn mid_point<P: Into<Coord>>(self, rhs: P) -> Coord {
82        let rhs = rhs.into();
83        let x = (self.x + rhs.x) / 2;
84        let y = (self.y + rhs.y) / 2;
85        coord!(x, y)
86    }
87
88    /// Angle in degrees from self to rhs
89    /// 0 is the top of the circle
90    #[must_use]
91    pub fn angle_to<P: Into<Coord>>(self, rhs: P) -> isize {
92        let rhs = rhs.into();
93        let x = (rhs.x - self.x) as f32;
94        let y = (rhs.y - self.y) as f32;
95        y.atan2(x).to_degrees().round() as isize + 90
96    }
97
98    #[must_use]
99    pub fn cross_product<P: Into<Coord>>(self, rhs: P) -> isize {
100        let rhs = rhs.into();
101        self.x * rhs.y - self.y * rhs.x
102    }
103
104    #[must_use]
105    pub fn dot_product<P: Into<Coord>>(self, rhs: P) -> isize {
106        let rhs = rhs.into();
107        self.x * rhs.x + self.y * rhs.y
108    }
109
110    /// Returns a perpendicular point
111    #[inline]
112    #[must_use]
113    pub const fn perpendicular(self) -> Coord {
114        Coord::new(self.y, -self.x)
115    }
116
117    /// Returns absolute copy of point
118    #[inline]
119    #[must_use]
120    pub const fn abs(self) -> Coord {
121        Coord {
122            x: self.x.abs(),
123            y: self.y.abs(),
124        }
125    }
126}
127
128impl<P: Into<Coord>> Add<P> for Coord {
129    type Output = Coord;
130
131    #[inline]
132    #[must_use]
133    fn add(self, rhs: P) -> Self::Output {
134        let rhs = rhs.into();
135        Coord {
136            x: self.x + rhs.x,
137            y: self.y + rhs.y,
138        }
139    }
140}
141
142impl Neg for Coord {
143    type Output = Coord;
144
145    #[inline]
146    #[must_use]
147    fn neg(self) -> Self::Output {
148        Coord {
149            x: -self.x,
150            y: -self.y,
151        }
152    }
153}
154
155impl<P: Into<Coord>> Sub<P> for Coord {
156    type Output = Coord;
157
158    #[inline]
159    #[must_use]
160    fn sub(self, rhs: P) -> Self::Output {
161        let rhs = rhs.into();
162        Coord {
163            x: self.x - rhs.x,
164            y: self.y - rhs.y,
165        }
166    }
167}
168
169impl<P: Into<Coord>> Mul<P> for Coord {
170    type Output = Coord;
171
172    #[inline]
173    #[must_use]
174    fn mul(self, rhs: P) -> Self::Output {
175        let rhs = rhs.into();
176        Coord {
177            x: self.x * rhs.x,
178            y: self.y * rhs.y,
179        }
180    }
181}
182
183#[cfg(feature = "mint")]
184impl From<Point2<isize>> for Coord {
185    #[inline]
186    #[must_use]
187    fn from(point: Point2<isize>) -> Self {
188        Coord {
189            x: point.x,
190            y: point.y,
191        }
192    }
193}
194
195#[cfg(feature = "mint")]
196impl From<&Point2<isize>> for Coord {
197    #[inline]
198    #[must_use]
199    fn from(point: &Point2<isize>) -> Self {
200        Coord {
201            x: point.x,
202            y: point.y,
203        }
204    }
205}
206
207#[cfg(feature = "mint")]
208impl From<Coord> for Point2<isize> {
209    #[inline]
210    #[must_use]
211    fn from(coord: Coord) -> Self {
212        Point2 {
213            x: coord.x,
214            y: coord.y,
215        }
216    }
217}
218
219#[cfg(feature = "mint")]
220impl From<&Coord> for Point2<isize> {
221    #[inline]
222    #[must_use]
223    fn from(coord: &Coord) -> Self {
224        Point2 {
225            x: coord.x,
226            y: coord.y,
227        }
228    }
229}
230
231macro_rules! impl_from_num {
232    ($num_type:ty) => {
233        impl From<($num_type, $num_type)> for Coord {
234            #[inline]
235            #[must_use]
236            fn from(nums: ($num_type, $num_type)) -> Coord {
237                Coord {
238                    x: nums.0 as isize,
239                    y: nums.1 as isize,
240                }
241            }
242        }
243
244        impl From<&($num_type, $num_type)> for Coord {
245            #[inline]
246            #[must_use]
247            fn from(nums: &($num_type, $num_type)) -> Coord {
248                Coord {
249                    x: nums.0 as isize,
250                    y: nums.1 as isize,
251                }
252            }
253        }
254
255        impl Add<$num_type> for Coord {
256            type Output = Coord;
257
258            #[inline]
259            #[must_use]
260            fn add(self, rhs: $num_type) -> Self::Output {
261                Coord {
262                    x: self.x + rhs as isize,
263                    y: self.y + rhs as isize,
264                }
265            }
266        }
267
268        impl Sub<$num_type> for Coord {
269            type Output = Coord;
270
271            #[inline]
272            #[must_use]
273            fn sub(self, rhs: $num_type) -> Self::Output {
274                Coord {
275                    x: self.x - rhs as isize,
276                    y: self.y - rhs as isize,
277                }
278            }
279        }
280    };
281}
282
283macro_rules! int_mul {
284    ($num_type:ty) => {
285        impl Mul<$num_type> for Coord {
286            type Output = Coord;
287
288            #[inline]
289            #[must_use]
290            fn mul(self, rhs: $num_type) -> Self::Output {
291                Coord {
292                    x: self.x * rhs as isize,
293                    y: self.y * rhs as isize,
294                }
295            }
296        }
297
298        impl Div<$num_type> for Coord {
299            type Output = Coord;
300
301            #[inline]
302            #[must_use]
303            fn div(self, rhs: $num_type) -> Self::Output {
304                Coord {
305                    x: self.x / rhs as isize,
306                    y: self.y / rhs as isize,
307                }
308            }
309        }
310    };
311}
312
313macro_rules! float_mul {
314    ($num_type:ty) => {
315        impl Mul<$num_type> for Coord {
316            type Output = Coord;
317
318            #[inline]
319            #[must_use]
320            fn mul(self, rhs: $num_type) -> Self::Output {
321                Coord {
322                    x: ((self.x as $num_type) * rhs).ceil() as isize,
323                    y: ((self.y as $num_type) * rhs).ceil() as isize,
324                }
325            }
326        }
327
328        impl Div<$num_type> for Coord {
329            type Output = Coord;
330
331            #[inline]
332            #[must_use]
333            fn div(self, rhs: $num_type) -> Self::Output {
334                Coord {
335                    x: ((self.x as $num_type) / rhs).ceil() as isize,
336                    y: ((self.y as $num_type) / rhs).ceil() as isize,
337                }
338            }
339        }
340    };
341}
342
343impl From<&Coord> for Coord {
344    #[inline]
345    fn from(value: &Coord) -> Self {
346        *value
347    }
348}
349
350/// Create a [Coord]
351/// Accepts
352/// * `Coord`
353/// * \<num>, \<num>
354/// * (\<num>, \<num>)
355#[macro_export]
356macro_rules! coord {
357    ($lhs: expr, $rhs: expr,) => {
358        $crate::coord::Coord::from(($lhs, $rhs))
359    };
360    ($lhs: expr, $rhs: expr) => {
361        $crate::coord::Coord::from(($lhs, $rhs))
362    };
363    ($pair: expr) => {
364        $crate::coord::Coord::from($pair)
365    };
366}
367
368impl_from_num!(u8);
369impl_from_num!(i8);
370impl_from_num!(u16);
371impl_from_num!(i16);
372impl_from_num!(u32);
373impl_from_num!(i32);
374impl_from_num!(u64);
375impl_from_num!(i64);
376impl_from_num!(u128);
377impl_from_num!(i128);
378impl_from_num!(usize);
379impl_from_num!(isize);
380impl_from_num!(f32);
381impl_from_num!(f64);
382int_mul!(u8);
383int_mul!(u16);
384int_mul!(u32);
385int_mul!(u64);
386int_mul!(u128);
387int_mul!(i8);
388int_mul!(i16);
389int_mul!(i32);
390int_mul!(i64);
391int_mul!(i128);
392int_mul!(usize);
393int_mul!(isize);
394float_mul!(f32);
395float_mul!(f64);
396
397/// Create a list of [Coord]s
398///
399/// # Example
400/// ```rust
401///# use graphics_shapes::coord::Coord;
402///# use graphics_shapes::{coord, coord_vec};
403/// let list = coord_vec![(5.0,6.0), (1_usize,2), coord!(-4,1)];
404/// assert_eq!(list, vec![coord!(5,6), coord!(1,2), coord!(-4,1)]);
405/// ```
406#[macro_export]
407macro_rules! coord_vec {
408    () => (
409        Vec::<$crate::coord::Coord>::new()
410    );
411    ($first:expr) => (
412        vec![$crate::coord::Coord::from($first)]
413    );
414    ($first:expr, $($vararg:expr),+) => (
415        vec![$crate::coord::Coord::from($first), $($crate::coord::Coord::from($vararg)),*]
416    );
417}
418
419#[cfg(test)]
420mod test {
421    mod list {
422        #[test]
423        fn empty() {
424            let list = coord_vec![];
425            assert_eq!(list, vec![]);
426        }
427
428        #[test]
429        fn one() {
430            let list = coord_vec![coord!(1, 1)];
431            assert_eq!(list, vec![coord!(1, 1)]);
432
433            let list = coord_vec![(4.0, 2.0)];
434            assert_eq!(list, vec![coord!(4, 2)]);
435        }
436
437        #[test]
438        fn many() {
439            let list = coord_vec![(-1_isize, 1), (9_usize, 4)];
440            assert_eq!(list, vec![coord!(-1, 1), coord!(9, 4)]);
441
442            let list = coord_vec![(-1, 1), (9_usize, 4), (5, 6), (9, 8)];
443            assert_eq!(
444                list,
445                vec![coord!(-1, 1), coord!(9, 4), coord!(5, 6), coord!(9, 8)]
446            );
447        }
448    }
449
450    mod point_on_circle {
451        use crate::Coord;
452
453        #[test]
454        fn zero_dist() {
455            let center = coord!(100, 100);
456
457            for i in 0..400 {
458                assert_eq!(Coord::from_angle(center, 0, i), center, "rot: {i}");
459            }
460        }
461
462        #[test]
463        fn twenty_dist_positive_degrees() {
464            let center = coord!(100, 100);
465
466            let zero_degree = Coord::from_angle(center, 20, 0);
467            let ninety_degree = Coord::from_angle(center, 20, 90);
468            let oneeighty_degree = Coord::from_angle(center, 20, 180);
469            let twoseventy_degree = Coord::from_angle(center, 20, 270);
470            let seventwenty_degree = Coord::from_angle(center, 20, 720);
471
472            assert_eq!(zero_degree, (100, 80).into());
473            assert_eq!(ninety_degree, (120, 100).into());
474            assert_eq!(oneeighty_degree, (100, 120).into());
475            assert_eq!(twoseventy_degree, (80, 100).into());
476            assert_eq!(seventwenty_degree, (100, 80).into());
477        }
478
479        #[test]
480        fn twenty_dist_negative_degrees() {
481            let center = coord!(100, 100);
482
483            let zero_degree = Coord::from_angle(center, 20, -0);
484            let ninety_degree = Coord::from_angle(center, 20, -90);
485            let oneeighty_degree = Coord::from_angle(center, 20, -180);
486            let twoseventy_degree = Coord::from_angle(center, 20, -270);
487            let seventwenty_degree = Coord::from_angle(center, 20, -720);
488
489            assert_eq!(zero_degree, (100, 80).into());
490            assert_eq!(ninety_degree, (80, 100).into());
491            assert_eq!(oneeighty_degree, (100, 120).into());
492            assert_eq!(twoseventy_degree, (120, 100).into());
493            assert_eq!(seventwenty_degree, (100, 80).into());
494        }
495
496        #[test]
497        fn eighths() {
498            let center = coord!(100, 100);
499
500            let degree_45 = Coord::from_angle(center, 20, 45);
501            let degree_135 = Coord::from_angle(center, 20, 135);
502            let degree_225 = Coord::from_angle(center, 20, 225);
503            let degree_315 = Coord::from_angle(center, 20, 315);
504
505            assert_eq!(degree_45, (114, 86).into());
506            assert_eq!(degree_135, (114, 114).into());
507            assert_eq!(degree_225, (86, 114).into());
508            assert_eq!(degree_315, (86, 86).into());
509        }
510
511        #[test]
512        fn both_ways() {
513            let center = coord!(60, 60);
514
515            let orig = coord!(90, 60);
516
517            let orig_angle = center.angle_to(orig);
518
519            let rotated = Coord::from_angle(center, center.distance(orig), orig_angle);
520
521            assert_eq!(orig, rotated);
522        }
523    }
524
525    mod methods {
526        #[test]
527        fn dist() {
528            let start = coord!(10, 10);
529
530            assert_eq!(start.distance((20, 10)), 10);
531            assert_eq!(start.distance((0, 10)), 10);
532            assert_eq!(start.distance((10, 0)), 10);
533            assert_eq!(start.distance((10, 20)), 10);
534            assert_eq!(start.distance((20, 20)), 14);
535            assert_eq!(start.distance((0, 0)), 14);
536            assert_eq!(start.distance((20, 0)), 14);
537            assert_eq!(start.distance((0, 20)), 14);
538        }
539
540        #[test]
541        fn angle() {
542            let center = coord!(20, 20);
543
544            assert_eq!(center.angle_to((30, 20)), 90);
545            assert_eq!(center.angle_to((20, 30)), 180);
546            assert_eq!(center.angle_to((10, 20)), 270);
547            assert_eq!(center.angle_to((20, 10)), 0);
548        }
549
550        #[test]
551        fn mid_points() {
552            let start = coord!(10, 10);
553
554            assert_eq!(start.mid_point((20, 10)), (15, 10).into());
555            assert_eq!(start.mid_point((0, 10)), (5, 10).into());
556            assert_eq!(start.mid_point((10, 0)), (10, 5).into());
557        }
558    }
559
560    mod ops {
561        use crate::Coord;
562        use std::ops::{Add, Mul, Neg, Sub};
563
564        #[test]
565        fn simple() {
566            assert_eq!(coord!(1, 1).add((1, 1)), (2, 2).into());
567            assert_eq!(coord!(1, 1).sub((1, 1)), (0, 0).into());
568            assert_eq!(coord!(1, 1).mul((1, 1)), (1, 1).into());
569            assert_eq!(coord!(1, 1).abs(), (1, 1).into());
570            assert_eq!(coord!(1, 1).neg(), (-1, -1).into());
571
572            assert_eq!(coord!(2, 8).add((12, 63)), (14, 71).into());
573            assert_eq!(coord!(3, 7).sub((13, 24)), (-10, -17).into());
574            assert_eq!(coord!(4, 6).mul((11, 21)), (44, 126).into());
575            assert_eq!(coord!(5, -5).abs(), (5, 5).into());
576            assert_eq!(coord!(6, -4).neg(), (-6, 4).into());
577
578            assert_eq!(coord!(4, 8).mul(0.5), (2, 4).into());
579            assert_eq!(coord!(4, 8).mul(Coord::from((0.5, 0.5))), (0, 0).into());
580            assert_eq!(coord!(4, 8).mul(Coord::from((0.4, 0.4))), (0, 0).into());
581        }
582    }
583}