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 {
53 Rect {
54 x0: self.x0.min(self.x1),
55 y0: self.y0.min(self.y1),
56 x1: self.x0.max(self.x1),
57 y1: self.y0.max(self.y1),
58 }
59 }
60
61 pub fn union(self, other: Rect) -> Rect {
63 let a = self.normalize();
64 let b = other.normalize();
65 Rect {
66 x0: a.x0.min(b.x0),
67 y0: a.y0.min(b.y0),
68 x1: a.x1.max(b.x1),
69 y1: a.y1.max(b.y1),
70 }
71 }
72
73 pub fn intersect(self, other: Rect) -> Option<Rect> {
77 let a = self.normalize();
78 let b = other.normalize();
79 let r = Rect {
80 x0: a.x0.max(b.x0),
81 y0: a.y0.max(b.y0),
82 x1: a.x1.min(b.x1),
83 y1: a.y1.min(b.y1),
84 };
85 if r.x0 <= r.x1 && r.y0 <= r.y1 {
86 Some(r)
87 } else {
88 None
89 }
90 }
91
92 pub fn contains(self, p: Point) -> bool {
94 let r = self.normalize();
95 p.x >= r.x0 && p.x <= r.x1 && p.y >= r.y0 && p.y <= r.y1
96 }
97
98 pub fn transform(self, m: Matrix) -> Rect {
101 let corners = [
102 m.apply(Point::new(self.x0, self.y0)),
103 m.apply(Point::new(self.x1, self.y0)),
104 m.apply(Point::new(self.x1, self.y1)),
105 m.apply(Point::new(self.x0, self.y1)),
106 ];
107 let mut r = Rect::new(corners[0].x, corners[0].y, corners[0].x, corners[0].y);
108 for c in &corners[1..] {
109 r.x0 = r.x0.min(c.x);
110 r.y0 = r.y0.min(c.y);
111 r.x1 = r.x1.max(c.x);
112 r.y1 = r.y1.max(c.y);
113 }
114 r
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq)]
125pub struct Matrix {
126 pub a: f32,
127 pub b: f32,
128 pub c: f32,
129 pub d: f32,
130 pub e: f32,
131 pub f: f32,
132}
133
134impl Matrix {
135 pub const fn identity() -> Matrix {
137 Matrix {
138 a: 1.0,
139 b: 0.0,
140 c: 0.0,
141 d: 1.0,
142 e: 0.0,
143 f: 0.0,
144 }
145 }
146
147 pub const fn translate(tx: f32, ty: f32) -> Matrix {
151 Matrix {
152 a: 1.0,
153 b: 0.0,
154 c: 0.0,
155 d: 1.0,
156 e: tx,
157 f: ty,
158 }
159 }
160
161 pub const fn scale(sx: f32, sy: f32) -> Matrix {
165 Matrix {
166 a: sx,
167 b: 0.0,
168 c: 0.0,
169 d: sy,
170 e: 0.0,
171 f: 0.0,
172 }
173 }
174
175 pub fn rotate_deg(deg: f32) -> Matrix {
177 let (sin, cos) = deg.to_radians().sin_cos();
178 Matrix {
179 a: cos,
180 b: sin,
181 c: -sin,
182 d: cos,
183 e: 0.0,
184 f: 0.0,
185 }
186 }
187
188 pub fn concat(self, other: Matrix) -> Matrix {
196 Matrix {
197 a: self.a * other.a + self.b * other.c,
198 b: self.a * other.b + self.b * other.d,
199 c: self.c * other.a + self.d * other.c,
200 d: self.c * other.b + self.d * other.d,
201 e: self.e * other.a + self.f * other.c + other.e,
202 f: self.e * other.b + self.f * other.d + other.f,
203 }
204 }
205
206 pub fn apply(self, p: Point) -> Point {
210 Point {
211 x: self.a * p.x + self.c * p.y + self.e,
212 y: self.b * p.x + self.d * p.y + self.f,
213 }
214 }
215
216 pub fn invert(self) -> Option<Matrix> {
219 let det = self.a * self.d - self.b * self.c;
220 if det == 0.0 || !det.is_finite() {
221 return None;
222 }
223 let inv = 1.0 / det;
224 Some(Matrix {
225 a: self.d * inv,
226 b: -self.b * inv,
227 c: -self.c * inv,
228 d: self.a * inv,
229 e: (self.c * self.f - self.d * self.e) * inv,
230 f: (self.b * self.e - self.a * self.f) * inv,
231 })
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 const EPS: f32 = 1e-4;
240
241 fn assert_point_eq(got: Point, want: Point) {
242 assert!(
243 (got.x - want.x).abs() < EPS && (got.y - want.y).abs() < EPS,
244 "expected {want:?}, got {got:?}"
245 );
246 }
247
248 fn assert_matrix_eq(got: Matrix, want: Matrix) {
249 for (g, w) in [
250 (got.a, want.a),
251 (got.b, want.b),
252 (got.c, want.c),
253 (got.d, want.d),
254 (got.e, want.e),
255 (got.f, want.f),
256 ] {
257 assert!((g - w).abs() < EPS, "expected {want:?}, got {got:?}");
258 }
259 }
260
261 fn assert_rect_eq(got: Rect, want: Rect) {
262 for (g, w) in [
263 (got.x0, want.x0),
264 (got.y0, want.y0),
265 (got.x1, want.x1),
266 (got.y1, want.y1),
267 ] {
268 assert!((g - w).abs() < EPS, "expected {want:?}, got {got:?}");
269 }
270 }
271
272 #[test]
273 fn identity_is_neutral() {
274 let id = Matrix::identity();
275 let m = Matrix::translate(3.0, -4.0).concat(Matrix::scale(2.0, 0.5));
276 assert_matrix_eq(id.concat(m), m);
277 assert_matrix_eq(m.concat(id), m);
278 assert_point_eq(id.apply(Point::new(7.5, -2.0)), Point::new(7.5, -2.0));
279 }
280
281 #[test]
283 fn apply_translate_scale_rotate() {
284 let p = Point::new(1.0, 2.0);
285 assert_point_eq(
286 Matrix::translate(10.0, 20.0).apply(p),
287 Point::new(11.0, 22.0),
288 );
289 assert_point_eq(Matrix::scale(2.0, 3.0).apply(p), Point::new(2.0, 6.0));
290 assert_point_eq(
291 Matrix::rotate_deg(90.0).apply(Point::new(1.0, 0.0)),
292 Point::new(0.0, 1.0),
293 );
294 assert_point_eq(
295 Matrix::rotate_deg(180.0).apply(Point::new(1.0, 2.0)),
296 Point::new(-1.0, -2.0),
297 );
298 }
299
300 #[test]
302 fn concat_applies_self_then_other() {
303 let m = Matrix::translate(10.0, 0.0).concat(Matrix::scale(2.0, 2.0));
305 assert_point_eq(m.apply(Point::new(1.0, 2.0)), Point::new(22.0, 4.0));
306 let m = Matrix::scale(2.0, 2.0).concat(Matrix::translate(10.0, 0.0));
308 assert_point_eq(m.apply(Point::new(1.0, 2.0)), Point::new(12.0, 4.0));
309 }
310
311 #[test]
313 fn concat_matches_sequential_application() {
314 let m1 = Matrix::rotate_deg(30.0).concat(Matrix::translate(5.0, -3.0));
315 let m2 = Matrix::scale(1.5, 0.25).concat(Matrix::rotate_deg(-45.0));
316 let p = Point::new(-2.0, 7.0);
317 assert_point_eq(m1.concat(m2).apply(p), m2.apply(m1.apply(p)));
318 }
319
320 #[test]
322 fn invert_round_trips() {
323 let m = Matrix::translate(4.0, -1.0)
324 .concat(Matrix::rotate_deg(37.0))
325 .concat(Matrix::scale(2.0, 5.0));
326 let inv = m.invert().expect("matrix should be invertible");
327 assert_matrix_eq(m.concat(inv), Matrix::identity());
328 assert_matrix_eq(inv.concat(m), Matrix::identity());
329
330 let p = Point::new(3.25, -9.5);
331 assert_point_eq(inv.apply(m.apply(p)), p);
332 }
333
334 #[test]
335 fn invert_translation() {
336 let inv = Matrix::translate(10.0, -2.0).invert().unwrap();
337 assert_matrix_eq(inv, Matrix::translate(-10.0, 2.0));
338 }
339
340 #[test]
342 fn invert_singular_is_none() {
343 assert!(Matrix::scale(0.0, 0.0).invert().is_none());
344 assert!(Matrix::scale(1.0, 0.0).invert().is_none());
345 let m = Matrix {
347 a: 1.0,
348 b: 2.0,
349 c: 2.0,
350 d: 4.0,
351 e: 5.0,
352 f: 6.0,
353 };
354 assert!(m.invert().is_none());
355 }
356
357 #[test]
359 fn rect_width_height_normalize() {
360 let r = Rect::new(10.0, 20.0, 4.0, 2.0);
361 assert!((r.width() - -6.0).abs() < EPS);
362 assert!((r.height() - -18.0).abs() < EPS);
363 let n = r.normalize();
364 assert_rect_eq(n, Rect::new(4.0, 2.0, 10.0, 20.0));
365 assert!((n.width() - 6.0).abs() < EPS);
366 assert!((n.height() - 18.0).abs() < EPS);
367 }
368
369 #[test]
370 fn rect_union() {
371 let a = Rect::new(0.0, 0.0, 2.0, 2.0);
372 let b = Rect::new(1.0, -1.0, 3.0, 1.0);
373 assert_rect_eq(a.union(b), Rect::new(0.0, -1.0, 3.0, 2.0));
374 let flipped = Rect::new(3.0, 1.0, 1.0, -1.0);
376 assert_rect_eq(a.union(flipped), Rect::new(0.0, -1.0, 3.0, 2.0));
377 }
378
379 #[test]
380 fn rect_intersect() {
381 let a = Rect::new(0.0, 0.0, 4.0, 4.0);
382 let b = Rect::new(2.0, 1.0, 6.0, 3.0);
383 assert_rect_eq(a.intersect(b).unwrap(), Rect::new(2.0, 1.0, 4.0, 3.0));
384 assert!(a.intersect(Rect::new(5.0, 5.0, 6.0, 6.0)).is_none());
386 let edge = a.intersect(Rect::new(4.0, 0.0, 8.0, 4.0)).unwrap();
388 assert!((edge.width() - 0.0).abs() < EPS);
389 }
390
391 #[test]
392 fn rect_contains() {
393 let r = Rect::new(0.0, 0.0, 10.0, 5.0);
394 assert!(r.contains(Point::new(5.0, 2.5)));
395 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)));
398 assert!(!r.contains(Point::new(5.0, -0.1)));
399 let f = Rect::new(10.0, 5.0, 0.0, 0.0);
401 assert!(f.contains(Point::new(5.0, 2.5)));
402 }
403
404 #[test]
405 fn rect_transform() {
406 let r = Rect::new(0.0, 0.0, 2.0, 1.0);
407 assert_rect_eq(
408 r.transform(Matrix::translate(5.0, 6.0)),
409 Rect::new(5.0, 6.0, 7.0, 7.0),
410 );
411 assert_rect_eq(
412 r.transform(Matrix::scale(2.0, 3.0)),
413 Rect::new(0.0, 0.0, 4.0, 3.0),
414 );
415 assert_rect_eq(
417 r.transform(Matrix::rotate_deg(90.0)),
418 Rect::new(-1.0, 0.0, 0.0, 2.0),
419 );
420 }
421}