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).
This commit is contained in:
reduz 2021-10-21 13:38:20 -03:00
parent 5ff0624a07
commit d03b7fbe09
38 changed files with 499 additions and 385 deletions

View file

@ -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,57 +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 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)
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[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;
}
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 {
// It's 1
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]);
euler.y = -Math_PI / 2.0;
euler.z = -Math::atan2(elements[0][1], elements[1][1]);
}
} else {
euler.x = Math::atan2(elements[2][1], elements[1][1]);
euler.y = -Math_PI / 2.0;
euler.z = 0.0;
return euler;
} break;
default: {
ERR_FAIL_V_MSG(Vector3(), "Invalid parameter for get_euler(order)");
}
} else {
euler.x = Math::atan2(elements[2][1], elements[1][1]);
euler.y = Math_PI / 2.0;
euler.z = 0.0;
}
return euler;
return Vector3();
}
// 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) {
void Basis::set_euler(const Vector3 &p_euler, EulerOrder p_order) {
real_t c, s;
c = Math::cos(p_euler.x);
@ -489,263 +635,29 @@ void Basis::set_euler_xyz(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 = 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;
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::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;
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;
}
return euler;
}
// 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) {
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 = 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;
}
} 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 {

View file

@ -85,40 +85,35 @@ public:
void rotate(const Quaternion &p_quaternion);
Basis rotated(const Quaternion &p_quaternion) const;
Vector3 get_rotation_euler() const;
enum EulerOrder {
EULER_ORDER_XYZ,
EULER_ORDER_XZY,
EULER_ORDER_YXZ,
EULER_ORDER_YZX,
EULER_ORDER_ZXY,
EULER_ORDER_ZYX
};
Vector3 get_euler_normalized(EulerOrder p_order = EULER_ORDER_YXZ) const;
void get_rotation_axis_angle(Vector3 &p_axis, real_t &p_angle) const;
void get_rotation_axis_angle_local(Vector3 &p_axis, real_t &p_angle) const;
Quaternion get_rotation_quaternion() const;
Vector3 get_rotation() const { return get_rotation_euler(); };
void rotate_to_align(Vector3 p_start_direction, Vector3 p_end_direction);
Vector3 rotref_posscale_decomposition(Basis &rotref) const;
Vector3 get_euler_xyz() const;
void set_euler_xyz(const Vector3 &p_euler);
Vector3 get_euler_xzy() const;
void set_euler_xzy(const Vector3 &p_euler);
Vector3 get_euler_yzx() const;
void set_euler_yzx(const Vector3 &p_euler);
Vector3 get_euler_yxz() const;
void set_euler_yxz(const Vector3 &p_euler);
Vector3 get_euler_zxy() const;
void set_euler_zxy(const Vector3 &p_euler);
Vector3 get_euler_zyx() const;
void set_euler_zyx(const Vector3 &p_euler);
Vector3 get_euler(EulerOrder p_order = EULER_ORDER_YXZ) const;
void set_euler(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ);
static Basis from_euler(const Vector3 &p_euler, EulerOrder p_order = EULER_ORDER_YXZ) {
Basis b;
b.set_euler(p_euler, p_order);
return b;
}
Quaternion get_quaternion() const;
void set_quaternion(const Quaternion &p_quaternion);
Vector3 get_euler() const { return get_euler_yxz(); }
void set_euler(const Vector3 &p_euler) { set_euler_yxz(p_euler); }
void get_axis_angle(Vector3 &r_axis, real_t &r_angle) const;
void set_axis_angle(const Vector3 &p_axis, real_t p_phi);
@ -250,9 +245,6 @@ public:
Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); };
Basis(const Quaternion &p_quaternion, const Vector3 &p_scale) { set_quaternion_scale(p_quaternion, p_scale); }
Basis(const Vector3 &p_euler) { set_euler(p_euler); }
Basis(const Vector3 &p_euler, const Vector3 &p_scale) { set_euler_scale(p_euler, p_scale); }
Basis(const Vector3 &p_axis, real_t p_phi) { set_axis_angle(p_axis, p_phi); }
Basis(const Vector3 &p_axis, real_t p_phi, const Vector3 &p_scale) { set_axis_angle_scale(p_axis, p_phi, p_scale); }
static Basis from_scale(const Vector3 &p_scale);

View file

