Há um ótimo artigo sobre esse processo de Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Agora também está implementado no glm, a partir da versão 0.9.7.0, 02/08/2015. Confira a implementação .
Para entender a matemática, você deve examinar os valores que estão na sua matriz de rotação. Além disso, você precisa saber a ordem em que as rotações foram aplicadas para criar sua matriz para extrair adequadamente os valores.
Uma matriz de rotação dos ângulos de Euler é formada pela combinação de rotações em torno dos eixos x, y e z. Por exemplo, girar θ graus em torno de Z pode ser feito com a matriz
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Existem matrizes semelhantes para girar sobre os eixos X e Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Podemos multiplicar essas matrizes para criar uma matriz que é o resultado de todas as três rotações. É importante observar que a ordem em que essas matrizes são multiplicadas é importante, porque a multiplicação da matriz não é comutativa . Isso significa isso Rx*Ry*Rz ≠ Rz*Ry*Rx
. Vamos considerar uma ordem de rotação possível, zyx. Quando as três matrizes são combinadas, resulta em uma matriz que se parece com isso:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
onde Cx
é o cosseno do x
ângulo de rotação, Sx
é o seno do x
ângulo de rotação, etc.
Agora, o desafio é extrair o original x
, y
e z
os valores que entraram na matriz.
Vamos primeiro entender o x
ângulo. Se conhecemos o sin(x)
e cos(x)
, podemos usar a função inversa da tangente atan2
para nos devolver o ângulo. Infelizmente, esses valores não aparecem sozinhos em nossa matriz. Mas, se dermos uma olhada mais atenta nos elementos M[1][2]
e M[2][2]
, podemos ver que sabemos -sin(x)*cos(y)
também cos(x)*cos(y)
. Como a função tangente é a razão entre os lados oposto e adjacente de um triângulo, escalar os dois valores na mesma quantidade (nesse caso cos(y)
) produzirá o mesmo resultado. Portanto,
x = atan2(-M[1][2], M[2][2])
Agora vamos tentar obter y
. Nós sabemos sin(y)
de M[0][2]
. Se tivéssemos cos (y), poderíamos usar atan2
novamente, mas não temos esse valor em nossa matriz. No entanto, devido à identidade pitagórica , sabemos que:
cosY = sqrt(1 - M[0][2])
Então, podemos calcular y
:
y = atan2(M[0][2], cosY)
Por último, precisamos calcular z
. É aqui que a abordagem de Mike Day difere da resposta anterior. Como neste momento sabemos a quantidade x
e a y
rotação, podemos construir uma matriz de rotação XY e encontrar a quantidade de z
rotação necessária para corresponder à matriz de destino. A RxRy
matriz fica assim:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Como sabemos que RxRy
* Rz
é igual à nossa matriz de entrada M
, podemos usar essa matriz para retornar a Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
O inverso de uma matriz de rotação é sua transposição , para que possamos expandir isso para:
┌ 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 ┘
Agora podemos resolver sinZ
e cosZ
realizar a multiplicação da matriz. Nós só precisamos calcular os elementos [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)
Aqui está uma implementação completa para referência:
#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;
}