1#[derive(Clone, Copy, Debug, Default, PartialEq)]
2pub struct Vec2 {
3 pub x: f32,
4 pub y: f32,
5}
6
7impl Vec2 {
8 pub const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 };
9}
10
11impl std::ops::Add for Vec2 {
12 type Output = Vec2;
13 fn add(self, other: Vec2) -> Vec2 {
14 Vec2 {
15 x: self.x + other.x,
16 y: self.y + other.y,
17 }
18 }
19}
20
21impl std::ops::Sub for Vec2 {
22 type Output = Vec2;
23 fn sub(self, other: Vec2) -> Vec2 {
24 Vec2 {
25 x: self.x - other.x,
26 y: self.y - other.y,
27 }
28 }
29}
30
31impl std::ops::Neg for Vec2 {
32 type Output = Vec2;
33 fn neg(self) -> Vec2 {
34 Vec2 {
35 x: -self.x,
36 y: -self.y,
37 }
38 }
39}
40
41#[derive(Clone, Copy, Debug, Default, PartialEq)]
42pub struct Size {
43 pub width: f32,
44 pub height: f32,
45}
46
47#[derive(Clone, Copy, Debug, Default, PartialEq)]
48pub struct Rect {
49 pub x: f32,
50 pub y: f32,
51 pub w: f32,
52 pub h: f32,
53}
54
55impl Rect {
56 pub fn contains(&self, p: Vec2) -> bool {
57 p.x >= self.x && p.x <= self.x + self.w && p.y >= self.y && p.y <= self.y + self.h
58 }
59}
60
61#[derive(Clone, Copy, Debug, Default, PartialEq)]
62pub struct Transform {
63 pub translate_x: f32,
64 pub translate_y: f32,
65 pub scale_x: f32,
66 pub scale_y: f32,
67 pub rotate: f32, pub shear_x: f32,
70 pub shear_y: f32,
72 pub origin_x: f32,
73 pub origin_y: f32,
74}
75
76impl Transform {
77 pub fn identity() -> Self {
78 Self {
79 translate_x: 0.0,
80 translate_y: 0.0,
81 scale_x: 1.0,
82 scale_y: 1.0,
83 rotate: 0.0,
84 shear_x: 0.0,
85 shear_y: 0.0,
86 origin_x: 0.5,
87 origin_y: 0.5,
88 }
89 }
90
91 pub fn translate(x: f32, y: f32) -> Self {
92 Self {
93 translate_x: x,
94 translate_y: y,
95 scale_x: 1.0,
96 scale_y: 1.0,
97 rotate: 0.0,
98 shear_x: 0.0,
99 shear_y: 0.0,
100 origin_x: 0.5,
101 origin_y: 0.5,
102 }
103 }
104
105 pub fn linear(&self) -> [f32; 4] {
108 let c = self.rotate.cos();
109 let s = self.rotate.sin();
110 let hsx = self.scale_x;
111 let hsy = self.scale_y;
112 let k00 = hsx;
113 let k01 = self.shear_x * hsy;
114 let k10 = self.shear_y * hsx;
115 let k11 = hsy;
116 [
117 c * k00 - s * k10,
118 c * k01 - s * k11,
119 s * k00 + c * k10,
120 s * k01 + c * k11,
121 ]
122 }
123
124 pub fn inverse_linear(&self) -> Option<[f32; 4]> {
126 let m = self.linear();
127 let det = m[0] * m[3] - m[1] * m[2];
128 if det.abs() < 1e-12 {
129 return None;
130 }
131 Some([m[3] / det, -m[1] / det, -m[2] / det, m[0] / det])
132 }
133
134 pub fn linear_is_identity(&self) -> bool {
136 self.scale_x == 1.0
137 && self.scale_y == 1.0
138 && self.rotate == 0.0
139 && self.shear_x == 0.0
140 && self.shear_y == 0.0
141 }
142
143 pub fn apply_to_point(&self, p: Vec2) -> Vec2 {
144 let ox = self.origin_x;
145 let oy = self.origin_y;
146 let m = self.linear();
147 let x = p.x - ox;
148 let y = p.y - oy;
149
150 Vec2 {
151 x: m[0] * x + m[1] * y + ox + self.translate_x,
152 y: m[2] * x + m[3] * y + oy + self.translate_y,
153 }
154 }
155
156 pub fn apply_to_rect(&self, r: Rect) -> Rect {
157 let ox = r.x + r.w * self.origin_x;
158 let oy = r.y + r.h * self.origin_y;
159 let m = self.linear();
160 let corners = [
161 Vec2 { x: r.x, y: r.y },
162 Vec2 {
163 x: r.x + r.w,
164 y: r.y,
165 },
166 Vec2 {
167 x: r.x,
168 y: r.y + r.h,
169 },
170 Vec2 {
171 x: r.x + r.w,
172 y: r.y + r.h,
173 },
174 ];
175 let mut min_x = f32::MAX;
176 let mut min_y = f32::MAX;
177 let mut max_x = f32::MIN;
178 let mut max_y = f32::MIN;
179 for c in corners {
180 let x = c.x - ox;
181 let y = c.y - oy;
182 let tx = m[0] * x + m[1] * y + ox + self.translate_x;
183 let ty = m[2] * x + m[3] * y + oy + self.translate_y;
184 min_x = min_x.min(tx);
185 min_y = min_y.min(ty);
186 max_x = max_x.max(tx);
187 max_y = max_y.max(ty);
188 }
189 Rect {
190 x: min_x,
191 y: min_y,
192 w: max_x - min_x,
193 h: max_y - min_y,
194 }
195 }
196
197 pub fn combine(&self, other: &Transform) -> Transform {
208 let a = self.linear();
209 let b = other.linear();
210 let m = [
211 a[0] * b[0] + a[1] * b[2],
212 a[0] * b[1] + a[1] * b[3],
213 a[2] * b[0] + a[3] * b[2],
214 a[2] * b[1] + a[3] * b[3],
215 ];
216 let translate_x = a[0] * other.translate_x + a[1] * other.translate_y + self.translate_x;
217 let translate_y = a[2] * other.translate_x + a[3] * other.translate_y + self.translate_y;
218
219 let (scale_x, scale_y, rotate, shear_x, shear_y) =
220 decompose_linear(m).unwrap_or((1.0, 1.0, 0.0, 0.0, 0.0));
221
222 Transform {
223 translate_x,
224 translate_y,
225 scale_x,
226 scale_y,
227 rotate,
228 shear_x,
229 shear_y,
230 origin_x: self.origin_x,
231 origin_y: self.origin_y,
232 }
233 }
234}
235
236fn decompose_linear(m: [f32; 4]) -> Option<(f32, f32, f32, f32, f32)> {
243 let (mut a, mut b, c, d) = (m[0], m[1], m[2], m[3]);
244 let mut angle_sign = 1.0;
245 if a * d - b * c < 0.0 {
246 a = -a;
247 b = -b;
248 angle_sign = -1.0;
249 }
250 let e = a * a + c * c;
251 let f = a * b + c * d;
252 let g = b * b + d * d;
253 let det_p = (e * g - f * f).max(0.0);
254 let s = (e + g + 2.0 * det_p.sqrt()).sqrt();
255 if !(s > 1e-12) {
256 return None;
257 }
258 let root_det = det_p.sqrt();
259 let k00 = (e + root_det) / s;
260 let k01 = f / s;
261 let k10 = f / s;
262 let k11 = (g + root_det) / s;
263 let det_k = (k00 * k11 - k01 * k10).max(1e-24);
264 let r00 = (a * k11 - b * k10) / det_k;
265 let r10 = (c * k11 - d * k10) / det_k;
266 let rotate = r10.atan2(r00);
267 let (sx, sy) = (k00, k11);
268 if sx.abs() < 1e-12 || sy.abs() < 1e-12 {
269 return None;
270 }
271 if angle_sign < 0.0 {
272 return Some((-sx, sy, -rotate, -(k01 / sy), -(k10 / sx)));
273 }
274 Some((sx, sy, rotate, k01 / sy, k10 / sx))
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 fn approx(a: f32, b: f32) -> bool {
282 (a - b).abs() < 1e-3
283 }
284
285 fn transform(tx: f32, ty: f32, sx: f32, sy: f32, rot: f32, hx: f32, hy: f32) -> Transform {
286 Transform {
287 translate_x: tx,
288 translate_y: ty,
289 scale_x: sx,
290 scale_y: sy,
291 rotate: rot,
292 shear_x: hx,
293 shear_y: hy,
294 origin_x: 0.0,
295 origin_y: 0.0,
296 }
297 }
298
299 #[test]
300 fn shear_free_matches_legacy_formula() {
301 let t = transform(5.0, -3.0, 2.0, 0.5, 0.7, 0.0, 0.0);
302 let p = Vec2 { x: 11.0, y: 7.0 };
303 let got = t.apply_to_point(p);
304 let (c, s) = (0.7f32.cos(), 0.7f32.sin());
305 let x = (p.x) * 2.0;
306 let y = (p.y) * 0.5;
307 assert!(approx(got.x, x * c - y * s + 5.0));
308 assert!(approx(got.y, x * s + y * c - 3.0));
309 }
310
311 #[test]
312 fn shear_order_is_scale_then_shear_then_rotate() {
313 let t = transform(0.0, 0.0, 2.0, 3.0, 0.0, 1.0, 0.0);
314 let got = t.apply_to_point(Vec2 { x: 1.0, y: 1.0 });
315 assert!(approx(got.x, 2.0 + 3.0));
316 assert!(approx(got.y, 3.0));
317 }
318
319 #[test]
320 fn combine_round_trips_through_apply() {
321 let cases = [
322 (
323 transform(3.0, 4.0, 1.0, 1.0, 0.0, 0.0, 0.0),
324 transform(0.0, 0.0, 2.0, 2.0, 0.0, 0.0, 0.0),
325 ),
326 (
327 transform(1.0, 2.0, 1.5, 0.5, 0.6, 0.0, 0.0),
328 transform(-2.0, 1.0, 0.7, 1.3, -0.4, 0.0, 0.0),
329 ),
330 (
331 transform(0.0, 0.0, 2.0, 0.5, 0.9, 0.0, 0.0),
332 transform(4.0, -1.0, 1.0, 1.0, 0.3, 0.0, 0.0),
333 ),
334 (
335 transform(2.0, 0.0, 1.0, 1.0, 0.2, 0.8, -0.3),
336 transform(0.0, 5.0, 1.2, 0.9, -0.5, 0.4, 0.1),
337 ),
338 (
339 transform(0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 0.0),
340 transform(7.0, 7.0, 1.0, 1.0, 1.1, 0.2, 0.0),
341 ),
342 ];
343 let points = [
344 Vec2 { x: 0.0, y: 0.0 },
345 Vec2 { x: 10.0, y: -4.0 },
346 Vec2 { x: -3.5, y: 8.25 },
347 Vec2 { x: 100.0, y: 200.0 },
348 ];
349 for (a, b) in cases {
350 let combined = a.combine(&b);
351 for p in points {
352 let expect = a.apply_to_point(b.apply_to_point(p));
353 let got = combined.apply_to_point(p);
354 assert!(
355 approx(got.x, expect.x) && approx(got.y, expect.y),
356 "combine mismatch: {a:?} then {b:?} at {p:?}: got {got:?}, want {expect:?}"
357 );
358 }
359 }
360 }
361
362 #[test]
363 fn inverse_linear_round_trips() {
364 let t = transform(3.0, -2.0, 1.5, 0.75, 0.8, 0.6, -0.2);
365 let m = t.linear();
366 let n = t.inverse_linear().expect("invertible");
367 let id = [
368 n[0] * m[0] + n[1] * m[2],
369 n[0] * m[1] + n[1] * m[3],
370 n[2] * m[0] + n[3] * m[2],
371 n[2] * m[1] + n[3] * m[3],
372 ];
373 assert!(approx(id[0], 1.0) && approx(id[3], 1.0));
374 assert!(approx(id[1], 0.0) && approx(id[2], 0.0));
375 }
376}