@ -44,7 +44,7 @@ real_t Quaternion::angle_to(const Quaternion &p_to) const {
// This implementation uses XYZ convention (Z is the first rotation).
Vector3 Quaternion::get_euler_xyz() const {
Basis m(*this);
return m.get_euler_xyz();
return m.get_euler(Basis::EULER_ORDER_XYZ);
}
// get_euler_yxz returns a vector containing the Euler angles in the format
@ -56,7 +56,7 @@ Vector3 Quaternion::get_euler_yxz() const {
ERR_FAIL_COND_V_MSG(!is_normalized(), Vector3(0, 0, 0), "The quaternion must be normalized.");
#endif
Basis m(*this);
return m.get_euler_yxz();
return m.get_euler(Basis::EULER_ORDER_YXZ);
}
void Quaternion::operator*=(const Quaternion &p_q) {

View file

@ -88,6 +88,7 @@ struct VariantCaster<const T &> {
VARIANT_ENUM_CAST(Object::ConnectFlags);
VARIANT_ENUM_CAST(Vector3::Axis);
VARIANT_ENUM_CAST(Basis::EulerOrder);
VARIANT_ENUM_CAST(Error);
VARIANT_ENUM_CAST(Side);

View file

@ -313,7 +313,6 @@ bool Variant::can_convert(Variant::Type p_type_from, Variant::Type p_type_to) {
case BASIS: {
static const Type valid[] = {
QUATERNION,
VECTOR3,
NIL
};
@ -620,7 +619,6 @@ bool Variant::can_convert_strict(Variant::Type p_type_from, Variant::Type p_type
case BASIS: {
static const Type valid[] = {
QUATERNION,
VECTOR3,
NIL
};
@ -1889,8 +1887,6 @@ Variant::operator Basis() const {
return *_data._basis;
} else if (type == QUATERNION) {
return *reinterpret_cast<const Quaternion *>(_data._mem);
} else if (type == VECTOR3) {
return Basis(*reinterpret_cast<const Vector3 *>(_data._mem));
} else if (type == TRANSFORM3D) { // unexposed in Variant::can_convert?
return _data._transform3d->basis;
} else {

View file

@ -1731,7 +1731,7 @@ static void _register_variant_builtin_methods() {
bind_methodv(Basis, rotated, static_cast<Basis (Basis::*)(const Vector3 &, real_t) const>(&Basis::rotated), sarray("axis", "phi"), varray());
bind_method(Basis, scaled, sarray("scale"), varray());
bind_method(Basis, get_scale, sarray(), varray());
bind_method(Basis, get_euler, sarray(), varray());
bind_method(Basis, get_euler, sarray("order"), varray(Basis::EULER_ORDER_YXZ));
bind_method(Basis, tdotx, sarray("with"), varray());
bind_method(Basis, tdoty, sarray("with"), varray());
bind_method(Basis, tdotz, sarray("with"), varray());
@ -1741,6 +1741,7 @@ static void _register_variant_builtin_methods() {
bind_method(Basis, get_rotation_quaternion, sarray(), varray());
bind_static_method(Basis, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0)));
bind_static_method(Basis, from_scale, sarray("scale"), varray());
bind_static_method(Basis, from_euler, sarray("euler", "order"), varray(Basis::EULER_ORDER_YXZ));
/* AABB */
@ -2109,6 +2110,13 @@ static void _register_variant_builtin_methods() {
_VariantCall::add_variant_constant(Variant::VECTOR2I, "UP", Vector2i(0, -1));
_VariantCall::add_variant_constant(Variant::VECTOR2I, "DOWN", Vector2i(0, 1));
_VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_XYZ", Basis::EULER_ORDER_XYZ);
_VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_XZY", Basis::EULER_ORDER_XZY);
_VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_YXZ", Basis::EULER_ORDER_YXZ);
_VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_YZX", Basis::EULER_ORDER_YZX);
_VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_ZXY", Basis::EULER_ORDER_ZXY);
_VariantCall::add_constant(Variant::BASIS, "EULER_ORDER_ZYX", Basis::EULER_ORDER_ZYX);
_VariantCall::add_variant_constant(Variant::TRANSFORM2D, "IDENTITY", Transform2D());
_VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_X", Transform2D(-1, 0, 0, 1, 0, 0));
_VariantCall::add_variant_constant(Variant::TRANSFORM2D, "FLIP_Y", Transform2D(1, 0, 0, -1, 0, 0));

View file

@ -128,10 +128,10 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Quaternion>>(sarray());
add_constructor<VariantConstructor<Quaternion, Quaternion>>(sarray("from"));
add_constructor<VariantConstructor<Quaternion, Basis>>(sarray("from"));
add_constructor<VariantConstructor<Quaternion, Vector3>>(sarray("euler"));
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"));
@ -140,7 +140,6 @@ void Variant::_register_variant_constructors() {
add_constructor<VariantConstructNoArgs<Basis>>(sarray());
add_constructor<VariantConstructor<Basis, Basis>>(sarray("from"));
add_constructor<VariantConstructor<Basis, Quaternion>>(sarray("from"));
add_constructor<VariantConstructor<Basis, Vector3>>(sarray("euler"));
add_constructor<VariantConstructor<Basis, Vector3, double>>(sarray("axis", "phi"));
add_constructor<VariantConstructor<Basis, Vector3, Vector3, Vector3>>(sarray("x_axis", "y_axis", "z_axis"));

View file

@ -756,6 +756,12 @@ VARIANT_ACCESSOR_NUMBER(char32_t)
VARIANT_ACCESSOR_NUMBER(Error)
VARIANT_ACCESSOR_NUMBER(Side)
template <>
struct VariantInternalAccessor<Basis::EulerOrder> {
static _FORCE_INLINE_ Basis::EulerOrder get(const Variant *v) { return Basis::EulerOrder(*VariantInternal::get_int(v)); }
static _FORCE_INLINE_ void set(Variant *v, Basis::EulerOrder p_value) { *VariantInternal::get_int(v) = p_value; }
};
template <>
struct VariantInternalAccessor<ObjectID> {
static _FORCE_INLINE_ ObjectID get(const Variant *v) { return ObjectID(*VariantInternal::get_int(v)); }

View file

@ -40,14 +40,6 @@
Constructs a pure rotation basis matrix, rotated around the given [code]axis[/code] by [code]phi[/code], in radians. The axis must be a normalized vector.
</description>
</method>
<method name="Basis" qualifiers="constructor">
<return type="Basis" />
<argument index="0" name="euler" type="Vector3" />
<description>
Constructs a pure rotation basis matrix from the given Euler angles (in the YXZ convention: when *composing*, first Y, then X, and Z last), given in the vector format as (X angle, Y angle, Z angle).
Consider using the [Quaternion] constructor instead, which uses a quaternion instead of Euler angles.
</description>
</method>
<method name="Basis" qualifiers="constructor">
<return type="Basis" />
<argument index="0" name="from" type="Quaternion" />
@ -71,6 +63,13 @@
A negative determinant means the basis has a negative scale. A zero determinant means the basis isn't invertible, and is usually considered invalid.
</description>
</method>
<method name="from_euler" qualifiers="static">
<return type="Basis" />
<argument index="0" name="euler" type="Vector3" />
<argument index="1" name="order" type="int" default="2" />
<description>
</description>
</method>
<method name="from_scale" qualifiers="static">
<return type="Basis" />
<argument index="0" name="scale" type="Vector3" />
@ -80,6 +79,7 @@
</method>
<method name="get_euler" qualifiers="const">
<return type="Vector3" />
<argument index="0" name="order" type="int" default="2" />
<description>
Returns the basis's rotation in the form of Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last). The returned vector contains the rotation angles in the format (X angle, Y angle, Z angle).
Consider using the [method get_rotation_quaternion] method instead, which returns a [Quaternion] quaternion instead of Euler angles.
@ -248,6 +248,18 @@
</member>
</members>
<constants>
<constant name="EULER_ORDER_XYZ" value="0">
</constant>
<constant name="EULER_ORDER_XZY" value="1">
</constant>
<constant name="EULER_ORDER_YXZ" value="2">
</constant>
<constant name="EULER_ORDER_YZX" value="3">
</constant>
<constant name="EULER_ORDER_ZXY" value="4">
</constant>
<constant name="EULER_ORDER_ZYX" value="5">
</constant>
<constant name="IDENTITY" value="Basis(1, 0, 0, 0, 1, 0, 0, 0, 1)">
The identity basis, with no rotation or scaling applied.
This is identical to calling [code]Basis()[/code] without any parameters. This constant can be used to make your code clearer, and for consistency with C#.

View file

@ -264,16 +264,28 @@
</method>
</methods>
<members>
<member name="basis" type="Basis" setter="set_basis" getter="get_basis">
Direct access to the 3x3 basis of the [Transform3D] property.
</member>
<member name="global_transform" type="Transform3D" setter="set_global_transform" getter="get_global_transform">
World3D space (global) [Transform3D] of this node.
</member>
<member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)">
Local position or translation of this node relative to the parent. This is equivalent to [code]transform.origin[/code].
</member>
<member name="quaternion" type="Quaternion" setter="set_quaternion" getter="get_quaternion">
Access to the node rotation as a [Quaternion]. This property is ideal for tweening complex rotations.
</member>
<member name="rotation" type="Vector3" setter="set_rotation" getter="get_rotation" default="Vector3(0, 0, 0)">
Rotation part of the local transformation in radians, specified in terms of YXZ-Euler angles in the format (X angle, Y angle, Z angle).
Rotation part of the local transformation in radians, specified in terms of Euler angles. The angles construct a rotaton in the order specified by the [member rotation_order] property.
[b]Note:[/b] In the mathematical sense, rotation is a matrix and not a vector. The three Euler angles, which are the three independent parameters of the Euler-angle parametrization of the rotation matrix, are stored in a [Vector3] data structure not because the rotation is a vector, but only because [Vector3] exists as a convenient data-structure to store 3 floating-point numbers. Therefore, applying affine operations on the rotation "vector" is not meaningful.
</member>
<member name="rotation_edit_mode" type="int" setter="set_rotation_edit_mode" getter="get_rotation_edit_mode" enum="Node3D.RotationEditMode" default="0">
Specify how rotation (and scale) will be presented in the editor.
</member>
<member name="rotation_order" type="int" setter="set_rotation_order" getter="get_rotation_order" enum="Node3D.RotationOrder" default="2">
Specify the axis rotation order of the [member rotation] property. The final orientation is constructed by rotating the Euler angles in the order specified by this property.
</member>
<member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3(1, 1, 1)">
Scale part of the local transformation.
</member>
@ -311,5 +323,23 @@
<constant name="NOTIFICATION_VISIBILITY_CHANGED" value="43">
Node3D nodes receives this notification when their visibility changes.
</constant>
<constant name="ROTATION_EDIT_MODE_EULER" value="0" enum="RotationEditMode">
</constant>
<constant name="ROTATION_EDIT_MODE_QUATERNION" value="1" enum="RotationEditMode">
</constant>
<constant name="ROTATION_EDIT_MODE_BASIS" value="2" enum="RotationEditMode">
</constant>
<constant name="ROTATION_ORDER_XYZ" value="0" enum="RotationOrder">
</constant>
<constant name="ROTATION_ORDER_XZY" value="1" enum="RotationOrder">
</constant>
<constant name="ROTATION_ORDER_YXZ" value="2" enum="RotationOrder">
</constant>
<constant name="ROTATION_ORDER_YZX" value="3" enum="RotationOrder">
</constant>
<constant name="ROTATION_ORDER_ZXY" value="4" enum="RotationOrder">
</constant>
<constant name="ROTATION_ORDER_ZYX" value="5" enum="RotationOrder">
</constant>
</constants>
</class>

View file

@ -43,9 +43,8 @@
</method>
<method name="Quaternion" qualifiers="constructor">
<return type="Quaternion" />
<argument index="0" name="euler" type="Vector3" />
<argument index="0" name="euler_yxz" type="Vector3" />
<description>
Constructs a 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).
</description>
</method>
<method name="Quaternion" qualifiers="constructor">

View file

@ -3253,7 +3253,7 @@ void Node3DEditorViewport::_menu_option(int p_option) {
continue;
}
undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_rotation());
undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_euler_normalized());
undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation());
}
undo_redo->commit_action();

View file

@ -576,6 +576,7 @@ void CSGShape3D::_validate_property(PropertyInfo &property) const {
} else if (is_collision_prefixed && !bool(get("use_collision"))) {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
GeometryInstance3D::_validate_property(property);
}
Array CSGShape3D::get_meshes() const {

View file

@ -45,27 +45,27 @@ Basis ImportUtils::EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector
// by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
switch (mode) {
case FBXDocParser::Model::RotOrder_EulerXYZ:
ret.set_euler_zyx(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ);
break;
case FBXDocParser::Model::RotOrder_EulerXZY:
ret.set_euler_yzx(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY);
break;
case FBXDocParser::Model::RotOrder_EulerYZX:
ret.set_euler_xzy(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX);
break;
case FBXDocParser::Model::RotOrder_EulerYXZ:
ret.set_euler_zxy(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ);
break;
case FBXDocParser::Model::RotOrder_EulerZXY:
ret.set_euler_yxz(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY);
break;
case FBXDocParser::Model::RotOrder_EulerZYX:
ret.set_euler_xyz(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX);
break;
case FBXDocParser::Model::RotOrder_SphericXYZ:
@ -89,22 +89,22 @@ Vector3 ImportUtils::BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basi
// by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
switch (mode) {
case FBXDocParser::Model::RotOrder_EulerXYZ:
return p_rotation.get_euler_zyx();
return p_rotation.get_euler(Basis::EULER_ORDER_XYZ);
case FBXDocParser::Model::RotOrder_EulerXZY:
return p_rotation.get_euler_yzx();
return p_rotation.get_euler(Basis::EULER_ORDER_XZY);
case FBXDocParser::Model::RotOrder_EulerYZX:
return p_rotation.get_euler_xzy();
return p_rotation.get_euler(Basis::EULER_ORDER_YZX);
case FBXDocParser::Model::RotOrder_EulerYXZ:
return p_rotation.get_euler_zxy();
return p_rotation.get_euler(Basis::EULER_ORDER_YXZ);
case FBXDocParser::Model::RotOrder_EulerZXY:
return p_rotation.get_euler_yxz();
return p_rotation.get_euler(Basis::EULER_ORDER_ZXY);
case FBXDocParser::Model::RotOrder_EulerZYX:
return p_rotation.get_euler_xyz();
return p_rotation.get_euler(Basis::EULER_ORDER_ZYX);
case FBXDocParser::Model::RotOrder_SphericXYZ:
// TODO

View file

@ -17,7 +17,7 @@ func test():
print(Plane(1, 2, 3, 4))
print(Quaternion(1, 2, 3, 4))
print(AABB(Vector3.ZERO, Vector3.ONE))
print(Basis(Vector3(0, 0, 0)))
print(Basis.from_euler(Vector3(0, 0, 0)))
print(Transform3D.IDENTITY)
print(Color(1, 2, 3, 4))

View file

@ -580,6 +580,8 @@ void Area3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
CollisionObject3D::_validate_property(property);
}
void Area3D::_bind_methods() {

View file

@ -638,6 +638,8 @@ void AudioStreamPlayer3D::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
Node3D::_validate_property(property);
}
void AudioStreamPlayer3D::_bus_layout_changed() {

View file

@ -58,6 +58,8 @@ void BoneAttachment3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "";
}
}
Node3D::_validate_property(property);
}
bool BoneAttachment3D::_set(const StringName &p_path, const Variant &p_value) {

View file

@ -71,6 +71,8 @@ void Camera3D::_validate_property(PropertyInfo &p_property) const {
p_property.usage = PROPERTY_USAGE_NOEDITOR;
}
}
Node3D::_validate_property(p_property);
}
void Camera3D::_update_camera() {

View file

@ -532,6 +532,8 @@ void CPUParticles3D::_validate_property(PropertyInfo &property) const {
if (property.name.begins_with("scale_curve_") && !split_scale) {
property.usage = PROPERTY_USAGE_NONE;
}
Node3D::_validate_property(property);
}
static uint32_t idhash(uint32_t x) {

View file

@ -160,6 +160,7 @@ void Decal::_validate_property(PropertyInfo &property) const {
if (!distance_fade_enabled && (property.name == "distance_fade_begin" || property.name == "distance_fade_length")) {
property.usage = PROPERTY_USAGE_NOEDITOR;
}
VisualInstance3D::_validate_property(property);
}
TypedArray<String> Decal::get_configuration_warnings() const {

View file

@ -388,6 +388,8 @@ void GPUParticles3D::_validate_property(PropertyInfo &property) const {
return;
}
}
GeometryInstance3D::_validate_property(property);
}
void GPUParticles3D::emit_particle(const Transform3D &p_transform, const Vector3 &p_velocity, const Color &p_color, const Color &p_custom, uint32_t p_emit_flags) {

View file

@ -215,6 +215,7 @@ void Light3D::_validate_property(PropertyInfo &property) const {
if (get_light_type() != RS::LIGHT_DIRECTIONAL && property.name == "light_angular_distance") {
property.usage = PROPERTY_USAGE_NONE;
}
VisualInstance3D::_validate_property(property);
}
void Light3D::_bind_methods() {

View file

@ -1370,6 +1370,7 @@ void LightmapGI::_validate_property(PropertyInfo &property) const {
if (property.name == "environment_custom_energy" && environment_mode != ENVIRONMENT_MODE_CUSTOM_COLOR && environment_mode != ENVIRONMENT_MODE_CUSTOM_SKY) {
property.usage = PROPERTY_USAGE_NONE;
}
VisualInstance3D::_validate_property(property);
}
void LightmapGI::_bind_methods() {

View file

@ -220,6 +220,13 @@ void Node3D::_notification(int p_what) {
}
}
void Node3D::set_basis(const Basis &p_basis) {
set_transform(Transform3D(p_basis, data.local_transform.origin));
}
void Node3D::set_quaternion(const Quaternion &p_quaternion) {
set_transform(Transform3D(Basis(p_quaternion), data.local_transform.origin));
}
void Node3D::set_transform(const Transform3D &p_transform) {
data.local_transform = p_transform;
data.dirty |= DIRTY_VECTORS;
@ -229,6 +236,13 @@ void Node3D::set_transform(const Transform3D &p_transform) {
}
}
Basis Node3D::get_basis() const {
return get_transform().basis;
}
Quaternion Node3D::get_quaternion() const {
return Quaternion(get_transform().basis);
}
void Node3D::set_global_transform(const Transform3D &p_transform) {
Transform3D xform =
(data.parent && !data.top_level_active) ?
@ -308,6 +322,45 @@ void Node3D::set_position(const Vector3 &p_position) {
}
}
void Node3D::set_rotation_edit_mode(RotationEditMode p_mode) {
if (data.rotation_edit_mode == p_mode) {
return;
}
data.rotation_edit_mode = p_mode;
notify_property_list_changed();
}
Node3D::RotationEditMode Node3D::get_rotation_edit_mode() const {
return data.rotation_edit_mode;
}
void Node3D::set_rotation_order(RotationOrder p_order) {
Basis::EulerOrder order = Basis::EulerOrder(p_order);
if (data.rotation_order == order) {
return;
}
ERR_FAIL_INDEX(int32_t(order), 6);
if (data.dirty & DIRTY_VECTORS) {
data.rotation = data.local_transform.basis.get_euler_normalized(order);
data.scale = data.local_transform.basis.get_scale();
data.dirty &= ~DIRTY_VECTORS;
} else {
data.rotation = Basis::from_euler(data.rotation, data.rotation_order).get_euler_normalized(order);
}
data.rotation_order = order;
//changing rotation order should not affect transform
notify_property_list_changed(); //will change rotation
}
Node3D::RotationOrder Node3D::get_rotation_order() const {
return RotationOrder(data.rotation_order);
}
void Node3D::set_rotation(const Vector3 &p_euler_rad) {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
@ -324,7 +377,7 @@ void Node3D::set_rotation(const Vector3 &p_euler_rad) {
void Node3D::set_scale(const Vector3 &p_scale) {
if (data.dirty & DIRTY_VECTORS) {
data.rotation = data.local_transform.basis.get_rotation();
data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@ -343,7 +396,7 @@ Vector3 Node3D::get_position() const {
Vector3 Node3D::get_rotation() const {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
data.rotation = data.local_transform.basis.get_rotation();
data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@ -354,7 +407,7 @@ Vector3 Node3D::get_rotation() const {
Vector3 Node3D::get_scale() const {
if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale();
data.rotation = data.local_transform.basis.get_rotation();
data.rotation = data.local_transform.basis.get_euler_normalized(data.rotation_order);
data.dirty &= ~DIRTY_VECTORS;
}
@ -780,6 +833,24 @@ NodePath Node3D::get_visibility_parent() const {
return visibility_parent_path;
}
void Node3D::_validate_property(PropertyInfo &property) const {
if (data.rotation_edit_mode != ROTATION_EDIT_MODE_BASIS && property.name == "basis") {
property.usage = 0;
}
if (data.rotation_edit_mode == ROTATION_EDIT_MODE_BASIS && property.name == "scale") {
property.usage = 0;
}
if (data.rotation_edit_mode != ROTATION_EDIT_MODE_QUATERNION && property.name == "quaternion") {
property.usage = 0;
}
if (data.rotation_edit_mode != ROTATION_EDIT_MODE_EULER && property.name == "rotation") {
property.usage = 0;
}
if (data.rotation_edit_mode != ROTATION_EDIT_MODE_EULER && property.name == "rotation_order") {
property.usage = 0;
}
}
void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_transform", "local"), &Node3D::set_transform);
ClassDB::bind_method(D_METHOD("get_transform"), &Node3D::get_transform);
@ -787,8 +858,16 @@ void Node3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_position"), &Node3D::get_position);
ClassDB::bind_method(D_METHOD("set_rotation", "euler"), &Node3D::set_rotation);
ClassDB::bind_method(D_METHOD("get_rotation"), &Node3D::get_rotation);
ClassDB::bind_method(D_METHOD("set_rotation_order", "order"), &Node3D::set_rotation_order);
ClassDB::bind_method(D_METHOD("get_rotation_order"), &Node3D::get_rotation_order);
ClassDB::bind_method(D_METHOD("set_rotation_edit_mode", "edit_mode"), &Node3D::set_rotation_edit_mode);
ClassDB::bind_method(D_METHOD("get_rotation_edit_mode"), &Node3D::get_rotation_edit_mode);
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &Node3D::set_scale);
ClassDB::bind_method(D_METHOD("get_scale"), &Node3D::get_scale);
ClassDB::bind_method(D_METHOD("set_quaternion", "quaternion"), &Node3D::set_quaternion);
ClassDB::bind_method(D_METHOD("get_quaternion"), &Node3D::get_quaternion);
ClassDB::bind_method(D_METHOD("set_basis", "basis"), &Node3D::set_basis);
ClassDB::bind_method(D_METHOD("get_basis"), &Node3D::get_basis);
ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform);
ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform);
ClassDB::bind_method(D_METHOD("get_parent_node_3d"), &Node3D::get_parent_node_3d);
@ -848,15 +927,29 @@ void Node3D::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_EXIT_WORLD);
BIND_CONSTANT(NOTIFICATION_VISIBILITY_CHANGED);
BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_EULER);
BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_QUATERNION);
BIND_ENUM_CONSTANT(ROTATION_EDIT_MODE_BASIS);
BIND_ENUM_CONSTANT(ROTATION_ORDER_XYZ);
BIND_ENUM_CONSTANT(ROTATION_ORDER_XZY);
BIND_ENUM_CONSTANT(ROTATION_ORDER_YXZ);
BIND_ENUM_CONSTANT(ROTATION_ORDER_YZX);
BIND_ENUM_CONSTANT(ROTATION_ORDER_ZXY);
BIND_ENUM_CONSTANT(ROTATION_ORDER_ZYX);
//ADD_PROPERTY( PropertyInfo(Variant::TRANSFORM3D,"transform/global",PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR ), "set_global_transform", "get_global_transform") ;
ADD_GROUP("Transform", "");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "global_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_transform", "get_global_transform");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "position", PROPERTY_HINT_RANGE, "-99999,99999,0,or_greater,or_lesser,noslider,suffix:m", PROPERTY_USAGE_EDITOR), "set_position", "get_position");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "rotation", PROPERTY_HINT_RANGE, "-360,360,0.1,or_lesser,or_greater,radians", PROPERTY_USAGE_EDITOR), "set_rotation", "get_rotation");
ADD_PROPERTY(PropertyInfo(Variant::QUATERNION, "quaternion", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_quaternion", "get_quaternion");
ADD_PROPERTY(PropertyInfo(Variant::BASIS, "basis", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_basis", "get_basis");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_scale", "get_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_edit_mode", PROPERTY_HINT_ENUM, "Euler,Quaternion,Basis"), "set_rotation_edit_mode", "get_rotation_edit_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "rotation_order", PROPERTY_HINT_ENUM, "XYZ,XZY,YXZ,YZX,ZXY,ZYX"), "set_rotation_order", "get_rotation_order");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "top_level"), "set_as_top_level", "is_set_as_top_level");
ADD_GROUP("Matrix", "");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, ""), "set_transform", "get_transform");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM3D, "transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_transform", "get_transform");
ADD_GROUP("Visibility", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visible", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "visibility_parent", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "GeometryInstance3D"), "set_visibility_parent", "get_visibility_parent");

View file

@ -51,6 +51,23 @@ class Node3D : public Node {
GDCLASS(Node3D, Node);
OBJ_CATEGORY("3D");
public:
enum RotationEditMode {
ROTATION_EDIT_MODE_EULER,
ROTATION_EDIT_MODE_QUATERNION,
ROTATION_EDIT_MODE_BASIS,
};
enum RotationOrder {
ROTATION_ORDER_XYZ,
ROTATION_ORDER_XZY,
ROTATION_ORDER_YXZ,
ROTATION_ORDER_YZX,
ROTATION_ORDER_ZXY,
ROTATION_ORDER_ZYX
};
private:
enum TransformDirty {
DIRTY_NONE = 0,
DIRTY_VECTORS = 1,
@ -63,8 +80,10 @@ class Node3D : public Node {
struct Data {
mutable Transform3D global_transform;
mutable Transform3D local_transform;
mutable Basis::EulerOrder rotation_order = Basis::EULER_ORDER_YXZ;
mutable Vector3 rotation;
mutable Vector3 scale = Vector3(1, 1, 1);
mutable RotationEditMode rotation_edit_mode = ROTATION_EDIT_MODE_EULER;
mutable int dirty = DIRTY_NONE;
@ -116,6 +135,8 @@ protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _validate_property(PropertyInfo &property) const override;
public:
enum {
NOTIFICATION_TRANSFORM_CHANGED = SceneTree::NOTIFICATION_TRANSFORM_CHANGED,
@ -130,17 +151,28 @@ public:
Ref<World3D> get_world_3d() const;
void set_position(const Vector3 &p_position);
void set_rotation_edit_mode(RotationEditMode p_mode);
RotationEditMode get_rotation_edit_mode() const;
void set_rotation_order(RotationOrder p_order);
void set_rotation(const Vector3 &p_euler_rad);
void set_scale(const Vector3 &p_scale);
Vector3 get_position() const;
RotationOrder get_rotation_order() const;
Vector3 get_rotation() const;
Vector3 get_scale() const;
void set_transform(const Transform3D &p_transform);
void set_basis(const Basis &p_basis);
void set_quaternion(const Quaternion &p_quaternion);
void set_global_transform(const Transform3D &p_transform);
Transform3D get_transform() const;
Basis get_basis() const;
Quaternion get_quaternion() const;
Transform3D get_global_transform() const;
#ifdef TOOLS_ENABLED
@ -214,4 +246,7 @@ public:
Node3D();
};
VARIANT_ENUM_CAST(Node3D::RotationEditMode)
VARIANT_ENUM_CAST(Node3D::RotationOrder)
#endif // NODE_3D_H

View file

@ -248,6 +248,7 @@ void PathFollow3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "0," + rtos(max) + ",0.01,or_lesser,or_greater";
}
Node3D::_validate_property(property);
}
TypedArray<String> PathFollow3D::get_configuration_warnings() const {

View file

@ -1059,6 +1059,7 @@ void RigidDynamicBody3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
PhysicsBody3D::_validate_property(property);
}
RigidDynamicBody3D::RigidDynamicBody3D() :
@ -1933,6 +1934,7 @@ void CharacterBody3D::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
PhysicsBody3D::_validate_property(property);
}
CharacterBody3D::CharacterBody3D() :
@ -3060,7 +3062,7 @@ void PhysicalBone3D::set_joint_rotation(const Vector3 &p_euler_rad) {
}
Vector3 PhysicalBone3D::get_joint_rotation() const {
return joint_offset.basis.get_rotation();
return joint_offset.basis.get_euler_normalized();
}
const Transform3D &PhysicalBone3D::get_body_offset() const {

View file

@ -188,6 +188,7 @@ void ReflectionProbe::_validate_property(PropertyInfo &property) const {
property.usage = PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL;
}
}
VisualInstance3D::_validate_property(property);
}
void ReflectionProbe::_bind_methods() {

View file

@ -68,7 +68,7 @@ void RemoteTransform3D::_update_remote() {
Transform3D our_trans = get_global_transform();
if (update_remote_rotation) {
n->set_rotation(our_trans.basis.get_rotation());
n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order())));
}
if (update_remote_scale) {
@ -90,7 +90,7 @@ void RemoteTransform3D::_update_remote() {
Transform3D our_trans = get_transform();
if (update_remote_rotation) {
n->set_rotation(our_trans.basis.get_rotation());
n->set_rotation(our_trans.basis.get_euler_normalized(Basis::EulerOrder(n->get_rotation_order())));
}
if (update_remote_scale) {

View file

@ -351,6 +351,8 @@ void SkeletonIK3D::_validate_property(PropertyInfo &property) const {
property.hint_string = "";
}
}
Node::_validate_property(property);
}
void SkeletonIK3D::_bind_methods() {

View file

@ -137,8 +137,7 @@ class SkeletonIK3D : public Node {
FabrikInverseKinematic::Task *task = nullptr;
protected:
virtual void
_validate_property(PropertyInfo &property) const override;
virtual void _validate_property(PropertyInfo &property) const override;
static void _bind_methods();
virtual void _notification(int p_what);

View file

@ -743,6 +743,8 @@ void Sprite3D::_validate_property(PropertyInfo &property) const {
if (property.name == "frame_coords") {
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
SpriteBase3D::_validate_property(property);
}
void Sprite3D::_bind_methods() {
@ -1015,6 +1017,8 @@ void AnimatedSprite3D::_validate_property(PropertyInfo &property) const {
}
property.usage |= PROPERTY_USAGE_KEYING_INCREMENTS;
}
SpriteBase3D::_validate_property(property);
}
void AnimatedSprite3D::_notification(int p_what) {

View file

@ -261,6 +261,8 @@ void XRNode3D::_validate_property(PropertyInfo &property) const {
}
property.hint_string = hint_string;
}
Node3D::_validate_property(property);
}
void XRNode3D::set_tracker(const StringName p_tracker_name) {

View file

@ -166,6 +166,8 @@ void AnimationPlayer::_validate_property(PropertyInfo &property) const {
property.hint_string = hint;
}
Node::_validate_property(property);
}
void AnimationPlayer::_get_property_list(List<PropertyInfo> *p_list) const {

View file

@ -292,6 +292,8 @@ void AudioStreamPlayer::_validate_property(PropertyInfo &property) const {
property.hint_string = options;
}
Node::_validate_property(property);
}
void AudioStreamPlayer::_bus_layout_changed() {

View file

@ -232,7 +232,7 @@ GodotGeneric6DOFJoint3D::GodotGeneric6DOFJoint3D(GodotBody3D *rbA, GodotBody3D *
void GodotGeneric6DOFJoint3D::calculateAngleInfo() {
Basis relative_frame = m_calculatedTransformB.basis.inverse() * m_calculatedTransformA.basis;
m_calculatedAxisAngleDiff = relative_frame.get_euler_xyz();
m_calculatedAxisAngleDiff = relative_frame.get_euler(Basis::EULER_ORDER_XYZ);
// in euler angle mode we do not actually constrain the angular velocity
// along the axes axis[0] and axis[2] (although we do use axis[1]) :

View file

@ -60,27 +60,27 @@ Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) {
Basis ret;
switch (mode) {
case EulerXYZ:
ret.set_euler_xyz(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ);
break;
case EulerXZY:
ret.set_euler_xzy(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY);
break;
case EulerYZX:
ret.set_euler_yzx(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX);
break;
case EulerYXZ:
ret.set_euler_yxz(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ);
break;
case EulerZXY:
ret.set_euler_zxy(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY);
break;
case EulerZYX:
ret.set_euler_zyx(p_rotation);
ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX);
break;
default:
@ -94,22 +94,22 @@ Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) {
Vector3 BasisToEuler(RotOrder mode, const Basis &p_rotation) {
switch (mode) {
case EulerXYZ:
return p_rotation.get_euler_xyz();
return p_rotation.get_euler(Basis::EULER_ORDER_XYZ);
case EulerXZY:
return p_rotation.get_euler_xzy();
return p_rotation.get_euler(Basis::EULER_ORDER_XZY);
case EulerYZX:
return p_rotation.get_euler_yzx();
return p_rotation.get_euler(Basis::EULER_ORDER_YZX);
case EulerYXZ:
return p_rotation.get_euler_yxz();
return p_rotation.get_euler(Basis::EULER_ORDER_YXZ);
case EulerZXY:
return p_rotation.get_euler_zxy();
return p_rotation.get_euler(Basis::EULER_ORDER_ZXY);
case EulerZYX:
return p_rotation.get_euler_zyx();
return p_rotation.get_euler(Basis::EULER_ORDER_ZYX);
default:
// If you land here, Please integrate all rotation orders.
@ -170,9 +170,9 @@ void test_rotation(Vector3 deg_original_euler, RotOrder rot_order) {
CHECK_MESSAGE((res.get_axis(2) - Vector3(0.0, 0.0, 1.0)).length() <= 0.1, vformat("Fail due to Z %s\n", String(res.get_axis(2))).utf8().ptr());
// Double check `to_rotation` decomposing with XYZ rotation order.
const Vector3 euler_xyz_from_rotation = to_rotation.get_euler_xyz();
const Vector3 euler_xyz_from_rotation = to_rotation.get_euler(Basis::EULER_ORDER_XYZ);
Basis rotation_from_xyz_computed_euler;
rotation_from_xyz_computed_euler.set_euler_xyz(euler_xyz_from_rotation);
rotation_from_xyz_computed_euler.set_euler(euler_xyz_from_rotation, Basis::EULER_ORDER_XYZ);
res = to_rotation.inverse() * rotation_from_xyz_computed_euler;