Skip to content

Implement XformInv<...> for Transform2D, Transform3D, Basis. #1082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion godot-core/src/builtin/basis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

use crate::builtin::math::{ApproxEq, FloatExt, GlamConv, GlamType};
use crate::builtin::math::{ApproxEq, FloatExt, GlamConv, GlamType, XformInv};
use crate::builtin::real_consts::FRAC_PI_2;
use crate::builtin::{real, EulerOrder, Quaternion, RMat3, RQuat, RVec2, RVec3, Vector3};
use std::cmp::Ordering;
Expand Down Expand Up @@ -629,6 +629,30 @@ impl Mul<Vector3> for Basis {
}
}

impl XformInv<Vector3> for Basis {
/// Inversely transforms given [`Vector3`] by this basis,
/// under the assumption that the basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
///
/// Since given basis is assumed to be orthonormal (i.e. it is both orthogonal – with all axis perpendicular to each other – and all the axis are normalized),
/// `basis.transposed()` (matrix flipped over its diagonal) is equal to `basis.inverse()`
/// (i.e. `basis * basis.transposed() == basis * basis.inverse() == Basis::Identity`),
/// thus `basis.xform_inv(vector)` is equivalent both to `basis.transposed() * vector` and `basis.inverse() * vector`.
///
/// See [`Basis::transposed()`] and [Godot docs (stable) for `Basis`](https://docs.godotengine.org/en/stable/classes/class_basis.html)
/// with following [Matrices and Transform tutorial](https://docs.godotengine.org/en/stable/tutorials/math/matrices_and_transforms.html).
///
/// For transforming by inverse of a non-orthonormal basis (e.g. with scaling) `basis.inverse() * vector` can be used instead. See [`Basis::inverse()`].
///
/// _Godot equivalent: `vector * basis`_
fn xform_inv(&self, rhs: Vector3) -> Vector3 {
Vector3::new(
self.col_a().dot(rhs),
self.col_b().dot(rhs),
self.col_c().dot(rhs),
)
}
}

// SAFETY:
// This type is represented as `Self` in Godot, so `*mut Self` is sound.
unsafe impl GodotFfi for Basis {
Expand Down
2 changes: 2 additions & 0 deletions godot-core/src/builtin/math/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
mod approx_eq;
mod float;
mod glam_helpers;
mod xform;

pub use crate::{assert_eq_approx, assert_ne_approx};
pub use approx_eq::ApproxEq;
pub use float::FloatExt;
pub use xform::XformInv;

// Internal glam re-exports
pub(crate) use glam_helpers::*;
Expand Down
15 changes: 15 additions & 0 deletions godot-core/src/builtin/math/xform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

/// Applying inverse transforms.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never includes scaling or translation, right? Might be worth mentioning... 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

///
/// See also: [`Transform2D`](crate::builtin::Transform2D), [`Transform3D`](crate::builtin::Transform3D), [`Basis`](crate::builtin::Basis).
///
/// _Godot equivalent: `rhs * mat`_
pub trait XformInv<T>: std::ops::Mul<T> {
fn xform_inv(&self, rhs: T) -> T;
}
1 change: 1 addition & 0 deletions godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub use crate::sys::VariantType;
pub mod __prelude_reexport {
use super::*;

pub use super::math::XformInv;
pub use aabb::*;
pub use basis::*;
pub use callable::*;
Expand Down
62 changes: 58 additions & 4 deletions godot-core/src/builtin/transform2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

use crate::builtin::math::{assert_ne_approx, ApproxEq, FloatExt, GlamConv, GlamType};
use crate::builtin::math::{assert_ne_approx, ApproxEq, FloatExt, GlamConv, GlamType, XformInv};
use crate::builtin::real_consts::PI;
use crate::builtin::{real, RAffine2, RMat2, Rect2, Vector2};

Expand Down Expand Up @@ -37,6 +37,15 @@ use std::ops::{Mul, MulAssign};
/// [`Transform3D`]: crate::builtin::Transform3D
/// [`Projection`]: crate::builtin::Projection
///
/// # Transform operations
///
/// | Operation | Transform2D | Notes |
/// |--------------------------------|----------------------------------|-------------------------------------|
/// | Apply | `transform * v` | Supports [`Rect2`] and [`Vector2`]. |
/// | Apply inverse | `transform.xform_inv(v)` | Supports [`Rect2`] and [`Vector2`]. |
/// | Apply, no translate | `transform.basis_xform(v)` | Supports [`Vector2`]. |
/// | Apply inverse, no translate | `transform.basis_xform_inv(v)` | Supports [`Vector2`]. |
///
/// # Godot docs
///
/// [`Transform2D` (stable)](https://docs.godotengine.org/en/stable/classes/class_transform2d.html)
Expand Down Expand Up @@ -349,6 +358,19 @@ impl Mul<Vector2> for Transform2D {
}
}

impl XformInv<Vector2> for Transform2D {
/// Inversely transforms (multiplies) the given [`Vector2`] by this transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
///
/// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * vector` can be used instead. See: [`Transform2D::affine_inverse()`].
///
/// _Godot equivalent: `vector * transform` or `transform.inverse() * vector`_
fn xform_inv(&self, rhs: Vector2) -> Vector2 {
let v = rhs - self.origin;
self.basis_xform_inv(v)
}
}

impl Mul<real> for Transform2D {
type Output = Self;

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

let position = Vector2::coord_min(xa, xb) + Vector2::coord_min(ya, yb) + self.origin;
let end = Vector2::coord_max(xa, xb) + Vector2::coord_max(ya, yb) + self.origin;
Rect2::new(position, end - position)
let position = Vector2::coord_min(xa, xb) + Vector2::coord_min(ya, yb);
let end = Vector2::coord_max(xa, xb) + Vector2::coord_max(ya, yb);
Rect2::new(position + self.origin, end - position)
}
}

