Il y a une grande synthèse sur ce processus par Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Il est également désormais implémenté dans glm, à partir de la version 0.9.7.0, 02/08/2015. Découvrez l'implémentation .
Pour comprendre les mathématiques, vous devez regarder les valeurs qui se trouvent dans votre matrice de rotation. De plus, vous devez connaître l'ordre dans lequel les rotations ont été appliquées pour créer votre matrice afin d'extraire correctement les valeurs.
Une matrice de rotation à partir des angles d'Euler est formée en combinant des rotations autour des axes x, y et z. Par exemple, la rotation de θ degrés autour de Z peut être effectuée avec la matrice
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Des matrices similaires existent pour la rotation autour des axes X et Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Nous pouvons multiplier ces matrices ensemble pour créer une matrice qui est le résultat des trois rotations. Il est important de noter que l'ordre de multiplication de ces matrices est important, car la multiplication matricielle n'est pas commutative . Cela veut dire que Rx*Ry*Rz ≠ Rz*Ry*Rx
. Considérons un ordre de rotation possible, zyx. Lorsque les trois matrices sont combinées, il en résulte une matrice qui ressemble à ceci:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
où Cx
est le cosinus de l' x
angle de rotation, Sx
est le sinus de l' x
angle de rotation, etc.
Maintenant, le défi est d'extraire l'original x
, y
et les z
valeurs qui sont entrées dans la matrice.
Voyons d'abord l' x
angle. Si nous connaissons le sin(x)
et cos(x)
, nous pouvons utiliser la fonction tangente inverse atan2
pour nous rendre notre angle. Malheureusement, ces valeurs n'apparaissent pas seules dans notre matrice. Mais, si nous regardons de plus près les éléments M[1][2]
et M[2][2]
, nous pouvons voir que nous le savons -sin(x)*cos(y)
aussi cos(x)*cos(y)
. Étant donné que la fonction tangente est le rapport des côtés opposés et adjacents d'un triangle, la mise à l'échelle des deux valeurs de la même quantité (dans ce cas cos(y)
) donnera le même résultat. Donc,
x = atan2(-M[1][2], M[2][2])
Essayons maintenant d'obtenir y
. Nous savons sin(y)
de M[0][2]
. Si nous avions cos (y), nous pourrions utiliser à atan2
nouveau, mais nous n'avons pas cette valeur dans notre matrice. Cependant, en raison de l'identité pythagoricienne , nous savons que:
cosY = sqrt(1 - M[0][2])
Ainsi, nous pouvons calculer y
:
y = atan2(M[0][2], cosY)
Enfin, nous devons calculer z
. C'est là que l'approche de Mike Day diffère de la réponse précédente. Puisqu'à ce stade, nous connaissons la quantité x
et la y
rotation, nous pouvons construire une matrice de rotation XY et trouver la quantité de z
rotation nécessaire pour correspondre à la matrice cible. La RxRy
matrice ressemble à ceci:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Puisque nous savons que RxRy
* Rz
est égal à notre matrice d'entrée M
, nous pouvons utiliser cette matrice pour revenir à Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
L' inverse d'une matrice de rotation est sa transposition , nous pouvons donc l'étendre à:
┌ Cy SxSy -CxSy ┐┌M00 M01 M02┐ ┌ cosZ -sinZ 0 ┐
│ 0 Cx Sx ││M10 M11 M12│ = │ sinZ cosZ 0 │
└ Sy -SxCy CxCy ┘└M20 M21 M22┘ └ 0 0 1 ┘
Nous pouvons maintenant résoudre pour sinZ
et cosZ
en effectuant la multiplication matricielle. Il suffit de calculer les éléments [1][0]
et [1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Voici une implémentation complète pour référence:
#include <iostream>
#include <cmath>
class Vec4 {
public:
Vec4(float x, float y, float z, float w) :
x(x), y(y), z(z), w(w) {}
float dot(const Vec4& other) const {
return x * other.x +
y * other.y +
z * other.z +
w * other.w;
};
float x, y, z, w;
};
class Mat4x4 {
public:
Mat4x4() {}
Mat4x4(float v00, float v01, float v02, float v03,
float v10, float v11, float v12, float v13,
float v20, float v21, float v22, float v23,
float v30, float v31, float v32, float v33) {
values[0] = v00;
values[1] = v01;
values[2] = v02;
values[3] = v03;
values[4] = v10;
values[5] = v11;
values[6] = v12;
values[7] = v13;
values[8] = v20;
values[9] = v21;
values[10] = v22;
values[11] = v23;
values[12] = v30;
values[13] = v31;
values[14] = v32;
values[15] = v33;
}
Vec4 row(const int row) const {
return Vec4(
values[row*4],
values[row*4+1],
values[row*4+2],
values[row*4+3]
);
}
Vec4 column(const int column) const {
return Vec4(
values[column],
values[column + 4],
values[column + 8],
values[column + 12]
);
}
Mat4x4 multiply(const Mat4x4& other) const {
Mat4x4 result;
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
result.values[row*4+column] = this->row(row).dot(other.column(column));
}
}
return result;
}
void extractEulerAngleXYZ(float& rotXangle, float& rotYangle, float& rotZangle) const {
rotXangle = atan2(-row(1).z, row(2).z);
float cosYangle = sqrt(pow(row(0).x, 2) + pow(row(0).y, 2));
rotYangle = atan2(row(0).z, cosYangle);
float sinXangle = sin(rotXangle);
float cosXangle = cos(rotXangle);
rotZangle = atan2(cosXangle * row(1).x + sinXangle * row(2).x, cosXangle * row(1).y + sinXangle * row(2).y);
}
float values[16];
};
float toRadians(float degrees) {
return degrees * (M_PI / 180);
}
float toDegrees(float radians) {
return radians * (180 / M_PI);
}
int main() {
float rotXangle = toRadians(15);
float rotYangle = toRadians(30);
float rotZangle = toRadians(60);
Mat4x4 rotX(
1, 0, 0, 0,
0, cos(rotXangle), -sin(rotXangle), 0,
0, sin(rotXangle), cos(rotXangle), 0,
0, 0, 0, 1
);
Mat4x4 rotY(
cos(rotYangle), 0, sin(rotYangle), 0,
0, 1, 0, 0,
-sin(rotYangle), 0, cos(rotYangle), 0,
0, 0, 0, 1
);
Mat4x4 rotZ(
cos(rotZangle), -sin(rotZangle), 0, 0,
sin(rotZangle), cos(rotZangle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
Mat4x4 concatenatedRotationMatrix =
rotX.multiply(rotY.multiply(rotZ));
float extractedXangle = 0, extractedYangle = 0, extractedZangle = 0;
concatenatedRotationMatrix.extractEulerAngleXYZ(
extractedXangle, extractedYangle, extractedZangle
);
std::cout << toDegrees(extractedXangle) << ' ' <<
toDegrees(extractedYangle) << ' ' <<
toDegrees(extractedZangle) << std::endl;
return 0;
}