Skip to content

Commit 7ed6dc9

Browse files
committed
Implement XformInv<T> for Transforms.
- Create new trait `XformInv<...>`. - Implement `XformInv<Rect2/Vector2>` for `Transform2D` and `Transform2D::basis_xform_inv`. - Implement `XformInv<Aabb/Plane/Vector3>` for `Transform3D` and `XformInv<Vector3>` for `Basis`. - Document transform/basis operations.
1 parent a752851 commit 7ed6dc9

File tree

8 files changed

+293
-8
lines changed

8 files changed

+293
-8
lines changed

godot-core/src/builtin/basis.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use godot_ffi as sys;
99
use sys::{ffi_methods, GodotFfi};
1010

11-
use crate::builtin::math::{ApproxEq, FloatExt, GlamConv, GlamType};
11+
use crate::builtin::math::{ApproxEq, FloatExt, GlamConv, GlamType, XformInv};
1212
use crate::builtin::real_consts::FRAC_PI_2;
1313
use crate::builtin::{real, EulerOrder, Quaternion, RMat3, RQuat, RVec2, RVec3, Vector3};
1414
use std::cmp::Ordering;
@@ -629,6 +629,24 @@ impl Mul<Vector3> for Basis {
629629
}
630630
}
631631

632+
impl XformInv<Vector3> for Basis {
633+
/// Inversely transforms given [`Vector3`] by this basis,
634+
/// under the assumption that the basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
635+
///
636+
/// `basis.xform_inv(vector)` is equivalent to `basis.transposed() * vector`. See [`Basis::transposed()`].
637+
///
638+
/// For transforming by inverse of a non-orthonormal basis (e.g. with scaling) `basis.inverse() * vector` can be used instead. See [`Basis::inverse()`].
639+
///
640+
/// _Godot equivalent: `vector * basis`_
641+
fn xform_inv(&self, rhs: Vector3) -> Vector3 {
642+
Vector3::new(
643+
self.col_a().dot(rhs),
644+
self.col_b().dot(rhs),
645+
self.col_c().dot(rhs),
646+
)
647+
}
648+
}
649+
632650
// SAFETY:
633651
// This type is represented as `Self` in Godot, so `*mut Self` is sound.
634652
unsafe impl GodotFfi for Basis {

godot-core/src/builtin/math/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
mod approx_eq;
99
mod float;
1010
mod glam_helpers;
11+
mod xform;
1112

1213
pub use crate::{assert_eq_approx, assert_ne_approx};
1314
pub use approx_eq::ApproxEq;
1415
pub use float::FloatExt;
16+
pub use xform::XformInv;
1517

1618
// Internal glam re-exports
1719
pub(crate) use glam_helpers::*;

godot-core/src/builtin/math/xform.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
8+
/// Applying inverse transforms.
9+
///
10+
/// See also: [`Transform2D`](crate::builtin::Transform2D), [`Transform3D`](crate::builtin::Transform3D), [`Basis`](crate::builtin::Basis).
11+
///
12+
/// _Godot equivalent: `rhs * mat`_
13+
pub trait XformInv<T> {
14+
fn xform_inv(&self, rhs: T) -> T;
15+
}

godot-core/src/builtin/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub use crate::sys::VariantType;
2626
pub mod __prelude_reexport {
2727
use super::*;
2828

29+
pub use super::math::XformInv;
2930
pub use aabb::*;
3031
pub use basis::*;
3132
pub use callable::*;

godot-core/src/builtin/transform2d.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use godot_ffi as sys;
99
use sys::{ffi_methods, GodotFfi};
1010

11-
use crate::builtin::math::{assert_ne_approx, ApproxEq, FloatExt, GlamConv, GlamType};
11+
use crate::builtin::math::{assert_ne_approx, ApproxEq, FloatExt, GlamConv, GlamType, XformInv};
1212
use crate::builtin::real_consts::PI;
1313
use crate::builtin::{real, RAffine2, RMat2, Rect2, Vector2};
1414

@@ -37,6 +37,15 @@ use std::ops::{Mul, MulAssign};
3737
/// [`Transform3D`]: crate::builtin::Transform3D
3838
/// [`Projection`]: crate::builtin::Projection
3939
///
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+
///
4049
/// # Godot docs
4150
///
4251
/// [`Transform2D` (stable)](https://docs.godotengine.org/en/stable/classes/class_transform2d.html)
@@ -349,6 +358,19 @@ impl Mul<Vector2> for Transform2D {
349358
}
350359
}
351360

361+
impl 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+
352374
impl Mul<real> for Transform2D {
353375
type Output = Self;
354376

@@ -370,9 +392,41 @@ impl Mul<Rect2> for Transform2D {
370392
let ya = self.b * rhs.position.y;
371393
let yb = self.b * rhs.end().y;
372394

373-
let position = Vector2::coord_min(xa, xb) + Vector2::coord_min(ya, yb) + self.origin;
374-
let end = Vector2::coord_max(xa, xb) + Vector2::coord_max(ya, yb) + self.origin;
375-
Rect2::new(position, end - position)
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+
401+
impl 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)
376430
}
377431
}
378432

