Coverage Report

Created: 2025-12-31 07:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}