diff --git a/godot-core/src/builtin/basis.rs b/godot-core/src/builtin/basis.rs index 7d8e15c20..e1f7bbb50 100644 --- a/godot-core/src/builtin/basis.rs +++ b/godot-core/src/builtin/basis.rs @@ -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; @@ -629,6 +629,30 @@ impl Mul for Basis { } } +impl XformInv 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 { diff --git a/godot-core/src/builtin/math/mod.rs b/godot-core/src/builtin/math/mod.rs index f33d852d4..62a742754 100644 --- a/godot-core/src/builtin/math/mod.rs +++ b/godot-core/src/builtin/math/mod.rs @@ -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::*; diff --git a/godot-core/src/builtin/math/xform.rs b/godot-core/src/builtin/math/xform.rs new file mode 100644 index 000000000..734500a85 --- /dev/null +++ b/godot-core/src/builtin/math/xform.rs @@ -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. +/// +/// See also: [`Transform2D`](crate::builtin::Transform2D), [`Transform3D`](crate::builtin::Transform3D), [`Basis`](crate::builtin::Basis). +/// +/// _Godot equivalent: `rhs * mat`_ +pub trait XformInv: std::ops::Mul { + fn xform_inv(&self, rhs: T) -> T; +} diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index bdeb67d01..a0c13f33f 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -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::*; diff --git a/godot-core/src/builtin/transform2d.rs b/godot-core/src/builtin/transform2d.rs index 6387ebe58..64360d75c 100644 --- a/godot-core/src/builtin/transform2d.rs +++ b/godot-core/src/builtin/transform2d.rs @@ -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}; @@ -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) @@ -349,6 +358,19 @@ impl Mul for Transform2D { } } +impl XformInv 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 for Transform2D { type Output = Self; @@ -370,9 +392,41 @@ impl Mul 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 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) } } diff --git a/godot-core/src/builtin/transform3d.rs b/godot-core/src/builtin/transform3d.rs index 0b39d3729..aafd8d1a3 100644 --- a/godot-core/src/builtin/transform3d.rs +++ b/godot-core/src/builtin/transform3d.rs @@ -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; @@ -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) @@ -303,6 +312,19 @@ impl Mul for Transform3D { } } +impl XformInv 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 for Transform3D { type Output = Self; @@ -342,6 +364,55 @@ impl Mul for Transform3D { } } +impl XformInv 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 for Transform3D { type Output = Plane; @@ -354,6 +425,17 @@ impl Mul for Transform3D { } } +impl XformInv 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 { diff --git a/itest/rust/src/builtin_tests/common.rs b/itest/rust/src/builtin_tests/common.rs new file mode 100644 index 000000000..0c0184f47 --- /dev/null +++ b/itest/rust/src/builtin_tests/common.rs @@ -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( + 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::(); + + assert_eq_approx!(lhs, expected); +} diff --git a/itest/rust/src/builtin_tests/geometry/basis_test.rs b/itest/rust/src/builtin_tests/geometry/basis_test.rs index a65de2cda..70a8aaaad 100644 --- a/itest/rust/src/builtin_tests/geometry/basis_test.rs +++ b/itest/rust/src/builtin_tests/geometry/basis_test.rs @@ -5,10 +5,10 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::builtin_tests::common::assert_evaluate_approx_eq; use godot::builtin::inner::InnerBasis; use godot::builtin::math::assert_eq_approx; -use godot::builtin::{real, Basis, EulerOrder, RealConv, VariantOperator, Vector3}; -use godot::meta::ToGodot; +use godot::builtin::{real, Basis, EulerOrder, RealConv, VariantOperator, Vector3, XformInv}; use crate::framework::itest; @@ -20,22 +20,37 @@ const TEST_BASIS: Basis = Basis::from_rows( #[itest] fn basis_multiply_same() { - let rust_res = TEST_BASIS * Basis::IDENTITY; - let godot_res = TEST_BASIS - .to_variant() - .evaluate(&Basis::IDENTITY.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(); - assert_eq_approx!(rust_res, godot_res); - - let rhs = Basis::from_axis_angle(Vector3::new(1.0, 2.0, 3.0).normalized(), 0.5); - let rust_res = TEST_BASIS * rhs; - let godot_res = TEST_BASIS - .to_variant() - .evaluate(&rhs.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(); - assert_eq_approx!(rust_res, godot_res); + // operator: Basis * Identity + assert_evaluate_approx_eq( + TEST_BASIS, + Basis::IDENTITY, + VariantOperator::MULTIPLY, + TEST_BASIS * Basis::IDENTITY, + ); + + // operator: Basis * rotated Basis + let rotated_basis = Basis::from_axis_angle(Vector3::new(1.0, 2.0, 3.0).normalized(), 0.5); + assert_evaluate_approx_eq( + TEST_BASIS, + rotated_basis, + VariantOperator::MULTIPLY, + TEST_BASIS * rotated_basis, + ); + + // orthonormalized + let orthonormalized_basis = TEST_BASIS.orthonormalized(); + assert_evaluate_approx_eq( + orthonormalized_basis, + orthonormalized_basis.inverse(), + VariantOperator::MULTIPLY, + Basis::IDENTITY, + ); + assert_evaluate_approx_eq( + orthonormalized_basis, + orthonormalized_basis.transposed(), + VariantOperator::MULTIPLY, + Basis::IDENTITY, + ); } #[itest] @@ -161,6 +176,34 @@ fn basis_equiv() { ) } +#[itest] +fn basis_xform_equiv() { + let orthonormalized_basis = TEST_BASIS.orthonormalized(); + let vec = Vector3::new(1.0, 2.0, 3.0); + + // operator: Basis * Vector3 + assert_evaluate_approx_eq( + orthonormalized_basis, + vec, + VariantOperator::MULTIPLY, + orthonormalized_basis * vec, + ); +} + +#[itest] +fn basis_xform_inv_equiv() { + let orthonormalized_basis = TEST_BASIS.orthonormalized(); + let vec = Vector3::new(1.0, 2.0, 3.0); + + // operator: Vector3 * Basis + assert_evaluate_approx_eq( + vec, + orthonormalized_basis, + VariantOperator::MULTIPLY, + orthonormalized_basis.xform_inv(vec), + ); +} + fn deg_to_rad(rotation: Vector3) -> Vector3 { Vector3::new( rotation.x.to_radians(), diff --git a/itest/rust/src/builtin_tests/geometry/transform2d_test.rs b/itest/rust/src/builtin_tests/geometry/transform2d_test.rs index 5c01717bb..faa4f1a44 100644 --- a/itest/rust/src/builtin_tests/geometry/transform2d_test.rs +++ b/itest/rust/src/builtin_tests/geometry/transform2d_test.rs @@ -7,9 +7,9 @@ use crate::framework::itest; +use crate::builtin_tests::common::assert_evaluate_approx_eq; use godot::builtin::inner::InnerTransform2D; -use godot::builtin::{real, RealConv, Rect2, Transform2D, VariantOperator, Vector2}; -use godot::meta::ToGodot; +use godot::builtin::{real, RealConv, Rect2, Transform2D, VariantOperator, Vector2, XformInv}; use godot::private::class_macros::assert_eq_approx; const TEST_TRANSFORM: Transform2D = Transform2D::from_cols( @@ -18,6 +18,12 @@ const TEST_TRANSFORM: Transform2D = Transform2D::from_cols( Vector2::new(5.0, 6.0), ); +const TEST_TRANSFORM_ORTHONORMAL: Transform2D = Transform2D::from_cols( + Vector2::new(1.0, 0.0), + Vector2::new(0.0, 1.0), + Vector2::new(5.0, 6.0), +); + #[itest] fn transform2d_equiv() { let inner = InnerTransform2D::from_outer(&TEST_TRANSFORM); @@ -73,36 +79,62 @@ fn transform2d_determinant() { fn transform2d_xform_equiv() { let vec = Vector2::new(1.0, 2.0); - assert_eq_approx!( + // operator: Transform2D * Vector2 + assert_evaluate_approx_eq( + TEST_TRANSFORM, + vec, + VariantOperator::MULTIPLY, TEST_TRANSFORM * vec, - TEST_TRANSFORM - .to_variant() - .evaluate(&vec.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(), - "operator: Transform2D * Vector2" ); let rect_2 = Rect2::new(Vector2::new(1.0, 2.0), Vector2::new(3.0, 4.0)); - assert_eq_approx!( + // operator: Transform2D * Rect2 (1) + assert_evaluate_approx_eq( + TEST_TRANSFORM, + rect_2, + VariantOperator::MULTIPLY, TEST_TRANSFORM * rect_2, - TEST_TRANSFORM - .to_variant() - .evaluate(&rect_2.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(), - "operator: Transform2D * Rect2 (1)" ); - assert_eq_approx!( - TEST_TRANSFORM.rotated(0.8) * rect_2, - TEST_TRANSFORM - .rotated(0.8) - .to_variant() - .evaluate(&rect_2.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(), - "operator: Transform2D * Rect2 (2)" + // "operator: Transform2D * Rect2 (2)" + let transform_rotated = TEST_TRANSFORM_ORTHONORMAL.rotated(0.8); + assert_evaluate_approx_eq( + transform_rotated, + rect_2, + VariantOperator::MULTIPLY, + transform_rotated * rect_2, + ); +} + +#[itest] +fn transform2d_xform_inv_equiv() { + let vec = Vector2::new(1.0, 2.0); + + // operator: Vector2 * Transform2D + assert_evaluate_approx_eq( + vec, + TEST_TRANSFORM_ORTHONORMAL, + VariantOperator::MULTIPLY, + TEST_TRANSFORM_ORTHONORMAL.xform_inv(vec), + ); + + let rect_2 = Rect2::new(Vector2::new(1.0, 2.0), Vector2::new(3.0, 4.0)); + + // operator: Rect2 * Transform2D (1) + assert_evaluate_approx_eq( + rect_2, + TEST_TRANSFORM_ORTHONORMAL, + VariantOperator::MULTIPLY, + TEST_TRANSFORM_ORTHONORMAL.xform_inv(rect_2), + ); + + // operator: Rect2 * Transform2D (2) + let transform_rotated = TEST_TRANSFORM_ORTHONORMAL.rotated(0.8); + assert_evaluate_approx_eq( + rect_2, + transform_rotated, + VariantOperator::MULTIPLY, + transform_rotated.xform_inv(rect_2), ); } diff --git a/itest/rust/src/builtin_tests/geometry/transform3d_test.rs b/itest/rust/src/builtin_tests/geometry/transform3d_test.rs index f38d6b44b..d66ead3e2 100644 --- a/itest/rust/src/builtin_tests/geometry/transform3d_test.rs +++ b/itest/rust/src/builtin_tests/geometry/transform3d_test.rs @@ -7,9 +7,9 @@ use crate::framework::itest; +use crate::builtin_tests::common::assert_evaluate_approx_eq; use godot::builtin::inner::InnerTransform3D; -use godot::builtin::{Aabb, Basis, Plane, Transform3D, VariantOperator, Vector3}; -use godot::meta::ToGodot; +use godot::builtin::{Aabb, Basis, Plane, Transform3D, VariantOperator, Vector3, XformInv}; use godot::private::class_macros::assert_eq_approx; const TEST_TRANSFORM: Transform3D = Transform3D::new( @@ -21,6 +21,9 @@ const TEST_TRANSFORM: Transform3D = Transform3D::new( Vector3::new(10.0, 11.0, 12.0), ); +const TEST_TRANSFORM_ORTHONORMAL: Transform3D = + Transform3D::new(Basis::IDENTITY, Vector3::new(10.0, 11.0, 12.0)); + #[itest] fn transform3d_equiv() { let inner = InnerTransform3D::from_outer(&TEST_TRANSFORM); @@ -48,37 +51,75 @@ fn transform3d_equiv() { fn transform3d_xform_equiv() { let vec = Vector3::new(1.0, 2.0, 3.0); - assert_eq_approx!( + // operator: Transform3D * Vector3 + assert_evaluate_approx_eq( + TEST_TRANSFORM, + vec, + VariantOperator::MULTIPLY, TEST_TRANSFORM * vec, - TEST_TRANSFORM - .to_variant() - .evaluate(&vec.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(), - "operator: Transform3D * Vector3" ); let aabb = Aabb::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(4.0, 5.0, 6.0)); - assert_eq_approx!( + // operator: Transform3D * Aabb + assert_evaluate_approx_eq( + TEST_TRANSFORM, + aabb, + VariantOperator::MULTIPLY, TEST_TRANSFORM * aabb, - TEST_TRANSFORM - .to_variant() - .evaluate(&aabb.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(), - "operator: Transform3D * Aabb" ); let plane = Plane::new(Vector3::new(1.0, 2.0, 3.0).normalized(), 5.0); - assert_eq_approx!( + // operator: Transform3D * Plane + assert_evaluate_approx_eq( + TEST_TRANSFORM, + plane, + VariantOperator::MULTIPLY, TEST_TRANSFORM * plane, - TEST_TRANSFORM - .to_variant() - .evaluate(&plane.to_variant(), VariantOperator::MULTIPLY) - .unwrap() - .to::(), - "operator: Transform3D * Plane" + ); +} + +#[itest] +fn transform3d_xform_inv_equiv() { + let vec = Vector3::new(1.0, 2.0, 3.0); + + // operator: Vector3 * Transform3D + assert_evaluate_approx_eq( + vec, + TEST_TRANSFORM_ORTHONORMAL, + VariantOperator::MULTIPLY, + TEST_TRANSFORM_ORTHONORMAL.xform_inv(vec), + ); + + let aabb = Aabb::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(4.0, 5.0, 6.0)); + + // operator: Aabb * Transform3D (1) + assert_evaluate_approx_eq( + aabb, + TEST_TRANSFORM_ORTHONORMAL, + VariantOperator::MULTIPLY, + TEST_TRANSFORM_ORTHONORMAL.xform_inv(aabb), + ); + + let transform_rotated = + TEST_TRANSFORM_ORTHONORMAL.rotated(Vector3::new(0.2, 0.4, 1.0).normalized(), 0.8); + + // operator: Aabb * Transform3D (2) + assert_evaluate_approx_eq( + aabb, + transform_rotated, + VariantOperator::MULTIPLY, + transform_rotated.xform_inv(aabb), + ); + + let plane = Plane::new(Vector3::new(1.0, 2.0, 3.0).normalized(), 5.0); + + // operator: Plane * Transform3D + assert_evaluate_approx_eq( + plane, + TEST_TRANSFORM_ORTHONORMAL, + VariantOperator::MULTIPLY, + TEST_TRANSFORM_ORTHONORMAL.xform_inv(plane), ); } diff --git a/itest/rust/src/builtin_tests/mod.rs b/itest/rust/src/builtin_tests/mod.rs index 1fb815950..5d839d23c 100644 --- a/itest/rust/src/builtin_tests/mod.rs +++ b/itest/rust/src/builtin_tests/mod.rs @@ -49,5 +49,7 @@ mod color_test; mod convert_test; +mod common; + #[cfg(feature = "serde")] mod serde_test;