godot_core/builtin/
transform2d.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::fmt::Display;
9use std::ops::{Mul, MulAssign};
10
11use godot_ffi as sys;
12use sys::{ffi_methods, ExtVariantType, GodotFfi};
13
14use crate::builtin::math::{assert_ne_approx, ApproxEq, FloatExt, GlamConv, GlamType, XformInv};
15use crate::builtin::real_consts::PI;
16use crate::builtin::{real, RAffine2, RMat2, Rect2, Vector2};
17
18/// Affine 2D transform (2x3 matrix).
19///
20/// Represents transformations such as translation, rotation, or scaling.
21///
22/// Expressed as a 2x3 matrix, this transform consists of a two column vectors
23/// `a` and `b` representing the basis of the transform, as well as the origin:
24/// ```text
25/// [ a.x  b.x  origin.x ]
26/// [ a.y  b.y  origin.y ]
27/// ```
28///
29/// # All matrix types
30///
31/// | Dimension | Orthogonal basis | Affine transform        | Projective transform |
32/// |-----------|------------------|-------------------------|----------------------|
33/// | 2D        |                  | **`Transform2D`** (2x3) |                      |
34/// | 3D        | [`Basis`] (3x3)  | [`Transform3D`] (3x4)   | [`Projection`] (4x4) |
35///
36/// [`Basis`]: crate::builtin::Basis
37/// [`Transform3D`]: crate::builtin::Transform3D
38/// [`Projection`]: crate::builtin::Projection
39///
40/// # Transform operations
41///
42/// | Operation                      | Transform2D                      | Notes                               |
43/// |--------------------------------|----------------------------------|-------------------------------------|
44/// | Apply                          | `transform * v`                  | Supports [`Rect2`] and [`Vector2`]. |
45/// | Apply inverse                  | `transform.xform_inv(v)`         | Supports [`Rect2`] and [`Vector2`]. |
46/// | Apply, no translate            | `transform.basis_xform(v)`       | Supports [`Vector2`].               |
47/// | Apply inverse, no translate    | `transform.basis_xform_inv(v)`   | Supports [`Vector2`].               |
48///
49/// # Godot docs
50///
51/// [`Transform2D` (stable)](https://docs.godotengine.org/en/stable/classes/class_transform2d.html)
52#[derive(Default, Copy, Clone, PartialEq, Debug)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[repr(C)]
55pub struct Transform2D {
56    /// The first basis vector.
57    ///
58    /// _Godot equivalent: `Transform2D.x`_, see [`Basis`][crate::builtin::Basis] for why it's changed
59    pub a: Vector2,
60
61    /// The second basis vector.
62    ///
63    /// _Godot equivalent: `Transform2D.y`_, see [`Basis`][crate::builtin::Basis] for why it's changed
64    pub b: Vector2,
65
66    /// The origin of the transform. The coordinate space defined by this transform
67    /// starts at this point.
68    ///
69    /// _Godot equivalent: `Transform2D.origin`_
70    pub origin: Vector2,
71}
72
73impl Transform2D {
74    /// The identity transform, with no translation, rotation or scaling
75    /// applied. When applied to other data structures, `IDENTITY` performs no
76    /// transformation.
77    ///
78    /// _Godot equivalent: `Transform2D.IDENTITY`_
79    pub const IDENTITY: Self = Self::from_basis_origin(Basis2D::IDENTITY, Vector2::ZERO);
80
81    /// The `Transform2D` that will flip something along its X axis.
82    ///
83    /// _Godot equivalent: `Transform2D.FLIP_X`_
84    pub const FLIP_X: Self = Self::from_basis_origin(Basis2D::FLIP_X, Vector2::ZERO);
85
86    /// The `Transform2D` that will flip something along its Y axis.
87    ///
88    /// _Godot equivalent: `Transform2D.FLIP_Y`_
89    pub const FLIP_Y: Self = Self::from_basis_origin(Basis2D::FLIP_Y, Vector2::ZERO);
90
91    const fn from_basis_origin(basis: Basis2D, origin: Vector2) -> Self {
92        let [a, b] = basis.cols;
93        Self { a, b, origin }
94    }
95
96    /// Create a new `Transform2D` with the given column vectors.
97    ///
98    /// _Godot equivalent: `Transform2D(Vector2 x_axis, Vector2 y_axis, Vector2 origin)`_, see [`Basis`][crate::builtin::Basis] for why it's
99    /// changed
100    pub const fn from_cols(a: Vector2, b: Vector2, origin: Vector2) -> Self {
101        Self { a, b, origin }
102    }
103
104    /// Create a new `Transform2D` which will rotate by the given angle.
105    pub fn from_angle(angle: real) -> Self {
106        Self::from_angle_origin(angle, Vector2::ZERO)
107    }
108
109    /// Create a new `Transform2D` which will rotate by `angle` and translate
110    /// by `origin`.
111    ///
112    /// _Godot equivalent: `Transform2D(float rotation, Vector2 position)`_
113    pub fn from_angle_origin(angle: real, origin: Vector2) -> Self {
114        Self::from_basis_origin(Basis2D::from_angle(angle), origin)
115    }
116
117    /// Create a new `Transform2D` which will rotate by `angle`, scale by
118    /// `scale`, skew by `skew` and translate by `origin`.
119    ///
120    /// _Godot equivalent: `Transform2D(float rotation, Vector2 scale, float skew, Vector2 position)`_
121    pub fn from_angle_scale_skew_origin(
122        angle: real,
123        scale: Vector2,
124        skew: real,
125        origin: Vector2,
126    ) -> Self {
127        // Translated from Godot's implementation
128
129        Self::from_basis_origin(
130            Basis2D::from_cols(
131                Vector2::new(angle.cos(), angle.sin()),
132                Vector2::new(-(angle + skew).sin(), (angle + skew).cos()),
133            )
134            .scaled(scale),
135            origin,
136        )
137    }
138
139    /// Unstable, used to simplify codegen. Too many parameters for public API and easy to have off-by-one, `from_cols()` is preferred.
140    #[doc(hidden)]
141    #[rustfmt::skip]
142    #[allow(clippy::too_many_arguments)]
143    pub const fn __internal_codegen(
144       ax: real, ay: real,
145       bx: real, by: real,
146       ox: real, oy: real,
147    ) -> Self {
148        Self::from_cols(
149            Vector2::new(ax, ay),
150            Vector2::new(bx, by),
151            Vector2::new(ox, oy),
152        )
153    }
154    /// Create a reference to the first two columns of the transform
155    /// interpreted as a [`Basis2D`].
156    fn basis<'a>(&'a self) -> &'a Basis2D {
157        // SAFETY: Both `Basis2D` and `Transform2D` are `repr(C)`, and the
158        // layout of `Basis2D` is a prefix of `Transform2D`
159
160        unsafe { std::mem::transmute::<&'a Transform2D, &'a Basis2D>(self) }
161    }
162
163    /// Create a [`Basis2D`] from the first two columns of the transform.
164    #[allow(clippy::wrong_self_convention)]
165    fn to_basis(&self) -> Basis2D {
166        Basis2D::from_cols(self.a, self.b)
167    }
168
169    /// Returns the inverse of the transform, under the assumption that the
170    /// transformation is composed of rotation, scaling and translation.
171    ///
172    /// _Godot equivalent: `Transform2D.affine_inverse()`_
173    #[must_use]
174    pub fn affine_inverse(&self) -> Self {
175        self.glam(|aff| aff.inverse())
176    }
177
178    /// Returns the determinant of the basis matrix.
179    ///
180    /// If the basis is uniformly scaled, then its determinant equals the square of the scale factor.
181    ///
182    /// A negative determinant means the basis was flipped, so one part of the scale is negative.
183    /// A zero determinant means the basis isn't invertible, and is usually considered invalid.
184    ///
185    /// _Godot equivalent: `Transform2D.determinant()`_
186    pub fn determinant(&self) -> real {
187        self.basis().determinant()
188    }
189
190    /// Returns the transform's rotation (in radians).
191    ///
192    /// _Godot equivalent: `Transform2D.get_rotation()`_
193    pub fn rotation(&self) -> real {
194        self.basis().rotation()
195    }
196
197    /// Returns the transform's scale.
198    ///
199    /// _Godot equivalent: `Transform2D.get_scale()`_
200    #[must_use]
201    pub fn scale(&self) -> Vector2 {
202        self.basis().scale()
203    }
204
205    /// Returns the transform's skew (in radians).
206    ///
207    /// _Godot equivalent: `Transform2D.get_skew()`_
208    #[must_use]
209    pub fn skew(&self) -> real {
210        self.basis().skew()
211    }
212
213    /// Returns a transform interpolated between this transform and another by
214    /// a given `weight` (on the range of 0.0 to 1.0).
215    ///
216    /// _Godot equivalent: `Transform2D.interpolate_with()`_
217    #[must_use]
218    pub fn interpolate_with(&self, other: &Self, weight: real) -> Self {
219        Self::from_angle_scale_skew_origin(
220            self.rotation().lerp_angle(other.rotation(), weight),
221            self.scale().lerp(other.scale(), weight),
222            self.skew().lerp_angle(other.skew(), weight),
223            self.origin.lerp(other.origin, weight),
224        )
225    }
226
227    /// Returns `true` if this transform is finite, by calling
228    /// [`Vector2::is_finite()`] on each component.
229    ///
230    /// _Godot equivalent: `Transform2D.is_finite()`_
231    pub fn is_finite(&self) -> bool {
232        self.a.is_finite() && self.b.is_finite() && self.origin.is_finite()
233    }
234
235    /// Returns the transform with the basis orthogonal (90 degrees), and
236    /// normalized axis vectors (scale of 1 or -1).
237    ///
238    /// _Godot equivalent: `Transform2D.orthonormalized()`_
239    #[must_use]
240    pub fn orthonormalized(&self) -> Self {
241        Self::from_basis_origin(self.basis().orthonormalized(), self.origin)
242    }
243
244    /// Returns a copy of the transform rotated by the given `angle` (in radians).
245    /// This method is an optimized version of multiplying the given transform `X`
246    /// with a corresponding rotation transform `R` from the left, i.e., `R * X`.
247    /// This can be seen as transforming with respect to the global/parent frame.
248    ///
249    /// _Godot equivalent: `Transform2D.rotated()`_
250    #[must_use]
251    pub fn rotated(&self, angle: real) -> Self {
252        Self::from_angle(angle) * (*self)
253    }
254
255    /// Returns a copy of the transform rotated by the given `angle` (in radians).
256    /// This method is an optimized version of multiplying the given transform `X`
257    /// with a corresponding rotation transform `R` from the right, i.e., `X * R`.
258    /// This can be seen as transforming with respect to the local frame.
259    ///
260    /// _Godot equivalent: `Transform2D.rotated_local()`_
261    #[must_use]
262    pub fn rotated_local(&self, angle: real) -> Self {
263        (*self) * Self::from_angle(angle)
264    }
265
266    /// Returns a copy of the transform scaled by the given scale factor.
267    /// This method is an optimized version of multiplying the given transform `X`
268    /// with a corresponding scaling transform `S` from the left, i.e., `S * X`.
269    /// This can be seen as transforming with respect to the global/parent frame.
270    ///
271    /// _Godot equivalent: `Transform2D.scaled()`_
272    #[must_use]
273    pub fn scaled(&self, scale: Vector2) -> Self {
274        let mut basis = self.to_basis();
275        basis.set_row_a(basis.row_a() * scale.x);
276        basis.set_row_b(basis.row_b() * scale.y);
277        Self::from_basis_origin(basis, self.origin * scale)
278    }
279
280    /// Returns a copy of the transform scaled by the given scale factor.
281    /// This method is an optimized version of multiplying the given transform `X`
282    /// with a corresponding scaling transform `S` from the right, i.e., `X * S`.
283    /// This can be seen as transforming with respect to the local frame.
284    ///
285    /// _Godot equivalent: `Transform2D.scaled_local()`_
286    #[must_use]
287    pub fn scaled_local(&self, scale: Vector2) -> Self {
288        Self::from_basis_origin(self.basis().scaled(scale), self.origin)
289    }
290
291    /// Returns a copy of the transform translated by the given offset.
292    /// This method is an optimized version of multiplying the given transform `X`
293    /// with a corresponding translation transform `T` from the left, i.e., `T * X`.
294    /// This can be seen as transforming with respect to the global/parent frame.
295    ///
296    /// _Godot equivalent: `Transform2D.translated()`_
297    #[must_use]
298    pub fn translated(&self, offset: Vector2) -> Self {
299        Self::from_cols(self.a, self.b, self.origin + offset)
300    }
301
302    /// Returns a copy of the transform translated by the given offset.
303    /// This method is an optimized version of multiplying the given transform `X`
304    /// with a corresponding translation transform `T` from the right, i.e., `X * T`.
305    /// This can be seen as transforming with respect to the local frame.
306    ///
307    /// _Godot equivalent: `Transform2D.translated()`_
308    #[must_use]
309    pub fn translated_local(&self, offset: Vector2) -> Self {
310        Self::from_cols(self.a, self.b, self.origin + (self.to_basis() * offset))
311    }
312
313    /// Returns a vector transformed (multiplied) by the basis matrix.
314    /// This method does not account for translation (the origin vector).
315    ///
316    /// _Godot equivalent: `Transform2D.basis_xform()`_
317    pub fn basis_xform(&self, v: Vector2) -> Vector2 {
318        self.to_basis() * v
319    }
320
321    /// Returns a vector transformed (multiplied) by the inverse basis matrix.
322    /// This method does not account for translation (the origin vector).
323    ///
324    /// _Godot equivalent: `Transform2D.basis_xform_inv()`_
325    pub fn basis_xform_inv(&self, v: Vector2) -> Vector2 {
326        self.basis().inverse() * v
327    }
328}
329
330impl Display for Transform2D {
331    /// Formats the value with the given formatter.  [Read more](https://doc.rust-lang.org/1.79.0/core/fmt/trait.Display.html#tymethod.fmt)
332    ///
333    /// The output is similar to Godot's, but calls the columns a/b instead of X/Y.  See [`Basis`][crate::builtin::Basis] for why.
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        // Godot output:
336        // [X: (1, 2), Y: (3, 4), O: (5, 6)]
337        // Where X,Y,O are the columns
338
339        let Transform2D { a, b, origin } = self;
340
341        write!(f, "[a: {a}, b: {b}, o: {origin}]")
342    }
343}
344
345impl Mul for Transform2D {
346    type Output = Self;
347
348    fn mul(self, rhs: Self) -> Self::Output {
349        self.glam2(&rhs, |a, b| a * b)
350    }
351}
352
353impl Mul<Vector2> for Transform2D {
354    type Output = Vector2;
355
356    fn mul(self, rhs: Vector2) -> Self::Output {
357        self.glam2(&rhs, |t, v| t.transform_point2(v))
358    }
359}
360
361impl XformInv<Vector2> for Transform2D {
362    /// Inversely transforms (multiplies) the given [`Vector2`] by this transformation matrix,
363    /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
364    ///
365    /// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * vector` can be used instead. See: [`Transform2D::affine_inverse()`].
366    ///
367    /// _Godot equivalent: `vector * transform` or `transform.inverse() * vector`_
368    fn xform_inv(&self, rhs: Vector2) -> Vector2 {
369        let v = rhs - self.origin;
370        self.basis_xform_inv(v)
371    }
372}
373
374impl Mul<real> for Transform2D {
375    type Output = Self;
376
377    fn mul(self, rhs: real) -> Self::Output {
378        Self::from_cols(self.a * rhs, self.b * rhs, self.origin * rhs)
379    }
380}
381
382impl Mul<Rect2> for Transform2D {
383    type Output = Rect2;
384
385    /// Transforms each coordinate in `rhs.position` and `rhs.end()` individually by this transform, then
386    /// creates a `Rect2` containing all of them.
387    fn mul(self, rhs: Rect2) -> Self::Output {
388        // https://web.archive.org/web/20220317024830/https://dev.theomader.com/transform-bounding-boxes/
389        let xa = self.a * rhs.position.x;
390        let xb = self.a * rhs.end().x;
391
392        let ya = self.b * rhs.position.y;
393        let yb = self.b * rhs.end().y;
394
395        let position = Vector2::coord_min(xa, xb) + Vector2::coord_min(ya, yb);
396        let end = Vector2::coord_max(xa, xb) + Vector2::coord_max(ya, yb);
397        Rect2::new(position + self.origin, end - position)
398    }
399}
400
401impl XformInv<Rect2> for Transform2D {
402    /// Inversely transforms (multiplies) the given [`Rect2`] by this [`Transform2D`] transformation matrix, under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
403    ///
404    /// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * vector` can be used instead. See: [`Transform2D::affine_inverse()`].
405    ///
406    /// _Godot equivalent: `rect2 * transform` or `transform.inverse() * rect2`_
407    fn xform_inv(&self, rhs: Rect2) -> Rect2 {
408        // https://web.archive.org/web/20220317024830/https://dev.theomader.com/transform-bounding-boxes/
409        // Same as Godot's `Transform2D::xform_inv` but omits unnecessary `Rect2::expand_to`.
410        // There is probably some more clever way to do that.
411
412        // Use the first point initialize our min/max.
413        let start = self.xform_inv(rhs.position);
414        // `min` is position of our aabb, `max` is the farthest vertex.
415        let (mut min, mut max) = (start, start);
416
417        let vertices = [
418            Vector2::new(rhs.position.x, rhs.position.y + rhs.size.y),
419            rhs.end(),
420            Vector2::new(rhs.position.x + rhs.size.x, rhs.position.y),
421        ];
422
423        for v in vertices {
424            let transformed = self.xform_inv(v);
425            min = Vector2::coord_min(min, transformed);
426            max = Vector2::coord_max(max, transformed);
427        }
428
429        Rect2::new(min, max - min)
430    }
431}
432
433impl ApproxEq for Transform2D {
434    /// Returns if the two transforms are approximately equal, by comparing each component separately.
435    #[inline]
436    fn approx_eq(&self, other: &Self) -> bool {
437        Vector2::approx_eq(&self.a, &other.a)
438            && Vector2::approx_eq(&self.b, &other.b)
439            && Vector2::approx_eq(&self.origin, &other.origin)
440    }
441}
442
443impl GlamType for RAffine2 {
444    type Mapped = Transform2D;
445
446    fn to_front(&self) -> Self::Mapped {
447        Transform2D::from_basis_origin(self.matrix2.to_front(), self.translation.to_front())
448    }
449
450    fn from_front(mapped: &Self::Mapped) -> Self {
451        Self {
452            matrix2: mapped.basis().to_glam(),
453            translation: mapped.origin.to_glam(),
454        }
455    }
456}
457
458impl GlamConv for Transform2D {
459    type Glam = RAffine2;
460}
461
462// SAFETY:
463// This type is represented as `Self` in Godot, so `*mut Self` is sound.
464unsafe impl GodotFfi for Transform2D {
465    const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::TRANSFORM2D);
466
467    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
468}
469
470crate::meta::impl_godot_as_self!(Transform2D: ByValue);
471
472/// A 2x2 matrix, typically used as an orthogonal basis for [`Transform2D`].
473///
474/// Indexing into a `Basis2D` is done in a column-major order, meaning that
475/// `basis[0]` is the first basis-vector.
476///
477/// This has no direct equivalent in Godot, but is the same as the `x` and `y`
478/// vectors from a `Transform2D`.
479#[derive(Copy, Clone, PartialEq, Debug)]
480#[repr(C)]
481pub(crate) struct Basis2D {
482    /// The columns of the matrix.
483    cols: [Vector2; 2],
484}
485
486impl Basis2D {
487    /// The identity basis, with no rotation or scaling applied.
488    pub(crate) const IDENTITY: Self = Self::from_diagonal(1.0, 1.0);
489
490    /// The basis that will flip something along the X axis when used in a
491    /// transformation.
492    pub(crate) const FLIP_X: Self = Self::from_diagonal(-1.0, 1.0);
493
494    /// The basis that will flip something along the X axis when used in a
495    /// transformation.
496    pub(crate) const FLIP_Y: Self = Self::from_diagonal(1.0, -1.0);
497
498    /// Create a diagonal matrix from the given values.
499    pub(crate) const fn from_diagonal(x: real, y: real) -> Self {
500        Self::from_cols(Vector2::new(x, 0.0), Vector2::new(0.0, y))
501    }
502
503    /// Create a new basis from 2 basis vectors.
504    pub(crate) const fn from_cols(x: Vector2, y: Vector2) -> Self {
505        Self { cols: [x, y] }
506    }
507
508    /// Create a `Basis2D` from an angle.
509    pub(crate) fn from_angle(angle: real) -> Self {
510        RMat2::from_angle(angle).to_front()
511    }
512
513    /// Returns the scale of the matrix.
514    #[must_use]
515    pub(crate) fn scale(&self) -> Vector2 {
516        let det_sign = self.determinant().signum();
517        Vector2::new(self.cols[0].length(), det_sign * self.cols[1].length())
518    }
519
520    /// Introduces an additional scaling.
521    #[must_use]
522    pub(crate) fn scaled(self, scale: Vector2) -> Self {
523        Self {
524            cols: [self.cols[0] * scale.x, self.cols[1] * scale.y],
525        }
526    }
527
528    /// Returns the determinant of the matrix.
529    pub fn determinant(&self) -> real {
530        self.glam(|mat| mat.determinant())
531    }
532
533    /// Returns the inverse of the matrix.
534    #[must_use]
535    pub fn inverse(self) -> Self {
536        self.glam(|mat| mat.inverse())
537    }
538
539    /// Returns the orthonormalized version of the basis.
540    #[must_use]
541    pub(crate) fn orthonormalized(self) -> Self {
542        assert_ne_approx!(self.determinant(), 0.0, "Determinant should not be zero.");
543
544        // Gram-Schmidt Process
545        let mut x = self.cols[0];
546        let mut y = self.cols[1];
547
548        x = x.normalized();
549        y = y - x * (x.dot(y));
550        y = y.normalized();
551
552        Self::from_cols(x, y)
553    }
554
555    /// Returns the rotation of the matrix
556    pub(crate) fn rotation(&self) -> real {
557        // Translated from Godot
558        real::atan2(self.cols[0].y, self.cols[0].x)
559    }
560
561    /// Returns the skew of the matrix
562    #[must_use]
563    pub(crate) fn skew(&self) -> real {
564        // Translated from Godot
565        let det_sign = self.determinant().signum();
566        self.cols[0]
567            .normalized()
568            .dot(det_sign * self.cols[1].normalized())
569            .acos()
570            - PI * 0.5
571    }
572
573    pub(crate) fn set_row_a(&mut self, v: Vector2) {
574        self.cols[0].x = v.x;
575        self.cols[1].x = v.y;
576    }
577
578    pub(crate) fn row_a(&self) -> Vector2 {
579        Vector2::new(self.cols[0].x, self.cols[1].x)
580    }
581
582    pub(crate) fn set_row_b(&mut self, v: Vector2) {
583        self.cols[0].y = v.x;
584        self.cols[1].y = v.y;
585    }
586
587    pub(crate) fn row_b(&self) -> Vector2 {
588        Vector2::new(self.cols[0].y, self.cols[1].y)
589    }
590}
591
592impl Default for Basis2D {
593    fn default() -> Self {
594        Self::IDENTITY
595    }
596}
597
598impl Display for Basis2D {
599    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600        let [a, b] = self.cols;
601        write!(f, "[a: {a}, b: {b})]")
602    }
603}
604
605impl Mul for Basis2D {
606    type Output = Self;
607
608    fn mul(self, rhs: Self) -> Self::Output {
609        self.glam2(&rhs, |a, b| a * b)
610    }
611}
612
613impl Mul<real> for Basis2D {
614    type Output = Self;
615
616    fn mul(self, rhs: real) -> Self::Output {
617        (self.to_glam() * rhs).to_front()
618    }
619}
620
621impl MulAssign<real> for Basis2D {
622    fn mul_assign(&mut self, rhs: real) {
623        self.cols[0] *= rhs;
624        self.cols[1] *= rhs;
625    }
626}
627
628impl Mul<Vector2> for Basis2D {
629    type Output = Vector2;
630
631    fn mul(self, rhs: Vector2) -> Self::Output {
632        self.glam2(&rhs, |a, b| a * b)
633    }
634}
635
636impl GlamType for RMat2 {
637    type Mapped = Basis2D;
638
639    fn to_front(&self) -> Self::Mapped {
640        Basis2D {
641            cols: [self.col(0).to_front(), self.col(1).to_front()],
642        }
643    }
644
645    fn from_front(mapped: &Self::Mapped) -> Self {
646        Self::from_cols(mapped.cols[0].to_glam(), mapped.cols[1].to_glam())
647    }
648}
649
650impl GlamConv for Basis2D {
651    type Glam = RMat2;
652}
653
654#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
655mod test {
656    use super::*;
657    use crate::assert_eq_approx;
658
659    #[test]
660    fn transform2d_constructors_correct() {
661        let trans = Transform2D::from_angle(real!(115.0).to_radians());
662        assert_eq_approx!(trans.rotation(), real!(115.0).to_radians());
663
664        let trans =
665            Transform2D::from_angle_origin(real!(-80.0).to_radians(), Vector2::new(1.4, 9.8));
666        assert_eq_approx!(trans.rotation(), real!(-80.0).to_radians());
667        assert_eq_approx!(trans.origin, Vector2::new(1.4, 9.8));
668
669        let trans = Transform2D::from_angle_scale_skew_origin(
670            real!(170.0).to_radians(),
671            Vector2::new(3.6, 8.0),
672            real!(20.0).to_radians(),
673            Vector2::new(2.4, 6.8),
674        );
675        assert_eq_approx!(trans.rotation(), real!(170.0).to_radians());
676        assert_eq_approx!(trans.scale(), Vector2::new(3.6, 8.0));
677        assert_eq_approx!(trans.skew(), real!(20.0).to_radians());
678        assert_eq_approx!(trans.origin, Vector2::new(2.4, 6.8));
679    }
680
681    // Tests translated from Godot.
682
683    const DUMMY_TRANSFORM: Transform2D = Transform2D::from_basis_origin(
684        Basis2D::from_cols(Vector2::new(1.0, 2.0), Vector2::new(3.0, 4.0)),
685        Vector2::new(5.0, 6.0),
686    );
687
688    #[test]
689    fn translation() {
690        let offset = Vector2::new(1.0, 2.0);
691
692        // Both versions should give the same result applied to identity.
693        assert_eq!(
694            Transform2D::IDENTITY.translated(offset),
695            Transform2D::IDENTITY.translated_local(offset)
696        );
697
698        // Check both versions against left and right multiplications.
699        let t = Transform2D::IDENTITY.translated(offset);
700        assert_eq!(DUMMY_TRANSFORM.translated(offset), t * DUMMY_TRANSFORM);
701        assert_eq!(
702            DUMMY_TRANSFORM.translated_local(offset),
703            DUMMY_TRANSFORM * t
704        );
705    }
706
707    #[test]
708    fn scaling() {
709        let scaling = Vector2::new(1.0, 2.0);
710
711        // Both versions should give the same result applied to identity.
712        assert_eq!(
713            Transform2D::IDENTITY.scaled(scaling),
714            Transform2D::IDENTITY.scaled_local(scaling)
715        );
716
717        // Check both versions against left and right multiplications.
718        let s: Transform2D = Transform2D::IDENTITY.scaled(scaling);
719        assert_eq!(DUMMY_TRANSFORM.scaled(scaling), s * DUMMY_TRANSFORM);
720        assert_eq!(DUMMY_TRANSFORM.scaled_local(scaling), DUMMY_TRANSFORM * s);
721    }
722
723    #[test]
724    fn rotation() {
725        let phi = 1.0;
726
727        // Both versions should give the same result applied to identity.
728        assert_eq!(
729            Transform2D::IDENTITY.rotated(phi),
730            Transform2D::IDENTITY.rotated_local(phi)
731        );
732
733        // Check both versions against left and right multiplications.
734        let r: Transform2D = Transform2D::IDENTITY.rotated(phi);
735        assert_eq!(DUMMY_TRANSFORM.rotated(phi), r * DUMMY_TRANSFORM);
736        assert_eq!(DUMMY_TRANSFORM.rotated_local(phi), DUMMY_TRANSFORM * r);
737    }
738
739    #[test]
740    fn interpolation() {
741        let rotate_scale_skew_pos: Transform2D = Transform2D::from_angle_scale_skew_origin(
742            real!(170.0).to_radians(),
743            Vector2::new(3.6, 8.0),
744            real!(20.0).to_radians(),
745            Vector2::new(2.4, 6.8),
746        );
747
748        let rotate_scale_skew_pos_halfway: Transform2D = Transform2D::from_angle_scale_skew_origin(
749            real!(85.0).to_radians(),
750            Vector2::new(2.3, 4.5),
751            real!(10.0).to_radians(),
752            Vector2::new(1.2, 3.4),
753        );
754
755        let interpolated: Transform2D =
756            Transform2D::IDENTITY.interpolate_with(&rotate_scale_skew_pos, 0.5);
757        assert_eq_approx!(interpolated.origin, rotate_scale_skew_pos_halfway.origin);
758        assert_eq_approx!(
759            interpolated.rotation(),
760            rotate_scale_skew_pos_halfway.rotation(),
761        );
762        assert_eq_approx!(interpolated.scale(), rotate_scale_skew_pos_halfway.scale());
763        assert_eq_approx!(interpolated.skew(), rotate_scale_skew_pos_halfway.skew());
764        assert_eq_approx!(interpolated, rotate_scale_skew_pos_halfway);
765
766        let interpolated = rotate_scale_skew_pos.interpolate_with(&Transform2D::IDENTITY, 0.5);
767        assert_eq_approx!(interpolated, rotate_scale_skew_pos_halfway);
768    }
769
770    #[test]
771    fn finite_number_checks() {
772        let x: Vector2 = Vector2::new(0.0, 1.0);
773        let infinite: Vector2 = Vector2::new(real::NAN, real::NAN);
774
775        assert!(
776            Transform2D::from_basis_origin(Basis2D::from_cols(x, x), x).is_finite(),
777            "let with: Transform2D all components finite should be finite",
778        );
779
780        assert!(
781            !Transform2D::from_basis_origin(Basis2D::from_cols(infinite, x), x).is_finite(),
782            "let with: Transform2D one component infinite should not be finite.",
783        );
784        assert!(
785            !Transform2D::from_basis_origin(Basis2D::from_cols(x, infinite), x).is_finite(),
786            "let with: Transform2D one component infinite should not be finite.",
787        );
788        assert!(
789            !Transform2D::from_basis_origin(Basis2D::from_cols(x, x), infinite).is_finite(),
790            "let with: Transform2D one component infinite should not be finite.",
791        );
792
793        assert!(
794            !Transform2D::from_basis_origin(Basis2D::from_cols(infinite, infinite), x).is_finite(),
795            "let with: Transform2D two components infinite should not be finite.",
796        );
797        assert!(
798            !Transform2D::from_basis_origin(Basis2D::from_cols(infinite, x), infinite).is_finite(),
799            "let with: Transform2D two components infinite should not be finite.",
800        );
801        assert!(
802            !Transform2D::from_basis_origin(Basis2D::from_cols(x, infinite), infinite).is_finite(),
803            "let with: Transform2D two components infinite should not be finite.",
804        );
805
806        assert!(
807            !Transform2D::from_basis_origin(Basis2D::from_cols(infinite, infinite), infinite)
808                .is_finite(),
809            "let with: Transform2D three components infinite should not be finite.",
810        );
811    }
812
813    #[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
814    #[test]
815    fn serde_roundtrip() {
816        let transform = Transform2D::default();
817        let expected_json = "{\"a\":{\"x\":0.0,\"y\":0.0},\"b\":{\"x\":0.0,\"y\":0.0},\"origin\":{\"x\":0.0,\"y\":0.0}}";
818
819        crate::builtin::test_utils::roundtrip(&transform, expected_json);
820    }
821}