Skip to main content

argui_core/
geometry.rs

1#[derive(Clone, Copy, Debug, Default, PartialEq)]
2pub struct Point {
3    pub x: f32,
4    pub y: f32,
5}
6
7impl Point {
8    #[must_use]
9    pub const fn new(x: f32, y: f32) -> Self {
10        Self { x, y }
11    }
12}
13
14#[derive(Clone, Copy, Debug, Default, PartialEq)]
15pub struct Size {
16    pub width: f32,
17    pub height: f32,
18}
19
20impl Size {
21    #[must_use]
22    pub const fn new(width: f32, height: f32) -> Self {
23        Self { width, height }
24    }
25}
26
27#[derive(Clone, Copy, Debug, Default, PartialEq)]
28pub struct Rect {
29    pub origin: Point,
30    pub size: Size,
31}
32
33impl Rect {
34    #[must_use]
35    pub const fn new(origin: Point, size: Size) -> Self {
36        Self { origin, size }
37    }
38
39    #[must_use]
40    pub fn contains(self, point: Point) -> bool {
41        point.x >= self.origin.x
42            && point.y >= self.origin.y
43            && point.x <= self.origin.x + self.size.width
44            && point.y <= self.origin.y + self.size.height
45    }
46
47    #[must_use]
48    pub fn intersection(self, other: Self) -> Option<Self> {
49        let left = self.origin.x.max(other.origin.x);
50        let top = self.origin.y.max(other.origin.y);
51        let right = (self.origin.x + self.size.width).min(other.origin.x + other.size.width);
52        let bottom = (self.origin.y + self.size.height).min(other.origin.y + other.size.height);
53        (right > left && bottom > top)
54            .then(|| Self::new(Point::new(left, top), Size::new(right - left, bottom - top)))
55    }
56
57    #[must_use]
58    pub fn corners(self) -> [Point; 4] {
59        let right = self.origin.x + self.size.width;
60        let bottom = self.origin.y + self.size.height;
61        [
62            self.origin,
63            Point::new(right, self.origin.y),
64            Point::new(right, bottom),
65            Point::new(self.origin.x, bottom),
66        ]
67    }
68}
69
70/// A decomposed, renderer-independent visual transform.
71#[derive(Clone, Copy, Debug, PartialEq)]
72pub struct Transform2D {
73    pub translation: Point,
74    pub scale: Point,
75    pub rotation: f32,
76    pub skew: Point,
77}
78
79impl Default for Transform2D {
80    fn default() -> Self {
81        Self::IDENTITY
82    }
83}
84
85impl Transform2D {
86    pub const IDENTITY: Self = Self {
87        translation: Point::new(0.0, 0.0),
88        scale: Point::new(1.0, 1.0),
89        rotation: 0.0,
90        skew: Point::new(0.0, 0.0),
91    };
92
93    #[must_use]
94    pub const fn translate(mut self, x: f32, y: f32) -> Self {
95        self.translation = Point::new(x, y);
96        self
97    }
98
99    #[must_use]
100    pub const fn scale(mut self, x: f32, y: f32) -> Self {
101        self.scale = Point::new(x, y);
102        self
103    }
104
105    #[must_use]
106    pub const fn rotate(mut self, radians: f32) -> Self {
107        self.rotation = radians;
108        self
109    }
110
111    #[must_use]
112    pub const fn skew(mut self, x_radians: f32, y_radians: f32) -> Self {
113        self.skew = Point::new(x_radians, y_radians);
114        self
115    }
116
117    #[must_use]
118    pub fn affine(self, bounds: Rect, origin: TransformOrigin) -> Affine2D {
119        let pivot = Point::new(
120            bounds.origin.x + bounds.size.width * origin.x,
121            bounds.origin.y + bounds.size.height * origin.y,
122        );
123        let (sin, cos) = self.rotation.sin_cos();
124        let skew_x = self.skew.x.tan();
125        let skew_y = self.skew.y.tan();
126        let linear = Affine2D {
127            matrix: [
128                self.scale.x * (cos - sin * skew_y),
129                self.scale.x * (sin + cos * skew_y),
130                self.scale.y * (cos * skew_x - sin),
131                self.scale.y * (sin * skew_x + cos),
132            ],
133            translation: Point::default(),
134        };
135        Affine2D::translation(self.translation.x + pivot.x, self.translation.y + pivot.y)
136            * linear
137            * Affine2D::translation(-pivot.x, -pivot.y)
138    }
139}
140
141#[derive(Clone, Copy, Debug, PartialEq)]
142pub struct TransformOrigin {
143    pub x: f32,
144    pub y: f32,
145}
146
147impl TransformOrigin {
148    pub const TOP_LEFT: Self = Self { x: 0.0, y: 0.0 };
149    pub const CENTER: Self = Self { x: 0.5, y: 0.5 };
150
151    #[must_use]
152    pub const fn new(x: f32, y: f32) -> Self {
153        Self { x, y }
154    }
155}
156
157impl Default for TransformOrigin {
158    fn default() -> Self {
159        Self::CENTER
160    }
161}
162
163/// An affine matrix laid out as `[m11, m12, m21, m22]` plus translation.
164#[derive(Clone, Copy, Debug, PartialEq)]
165pub struct Affine2D {
166    pub matrix: [f32; 4],
167    pub translation: Point,
168}
169
170impl Default for Affine2D {
171    fn default() -> Self {
172        Self::IDENTITY
173    }
174}
175
176impl Affine2D {
177    pub const IDENTITY: Self = Self {
178        matrix: [1.0, 0.0, 0.0, 1.0],
179        translation: Point::new(0.0, 0.0),
180    };
181
182    #[must_use]
183    pub const fn translation(x: f32, y: f32) -> Self {
184        Self {
185            matrix: [1.0, 0.0, 0.0, 1.0],
186            translation: Point::new(x, y),
187        }
188    }
189
190    #[must_use]
191    pub fn transform_point(self, point: Point) -> Point {
192        Point::new(
193            self.matrix[0] * point.x + self.matrix[2] * point.y + self.translation.x,
194            self.matrix[1] * point.x + self.matrix[3] * point.y + self.translation.y,
195        )
196    }
197
198    #[must_use]
199    pub fn transform_rect(self, rect: Rect) -> Rect {
200        let corners = rect.corners().map(|point| self.transform_point(point));
201        let left = corners.iter().map(|p| p.x).fold(f32::INFINITY, f32::min);
202        let top = corners.iter().map(|p| p.y).fold(f32::INFINITY, f32::min);
203        let right = corners
204            .iter()
205            .map(|p| p.x)
206            .fold(f32::NEG_INFINITY, f32::max);
207        let bottom = corners
208            .iter()
209            .map(|p| p.y)
210            .fold(f32::NEG_INFINITY, f32::max);
211        Rect::new(Point::new(left, top), Size::new(right - left, bottom - top))
212    }
213
214    #[must_use]
215    pub fn inverse(self) -> Option<Self> {
216        let determinant = self.matrix[0] * self.matrix[3] - self.matrix[1] * self.matrix[2];
217        if determinant.abs() <= f32::EPSILON {
218            return None;
219        }
220        let inverse = [
221            self.matrix[3] / determinant,
222            -self.matrix[1] / determinant,
223            -self.matrix[2] / determinant,
224            self.matrix[0] / determinant,
225        ];
226        let translation = Point::new(
227            -(inverse[0] * self.translation.x + inverse[2] * self.translation.y),
228            -(inverse[1] * self.translation.x + inverse[3] * self.translation.y),
229        );
230        Some(Self {
231            matrix: inverse,
232            translation,
233        })
234    }
235
236    #[must_use]
237    pub const fn scaled(self, factor: f32) -> Self {
238        Self {
239            matrix: self.matrix,
240            translation: Point::new(self.translation.x * factor, self.translation.y * factor),
241        }
242    }
243}
244
245impl core::ops::Mul for Affine2D {
246    type Output = Self;
247
248    fn mul(self, rhs: Self) -> Self::Output {
249        Self {
250            matrix: [
251                self.matrix[0] * rhs.matrix[0] + self.matrix[2] * rhs.matrix[1],
252                self.matrix[1] * rhs.matrix[0] + self.matrix[3] * rhs.matrix[1],
253                self.matrix[0] * rhs.matrix[2] + self.matrix[2] * rhs.matrix[3],
254                self.matrix[1] * rhs.matrix[2] + self.matrix[3] * rhs.matrix[3],
255            ],
256            translation: self.transform_point(rhs.translation),
257        }
258    }
259}