Skip to main content

orinium_browser/engine/renderer_model/
geom.rs

1//! Geometry primitives shared across the render model and the renderer.
2
3/// An affine transformation matrix (2D), in row-major convention with an
4/// implicit translation.
5#[derive(Debug, Clone, Copy)]
6pub struct AffineTransform {
7    pub m11: f32,
8    pub m12: f32,
9    pub m21: f32,
10    pub m22: f32,
11    pub dx: f32,
12    pub dy: f32,
13}
14
15impl AffineTransform {
16    /// The identity transform.
17    pub const fn identity() -> Self {
18        AffineTransform {
19            m11: 1.0,
20            m12: 0.0,
21            m21: 0.0,
22            m22: 1.0,
23            dx: 0.0,
24            dy: 0.0,
25        }
26    }
27
28    /// A pure translation transform.
29    pub fn translate(dx: f32, dy: f32) -> Self {
30        AffineTransform {
31            m11: 1.0,
32            m12: 0.0,
33            m21: 0.0,
34            m22: 1.0,
35            dx,
36            dy,
37        }
38    }
39
40    /// A pure scale transform.
41    pub const fn scale(sx: f32, sy: f32) -> Self {
42        AffineTransform {
43            m11: sx,
44            m12: 0.0,
45            m21: 0.0,
46            m22: sy,
47            dx: 0.0,
48            dy: 0.0,
49        }
50    }
51
52    /// A rotation transform by `angle` radians.
53    pub fn rotate(angle: f32) -> Self {
54        let c = angle.cos();
55        let s = angle.sin();
56        AffineTransform {
57            m11: c,
58            m12: -s,
59            m21: s,
60            m22: c,
61            dx: 0.0,
62            dy: 0.0,
63        }
64    }
65
66    /// Apply this transform to a point.
67    pub fn apply(&self, x: f32, y: f32) -> (f32, f32) {
68        (
69            x * self.m11 + y * self.m12 + self.dx,
70            x * self.m21 + y * self.m22 + self.dy,
71        )
72    }
73
74    /// Compose: `self` after `rhs` (rhs is applied first, then self).
75    pub fn then(&self, rhs: &AffineTransform) -> Self {
76        AffineTransform {
77            m11: self.m11 * rhs.m11 + self.m12 * rhs.m21,
78            m12: self.m11 * rhs.m12 + self.m12 * rhs.m22,
79            m21: self.m21 * rhs.m11 + self.m22 * rhs.m21,
80            m22: self.m21 * rhs.m12 + self.m22 * rhs.m22,
81            dx: self.m11 * rhs.dx + self.m12 * rhs.dy + self.dx,
82            dy: self.m21 * rhs.dx + self.m22 * rhs.dy + self.dy,
83        }
84    }
85
86    /// Returns the inverse transform, or `None` when the matrix is singular.
87    pub fn inverse(&self) -> Option<Self> {
88        let det = self.m11 * self.m22 - self.m12 * self.m21;
89        if det.abs() < f32::EPSILON {
90            return None;
91        }
92        let inv_det = 1.0 / det;
93        Some(AffineTransform {
94            m11: self.m22 * inv_det,
95            m12: -self.m12 * inv_det,
96            m21: -self.m21 * inv_det,
97            m22: self.m11 * inv_det,
98            dx: (self.m21 * self.dy - self.m22 * self.dx) * inv_det,
99            dy: (self.m12 * self.dx - self.m11 * self.dy) * inv_det,
100        })
101    }
102}
103
104/// An axis-aligned rectangle.
105#[derive(Debug, Clone, Copy)]
106pub struct Rect {
107    pub x: f32,
108    pub y: f32,
109    pub width: f32,
110    pub height: f32,
111}
112
113impl Rect {
114    /// Creates a new rectangle.
115    pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
116        Self {
117            x,
118            y,
119            width,
120            height,
121        }
122    }
123
124    /// Returns `true` if the rectangle contains the given point.
125    pub fn contains(&self, x: f32, y: f32) -> bool {
126        x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
127    }
128
129    /// Returns the intersection of two rectangles, or `None` if they do not
130    /// overlap.
131    pub fn intersect(&self, other: &Rect) -> Option<Rect> {
132        let x1 = self.x.max(other.x);
133        let y1 = self.y.max(other.y);
134        let x2 = (self.x + self.width).min(other.x + other.width);
135        let y2 = (self.y + self.height).min(other.y + other.height);
136        if x2 > x1 && y2 > y1 {
137            Some(Rect::new(x1, y1, x2 - x1, y2 - y1))
138        } else {
139            None
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_affine_transform_composition_and_inverse() {
150        let t = AffineTransform::translate(3.0, 4.0).then(&AffineTransform::translate(5.0, 6.0));
151        assert_eq!(t.apply(1.0, 1.0), (9.0, 11.0));
152
153        let inv = t.inverse().unwrap();
154        let (x, y) = t.apply(10.0, 20.0);
155        let (rx, ry) = inv.apply(x, y);
156        assert!((rx - 10.0).abs() < 1e-5, "rx={rx}");
157        assert!((ry - 20.0).abs() < 1e-5, "ry={ry}");
158    }
159
160    #[test]
161    fn test_affine_transform_scale_rotate() {
162        // scale.then(rotate) applies rotate first, then scale.
163        let t = AffineTransform::scale(2.0, 3.0)
164            .then(&AffineTransform::rotate(std::f32::consts::FRAC_PI_2));
165        let (x, y) = t.apply(1.0, 0.0);
166        assert!((x - 0.0).abs() < 1e-5, "x={x}");
167        assert!((y - 3.0).abs() < 1e-5, "y={y}");
168    }
169
170    #[test]
171    fn test_rect_intersect() {
172        let a = Rect::new(0.0, 0.0, 10.0, 10.0);
173        let b = Rect::new(5.0, 5.0, 10.0, 10.0);
174        let i = a.intersect(&b).unwrap();
175        assert!((i.x - 5.0).abs() < 1e-6);
176        assert!((i.width - 5.0).abs() < 1e-6);
177        assert!(Rect::new(20.0, 20.0, 1.0, 1.0).intersect(&a).is_none());
178    }
179}