godot-core/src/builtin/transform3d.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use godot_ffi as sys;
99
use sys::{ffi_methods, GodotFfi};
1010

11-
use crate::builtin::math::{ApproxEq, GlamConv, GlamType};
11+
use crate::builtin::math::{ApproxEq, GlamConv, GlamType, XformInv};
1212
use crate::builtin::{real, Aabb, Basis, Plane, Projection, RAffine3, Vector3};
1313

1414
use std::fmt::Display;
@@ -37,6 +37,15 @@ use std::ops::Mul;
3737
/// [`Transform2D`]: crate::builtin::Transform2D
3838
/// [`Projection`]: Projection
3939
///
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+
///
4049
/// # Godot docs
4150
///
4251
/// [`Transform3D` (stable)](https://docs.godotengine.org/en/stable/classes/class_transform3d.html)
@@ -303,6 +312,19 @@ impl Mul<Vector3> for Transform3D {
303312
}
304313
}
305314

315+
impl 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+
306328
impl Mul<real> for Transform3D {
307329
type Output = Self;
308330

@@ -342,6 +364,55 @@ impl Mul<Aabb> for Transform3D {
342364
}
343365
}
344366

367+
impl 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+
345416
impl Mul<Plane> for Transform3D {
346417
type Output = Plane;
347418

@@ -354,6 +425,17 @@ impl Mul<Plane> for Transform3D {
354425
}
355426
}
356427

428+
impl 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+
357439
impl ApproxEq for Transform3D {
358440
/// Returns if the two transforms are approximately equal, by comparing `basis` and `origin` separately.
359441
fn approx_eq(&self, other: &Self) -> bool {

itest/rust/src/builtin_tests/geometry/transform2d_test.rs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use crate::framework::itest;
99

1010
use godot::builtin::inner::InnerTransform2D;
11-
use godot::builtin::{real, RealConv, Rect2, Transform2D, VariantOperator, Vector2};
11+
use godot::builtin::{real, RealConv, Rect2, Transform2D, VariantOperator, Vector2, XformInv};
1212
use godot::meta::ToGodot;
1313
use godot::private::class_macros::assert_eq_approx;
1414

@@ -18,6 +18,12 @@ const TEST_TRANSFORM: Transform2D = Transform2D::from_cols(
1818
Vector2::new(5.0, 6.0),
1919
);
2020

21+
const TEST_TRANSFORM_ORTHONORMAL: Transform2D = Transform2D::from_cols(
22+
Vector2::new(1.0, 0.0),
23+
Vector2::new(0.0, 1.0),
24+
Vector2::new(5.0, 6.0),
25+
);
26+
2127
#[itest]
2228
fn transform2d_equiv() {
2329
let inner = InnerTransform2D::from_outer(&TEST_TRANSFORM);
@@ -106,3 +112,48 @@ fn transform2d_xform_equiv() {
106112
"operator: Transform2D * Rect2 (2)"
107113
);
108114
}
115+
116+
#[itest]
117+
fn transform2d_xform_inv_equiv() {
118+
let vec = Vector2::new(1.0, 2.0);
119+
120+
assert_eq_approx!(
121+
TEST_TRANSFORM_ORTHONORMAL.xform_inv(vec),
122+
vec.to_variant()
123+
.evaluate(
124+
&TEST_TRANSFORM_ORTHONORMAL.to_variant(),
125+
VariantOperator::MULTIPLY
126+
)
127+
.unwrap()
128+
.to::<Vector2>(),
129+
"operator: Transform2D * Vector2"
130+
);
131+
132+
let rect_2 = Rect2::new(Vector2::new(1.0, 2.0), Vector2::new(3.0, 4.0));
133+
134+
assert_eq_approx!(
135+
TEST_TRANSFORM_ORTHONORMAL.xform_inv(rect_2),
136+
rect_2
137+
.to_variant()
138+
.evaluate(
139+
&TEST_TRANSFORM_ORTHONORMAL.to_variant(),
140+
VariantOperator::MULTIPLY
141+
)
142+
.unwrap()
143+
.to::<Rect2>(),
144+
"operator: Transform2D * Rect2 (1)"
145+
);
146+
147+
assert_eq_approx!(
148+
TEST_TRANSFORM_ORTHONORMAL.rotated(0.8).xform_inv(rect_2),
149+
rect_2
150+
.to_variant()
151+
.evaluate(
152+
&TEST_TRANSFORM_ORTHONORMAL.rotated(0.8).to_variant(),
153+
VariantOperator::MULTIPLY
154+
)
155+
.unwrap()
156+
.to::<Rect2>(),
157+
"operator: Transform2D * Rect2 (2)"
158+
);
159+
}

0 commit comments

Comments
 (0)