From 56806ffeed8f52781b460e328b347323b4dbaec0 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Thu, 7 Sep 2023 09:43:22 -0500 Subject: [PATCH] Add `is_conformal` method to Basis and Transform2D --- core/math/basis.cpp | 8 ++++++++ core/math/basis.h | 1 + core/math/transform_2d.cpp | 12 ++++++++++++ core/math/transform_2d.h | 1 + core/variant/variant_call.cpp | 2 ++ doc/classes/Basis.xml | 6 ++++++ doc/classes/Transform2D.xml | 6 ++++++ tests/core/math/test_basis.h | 30 +++++++++++++++++++++++++++++ tests/core/math/test_transform_2d.h | 30 +++++++++++++++++++++++++++++ 9 files changed, 96 insertions(+) diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 6b0ecadc7fdb..9796ac59c22a 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -96,6 +96,14 @@ bool Basis::is_orthogonal() const { return m.is_equal_approx(identity); } +bool Basis::is_conformal() const { + const Vector3 x = get_column(0); + const Vector3 y = get_column(1); + const Vector3 z = get_column(2); + const real_t x_len_sq = x.length_squared(); + return Math::is_equal_approx(x_len_sq, y.length_squared()) && Math::is_equal_approx(x_len_sq, z.length_squared()) && Math::is_zero_approx(x.dot(y)) && Math::is_zero_approx(x.dot(z)) && Math::is_zero_approx(y.dot(z)); +} + bool Basis::is_diagonal() const { return ( Math::is_zero_approx(rows[0][1]) && Math::is_zero_approx(rows[0][2]) && diff --git a/core/math/basis.h b/core/math/basis.h index 1a68bee6861a..adacd1c21697 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -138,6 +138,7 @@ struct _NO_DISCARD_ Basis { _FORCE_INLINE_ Basis operator*(const real_t p_val) const; bool is_orthogonal() const; + bool is_conformal() const; bool is_diagonal() const; bool is_rotation() const; diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index a0187e00b1bc..bc4682fd901d 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -164,6 +164,18 @@ Transform2D Transform2D::orthonormalized() const { return ortho; } +bool Transform2D::is_conformal() const { + // Non-flipped case. + if (Math::is_equal_approx(columns[0][0], columns[1][1]) && Math::is_equal_approx(columns[0][1], -columns[1][0])) { + return true; + } + // Flipped case. + if (Math::is_equal_approx(columns[0][0], -columns[1][1]) && Math::is_equal_approx(columns[0][1], columns[1][0])) { + return true; + } + return false; +} + bool Transform2D::is_equal_approx(const Transform2D &p_transform) const { return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]); } diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index c51103466912..dd1a33c5d5fd 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -96,6 +96,7 @@ struct _NO_DISCARD_ Transform2D { void orthonormalize(); Transform2D orthonormalized() const; + bool is_conformal() const; bool is_equal_approx(const Transform2D &p_transform) const; bool is_finite() const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 19ae1b015772..9af5b0a1abe9 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2076,6 +2076,7 @@ static void _register_variant_builtin_methods() { bind_method(Transform2D, basis_xform, sarray("v"), varray()); bind_method(Transform2D, basis_xform_inv, sarray("v"), varray()); bind_method(Transform2D, interpolate_with, sarray("xform", "weight"), varray()); + bind_method(Transform2D, is_conformal, sarray(), varray()); bind_method(Transform2D, is_equal_approx, sarray("xform"), varray()); bind_method(Transform2D, is_finite, sarray(), varray()); // Do not bind functions like set_rotation, set_scale, set_skew, etc because this type is immutable and can't be modified. @@ -2095,6 +2096,7 @@ static void _register_variant_builtin_methods() { bind_method(Basis, tdoty, sarray("with"), varray()); bind_method(Basis, tdotz, sarray("with"), varray()); bind_method(Basis, slerp, sarray("to", "weight"), varray()); + bind_method(Basis, is_conformal, sarray(), varray()); bind_method(Basis, is_equal_approx, sarray("b"), varray()); bind_method(Basis, is_finite, sarray(), varray()); bind_method(Basis, get_rotation_quaternion, sarray(), varray()); diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 2034f4a8ff06..972a8eb114cb 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -106,6 +106,12 @@ Returns the inverse of the matrix. + + + + Returns [code]true[/code] if the basis is conformal, meaning it preserves angles and distance ratios, and may only be composed of rotation and uniform scale. Returns [code]false[/code] if the basis has non-uniform scale or shear/skew. This can be used to validate if the basis is non-distorted, which is important for physics and other use cases. + + diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml index cd79987ce9dd..629541269241 100644 --- a/doc/classes/Transform2D.xml +++ b/doc/classes/Transform2D.xml @@ -123,6 +123,12 @@ Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling, use [method affine_inverse] for transforms with scaling). + + + + Returns [code]true[/code] if the transform's basis is conformal, meaning it preserves angles and distance ratios, and may only be composed of rotation and uniform scale. Returns [code]false[/code] if the transform's basis has non-uniform scale or shear/skew. This can be used to validate if the transform is non-distorted, which is important for physics and other use cases. + + diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h index 0a34954bd31b..fcac9a623180 100644 --- a/tests/core/math/test_basis.h +++ b/tests/core/math/test_basis.h @@ -296,6 +296,36 @@ TEST_CASE("[Basis] Finite number checks") { "Basis with three components infinite should not be finite."); } +TEST_CASE("[Basis] Is conformal checks") { + CHECK_MESSAGE( + Basis().is_conformal(), + "Identity Basis should be conformal."); + + CHECK_MESSAGE( + Basis::from_euler(Vector3(1.2, 3.4, 5.6)).is_conformal(), + "Basis with only rotation should be conformal."); + + CHECK_MESSAGE( + Basis::from_scale(Vector3(-1, -1, -1)).is_conformal(), + "Basis with only a flip should be conformal."); + + CHECK_MESSAGE( + Basis::from_scale(Vector3(1.2, 1.2, 1.2)).is_conformal(), + "Basis with only uniform scale should be conformal."); + + CHECK_MESSAGE( + Basis(Vector3(3, 4, 0), Vector3(4, -3, 0.0), Vector3(0, 0, 5)).is_conformal(), + "Basis with a flip, rotation, and uniform scale should be conformal."); + + CHECK_FALSE_MESSAGE( + Basis::from_scale(Vector3(1.2, 3.4, 5.6)).is_conformal(), + "Basis with non-uniform scale should not be conformal."); + + CHECK_FALSE_MESSAGE( + Basis(Vector3(Math_SQRT12, Math_SQRT12, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)).is_conformal(), + "Basis with the X axis skewed 45 degrees should not be conformal."); +} + } // namespace TestBasis #endif // TEST_BASIS_H diff --git a/tests/core/math/test_transform_2d.h b/tests/core/math/test_transform_2d.h index ca277761808a..36d27ce7a9db 100644 --- a/tests/core/math/test_transform_2d.h +++ b/tests/core/math/test_transform_2d.h @@ -130,6 +130,36 @@ TEST_CASE("[Transform2D] Finite number checks") { "Transform2D with three components infinite should not be finite."); } +TEST_CASE("[Transform2D] Is conformal checks") { + CHECK_MESSAGE( + Transform2D().is_conformal(), + "Identity Transform2D should be conformal."); + + CHECK_MESSAGE( + Transform2D(1.2, Vector2()).is_conformal(), + "Transform2D with only rotation should be conformal."); + + CHECK_MESSAGE( + Transform2D(Vector2(1, 0), Vector2(0, -1), Vector2()).is_conformal(), + "Transform2D with only a flip should be conformal."); + + CHECK_MESSAGE( + Transform2D(Vector2(1.2, 0), Vector2(0, 1.2), Vector2()).is_conformal(), + "Transform2D with only uniform scale should be conformal."); + + CHECK_MESSAGE( + Transform2D(Vector2(1.2, 3.4), Vector2(3.4, -1.2), Vector2()).is_conformal(), + "Transform2D with a flip, rotation, and uniform scale should be conformal."); + + CHECK_FALSE_MESSAGE( + Transform2D(Vector2(1.2, 0), Vector2(0, 3.4), Vector2()).is_conformal(), + "Transform2D with non-uniform scale should not be conformal."); + + CHECK_FALSE_MESSAGE( + Transform2D(Vector2(Math_SQRT12, Math_SQRT12), Vector2(0, 1), Vector2()).is_conformal(), + "Transform2D with the X axis skewed 45 degrees should not be conformal."); +} + } // namespace TestTransform2D #endif // TEST_TRANSFORM_2D_H