Skip to main content

euclid/
transform2d.rs

1// Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10#![allow(clippy::just_underscores_and_digits)]
11
12use crate::box2d::Box2D;
13use crate::num::{One, Zero};
14use crate::point::{point2, Point2D};
15use crate::rect::Rect;
16use crate::scale::Scale;
17use crate::transform3d::Transform3D;
18use crate::translation::Translation2D;
19use crate::vector::{vec2, Vector2D};
20use crate::UnknownUnit;
21use core::cmp::{Eq, PartialEq};
22use core::fmt;
23use core::hash::Hash;
24use core::marker::PhantomData;
25use core::ops::{Add, Div, Mul, Neg, Sub};
26
27#[cfg(feature = "bytemuck")]
28use bytemuck::{Pod, Zeroable};
29#[cfg(feature = "malloc_size_of")]
30use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
31#[cfg(feature = "mint")]
32use mint;
33use num_traits::{NumCast, Signed};
34#[cfg(feature = "serde")]
35use serde::{Deserialize, Serialize};
36
37/// A 2d transform represented by a column-major 3 by 3 matrix, compressed down to 3 by 2.
38///
39/// Transforms can be parametrized over the source and destination units, to describe a
40/// transformation from a space to another.
41/// For example, `Transform2D<f32, WorldSpace, ScreenSpace>::transform_point4d`
42/// takes a `Point2D<f32, WorldSpace>` and returns a `Point2D<f32, ScreenSpace>`.
43///
44/// Transforms expose a set of convenience methods for pre- and post-transformations.
45/// Pre-transformations (`pre_*` methods) correspond to adding an operation that is
46/// applied before the rest of the transformation, while post-transformations (`then_*`
47/// methods) add an operation that is applied after.
48///
49/// The matrix representation is conceptually equivalent to a 3 by 3 matrix transformation
50/// compressed to 3 by 2 with the components that aren't needed to describe the set of 2d
51/// transformations we are interested in implicitly defined:
52///
53/// ```text
54///  | m11 m21 m31 |   |x|   |x'|
55///  | m12 m22 m32 | x |y| = |y'|
56///  |   0   0   1 |   |1|   |1 |
57/// ```
58///
59/// When translating `Transform2D` into general matrix representations, consider that the
60/// representation follows the column-major notation with column vectors.
61///
62/// The translation terms are `m31` and `m32`.
63#[repr(C)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65#[cfg_attr(
66    feature = "serde",
67    serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
68)]
69#[rustfmt::skip]
70pub struct Transform2D<T, Src, Dst> {
71    pub m11: T, pub m12: T,
72    pub m21: T, pub m22: T,
73    pub m31: T, pub m32: T,
74    #[doc(hidden)]
75    pub _unit: PhantomData<(Src, Dst)>,
76}
77
78#[cfg(feature = "arbitrary")]
79impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for Transform2D<T, Src, Dst>
80where
81    T: arbitrary::Arbitrary<'a>,
82{
83    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
84        let (m11, m12, m21, m22, m31, m32) = arbitrary::Arbitrary::arbitrary(u)?;
85        Ok(Transform2D {
86            m11,
87            m12,
88            m21,
89            m22,
90            m31,
91            m32,
92            _unit: PhantomData,
93        })
94    }
95}
96
97#[cfg(feature = "bytemuck")]
98unsafe impl<T: Zeroable, Src, Dst> Zeroable for Transform2D<T, Src, Dst> {}
99
100#[cfg(feature = "bytemuck")]
101unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for Transform2D<T, Src, Dst> {}
102
103#[cfg(feature = "malloc_size_of")]
104impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for Transform2D<T, Src, Dst> {
105    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
106        self.m11.size_of(ops)
107            + self.m12.size_of(ops)
108            + self.m21.size_of(ops)
109            + self.m22.size_of(ops)
110            + self.m31.size_of(ops)
111            + self.m32.size_of(ops)
112    }
113}
114
115impl<T: Copy, Src, Dst> Copy for Transform2D<T, Src, Dst> {}
116
117impl<T: Clone, Src, Dst> Clone for Transform2D<T, Src, Dst> {
118    fn clone(&self) -> Self {
119        Transform2D {
120            m11: self.m11.clone(),
121            m12: self.m12.clone(),
122            m21: self.m21.clone(),
123            m22: self.m22.clone(),
124            m31: self.m31.clone(),
125            m32: self.m32.clone(),
126            _unit: PhantomData,
127        }
128    }
129}
130
131impl<T, Src, Dst> Eq for Transform2D<T, Src, Dst> where T: Eq {}
132
133impl<T, Src, Dst> PartialEq for Transform2D<T, Src, Dst>
134where
135    T: PartialEq,
136{
137    fn eq(&self, other: &Self) -> bool {
138        self.m11 == other.m11
139            && self.m12 == other.m12
140            && self.m21 == other.m21
141            && self.m22 == other.m22
142            && self.m31 == other.m31
143            && self.m32 == other.m32
144    }
145}
146
147impl<T, Src, Dst> Hash for Transform2D<T, Src, Dst>
148where
149    T: Hash,
150{
151    fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
152        self.m11.hash(h);
153        self.m12.hash(h);
154        self.m21.hash(h);
155        self.m22.hash(h);
156        self.m31.hash(h);
157        self.m32.hash(h);
158    }
159}
160
161impl<T, Src, Dst> Transform2D<T, Src, Dst> {
162    /// Create a transform specifying its components in using the column-major-column-vector
163    /// matrix notation.
164    ///
165    /// For example, the translation terms m31 and m32 are the last two parameters parameters.
166    ///
167    /// ```
168    /// use euclid::default::Transform2D;
169    /// let tx = 1.0;
170    /// let ty = 2.0;
171    /// let translation = Transform2D::new(
172    ///   1.0, 0.0,
173    ///   0.0, 1.0,
174    ///   tx,  ty,
175    /// );
176    /// ```
177    #[rustfmt::skip]
178    pub const fn new(m11: T, m12: T, m21: T, m22: T, m31: T, m32: T) -> Self {
179        Transform2D {
180            m11, m12,
181            m21, m22,
182            m31, m32,
183            _unit: PhantomData,
184        }
185    }
186}
187
188impl<T: Copy, Src, Dst> Transform2D<T, Src, Dst> {
189    /// Returns an array containing this transform's terms.
190    ///
191    /// The terms are laid out in the same order as they are
192    /// specified in [`Transform2D::new`], that is following the
193    /// column-major-column-vector matrix notation.
194    ///
195    /// For example the translation terms are found in the
196    /// last two slots of the array.
197    #[inline]
198    #[rustfmt::skip]
199    pub fn to_array(&self) -> [T; 6] {
200        [
201            self.m11, self.m12,
202            self.m21, self.m22,
203            self.m31, self.m32
204        ]
205    }
206
207    /// Returns an array containing this transform's terms transposed.
208    ///
209    /// The terms are laid out in transposed order from the same order of
210    /// `Transform3D::new` and `Transform3D::to_array`, that is following
211    /// the row-major-column-vector matrix notation.
212    ///
213    /// For example the translation terms are found at indices 2 and 5
214    /// in the array.
215    #[inline]
216    #[rustfmt::skip]
217    pub fn to_array_transposed(&self) -> [T; 6] {
218        [
219            self.m11, self.m21, self.m31,
220            self.m12, self.m22, self.m32
221        ]
222    }
223
224    /// Equivalent to `to_array` with elements packed two at a time
225    /// in an array of arrays.
226    #[inline]
227    pub fn to_arrays(&self) -> [[T; 2]; 3] {
228        [
229            [self.m11, self.m12],
230            [self.m21, self.m22],
231            [self.m31, self.m32],
232        ]
233    }
234
235    /// Create a transform providing its components via an array
236    /// of 6 elements instead of as individual parameters.
237    ///
238    /// The order of the components corresponds to the
239    /// column-major-column-vector matrix notation (the same order
240    /// as `Transform2D::new`).
241    #[inline]
242    #[rustfmt::skip]
243    pub fn from_array(array: [T; 6]) -> Self {
244        Self::new(
245            array[0], array[1],
246            array[2], array[3],
247            array[4], array[5],
248        )
249    }
250
251    /// Equivalent to `from_array` with elements packed two at a time
252    /// in an array of arrays.
253    ///
254    /// The order of the components corresponds to the
255    /// column-major-column-vector matrix notation (the same order
256    /// as `Transform3D::new`).
257    #[inline]
258    #[rustfmt::skip]
259    pub fn from_arrays(array: [[T; 2]; 3]) -> Self {
260        Self::new(
261            array[0][0], array[0][1],
262            array[1][0], array[1][1],
263            array[2][0], array[2][1],
264        )
265    }
266
267    /// Drop the units, preserving only the numeric value.
268    #[inline]
269    #[rustfmt::skip]
270    pub fn to_untyped(&self) -> Transform2D<T, UnknownUnit, UnknownUnit> {
271        Transform2D::new(
272            self.m11, self.m12,
273            self.m21, self.m22,
274            self.m31, self.m32
275        )
276    }
277
278    /// Tag a unitless value with units.
279    #[inline]
280    #[rustfmt::skip]
281    pub fn from_untyped(p: &Transform2D<T, UnknownUnit, UnknownUnit>) -> Self {
282        Transform2D::new(
283            p.m11, p.m12,
284            p.m21, p.m22,
285            p.m31, p.m32
286        )
287    }
288
289    /// Returns the same transform with a different source unit.
290    #[inline]
291    #[rustfmt::skip]
292    pub fn with_source<NewSrc>(&self) -> Transform2D<T, NewSrc, Dst> {
293        Transform2D::new(
294            self.m11, self.m12,
295            self.m21, self.m22,
296            self.m31, self.m32,
297        )
298    }
299
300    /// Returns the same transform with a different destination unit.
301    #[inline]
302    #[rustfmt::skip]
303    pub fn with_destination<NewDst>(&self) -> Transform2D<T, Src, NewDst> {
304        Transform2D::new(
305            self.m11, self.m12,
306            self.m21, self.m22,
307            self.m31, self.m32,
308        )
309    }
310
311    /// Create a 3D transform from the current transform
312    #[inline]
313    pub fn to_3d(&self) -> Transform3D<T, Src, Dst>
314    where
315        T: Zero + One,
316    {
317        Transform3D::new_2d(self.m11, self.m12, self.m21, self.m22, self.m31, self.m32)
318    }
319
320    /// Returns true if self can be represented as a 2d scale+offset
321    /// transform.
322    #[inline]
323    pub fn is_scale_offset_eps(&self, epsilon: T) -> bool
324    where
325        T: Signed + One + PartialOrd,
326    {
327        (self.m12.abs() < epsilon) & (self.m21.abs() < epsilon)
328    }
329
330    /// Creates a 2D scale+offset transform from the current transform.
331    ///
332    /// This method assumes that self can be represented as a 2d scale+offset
333    /// transformation, callers should check that [`is_scale_offset_2d`] or
334    /// [`is_scale_offset_2d_eps`] returns `true` beforehand.
335    #[inline]
336    pub fn to_scale_offset(&self) -> ScaleOffset2D<T, Src, Dst> {
337        ScaleOffset2D::new(self.m11, self.m22, self.m31, self.m32)
338    }
339}
340
341impl<T: NumCast + Copy, Src, Dst> Transform2D<T, Src, Dst> {
342    /// Cast from one numeric representation to another, preserving the units.
343    #[inline]
344    pub fn cast<NewT: NumCast>(&self) -> Transform2D<NewT, Src, Dst> {
345        self.try_cast().unwrap()
346    }
347
348    /// Fallible cast from one numeric representation to another, preserving the units.
349    #[rustfmt::skip]
350    pub fn try_cast<NewT: NumCast>(&self) -> Option<Transform2D<NewT, Src, Dst>> {
351        match (NumCast::from(self.m11), NumCast::from(self.m12),
352               NumCast::from(self.m21), NumCast::from(self.m22),
353               NumCast::from(self.m31), NumCast::from(self.m32)) {
354            (Some(m11), Some(m12),
355             Some(m21), Some(m22),
356             Some(m31), Some(m32)) => {
357                Some(Transform2D::new(
358                    m11, m12,
359                    m21, m22,
360                    m31, m32
361                ))
362            },
363            _ => None
364        }
365    }
366}
367
368impl<T, Src, Dst> Transform2D<T, Src, Dst>
369where
370    T: Zero + One,
371{
372    /// Create an identity matrix:
373    ///
374    /// ```text
375    /// 1 0
376    /// 0 1
377    /// 0 0
378    /// ```
379    #[inline]
380    pub fn identity() -> Self {
381        Self::translation(T::zero(), T::zero())
382    }
383
384    /// Intentional not public, because it checks for exact equivalence
385    /// while most consumers will probably want some sort of approximate
386    /// equivalence to deal with floating-point errors.
387    fn is_identity(&self) -> bool
388    where
389        T: PartialEq,
390    {
391        *self == Self::identity()
392    }
393}
394
395/// Methods for combining generic transformations
396impl<T, Src, Dst> Transform2D<T, Src, Dst>
397where
398    T: Copy + Add<Output = T> + Mul<Output = T>,
399{
400    /// Returns the multiplication of the two matrices such that mat's transformation
401    /// applies after self's transformation.
402    #[must_use]
403    #[rustfmt::skip]
404    pub fn then<NewDst>(&self, mat: &Transform2D<T, Dst, NewDst>) -> Transform2D<T, Src, NewDst> {
405        Transform2D::new(
406            self.m11 * mat.m11 + self.m12 * mat.m21,
407            self.m11 * mat.m12 + self.m12 * mat.m22,
408
409            self.m21 * mat.m11 + self.m22 * mat.m21,
410            self.m21 * mat.m12 + self.m22 * mat.m22,
411
412            self.m31 * mat.m11 + self.m32 * mat.m21 + mat.m31,
413            self.m31 * mat.m12 + self.m32 * mat.m22 + mat.m32,
414        )
415    }
416}
417
418/// Methods for creating and combining translation transformations
419impl<T, Src, Dst> Transform2D<T, Src, Dst>
420where
421    T: Zero + One,
422{
423    /// Create a 2d translation transform:
424    ///
425    /// ```text
426    /// 1 0
427    /// 0 1
428    /// x y
429    /// ```
430    #[inline]
431    #[rustfmt::skip]
432    pub fn translation(x: T, y: T) -> Self {
433        let _0 = || T::zero();
434        let _1 = || T::one();
435
436        Self::new(
437            _1(), _0(),
438            _0(), _1(),
439             x,    y,
440        )
441    }
442
443    /// Applies a translation after self's transformation and returns the resulting transform.
444    #[inline]
445    #[must_use]
446    pub fn then_translate(&self, v: Vector2D<T, Dst>) -> Self
447    where
448        T: Copy + Add<Output = T> + Mul<Output = T>,
449    {
450        self.then(&Transform2D::translation(v.x, v.y))
451    }
452
453    /// Applies a translation before self's transformation and returns the resulting transform.
454    #[inline]
455    #[must_use]
456    pub fn pre_translate(&self, v: Vector2D<T, Src>) -> Self
457    where
458        T: Copy + Add<Output = T> + Mul<Output = T>,
459    {
460        Transform2D::translation(v.x, v.y).then(self)
461    }
462}
463
464/// Methods for creating and combining scale transformations
465impl<T, Src, Dst> Transform2D<T, Src, Dst> {
466    /// Create a 2d scale transform:
467    ///
468    /// ```text
469    /// x 0
470    /// 0 y
471    /// 0 0
472    /// ```
473    #[inline]
474    #[rustfmt::skip]
475    pub fn scale(x: T, y: T) -> Self
476    where
477        T: Zero,
478    {
479        let _0 = || Zero::zero();
480
481        Self::new(
482             x,   _0(),
483            _0(),  y,
484            _0(), _0(),
485        )
486    }
487
488    /// Applies a scale after self's transformation and returns the resulting transform.
489    #[inline]
490    #[must_use]
491    pub fn then_scale(&self, x: T, y: T) -> Self
492    where
493        T: Copy + Add<Output = T> + Mul<Output = T> + Zero,
494    {
495        self.then(&Transform2D::scale(x, y))
496    }
497
498    /// Applies a scale before self's transformation and returns the resulting transform.
499    #[inline]
500    #[must_use]
501    #[rustfmt::skip]
502    pub fn pre_scale(&self, x: T, y: T) -> Self
503    where
504        T: Copy + Mul<Output = T>,
505    {
506        Transform2D::new(
507            self.m11 * x, self.m12 * x,
508            self.m21 * y, self.m22 * y,
509            self.m31,     self.m32
510        )
511    }
512}
513
514/// Methods for apply transformations to objects
515impl<T, Src, Dst> Transform2D<T, Src, Dst>
516where
517    T: Copy + Add<Output = T> + Mul<Output = T>,
518{
519    /// Returns the given point transformed by this transform.
520    #[inline]
521    #[must_use]
522    pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T, Dst> {
523        Point2D::new(
524            point.x * self.m11 + point.y * self.m21 + self.m31,
525            point.x * self.m12 + point.y * self.m22 + self.m32,
526        )
527    }
528
529    /// Returns the given vector transformed by this matrix.
530    #[inline]
531    #[must_use]
532    pub fn transform_vector(&self, vec: Vector2D<T, Src>) -> Vector2D<T, Dst> {
533        vec2(
534            vec.x * self.m11 + vec.y * self.m21,
535            vec.x * self.m12 + vec.y * self.m22,
536        )
537    }
538
539    /// Returns a rectangle that encompasses the result of transforming the given rectangle by this
540    /// transform.
541    #[inline]
542    #[must_use]
543    pub fn outer_transformed_rect(&self, rect: &Rect<T, Src>) -> Rect<T, Dst>
544    where
545        T: Sub<Output = T> + Zero + PartialOrd,
546    {
547        let min = rect.min();
548        let max = rect.max();
549        Rect::from_points(&[
550            self.transform_point(min),
551            self.transform_point(max),
552            self.transform_point(point2(max.x, min.y)),
553            self.transform_point(point2(min.x, max.y)),
554        ])
555    }
556
557    /// Returns a box that encompasses the result of transforming the given box by this
558    /// transform.
559    #[inline]
560    #[must_use]
561    pub fn outer_transformed_box(&self, b: &Box2D<T, Src>) -> Box2D<T, Dst>
562    where
563        T: Sub<Output = T> + Zero + PartialOrd,
564    {
565        Box2D::from_points(&[
566            self.transform_point(b.min),
567            self.transform_point(b.max),
568            self.transform_point(point2(b.max.x, b.min.y)),
569            self.transform_point(point2(b.min.x, b.max.y)),
570        ])
571    }
572}
573
574impl<T, Src, Dst> Transform2D<T, Src, Dst>
575where
576    T: Copy + Sub<Output = T> + Mul<Output = T> + Div<Output = T> + PartialEq + Zero + One,
577{
578    /// Computes and returns the determinant of this transform.
579    pub fn determinant(&self) -> T {
580        self.m11 * self.m22 - self.m12 * self.m21
581    }
582
583    /// Returns whether it is possible to compute the inverse transform.
584    #[inline]
585    pub fn is_invertible(&self) -> bool {
586        self.determinant() != Zero::zero()
587    }
588
589    /// Returns the inverse transform if possible.
590    #[must_use]
591    pub fn inverse(&self) -> Option<Transform2D<T, Dst, Src>> {
592        let det = self.determinant();
593
594        let _0: T = Zero::zero();
595        let _1: T = One::one();
596
597        if det == _0 {
598            return None;
599        }
600
601        let inv_det = _1 / det;
602        Some(Transform2D::new(
603            inv_det * self.m22,
604            inv_det * (_0 - self.m12),
605            inv_det * (_0 - self.m21),
606            inv_det * self.m11,
607            inv_det * (self.m21 * self.m32 - self.m22 * self.m31),
608            inv_det * (self.m31 * self.m12 - self.m11 * self.m32),
609        ))
610    }
611}
612
613impl<T, Src, Dst> Default for Transform2D<T, Src, Dst>
614where
615    T: Zero + One,
616{
617    /// Returns the [identity transform](Transform2D::identity).
618    fn default() -> Self {
619        Self::identity()
620    }
621}
622
623impl<T, Src, Dst> fmt::Debug for Transform2D<T, Src, Dst>
624where
625    T: Copy + fmt::Debug + PartialEq + One + Zero,
626{
627    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
628        if self.is_identity() {
629            write!(f, "[I]")
630        } else {
631            self.to_array().fmt(f)
632        }
633    }
634}
635
636#[cfg(feature = "mint")]
637impl<T, Src, Dst> From<mint::RowMatrix3x2<T>> for Transform2D<T, Src, Dst> {
638    #[rustfmt::skip]
639    fn from(m: mint::RowMatrix3x2<T>) -> Self {
640        Transform2D {
641            m11: m.x.x, m12: m.x.y,
642            m21: m.y.x, m22: m.y.y,
643            m31: m.z.x, m32: m.z.y,
644            _unit: PhantomData,
645        }
646    }
647}
648#[cfg(feature = "mint")]
649impl<T, Src, Dst> From<Transform2D<T, Src, Dst>> for mint::RowMatrix3x2<T> {
650    fn from(t: Transform2D<T, Src, Dst>) -> Self {
651        mint::RowMatrix3x2 {
652            x: mint::Vector2 { x: t.m11, y: t.m12 },
653            y: mint::Vector2 { x: t.m21, y: t.m22 },
654            z: mint::Vector2 { x: t.m31, y: t.m32 },
655        }
656    }
657}
658
659impl<T: Copy + Zero, Src, Dst> From<ScaleOffset2D<T, Src, Dst>> for Transform2D<T, Src, Dst> {
660    fn from(t: ScaleOffset2D<T, Src, Dst>) -> Self {
661        t.to_transform2d()
662    }
663}
664
665impl<T: Copy + Zero, Src, Dst> From<Scale<T, Src, Dst>> for Transform2D<T, Src, Dst> {
666    fn from(s: Scale<T, Src, Dst>) -> Self {
667        Transform2D::scale(s.0, s.0)
668    }
669}
670
671#[cfg(any(feature = "std", feature = "libm"))]
672mod transform2d_float {
673    use super::Transform2D;
674    use crate::approxeq::ApproxEq;
675    use crate::num::{One, Zero};
676    use crate::{Angle, Rotation2D, Trig};
677    use core::ops::{Add, Mul, Sub};
678    use num_traits::Signed;
679
680    impl<T, Src, Dst> Transform2D<T, Src, Dst> {
681        /// Returns `true` if this transform is approximately equal to the other one, using
682        /// `T`'s default epsilon value.
683        ///
684        /// The same as [`ApproxEq::approx_eq`] but available without importing trait.
685        #[inline]
686        pub fn approx_eq(&self, other: &Self) -> bool
687        where
688            T: ApproxEq<T>,
689        {
690            <Self as ApproxEq<T>>::approx_eq(self, other)
691        }
692
693        /// Returns `true` if this transform is approximately equal to the other one, using
694        /// a provided epsilon value.
695        ///
696        /// The same as [`ApproxEq::approx_eq_eps`] but available without importing trait.
697        #[inline]
698        pub fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool
699        where
700            T: ApproxEq<T>,
701        {
702            <Self as ApproxEq<T>>::approx_eq_eps(self, other, eps)
703        }
704
705        /// Returns true if self can be represented as a 2d scale+offset
706        /// transform, using `T`'s default epsilon value.
707        #[inline]
708        pub fn is_scale_offset(&self) -> bool
709        where
710            T: Copy + Signed + One + PartialOrd + ApproxEq<T>,
711        {
712            self.is_scale_offset_eps(T::approx_epsilon())
713        }
714    }
715
716    /// Methods for creating and combining rotation transformations
717    impl<T, Src, Dst> Transform2D<T, Src, Dst>
718    where
719        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig,
720    {
721        /// Returns a rotation transform.fn pre_translate
722        #[inline]
723        #[rustfmt::skip]
724        pub fn rotation(theta: Angle<T>) -> Self {
725            let _0 = Zero::zero();
726            let cos = theta.get().cos();
727            let sin = theta.get().sin();
728            Transform2D::new(
729                cos, sin,
730                _0 - sin, cos,
731                _0, _0
732            )
733        }
734
735        /// Applies a rotation after self's transformation and returns the resulting transform.
736        #[inline]
737        #[must_use]
738        pub fn then_rotate(&self, theta: Angle<T>) -> Self {
739            self.then(&Transform2D::rotation(theta))
740        }
741
742        /// Applies a rotation before self's transformation and returns the resulting transform.
743        #[inline]
744        #[must_use]
745        pub fn pre_rotate(&self, theta: Angle<T>) -> Self {
746            Transform2D::rotation(theta).then(self)
747        }
748    }
749
750    impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for Transform2D<T, Src, Dst> {
751        #[inline]
752        fn approx_epsilon() -> T {
753            T::approx_epsilon()
754        }
755
756        /// Returns `true` if this transform is approximately equal to the other one, using
757        /// a provided epsilon value.
758        fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
759            self.m11.approx_eq_eps(&other.m11, eps)
760                && self.m12.approx_eq_eps(&other.m12, eps)
761                && self.m21.approx_eq_eps(&other.m21, eps)
762                && self.m22.approx_eq_eps(&other.m22, eps)
763                && self.m31.approx_eq_eps(&other.m31, eps)
764                && self.m32.approx_eq_eps(&other.m32, eps)
765        }
766    }
767
768    impl<T, Src, Dst> From<Rotation2D<T, Src, Dst>> for Transform2D<T, Src, Dst>
769    where
770        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Zero + Trig,
771    {
772        fn from(r: Rotation2D<T, Src, Dst>) -> Self {
773            r.to_transform()
774        }
775    }
776}
777
778#[repr(C)]
779#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
780#[cfg_attr(
781    feature = "serde",
782    serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))
783)]
784pub struct ScaleOffset2D<T, Src, Dst> {
785    pub sx: T,
786    pub sy: T,
787    pub tx: T,
788    pub ty: T,
789    #[doc(hidden)]
790    pub _unit: PhantomData<(Src, Dst)>,
791}
792
793impl<T, Src, Dst> ScaleOffset2D<T, Src, Dst> {
794    /// Create an identity transform.
795    #[inline]
796    pub fn identity() -> Self
797    where
798        T: Zero + One,
799    {
800        ScaleOffset2D {
801            sx: T::one(),
802            sy: T::one(),
803            tx: T::zero(),
804            ty: T::zero(),
805            _unit: PhantomData,
806        }
807    }
808
809    /// Create a transform with provided scale and offset terms.
810    #[inline]
811    pub fn new(sx: T, sy: T, tx: T, ty: T) -> Self {
812        ScaleOffset2D {
813            sx,
814            sy,
815            tx,
816            ty,
817            _unit: PhantomData,
818        }
819    }
820
821    /// Create a transform from a scale.
822    #[inline]
823    pub fn scale(sx: T, sy: T) -> Self
824    where
825        T: Zero,
826    {
827        ScaleOffset2D {
828            sx,
829            sy,
830            tx: T::zero(),
831            ty: T::zero(),
832            _unit: PhantomData,
833        }
834    }
835
836    /// Create a transform from an offset.
837    #[inline]
838    pub fn offset(tx: T, ty: T) -> Self
839    where
840        T: One,
841    {
842        ScaleOffset2D {
843            sx: T::one(),
844            sy: T::one(),
845            tx,
846            ty,
847            _unit: PhantomData,
848        }
849    }
850}
851
852impl<T: Copy, Src, Dst> ScaleOffset2D<T, Src, Dst> {
853    /// Returns the given point transformed by this transform.
854    #[inline]
855    pub fn transform_point(&self, p: Point2D<T, Src>) -> Point2D<T, Dst>
856    where
857        T: Add<Output = T> + Mul<Output = T>,
858    {
859        point2(p.x * self.sx + self.tx, p.y * self.sy + self.ty)
860    }
861
862    /// Returns the given vector transformed by this transform.
863    #[inline]
864    pub fn transform_vector(&self, v: Vector2D<T, Src>) -> Vector2D<T, Dst>
865    where
866        T: Mul<Output = T>,
867    {
868        vec2(v.x * self.sx, v.y * self.sy)
869    }
870
871    /// Returns the given box transformed by this transform.
872    #[inline]
873    pub fn transform_box(&self, b: &Box2D<T, Src>) -> Box2D<T, Dst>
874    where
875        T: Zero + Add<Output = T> + Mul<Output = T> + PartialOrd,
876    {
877        let mut min = self.transform_point(b.min);
878        let mut max = self.transform_point(b.max);
879
880        if self.sx < T::zero() {
881            core::mem::swap(&mut min.x, &mut max.x);
882        }
883        if self.sy < T::zero() {
884            core::mem::swap(&mut min.y, &mut max.y);
885        }
886
887        Box2D { min, max }
888    }
889
890    /// Returns the given rectangle transformed by this transform.
891    #[inline]
892    pub fn transform_rect(&self, r: &Rect<T, Src>) -> Rect<T, Dst>
893    where
894        T: Zero + Add<Output = T> + Sub<Output = T> + Mul<Output = T> + PartialOrd,
895    {
896        self.transform_box(&r.to_box2d()).to_rect()
897    }
898
899    /// Produce a ScaleOffset2D that includes both self and other.
900    /// The 'other' ScaleOffset2D is applied after `self`.
901    ///
902    /// This is equivalent to `Transform2D::then`.
903    #[inline]
904    pub fn then<NewDst>(
905        &self,
906        other: &ScaleOffset2D<T, Dst, NewDst>,
907    ) -> ScaleOffset2D<T, Src, NewDst>
908    where
909        T: Add<Output = T> + Mul<Output = T>,
910    {
911        ScaleOffset2D {
912            sx: self.sx * other.sx,
913            sy: self.sy * other.sy,
914            tx: self.tx * other.sx + other.tx,
915            ty: self.ty * other.sy + other.ty,
916            _unit: PhantomData,
917        }
918    }
919
920    /// Applies a translation before self's transformation and returns the resulting transform.
921    #[inline]
922    #[must_use]
923    pub fn pre_translate(&self, v: Vector2D<T, Src>) -> Self
924    where
925        T: Add<Output = T> + Mul<Output = T>,
926    {
927        ScaleOffset2D {
928            sx: self.sx,
929            sy: self.sy,
930            tx: self.tx + v.x * self.sx,
931            ty: self.ty + v.y * self.sy,
932            _unit: PhantomData,
933        }
934    }
935
936    /// Applies a translation after self's transformation and returns the resulting transform.
937    #[inline]
938    pub fn then_translate(&self, v: Vector2D<T, Dst>) -> Self
939    where
940        T: Add<Output = T> + Mul<Output = T>,
941    {
942        ScaleOffset2D {
943            sx: self.sx,
944            sy: self.sy,
945            tx: self.tx + v.x,
946            ty: self.ty + v.y,
947            _unit: PhantomData,
948        }
949    }
950
951    /// Applies a scale before self's transformation and returns the resulting transform.
952    #[inline]
953    pub fn pre_scale(&self, sx: T, sy: T) -> Self
954    where
955        T: Add<Output = T> + Mul<Output = T>,
956    {
957        ScaleOffset2D {
958            sx: self.sx * sx,
959            sy: self.sy * sy,
960            tx: self.tx,
961            ty: self.ty,
962            _unit: PhantomData,
963        }
964    }
965
966    /// Applies a scale after self's transformation and returns the resulting transform.
967    #[inline]
968    pub fn then_scale(&self, sx: T, sy: T) -> Self
969    where
970        T: Add<Output = T> + Mul<Output = T>,
971    {
972        ScaleOffset2D {
973            sx: self.sx * sx,
974            sy: self.sy * sy,
975            tx: self.tx * sx,
976            ty: self.ty * sy,
977            _unit: PhantomData,
978        }
979    }
980
981    /// Returns whether it is possible to compute the inverse transform.
982    #[inline]
983    pub fn is_invertible(&self) -> bool
984    where
985        T: Zero + PartialEq,
986    {
987        // Expressing the negation this way makes it so NaN scales
988        // count as non-invertible, although we don't attemp to check
989        // for NaN offsets.
990        !(self.sx == T::zero() || self.sy == T::zero())
991    }
992
993    /// Returns the inverse transform if possible.
994    #[must_use]
995    pub fn inverse(&self) -> Option<ScaleOffset2D<T, Dst, Src>>
996    where
997        T: Zero + One + PartialEq + Div<Output = T> + Mul<Output = T> + Neg<Output = T>,
998    {
999        if self.sx == T::zero() || self.sy == T::zero() {
1000            return None;
1001        }
1002
1003        let sx = T::one() / self.sx;
1004        let sy = T::one() / self.sy;
1005        let tx = -self.tx * sx;
1006        let ty = -self.ty * sy;
1007
1008        Some(ScaleOffset2D::new(sx, sy, tx, ty))
1009    }
1010
1011    /// Returns the same transform using Transform2D's matrix representation.
1012    #[inline]
1013    pub fn to_transform2d(&self) -> Transform2D<T, Src, Dst>
1014    where
1015        T: Zero,
1016    {
1017        Transform2D::new(self.sx, T::zero(), T::zero(), self.sy, self.tx, self.ty)
1018    }
1019
1020    /// Returns the same transform using Transform3D's matrix representation.
1021    #[inline]
1022    #[rustfmt::skip]
1023    pub fn to_transform3d(&self) -> Transform3D<T, Src, Dst>
1024    where
1025        T: Zero + One
1026    {
1027        Transform3D::new(
1028            self.sx,   T::zero(), T::zero(), T::zero(),
1029            T::zero(), self.sy,   T::zero(), T::zero(),
1030            T::zero(), T::zero(), T::one(),  T::zero(),
1031            self.tx,   self.ty,   T::zero(), T::one(),
1032        )
1033    }
1034
1035    /// Returns an array containing this transform's terms.
1036    ///
1037    /// The terms are laid out in the same order as they are
1038    /// specified in [`ScaleOffset2D::new`].
1039    #[inline]
1040    pub fn to_array(&self) -> [T; 4] {
1041        [self.sx, self.sy, self.tx, self.ty]
1042    }
1043
1044    /// Returns the same transform with a different source unit.
1045    #[inline]
1046    pub fn with_source<NewSrc>(&self) -> ScaleOffset2D<T, NewSrc, Dst> {
1047        ScaleOffset2D::new(self.sx, self.sy, self.tx, self.ty)
1048    }
1049
1050    /// Returns the same transform with a different destination unit.
1051    #[inline]
1052    pub fn with_destination<NewDst>(&self) -> ScaleOffset2D<T, Src, NewDst> {
1053        ScaleOffset2D::new(self.sx, self.sy, self.tx, self.ty)
1054    }
1055
1056    /// Drop the units, preserving only the numeric value.
1057    #[inline]
1058    pub fn to_untyped(&self) -> ScaleOffset2D<T, UnknownUnit, UnknownUnit> {
1059        ScaleOffset2D::new(self.sx, self.sy, self.tx, self.ty)
1060    }
1061
1062    /// Tag a unitless value with units.
1063    #[inline]
1064    pub fn from_untyped(val: ScaleOffset2D<T, UnknownUnit, UnknownUnit>) -> Self {
1065        ScaleOffset2D::new(val.sx, val.sy, val.tx, val.ty)
1066    }
1067}
1068
1069impl<T: Copy, Src, Dst> Copy for ScaleOffset2D<T, Src, Dst> {}
1070
1071impl<T: Clone, Src, Dst> Clone for ScaleOffset2D<T, Src, Dst> {
1072    fn clone(&self) -> Self {
1073        ScaleOffset2D {
1074            sx: self.sx.clone(),
1075            sy: self.sy.clone(),
1076            tx: self.tx.clone(),
1077            ty: self.ty.clone(),
1078            _unit: PhantomData,
1079        }
1080    }
1081}
1082
1083impl<T, Src, Dst> Eq for ScaleOffset2D<T, Src, Dst> where T: Eq {}
1084
1085impl<T, Src, Dst> PartialEq for ScaleOffset2D<T, Src, Dst>
1086where
1087    T: PartialEq,
1088{
1089    fn eq(&self, other: &Self) -> bool {
1090        (self.sx == other.sx)
1091            & (self.sy == other.sy)
1092            & (self.tx == other.tx)
1093            & (self.ty == other.ty)
1094    }
1095}
1096
1097impl<T, Src, Dst> Hash for ScaleOffset2D<T, Src, Dst>
1098where
1099    T: Hash,
1100{
1101    fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
1102        self.sx.hash(h);
1103        self.sy.hash(h);
1104        self.tx.hash(h);
1105        self.ty.hash(h);
1106    }
1107}
1108
1109impl<T, Src, Dst> Default for ScaleOffset2D<T, Src, Dst>
1110where
1111    T: Copy + Zero + One,
1112{
1113    /// Returns the [identity transform](ScaleOffset2D::identity).
1114    fn default() -> Self {
1115        Self::identity()
1116    }
1117}
1118
1119impl<T: Copy + Zero, Src, Dst> From<Scale<T, Src, Dst>> for ScaleOffset2D<T, Src, Dst> {
1120    fn from(s: Scale<T, Src, Dst>) -> Self {
1121        ScaleOffset2D::scale(s.0, s.0)
1122    }
1123}
1124
1125impl<T: Copy + One, Src, Dst> From<Translation2D<T, Src, Dst>> for ScaleOffset2D<T, Src, Dst> {
1126    fn from(t: Translation2D<T, Src, Dst>) -> Self {
1127        ScaleOffset2D::offset(t.x, t.y)
1128    }
1129}
1130
1131#[cfg(feature = "arbitrary")]
1132impl<'a, T, Src, Dst> arbitrary::Arbitrary<'a> for ScaleOffset2D<T, Src, Dst>
1133where
1134    T: arbitrary::Arbitrary<'a>,
1135{
1136    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1137        let (sx, sy, tx, ty) = arbitrary::Arbitrary::arbitrary(u)?;
1138        Ok(ScaleOffset2D::new(sx, sy, tx, ty))
1139    }
1140}
1141
1142#[cfg(feature = "bytemuck")]
1143unsafe impl<T: Zeroable, Src, Dst> Zeroable for ScaleOffset2D<T, Src, Dst> {}
1144
1145#[cfg(feature = "bytemuck")]
1146unsafe impl<T: Pod, Src: 'static, Dst: 'static> Pod for ScaleOffset2D<T, Src, Dst> {}
1147
1148#[cfg(feature = "malloc_size_of")]
1149impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for ScaleOffset2D<T, Src, Dst> {
1150    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
1151        self.sx.size_of(ops) + self.sy.size_of(ops) + self.tx.size_of(ops) + self.ty.size_of(ops)
1152    }
1153}
1154
1155#[cfg(any(feature = "std", feature = "libm"))]
1156mod scaleoffset_float {
1157    use super::ScaleOffset2D;
1158    use crate::{
1159        approxeq::ApproxEq,
1160        num::{One, Zero},
1161    };
1162    use core::fmt;
1163
1164    impl<T: Copy, Src, Dst> ScaleOffset2D<T, Src, Dst> {
1165        /// Returns true if self is an identity transform, using `T`'s
1166        /// default epsilon value.
1167        #[inline]
1168        pub fn is_identity(&self) -> bool
1169        where
1170            T: One + Zero + ApproxEq<T>,
1171        {
1172            self.is_identity_eps(T::approx_epsilon())
1173        }
1174
1175        /// Returns true if self is an identity transform.
1176        #[inline]
1177        pub fn is_identity_eps(&self, epsilon: T) -> bool
1178        where
1179            T: One + Zero + ApproxEq<T>,
1180        {
1181            self.sx.approx_eq_eps(&T::one(), &epsilon)
1182                & self.sy.approx_eq_eps(&T::one(), &epsilon)
1183                & self.tx.approx_eq_eps(&T::zero(), &epsilon)
1184                & self.ty.approx_eq_eps(&T::zero(), &epsilon)
1185        }
1186    }
1187
1188    impl<T: ApproxEq<T>, Src, Dst> ApproxEq<T> for ScaleOffset2D<T, Src, Dst> {
1189        #[inline]
1190        fn approx_epsilon() -> T {
1191            T::approx_epsilon()
1192        }
1193
1194        /// Returns `true` if this transform is approximately equal to the other one, using
1195        /// a provided epsilon value.
1196        fn approx_eq_eps(&self, other: &Self, eps: &T) -> bool {
1197            self.sx.approx_eq_eps(&other.sx, eps)
1198                && self.sy.approx_eq_eps(&other.sy, eps)
1199                && self.tx.approx_eq_eps(&other.tx, eps)
1200                && self.ty.approx_eq_eps(&other.ty, eps)
1201        }
1202    }
1203
1204    impl<T, Src, Dst> fmt::Debug for ScaleOffset2D<T, Src, Dst>
1205    where
1206        T: Copy + fmt::Debug + PartialEq + One + Zero + ApproxEq<T>,
1207    {
1208        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1209            if self.is_identity() {
1210                write!(f, "[I]")
1211            } else {
1212                self.to_array().fmt(f)
1213            }
1214        }
1215    }
1216}
1217
1218#[cfg(test)]
1219#[cfg(any(feature = "std", feature = "libm"))]
1220mod test {
1221    use super::*;
1222    use crate::approxeq::ApproxEq;
1223    use crate::{default, Angle};
1224    #[cfg(feature = "mint")]
1225    use mint;
1226    type ScaleOffset = crate::default::ScaleOffset2D<f32>;
1227    type IntScaleOffset = crate::default::ScaleOffset2D<i32>;
1228
1229    use core::f32::consts::FRAC_PI_2;
1230
1231    type Mat = default::Transform2D<f32>;
1232
1233    fn rad(v: f32) -> Angle<f32> {
1234        Angle::radians(v)
1235    }
1236
1237    #[test]
1238    pub fn test_translation() {
1239        let t1 = Mat::translation(1.0, 2.0);
1240        let t2 = Mat::identity().pre_translate(vec2(1.0, 2.0));
1241        let t3 = Mat::identity().then_translate(vec2(1.0, 2.0));
1242        assert_eq!(t1, t2);
1243        assert_eq!(t1, t3);
1244
1245        assert_eq!(
1246            t1.transform_point(Point2D::new(1.0, 1.0)),
1247            Point2D::new(2.0, 3.0)
1248        );
1249
1250        assert_eq!(t1.then(&t1), Mat::translation(2.0, 4.0));
1251    }
1252
1253    #[test]
1254    pub fn test_rotation() {
1255        let r1 = Mat::rotation(rad(FRAC_PI_2));
1256        let r2 = Mat::identity().pre_rotate(rad(FRAC_PI_2));
1257        let r3 = Mat::identity().then_rotate(rad(FRAC_PI_2));
1258        assert_eq!(r1, r2);
1259        assert_eq!(r1, r3);
1260
1261        assert!(r1
1262            .transform_point(Point2D::new(1.0, 2.0))
1263            .approx_eq(&Point2D::new(-2.0, 1.0)));
1264
1265        assert!(r1.then(&r1).approx_eq(&Mat::rotation(rad(FRAC_PI_2 * 2.0))));
1266    }
1267
1268    #[test]
1269    pub fn test_scale() {
1270        let s1 = Mat::scale(2.0, 3.0);
1271        let s2 = Mat::identity().pre_scale(2.0, 3.0);
1272        let s3 = Mat::identity().then_scale(2.0, 3.0);
1273        assert_eq!(s1, s2);
1274        assert_eq!(s1, s3);
1275
1276        assert!(s1
1277            .transform_point(Point2D::new(2.0, 2.0))
1278            .approx_eq(&Point2D::new(4.0, 6.0)));
1279    }
1280
1281    #[test]
1282    pub fn test_pre_then_scale() {
1283        let m = Mat::rotation(rad(FRAC_PI_2)).then_translate(vec2(6.0, 7.0));
1284        let s = Mat::scale(2.0, 3.0);
1285        assert_eq!(m.then(&s), m.then_scale(2.0, 3.0));
1286    }
1287
1288    #[test]
1289    pub fn test_inverse_simple() {
1290        let m1 = Mat::identity();
1291        let m2 = m1.inverse().unwrap();
1292        assert!(m1.approx_eq(&m2));
1293    }
1294
1295    #[test]
1296    pub fn test_inverse_scale() {
1297        let m1 = Mat::scale(1.5, 0.3);
1298        let m2 = m1.inverse().unwrap();
1299        assert!(m1.then(&m2).approx_eq(&Mat::identity()));
1300        assert!(m2.then(&m1).approx_eq(&Mat::identity()));
1301    }
1302
1303    #[test]
1304    pub fn test_inverse_translate() {
1305        let m1 = Mat::translation(-132.0, 0.3);
1306        let m2 = m1.inverse().unwrap();
1307        assert!(m1.then(&m2).approx_eq(&Mat::identity()));
1308        assert!(m2.then(&m1).approx_eq(&Mat::identity()));
1309    }
1310
1311    #[test]
1312    fn test_inverse_none() {
1313        assert!(Mat::scale(2.0, 0.0).inverse().is_none());
1314        assert!(Mat::scale(2.0, 2.0).inverse().is_some());
1315    }
1316
1317    #[test]
1318    pub fn test_pre_post() {
1319        let m1 = default::Transform2D::identity()
1320            .then_scale(1.0, 2.0)
1321            .then_translate(vec2(1.0, 2.0));
1322        let m2 = default::Transform2D::identity()
1323            .pre_translate(vec2(1.0, 2.0))
1324            .pre_scale(1.0, 2.0);
1325        assert!(m1.approx_eq(&m2));
1326
1327        let r = Mat::rotation(rad(FRAC_PI_2));
1328        let t = Mat::translation(2.0, 3.0);
1329
1330        let a = Point2D::new(1.0, 1.0);
1331
1332        assert!(r
1333            .then(&t)
1334            .transform_point(a)
1335            .approx_eq(&Point2D::new(1.0, 4.0)));
1336        assert!(t
1337            .then(&r)
1338            .transform_point(a)
1339            .approx_eq(&Point2D::new(-4.0, 3.0)));
1340        assert!(t
1341            .then(&r)
1342            .transform_point(a)
1343            .approx_eq(&r.transform_point(t.transform_point(a))));
1344    }
1345
1346    #[test]
1347    fn test_size_of() {
1348        use core::mem::size_of;
1349        assert_eq!(size_of::<default::Transform2D<f32>>(), 6 * size_of::<f32>());
1350        assert_eq!(size_of::<default::Transform2D<f64>>(), 6 * size_of::<f64>());
1351    }
1352
1353    #[test]
1354    pub fn test_is_identity() {
1355        let m1 = default::Transform2D::identity();
1356        assert!(m1.is_identity());
1357        let m2 = m1.then_translate(vec2(0.1, 0.0));
1358        assert!(!m2.is_identity());
1359    }
1360
1361    #[test]
1362    pub fn test_transform_vector() {
1363        // Translation does not apply to vectors.
1364        let m1 = Mat::translation(1.0, 1.0);
1365        let v1 = vec2(10.0, -10.0);
1366        assert_eq!(v1, m1.transform_vector(v1));
1367    }
1368
1369    #[cfg(feature = "mint")]
1370    #[test]
1371    pub fn test_mint() {
1372        let m1 = Mat::rotation(rad(FRAC_PI_2));
1373        let mm: mint::RowMatrix3x2<_> = m1.into();
1374        let m2 = Mat::from(mm);
1375
1376        assert_eq!(m1, m2);
1377    }
1378
1379    #[test]
1380    pub fn scale_offset_transform_point() {
1381        let t1 = IntScaleOffset::new(2, 3, 4, 5);
1382
1383        let p = point2(6, 7);
1384
1385        assert_eq!(
1386            t1.transform_point(p),
1387            t1.to_transform2d().transform_point(p),
1388        );
1389    }
1390
1391    #[test]
1392    pub fn scale_offset_transform_vector() {
1393        let t1 = IntScaleOffset::new(2, 3, 4, 5);
1394
1395        let v = vec2(6, 7);
1396
1397        assert_eq!(
1398            t1.transform_vector(v),
1399            t1.to_transform2d().transform_vector(v),
1400        );
1401    }
1402
1403    #[test]
1404    pub fn scale_offset_transform_box() {
1405        let transforms = [
1406            ScaleOffset::identity(),
1407            ScaleOffset::new(2.0, 3.0, 4.0, 5.0),
1408            ScaleOffset::new(-2.0, 3.0, 4.0, -5.0),
1409            ScaleOffset::new(2.0, -3.0, 4.0, -5.0),
1410            ScaleOffset::new(-2.0, -3.0, -4.0, 5.0),
1411        ];
1412
1413        let boxes = [
1414            default::Box2D {
1415                min: point2(0.0, 0.0),
1416                max: point2(1.0, 1.0),
1417            },
1418            Box2D {
1419                min: point2(-10.0, -10.0),
1420                max: point2(10.0, 10.0),
1421            },
1422            Box2D {
1423                min: point2(100.0, 130.0),
1424                max: point2(150.0, 160.0),
1425            },
1426            Box2D {
1427                min: point2(-100.0, -130.0),
1428                max: point2(-50.0, 160.0),
1429            },
1430            Box2D {
1431                min: point2(0.0, 0.0),
1432                max: point2(0.0, 0.0),
1433            },
1434            Box2D {
1435                min: point2(1.0, 0.0),
1436                max: point2(-1.0, 1.0),
1437            },
1438            Box2D {
1439                min: point2(1.0, 1.0),
1440                max: point2(1.0, -1.0),
1441            },
1442            Box2D {
1443                min: point2(1.0, 1.0),
1444                max: point2(-1.0, -1.0),
1445            },
1446        ];
1447
1448        for b in &boxes {
1449            for transform in &transforms {
1450                let b2 = transform.transform_box(b);
1451                assert_eq!(
1452                    b2.is_empty(),
1453                    b.is_empty(),
1454                    "transform: {transform:?}, box: {b:?}, empty before: {:?} after {:?}",
1455                    b.is_empty(),
1456                    b2.is_empty()
1457                );
1458
1459                let mat = transform.to_transform2d();
1460                // We don't guarantee that Transform2D and ScaleOffset2D
1461                // deal with empty boxes the same way.
1462                if !b.is_empty() {
1463                    assert_eq!(b2, mat.outer_transformed_box(b));
1464                }
1465            }
1466        }
1467    }
1468
1469    #[test]
1470    pub fn scale_offset_then() {
1471        let t1 = IntScaleOffset::new(2, 3, 4, 5);
1472        let t2 = IntScaleOffset::new(6, 7, 8, 9);
1473
1474        let t3 = t1.then(&t2).to_transform2d();
1475        let t3_tx = t1.to_transform2d().then(&t2.to_transform2d());
1476
1477        assert_eq!(t3, t3_tx);
1478    }
1479
1480    #[test]
1481    pub fn scale_offset_pre_post() {
1482        let t1 = ScaleOffset::new(2.0, 3.0, 4.0, 5.0);
1483        let v = vec2(100.0, 200.0);
1484        assert!(t1
1485            .then_translate(v)
1486            .to_transform2d()
1487            .approx_eq(&t1.to_transform2d().then_translate(v)));
1488        assert!(t1
1489            .pre_translate(v)
1490            .to_transform2d()
1491            .approx_eq(&t1.to_transform2d().pre_translate(v)));
1492        let sx = 100.0;
1493        let sy = 120.0;
1494        assert!(t1
1495            .then_scale(sx, sy)
1496            .to_transform2d()
1497            .approx_eq(&t1.to_transform2d().then_scale(sx, sy)));
1498        assert!(t1
1499            .pre_scale(sx, sy)
1500            .to_transform2d()
1501            .approx_eq(&t1.to_transform2d().pre_scale(sx, sy)));
1502    }
1503
1504    #[test]
1505    pub fn scale_offset_inverse() {
1506        let t1 = ScaleOffset::new(2.0, 3.0, 4.0, 5.0);
1507
1508        assert_eq!(
1509            t1.inverse().unwrap().to_transform2d(),
1510            t1.to_transform2d().inverse().unwrap(),
1511        )
1512    }
1513
1514    #[test]
1515    pub fn scale_offset_identity() {
1516        assert!(ScaleOffset::identity().is_identity())
1517    }
1518}