impl XformInv<Rect2> for Transform2D {
/// 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).
///
/// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * vector` can be used instead. See: [`Transform2D::affine_inverse()`].
///
/// _Godot equivalent: `rect2 * transform` or `transform.inverse() * rect2`_
fn xform_inv(&self, rhs: Rect2) -> Rect2 {
// https://web.archive.org/web/20220317024830/https://dev.theomader.com/transform-bounding-boxes/
// Same as Godot's `Transform2D::xform_inv` but omits unnecessary `Rect2::expand_to`.
// There is probably some more clever way to do that.

// Use the first point initialize our min/max.
let start = self.xform_inv(rhs.position);
// `min` is position of our aabb, `max` is the farthest vertex.
let (mut min, mut max) = (start, start);

let vertices = [
Vector2::new(rhs.position.x, rhs.position.y + rhs.size.y),
rhs.end(),
Vector2::new(rhs.position.x + rhs.size.x, rhs.position.y),
];

for v in vertices {
let transformed = self.xform_inv(v);
min = Vector2::coord_min(min, transformed);
max = Vector2::coord_max(max, transformed);
}

Rect2::new(min, max - min)
}
}

Expand Down
84 changes: 83 additions & 1 deletion godot-core/src/builtin/transform3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

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

use std::fmt::Display;
Expand Down Expand Up @@ -37,6 +37,15 @@ use std::ops::Mul;
/// [`Transform2D`]: crate::builtin::Transform2D
/// [`Projection`]: Projection
///
/// # Transform operations
///
/// | Operation | Transform3D | Notes |
/// |--------------------------------|--------------------------------|--------------------------------------------|
/// | Apply | `transform * v` | Supports [`Aabb`], [`Plane`], [`Vector3`]. |
/// | Apply inverse | `transform.xform_inv(v)` | Supports [`Aabb`], [`Plane`], [`Vector3`]. |
/// | Apply, no translate | `transform.basis * v` | Supports [`Vector3`]. |
/// | Apply inverse, no translate | `transform.basis.xform_inv(v)` | Supports [`Vector3`]. |
///
/// # Godot docs
///
/// [`Transform3D` (stable)](https://docs.godotengine.org/en/stable/classes/class_transform3d.html)
Expand Down Expand Up @@ -303,6 +312,19 @@ impl Mul<Vector3> for Transform3D {
}
}

