Skip to main content

sightloom_core/
geometry.rs

1//! Validated two-dimensional geometry primitives.
2
3use crate::GeometryError;
4
5/// A finite point in two-dimensional pixel space.
6#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct Point {
8    x: f32,
9    y: f32,
10}
11
12impl Point {
13    /// Creates a point when both coordinates are finite.
14    ///
15    /// # Errors
16    ///
17    /// Returns [`GeometryError::NonFinite`] when either coordinate is NaN or
18    /// infinite.
19    pub fn new(x: f32, y: f32) -> Result<Self, GeometryError> {
20        if !x.is_finite() || !y.is_finite() {
21            return Err(GeometryError::NonFinite);
22        }
23
24        Ok(Self { x, y })
25    }
26
27    /// Returns the horizontal coordinate.
28    #[must_use]
29    pub const fn x(self) -> f32 {
30        self.x
31    }
32
33    /// Returns the vertical coordinate.
34    #[must_use]
35    pub const fn y(self) -> f32 {
36        self.y
37    }
38}
39
40/// A validated axis-aligned rectangle using half-open pixel coordinates.
41#[derive(Clone, Copy, Debug, Default, PartialEq)]
42pub struct Rect {
43    left: f32,
44    top: f32,
45    right: f32,
46    bottom: f32,
47}
48
49impl Rect {
50    /// Creates a rectangle from finite, non-inverted bounds.
51    ///
52    /// # Errors
53    ///
54    /// Returns [`GeometryError::NonFinite`] when any bound is NaN or infinite,
55    /// and [`GeometryError::InvertedBounds`] when the right or bottom edge
56    /// precedes its opposite edge.
57    pub fn new(left: f32, top: f32, right: f32, bottom: f32) -> Result<Self, GeometryError> {
58        if !left.is_finite() || !top.is_finite() || !right.is_finite() || !bottom.is_finite() {
59            return Err(GeometryError::NonFinite);
60        }
61        if right < left || bottom < top {
62            return Err(GeometryError::InvertedBounds);
63        }
64
65        Ok(Self {
66            left,
67            top,
68            right,
69            bottom,
70        })
71    }
72
73    /// Returns the left edge.
74    #[must_use]
75    pub const fn left(self) -> f32 {
76        self.left
77    }
78
79    /// Returns the top edge.
80    #[must_use]
81    pub const fn top(self) -> f32 {
82        self.top
83    }
84
85    /// Returns the right edge.
86    #[must_use]
87    pub const fn right(self) -> f32 {
88        self.right
89    }
90
91    /// Returns the bottom edge.
92    #[must_use]
93    pub const fn bottom(self) -> f32 {
94        self.bottom
95    }
96
97    /// Returns the rectangle width.
98    #[must_use]
99    pub fn width(self) -> f32 {
100        self.right - self.left
101    }
102
103    /// Returns the rectangle height.
104    #[must_use]
105    pub fn height(self) -> f32 {
106        self.bottom - self.top
107    }
108
109    /// Returns the rectangle area.
110    #[must_use]
111    pub fn area(self) -> f32 {
112        self.width() * self.height()
113    }
114
115    /// Returns the center point without clamping the rectangle.
116    #[must_use]
117    pub fn center(self) -> Point {
118        Point {
119            x: self.left * 0.5 + self.right * 0.5,
120            y: self.top * 0.5 + self.bottom * 0.5,
121        }
122    }
123
124    /// Returns the geometric intersection, including a valid zero-area result
125    /// when the rectangles are disjoint or only touch at an edge.
126    #[must_use]
127    pub fn intersection(self, other: Self) -> Self {
128        let left = self.left.max(other.left);
129        let top = self.top.max(other.top);
130        let right = self.right.min(other.right).max(left);
131        let bottom = self.bottom.min(other.bottom).max(top);
132
133        Self {
134            left,
135            top,
136            right,
137            bottom,
138        }
139    }
140}