/rust/registry/src/index.crates.io-1949cf8c6b5b557f/kurbo-0.13.0/src/affine.rs
Line | Count | Source |
1 | | // Copyright 2018 the Kurbo Authors |
2 | | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | | |
4 | | //! Affine transforms. |
5 | | |
6 | | use core::ops::{Mul, MulAssign}; |
7 | | |
8 | | use crate::{Point, Rect, Vec2}; |
9 | | |
10 | | #[cfg(not(feature = "std"))] |
11 | | use crate::common::FloatFuncs; |
12 | | |
13 | | /// A 2D affine transform. |
14 | | #[derive(Clone, Copy, Debug, PartialEq)] |
15 | | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] |
16 | | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
17 | | pub struct Affine([f64; 6]); |
18 | | |
19 | | impl Affine { |
20 | | /// The identity transform. |
21 | | pub const IDENTITY: Affine = Affine::scale(1.0); |
22 | | |
23 | | /// A transform that is flipped on the y-axis. Useful for converting between |
24 | | /// y-up and y-down spaces. |
25 | | pub const FLIP_Y: Affine = Affine::new([1.0, 0., 0., -1.0, 0., 0.]); |
26 | | |
27 | | /// A transform that is flipped on the x-axis. |
28 | | pub const FLIP_X: Affine = Affine::new([-1.0, 0., 0., 1.0, 0., 0.]); |
29 | | |
30 | | /// Construct an affine transform from coefficients. |
31 | | /// |
32 | | /// If the coefficients are `(a, b, c, d, e, f)`, then the resulting |
33 | | /// transformation represents this augmented matrix: |
34 | | /// |
35 | | /// ```text |
36 | | /// | a c e | |
37 | | /// | b d f | |
38 | | /// | 0 0 1 | |
39 | | /// ``` |
40 | | /// |
41 | | /// Note that this convention is transposed from PostScript and |
42 | | /// Direct2D, but is consistent with the |
43 | | /// [Wikipedia](https://en.wikipedia.org/wiki/Affine_transformation) |
44 | | /// formulation of affine transformation as augmented matrix. The |
45 | | /// idea is that `(A * B) * v == A * (B * v)`, where `*` is the |
46 | | /// [`Mul`] trait. |
47 | | #[inline(always)] |
48 | 0 | pub const fn new(c: [f64; 6]) -> Affine { |
49 | 0 | Affine(c) |
50 | 0 | } |
51 | | |
52 | | /// An affine transform representing uniform scaling. |
53 | | #[inline(always)] |
54 | 0 | pub const fn scale(s: f64) -> Affine { |
55 | 0 | Affine([s, 0.0, 0.0, s, 0.0, 0.0]) |
56 | 0 | } |
57 | | |
58 | | /// An affine transform representing non-uniform scaling |
59 | | /// with different scale values for x and y |
60 | | #[inline(always)] |
61 | 0 | pub const fn scale_non_uniform(s_x: f64, s_y: f64) -> Affine { |
62 | 0 | Affine([s_x, 0.0, 0.0, s_y, 0.0, 0.0]) |
63 | 0 | } |
64 | | |
65 | | /// An affine transform representing a scale of `scale` about `center`. |
66 | | /// |
67 | | /// Useful for a view transform that zooms at a specific point, |
68 | | /// while keeping that point fixed in the result space. |
69 | | /// |
70 | | /// See [`Affine::scale()`] for more info. |
71 | | #[inline] |
72 | 0 | pub fn scale_about(s: f64, center: impl Into<Point>) -> Affine { |
73 | 0 | let center = center.into().to_vec2(); |
74 | 0 | Self::translate(-center) |
75 | 0 | .then_scale(s) |
76 | 0 | .then_translate(center) |
77 | 0 | } |
78 | | |
79 | | /// An affine transform representing rotation. |
80 | | /// |
81 | | /// The convention for rotation is that a positive angle rotates a |
82 | | /// positive X direction into positive Y. Thus, in a Y-down coordinate |
83 | | /// system (as is common for graphics), it is a clockwise rotation, and |
84 | | /// in Y-up (traditional for math), it is anti-clockwise. |
85 | | /// |
86 | | /// The angle, `th`, is expressed in radians. |
87 | | #[inline] |
88 | 0 | pub fn rotate(th: f64) -> Affine { |
89 | 0 | let (s, c) = th.sin_cos(); |
90 | 0 | Affine([c, s, -s, c, 0.0, 0.0]) |
91 | 0 | } |
92 | | |
93 | | /// An affine transform representing a rotation of `th` radians about `center`. |
94 | | /// |
95 | | /// See [`Affine::rotate()`] for more info. |
96 | | #[inline] |
97 | 0 | pub fn rotate_about(th: f64, center: impl Into<Point>) -> Affine { |
98 | 0 | let center = center.into().to_vec2(); |
99 | 0 | Self::translate(-center) |
100 | 0 | .then_rotate(th) |
101 | 0 | .then_translate(center) |
102 | 0 | } |
103 | | |
104 | | /// An affine transform representing translation. |
105 | | #[inline(always)] |
106 | 0 | pub fn translate<V: Into<Vec2>>(p: V) -> Affine { |
107 | 0 | let p = p.into(); |
108 | 0 | Affine([1.0, 0.0, 0.0, 1.0, p.x, p.y]) |
109 | 0 | } |
110 | | |
111 | | /// An affine transformation representing a skew. |
112 | | /// |
113 | | /// The `skew_x` and `skew_y` parameters represent skew factors for the |
114 | | /// horizontal and vertical directions, respectively. |
115 | | /// |
116 | | /// This is commonly used to generate a faux oblique transform for |
117 | | /// font rendering. In this case, you can slant the glyph 20 degrees |
118 | | /// clockwise in the horizontal direction (assuming a Y-up coordinate |
119 | | /// system): |
120 | | /// |
121 | | /// ``` |
122 | | /// let oblique_transform = kurbo::Affine::skew(20f64.to_radians().tan(), 0.0); |
123 | | /// ``` |
124 | | #[inline(always)] |
125 | 0 | pub const fn skew(skew_x: f64, skew_y: f64) -> Affine { |
126 | 0 | Affine([1.0, skew_y, skew_x, 1.0, 0.0, 0.0]) |
127 | 0 | } |
128 | | |
129 | | /// Create an affine transform that represents reflection about the line `point + direction * t, t in (-infty, infty)` |
130 | | /// |
131 | | /// # Examples |
132 | | /// |
133 | | /// ``` |
134 | | /// # use kurbo::{Point, Vec2, Affine}; |
135 | | /// # fn assert_near(p0: Point, p1: Point) { |
136 | | /// # assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}"); |
137 | | /// # } |
138 | | /// let point = Point::new(1., 0.); |
139 | | /// let vec = Vec2::new(1., 1.); |
140 | | /// let map = Affine::reflect(point, vec); |
141 | | /// assert_near(map * Point::new(1., 0.), Point::new(1., 0.)); |
142 | | /// assert_near(map * Point::new(2., 1.), Point::new(2., 1.)); |
143 | | /// assert_near(map * Point::new(2., 2.), Point::new(3., 1.)); |
144 | | /// ``` |
145 | | #[inline] |
146 | | #[must_use] |
147 | 0 | pub fn reflect(point: impl Into<Point>, direction: impl Into<Vec2>) -> Self { |
148 | 0 | let point = point.into(); |
149 | 0 | let direction = direction.into(); |
150 | | |
151 | 0 | let n = Vec2 { |
152 | 0 | x: direction.y, |
153 | 0 | y: -direction.x, |
154 | 0 | } |
155 | 0 | .normalize(); |
156 | | |
157 | | // Compute Householder reflection matrix |
158 | 0 | let x2 = n.x * n.x; |
159 | 0 | let xy = n.x * n.y; |
160 | 0 | let y2 = n.y * n.y; |
161 | | // Here we also add in the post translation, because it doesn't require any further calc. |
162 | 0 | let aff = Affine::new([ |
163 | 0 | 1. - 2. * x2, |
164 | 0 | -2. * xy, |
165 | 0 | -2. * xy, |
166 | 0 | 1. - 2. * y2, |
167 | 0 | point.x, |
168 | 0 | point.y, |
169 | 0 | ]); |
170 | 0 | aff.pre_translate(-point.to_vec2()) |
171 | 0 | } |
172 | | |
173 | | /// A [rotation] by `th` followed by `self`. |
174 | | /// |
175 | | /// Equivalent to `self * Affine::rotate(th)` |
176 | | /// |
177 | | /// [rotation]: Affine::rotate |
178 | | #[inline] |
179 | | #[must_use] |
180 | 0 | pub fn pre_rotate(self, th: f64) -> Self { |
181 | 0 | self * Affine::rotate(th) |
182 | 0 | } |
183 | | |
184 | | /// A [rotation] by `th` about `center` followed by `self`. |
185 | | /// |
186 | | /// Equivalent to `self * Affine::rotate_about(th, center)` |
187 | | /// |
188 | | /// [rotation]: Affine::rotate_about |
189 | | #[inline] |
190 | | #[must_use] |
191 | 0 | pub fn pre_rotate_about(self, th: f64, center: impl Into<Point>) -> Self { |
192 | 0 | Affine::rotate_about(th, center) * self |
193 | 0 | } |
194 | | |
195 | | /// A [scale] by `scale` followed by `self`. |
196 | | /// |
197 | | /// Equivalent to `self * Affine::scale(scale)` |
198 | | /// |
199 | | /// [scale]: Affine::scale |
200 | | #[inline] |
201 | | #[must_use] |
202 | 0 | pub fn pre_scale(self, scale: f64) -> Self { |
203 | 0 | self * Affine::scale(scale) |
204 | 0 | } |
205 | | |
206 | | /// A [scale] by `(scale_x, scale_y)` followed by `self`. |
207 | | /// |
208 | | /// Equivalent to `self * Affine::scale_non_uniform(scale_x, scale_y)` |
209 | | /// |
210 | | /// [scale]: Affine::scale_non_uniform |
211 | | #[inline] |
212 | | #[must_use] |
213 | 0 | pub fn pre_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self { |
214 | 0 | self * Affine::scale_non_uniform(scale_x, scale_y) |
215 | 0 | } |
216 | | |
217 | | /// A [translation] of `trans` followed by `self`. |
218 | | /// |
219 | | /// Equivalent to `self * Affine::translate(trans)` |
220 | | /// |
221 | | /// [translation]: Affine::translate |
222 | | #[inline] |
223 | | #[must_use] |
224 | 0 | pub fn pre_translate(self, trans: Vec2) -> Self { |
225 | 0 | self * Affine::translate(trans) |
226 | 0 | } |
227 | | |
228 | | /// `self` followed by a [rotation] of `th`. |
229 | | /// |
230 | | /// Equivalent to `Affine::rotate(th) * self` |
231 | | /// |
232 | | /// [rotation]: Affine::rotate |
233 | | #[inline] |
234 | | #[must_use] |
235 | 0 | pub fn then_rotate(self, th: f64) -> Self { |
236 | 0 | Affine::rotate(th) * self |
237 | 0 | } |
238 | | |
239 | | /// `self` followed by a [rotation] of `th` about `center`. |
240 | | /// |
241 | | /// Equivalent to `Affine::rotate_about(th, center) * self` |
242 | | /// |
243 | | /// [rotation]: Affine::rotate_about |
244 | | #[inline] |
245 | | #[must_use] |
246 | 0 | pub fn then_rotate_about(self, th: f64, center: impl Into<Point>) -> Self { |
247 | 0 | Affine::rotate_about(th, center) * self |
248 | 0 | } |
249 | | |
250 | | /// `self` followed by a [scale] of `scale`. |
251 | | /// |
252 | | /// Equivalent to `Affine::scale(scale) * self` |
253 | | /// |
254 | | /// [scale]: Affine::scale |
255 | | #[inline] |
256 | | #[must_use] |
257 | 0 | pub fn then_scale(self, scale: f64) -> Self { |
258 | 0 | Affine::scale(scale) * self |
259 | 0 | } |
260 | | |
261 | | /// `self` followed by a [scale] of `(scale_x, scale_y)`. |
262 | | /// |
263 | | /// Equivalent to `Affine::scale_non_uniform(scale_x, scale_y) * self` |
264 | | /// |
265 | | /// [scale]: Affine::scale_non_uniform |
266 | | #[inline] |
267 | | #[must_use] |
268 | 0 | pub fn then_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self { |
269 | 0 | Affine::scale_non_uniform(scale_x, scale_y) * self |
270 | 0 | } |
271 | | |
272 | | /// `self` followed by a [scale] of `scale` about `center`. |
273 | | /// |
274 | | /// Equivalent to `Affine::scale_about(scale) * self` |
275 | | /// |
276 | | /// [scale]: Affine::scale_about |
277 | | #[inline] |
278 | | #[must_use] |
279 | 0 | pub fn then_scale_about(self, scale: f64, center: impl Into<Point>) -> Self { |
280 | 0 | Affine::scale_about(scale, center) * self |
281 | 0 | } |
282 | | |
283 | | /// `self` followed by a translation of `trans`. |
284 | | /// |
285 | | /// Equivalent to `Affine::translate(trans) * self` |
286 | | /// |
287 | | /// [translation]: Affine::translate |
288 | | #[inline] |
289 | | #[must_use] |
290 | 0 | pub const fn then_translate(mut self, trans: Vec2) -> Self { |
291 | 0 | self.0[4] += trans.x; |
292 | 0 | self.0[5] += trans.y; |
293 | 0 | self |
294 | 0 | } |
295 | | |
296 | | /// Creates an affine transformation that takes the unit square to the given rectangle. |
297 | | /// |
298 | | /// Useful when you want to draw into the unit square but have your output fill any rectangle. |
299 | | /// In this case push the `Affine` onto the transform stack. |
300 | 0 | pub const fn map_unit_square(rect: Rect) -> Affine { |
301 | 0 | Affine([rect.width(), 0., 0., rect.height(), rect.x0, rect.y0]) |
302 | 0 | } |
303 | | |
304 | | /// Get the coefficients of the transform. |
305 | | #[inline(always)] |
306 | 0 | pub const fn as_coeffs(self) -> [f64; 6] { |
307 | 0 | self.0 |
308 | 0 | } |
309 | | |
310 | | /// Compute the determinant of this transform. |
311 | 0 | pub const fn determinant(self) -> f64 { |
312 | 0 | self.0[0] * self.0[3] - self.0[1] * self.0[2] |
313 | 0 | } |
314 | | |
315 | | /// Compute the inverse transform. |
316 | | /// |
317 | | /// Produces NaN values when the determinant is zero. |
318 | 0 | pub const fn inverse(self) -> Affine { |
319 | 0 | let inv_det = self.determinant().recip(); |
320 | 0 | Affine([ |
321 | 0 | inv_det * self.0[3], |
322 | 0 | -inv_det * self.0[1], |
323 | 0 | -inv_det * self.0[2], |
324 | 0 | inv_det * self.0[0], |
325 | 0 | inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]), |
326 | 0 | inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]), |
327 | 0 | ]) |
328 | 0 | } |
329 | | |
330 | | /// Compute the bounding box of a transformed rectangle. |
331 | | /// |
332 | | /// Returns the minimal `Rect` that encloses the given `Rect` after affine transformation. |
333 | | /// If the transform is axis-aligned, then this bounding box is "tight", in other words the |
334 | | /// returned `Rect` is the transformed rectangle. |
335 | | /// |
336 | | /// The returned rectangle always has non-negative width and height. |
337 | 0 | pub fn transform_rect_bbox(self, rect: Rect) -> Rect { |
338 | 0 | let p00 = self * Point::new(rect.x0, rect.y0); |
339 | 0 | let p01 = self * Point::new(rect.x0, rect.y1); |
340 | 0 | let p10 = self * Point::new(rect.x1, rect.y0); |
341 | 0 | let p11 = self * Point::new(rect.x1, rect.y1); |
342 | 0 | Rect::from_points(p00, p01).union(Rect::from_points(p10, p11)) |
343 | 0 | } |
344 | | |
345 | | /// Is this map [finite]? |
346 | | /// |
347 | | /// [finite]: f64::is_finite |
348 | | #[inline] |
349 | 0 | pub const fn is_finite(&self) -> bool { |
350 | 0 | self.0[0].is_finite() |
351 | 0 | && self.0[1].is_finite() |
352 | 0 | && self.0[2].is_finite() |
353 | 0 | && self.0[3].is_finite() |
354 | 0 | && self.0[4].is_finite() |
355 | 0 | && self.0[5].is_finite() |
356 | 0 | } |
357 | | |
358 | | /// Is this map [NaN]? |
359 | | /// |
360 | | /// [NaN]: f64::is_nan |
361 | | #[inline] |
362 | 0 | pub const fn is_nan(&self) -> bool { |
363 | 0 | self.0[0].is_nan() |
364 | 0 | || self.0[1].is_nan() |
365 | 0 | || self.0[2].is_nan() |
366 | 0 | || self.0[3].is_nan() |
367 | 0 | || self.0[4].is_nan() |
368 | 0 | || self.0[5].is_nan() |
369 | 0 | } |
370 | | |
371 | | /// Compute the singular value decomposition of the linear transformation (ignoring the |
372 | | /// translation). |
373 | | /// |
374 | | /// All non-degenerate linear transformations can be represented as |
375 | | /// |
376 | | /// 1. a rotation about the origin. |
377 | | /// 2. a scaling along the x and y axes |
378 | | /// 3. another rotation about the origin |
379 | | /// |
380 | | /// composed together. Decomposing a 2x2 matrix in this way is called a "singular value |
381 | | /// decomposition" and is written `U Σ V^T`, where U and V^T are orthogonal (rotations) and Σ |
382 | | /// is a diagonal matrix (a scaling). |
383 | | /// |
384 | | /// Since currently this function is used to calculate ellipse radii and rotation from an |
385 | | /// affine map on the unit circle, we don't calculate V^T, since a rotation of the unit (or |
386 | | /// any) circle about its center always results in the same circle. This is the reason that an |
387 | | /// ellipse mapped using an affine map is always an ellipse. |
388 | | /// |
389 | | /// Will return NaNs if the matrix (or equivalently the linear map) is non-finite. |
390 | | /// |
391 | | /// The first part of the returned tuple is the scaling, the second part is the angle of |
392 | | /// rotation (in radians). The scaling along the x-axis is guaranteed to be greater than or |
393 | | /// equal to the scaling along the y-axis. |
394 | | // |
395 | | // Note: though this does quite some computation, we are often interested only in specific |
396 | | // components of the result. Hence this is marked `#[inline(always)]`, to give the compiler a |
397 | | // good chance at eliminating dead code. |
398 | | #[inline(always)] |
399 | 0 | pub(crate) fn svd(self) -> (Vec2, f64) { |
400 | 0 | let [a, b, c, d, _, _] = self.0; |
401 | 0 | let a2 = a * a; |
402 | 0 | let b2 = b * b; |
403 | 0 | let c2 = c * c; |
404 | 0 | let d2 = d * d; |
405 | 0 | let ab = a * b; |
406 | 0 | let cd = c * d; |
407 | 0 | let angle = 0.5 * (2.0 * (ab + cd)).atan2(a2 - b2 + c2 - d2); |
408 | | |
409 | | // Given matrix A = [ a c ] |
410 | | // [ b d ] |
411 | | // |
412 | | // The two singular values σ1, σ2 of A are the square roots of the two eigen values λ1, λ2 |
413 | | // of M = A^T A. The common formula for 2x2 eigenvalues requires evaluating a square root, |
414 | | // but we'd like to compute the singular values of the matrix without nested square roots. |
415 | | // |
416 | | // M = A^T A = [ aa+cc ab+cd ] |
417 | | // [ ab+cd bb+dd ] |
418 | | // |
419 | | // We have |
420 | | // λ = 1/2 (tr(M) ± sqrt(tr(M)^2 - 4 det(M))). |
421 | | // |
422 | | // Note det(M) = det(A^T A) = det(A)^2. |
423 | | // => 2λ = tr(M) ± sqrt(tr(M)^2 - 4 det(A)^2) |
424 | | // => 2λ = tr(M) ± sqrt[(a^2+b^2+c^2+d^2)^2 - 4 (ad-bc)^2] |
425 | | // By factorizing the inner term, |
426 | | // => 2λ = tr(M) ± sqrt[((a+d)^2 + (b-c)^2) ((a-d)^2 + (b+c)^2)] |
427 | | // => 2λ = tr(M) ± sqrt[(a+d)^2 + (b-c)^2] sqrt[(a-d)^2 + (b+c)^2] |
428 | | // |
429 | | // Define S1 = sqrt[(a+d)^2 + (b-c)^2] |
430 | | // S2 = sqrt[(a-d)^2 + (b+c)^2]. |
431 | | // |
432 | | // => 2λ = tr(M) ± S1 S2 |
433 | | // => 2λ = 1/2 (S1^2 + S2^2) ± S1 S2 |
434 | | // => λ = 1/4 (S1^2 + S2^2 ± 2 S1 S2) |
435 | | // => λ = 1/4 (S1 ± S2)^2 |
436 | | // |
437 | | // Note we're interested in |
438 | | // σ = sqrt(λ). |
439 | | // |
440 | | // => σ1 = 1/2 (S1 + S2) |
441 | | // and similarly σ2 = 1/2 |S1 - S2| |
442 | 0 | let s1 = ((a + d).powi(2) + (b - c).powi(2)).sqrt(); |
443 | 0 | let s2 = ((a - d).powi(2) + (b + c).powi(2)).sqrt(); |
444 | 0 | ( |
445 | 0 | Vec2 { |
446 | 0 | x: 0.5 * (s1 + s2), |
447 | 0 | y: 0.5 * (s1 - s2).abs(), |
448 | 0 | }, |
449 | 0 | angle, |
450 | 0 | ) |
451 | 0 | } |
452 | | |
453 | | /// Returns the translation part of this affine map (`(self.0[4], self.0[5])`). |
454 | | #[inline(always)] |
455 | 0 | pub const fn translation(self) -> Vec2 { |
456 | 0 | Vec2 { |
457 | 0 | x: self.0[4], |
458 | 0 | y: self.0[5], |
459 | 0 | } |
460 | 0 | } |
461 | | |
462 | | /// Replaces the translation portion of this affine map |
463 | | /// |
464 | | /// The translation can be seen as being applied after the linear part of the map. |
465 | | #[must_use] |
466 | | #[inline(always)] |
467 | 0 | pub const fn with_translation(mut self, trans: Vec2) -> Affine { |
468 | 0 | self.0[4] = trans.x; |
469 | 0 | self.0[5] = trans.y; |
470 | 0 | self |
471 | 0 | } |
472 | | } |
473 | | |
474 | | impl Default for Affine { |
475 | | #[inline(always)] |
476 | 0 | fn default() -> Affine { |
477 | 0 | Affine::IDENTITY |
478 | 0 | } |
479 | | } |
480 | | |
481 | | impl Mul<Point> for Affine { |
482 | | type Output = Point; |
483 | | |
484 | | #[inline] |
485 | 0 | fn mul(self, other: Point) -> Point { |
486 | 0 | Point::new( |
487 | 0 | self.0[0] * other.x + self.0[2] * other.y + self.0[4], |
488 | 0 | self.0[1] * other.x + self.0[3] * other.y + self.0[5], |
489 | | ) |
490 | 0 | } |
491 | | } |
492 | | |
493 | | impl Mul for Affine { |
494 | | type Output = Affine; |
495 | | |
496 | | #[inline] |
497 | 0 | fn mul(self, other: Affine) -> Affine { |
498 | 0 | Affine([ |
499 | 0 | self.0[0] * other.0[0] + self.0[2] * other.0[1], |
500 | 0 | self.0[1] * other.0[0] + self.0[3] * other.0[1], |
501 | 0 | self.0[0] * other.0[2] + self.0[2] * other.0[3], |
502 | 0 | self.0[1] * other.0[2] + self.0[3] * other.0[3], |
503 | 0 | self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4], |
504 | 0 | self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5], |
505 | 0 | ]) |
506 | 0 | } |
507 | | } |
508 | | |
509 | | impl MulAssign for Affine { |
510 | | #[inline] |
511 | 0 | fn mul_assign(&mut self, other: Affine) { |
512 | 0 | *self = self.mul(other); |
513 | 0 | } |
514 | | } |
515 | | |
516 | | impl Mul<Affine> for f64 { |
517 | | type Output = Affine; |
518 | | |
519 | | #[inline] |
520 | 0 | fn mul(self, other: Affine) -> Affine { |
521 | 0 | Affine([ |
522 | 0 | self * other.0[0], |
523 | 0 | self * other.0[1], |
524 | 0 | self * other.0[2], |
525 | 0 | self * other.0[3], |
526 | 0 | self * other.0[4], |
527 | 0 | self * other.0[5], |
528 | 0 | ]) |
529 | 0 | } |
530 | | } |
531 | | |
532 | | // Conversions to and from mint |
533 | | #[cfg(feature = "mint")] |
534 | | impl From<Affine> for mint::ColumnMatrix2x3<f64> { |
535 | | #[inline(always)] |
536 | | fn from(a: Affine) -> mint::ColumnMatrix2x3<f64> { |
537 | | mint::ColumnMatrix2x3 { |
538 | | x: mint::Vector2 { |
539 | | x: a.0[0], |
540 | | y: a.0[1], |
541 | | }, |
542 | | y: mint::Vector2 { |
543 | | x: a.0[2], |
544 | | y: a.0[3], |
545 | | }, |
546 | | z: mint::Vector2 { |
547 | | x: a.0[4], |
548 | | y: a.0[5], |
549 | | }, |
550 | | } |
551 | | } |
552 | | } |
553 | | |
554 | | #[cfg(feature = "mint")] |
555 | | impl From<mint::ColumnMatrix2x3<f64>> for Affine { |
556 | | #[inline(always)] |
557 | | fn from(m: mint::ColumnMatrix2x3<f64>) -> Affine { |
558 | | Affine([m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y]) |
559 | | } |
560 | | } |
561 | | |
562 | | #[cfg(test)] |
563 | | mod tests { |
564 | | use crate::{Affine, Point, Vec2}; |
565 | | use std::f64::consts::PI; |
566 | | |
567 | | fn assert_near(p0: Point, p1: Point) { |
568 | | assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}"); |
569 | | } |
570 | | |
571 | | fn affine_assert_near(a0: Affine, a1: Affine) { |
572 | | for i in 0..6 { |
573 | | assert!((a0.0[i] - a1.0[i]).abs() < 1e-9, "{a0:?} != {a1:?}"); |
574 | | } |
575 | | } |
576 | | |
577 | | #[test] |
578 | | fn affine_basic() { |
579 | | let p = Point::new(3.0, 4.0); |
580 | | |
581 | | assert_near(Affine::default() * p, p); |
582 | | assert_near(Affine::scale(2.0) * p, Point::new(6.0, 8.0)); |
583 | | assert_near(Affine::rotate(0.0) * p, p); |
584 | | assert_near(Affine::rotate(PI / 2.0) * p, Point::new(-4.0, 3.0)); |
585 | | assert_near(Affine::translate((5.0, 6.0)) * p, Point::new(8.0, 10.0)); |
586 | | assert_near(Affine::skew(0.0, 0.0) * p, p); |
587 | | assert_near(Affine::skew(2.0, 4.0) * p, Point::new(11.0, 16.0)); |
588 | | } |
589 | | |
590 | | #[test] |
591 | | fn affine_mul() { |
592 | | let a1 = Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); |
593 | | let a2 = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]); |
594 | | |
595 | | let px = Point::new(1.0, 0.0); |
596 | | let py = Point::new(0.0, 1.0); |
597 | | let pxy = Point::new(1.0, 1.0); |
598 | | assert_near(a1 * (a2 * px), (a1 * a2) * px); |
599 | | assert_near(a1 * (a2 * py), (a1 * a2) * py); |
600 | | assert_near(a1 * (a2 * pxy), (a1 * a2) * pxy); |
601 | | } |
602 | | |
603 | | #[test] |
604 | | fn affine_inv() { |
605 | | let a = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]); |
606 | | let a_inv = a.inverse(); |
607 | | |
608 | | let px = Point::new(1.0, 0.0); |
609 | | let py = Point::new(0.0, 1.0); |
610 | | let pxy = Point::new(1.0, 1.0); |
611 | | assert_near(a * (a_inv * px), px); |
612 | | assert_near(a * (a_inv * py), py); |
613 | | assert_near(a * (a_inv * pxy), pxy); |
614 | | assert_near(a_inv * (a * px), px); |
615 | | assert_near(a_inv * (a * py), py); |
616 | | assert_near(a_inv * (a * pxy), pxy); |
617 | | } |
618 | | |
619 | | #[test] |
620 | | fn reflection() { |
621 | | affine_assert_near( |
622 | | Affine::reflect(Point::ZERO, (1., 0.)), |
623 | | Affine::new([1., 0., 0., -1., 0., 0.]), |
624 | | ); |
625 | | affine_assert_near( |
626 | | Affine::reflect(Point::ZERO, (0., 1.)), |
627 | | Affine::new([-1., 0., 0., 1., 0., 0.]), |
628 | | ); |
629 | | // y = x |
630 | | affine_assert_near( |
631 | | Affine::reflect(Point::ZERO, (1., 1.)), |
632 | | Affine::new([0., 1., 1., 0., 0., 0.]), |
633 | | ); |
634 | | |
635 | | // no translate |
636 | | let point = Point::new(0., 0.); |
637 | | let vec = Vec2::new(1., 1.); |
638 | | let map = Affine::reflect(point, vec); |
639 | | assert_near(map * Point::new(0., 0.), Point::new(0., 0.)); |
640 | | assert_near(map * Point::new(1., 1.), Point::new(1., 1.)); |
641 | | assert_near(map * Point::new(1., 2.), Point::new(2., 1.)); |
642 | | |
643 | | // with translate |
644 | | let point = Point::new(1., 0.); |
645 | | let vec = Vec2::new(1., 1.); |
646 | | let map = Affine::reflect(point, vec); |
647 | | assert_near(map * Point::new(1., 0.), Point::new(1., 0.)); |
648 | | assert_near(map * Point::new(2., 1.), Point::new(2., 1.)); |
649 | | assert_near(map * Point::new(2., 2.), Point::new(3., 1.)); |
650 | | } |
651 | | |
652 | | #[test] |
653 | | fn svd() { |
654 | | let a = Affine::new([1., 2., 3., 4., 5., 6.]); |
655 | | let a_no_translate = a.with_translation(Vec2::ZERO); |
656 | | |
657 | | // translation should have no effect |
658 | | let (scale, rotation) = a.svd(); |
659 | | let (scale_no_translate, rotation_no_translate) = a_no_translate.svd(); |
660 | | assert_near(scale.to_point(), scale_no_translate.to_point()); |
661 | | assert!((rotation - rotation_no_translate).abs() <= 1e-9); |
662 | | |
663 | | assert_near( |
664 | | scale.to_point(), |
665 | | Point::new(5.4649857042190427, 0.36596619062625782), |
666 | | ); |
667 | | assert!((rotation - 0.95691013360780001).abs() <= 1e-9); |
668 | | |
669 | | // singular affine |
670 | | let a = Affine::new([0., 0., 0., 0., 5., 6.]); |
671 | | assert_eq!(a.determinant(), 0.); |
672 | | let (scale, rotation) = a.svd(); |
673 | | assert_eq!(scale, Vec2::new(0., 0.)); |
674 | | assert_eq!(rotation, 0.); |
675 | | } |
676 | | |
677 | | #[test] |
678 | | fn svd_singular_values() { |
679 | | // Test a few known singular values. |
680 | | let mat = |a, b, c, d| Affine::new([a, b, c, d, 0., 0.]); |
681 | | |
682 | | let s = mat(1., 0., 0., 1.).svd().0; |
683 | | assert_near(s.to_point(), Point::new(1., 1.)); |
684 | | |
685 | | let s = mat(1., 0., 0., -1.).svd().0; |
686 | | assert_near(s.to_point(), Point::new(1., 1.)); |
687 | | |
688 | | let s = mat(1., 1., 1., 1.).svd().0; |
689 | | assert_near(s.to_point(), Point::new(2., 0.)); |
690 | | |
691 | | let s = mat(1., 1., 1., 1.).svd().0; |
692 | | assert_near(s.to_point(), Point::new(2., 0.)); |
693 | | |
694 | | let s = mat(0., 0., 1., 0.).svd().0; |
695 | | assert_near(s.to_point(), Point::new(1., 0.)); |
696 | | |
697 | | // The singular values are the scaling of the affine map. So let's test that. |
698 | | let s = Affine::scale_non_uniform(4., 8.) |
699 | | .then_rotate_about(42_f64.to_radians(), (-2., 50.)) |
700 | | .svd() |
701 | | .0; |
702 | | assert_near(s.to_point(), Point::new(8., 4.)); |
703 | | |
704 | | // Correctly handles negative scaling (singular values are necessarily non-negative). |
705 | | let s = Affine::scale_non_uniform(-20., 3.).svd().0; |
706 | | assert_near(s.to_point(), Point::new(20., 3.)); |
707 | | let s = Affine::scale_non_uniform(-20., -3.).svd().0; |
708 | | assert_near(s.to_point(), Point::new(20., 3.)); |
709 | | let s = Affine::scale_non_uniform(20., -3.).svd().0; |
710 | | assert_near(s.to_point(), Point::new(20., 3.)); |
711 | | |
712 | | // One more property: given a full-rank transform, the product of its singular values |
713 | | // should be equal to its absolute determinant. |
714 | | let m = mat(10., 9., -2.5, 3.3333); |
715 | | let s = m.svd().0; |
716 | | let prod = s.x * s.y; |
717 | | let det = m.determinant().abs(); |
718 | | assert!( |
719 | | (prod - det) < 1e-9, |
720 | | "The product of the singular values {s:?} ({prod}) should be equal to the absolute determinant {det}.", |
721 | | ); |
722 | | } |
723 | | } |