godot_core/builtin/
transform3d.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;
10
11use godot_ffi as sys;
12use sys::{ffi_methods, ExtVariantType, GodotFfi};
13
14use crate::builtin::math::{ApproxEq, GlamConv, GlamType, XformInv};
15use crate::builtin::{real, Aabb, Basis, Plane, Projection, RAffine3, Vector3};
16
17/// Affine 3D transform (3x4 matrix).
18///
19/// Used for 3D linear transformations. Uses a basis + origin representation.
20///
21/// Expressed as a 3x4 matrix, this transform consists of 3 basis (column)
22/// vectors `a`, `b`, `c` as well as an origin `o`:
23/// ```text
24/// [ a.x  b.x  c.x  o.x ]
25/// [ a.y  b.y  c.y  o.y ]
26/// [ a.z  b.z  c.z  o.z ]
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`]: Basis
37/// [`Transform2D`]: crate::builtin::Transform2D
38/// [`Projection`]: Projection
39///
40/// # Transform operations
41///
42/// | Operation                      | Transform3D                    | Notes                                      |
43/// |--------------------------------|--------------------------------|--------------------------------------------|
44/// | Apply                          | `transform * v`                | Supports [`Aabb`], [`Plane`], [`Vector3`]. |
45/// | Apply inverse                  | `transform.xform_inv(v)`       | Supports [`Aabb`], [`Plane`], [`Vector3`]. |
46/// | Apply, no translate            | `transform.basis * v`          | Supports [`Vector3`].                      |
47/// | Apply inverse, no translate    | `transform.basis.xform_inv(v)` | Supports [`Vector3`].                      |
48///
49/// # Godot docs
50///
51/// [`Transform3D` (stable)](https://docs.godotengine.org/en/stable/classes/class_transform3d.html)
52#[derive(Default, Copy, Clone, PartialEq, Debug)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54#[repr(C)]
55pub struct Transform3D {
56    /// The basis is a matrix containing 3 vectors as its columns. They can be
57    /// interpreted as the basis vectors of the transformed coordinate system.
58    pub basis: Basis,
59
60    /// The new origin of the transformed coordinate system.
61    pub origin: Vector3,
62}
63
64impl Transform3D {
65    /// The identity transform, with no translation, rotation or scaling
66    /// applied. When applied to other data structures, `IDENTITY` performs no
67    /// transformation.
68    ///
69    /// _Godot equivalent: `Transform3D.IDENTITY`_
70    pub const IDENTITY: Self = Self::new(Basis::IDENTITY, Vector3::ZERO);
71
72    /// `Transform3D` with mirroring applied perpendicular to the YZ plane.
73    ///
74    /// _Godot equivalent: `Transform3D.FLIP_X`_
75    pub const FLIP_X: Self = Self::new(Basis::FLIP_X, Vector3::ZERO);
76
77    /// `Transform3D` with mirroring applied perpendicular to the XZ plane.
78    ///
79    /// _Godot equivalent: `Transform3D.FLIP_Y`_
80    pub const FLIP_Y: Self = Self::new(Basis::FLIP_Y, Vector3::ZERO);
81
82    /// `Transform3D` with mirroring applied perpendicular to the XY plane.
83    ///
84    /// _Godot equivalent: `Transform3D.FLIP_Z`_
85    pub const FLIP_Z: Self = Self::new(Basis::FLIP_Z, Vector3::ZERO);
86
87    /// Create a new transform from a [`Basis`] and a [`Vector3`].
88    ///
89    /// _Godot equivalent: `Transform3D(Basis basis, Vector3 origin)`_
90    pub const fn new(basis: Basis, origin: Vector3) -> Self {
91        Self { basis, origin }
92    }
93
94    /// Create a new transform from 4 matrix-columns.
95    ///
96    /// _Godot equivalent: `Transform3D(Vector3 x_axis, Vector3 y_axis, Vector3 z_axis, Vector3 origin)`_, see [`Basis`][crate::builtin::Basis]
97    /// for why it's changed
98    pub const fn from_cols(a: Vector3, b: Vector3, c: Vector3, origin: Vector3) -> Self {
99        Self {
100            basis: Basis::from_cols(a, b, c),
101            origin,
102        }
103    }
104
105    /// Constructs a `Transform3D` from a `Projection` by trimming the last row of the projection matrix.
106    ///
107    /// _Godot equivalent: `Transform3D(Projection from)`_
108    pub fn from_projection(proj: &Projection) -> Self {
109        let a = Vector3::new(proj.cols[0].x, proj.cols[0].y, proj.cols[0].z);
110        let b = Vector3::new(proj.cols[1].x, proj.cols[1].y, proj.cols[1].z);
111        let c = Vector3::new(proj.cols[2].x, proj.cols[2].y, proj.cols[2].z);
112        let o = Vector3::new(proj.cols[3].x, proj.cols[3].y, proj.cols[3].z);
113
114        Self {
115            basis: Basis::from_cols(a, b, c),
116            origin: o,
117        }
118    }
119
120    /// Unstable, used to simplify codegen. Too many parameters for public API and easy to have off-by-one, `from_cols()` is preferred.
121    #[doc(hidden)]
122    #[rustfmt::skip]
123    #[allow(clippy::too_many_arguments)]
124    pub const fn __internal_codegen(
125        ax: real, ay: real, az: real,
126        bx: real, by: real, bz: real,
127        cx: real, cy: real, cz: real,
128        ox: real, oy: real, oz: real
129    ) -> Self {
130        Self::from_cols(
131            Vector3::new(ax, ay, az),
132            Vector3::new(bx, by, bz),
133            Vector3::new(cx, cy, cz),
134            Vector3::new(ox, oy, oz),
135        )
136    }
137
138    /// Returns the inverse of the transform, under the assumption that the
139    /// transformation is composed of rotation, scaling and translation.
140    #[must_use]
141    pub fn affine_inverse(&self) -> Self {
142        self.glam(|aff| aff.inverse())
143    }
144
145    /// Returns a transform interpolated between this transform and another by
146    /// a given weight (on the range of 0.0 to 1.0).
147    #[must_use]
148    pub fn interpolate_with(&self, other: &Self, weight: real) -> Self {
149        let src_scale = self.basis.get_scale();
150        let src_rot = self.basis.get_quaternion().normalized();
151        let src_loc = self.origin;
152
153        let dst_scale = other.basis.get_scale();
154        let dst_rot = other.basis.get_quaternion().normalized();
155        let dst_loc = other.origin;
156
157        let mut basis = Basis::from_scale(src_scale.lerp(dst_scale, weight));
158        basis = Basis::from_quaternion(src_rot.slerp(dst_rot, weight)) * basis;
159
160        Self {
161            basis,
162            origin: src_loc.lerp(dst_loc, weight),
163        }
164    }
165
166    /// Returns true if this transform is finite by calling `is_finite` on the
167    /// basis and origin.
168    pub fn is_finite(&self) -> bool {
169        self.basis.is_finite() && self.origin.is_finite()
170    }
171
172    #[must_use]
173    pub fn looking_at(&self, target: Vector3, up: Vector3, use_model_front: bool) -> Self {
174        Self {
175            basis: Basis::looking_at(target - self.origin, up, use_model_front),
176            origin: self.origin,
177        }
178    }
179
180    /// Returns the transform with the basis orthogonal (90 degrees), and
181    /// normalized axis vectors (scale of 1 or -1).
182    ///
183    /// _Godot equivalent: Transform3D.orthonormalized()_
184    #[must_use]
185    pub fn orthonormalized(&self) -> Self {
186        Self {
187            basis: self.basis.orthonormalized(),
188            origin: self.origin,
189        }
190    }
191
192    /// Returns a copy of the transform rotated by the given `angle` (in radians).
193    /// This method is an optimized version of multiplying the given transform `X`
194    /// with a corresponding rotation transform `R` from the left, i.e., `R * X`.
195    /// This can be seen as transforming with respect to the global/parent frame.
196    ///
197    /// _Godot equivalent: `Transform2D.rotated()`_
198    #[must_use]
199    pub fn rotated(&self, axis: Vector3, angle: real) -> Self {
200        let rotation = Basis::from_axis_angle(axis, angle);
201        Self {
202            basis: rotation * self.basis,
203            origin: rotation * self.origin,
204        }
205    }
206    /// Returns a copy of the transform rotated by the given `angle` (in radians).
207    /// This method is an optimized version of multiplying the given transform `X`
208    /// with a corresponding rotation transform `R` from the right, i.e., `X * R`.
209    /// This can be seen as transforming with respect to the local frame.
210    ///
211    /// _Godot equivalent: `Transform2D.rotated_local()`_
212    #[must_use]
213    pub fn rotated_local(&self, axis: Vector3, angle: real) -> Self {
214        Self {
215            basis: self.basis * Basis::from_axis_angle(axis, angle),
216            origin: self.origin,
217        }
218    }
219
220    /// Returns a copy of the transform scaled by the given scale factor.
221    /// This method is an optimized version of multiplying the given transform `X`
222    /// with a corresponding scaling transform `S` from the left, i.e., `S * X`.
223    /// This can be seen as transforming with respect to the global/parent frame.
224    ///
225    /// _Godot equivalent: `Transform2D.scaled()`_
226    #[must_use]
227    pub fn scaled(&self, scale: Vector3) -> Self {
228        Self {
229            basis: Basis::from_scale(scale) * self.basis,
230            origin: self.origin * scale,
231        }
232    }
233
234    /// Returns a copy of the transform scaled by the given scale factor.
235    /// This method is an optimized version of multiplying the given transform `X`
236    /// with a corresponding scaling transform `S` from the right, i.e., `X * S`.
237    /// This can be seen as transforming with respect to the local frame.
238    ///
239    /// _Godot equivalent: `Transform2D.scaled_local()`_
240    #[must_use]
241    pub fn scaled_local(&self, scale: Vector3) -> Self {
242        Self {
243            basis: self.basis * Basis::from_scale(scale),
244            origin: self.origin,
245        }
246    }
247
248    /// Returns a copy of the transform translated by the given offset.
249    /// This method is an optimized version of multiplying the given transform `X`
250    /// with a corresponding translation transform `T` from the left, i.e., `T * X`.
251    /// This can be seen as transforming with respect to the global/parent frame.
252    ///
253    /// _Godot equivalent: `Transform2D.translated()`_
254    #[must_use]
255    pub fn translated(&self, offset: Vector3) -> Self {
256        Self {
257            basis: self.basis,
258            origin: self.origin + offset,
259        }
260    }
261
262    /// Returns a copy of the transform translated by the given offset.
263    /// This method is an optimized version of multiplying the given transform `X`
264    /// with a corresponding translation transform `T` from the right, i.e., `X * T`.
265    /// This can be seen as transforming with respect to the local frame.
266    ///
267    /// _Godot equivalent: `Transform2D.translated()`_
268    #[must_use]
269    pub fn translated_local(&self, offset: Vector3) -> Self {
270        Self {
271            basis: self.basis,
272            origin: self.origin + (self.basis * offset),
273        }
274    }
275}
276
277impl Display for Transform3D {
278    /// Formats the value with the given formatter.  [Read more](https://doc.rust-lang.org/1.79.0/core/fmt/trait.Display.html#tymethod.fmt)
279    ///
280    /// The output is similar to Godot's, but calls the columns a/b/c instead of X/Y/Z.  See [`Basis`][crate::builtin::Basis] for why.
281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282        // Godot output:
283        // [X: (1, 2, 3), Y: (4, 5, 6), Z: (7, 8, 9), O: (10, 11, 12)]
284        // Where X,Y,Z,O are the columns
285        let [a, b, c] = self.basis.to_cols();
286        let o = self.origin;
287
288        write!(f, "[a: {a}, b: {b}, c: {c}, o: {o}]")
289    }
290}
291
292impl From<Basis> for Transform3D {
293    /// Create a new transform with origin `(0,0,0)` from this basis.
294    fn from(basis: Basis) -> Self {
295        Self::new(basis, Vector3::ZERO)
296    }
297}
298
299impl Mul for Transform3D {
300    type Output = Self;
301
302    fn mul(self, rhs: Self) -> Self::Output {
303        self.glam2(&rhs, |a, b| a * b)
304    }
305}
306
307impl Mul<Vector3> for Transform3D {
308    type Output = Vector3;
309
310    fn mul(self, rhs: Vector3) -> Self::Output {
311        self.glam2(&rhs, |t, v| t.transform_point3(v))
312    }
313}
314
315impl XformInv<Vector3> for Transform3D {
316    /// Inversely transforms given [`Vector3`] by this transformation matrix,
317    /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
318    ///
319    /// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * vector` can be used instead. See [`Transform3D::affine_inverse()`].
320    ///
321    /// _Godot equivalent: `aabb * transform`_
322    fn xform_inv(&self, rhs: Vector3) -> Vector3 {
323        let v = rhs - self.origin;
324        self.basis.xform_inv(v)
325    }
326}
327
328impl Mul<real> for Transform3D {
329    type Output = Self;
330
331    fn mul(self, rhs: real) -> Self::Output {
332        Self {
333            basis: self.basis * rhs,
334            origin: self.origin * rhs,
335        }
336    }
337}
338
339impl Mul<Aabb> for Transform3D {
340    type Output = Aabb;
341
342    /// Transforms each coordinate in `rhs.position` and `rhs.end()` individually by this transform, then
343    /// creates an `Aabb` containing all of them.
344    fn mul(self, rhs: Aabb) -> Self::Output {
345        // https://web.archive.org/web/20220317024830/https://dev.theomader.com/transform-bounding-boxes/
346        let xa = self.basis.col_a() * rhs.position.x;
347        let xb = self.basis.col_a() * rhs.end().x;
348
349        let ya = self.basis.col_b() * rhs.position.y;
350        let yb = self.basis.col_b() * rhs.end().y;
351
352        let za = self.basis.col_c() * rhs.position.z;
353        let zb = self.basis.col_c() * rhs.end().z;
354
355        let position = Vector3::coord_min(xa, xb)
356            + Vector3::coord_min(ya, yb)
357            + Vector3::coord_min(za, zb)
358            + self.origin;
359        let end = Vector3::coord_max(xa, xb)
360            + Vector3::coord_max(ya, yb)
361            + Vector3::coord_max(za, zb)
362            + self.origin;
363        Aabb::new(position, end - position)
364    }
365}
366
367impl XformInv<Aabb> for Transform3D {
368    /// Inversely transforms each vertex in given [`Aabb`] individually by this transformation matrix,
369    /// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not),
370    /// and then creates an `Aabb` encompassing all of them.
371    ///
372    /// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * aabb` can be used instead. See [`Transform3D::affine_inverse()`].
373    ///
374    /// _Godot equivalent: `aabb * transform`_
375    fn xform_inv(&self, rhs: Aabb) -> Aabb {
376        // Same as Godot's `Transform3D::xform_inv` but omits unnecessary `Aabb::expand_to`.
377        // There is probably some more clever way to do that.
378
379        // Use the first vertex initialize our min/max.
380        let end = self.xform_inv(rhs.end());
381        // `min` is the "lowest" vertex of our Aabb, `max` is the farthest vertex.
382        let (mut min, mut max) = (end, end);
383
384        let vertices = [
385            Vector3::new(
386                rhs.position.x + rhs.size.x,
387                rhs.position.y + rhs.size.y,
388                rhs.position.z,
389            ),
390            Vector3::new(
391                rhs.position.x + rhs.size.x,
392                rhs.position.y,
393                rhs.position.z + rhs.size.z,
394            ),
395            Vector3::new(rhs.position.x + rhs.size.x, rhs.position.y, rhs.position.z),
396            Vector3::new(
397                rhs.position.x,
398                rhs.position.y + rhs.size.y,
399                rhs.position.z + rhs.size.z,
400            ),
401            Vector3::new(rhs.position.x, rhs.position.y + rhs.size.y, rhs.position.z),
402            Vector3::new(rhs.position.x, rhs.position.y, rhs.position.z + rhs.size.z),
403            rhs.position,
404        ];
405
406        for v in vertices {
407            let transformed = self.xform_inv(v);
408            min = Vector3::coord_min(min, transformed);
409            max = Vector3::coord_max(max, transformed);
410        }
411
412        Aabb::new(min, max - min)
413    }
414}
415
416impl Mul<Plane> for Transform3D {
417    type Output = Plane;
418
419    fn mul(self, rhs: Plane) -> Self::Output {
420        let point = self * (rhs.normal * rhs.d);
421
422        let basis = self.basis.inverse().transposed();
423
424        Plane::from_point_normal(point, (basis * rhs.normal).normalized())
425    }
426}
427
428impl XformInv<Plane> for Transform3D {
429    /// Inversely transforms (multiplies) the Plane by the given Transform3D transformation matrix.
430    ///
431    /// `transform.xform_inv(plane)` is equivalent to `transform.affine_inverse() * plane`. See [`Transform3D::affine_inverse()`].
432    ///
433    /// _Godot equivalent: `plane * transform`_
434    fn xform_inv(&self, rhs: Plane) -> Plane {
435        self.affine_inverse() * rhs
436    }
437}
438
439impl ApproxEq for Transform3D {
440    /// Returns if the two transforms are approximately equal, by comparing `basis` and `origin` separately.
441    fn approx_eq(&self, other: &Self) -> bool {
442        Basis::approx_eq(&self.basis, &other.basis)
443            && Vector3::approx_eq(&self.origin, &other.origin)
444    }
445}
446
447impl GlamType for RAffine3 {
448    type Mapped = Transform3D;
449
450    fn to_front(&self) -> Self::Mapped {
451        Transform3D::new(self.matrix3.to_front(), self.translation.to_front())
452    }
453
454    // When `double-precision` is enabled this will complain. But it is
455    // needed for when it is not enabled.
456    #[allow(clippy::useless_conversion)]
457    fn from_front(mapped: &Self::Mapped) -> Self {
458        Self {
459            matrix3: mapped.basis.to_glam().into(),
460            translation: mapped.origin.to_glam().into(),
461        }
462    }
463}
464
465impl GlamConv for Transform3D {
466    type Glam = RAffine3;
467}
468
469// SAFETY:
470// This type is represented as `Self` in Godot, so `*mut Self` is sound.
471unsafe impl GodotFfi for Transform3D {
472    const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::TRANSFORM3D);
473
474    ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
475}
476
477crate::meta::impl_godot_as_self!(Transform3D: ByValue);
478
479#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
480mod test {
481    use super::*;
482
483    // Tests translated from Godot.
484
485    const DUMMY_TRANSFORM: Transform3D = Transform3D::new(
486        Basis::from_cols(
487            Vector3::new(1.0, 2.0, 3.0),
488            Vector3::new(4.0, 5.0, 6.0),
489            Vector3::new(7.0, 8.0, 9.0),
490        ),
491        Vector3::new(10.0, 11.0, 12.0),
492    );
493
494    #[test]
495    fn translation() {
496        let offset = Vector3::new(1.0, 2.0, 3.0);
497
498        // Both versions should give the same result applied to identity.
499        assert_eq!(
500            Transform3D::IDENTITY.translated(offset),
501            Transform3D::IDENTITY.translated_local(offset)
502        );
503
504        // Check both versions against left and right multiplications.
505        let t = Transform3D::IDENTITY.translated(offset);
506        assert_eq!(DUMMY_TRANSFORM.translated(offset), t * DUMMY_TRANSFORM);
507        assert_eq!(
508            DUMMY_TRANSFORM.translated_local(offset),
509            DUMMY_TRANSFORM * t
510        );
511    }
512
513    #[test]
514    fn scaling() {
515        let scaling = Vector3::new(1.0, 2.0, 3.0);
516
517        // Both versions should give the same result applied to identity.
518        assert_eq!(
519            Transform3D::IDENTITY.scaled(scaling),
520            Transform3D::IDENTITY.scaled_local(scaling)
521        );
522
523        // Check both versions against left and right multiplications.
524        let s = Transform3D::IDENTITY.scaled(scaling);
525        assert_eq!(DUMMY_TRANSFORM.scaled(scaling), s * DUMMY_TRANSFORM);
526        assert_eq!(DUMMY_TRANSFORM.scaled_local(scaling), DUMMY_TRANSFORM * s);
527    }
528
529    #[test]
530    fn rotation() {
531        let axis = Vector3::new(1.0, 2.0, 3.0).normalized();
532        let phi: real = 1.0;
533
534        // Both versions should give the same result applied to identity.
535        assert_eq!(
536            Transform3D::IDENTITY.rotated(axis, phi),
537            Transform3D::IDENTITY.rotated_local(axis, phi)
538        );
539
540        // Check both versions against left and right multiplications.
541        let r = Transform3D::IDENTITY.rotated(axis, phi);
542        assert_eq!(DUMMY_TRANSFORM.rotated(axis, phi), r * DUMMY_TRANSFORM);
543        assert_eq!(
544            DUMMY_TRANSFORM.rotated_local(axis, phi),
545            DUMMY_TRANSFORM * r
546        );
547    }
548
549    #[test]
550    fn finite_number_checks() {
551        let y = Vector3::new(0.0, 1.0, 2.0);
552        let infinite_vec = Vector3::new(real::NAN, real::NAN, real::NAN);
553        let x = Basis::from_rows(y, y, y);
554        let infinite_basis = Basis::from_rows(infinite_vec, infinite_vec, infinite_vec);
555
556        assert!(
557            Transform3D::new(x, y).is_finite(),
558            "Transform3D with all components finite should be finite",
559        );
560
561        assert!(
562            !Transform3D::new(x, infinite_vec).is_finite(),
563            "Transform3D with one component infinite should not be finite.",
564        );
565        assert!(
566            !Transform3D::new(infinite_basis, y).is_finite(),
567            "Transform3D with one component infinite should not be finite.",
568        );
569
570        assert!(
571            !Transform3D::new(infinite_basis, infinite_vec).is_finite(),
572            "Transform3D with two components infinite should not be finite.",
573        );
574    }
575
576    #[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
577    #[test]
578    fn serde_roundtrip() {
579        let transform = Transform3D::default();
580        let expected_json = "{\"basis\":{\"rows\":[{\"x\":1.0,\"y\":0.0,\"z\":0.0},{\"x\":0.0,\"y\":1.0,\"z\":0.0},{\"x\":0.0,\"y\":0.0,\"z\":1.0}]},\"origin\":{\"x\":0.0,\"y\":0.0,\"z\":0.0}}";
581
582        crate::builtin::test_utils::roundtrip(&transform, expected_json);
583    }
584}