C'è un ottimo resoconto su questo processo di Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Ora è anche implementato in glm, a partire dalla versione 0.9.7.0, 02/08/2015. Controlla l'implementazione .
Per capire la matematica, dovresti guardare i valori che sono nella tua matrice di rotazione. Inoltre, devi conoscere l'ordine in cui sono state applicate le rotazioni per creare la tua matrice al fine di estrarre correttamente i valori.
Una matrice di rotazione dagli angoli di Eulero si forma combinando le rotazioni attorno agli assi x, y e z. Ad esempio, con la matrice è possibile ruotare di around gradi attorno a Z.
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Matrici simili esistono per ruotare attorno agli assi X e Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Possiamo moltiplicare queste matrici insieme per creare una matrice che è il risultato di tutte e tre le rotazioni. È importante notare che l'ordine in cui queste matrici vengono moltiplicate insieme è importante, poiché la moltiplicazione delle matrici non è commutativa . Questo significa che Rx*Ry*Rz ≠ Rz*Ry*Rx
. Consideriamo un possibile ordine di rotazione, zyx. Quando le tre matrici vengono combinate, si ottiene una matrice simile a questa:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
dove Cx
è il coseno x
dell'angolo di rotazione, Sx
è il seno x
dell'angolo di rotazione, ecc.
Ora, la sfida è quella di estrarre l'originale x
, y
e z
valori che è andato in matrice.
Prima di tutto x
tiriamo fuori l' angolo. Se conosciamo il sin(x)
e cos(x)
, possiamo usare la funzione tangente inversa atan2
per restituirci il nostro angolo. Sfortunatamente, questi valori non compaiono da soli nella nostra matrice. Ma, se diamo uno sguardo più da vicino agli elementi M[1][2]
e M[2][2]
, possiamo vedere che lo sappiamo -sin(x)*cos(y)
bene cos(x)*cos(y)
. Poiché la funzione tangente è il rapporto tra i lati opposti e adiacenti di un triangolo, il ridimensionamento di entrambi i valori della stessa quantità (in questo caso cos(y)
) produrrà lo stesso risultato. Così,
x = atan2(-M[1][2], M[2][2])
Ora proviamo a ottenere y
. Lo sappiamo sin(y)
da M[0][2]
. Se avessimo cos (y), potremmo usarlo di atan2
nuovo, ma non abbiamo quel valore nella nostra matrice. Tuttavia, a causa dell'identità pitagorica , sappiamo che:
cosY = sqrt(1 - M[0][2])
Quindi, possiamo calcolare y
:
y = atan2(M[0][2], cosY)
Infine, dobbiamo calcolare z
. È qui che l'approccio di Mike Day differisce dalla risposta precedente. Poiché a questo punto conosciamo la quantità x
e la y
rotazione, possiamo costruire una matrice di rotazione XY e trovare la quantità di z
rotazione necessaria per abbinare la matrice target. La RxRy
matrice si presenta così:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Poiché sappiamo che RxRy
* Rz
è uguale alla nostra matrice di input M
, possiamo usare questa matrice per tornare a Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
L' inverso di una matrice di rotazione è la sua trasposizione , quindi possiamo espanderlo a:
┌ 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 ┘
Ora possiamo risolvere per sinZ
ed cosZ
eseguendo la moltiplicazione della matrice. Dobbiamo solo calcolare gli elementi [1][0]
e [1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Ecco un'implementazione completa per riferimento:
#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;
}