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}