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}