diff --git a/core/math/matrix3.cpp b/core/math/matrix3.cpp
index a985e29abb0..a928b4d0e59 100644
--- a/core/math/matrix3.cpp
+++ b/core/math/matrix3.cpp
@@ -133,16 +133,18 @@ Matrix3 Matrix3::transposed() const {
return tr;
}
+// Multiplies the matrix from left by the scaling matrix: M -> S.M
+// See the comment for Matrix3::rotated for further explanation.
void Matrix3::scale(const Vector3& p_scale) {
elements[0][0]*=p_scale.x;
- elements[1][0]*=p_scale.x;
- elements[2][0]*=p_scale.x;
- elements[0][1]*=p_scale.y;
+ elements[0][1]*=p_scale.x;
+ elements[0][2]*=p_scale.x;
+ elements[1][0]*=p_scale.y;
elements[1][1]*=p_scale.y;
- elements[2][1]*=p_scale.y;
- elements[0][2]*=p_scale.z;
- elements[1][2]*=p_scale.z;
+ elements[1][2]*=p_scale.y;
+ elements[2][0]*=p_scale.z;
+ elements[2][1]*=p_scale.z;
elements[2][2]*=p_scale.z;
}
@@ -154,8 +156,13 @@ Matrix3 Matrix3::scaled( const Vector3& p_scale ) const {
}
Vector3 Matrix3::get_scale() const {
-
- return Vector3(
+ // We are assuming M = R.S, and performing a polar decomposition to extract R and S.
+ // FIXME: We eventually need a proper polar decomposition.
+ // As a cheap workaround until then, to ensure that R is a proper rotation matrix with determinant +1
+ // (such that it can be represented by a Quat or Euler angles), we absorb the sign flip into the scaling matrix.
+ // As such, it works in conjuction with get_rotation().
+ real_t det_sign = determinant() > 0 ? 1 : -1;
+ return det_sign*Vector3(
Vector3(elements[0][0],elements[1][0],elements[2][0]).length(),
Vector3(elements[0][1],elements[1][1],elements[2][1]).length(),
Vector3(elements[0][2],elements[1][2],elements[2][2]).length()
@@ -163,18 +170,40 @@ Vector3 Matrix3::get_scale() const {
}
-// Matrix3::rotate and Matrix3::rotated return M * R(axis,phi), and is a convenience function. They do *not* perform proper matrix rotation.
-void Matrix3::rotate(const Vector3& p_axis, real_t p_phi) {
- // TODO: This function should also be renamed as the current name is misleading: rotate does *not* perform matrix rotation.
- // Same problem affects Matrix3::rotated.
- // A similar problem exists in 2D math, which will be handled separately.
- // After Matrix3 is renamed to Basis, this comments needs to be revised.
- *this = *this * Matrix3(p_axis, p_phi);
+// Multiplies the matrix from left by the rotation matrix: M -> R.M
+// Note that this does *not* rotate the matrix itself.
+//
+// The main use of Matrix3 is as Transform.basis, which is used a the transformation matrix
+// of 3D object. Rotate here refers to rotation of the object (which is R * (*this)),
+// not the matrix itself (which is R * (*this) * R.transposed()).
+Matrix3 Matrix3::rotated(const Vector3& p_axis, real_t p_phi) const {
+ return Matrix3(p_axis, p_phi) * (*this);
}
-Matrix3 Matrix3::rotated(const Vector3& p_axis, real_t p_phi) const {
- return *this * Matrix3(p_axis, p_phi);
+void Matrix3::rotate(const Vector3& p_axis, real_t p_phi) {
+ *this = rotated(p_axis, p_phi);
+}
+Matrix3 Matrix3::rotated(const Vector3& p_euler) const {
+ return Matrix3(p_euler) * (*this);
+}
+
+void Matrix3::rotate(const Vector3& p_euler) {
+ *this = rotated(p_euler);
+}
+
+Vector3 Matrix3::get_rotation() 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.
+ Matrix3 m = orthonormalized();
+ real_t det = m.determinant();
+ if (det < 0) {
+ // Ensure that the determinant is 1, such that result is a proper rotation matrix which can be represented by Euler angles.
+ m.scale(Vector3(-1,-1,-1));
+ }
+
+ return m.get_euler();
}
// get_euler returns a vector containing the Euler angles in the format
@@ -363,7 +392,7 @@ int Matrix3::get_orthogonal_index() const {
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
- float v = orth[i][j];
+ real_t v = orth[i][j];
if (v>0.5)
v=1.0;
else if (v<-0.5)
@@ -398,9 +427,6 @@ void Matrix3::set_orthogonal_index(int p_index){
void Matrix3::get_axis_and_angle(Vector3 &r_axis,real_t& r_angle) const {
- // TODO: We can handle improper matrices here too, in which case axis will also correspond to the axis of reflection.
- // See Eq. (52) in http://scipp.ucsc.edu/~haber/ph251/rotreflect_13.pdf for example
- // After that change, we should fail on is_orthogonal() == false.
ERR_FAIL_COND(is_rotation() == false);
diff --git a/core/math/matrix3.h b/core/math/matrix3.h
index 1d967c03b82..33a5ce86874 100644
--- a/core/math/matrix3.h
+++ b/core/math/matrix3.h
@@ -55,7 +55,7 @@ public:
Matrix3 inverse() const;
Matrix3 transposed() const;
- _FORCE_INLINE_ float determinant() const;
+ _FORCE_INLINE_ real_t determinant() const;
void from_z(const Vector3& p_z);
@@ -73,6 +73,10 @@ public:
void rotate(const Vector3& p_axis, real_t p_phi);
Matrix3 rotated(const Vector3& p_axis, real_t p_phi) const;
+ void rotate(const Vector3& p_euler);
+ Matrix3 rotated(const Vector3& p_euler) const;
+ Vector3 get_rotation() const;
+
void scale( const Vector3& p_scale );
Matrix3 scaled( const Vector3& p_scale ) const;
Vector3 get_scale() const;
@@ -226,7 +230,7 @@ Vector3 Matrix3::xform_inv(const Vector3& p_vector) const {
);
}
-float Matrix3::determinant() const {
+real_t Matrix3::determinant() const {
return elements[0][0]*(elements[1][1]*elements[2][2] - elements[2][1]*elements[1][2]) -
elements[1][0]*(elements[0][1]*elements[2][2] - elements[2][1]*elements[0][2]) +
diff --git a/core/math/transform.cpp b/core/math/transform.cpp
index 8516e4afcfa..0dba1210134 100644
--- a/core/math/transform.cpp
+++ b/core/math/transform.cpp
@@ -54,7 +54,8 @@ void Transform::invert() {
}
Transform Transform::inverse() const {
-
+ // FIXME: this function assumes the basis is a rotation matrix, with no scaling.
+ // Transform::affine_inverse can handle matrices with scaling, so GDScript should eventually use that.
Transform ret=*this;
ret.invert();
return ret;
@@ -63,12 +64,12 @@ Transform Transform::inverse() const {
void Transform::rotate(const Vector3& p_axis,real_t p_phi) {
- *this = *this * Transform( Matrix3( p_axis, p_phi ), Vector3() );
+ *this = rotated(p_axis, p_phi);
}
Transform Transform::rotated(const Vector3& p_axis,real_t p_phi) const{
- return *this * Transform( Matrix3( p_axis, p_phi ), Vector3() );
+ return Transform(Matrix3( p_axis, p_phi ), Vector3()) * (*this);
}
void Transform::rotate_basis(const Vector3& p_axis,real_t p_phi) {
@@ -113,7 +114,7 @@ void Transform::set_look_at( const Vector3& p_eye, const Vector3& p_target, cons
}
-Transform Transform::interpolate_with(const Transform& p_transform, float p_c) const {
+Transform Transform::interpolate_with(const Transform& p_transform, real_t p_c) const {
/* not sure if very "efficient" but good enough? */
diff --git a/core/math/transform.h b/core/math/transform.h
index 7999f0b347f..5f069ab586c 100644
--- a/core/math/transform.h
+++ b/core/math/transform.h
@@ -86,7 +86,7 @@ public:
void operator*=(const Transform& p_transform);
Transform operator*(const Transform& p_transform) const;
- Transform interpolate_with(const Transform& p_transform, float p_c) const;
+ Transform interpolate_with(const Transform& p_transform, real_t p_c) const;
_FORCE_INLINE_ Transform inverse_xform(const Transform& t) const {
diff --git a/doc/base/classes.xml b/doc/base/classes.xml
index 4be1666e59a..be18de9d25d 100644
--- a/doc/base/classes.xml
+++ b/doc/base/classes.xml
@@ -20694,7 +20694,8 @@
3x3 matrix datatype.
- 3x3 matrix used for 3D rotation and scale. Contains 3 vector fields x,y and z. Can also be accessed as array of 3D vectors. Almost always used as orthogonal basis for a [Transform].
+ 3x3 matrix used for 3D rotation and scale. Contains 3 vector fields x,y and z as its columns, which can be interpreted as the local basis vectors of a transformation. Can also be accessed as array of 3D vectors. Almost always used as orthogonal basis for a [Transform].
+ For such use, it is composed of a scaling and a rotation matrix, in that order (M = R.S).
@@ -20703,7 +20704,7 @@
- Create a matrix from a quaternion.
+ Create a rotation matrix from the given quaternion.
@@ -20714,7 +20715,7 @@
- Create a matrix which rotates around the given axis by the specified angle.
+ Create a rotation matrix which rotates around the given axis by the specified angle.
@@ -20741,26 +20742,29 @@
- Return euler angles (in the XYZ convention: first Z, then Y, and X last) from the matrix. Returned vector contains the rotation angles in the format (third,second,first).
+ Return Euler angles (in the XYZ convention: first Z, then Y, and X last) from the matrix. Returned vector contains the rotation angles in the format (third,second,first).
+ This function only works if the matrix represents a proper rotation.
+ This function considers a discretization of rotations into 24 points on unit sphere, lying along the vectors (x,y,z) with each component being either -1,0 or 1, and returns the index of the point best representing the orientation of the object. It is mainly used by the grid map editor. For further details, refer to Godot source code.
+ Assuming that the matrix is the combination of a rotation and scaling, return the absolute value of scaling factors along each axis.
- Return the affine inverse of the matrix.
+ Return the inverse of the matrix.
@@ -20777,6 +20781,9 @@
+
+ Introduce an additional rotation around the given axis by phi. Only relevant when the matrix is being used as a part of [Transform].
+
@@ -20784,7 +20791,7 @@
- Return the scaled version of the matrix, by a 3D scale.
+ Introduce an additional scaling specified by the given 3D scaling factor. Only relevant when the matrix is being used as a part of [Transform].
@@ -20827,7 +20834,7 @@
- Return a vector transformed by the matrix and return it.
+ Return a vector transformed (multiplied) by the matrix and return it.
@@ -20836,7 +20843,7 @@
- Return a vector transformed by the transposed matrix and return it.
+ Return a vector transformed (multiplied) by the transposed matrix and return it. Note that this is a multiplication by inverse only when the matrix represents a rotation-reflection.
@@ -20893,6 +20900,7 @@
+ Return the inverse of the matrix.
@@ -31482,7 +31490,7 @@
Quaternion.
- Quaternion is a 4 dimensional vector that is used to represent a rotation. It mainly exists to perform SLERP (spherical-linear interpolation) between to rotations obtained by a Matrix3 cheaply. Multiplying quaternions also cheaply reproduces rotation sequences, however quaternions need to be often normalized, or else they suffer from precision issues.
+ Quaternion is a 4 dimensional vector that is used to represent a rotation. It mainly exists to perform SLERP (spherical-linear interpolation) between two rotations. Multiplying quaternions also cheaply reproduces rotation sequences. However quaternions need to be often renormalized, or else they suffer from precision issues.
@@ -43038,7 +43046,7 @@
3D Transformation.
- Transform is used to store transformations, including translations. It consists of a Matrix3 "basis" and Vector3 "origin". Transform is used to represent transformations of any object in space. It is similar to a 4x3 matrix.
+ Transform is used to store translation, rotation and scaling transformations. It consists of a Matrix3 "basis" and Vector3 "origin". Transform is used to represent transformations of objects in space, and as such, determine their position, orientation and scale. It is similar to a 3x4 matrix.
@@ -43053,7 +43061,7 @@
- Construct the Transform from four Vector3. Each axis creates the basis.
+ Construct the Transform from four Vector3. Each axis corresponds to local basis vectors (some of which may be scaled).
@@ -43082,7 +43090,7 @@
- Construct the Transform from a Quat. The origin will be Vector3(0, 0, 0)
+ Construct the Transform from a Quat. The origin will be Vector3(0, 0, 0).
@@ -43091,21 +43099,21 @@
- Construct the Transform from a Matrix3. The origin will be Vector3(0, 0, 0)
+ Construct the Transform from a Matrix3. The origin will be Vector3(0, 0, 0).
- Returns the inverse of the transfrom, even if the transform has scale or the axis vectors are not orthogonal.
+ Returns the inverse of the transfrom, under the assumption that the transformation is composed of rotation, scaling and translation.
- Returns the inverse of the transform.
+ Returns the inverse of the transform, under the assumption that the transformation is composed of rotation and translation (no scaling).
@@ -43134,7 +43142,7 @@
- Rotate the transform locally. This introduces an additional pre-rotation to the transform, changing the basis to basis * Matrix3(axis, phi).
+ Rotate the transform around given axis by phi.
@@ -43143,7 +43151,7 @@
- Scale the transform locally.
+ Scale the transform by the specified 3D scaling factors.
@@ -43152,7 +43160,7 @@
- Translate the transform locally.
+ Translate the transform by the specified displacement.
@@ -43161,7 +43169,7 @@
- Transforms vector "v" by this transform.
+ Transforms the given vector "v" by this transform.
@@ -43176,7 +43184,7 @@
- The basis contains 3 [Vector3]. X axis, Y axis, and Z axis.
+ The basis is a matrix containing 3 [Vector3] as its columns: X axis, Y axis, and Z axis. These vectors can be interpreted as the basis vectors of local coordinate system travelling with the object.
The origin of the transform. Which is the translation offset.
diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp
index b5e80c48a1c..50f9e450191 100644
--- a/scene/3d/spatial.cpp
+++ b/scene/3d/spatial.cpp
@@ -83,9 +83,9 @@ void Spatial::_notify_dirty() {
void Spatial::_update_local_transform() const {
-
- data.local_transform.basis.set_euler(data.rotation);
+ data.local_transform.basis = Matrix3();
data.local_transform.basis.scale(data.scale);
+ data.local_transform.basis.rotate(data.rotation);
data.dirty&=~DIRTY_LOCAL;
}
@@ -376,7 +376,7 @@ void Spatial::_set_rotation_deg(const Vector3& p_euler_deg) {
void Spatial::set_scale(const Vector3& p_scale){
if (data.dirty&DIRTY_VECTORS) {
- data.rotation=data.local_transform.basis.get_euler();
+ data.rotation=data.local_transform.basis.get_rotation();
data.dirty&=~DIRTY_VECTORS;
}
@@ -398,7 +398,8 @@ Vector3 Spatial::get_rotation() const{
if (data.dirty&DIRTY_VECTORS) {
data.scale=data.local_transform.basis.get_scale();
- data.rotation=data.local_transform.basis.get_euler();
+ data.rotation=data.local_transform.basis.get_rotation();
+
data.dirty&=~DIRTY_VECTORS;
}
@@ -422,7 +423,8 @@ Vector3 Spatial::get_scale() const{
if (data.dirty&DIRTY_VECTORS) {
data.scale=data.local_transform.basis.get_scale();
- data.rotation=data.local_transform.basis.get_euler();
+ data.rotation=data.local_transform.basis.get_rotation();
+
data.dirty&=~DIRTY_VECTORS;
}
diff --git a/tools/editor/plugins/multimesh_editor_plugin.cpp b/tools/editor/plugins/multimesh_editor_plugin.cpp
index 9b195422689..4e9c4ebf9da 100644
--- a/tools/editor/plugins/multimesh_editor_plugin.cpp
+++ b/tools/editor/plugins/multimesh_editor_plugin.cpp
@@ -238,9 +238,10 @@ void MultiMeshEditor::_populate() {
Matrix3 post_xform;
- post_xform.rotate(xform.basis.get_axis(0),-Math::random(-_tilt_random,_tilt_random)*Math_PI);
- post_xform.rotate(xform.basis.get_axis(2),-Math::random(-_tilt_random,_tilt_random)*Math_PI);
post_xform.rotate(xform.basis.get_axis(1),-Math::random(-_rotate_random,_rotate_random)*Math_PI);
+ post_xform.rotate(xform.basis.get_axis(2),-Math::random(-_tilt_random,_tilt_random)*Math_PI);
+ post_xform.rotate(xform.basis.get_axis(0),-Math::random(-_tilt_random,_tilt_random)*Math_PI);
+
xform.basis = post_xform * xform.basis;
//xform.basis.orthonormalize();
diff --git a/tools/editor/plugins/spatial_editor_plugin.cpp b/tools/editor/plugins/spatial_editor_plugin.cpp
index a577e97843c..b6627d72bf6 100644
--- a/tools/editor/plugins/spatial_editor_plugin.cpp
+++ b/tools/editor/plugins/spatial_editor_plugin.cpp
@@ -61,8 +61,8 @@ void SpatialEditorViewport::_update_camera() {
Transform camera_transform;
camera_transform.translate(cursor.pos);
- camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot);
if (orthogonal)
camera_transform.translate(0, 0, 4096);
@@ -474,8 +474,8 @@ Vector3 SpatialEditorViewport::_get_screen_to_space(const Vector3& p_pos) {
Transform camera_transform;
camera_transform.translate( cursor.pos );
- camera_transform.basis.rotate(Vector3(0,1,0),-cursor.y_rot);
camera_transform.basis.rotate(Vector3(1,0,0),-cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0,1,0),-cursor.y_rot);
camera_transform.translate(0,0,cursor.distance);
return camera_transform.xform(Vector3( ((p_pos.x/get_size().width)*2.0-1.0)*screen_w, ((1.0-(p_pos.y/get_size().height))*2.0-1.0)*screen_h,-get_znear()));
@@ -1502,7 +1502,7 @@ void SpatialEditorViewport::_sinput(const InputEvent &p_event) {
Transform original=se->original;
Transform base=Transform( Matrix3(), _edit.center);
- Transform t=base * (r * (base.inverse() * original));
+ Transform t=base * r * base.inverse() * original;
sp->set_global_transform(t);
}
@@ -1591,8 +1591,8 @@ void SpatialEditorViewport::_sinput(const InputEvent &p_event) {
Transform camera_transform;
camera_transform.translate(cursor.pos);
- camera_transform.basis.rotate(Vector3(0,1,0),-cursor.y_rot);
camera_transform.basis.rotate(Vector3(1,0,0),-cursor.x_rot);
+ camera_transform.basis.rotate(Vector3(0,1,0),-cursor.y_rot);
Vector3 translation(-m.relative_x*pan_speed,m.relative_y*pan_speed,0);
translation*=cursor.distance/DISTANCE_DEFAULT;
camera_transform.translate(translation);
@@ -2803,21 +2803,10 @@ void SpatialEditor::_xform_dialog_action() {
rotate[i]=Math::deg2rad(xform_rotate[i]->get_text().to_double());
scale[i]=xform_scale[i]->get_text().to_double();
}
-
+
+ t.basis.scale(scale);
+ t.basis.rotate(rotate);
t.origin=translate;
- for(int i=0;i<3;i++) {
- if (!rotate[i])
- continue;
- Vector3 axis;
- axis[i]=1.0;
- t.basis.rotate(axis,rotate[i]); // BUG(?): Angle not flipped; please check during the review of PR #6865.
- }
-
- for(int i=0;i<3;i++) {
- if (scale[i]==1)
- continue;
- t.basis.set_axis(i,t.basis.get_axis(i)*scale[i]);
- }
undo_redo->create_action(TTR("XForm Dialog"));