Skip to main content

kurbo/
affine.rs

1// Copyright 2018 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Affine transforms.
5
6use core::ops::{Mul, MulAssign};
7
8use crate::{Point, Rect, Vec2};
9
10#[cfg(not(feature = "std"))]
11use 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))]
17pub struct Affine([f64; 6]);
18
19impl 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    pub const fn new(c: [f64; 6]) -> Affine {
49        Affine(c)
50    }
51
52    /// An affine transform representing uniform scaling.
53    #[inline(always)]
54    pub const fn scale(s: f64) -> Affine {
55        Affine([s, 0.0, 0.0, s, 0.0, 0.0])
56    }
57
58    /// An affine transform representing non-uniform scaling
59    /// with different scale values for x and y
60    #[inline(always)]
61    pub const fn scale_non_uniform(s_x: f64, s_y: f64) -> Affine {
62        Affine([s_x, 0.0, 0.0, s_y, 0.0, 0.0])
63    }
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    pub fn scale_about(s: f64, center: impl Into<Point>) -> Affine {
73        let center = center.into().to_vec2();
74        Self::translate(-center)
75            .then_scale(s)
76            .then_translate(center)
77    }
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    pub fn rotate(th: f64) -> Affine {
89        let (s, c) = th.sin_cos();
90        Affine([c, s, -s, c, 0.0, 0.0])
91    }
92
93    /// An affine transform representing a rotation of `th` radians about `center`.
94    ///
95    /// See [`Affine::rotate()`] for more info.
96    #[inline]
97    pub fn rotate_about(th: f64, center: impl Into<Point>) -> Affine {
98        let center = center.into().to_vec2();
99        Self::translate(-center)
100            .then_rotate(th)
101            .then_translate(center)
102    }
103
104    /// An affine transform representing translation.
105    #[inline(always)]
106    pub fn translate<V: Into<Vec2>>(p: V) -> Affine {
107        let p = p.into();
108        Affine([1.0, 0.0, 0.0, 1.0, p.x, p.y])
109    }
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    pub const fn skew(skew_x: f64, skew_y: f64) -> Affine {
126        Affine([1.0, skew_y, skew_x, 1.0, 0.0, 0.0])
127    }
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    pub fn reflect(point: impl Into<Point>, direction: impl Into<Vec2>) -> Self {
148        let point = point.into();
149        let direction = direction.into();
150
151        let n = Vec2 {
152            x: direction.y,
153            y: -direction.x,
154        }
155        .normalize();
156
157        // Compute Householder reflection matrix
158        let x2 = n.x * n.x;
159        let xy = n.x * n.y;
160        let y2 = n.y * n.y;
161        // Here we also add in the post translation, because it doesn't require any further calc.
162        let aff = Affine::new([
163            1. - 2. * x2,
164            -2. * xy,
165            -2. * xy,
166            1. - 2. * y2,
167            point.x,
168            point.y,
169        ]);
170        aff.pre_translate(-point.to_vec2())
171    }
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    pub fn pre_rotate(self, th: f64) -> Self {
181        self * Affine::rotate(th)
182    }
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    pub fn pre_rotate_about(self, th: f64, center: impl Into<Point>) -> Self {
192        self * Affine::rotate_about(th, center)
193    }
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    pub fn pre_scale(self, scale: f64) -> Self {
203        self * Affine::scale(scale)
204    }
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    pub fn pre_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self {
214        self * Affine::scale_non_uniform(scale_x, scale_y)
215    }
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    pub fn pre_translate(self, trans: Vec2) -> Self {
225        self * Affine::translate(trans)
226    }
227
228    /// A [skew] of `(skew_x, skew_y)` followed by `self`.
229    ///
230    /// Equivalent to `self * Affine::skew(skew_x, skew_y)`
231    ///
232    /// [skew]: Affine::skew
233    #[inline]
234    #[must_use]
235    pub fn pre_skew(self, skew_x: f64, skew_y: f64) -> Self {
236        self * Affine::skew(skew_x, skew_y)
237    }
238
239    /// A [reflection] about the line through `point` in `direction` followed by `self`.
240    ///
241    /// Equivalent to `self * Affine::reflect(point, direction)`
242    ///
243    /// [reflection]: Affine::reflect
244    #[inline]
245    #[must_use]
246    pub fn pre_reflect(self, point: impl Into<Point>, direction: impl Into<Vec2>) -> Self {
247        self * Affine::reflect(point, direction)
248    }
249
250    /// `self` followed by a [rotation] of `th`.
251    ///
252    /// Equivalent to `Affine::rotate(th) * self`
253    ///
254    /// [rotation]: Affine::rotate
255    #[inline]
256    #[must_use]
257    pub fn then_rotate(self, th: f64) -> Self {
258        Affine::rotate(th) * self
259    }
260
261    /// `self` followed by a [rotation] of `th` about `center`.
262    ///
263    /// Equivalent to `Affine::rotate_about(th, center) * self`
264    ///
265    /// [rotation]: Affine::rotate_about
266    #[inline]
267    #[must_use]
268    pub fn then_rotate_about(self, th: f64, center: impl Into<Point>) -> Self {
269        Affine::rotate_about(th, center) * self
270    }
271
272    /// `self` followed by a [scale] of `scale`.
273    ///
274    /// Equivalent to `Affine::scale(scale) * self`
275    ///
276    /// [scale]: Affine::scale
277    #[inline]
278    #[must_use]
279    pub fn then_scale(self, scale: f64) -> Self {
280        Affine::scale(scale) * self
281    }
282
283    /// `self` followed by a [scale] of `(scale_x, scale_y)`.
284    ///
285    /// Equivalent to `Affine::scale_non_uniform(scale_x, scale_y) * self`
286    ///
287    /// [scale]: Affine::scale_non_uniform
288    #[inline]
289    #[must_use]
290    pub fn then_scale_non_uniform(self, scale_x: f64, scale_y: f64) -> Self {
291        Affine::scale_non_uniform(scale_x, scale_y) * self
292    }
293
294    /// `self` followed by a [scale] of `scale` about `center`.
295    ///
296    /// Equivalent to `Affine::scale_about(scale) * self`
297    ///
298    /// [scale]: Affine::scale_about
299    #[inline]
300    #[must_use]
301    pub fn then_scale_about(self, scale: f64, center: impl Into<Point>) -> Self {
302        Affine::scale_about(scale, center) * self
303    }
304
305    /// `self` followed by a [skew] of `(skew_x, skew_y)`.
306    ///
307    /// Equivalent to `Affine::skew(skew_x, skew_y) * self`
308    ///
309    /// [skew]: Affine::skew
310    #[inline]
311    #[must_use]
312    pub fn then_skew(self, skew_x: f64, skew_y: f64) -> Self {
313        Affine::skew(skew_x, skew_y) * self
314    }
315
316    /// `self` followed by a [reflection] about the line through `point` in `direction`.
317    ///
318    /// Equivalent to `Affine::reflect(point, direction) * self`
319    ///
320    /// [reflection]: Affine::reflect
321    #[inline]
322    #[must_use]
323    pub fn then_reflect(self, point: impl Into<Point>, direction: impl Into<Vec2>) -> Self {
324        Affine::reflect(point, direction) * self
325    }
326
327    /// `self` followed by a translation of `trans`.
328    ///
329    /// Equivalent to `Affine::translate(trans) * self`
330    ///
331    /// [translation]: Affine::translate
332    #[inline]
333    #[must_use]
334    pub const fn then_translate(mut self, trans: Vec2) -> Self {
335        self.0[4] += trans.x;
336        self.0[5] += trans.y;
337        self
338    }
339
340    /// Creates an affine transformation that takes the unit square to the given rectangle.
341    ///
342    /// Useful when you want to draw into the unit square but have your output fill any rectangle.
343    /// In this case push the `Affine` onto the transform stack.
344    pub const fn map_unit_square(rect: Rect) -> Affine {
345        Affine([rect.width(), 0., 0., rect.height(), rect.x0, rect.y0])
346    }
347
348    /// Get the coefficients of the transform.
349    #[inline(always)]
350    pub const fn as_coeffs(self) -> [f64; 6] {
351        self.0
352    }
353
354    /// Compute the determinant of this transform.
355    ///
356    /// # Geometric interpretation
357    ///
358    /// Consider a region transformed by this affine. The transformed region's area is the area of
359    /// the original region scaled by the absolute value of the determinant. A negative determinant
360    /// indicates orientation reversal.
361    #[inline]
362    pub const fn determinant(self) -> f64 {
363        self.0[0] * self.0[3] - self.0[1] * self.0[2]
364    }
365
366    /// Compute the square of the nuclear norm of this transform.
367    ///
368    /// This is the square of the [Schatten p-norm][schatten] with `p=1`, also known as the "trace norm."
369    ///
370    /// Returns the squared norm for efficiency; take the square root as necessary.
371    ///
372    /// # Geometric interpretation
373    ///
374    /// Consider a unit circle transformed by this affine. The nuclear norm is the sum of the
375    /// resulting ellipse's radii (semi axes). That sum multiplied by π is a first-order
376    /// approximation of the ellipse's perimeter.
377    ///
378    /// [schatten]: <https://en.wikipedia.org/w/index.php?title=Matrix_norm&oldid=1348997593#Schatten_norms>
379    #[inline]
380    pub const fn nuclear_norm_squared(self) -> f64 {
381        self.frobenius_norm_squared() + 2. * self.determinant().abs()
382    }
383
384    /// Compute the square of the Frobenius norm of this transform.
385    ///
386    /// This is the square of the [Schatten p-norm][schatten] with `p=2`.
387    ///
388    /// Returns the squared norm for efficiency; take the square root as necessary.
389    ///
390    /// # Geometric interpretation
391    ///
392    /// Consider a unit circle transformed by this affine. The squared Frobenius norm is twice the
393    /// mean squared radius of the resulting ellipse. Alternatively, it is equal to the squared
394    /// distance from the ellipse's center to a corner of the rectangle spanned by the ellipse's
395    /// axes.
396    ///
397    /// [schatten]: <https://en.wikipedia.org/w/index.php?title=Matrix_norm&oldid=1348997593#Schatten_norms>
398    #[inline]
399    pub const fn frobenius_norm_squared(self) -> f64 {
400        let [a, b, c, d, _, _] = self.as_coeffs();
401        a * a + b * b + c * c + d * d
402    }
403
404    /// Compute the spectral norm of this transform.
405    ///
406    /// This is the [Schatten p-norm][schatten] with `p=∞`.
407    ///
408    /// # Geometric interpretation
409    ///
410    /// Consider a unit circle transformed by this affine. The spectral norm is the major radius
411    /// (semi-major axis) of the Ellipse.
412    ///
413    /// [schatten]: <https://en.wikipedia.org/w/index.php?title=Matrix_norm&oldid=1348997593#Schatten_norms>
414    #[inline]
415    pub fn spectral_norm(self) -> f64 {
416        // Note a different calculation, returning the `_squared` form like our nuclear and
417        // Frobenius norms, could be `0.5 (frob^2 + sqrt(frob^4 - 4 det^2))`. In terms of operations
418        // it's a wash: one fewer sqrt if the user actually wants the squared form, but it uses more
419        // muls. More importantly, that form has worse numeric conditioning.
420        self.svd().0.x
421    }
422
423    /// Compute the inverse transform.
424    ///
425    /// Produces NaN values when the determinant is zero.
426    pub const fn inverse(self) -> Affine {
427        let inv_det = self.determinant().recip();
428        Affine([
429            inv_det * self.0[3],
430            -inv_det * self.0[1],
431            -inv_det * self.0[2],
432            inv_det * self.0[0],
433            inv_det * (self.0[2] * self.0[5] - self.0[3] * self.0[4]),
434            inv_det * (self.0[1] * self.0[4] - self.0[0] * self.0[5]),
435        ])
436    }
437
438    /// Compute the bounding box of a transformed rectangle.
439    ///
440    /// Returns the minimal `Rect` that encloses the given `Rect` after affine transformation.
441    /// If the transform is axis-aligned, then this bounding box is "tight", in other words the
442    /// returned `Rect` is the transformed rectangle.
443    ///
444    /// The returned rectangle always has non-negative width and height.
445    pub fn transform_rect_bbox(self, rect: Rect) -> Rect {
446        let p00 = self * Point::new(rect.x0, rect.y0);
447        let p01 = self * Point::new(rect.x0, rect.y1);
448        let p10 = self * Point::new(rect.x1, rect.y0);
449        let p11 = self * Point::new(rect.x1, rect.y1);
450        Rect::from_points(p00, p01).union(Rect::from_points(p10, p11))
451    }
452
453    /// Is this map [finite]?
454    ///
455    /// [finite]: f64::is_finite
456    #[inline]
457    pub const fn is_finite(&self) -> bool {
458        self.0[0].is_finite()
459            && self.0[1].is_finite()
460            && self.0[2].is_finite()
461            && self.0[3].is_finite()
462            && self.0[4].is_finite()
463            && self.0[5].is_finite()
464    }
465
466    /// Is this map [NaN]?
467    ///
468    /// [NaN]: f64::is_nan
469    #[inline]
470    pub const fn is_nan(&self) -> bool {
471        self.0[0].is_nan()
472            || self.0[1].is_nan()
473            || self.0[2].is_nan()
474            || self.0[3].is_nan()
475            || self.0[4].is_nan()
476            || self.0[5].is_nan()
477    }
478
479    /// Compute the singular value decomposition of the linear transformation (ignoring the
480    /// translation).
481    ///
482    /// All non-degenerate linear transformations can be represented as
483    ///
484    ///  1. a rotation about the origin.
485    ///  2. a scaling along the x and y axes
486    ///  3. another rotation about the origin
487    ///
488    /// composed together. Decomposing a 2x2 matrix in this way is called a "singular value
489    /// decomposition" and is written `U Σ V^T`, where U and V^T are orthogonal (rotations) and Σ
490    /// is a diagonal matrix (a scaling).
491    ///
492    /// Since currently this function is used to calculate ellipse radii and rotation from an
493    /// affine map on the unit circle, we don't calculate V^T, since a rotation of the unit (or
494    /// any) circle about its center always results in the same circle. This is the reason that an
495    /// ellipse mapped using an affine map is always an ellipse.
496    ///
497    /// Will return NaNs if the matrix (or equivalently the linear map) is non-finite.
498    ///
499    /// The first part of the returned tuple is the scaling, the second part is the angle of
500    /// rotation (in radians). The scaling along the x-axis is guaranteed to be greater than or
501    /// equal to the scaling along the y-axis.
502    //
503    // Note: though this does quite some computation, we are often interested only in specific
504    // components of the result. Hence this is marked `#[inline(always)]`, to give the compiler a
505    // good chance at eliminating dead code.
506    #[inline(always)]
507    pub(crate) fn svd(self) -> (Vec2, f64) {
508        let [a, b, c, d, _, _] = self.0;
509        let a2 = a * a;
510        let b2 = b * b;
511        let c2 = c * c;
512        let d2 = d * d;
513        let ab = a * b;
514        let cd = c * d;
515        let angle = 0.5 * (2.0 * (ab + cd)).atan2(a2 - b2 + c2 - d2);
516
517        // Given matrix A = [ a c ]
518        //                  [ b d ]
519        //
520        // The two singular values σ1, σ2 of A are the square roots of the two eigen values λ1, λ2
521        // of M = A^T A. The common formula for 2x2 eigenvalues requires evaluating a square root,
522        // but we'd like to compute the singular values of the matrix without nested square roots.
523        //
524        // M = A^T A = [ aa+cc   ab+cd ]
525        //             [ ab+cd   bb+dd ]
526        //
527        // We have
528        // λ = 1/2 (tr(M) ± sqrt(tr(M)^2 - 4 det(M))).
529        //
530        // Note det(M) = det(A^T A) = det(A)^2.
531        // => 2λ = tr(M) ± sqrt(tr(M)^2 - 4 det(A)^2)
532        // => 2λ = tr(M) ± sqrt[(a^2+b^2+c^2+d^2)^2 - 4 (ad-bc)^2]
533        // By factorizing the inner term,
534        // => 2λ = tr(M) ± sqrt[((a+d)^2 + (b-c)^2) ((a-d)^2 + (b+c)^2)]
535        // => 2λ = tr(M) ± sqrt[(a+d)^2 + (b-c)^2] sqrt[(a-d)^2 + (b+c)^2]
536        //
537        // Define S1 = sqrt[(a+d)^2 + (b-c)^2]
538        //        S2 = sqrt[(a-d)^2 + (b+c)^2].
539        //
540        // => 2λ = tr(M) ± S1 S2
541        // => 2λ = 1/2 (S1^2 + S2^2) ± S1 S2
542        // => λ = 1/4 (S1^2 + S2^2 ± 2 S1 S2)
543        // => λ = 1/4 (S1 ± S2)^2
544        //
545        // Note we're interested in
546        // σ = sqrt(λ).
547        //
548        // => σ1 = 1/2 (S1 + S2)
549        // and similarly σ2 = 1/2 |S1 - S2|
550        let s1 = ((a + d).powi(2) + (b - c).powi(2)).sqrt();
551        let s2 = ((a - d).powi(2) + (b + c).powi(2)).sqrt();
552        (
553            Vec2 {
554                x: 0.5 * (s1 + s2),
555                y: 0.5 * (s1 - s2).abs(),
556            },
557            angle,
558        )
559    }
560
561    /// Returns the translation part of this affine map (`(self.0[4], self.0[5])`).
562    #[inline(always)]
563    pub const fn translation(self) -> Vec2 {
564        Vec2 {
565            x: self.0[4],
566            y: self.0[5],
567        }
568    }
569
570    /// Replaces the translation portion of this affine map
571    ///
572    /// The translation can be seen as being applied after the linear part of the map.
573    #[must_use]
574    #[inline(always)]
575    pub const fn with_translation(mut self, trans: Vec2) -> Affine {
576        self.0[4] = trans.x;
577        self.0[5] = trans.y;
578        self
579    }
580}
581
582impl Default for Affine {
583    #[inline(always)]
584    fn default() -> Affine {
585        Affine::IDENTITY
586    }
587}
588
589impl Mul<Point> for Affine {
590    type Output = Point;
591
592    #[inline]
593    fn mul(self, other: Point) -> Point {
594        Point::new(
595            self.0[0] * other.x + self.0[2] * other.y + self.0[4],
596            self.0[1] * other.x + self.0[3] * other.y + self.0[5],
597        )
598    }
599}
600
601impl Mul for Affine {
602    type Output = Affine;
603
604    #[inline]
605    fn mul(self, other: Affine) -> Affine {
606        Affine([
607            self.0[0] * other.0[0] + self.0[2] * other.0[1],
608            self.0[1] * other.0[0] + self.0[3] * other.0[1],
609            self.0[0] * other.0[2] + self.0[2] * other.0[3],
610            self.0[1] * other.0[2] + self.0[3] * other.0[3],
611            self.0[0] * other.0[4] + self.0[2] * other.0[5] + self.0[4],
612            self.0[1] * other.0[4] + self.0[3] * other.0[5] + self.0[5],
613        ])
614    }
615}
616
617impl MulAssign for Affine {
618    #[inline]
619    fn mul_assign(&mut self, other: Affine) {
620        *self = self.mul(other);
621    }
622}
623
624impl Mul<Affine> for f64 {
625    type Output = Affine;
626
627    #[inline]
628    fn mul(self, other: Affine) -> Affine {
629        Affine([
630            self * other.0[0],
631            self * other.0[1],
632            self * other.0[2],
633            self * other.0[3],
634            self * other.0[4],
635            self * other.0[5],
636        ])
637    }
638}
639
640// Conversions to and from mint
641#[cfg(feature = "mint")]
642impl From<Affine> for mint::ColumnMatrix2x3<f64> {
643    #[inline(always)]
644    fn from(a: Affine) -> mint::ColumnMatrix2x3<f64> {
645        mint::ColumnMatrix2x3 {
646            x: mint::Vector2 {
647                x: a.0[0],
648                y: a.0[1],
649            },
650            y: mint::Vector2 {
651                x: a.0[2],
652                y: a.0[3],
653            },
654            z: mint::Vector2 {
655                x: a.0[4],
656                y: a.0[5],
657            },
658        }
659    }
660}
661
662#[cfg(feature = "mint")]
663impl From<mint::ColumnMatrix2x3<f64>> for Affine {
664    #[inline(always)]
665    fn from(m: mint::ColumnMatrix2x3<f64>) -> Affine {
666        Affine([m.x.x, m.x.y, m.y.x, m.y.y, m.z.x, m.z.y])
667    }
668}
669
670#[cfg(test)]
671mod tests {
672    use crate::{Affine, Point, Vec2};
673    use std::f64::consts::PI;
674
675    fn assert_near(p0: Point, p1: Point) {
676        assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
677    }
678
679    fn affine_assert_near(a0: Affine, a1: Affine) {
680        for i in 0..6 {
681            assert!((a0.0[i] - a1.0[i]).abs() < 1e-9, "{a0:?} != {a1:?}");
682        }
683    }
684
685    #[test]
686    fn affine_basic() {
687        let p = Point::new(3.0, 4.0);
688
689        assert_near(Affine::default() * p, p);
690        assert_near(Affine::scale(2.0) * p, Point::new(6.0, 8.0));
691        assert_near(Affine::rotate(0.0) * p, p);
692        assert_near(Affine::rotate(PI / 2.0) * p, Point::new(-4.0, 3.0));
693        assert_near(Affine::translate((5.0, 6.0)) * p, Point::new(8.0, 10.0));
694        assert_near(Affine::skew(0.0, 0.0) * p, p);
695        assert_near(Affine::skew(2.0, 4.0) * p, Point::new(11.0, 16.0));
696    }
697
698    #[test]
699    fn affine_mul() {
700        let a1 = Affine::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
701        let a2 = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
702
703        let px = Point::new(1.0, 0.0);
704        let py = Point::new(0.0, 1.0);
705        let pxy = Point::new(1.0, 1.0);
706        assert_near(a1 * (a2 * px), (a1 * a2) * px);
707        assert_near(a1 * (a2 * py), (a1 * a2) * py);
708        assert_near(a1 * (a2 * pxy), (a1 * a2) * pxy);
709    }
710
711    #[test]
712    fn affine_inv() {
713        let a = Affine::new([0.1, 1.2, 2.3, 3.4, 4.5, 5.6]);
714        let a_inv = a.inverse();
715
716        let px = Point::new(1.0, 0.0);
717        let py = Point::new(0.0, 1.0);
718        let pxy = Point::new(1.0, 1.0);
719        assert_near(a * (a_inv * px), px);
720        assert_near(a * (a_inv * py), py);
721        assert_near(a * (a_inv * pxy), pxy);
722        assert_near(a_inv * (a * px), px);
723        assert_near(a_inv * (a * py), py);
724        assert_near(a_inv * (a * pxy), pxy);
725    }
726
727    #[test]
728    fn reflection() {
729        affine_assert_near(
730            Affine::reflect(Point::ZERO, (1., 0.)),
731            Affine::new([1., 0., 0., -1., 0., 0.]),
732        );
733        affine_assert_near(
734            Affine::reflect(Point::ZERO, (0., 1.)),
735            Affine::new([-1., 0., 0., 1., 0., 0.]),
736        );
737        // y = x
738        affine_assert_near(
739            Affine::reflect(Point::ZERO, (1., 1.)),
740            Affine::new([0., 1., 1., 0., 0., 0.]),
741        );
742
743        // no translate
744        let point = Point::new(0., 0.);
745        let vec = Vec2::new(1., 1.);
746        let map = Affine::reflect(point, vec);
747        assert_near(map * Point::new(0., 0.), Point::new(0., 0.));
748        assert_near(map * Point::new(1., 1.), Point::new(1., 1.));
749        assert_near(map * Point::new(1., 2.), Point::new(2., 1.));
750
751        // with translate
752        let point = Point::new(1., 0.);
753        let vec = Vec2::new(1., 1.);
754        let map = Affine::reflect(point, vec);
755        assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
756        assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
757        assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
758    }
759
760    #[test]
761    fn svd() {
762        let a = Affine::new([1., 2., 3., 4., 5., 6.]);
763        let a_no_translate = a.with_translation(Vec2::ZERO);
764
765        // translation should have no effect
766        let (scale, rotation) = a.svd();
767        let (scale_no_translate, rotation_no_translate) = a_no_translate.svd();
768        assert_near(scale.to_point(), scale_no_translate.to_point());
769        assert!((rotation - rotation_no_translate).abs() <= 1e-9);
770
771        assert_near(
772            scale.to_point(),
773            Point::new(5.4649857042190427, 0.36596619062625782),
774        );
775        assert!((rotation - 0.95691013360780001).abs() <= 1e-9);
776
777        // singular affine
778        let a = Affine::new([0., 0., 0., 0., 5., 6.]);
779        assert_eq!(a.determinant(), 0.);
780        let (scale, rotation) = a.svd();
781        assert_eq!(scale, Vec2::new(0., 0.));
782        assert_eq!(rotation, 0.);
783    }
784
785    #[test]
786    fn svd_singular_values() {
787        // Test a few known singular values.
788        let mat = |a, b, c, d| Affine::new([a, b, c, d, 0., 0.]);
789
790        let s = mat(1., 0., 0., 1.).svd().0;
791        assert_near(s.to_point(), Point::new(1., 1.));
792
793        let s = mat(1., 0., 0., -1.).svd().0;
794        assert_near(s.to_point(), Point::new(1., 1.));
795
796        let s = mat(1., 1., 1., 1.).svd().0;
797        assert_near(s.to_point(), Point::new(2., 0.));
798
799        let s = mat(1., 1., 1., 1.).svd().0;
800        assert_near(s.to_point(), Point::new(2., 0.));
801
802        let s = mat(0., 0., 1., 0.).svd().0;
803        assert_near(s.to_point(), Point::new(1., 0.));
804
805        // The singular values are the scaling of the affine map. So let's test that.
806        let s = Affine::scale_non_uniform(4., 8.)
807            .then_rotate_about(42_f64.to_radians(), (-2., 50.))
808            .svd()
809            .0;
810        assert_near(s.to_point(), Point::new(8., 4.));
811
812        // Correctly handles negative scaling (singular values are necessarily non-negative).
813        let s = Affine::scale_non_uniform(-20., 3.).svd().0;
814        assert_near(s.to_point(), Point::new(20., 3.));
815        let s = Affine::scale_non_uniform(-20., -3.).svd().0;
816        assert_near(s.to_point(), Point::new(20., 3.));
817        let s = Affine::scale_non_uniform(20., -3.).svd().0;
818        assert_near(s.to_point(), Point::new(20., 3.));
819
820        // One more property: given a full-rank transform, the product of its singular values
821        // should be equal to its absolute determinant.
822        let m = mat(10., 9., -2.5, 3.3333);
823        let s = m.svd().0;
824        let prod = s.x * s.y;
825        let det = m.determinant().abs();
826        assert!(
827            (prod - det) < 1e-9,
828            "The product of the singular values {s:?} ({prod}) should be equal to the absolute determinant {det}.",
829        );
830    }
831
832    #[test]
833    fn rotate_about_composition() {
834        let theta = core::f64::consts::FRAC_PI_2;
835        let center = Point::new(-1., 0.);
836        let translation = Vec2::new(0., 1.);
837        let probe = Point::ORIGIN;
838
839        let rotate_about = Affine::rotate_about(theta, center);
840        let translate = Affine::translate(translation);
841
842        // Establish baselines with raw matrix composition
843        // (also a sanity check to ensure the order of ops matters for this contrived test)
844        let rotate_then_translate = translate * rotate_about;
845        let translate_then_rotate = rotate_about * translate;
846        assert_near(rotate_then_translate * probe, Point::new(-1., 2.));
847        assert_near(translate_then_rotate * probe, Point::new(-2., 1.));
848
849        // Check .then_* semantics
850        affine_assert_near(
851            rotate_about.then_translate(translation),
852            rotate_then_translate,
853        );
854        affine_assert_near(
855            translate.then_rotate_about(theta, center),
856            translate_then_rotate,
857        );
858
859        // Check .pre_rotate_about semantics
860        affine_assert_near(
861            translate.pre_rotate_about(theta, center),
862            rotate_then_translate,
863        );
864    }
865}