Browse Source

Merge pull request #68118 from aaronfranke/quat-from-euler

Replace Quaternion Euler constructor with `from_euler` method
Rémi Verschelde 2 years ago
parent
commit
d2e1b4b9f0

+ 6 - 5
core/math/quaternion.cpp

@@ -330,7 +330,7 @@ Quaternion::Quaternion(const Vector3 &p_axis, real_t p_angle) {
 // (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).
-Quaternion::Quaternion(const Vector3 &p_euler) {
+Quaternion Quaternion::from_euler(const Vector3 &p_euler) {
 	real_t half_a1 = p_euler.y * 0.5f;
 	real_t half_a2 = p_euler.x * 0.5f;
 	real_t half_a3 = p_euler.z * 0.5f;
@@ -346,8 +346,9 @@ Quaternion::Quaternion(const Vector3 &p_euler) {
 	real_t cos_a3 = Math::cos(half_a3);
 	real_t sin_a3 = Math::sin(half_a3);
 
-	x = sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3;
-	y = sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3;
-	z = -sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3;
-	w = sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3;
+	return Quaternion(
+			sin_a1 * cos_a2 * sin_a3 + cos_a1 * sin_a2 * cos_a3,
+			sin_a1 * cos_a2 * cos_a3 - cos_a1 * sin_a2 * sin_a3,
+			-sin_a1 * sin_a2 * cos_a3 + cos_a1 * cos_a2 * sin_a3,
+			sin_a1 * sin_a2 * sin_a3 + cos_a1 * cos_a2 * cos_a3);
 }

+ 1 - 2
core/math/quaternion.h

@@ -69,6 +69,7 @@ struct _NO_DISCARD_ Quaternion {
 	Vector3 get_euler_xyz() const;
 	Vector3 get_euler_yxz() const;
 	Vector3 get_euler() const { return get_euler_yxz(); };
+	static Quaternion from_euler(const Vector3 &p_euler);
 
 	Quaternion slerp(const Quaternion &p_to, const real_t &p_weight) const;
 	Quaternion slerpni(const Quaternion &p_to, const real_t &p_weight) const;
@@ -128,8 +129,6 @@ struct _NO_DISCARD_ Quaternion {
 
 	Quaternion(const Vector3 &p_axis, real_t p_angle);
 
-	Quaternion(const Vector3 &p_euler);
-
 	Quaternion(const Quaternion &p_q) :
 			x(p_q.x),
 			y(p_q.y),

+ 1 - 0
core/variant/variant_call.cpp

@@ -1806,6 +1806,7 @@ static void _register_variant_builtin_methods() {
 	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_static_method(Quaternion, from_euler, sarray("euler"), varray());
 	bind_method(Quaternion, get_axis, sarray(), varray());
 	bind_method(Quaternion, get_angle, sarray(), varray());
 

+ 0 - 1
core/variant/variant_construct.cpp

@@ -141,7 +141,6 @@ void Variant::_register_variant_constructors() {
 	add_constructor<VariantConstructor<Quaternion, Vector3, double>>(sarray("axis", "angle"));
 	add_constructor<VariantConstructor<Quaternion, Vector3, Vector3>>(sarray("arc_from", "arc_to"));
 	add_constructor<VariantConstructor<Quaternion, double, double, double, double>>(sarray("x", "y", "z", "w"));
-	add_constructor<VariantConstructor<Quaternion, Vector3>>(sarray("euler_yxz"));
 
 	add_constructor<VariantConstructNoArgs<::AABB>>(sarray());
 	add_constructor<VariantConstructor<::AABB, ::AABB>>(sarray("from"));

+ 7 - 6
doc/classes/Quaternion.xml

@@ -41,12 +41,6 @@
 				Constructs a quaternion that will rotate around the given axis by the specified angle. The axis must be a normalized vector.
 			</description>
 		</constructor>
-		<constructor name="Quaternion">
-			<return type="Quaternion" />
-			<param index="0" name="euler_yxz" type="Vector3" />
-			<description>
-			</description>
-		</constructor>
 		<constructor name="Quaternion">
 			<return type="Quaternion" />
 			<param index="0" name="from" type="Basis" />
@@ -86,6 +80,13 @@
 			<description>
 			</description>
 		</method>
+		<method name="from_euler" qualifiers="static">
+			<return type="Quaternion" />
+			<param index="0" name="euler" type="Vector3" />
+			<description>
+				Constructs a Quaternion from Euler angles in YXZ rotation order.
+			</description>
+		</method>
 		<method name="get_angle" qualifiers="const">
 			<return type="float" />
 			<description>

+ 1 - 1
editor/editor_properties.cpp

@@ -2688,7 +2688,7 @@ void EditorPropertyQuaternion::_custom_value_changed(double val) {
 	v.y = Math::deg_to_rad(edit_euler.y);
 	v.z = Math::deg_to_rad(edit_euler.z);
 
-	Quaternion temp_q = Quaternion(v);
+	Quaternion temp_q = Quaternion::from_euler(v);
 	spin[0]->set_value(temp_q.x);
 	spin[1]->set_value(temp_q.y);
 	spin[2]->set_value(temp_q.z);

+ 1 - 1
modules/gltf/gltf_document.cpp

@@ -6289,7 +6289,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
 
 			for (int32_t key_i = 0; key_i < key_count; key_i++) {
 				Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
-				p_track.rotation_track.values.write[key_i] = Quaternion(rotation_radian);
+				p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
 			}
 		} else if (path.contains(":scale")) {
 			p_track.scale_track.times = times;

+ 31 - 29
modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs

@@ -506,35 +506,6 @@ namespace Godot
             this = basis.GetQuaternion();
         }
 
-        /// <summary>
-        /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by
-        /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last),
-        /// given in the vector format as (X angle, Y angle, Z angle).
-        /// </summary>
-        /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param>
-        public Quaternion(Vector3 eulerYXZ)
-        {
-            real_t halfA1 = eulerYXZ.y * 0.5f;
-            real_t halfA2 = eulerYXZ.x * 0.5f;
-            real_t halfA3 = eulerYXZ.z * 0.5f;
-
-            // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
-            // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
-            // a3 is the angle of the first rotation, following the notation in this reference.
-
-            real_t cosA1 = Mathf.Cos(halfA1);
-            real_t sinA1 = Mathf.Sin(halfA1);
-            real_t cosA2 = Mathf.Cos(halfA2);
-            real_t sinA2 = Mathf.Sin(halfA2);
-            real_t cosA3 = Mathf.Cos(halfA3);
-            real_t sinA3 = Mathf.Sin(halfA3);
-
-            x = (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3);
-            y = (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3);
-            z = (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3);
-            w = (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3);
-        }
-
         /// <summary>
         /// Constructs a <see cref="Quaternion"/> that will rotate around the given axis
         /// by the specified angle. The axis must be a normalized vector.
@@ -596,6 +567,37 @@ namespace Godot
             }
         }
 
+        /// <summary>
+        /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by
+        /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last),
+        /// given in the vector format as (X angle, Y angle, Z angle).
+        /// </summary>
+        /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param>
+        public static Quaternion FromEuler(Vector3 eulerYXZ)
+        {
+            real_t halfA1 = eulerYXZ.y * 0.5f;
+            real_t halfA2 = eulerYXZ.x * 0.5f;
+            real_t halfA3 = eulerYXZ.z * 0.5f;
+
+            // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
+            // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
+            // a3 is the angle of the first rotation, following the notation in this reference.
+
+            real_t cosA1 = Mathf.Cos(halfA1);
+            real_t sinA1 = Mathf.Sin(halfA1);
+            real_t cosA2 = Mathf.Cos(halfA2);
+            real_t sinA2 = Mathf.Sin(halfA2);
+            real_t cosA3 = Mathf.Cos(halfA3);
+            real_t sinA3 = Mathf.Sin(halfA3);
+
+            return new Quaternion(
+                (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3),
+                (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3),
+                (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3),
+                (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3)
+            );
+        }
+
         /// <summary>
         /// Composes these two quaternions by multiplying them together.
         /// This has the effect of rotating the second quaternion

+ 15 - 15
tests/core/math/test_quaternion.h

@@ -47,9 +47,9 @@ Quaternion quat_euler_yxz_deg(Vector3 angle) {
 
 	// Generate YXZ (Z-then-X-then-Y) Quaternion using single-axis Euler
 	// constructor and quaternion product, both tested separately.
-	Quaternion q_y(Vector3(0.0, yaw, 0.0));
-	Quaternion q_p(Vector3(pitch, 0.0, 0.0));
-	Quaternion q_r(Vector3(0.0, 0.0, roll));
+	Quaternion q_y = Quaternion::from_euler(Vector3(0.0, yaw, 0.0));
+	Quaternion q_p = Quaternion::from_euler(Vector3(pitch, 0.0, 0.0));
+	Quaternion q_r = Quaternion::from_euler(Vector3(0.0, 0.0, roll));
 	// Roll-Z is followed by Pitch-X, then Yaw-Y.
 	Quaternion q_yxz = q_y * q_p * q_r;
 
@@ -134,21 +134,21 @@ TEST_CASE("[Quaternion] Construct Euler SingleAxis") {
 	double roll = Math::deg_to_rad(10.0);
 
 	Vector3 euler_y(0.0, yaw, 0.0);
-	Quaternion q_y(euler_y);
+	Quaternion q_y = Quaternion::from_euler(euler_y);
 	CHECK(q_y[0] == doctest::Approx(0.0));
 	CHECK(q_y[1] == doctest::Approx(0.382684));
 	CHECK(q_y[2] == doctest::Approx(0.0));
 	CHECK(q_y[3] == doctest::Approx(0.923879));
 
 	Vector3 euler_p(pitch, 0.0, 0.0);
-	Quaternion q_p(euler_p);
+	Quaternion q_p = Quaternion::from_euler(euler_p);
 	CHECK(q_p[0] == doctest::Approx(0.258819));
 	CHECK(q_p[1] == doctest::Approx(0.0));
 	CHECK(q_p[2] == doctest::Approx(0.0));
 	CHECK(q_p[3] == doctest::Approx(0.965926));
 
 	Vector3 euler_r(0.0, 0.0, roll);
-	Quaternion q_r(euler_r);
+	Quaternion q_r = Quaternion::from_euler(euler_r);
 	CHECK(q_r[0] == doctest::Approx(0.0));
 	CHECK(q_r[1] == doctest::Approx(0.0));
 	CHECK(q_r[2] == doctest::Approx(0.0871558));
@@ -163,11 +163,11 @@ TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") {
 	// Generate YXZ comparison data (Z-then-X-then-Y) using single-axis Euler
 	// constructor and quaternion product, both tested separately.
 	Vector3 euler_y(0.0, yaw, 0.0);
-	Quaternion q_y(euler_y);
+	Quaternion q_y = Quaternion::from_euler(euler_y);
 	Vector3 euler_p(pitch, 0.0, 0.0);
-	Quaternion q_p(euler_p);
+	Quaternion q_p = Quaternion::from_euler(euler_p);
 	Vector3 euler_r(0.0, 0.0, roll);
-	Quaternion q_r(euler_r);
+	Quaternion q_r = Quaternion::from_euler(euler_r);
 
 	// Roll-Z is followed by Pitch-X.
 	Quaternion check_xz = q_p * q_r;
@@ -176,7 +176,7 @@ TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") {
 
 	// Test construction from YXZ Euler angles.
 	Vector3 euler_yxz(pitch, yaw, roll);
-	Quaternion q(euler_yxz);
+	Quaternion q = Quaternion::from_euler(euler_yxz);
 	CHECK(q[0] == doctest::Approx(check_yxz[0]));
 	CHECK(q[1] == doctest::Approx(check_yxz[1]));
 	CHECK(q[2] == doctest::Approx(check_yxz[2]));
@@ -191,7 +191,7 @@ TEST_CASE("[Quaternion] Construct Basis Euler") {
 	double pitch = Math::deg_to_rad(30.0);
 	double roll = Math::deg_to_rad(10.0);
 	Vector3 euler_yxz(pitch, yaw, roll);
-	Quaternion q_yxz(euler_yxz);
+	Quaternion q_yxz = Quaternion::from_euler(euler_yxz);
 	Basis basis_axes = Basis::from_euler(euler_yxz);
 	Quaternion q(basis_axes);
 	CHECK(q.is_equal_approx(q_yxz));
@@ -209,7 +209,7 @@ TEST_CASE("[Quaternion] Construct Basis Axes") {
 	// Quaternion from local calculation.
 	Quaternion q_local = quat_euler_yxz_deg(Vector3(31.41, -49.16, 12.34));
 	// Quaternion from Euler angles constructor.
-	Quaternion q_euler(euler_yxz);
+	Quaternion q_euler = Quaternion::from_euler(euler_yxz);
 	CHECK(q_calc.is_equal_approx(q_local));
 	CHECK(q_local.is_equal_approx(q_euler));
 
@@ -253,21 +253,21 @@ TEST_CASE("[Quaternion] Product") {
 	double roll = Math::deg_to_rad(10.0);
 
 	Vector3 euler_y(0.0, yaw, 0.0);
-	Quaternion q_y(euler_y);
+	Quaternion q_y = Quaternion::from_euler(euler_y);
 	CHECK(q_y[0] == doctest::Approx(0.0));
 	CHECK(q_y[1] == doctest::Approx(0.382684));
 	CHECK(q_y[2] == doctest::Approx(0.0));
 	CHECK(q_y[3] == doctest::Approx(0.923879));
 
 	Vector3 euler_p(pitch, 0.0, 0.0);
-	Quaternion q_p(euler_p);
+	Quaternion q_p = Quaternion::from_euler(euler_p);
 	CHECK(q_p[0] == doctest::Approx(0.258819));
 	CHECK(q_p[1] == doctest::Approx(0.0));
 	CHECK(q_p[2] == doctest::Approx(0.0));
 	CHECK(q_p[3] == doctest::Approx(0.965926));
 
 	Vector3 euler_r(0.0, 0.0, roll);
-	Quaternion q_r(euler_r);
+	Quaternion q_r = Quaternion::from_euler(euler_r);
 	CHECK(q_r[0] == doctest::Approx(0.0));
 	CHECK(q_r[1] == doctest::Approx(0.0));
 	CHECK(q_r[2] == doctest::Approx(0.0871558));

+ 4 - 4
tests/scene/test_animation.h

@@ -140,13 +140,13 @@ TEST_CASE("[Animation] Create 3D rotation track") {
 	Ref<Animation> animation = memnew(Animation);
 	const int track_index = animation->add_track(Animation::TYPE_ROTATION_3D);
 	animation->track_set_path(track_index, NodePath("Enemy:rotation"));
-	animation->rotation_track_insert_key(track_index, 0.0, Quaternion(Vector3(0, 1, 2)));
-	animation->rotation_track_insert_key(track_index, 0.5, Quaternion(Vector3(3.5, 4, 5)));
+	animation->rotation_track_insert_key(track_index, 0.0, Quaternion::from_euler(Vector3(0, 1, 2)));
+	animation->rotation_track_insert_key(track_index, 0.5, Quaternion::from_euler(Vector3(3.5, 4, 5)));
 
 	CHECK(animation->get_track_count() == 1);
 	CHECK(!animation->track_is_compressed(0));
-	CHECK(Quaternion(animation->track_get_key_value(0, 0)).is_equal_approx(Quaternion(Vector3(0, 1, 2))));
-	CHECK(Quaternion(animation->track_get_key_value(0, 1)).is_equal_approx(Quaternion(Vector3(3.5, 4, 5))));
+	CHECK(Quaternion(animation->track_get_key_value(0, 0)).is_equal_approx(Quaternion::from_euler(Vector3(0, 1, 2))));
+	CHECK(Quaternion(animation->track_get_key_value(0, 1)).is_equal_approx(Quaternion::from_euler(Vector3(3.5, 4, 5))));
 
 	Quaternion r_interpolation;
 

+ 1 - 1
tests/test_validate_testing.h

@@ -86,7 +86,7 @@ TEST_SUITE("Validate tests") {
 		Plane plane(Vector3(1, 1, 1), 1.0);
 		INFO(plane);
 
-		Quaternion quat(Vector3(0.5, 1.0, 2.0));
+		Quaternion quat = Quaternion::from_euler(Vector3(0.5, 1.0, 2.0));
 		INFO(quat);
 
 		AABB aabb(Vector3(), Vector3(100, 100, 100));