mirror of
https://github.com/godotengine/godot
synced 2024-09-16 02:27:37 +00:00
Allow getting Quaternion rotation in different Euler orders
This commit is contained in:
parent
889868cbbc
commit
9e952c8386
|
@ -38,25 +38,11 @@ real_t Quaternion::angle_to(const Quaternion &p_to) const {
|
||||||
return Math::acos(CLAMP(d * d * 2 - 1, -1, 1));
|
return Math::acos(CLAMP(d * d * 2 - 1, -1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// get_euler_xyz returns a vector containing the Euler angles in the format
|
Vector3 Quaternion::get_euler(EulerOrder p_order) const {
|
||||||
// (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 {
|
|
||||||
#ifdef MATH_CHECKS
|
#ifdef MATH_CHECKS
|
||||||
ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized.");
|
ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized.");
|
||||||
#endif
|
#endif
|
||||||
Basis m(*this);
|
return Basis(*this).get_euler(p_order);
|
||||||
return m.get_euler(EulerOrder::YXZ);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Quaternion::operator*=(const Quaternion &p_q) {
|
void Quaternion::operator*=(const Quaternion &p_q) {
|
||||||
|
|
|
@ -66,9 +66,7 @@ struct _NO_DISCARD_ Quaternion {
|
||||||
_FORCE_INLINE_ real_t dot(const Quaternion &p_q) const;
|
_FORCE_INLINE_ real_t dot(const Quaternion &p_q) const;
|
||||||
real_t angle_to(const Quaternion &p_to) const;
|
real_t angle_to(const Quaternion &p_to) const;
|
||||||
|
|
||||||
Vector3 get_euler_xyz() const;
|
Vector3 get_euler(EulerOrder p_order = EulerOrder::YXZ) const;
|
||||||
Vector3 get_euler_yxz() const;
|
|
||||||
Vector3 get_euler() const { return get_euler_yxz(); };
|
|
||||||
static Quaternion from_euler(const Vector3 &p_euler);
|
static Quaternion from_euler(const Vector3 &p_euler);
|
||||||
|
|
||||||
Quaternion slerp(const Quaternion &p_to, const real_t &p_weight) const;
|
Quaternion slerp(const Quaternion &p_to, const real_t &p_weight) const;
|
||||||
|
|
|
@ -1805,7 +1805,7 @@ static void _register_variant_builtin_methods() {
|
||||||
bind_method(Quaternion, slerpni, sarray("to", "weight"), varray());
|
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, 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, 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_static_method(Quaternion, from_euler, sarray("euler"), varray());
|
||||||
bind_method(Quaternion, get_axis, sarray(), varray());
|
bind_method(Quaternion, get_axis, sarray(), varray());
|
||||||
bind_method(Quaternion, get_angle, sarray(), varray());
|
bind_method(Quaternion, get_angle, sarray(), varray());
|
||||||
|
|
|
@ -99,8 +99,9 @@
|
||||||
</method>
|
</method>
|
||||||
<method name="get_euler" qualifiers="const">
|
<method name="get_euler" qualifiers="const">
|
||||||
<return type="Vector3" />
|
<return type="Vector3" />
|
||||||
|
<param index="0" name="order" type="int" default="2" />
|
||||||
<description>
|
<description>
|
||||||
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).
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="inverse" qualifiers="const">
|
<method name="inverse" qualifiers="const">
|
||||||
|
|
|
@ -2725,7 +2725,7 @@ void EditorPropertyQuaternion::update_property() {
|
||||||
spin[2]->set_value(val.z);
|
spin[2]->set_value(val.z);
|
||||||
spin[3]->set_value(val.w);
|
spin[3]->set_value(val.w);
|
||||||
if (!is_grabbing_euler()) {
|
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.x = Math::rad_to_deg(v.x);
|
||||||
edit_euler.y = Math::rad_to_deg(v.y);
|
edit_euler.y = Math::rad_to_deg(v.y);
|
||||||
edit_euler.z = Math::rad_to_deg(v.z);
|
edit_euler.z = Math::rad_to_deg(v.z);
|
||||||
|
|
|
@ -312,7 +312,7 @@ namespace Godot
|
||||||
/// the rotation angles in the format (X angle, Y angle, Z angle).
|
/// the rotation angles in the format (X angle, Y angle, Z angle).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The Euler angle representation of this quaternion.</returns>
|
/// <returns>The Euler angle representation of this quaternion.</returns>
|
||||||
public Vector3 GetEuler()
|
public Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
if (!IsNormalized())
|
if (!IsNormalized())
|
||||||
|
@ -321,7 +321,7 @@ namespace Godot
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
var basis = new Basis(this);
|
var basis = new Basis(this);
|
||||||
return basis.GetEuler();
|
return basis.GetEuler(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -169,10 +169,9 @@ TEST_CASE("[Quaternion] Construct Euler YXZ dynamic axes") {
|
||||||
Vector3 euler_r(0.0, 0.0, roll);
|
Vector3 euler_r(0.0, 0.0, roll);
|
||||||
Quaternion q_r = Quaternion::from_euler(euler_r);
|
Quaternion q_r = Quaternion::from_euler(euler_r);
|
||||||
|
|
||||||
// Roll-Z is followed by Pitch-X.
|
// Instrinsically, Yaw-Y then Pitch-X then Roll-Z.
|
||||||
Quaternion check_xz = q_p * q_r;
|
// Extrinsically, Roll-Z is followed by Pitch-X, then Yaw-Y.
|
||||||
// Then Yaw-Y follows both.
|
Quaternion check_yxz = q_y * q_p * q_r;
|
||||||
Quaternion check_yxz = q_y * check_xz;
|
|
||||||
|
|
||||||
// Test construction from YXZ Euler angles.
|
// Test construction from YXZ Euler angles.
|
||||||
Vector3 euler_yxz(pitch, yaw, roll);
|
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[2] == doctest::Approx(check_yxz[2]));
|
||||||
CHECK(q[3] == doctest::Approx(check_yxz[3]));
|
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.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") {
|
TEST_CASE("[Quaternion] Construct Basis Euler") {
|
||||||
|
@ -235,6 +235,23 @@ TEST_CASE("[Quaternion] Construct Basis Axes") {
|
||||||
CHECK(q[3] == doctest::Approx(0.8582598));
|
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)") {
|
TEST_CASE("[Quaternion] Product (book)") {
|
||||||
// Example from "Quaternions and Rotation Sequences" by Jack Kuipers, p. 108.
|
// Example from "Quaternions and Rotation Sequences" by Jack Kuipers, p. 108.
|
||||||
Quaternion p(1.0, -2.0, 1.0, 3.0);
|
Quaternion p(1.0, -2.0, 1.0, 3.0);
|
||||||
|
|
Loading…
Reference in a new issue