Skip to main content

anathema_geometry/
region.rs

1use crate::{Pos, Size};
2
3/// A region in global space
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct Region {
6    /// The starting position of the region
7    pub from: Pos,
8    /// The end position of the region
9    pub to: Pos,
10}
11
12impl Region {
13    /// Zero...
14    pub const ZERO: Self = Self::new(Pos::ZERO, Pos::ZERO);
15
16    /// Create a new instance of a region.
17    pub const fn new(from: Pos, to: Pos) -> Self {
18        Self { from, to }
19    }
20
21    /// Check if another region is intersecting with this region
22    pub const fn intersects(&self, other: &Region) -> bool {
23        if other.to.x <= self.from.x || other.from.x >= self.to.x {
24            return false;
25        }
26
27        if other.from.y >= self.to.y || other.to.y <= self.from.y {
28            return false;
29        }
30
31        true
32    }
33
34    /// Create a new region by intersecting two regions
35    pub fn intersect_with(self, other: &Region) -> Self {
36        // There is no intersection, making this a zero sized region
37        // as there is no space in between
38        if !self.intersects(other) {
39            return Self::ZERO;
40        }
41
42        let from_x = self.from.x.max(other.from.x);
43        let from_y = self.from.y.max(other.from.y);
44
45        let to_x = self.to.x.min(other.to.x);
46        let to_y = self.to.y.min(other.to.y);
47
48        Region::new(Pos::new(from_x, from_y), Pos::new(to_x, to_y))
49    }
50
51    /// Check if a region contains a position.
52    /// Regions are exclusive, so a region from 0,0 to 10, 10 contains `Pos::ZERO`
53    /// but not `Pos::New(10, 10)`
54    pub const fn contains(&self, pos: Pos) -> bool {
55        pos.x >= self.from.x && pos.x < self.to.x && pos.y >= self.from.y && pos.y < self.to.y
56    }
57
58    /// Constrain a region to fit within another region
59    pub fn constrain(&mut self, other: &Region) {
60        self.from.x = self.from.x.max(other.from.x);
61        self.from.y = self.from.y.max(other.from.y);
62        self.to.x = self.to.x.min(other.to.x);
63        self.to.y = self.to.y.min(other.to.y);
64    }
65}
66
67impl From<(Pos, Size)> for Region {
68    fn from((from, size): (Pos, Size)) -> Self {
69        let to = Pos::new(from.x + size.width as i32, from.y + size.height as i32);
70        Self::new(from, to)
71    }
72}
73
74#[cfg(test)]
75mod test {
76    use super::*;
77
78    #[test]
79    fn region_inersect() {
80        let a = Region::new(Pos::ZERO, Pos::new(10, 10));
81        let b = Region::new(Pos::new(5, 5), Pos::new(8, 8));
82        assert!(a.intersects(&b));
83        assert!(b.intersects(&a));
84    }
85
86    #[test]
87    fn region_contains() {
88        let a = Region::new(Pos::ZERO, Pos::new(10, 10));
89        assert!(a.contains(Pos::ZERO));
90        assert!(a.contains(Pos::new(9, 9)));
91        assert!(!a.contains(Pos::new(10, 10)));
92    }
93
94    #[test]
95    fn constrain_region() {
96        let inner = Region::from((Pos::ZERO, Size::new(10, 10)));
97        let mut outer = Region::from((Pos::ZERO, Size::new(100, 100)));
98        outer.constrain(&inner);
99        let expected = inner;
100        assert_eq!(expected, outer);
101    }
102}