impl XformInv<Vector3> for Transform3D {
/// Inversely transforms given [`Vector3`] by this transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not).
///
/// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * vector` can be used instead. See [`Transform3D::affine_inverse()`].
///
/// _Godot equivalent: `aabb * transform`_
fn xform_inv(&self, rhs: Vector3) -> Vector3 {
let v = rhs - self.origin;
self.basis.xform_inv(v)
}
}

impl Mul<real> for Transform3D {
type Output = Self;

Expand Down Expand Up @@ -342,6 +364,55 @@ impl Mul<Aabb> for Transform3D {
}
}

impl XformInv<Aabb> for Transform3D {
/// Inversely transforms each vertex in given [`Aabb`] individually by this transformation matrix,
/// under the assumption that the transformation basis is orthonormal (i.e. rotation/reflection is fine, scaling/skew is not),
/// and then creates an `Aabb` encompassing all of them.
///
/// For transforming by inverse of an affine transformation (e.g. with scaling) `transform.affine_inverse() * aabb` can be used instead. See [`Transform3D::affine_inverse()`].
///
/// _Godot equivalent: `aabb * transform`_
fn xform_inv(&self, rhs: Aabb) -> Aabb {
// Same as Godot's `Transform3D::xform_inv` but omits unnecessary `Aabb::expand_to`.
// There is probably some more clever way to do that.

// Use the first vertex initialize our min/max.
let end = self.xform_inv(rhs.end());
// `min` is the "lowest" vertex of our Aabb, `max` is the farthest vertex.
let (mut min, mut max) = (end, end);

let vertices = [
Vector3::new(
rhs.position.x + rhs.size.x,
rhs.position.y + rhs.size.y,
rhs.position.z,
),
Vector3::new(
rhs.position.x + rhs.size.x,
rhs.position.y,
rhs.position.z + rhs.size.z,
),
Vector3::new(rhs.position.x + rhs.size.x, rhs.position.y, rhs.position.z),
Vector3::new(
rhs.position.x,
rhs.position.y + rhs.size.y,
rhs.position.z + rhs.size.z,
),
Vector3::new(rhs.position.x, rhs.position.y + rhs.size.y, rhs.position.z),
Vector3::new(rhs.position.x, rhs.position.y, rhs.position.z + rhs.size.z),
rhs.position,
];

for v in vertices {
let transformed = self.xform_inv(v);
min = Vector3::coord_min(min, transformed);
max = Vector3::coord_max(max, transformed);
}

Aabb::new(min, max - min)
}
}

impl Mul<Plane> for Transform3D {
type Output = Plane;

Expand All @@ -354,6 +425,17 @@ impl Mul<Plane> for Transform3D {
}
}

impl XformInv<Plane> for Transform3D {
/// Inversely transforms (multiplies) the Plane by the given Transform3D transformation matrix.
///
/// `transform.xform_inv(plane)` is equivalent to `transform.affine_inverse() * plane`. See [`Transform3D::affine_inverse()`].
///
/// _Godot equivalent: `plane * transform`_
fn xform_inv(&self, rhs: Plane) -> Plane {
self.affine_inverse() * rhs
}
}

impl ApproxEq for Transform3D {
/// Returns if the two transforms are approximately equal, by comparing `basis` and `origin` separately.
fn approx_eq(&self, other: &Self) -> bool {
Expand Down
34 changes: 34 additions & 0 deletions itest/rust/src/builtin_tests/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Functions shared between various built-in tests.

use godot::builtin::VariantOperator;
use godot::meta::{FromGodot, ToGodot};
use godot::private::class_macros::assert_eq_approx;

/// Asserts that result of evaluated operation via variants and expected one are approximately equal.
///
/// Used to check if operations performed in Godot Rust yield the same result as ones performed via Godot runtime.
pub(crate) fn assert_evaluate_approx_eq<T, U, E>(
left: T,
right: U,
op: VariantOperator,
expected: E,
) where
T: ToGodot,
U: ToGodot,
E: FromGodot + std::fmt::Debug + godot::builtin::math::ApproxEq + Copy,
{
let lhs = left
.to_variant()
.evaluate(&right.to_variant(), op)
.expect("Result of evaluation can't be null!")
.to::<E>();

assert_eq_approx!(lhs, expected);
}
Loading
Loading