Skip to main content

densa_core/
geometry.rs

1/// A position in pixels, y down.
2#[repr(C)]
3#[derive(Debug, Clone, Copy, PartialEq, Default)]
4pub struct Point {
5    /// Horizontal position, rightwards.
6    pub x: f32,
7    /// Vertical position, downwards.
8    pub y: f32,
9}
10
11impl Point {
12    /// The origin, `(0, 0)`.
13    pub const ORIGIN: Self = Self::new(0.0, 0.0);
14
15    /// The point `(x, y)`.
16    pub const fn new(x: f32, y: f32) -> Self {
17        Self { x, y }
18    }
19
20    /// The point moved by `dx`, `dy`.
21    #[must_use]
22    pub fn offset(self, dx: f32, dy: f32) -> Self {
23        Self::new(self.x + dx, self.y + dy)
24    }
25
26    /// `[x, y]`.
27    pub const fn to_array(self) -> [f32; 2] {
28        [self.x, self.y]
29    }
30}
31
32impl From<[f32; 2]> for Point {
33    fn from([x, y]: [f32; 2]) -> Self {
34        Self::new(x, y)
35    }
36}
37
38/// An axis-aligned rectangle in pixels, y down, stored as its four edges.
39///
40/// Edges, not origin plus size, so rectangles built from shared edges tile exactly: with
41/// half-open [`contains`](Self::contains), a point on a shared edge belongs to exactly one of
42/// them, even after both are [translated](Self::translate) by the same amount. (Origin plus
43/// size recomputes `x + width`, which can miss the original edge by a rounding step.)
44///
45/// A rectangle whose right edge is not past its left (or bottom past top), or with a NaN
46/// edge, is [empty](Self::is_empty) and contains no point.
47#[repr(C)]
48#[derive(Debug, Clone, Copy, PartialEq, Default)]
49pub struct Rect {
50    /// Left edge.
51    pub x0: f32,
52    /// Top edge.
53    pub y0: f32,
54    /// Right edge.
55    pub x1: f32,
56    /// Bottom edge.
57    pub y1: f32,
58}
59
60impl Rect {
61    /// The rectangle with top-left corner `(x, y)` and size `width` x `height`.
62    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
63        Self::from_edges(x, y, x + width, y + height)
64    }
65
66    /// The rectangle spanning the given edges, in either order. NaN edges are kept, giving
67    /// an empty rectangle rather than a silently different one.
68    pub fn from_edges(left: f32, top: f32, right: f32, bottom: f32) -> Self {
69        let (x0, x1) = ordered(left, right);
70        let (y0, y1) = ordered(top, bottom);
71        Self { x0, y0, x1, y1 }
72    }
73
74    /// Left edge.
75    pub fn left(&self) -> f32 {
76        self.x0
77    }
78
79    /// Top edge.
80    pub fn top(&self) -> f32 {
81        self.y0
82    }
83
84    /// Right edge.
85    pub fn right(&self) -> f32 {
86        self.x1
87    }
88
89    /// Bottom edge.
90    pub fn bottom(&self) -> f32 {
91        self.y1
92    }
93
94    /// Distance between the left and right edges.
95    pub fn width(&self) -> f32 {
96        self.x1 - self.x0
97    }
98
99    /// Distance between the top and bottom edges.
100    pub fn height(&self) -> f32 {
101        self.y1 - self.y0
102    }
103
104    /// Whether the rectangle covers no area (also true with a NaN edge).
105    pub fn is_empty(&self) -> bool {
106        !(self.x1 > self.x0 && self.y1 > self.y0)
107    }
108
109    /// Whether `point` lies inside: left and top edges included, right and bottom not, so a
110    /// point on an edge shared by two rectangles belongs to exactly one.
111    pub fn contains(&self, point: Point) -> bool {
112        point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
113    }
114
115    /// The rectangle moved by `by`.
116    #[must_use]
117    pub fn translate(self, by: Point) -> Self {
118        Self {
119            x0: self.x0 + by.x,
120            y0: self.y0 + by.y,
121            x1: self.x1 + by.x,
122            y1: self.y1 + by.y,
123        }
124    }
125
126    /// `[x, y, width, height]`, the layout GPU instances use.
127    pub fn to_xywh(self) -> [f32; 4] {
128        [self.x0, self.y0, self.width(), self.height()]
129    }
130
131    /// The top-left corner.
132    pub fn origin(&self) -> Point {
133        Point::new(self.x0, self.y0)
134    }
135
136    /// Width and height.
137    pub fn size(&self) -> Size {
138        Size::new(self.width(), self.height())
139    }
140
141    /// The area both rectangles cover; empty if they do not overlap or either is empty.
142    #[must_use]
143    pub fn intersect(self, other: Self) -> Self {
144        if self.is_empty() || other.is_empty() {
145            return Self::default();
146        }
147        Self {
148            x0: self.x0.max(other.x0),
149            y0: self.y0.max(other.y0),
150            x1: self.x1.min(other.x1),
151            y1: self.y1.min(other.y1),
152        }
153    }
154
155    /// Every point: what a clip that clips nothing is.
156    pub const EVERYTHING: Self = Self {
157        x0: f32::NEG_INFINITY,
158        y0: f32::NEG_INFINITY,
159        x1: f32::INFINITY,
160        y1: f32::INFINITY,
161    };
162}
163
164/// A width and a height in pixels.
165#[repr(C)]
166#[derive(Debug, Clone, Copy, PartialEq, Default)]
167pub struct Size {
168    /// Horizontal extent.
169    pub width: f32,
170    /// Vertical extent.
171    pub height: f32,
172}
173
174impl Size {
175    /// No extent.
176    pub const ZERO: Self = Self::new(0.0, 0.0);
177
178    /// `width` x `height`.
179    pub const fn new(width: f32, height: f32) -> Self {
180        Self { width, height }
181    }
182}
183
184/// `(a, b)` in ascending order; a NaN keeps its place instead of being dropped.
185fn ordered(a: f32, b: f32) -> (f32, f32) {
186    if b < a {
187        (b, a)
188    } else {
189        (a, b)
190    }
191}