Skip to main content

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    fn add(self, rhs: P) -> Self::Output {
133        let rhs = rhs.into();
134        Coord {
135            x: self.x + rhs.x,
136            y: self.y + rhs.y,
137        }
138    }
139}
140
141impl Neg for Coord {
142    type Output = Coord;
143
144    #[inline]
145    fn neg(self) -> Self::Output {
146        Coord {
147            x: -self.x,
148            y: -self.y,
149        }
150    }
151}
152
153impl<P: Into<Coord>> Sub<P> for Coord {
154    type Output = Coord;
155
156    #[inline]
157    fn sub(self, rhs: P) -> Self::Output {
158        let rhs = rhs.into();
159        Coord {
160            x: self.x - rhs.x,
161            y: self.y - rhs.y,
162        }
163    }
164}
165
166impl<P: Into<Coord>> Mul<P> for Coord {
167    type Output = Coord;
168
169    #[inline]
170    fn mul(self, rhs: P) -> Self::Output {
171        let rhs = rhs.into();
172        Coord {
173            x: self.x * rhs.x,
174            y: self.y * rhs.y,
175        }
176    }
177}
178
179#[cfg(feature = "mint")]
180impl From<Point2<isize>> for Coord {
181    #[inline]
182    #[must_use]
183    fn from(point: Point2<isize>) -> Self {
184        Coord {
185            x: point.x,
186            y: point.y,
187        }
188    }
189}
190
191#[cfg(feature = "mint")]
192impl From<&Point2<isize>> for Coord {
193    #[inline]
194    #[must_use]
195    fn from(point: &Point2<isize>) -> Self {
196        Coord {
197            x: point.x,
198            y: point.y,
199        }
200    }
201}
202
203#[cfg(feature = "mint")]
204impl From<Coord> for Point2<isize> {
205    #[inline]
206    #[must_use]
207    fn from(coord: Coord) -> Self {
208        Point2 {
209            x: coord.x,
210            y: coord.y,
211        }
212    }
213}
214
215#[cfg(feature = "mint")]
216impl From<&Coord> for Point2<isize> {
217    #[inline]
218    #[must_use]
219    fn from(coord: &Coord) -> Self {
220        Point2 {
221            x: coord.x,
222            y: coord.y,
223        }
224    }
225}
226
227macro_rules! impl_from_num {
228    ($num_type:ty) => {
229        impl From<($num_type, $num_type)> for Coord {
230            #[inline]
231
232            fn from(nums: ($num_type, $num_type)) -> Coord {
233                Coord {
234                    x: nums.0 as isize,
235                    y: nums.1 as isize,
236                }
237            }
238        }
239
240        impl From<&($num_type, $num_type)> for Coord {
241            #[inline]
242
243            fn from(nums: &($num_type, $num_type)) -> Coord {
244                Coord {
245                    x: nums.0 as isize,
246                    y: nums.1 as isize,
247                }
248            }
249        }
250
251        impl Add<$num_type> for Coord {
252            type Output = Coord;
253
254            #[inline]
255
256            fn add(self, rhs: $num_type) -> Self::Output {
257                Coord {
258                    x: self.x + rhs as isize,
259                    y: self.y + rhs as isize,
260                }
261            }
262        }
263
264        impl Sub<$num_type> for Coord {
265            type Output = Coord;
266
267            #[inline]
268
269            fn sub(self, rhs: $num_type) -> Self::Output {
270                Coord {
271                    x: self.x - rhs as isize,
272                    y: self.y - rhs as isize,
273                }
274            }
275        }
276    };
277}
278
279macro_rules! int_mul {
280    ($num_type:ty) => {
281        impl Mul<$num_type> for Coord {
282            type Output = Coord;
283
284            #[inline]
285
286            fn mul(self, rhs: $num_type) -> Self::Output {
287                Coord {
288                    x: self.x * rhs as isize,
289                    y: self.y * rhs as isize,
290                }
291            }
292        }
293
294        impl Div<$num_type> for Coord {
295            type Output = Coord;
296
297            #[inline]
298
299            fn div(self, rhs: $num_type) -> Self::Output {
300                Coord {
301                    x: self.x / rhs as isize,
302                    y: self.y / rhs as isize,
303                }
304            }
305        }
306    };
307}
308
309macro_rules! float_mul {
310    ($num_type:ty) => {
311        impl Mul<$num_type> for Coord {
312            type Output = Coord;
313
314            #[inline]
315
316            fn mul(self, rhs: $num_type) -> Self::Output {
317                Coord {
318                    x: ((self.x as $num_type) * rhs).ceil() as isize,
319                    y: ((self.y as $num_type) * rhs).ceil() as isize,
320                }
321            }
322        }
323
324        impl Div<$num_type> for Coord {
325            type Output = Coord;
326
327            #[inline]
328
329            fn div(self, rhs: $num_type) -> Self::Output {
330                Coord {
331                    x: ((self.x as $num_type) / rhs).ceil() as isize,
332                    y: ((self.y as $num_type) / rhs).ceil() as isize,
333                }
334            }
335        }
336    };
337}
338
339impl From<&Coord> for Coord {
340    #[inline]
341    fn from(value: &Coord) -> Self {
342        *value
343    }
344}
345
346/// Create a [Coord]
347/// Accepts
348/// * `Coord`
349/// * \<num>, \<num>
350/// * (\<num>, \<num>)
351#[macro_export]
352macro_rules! coord {
353    ($lhs: expr, $rhs: expr,) => {
354        $crate::coord::Coord::from(($lhs, $rhs))
355    };
356    ($lhs: expr, $rhs: expr) => {
357        $crate::coord::Coord::from(($lhs, $rhs))
358    };
359    ($pair: expr) => {
360        $crate::coord::Coord::from($pair)
361    };
362}
363
364impl_from_num!(u8);
365impl_from_num!(i8);
366impl_from_num!(u16);
367impl_from_num!(i16);
368impl_from_num!(u32);
369impl_from_num!(i32);
370impl_from_num!(u64);
371impl_from_num!(i64);
372impl_from_num!(u128);
373impl_from_num!(i128);
374impl_from_num!(usize);
375impl_from_num!(isize);
376impl_from_num!(f32);
377impl_from_num!(f64);
378int_mul!(u8);
379int_mul!(u16);
380int_mul!(u32);
381int_mul!(u64);
382int_mul!(u128);
383int_mul!(i8);
384int_mul!(i16);
385int_mul!(i32);
386int_mul!(i64);
387int_mul!(i128);
388int_mul!(usize);
389int_mul!(isize);
390float_mul!(f32);
391float_mul!(f64);
392
393/// Create a list of [Coord]s
394///
395/// # Example
396/// ```rust
397///# use graphics_shapes::coord::Coord;
398///# use graphics_shapes::{coord, coord_vec};
399/// let list = coord_vec![(5.0,6.0), (1_usize,2), coord!(-4,1)];
400/// assert_eq!(list, vec![coord!(5,6), coord!(1,2), coord!(-4,1)]);
401/// ```
402#[macro_export]
403macro_rules! coord_vec {
404    () => (
405        Vec::<$crate::coord::Coord>::new()
406    );
407    ($first:expr) => (
408        vec![$crate::coord::Coord::from($first)]
409    );
410    ($first:expr, $($vararg:expr),+) => (
411        vec![$crate::coord::Coord::from($first), $($crate::coord::Coord::from($vararg)),*]
412    );
413}
414
415#[cfg(test)]
416mod test {
417    mod list {
418        #[test]
419        fn empty() {
420            let list = coord_vec![];
421            assert_eq!(list, vec![]);
422        }
423
424        #[test]
425        fn one() {
426            let list = coord_vec![coord!(1, 1)];
427            assert_eq!(list, vec![coord!(1, 1)]);
428
429            let list = coord_vec![(4.0, 2.0)];
430            assert_eq!(list, vec![coord!(4, 2)]);
431        }
432
433        #[test]
434        fn many() {
435            let list = coord_vec![(-1_isize, 1), (9_usize, 4)];
436            assert_eq!(list, vec![coord!(-1, 1), coord!(9, 4)]);
437
438            let list = coord_vec![(-1, 1), (9_usize, 4), (5, 6), (9, 8)];
439            assert_eq!(
440                list,
441                vec![coord!(-1, 1), coord!(9, 4), coord!(5, 6), coord!(9, 8)]
442            );
443        }
444    }
445
446    mod point_on_circle {
447        use crate::Coord;
448
449        #[test]
450        fn zero_dist() {
451            let center = coord!(100, 100);
452
453            for i in 0..400 {
454                assert_eq!(Coord::from_angle(center, 0, i), center, "rot: {i}");
455            }
456        }
457
458        #[test]
459        fn twenty_dist_positive_degrees() {
460            let center = coord!(100, 100);
461
462            let zero_degree = Coord::from_angle(center, 20, 0);
463            let ninety_degree = Coord::from_angle(center, 20, 90);
464            let oneeighty_degree = Coord::from_angle(center, 20, 180);
465            let twoseventy_degree = Coord::from_angle(center, 20, 270);
466            let seventwenty_degree = Coord::from_angle(center, 20, 720);
467
468            assert_eq!(zero_degree, (100, 80).into());
469            assert_eq!(ninety_degree, (120, 100).into());
470            assert_eq!(oneeighty_degree, (100, 120).into());
471            assert_eq!(twoseventy_degree, (80, 100).into());
472            assert_eq!(seventwenty_degree, (100, 80).into());
473        }
474
475        #[test]
476        fn twenty_dist_negative_degrees() {
477            let center = coord!(100, 100);
478
479            let zero_degree = Coord::from_angle(center, 20, -0);
480            let ninety_degree = Coord::from_angle(center, 20, -90);
481            let oneeighty_degree = Coord::from_angle(center, 20, -180);
482            let twoseventy_degree = Coord::from_angle(center, 20, -270);
483            let seventwenty_degree = Coord::from_angle(center, 20, -720);
484
485            assert_eq!(zero_degree, (100, 80).into());
486            assert_eq!(ninety_degree, (80, 100).into());
487            assert_eq!(oneeighty_degree, (100, 120).into());
488            assert_eq!(twoseventy_degree, (120, 100).into());
489            assert_eq!(seventwenty_degree, (100, 80).into());
490        }
491
492        #[test]
493        fn eighths() {
494            let center = coord!(100, 100);
495
496            let degree_45 = Coord::from_angle(center, 20, 45);
497            let degree_135 = Coord::from_angle(center, 20, 135);
498            let degree_225 = Coord::from_angle(center, 20, 225);
499            let degree_315 = Coord::from_angle(center, 20, 315);
500
501            assert_eq!(degree_45, (114, 86).into());
502            assert_eq!(degree_135, (114, 114).into());
503            assert_eq!(degree_225, (86, 114).into());
504            assert_eq!(degree_315, (86, 86).into());
505        }
506
507        #[test]
508        fn both_ways() {
509            let center = coord!(60, 60);
510
511            let orig = coord!(90, 60);
512
513            let orig_angle = center.angle_to(orig);
514
515            let rotated = Coord::from_angle(center, center.distance(orig), orig_angle);
516
517            assert_eq!(orig, rotated);
518        }
519    }
520
521    mod methods {
522        #[test]
523        fn dist() {
524            let start = coord!(10, 10);
525
526            assert_eq!(start.distance((20, 10)), 10);
527            assert_eq!(start.distance((0, 10)), 10);
528            assert_eq!(start.distance((10, 0)), 10);
529            assert_eq!(start.distance((10, 20)), 10);
530            assert_eq!(start.distance((20, 20)), 14);
531            assert_eq!(start.distance((0, 0)), 14);
532            assert_eq!(start.distance((20, 0)), 14);
533            assert_eq!(start.distance((0, 20)), 14);
534        }
535
536        #[test]
537        fn angle() {
538            let center = coord!(20, 20);
539
540            assert_eq!(center.angle_to((30, 20)), 90);
541            assert_eq!(center.angle_to((20, 30)), 180);
542            assert_eq!(center.angle_to((10, 20)), 270);
543            assert_eq!(center.angle_to((20, 10)), 0);
544        }
545
546        #[test]
547        fn mid_points() {
548            let start = coord!(10, 10);
549
550            assert_eq!(start.mid_point((20, 10)), (15, 10).into());
551            assert_eq!(start.mid_point((0, 10)), (5, 10).into());
552            assert_eq!(start.mid_point((10, 0)), (10, 5).into());
553        }
554    }
555
556    mod ops {
557        use crate::Coord;
558        use std::ops::{Add, Mul, Neg, Sub};
559
560        #[test]
561        fn simple() {
562            assert_eq!(coord!(1, 1).add((1, 1)), (2, 2).into());
563            assert_eq!(coord!(1, 1).sub((1, 1)), (0, 0).into());
564            assert_eq!(coord!(1, 1).mul((1, 1)), (1, 1).into());
565            assert_eq!(coord!(1, 1).abs(), (1, 1).into());
566            assert_eq!(coord!(1, 1).neg(), (-1, -1).into());
567
568            assert_eq!(coord!(2, 8).add((12, 63)), (14, 71).into());
569            assert_eq!(coord!(3, 7).sub((13, 24)), (-10, -17).into());
570            assert_eq!(coord!(4, 6).mul((11, 21)), (44, 126).into());
571            assert_eq!(coord!(5, -5).abs(), (5, 5).into());
572            assert_eq!(coord!(6, -4).neg(), (-6, 4).into());
573
574            assert_eq!(coord!(4, 8).mul(0.5), (2, 4).into());
575            assert_eq!(coord!(4, 8).mul(Coord::from((0.5, 0.5))), (0, 0).into());
576            assert_eq!(coord!(4, 8).mul(Coord::from((0.4, 0.4))), (0, 0).into());
577        }
578    }
579}