1#[derive(Debug, Clone, Copy, PartialEq, Default)]
7pub struct Point {
8 pub x: f32,
9 pub y: f32,
10}
11
12impl Point {
13 pub const fn new(x: f32, y: f32) -> Point {
15 Point { x, y }
16 }
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Default)]
26pub struct Rect {
27 pub x0: f32,
28 pub y0: f32,
29 pub x1: f32,
30 pub y1: f32,
31}
32
33impl Rect {
34 pub const fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
36 Rect { x0, y0, x1, y1 }
37 }
38
39 pub fn width(self) -> f32 {
41 self.x1 - self.x0
42 }
43
44 pub fn height(self) -> f32 {
46 self.y1 - self.y0
47 }
48
49 pub fn normalize(self) -> Rect {
51 Rect {
52 x0: self.x0.min(self.x1),
53 y0: self.y0.min(self.y1),
54 x1: self.x0.max(self.x1),
55 y1: self.y0.max(self.y1),
56 }
57 }
58
59 pub fn union(self, other: Rect) -> Rect {
61 let a = self.normalize();
62 let b = other.normalize();
63 Rect {
64 x0: a.x0.min(b.x0),
65 y0: a.y0.min(b.y0),
66 x1: a.x1.max(b.x1),
67 y1: a.y1.max(b.y1),
68 }
69 }
70
71 pub fn intersect(self, other: Rect) -> Option<Rect> {
75 let a = self.normalize();
76 let b = other.normalize();
77 let r = Rect {
78 x0: a.x0.max(b.x0),
79 y0: a.y0.max(b.y0),
80 x1: a.x1.min(b.x1),
81 y1: a.y1.min(b.y1),
82 };
83 if r.x0 <= r.x1 && r.y0 <= r.y1 {
84 Some(r)
85 } else {
86 None
87 }
88 }
89
90 pub fn contains(self, p: Point) -> bool {
92 let r = self.normalize();
93 p.x >= r.x0 && p.x <= r.x1 && p.y >= r.y0 && p.y <= r.y1
94 }
95
96 pub fn transform(self, m: Matrix) -> Rect {
99 let corners = [
100 m.apply(Point::new(self.x0, self.y0)),
101 m.apply(Point::new(self.x1, self.y0)),
102 m.apply(Point::new(self.x1, self.y1)),
103 m.apply(Point::new(self.x0, self.y1)),
104 ];
105 let mut r = Rect::new(corners[0].x, corners[0].y, corners[0].x, corners[0].y);
106 for c in &corners[1..] {
107 r.x0 = r.x0.min(c.x);
108 r.y0 = r.y0.min(c.y);
109 r.x1 = r.x1.max(c.x);
110 r.y1 = r.y1.max(c.y);
111 }
112 r
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq)]
123pub struct Matrix {
124 pub a: f32,
125 pub b: f32,
126 pub c: f32,
127 pub d: f32,
128 pub e: f32,
129 pub f: f32,
130}
131
132impl Matrix {
133 pub const fn identity() -> Matrix {
135 Matrix {
136 a: 1.0,
137 b: 0.0,
138 c: 0.0,
139 d: 1.0,
140 e: 0.0,
141 f: 0.0,
142 }
143 }
144
145 pub const fn translate(tx: f32, ty: f32) -> Matrix {
147 Matrix {
148 a: 1.0,
149 b: 0.0,
150 c: 0.0,
151 d: 1.0,
152 e: tx,
153 f: ty,
154 }
155 }
156
157 pub const fn scale(sx: f32, sy: f32) -> Matrix {
159 Matrix {
160 a: sx,
161 b: 0.0,
162 c: 0.0,
163 d: sy,
164 e: 0.0,
165 f: 0.0,
166 }
167 }
168
169 pub fn rotate_deg(deg: f32) -> Matrix {
171 let (sin, cos) = deg.to_radians().sin_cos();
172 Matrix {
173 a: cos,
174 b: sin,
175 c: -sin,
176 d: cos,
177 e: 0.0,
178 f: 0.0,
179 }
180 }
181
182 pub fn concat(self, other: Matrix) -> Matrix {
188 Matrix {
189 a: self.a * other.a + self.b * other.c,
190 b: self.a * other.b + self.b * other.d,
191 c: self.c * other.a + self.d * other.c,
192 d: self.c * other.b + self.d * other.d,
193 e: self.e * other.a + self.f * other.c + other.e,
194 f: self.e * other.b + self.f * other.d + other.f,
195 }
196 }
197
198 pub fn apply(self, p: Point) -> Point {
200 Point {
201 x: self.a * p.x + self.c * p.y + self.e,
202 y: self.b * p.x + self.d * p.y + self.f,
203 }
204 }
205
206 pub fn invert(self) -> Option<Matrix> {
209 let det = self.a * self.d - self.b * self.c;
210 if det == 0.0 || !det.is_finite() {
211 return None;
212 }
213 let inv = 1.0 / det;
214 Some(Matrix {
215 a: self.d * inv,
216 b: -self.b * inv,
217 c: -self.c * inv,
218 d: self.a * inv,
219 e: (self.c * self.f - self.d * self.e) * inv,
220 f: (self.b * self.e - self.a * self.f) * inv,
221 })
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228
229 const EPS: f32 = 1e-4;
230
231 fn assert_point_eq(got: Point, want: Point) {
232 assert!(
233 (got.x - want.x).abs() < EPS && (got.y - want.y).abs() < EPS,
234 "expected {want:?}, got {got:?}"
235 );
236 }
237
238 fn assert_matrix_eq(got: Matrix, want: Matrix) {
239 for (g, w) in [
240 (got.a, want.a),
241 (got.b, want.b),
242 (got.c, want.c),
243 (got.d, want.d),
244 (got.e, want.e),
245 (got.f, want.f),
246 ] {
247 assert!((g - w).abs() < EPS, "expected {want:?}, got {got:?}");
248 }
249 }
250
251 fn assert_rect_eq(got: Rect, want: Rect) {
252 for (g, w) in [
253 (got.x0, want.x0),
254 (got.y0, want.y0),
255 (got.x1, want.x1),
256 (got.y1, want.y1),
257 ] {
258 assert!((g - w).abs() < EPS, "expected {want:?}, got {got:?}");
259 }
260 }
261
262 #[test]
263 fn identity_is_neutral() {
264 let id = Matrix::identity();
265 let m = Matrix::translate(3.0, -4.0).concat(Matrix::scale(2.0, 0.5));
266 assert_matrix_eq(id.concat(m), m);
267 assert_matrix_eq(m.concat(id), m);
268 assert_point_eq(id.apply(Point::new(7.5, -2.0)), Point::new(7.5, -2.0));
269 }
270
271 #[test]
272 fn apply_translate_scale_rotate() {
273 let p = Point::new(1.0, 2.0);
274 assert_point_eq(
275 Matrix::translate(10.0, 20.0).apply(p),
276 Point::new(11.0, 22.0),
277 );
278 assert_point_eq(Matrix::scale(2.0, 3.0).apply(p), Point::new(2.0, 6.0));
279 assert_point_eq(
280 Matrix::rotate_deg(90.0).apply(Point::new(1.0, 0.0)),
281 Point::new(0.0, 1.0),
282 );
283 assert_point_eq(
284 Matrix::rotate_deg(180.0).apply(Point::new(1.0, 2.0)),
285 Point::new(-1.0, -2.0),
286 );
287 }
288
289 #[test]
290 fn concat_applies_self_then_other() {
291 let m = Matrix::translate(10.0, 0.0).concat(Matrix::scale(2.0, 2.0));
293 assert_point_eq(m.apply(Point::new(1.0, 2.0)), Point::new(22.0, 4.0));
294 let m = Matrix::scale(2.0, 2.0).concat(Matrix::translate(10.0, 0.0));
296 assert_point_eq(m.apply(Point::new(1.0, 2.0)), Point::new(12.0, 4.0));
297 }
298
299 #[test]
300 fn concat_matches_sequential_application() {
301 let m1 = Matrix::rotate_deg(30.0).concat(Matrix::translate(5.0, -3.0));
302 let m2 = Matrix::scale(1.5, 0.25).concat(Matrix::rotate_deg(-45.0));
303 let p = Point::new(-2.0, 7.0);
304 assert_point_eq(m1.concat(m2).apply(p), m2.apply(m1.apply(p)));
305 }
306
307 #[test]
308 fn invert_round_trips() {
309 let m = Matrix::translate(4.0, -1.0)
310 .concat(Matrix::rotate_deg(37.0))
311 .concat(Matrix::scale(2.0, 5.0));
312 let inv = m.invert().expect("matrix should be invertible");
313 assert_matrix_eq(m.concat(inv), Matrix::identity());
314 assert_matrix_eq(inv.concat(m), Matrix::identity());
315
316 let p = Point::new(3.25, -9.5);
317 assert_point_eq(inv.apply(m.apply(p)), p);
318 }
319
320 #[test]
321 fn invert_translation() {
322 let inv = Matrix::translate(10.0, -2.0).invert().unwrap();
323 assert_matrix_eq(inv, Matrix::translate(-10.0, 2.0));
324 }
325
326 #[test]
327 fn invert_singular_is_none() {
328 assert!(Matrix::scale(0.0, 0.0).invert().is_none());
329 assert!(Matrix::scale(1.0, 0.0).invert().is_none());
330 let m = Matrix {
332 a: 1.0,
333 b: 2.0,
334 c: 2.0,
335 d: 4.0,
336 e: 5.0,
337 f: 6.0,
338 };
339 assert!(m.invert().is_none());
340 }
341
342 #[test]
343 fn rect_width_height_normalize() {
344 let r = Rect::new(10.0, 20.0, 4.0, 2.0);
345 assert!((r.width() - -6.0).abs() < EPS);
346 assert!((r.height() - -18.0).abs() < EPS);
347 let n = r.normalize();
348 assert_rect_eq(n, Rect::new(4.0, 2.0, 10.0, 20.0));
349 assert!((n.width() - 6.0).abs() < EPS);
350 assert!((n.height() - 18.0).abs() < EPS);
351 }
352
353 #[test]
354 fn rect_union() {
355 let a = Rect::new(0.0, 0.0, 2.0, 2.0);
356 let b = Rect::new(1.0, -1.0, 3.0, 1.0);
357 assert_rect_eq(a.union(b), Rect::new(0.0, -1.0, 3.0, 2.0));
358 let flipped = Rect::new(3.0, 1.0, 1.0, -1.0);
360 assert_rect_eq(a.union(flipped), Rect::new(0.0, -1.0, 3.0, 2.0));
361 }
362
363 #[test]
364 fn rect_intersect() {
365 let a = Rect::new(0.0, 0.0, 4.0, 4.0);
366 let b = Rect::new(2.0, 1.0, 6.0, 3.0);
367 assert_rect_eq(a.intersect(b).unwrap(), Rect::new(2.0, 1.0, 4.0, 3.0));
368 assert!(a.intersect(Rect::new(5.0, 5.0, 6.0, 6.0)).is_none());
370 let edge = a.intersect(Rect::new(4.0, 0.0, 8.0, 4.0)).unwrap();
372 assert!((edge.width() - 0.0).abs() < EPS);
373 }
374
375 #[test]
376 fn rect_contains() {
377 let r = Rect::new(0.0, 0.0, 10.0, 5.0);
378 assert!(r.contains(Point::new(5.0, 2.5)));
379 assert!(r.contains(Point::new(0.0, 0.0))); assert!(r.contains(Point::new(10.0, 5.0))); assert!(!r.contains(Point::new(10.1, 2.0)));
382 assert!(!r.contains(Point::new(5.0, -0.1)));
383 let f = Rect::new(10.0, 5.0, 0.0, 0.0);
385 assert!(f.contains(Point::new(5.0, 2.5)));
386 }
387
388 #[test]
389 fn rect_transform() {
390 let r = Rect::new(0.0, 0.0, 2.0, 1.0);
391 assert_rect_eq(
392 r.transform(Matrix::translate(5.0, 6.0)),
393 Rect::new(5.0, 6.0, 7.0, 7.0),
394 );
395 assert_rect_eq(
396 r.transform(Matrix::scale(2.0, 3.0)),
397 Rect::new(0.0, 0.0, 4.0, 3.0),
398 );
399 assert_rect_eq(
401 r.transform(Matrix::rotate_deg(90.0)),
402 Rect::new(-1.0, 0.0, 0.0, 2.0),
403 );
404 }
405}