Browse Source

Add `is_conformal` method to Basis and Transform2D

Aaron Franke 1 year ago
parent
commit
56806ffeed

+ 8 - 0
core/math/basis.cpp

@@ -96,6 +96,14 @@ bool Basis::is_orthogonal() const {
 	return m.is_equal_approx(identity);
 	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 {
 bool Basis::is_diagonal() const {
 	return (
 	return (
 			Math::is_zero_approx(rows[0][1]) && Math::is_zero_approx(rows[0][2]) &&
 			Math::is_zero_approx(rows[0][1]) && Math::is_zero_approx(rows[0][2]) &&

+ 1 - 0
core/math/basis.h

@@ -138,6 +138,7 @@ struct _NO_DISCARD_ Basis {
 	_FORCE_INLINE_ Basis operator*(const real_t p_val) const;
 	_FORCE_INLINE_ Basis operator*(const real_t p_val) const;
 
 
 	bool is_orthogonal() const;
 	bool is_orthogonal() const;
+	bool is_conformal() const;
 	bool is_diagonal() const;
 	bool is_diagonal() const;
 	bool is_rotation() const;
 	bool is_rotation() const;
 
 

+ 12 - 0
core/math/transform_2d.cpp

@@ -164,6 +164,18 @@ Transform2D Transform2D::orthonormalized() const {
 	return ortho;
 	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 {
 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]);
 	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]);
 }
 }

+ 1 - 0
core/math/transform_2d.h

@@ -96,6 +96,7 @@ struct _NO_DISCARD_ Transform2D {
 
 
 	void orthonormalize();
 	void orthonormalize();
 	Transform2D orthonormalized() const;
 	Transform2D orthonormalized() const;
+	bool is_conformal() const;
 	bool is_equal_approx(const Transform2D &p_transform) const;
 	bool is_equal_approx(const Transform2D &p_transform) const;
 	bool is_finite() const;
 	bool is_finite() const;
 
 

+ 2 - 0
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, sarray("v"), varray());
 	bind_method(Transform2D, basis_xform_inv, sarray("v"), varray());
 	bind_method(Transform2D, basis_xform_inv, sarray("v"), varray());
 	bind_method(Transform2D, interpolate_with, sarray("xform", "weight"), 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_equal_approx, sarray("xform"), varray());
 	bind_method(Transform2D, is_finite, sarray(), 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.
 	// 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, tdoty, sarray("with"), varray());
 	bind_method(Basis, tdotz, sarray("with"), varray());
 	bind_method(Basis, tdotz, sarray("with"), varray());
 	bind_method(Basis, slerp, sarray("to", "weight"), 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_equal_approx, sarray("b"), varray());
 	bind_method(Basis, is_finite, sarray(), varray());
 	bind_method(Basis, is_finite, sarray(), varray());
 	bind_method(Basis, get_rotation_quaternion, sarray(), varray());
 	bind_method(Basis, get_rotation_quaternion, sarray(), varray());

+ 6 - 0
doc/classes/Basis.xml

@@ -106,6 +106,12 @@
 				Returns the inverse of the matrix.
 				Returns the inverse of the matrix.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="is_conformal" qualifiers="const">
+			<return type="bool" />
+			<description>
+				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.
+			</description>
+		</method>
 		<method name="is_equal_approx" qualifiers="const">
 		<method name="is_equal_approx" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<param index="0" name="b" type="Basis" />
 			<param index="0" name="b" type="Basis" />

+ 6 - 0
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 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).
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="is_conformal" qualifiers="const">
+			<return type="bool" />
+			<description>
+				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.
+			</description>
+		</method>
 		<method name="is_equal_approx" qualifiers="const">
 		<method name="is_equal_approx" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<param index="0" name="xform" type="Transform2D" />
 			<param index="0" name="xform" type="Transform2D" />

+ 30 - 0
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.");
 			"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
 } // namespace TestBasis
 
 
 #endif // TEST_BASIS_H
 #endif // TEST_BASIS_H

+ 30 - 0
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.");
 			"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
 } // namespace TestTransform2D
 
 
 #endif // TEST_TRANSFORM_2D_H
 #endif // TEST_TRANSFORM_2D_H