From 9e952c83869cd2c1d928beeb6493e635e2b690ff Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Sat, 1 Oct 2022 21:54:19 -0500 Subject: Allow getting Quaternion rotation in different Euler orders --- core/math/quaternion.cpp | 18 ++------------- core/math/quaternion.h | 4 +--- core/variant/variant_call.cpp | 2 +- doc/classes/Quaternion.xml | 3 ++- editor/editor_properties.cpp | 2 +- .../glue/GodotSharp/GodotSharp/Core/Quaternion.cs | 4 ++-- tests/core/math/test_quaternion.h | 27 ++++++++++++++++++---- 7 files changed, 31 insertions(+), 29 deletions(-) diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index 42b61f6dfe..942a0b766e 100644 --- a/core/math/quaternion.cpp +++ b/core/math/quaternion.cpp @@ -38,25 +38,11 @@ real_t Quaternion::angle_to(const Quaternion &p_to) const { return Math::acos(CLAMP(d * d * 2 - 1, -1, 1)); } -// get_euler_xyz returns a vector containing the Euler angles in the format -// (ax,ay,az), where ax is the angle of rotation around x axis, -// and similar for other axes. -// This implementation uses XYZ convention (Z is the first rotation). -Vector3 Quaternion::get_euler_xyz() const { - Basis m(*this); - return m.get_euler(EulerOrder::XYZ); -} - -// get_euler_yxz returns a vector containing the Euler angles in the format -// (ax,ay,az), where ax is the angle of rotation around x axis, -// and similar for other axes. -// This implementation uses YXZ convention (Z is the first rotation). -Vector3 Quaternion::get_euler_yxz() const { +Vector3 Quaternion::get_euler(EulerOrder p_order) const { #ifdef MATH_CHECKS ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized."); #endif - Basis m(*this); - return m.get_euler(EulerOrder::YXZ); + return Basis(*this).get_euler(p_order); } void Quaternion::operator*=(const Quaternion &p_q) { diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 7497f1643e..c5af2121d9 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -66,9 +66,7 @@ struct _NO_DISCARD_ Quaternion { _FORCE_INLINE_ real_t dot(const Quaternion &p_q) const; real_t angle_to(const Quaternion &p_to) const; - Vector3 get_euler_xyz() const; - Vector3 get_euler_yxz() const; - Vector3 get_euler() const { return get_euler_yxz(); }; + Vector3 get_euler(EulerOrder p_order = EulerOrder::YXZ) const; static Quaternion from_euler(const Vector3 &p_euler); Quaternion slerp(const Quaternion &p_to, const real_t &p_weight) const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index f32233d808..7da46a2d05 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1805,7 +1805,7 @@ static void _register_variant_builtin_methods() { bind_method(Quaternion, slerpni, sarray("to", "weight"), varray()); bind_method(Quaternion, spherical_cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); bind_method(Quaternion, spherical_cubic_interpolate_in_time, sarray("b", "pre_a", "post_b", "weight", "b_t", "pre_a_t", "post_b_t"), varray()); - bind_method(Quaternion, get_euler, sarray(), varray()); + bind_method(Quaternion, get_euler, sarray("order"), varray((int64_t)EulerOrder::YXZ)); bind_static_method(Quaternion, from_euler, sarray("euler"), varray()); bind_method(Quaternion, get_axis, sarray(), varray()); bind_method(Quaternion, get_angle, sarray(), varray()); diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml index 08a9231751..ac2eda9f28 100644 --- a/doc/classes/Quaternion.xml +++ b/doc/classes/Quaternion.xml @@ -99,8 +99,9 @@ + - Returns Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last) corresponding to the rotation represented by the unit quaternion. Returned vector contains the rotation angles in the format (X angle, Y angle, Z angle). + Returns the quaternion's rotation in the form of Euler angles. The Euler order depends on the [param order] parameter, for example using the YXZ convention: since this method decomposes, first Z, then X, and Y last. See the [enum EulerOrder] enum for possible values. The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle). diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 115fda6ca0..d76dce0c1d 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -2725,7 +2725,7 @@ void EditorPropertyQuaternion::update_property() { spin[2]->set_value(val.z); spin[3]->set_value(val.w); if (!is_grabbing_euler()) { - Vector3 v = val.normalized().get_euler_yxz(); + Vector3 v = val.normalized().get_euler(); edit_euler.x = Math::rad_to_deg(v.x); edit_euler.y = Math::rad_to_deg(v.y); edit_euler.z = Math::rad_to_deg(v.z); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index 5dd629aeb0..f01f0be985 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -312,7 +312,7 @@ namespace Godot /// the rotation angles in the format (X angle, Y angle, Z angle). /// /// The Euler angle representation of this quaternion. - public Vector3 GetEuler() + public Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz) { #if DEBUG if (!IsNormalized()) @@ -321,7 +321,7 @@ namespace Godot } #endif var basis = new Basis(this); - return basis.GetEuler(); + return basis.GetEuler(order); } /// diff --git a/tests/core/math/test_quaternion.h b/tests/core/math/test_quaternion.h index 909fc2499f..c3ae322991 100644 --- a/tests/core/math/test_quaternion.h +++ b/tests/core/math/test_quaternion.h @@ -169,10 +169,9 @@ TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") { Vector3 euler_r(0.0, 0.0, roll); Quaternion q_r = Quaternion::from_euler(euler_r); - // Roll-Z is followed by Pitch-X. - Quaternion check_xz = q_p * q_r; - // Then Yaw-Y follows both. - Quaternion check_yxz = q_y * check_xz; + // Instrinsically, Yaw-Y then Pitch-X then Roll-Z. + // Extrinsically, Roll-Z is followed by Pitch-X, then Yaw-Y. + Quaternion check_yxz = q_y * q_p * q_r; // Test construction from YXZ Euler angles. Vector3 euler_yxz(pitch, yaw, roll); @@ -182,8 +181,9 @@ TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") { CHECK(q[2] == doctest::Approx(check_yxz[2])); CHECK(q[3] == doctest::Approx(check_yxz[3])); - // Sneak in a test of is_equal_approx. CHECK(q.is_equal_approx(check_yxz)); + CHECK(q.get_euler().is_equal_approx(euler_yxz)); + CHECK(check_yxz.get_euler().is_equal_approx(euler_yxz)); } TEST_CASE("[Quaternion] Construct Basis Euler") { @@ -235,6 +235,23 @@ TEST_CASE("[Quaternion] Construct Basis Axes") { CHECK(q[3] == doctest::Approx(0.8582598)); } +TEST_CASE("[Quaternion] Get Euler Orders") { + double x = Math::deg_to_rad(30.0); + double y = Math::deg_to_rad(45.0); + double z = Math::deg_to_rad(10.0); + Vector3 euler(x, y, z); + for (int i = 0; i < 6; i++) { + EulerOrder order = (EulerOrder)i; + Basis basis = Basis::from_euler(euler, order); + Quaternion q = Quaternion(basis); + Vector3 check = q.get_euler(order); + CHECK_MESSAGE(check.is_equal_approx(euler), + "Quaternion get_euler method should return the original angles."); + CHECK_MESSAGE(check.is_equal_approx(basis.get_euler(order)), + "Quaternion get_euler method should behave the same as Basis get_euler."); + } +} + TEST_CASE("[Quaternion] Product (book)") { // Example from "Quaternions and Rotation Sequences" by Jack Kuipers, p. 108. Quaternion p(1.0, -2.0, 1.0, 3.0); -- cgit v1.2.3