From d03b7fbe090dca1f9ea4190116ac0efbee37e929 Mon Sep 17 00:00:00 2001 From: reduz Date: Thu, 21 Oct 2021 13:38:20 -0300 Subject: Refactored Node3D rotation modes * Made the Basis euler orders indexed via enum. * Node3D has a new rotation_order property to choose Euler rotation order. * Node3D has also a rotation_mode property to choose between Euler, Quaternion and Basis Exposing these modes as well as the order makes Godot a lot friendlier for animators, which can choose the best way to interpolate rotations. The new *Basis* mode makes the (exposed) transform property obsolete, so it was removed (can still be accessed by code of course). --- core/math/basis.cpp | 508 ++++++++++++++++++++++------------------------------ 1 file changed, 210 insertions(+), 298 deletions(-) (limited to 'core/math/basis.cpp') diff --git a/core/math/basis.cpp b/core/math/basis.cpp index a7f89522d7..0030cb1144 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -354,7 +354,7 @@ void Basis::rotate(const Quaternion &p_quaternion) { *this = rotated(p_quaternion); } -Vector3 Basis::get_rotation_euler() const { +Vector3 Basis::get_euler_normalized(EulerOrder p_order) const { // Assumes that the matrix can be decomposed into a proper rotation and scaling matrix as M = R.S, // and returns the Euler angles corresponding to the rotation part, complementing get_scale(). // See the comment in get_scale() for further information. @@ -365,7 +365,7 @@ Vector3 Basis::get_rotation_euler() const { m.scale(Vector3(-1, -1, -1)); } - return m.get_euler(); + return m.get_euler(p_order); } Quaternion Basis::get_rotation_quaternion() const { @@ -424,218 +424,203 @@ void Basis::get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) cons p_angle = -p_angle; } -// get_euler_xyz returns a vector containing the Euler angles in the format -// (a1,a2,a3), where a3 is the angle of the first rotation, and a1 is the last -// (following the convention they are commonly defined in the literature). -// -// The current implementation uses XYZ convention (Z is the first rotation), -// so euler.z is the angle of the (first) rotation around Z axis and so on, -// -// And thus, assuming the matrix is a rotation matrix, this function returns -// the angles in the decomposition R = X(a1).Y(a2).Z(a3) where Z(a) rotates -// around the z-axis by a and so on. -Vector3 Basis::get_euler_xyz() const { - // Euler angles in XYZ convention. - // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix - // - // rot = cy*cz -cy*sz sy - // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx - // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy - - Vector3 euler; - real_t sy = elements[0][2]; - if (sy < (1.0 - CMP_EPSILON)) { - if (sy > -(1.0 - CMP_EPSILON)) { - // is this a pure Y rotation? - if (elements[1][0] == 0.0 && elements[0][1] == 0.0 && elements[1][2] == 0 && elements[2][1] == 0 && elements[1][1] == 1) { - // return the simplest form (human friendlier in editor and scripts) - euler.x = 0; - euler.y = atan2(elements[0][2], elements[0][0]); - euler.z = 0; +Vector3 Basis::get_euler(EulerOrder p_order) const { + switch (p_order) { + case EULER_ORDER_XYZ: { + // Euler angles in XYZ convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz -cy*sz sy + // cz*sx*sy+cx*sz cx*cz-sx*sy*sz -cy*sx + // -cx*cz*sy+sx*sz cz*sx+cx*sy*sz cx*cy + + Vector3 euler; + real_t sy = elements[0][2]; + if (sy < (1.0 - CMP_EPSILON)) { + if (sy > -(1.0 - CMP_EPSILON)) { + // is this a pure Y rotation? + if (elements[1][0] == 0.0 && elements[0][1] == 0.0 && elements[1][2] == 0 && elements[2][1] == 0 && elements[1][1] == 1) { + // return the simplest form (human friendlier in editor and scripts) + euler.x = 0; + euler.y = atan2(elements[0][2], elements[0][0]); + euler.z = 0; + } else { + euler.x = Math::atan2(-elements[1][2], elements[2][2]); + euler.y = Math::asin(sy); + euler.z = Math::atan2(-elements[0][1], elements[0][0]); + } + } else { + euler.x = Math::atan2(elements[2][1], elements[1][1]); + euler.y = -Math_PI / 2.0; + euler.z = 0.0; + } } else { - euler.x = Math::atan2(-elements[1][2], elements[2][2]); - euler.y = Math::asin(sy); - euler.z = Math::atan2(-elements[0][1], elements[0][0]); + euler.x = Math::atan2(elements[2][1], elements[1][1]); + euler.y = Math_PI / 2.0; + euler.z = 0.0; + } + return euler; + } break; + case EULER_ORDER_XZY: { + // Euler angles in XZY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy -sz cz*sy + // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx + // cy*sx*sz cz*sx cx*cy+sx*sz*sy + + Vector3 euler; + real_t sz = elements[0][1]; + if (sz < (1.0 - CMP_EPSILON)) { + if (sz > -(1.0 - CMP_EPSILON)) { + euler.x = Math::atan2(elements[2][1], elements[1][1]); + euler.y = Math::atan2(elements[0][2], elements[0][0]); + euler.z = Math::asin(-sz); + } else { + // It's -1 + euler.x = -Math::atan2(elements[1][2], elements[2][2]); + euler.y = 0.0; + euler.z = Math_PI / 2.0; + } + } else { + // It's 1 + euler.x = -Math::atan2(elements[1][2], elements[2][2]); + euler.y = 0.0; + euler.z = -Math_PI / 2.0; + } + return euler; + } break; + case EULER_ORDER_YXZ: { + // Euler angles in YXZ convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy + // cx*sz cx*cz -sx + // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx + + Vector3 euler; + + real_t m12 = elements[1][2]; + + if (m12 < (1 - CMP_EPSILON)) { + if (m12 > -(1 - CMP_EPSILON)) { + // is this a pure X rotation? + if (elements[1][0] == 0 && elements[0][1] == 0 && elements[0][2] == 0 && elements[2][0] == 0 && elements[0][0] == 1) { + // return the simplest form (human friendlier in editor and scripts) + euler.x = atan2(-m12, elements[1][1]); + euler.y = 0; + euler.z = 0; + } else { + euler.x = asin(-m12); + euler.y = atan2(elements[0][2], elements[2][2]); + euler.z = atan2(elements[1][0], elements[1][1]); + } + } else { // m12 == -1 + euler.x = Math_PI * 0.5; + euler.y = atan2(elements[0][1], elements[0][0]); + euler.z = 0; + } + } else { // m12 == 1 + euler.x = -Math_PI * 0.5; + euler.y = -atan2(elements[0][1], elements[0][0]); + euler.z = 0; } - } else { - euler.x = Math::atan2(elements[2][1], elements[1][1]); - euler.y = -Math_PI / 2.0; - euler.z = 0.0; - } - } else { - euler.x = Math::atan2(elements[2][1], elements[1][1]); - euler.y = Math_PI / 2.0; - euler.z = 0.0; - } - return euler; -} - -// set_euler_xyz expects 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. -// The current implementation uses XYZ convention (Z is the first rotation). -void Basis::set_euler_xyz(const Vector3 &p_euler) { - real_t c, s; - - c = Math::cos(p_euler.x); - s = Math::sin(p_euler.x); - Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); - - c = Math::cos(p_euler.y); - s = Math::sin(p_euler.y); - Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); - - c = Math::cos(p_euler.z); - s = Math::sin(p_euler.z); - Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); - - //optimizer will optimize away all this anyway - *this = xmat * (ymat * zmat); -} - -Vector3 Basis::get_euler_xzy() const { - // Euler angles in XZY convention. - // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix - // - // rot = cz*cy -sz cz*sy - // sx*sy+cx*cy*sz cx*cz cx*sz*sy-cy*sx - // cy*sx*sz cz*sx cx*cy+sx*sz*sy - - Vector3 euler; - real_t sz = elements[0][1]; - if (sz < (1.0 - CMP_EPSILON)) { - if (sz > -(1.0 - CMP_EPSILON)) { - euler.x = Math::atan2(elements[2][1], elements[1][1]); - euler.y = Math::atan2(elements[0][2], elements[0][0]); - euler.z = Math::asin(-sz); - } else { - // It's -1 - euler.x = -Math::atan2(elements[1][2], elements[2][2]); - euler.y = 0.0; - euler.z = Math_PI / 2.0; - } - } else { - // It's 1 - euler.x = -Math::atan2(elements[1][2], elements[2][2]); - euler.y = 0.0; - euler.z = -Math_PI / 2.0; - } - return euler; -} - -void Basis::set_euler_xzy(const Vector3 &p_euler) { - real_t c, s; - - c = Math::cos(p_euler.x); - s = Math::sin(p_euler.x); - Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); - - c = Math::cos(p_euler.y); - s = Math::sin(p_euler.y); - Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); - - c = Math::cos(p_euler.z); - s = Math::sin(p_euler.z); - Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); - - *this = xmat * zmat * ymat; -} - -Vector3 Basis::get_euler_yzx() const { - // Euler angles in YZX convention. - // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix - // - // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx - // sz cz*cx -cz*sx - // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx - - Vector3 euler; - real_t sz = elements[1][0]; - if (sz < (1.0 - CMP_EPSILON)) { - if (sz > -(1.0 - CMP_EPSILON)) { - euler.x = Math::atan2(-elements[1][2], elements[1][1]); - euler.y = Math::atan2(-elements[2][0], elements[0][0]); - euler.z = Math::asin(sz); - } else { - // It's -1 - euler.x = Math::atan2(elements[2][1], elements[2][2]); - euler.y = 0.0; - euler.z = -Math_PI / 2.0; - } - } else { - // It's 1 - euler.x = Math::atan2(elements[2][1], elements[2][2]); - euler.y = 0.0; - euler.z = Math_PI / 2.0; - } - return euler; -} - -void Basis::set_euler_yzx(const Vector3 &p_euler) { - real_t c, s; - - c = Math::cos(p_euler.x); - s = Math::sin(p_euler.x); - Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); - - c = Math::cos(p_euler.y); - s = Math::sin(p_euler.y); - Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); - - c = Math::cos(p_euler.z); - s = Math::sin(p_euler.z); - Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); - - *this = ymat * zmat * xmat; -} - -// get_euler_yxz returns a vector containing the Euler angles in the YXZ convention, -// as in first-Z, then-X, last-Y. The angles for X, Y, and Z rotations are returned -// as the x, y, and z components of a Vector3 respectively. -Vector3 Basis::get_euler_yxz() const { - // Euler angles in YXZ convention. - // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix - // - // rot = cy*cz+sy*sx*sz cz*sy*sx-cy*sz cx*sy - // cx*sz cx*cz -sx - // cy*sx*sz-cz*sy cy*cz*sx+sy*sz cy*cx - - Vector3 euler; - - real_t m12 = elements[1][2]; - if (m12 < (1 - CMP_EPSILON)) { - if (m12 > -(1 - CMP_EPSILON)) { - // is this a pure X rotation? - if (elements[1][0] == 0 && elements[0][1] == 0 && elements[0][2] == 0 && elements[2][0] == 0 && elements[0][0] == 1) { - // return the simplest form (human friendlier in editor and scripts) - euler.x = atan2(-m12, elements[1][1]); - euler.y = 0; + return euler; + } break; + case EULER_ORDER_YZX: { + // Euler angles in YZX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cy*cz sy*sx-cy*cx*sz cx*sy+cy*sz*sx + // sz cz*cx -cz*sx + // -cz*sy cy*sx+cx*sy*sz cy*cx-sy*sz*sx + + Vector3 euler; + real_t sz = elements[1][0]; + if (sz < (1.0 - CMP_EPSILON)) { + if (sz > -(1.0 - CMP_EPSILON)) { + euler.x = Math::atan2(-elements[1][2], elements[1][1]); + euler.y = Math::atan2(-elements[2][0], elements[0][0]); + euler.z = Math::asin(sz); + } else { + // It's -1 + euler.x = Math::atan2(elements[2][1], elements[2][2]); + euler.y = 0.0; + euler.z = -Math_PI / 2.0; + } + } else { + // It's 1 + euler.x = Math::atan2(elements[2][1], elements[2][2]); + euler.y = 0.0; + euler.z = Math_PI / 2.0; + } + return euler; + } break; + case EULER_ORDER_ZXY: { + // Euler angles in ZXY convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx + // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx + // -cx*sy sx cx*cy + Vector3 euler; + real_t sx = elements[2][1]; + if (sx < (1.0 - CMP_EPSILON)) { + if (sx > -(1.0 - CMP_EPSILON)) { + euler.x = Math::asin(sx); + euler.y = Math::atan2(-elements[2][0], elements[2][2]); + euler.z = Math::atan2(-elements[0][1], elements[1][1]); + } else { + // It's -1 + euler.x = -Math_PI / 2.0; + euler.y = Math::atan2(elements[0][2], elements[0][0]); + euler.z = 0; + } + } else { + // It's 1 + euler.x = Math_PI / 2.0; + euler.y = Math::atan2(elements[0][2], elements[0][0]); euler.z = 0; + } + return euler; + } break; + case EULER_ORDER_ZYX: { + // Euler angles in ZYX convention. + // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix + // + // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy + // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx + // -sy cy*sx cy*cx + Vector3 euler; + real_t sy = elements[2][0]; + if (sy < (1.0 - CMP_EPSILON)) { + if (sy > -(1.0 - CMP_EPSILON)) { + euler.x = Math::atan2(elements[2][1], elements[2][2]); + euler.y = Math::asin(-sy); + euler.z = Math::atan2(elements[1][0], elements[0][0]); + } else { + // It's -1 + euler.x = 0; + euler.y = Math_PI / 2.0; + euler.z = -Math::atan2(elements[0][1], elements[1][1]); + } } else { - euler.x = asin(-m12); - euler.y = atan2(elements[0][2], elements[2][2]); - euler.z = atan2(elements[1][0], elements[1][1]); + // It's 1 + euler.x = 0; + euler.y = -Math_PI / 2.0; + euler.z = -Math::atan2(elements[0][1], elements[1][1]); } - } else { // m12 == -1 - euler.x = Math_PI * 0.5; - euler.y = atan2(elements[0][1], elements[0][0]); - euler.z = 0; + return euler; + } break; + default: { + ERR_FAIL_V_MSG(Vector3(), "Invalid parameter for get_euler(order)"); } - } else { // m12 == 1 - euler.x = -Math_PI * 0.5; - euler.y = -atan2(elements[0][1], elements[0][0]); - euler.z = 0; } - - return euler; + return Vector3(); } -// set_euler_yxz expects 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. -// The current implementation uses YXZ convention (Z is the first rotation). -void Basis::set_euler_yxz(const Vector3 &p_euler) { +void Basis::set_euler(const Vector3 &p_euler, EulerOrder p_order) { real_t c, s; c = Math::cos(p_euler.x); @@ -650,102 +635,29 @@ void Basis::set_euler_yxz(const Vector3 &p_euler) { s = Math::sin(p_euler.z); Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); - //optimizer will optimize away all this anyway - *this = ymat * xmat * zmat; -} - -Vector3 Basis::get_euler_zxy() const { - // Euler angles in ZXY convention. - // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix - // - // rot = cz*cy-sz*sx*sy -cx*sz cz*sy+cy*sz*sx - // cy*sz+cz*sx*sy cz*cx sz*sy-cz*cy*sx - // -cx*sy sx cx*cy - Vector3 euler; - real_t sx = elements[2][1]; - if (sx < (1.0 - CMP_EPSILON)) { - if (sx > -(1.0 - CMP_EPSILON)) { - euler.x = Math::asin(sx); - euler.y = Math::atan2(-elements[2][0], elements[2][2]); - euler.z = Math::atan2(-elements[0][1], elements[1][1]); - } else { - // It's -1 - euler.x = -Math_PI / 2.0; - euler.y = Math::atan2(elements[0][2], elements[0][0]); - euler.z = 0; + switch (p_order) { + case EULER_ORDER_XYZ: { + *this = xmat * (ymat * zmat); + } break; + case EULER_ORDER_XZY: { + *this = xmat * zmat * ymat; + } break; + case EULER_ORDER_YXZ: { + *this = ymat * xmat * zmat; + } break; + case EULER_ORDER_YZX: { + *this = ymat * zmat * xmat; + } break; + case EULER_ORDER_ZXY: { + *this = zmat * xmat * ymat; + } break; + case EULER_ORDER_ZYX: { + *this = zmat * ymat * xmat; + } break; + default: { + ERR_FAIL_MSG("Invalid order parameter for set_euler(vec3,order)"); } - } else { - // It's 1 - euler.x = Math_PI / 2.0; - euler.y = Math::atan2(elements[0][2], elements[0][0]); - euler.z = 0; } - return euler; -} - -void Basis::set_euler_zxy(const Vector3 &p_euler) { - real_t c, s; - - c = Math::cos(p_euler.x); - s = Math::sin(p_euler.x); - Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); - - c = Math::cos(p_euler.y); - s = Math::sin(p_euler.y); - Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); - - c = Math::cos(p_euler.z); - s = Math::sin(p_euler.z); - Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); - - *this = zmat * xmat * ymat; -} - -Vector3 Basis::get_euler_zyx() const { - // Euler angles in ZYX convention. - // See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix - // - // rot = cz*cy cz*sy*sx-cx*sz sz*sx+cz*cx*cy - // cy*sz cz*cx+sz*sy*sx cx*sz*sy-cz*sx - // -sy cy*sx cy*cx - Vector3 euler; - real_t sy = elements[2][0]; - if (sy < (1.0 - CMP_EPSILON)) { - if (sy > -(1.0 - CMP_EPSILON)) { - euler.x = Math::atan2(elements[2][1], elements[2][2]); - euler.y = Math::asin(-sy); - euler.z = Math::atan2(elements[1][0], elements[0][0]); - } else { - // It's -1 - euler.x = 0; - euler.y = Math_PI / 2.0; - euler.z = -Math::atan2(elements[0][1], elements[1][1]); - } - } else { - // It's 1 - euler.x = 0; - euler.y = -Math_PI / 2.0; - euler.z = -Math::atan2(elements[0][1], elements[1][1]); - } - return euler; -} - -void Basis::set_euler_zyx(const Vector3 &p_euler) { - real_t c, s; - - c = Math::cos(p_euler.x); - s = Math::sin(p_euler.x); - Basis xmat(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); - - c = Math::cos(p_euler.y); - s = Math::sin(p_euler.y); - Basis ymat(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); - - c = Math::cos(p_euler.z); - s = Math::sin(p_euler.z); - Basis zmat(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0); - - *this = zmat * ymat * xmat; } bool Basis::is_equal_approx(const Basis &p_basis) const { -- cgit v1.2.3