Hay un gran comentario sobre este proceso por Mike Day:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Ahora también se implementa en glm, a partir de la versión 0.9.7.0, 08/02/2015. Echa un vistazo a la implementación .
Para comprender las matemáticas, debe mirar los valores que están en su matriz de rotación. Además, debe conocer el orden en el que se aplicaron las rotaciones para crear su matriz y extraer los valores correctamente.
Se forma una matriz de rotación desde los ángulos de Euler combinando rotaciones alrededor de los ejes x, y y z. Por ejemplo, la rotación de θ grados alrededor de Z se puede hacer con la matriz
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Existen matrices similares para rotar alrededor de los ejes 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 estas matrices para crear una matriz que sea el resultado de las tres rotaciones. Es importante tener en cuenta que el orden en que estas matrices se multiplican juntas es importante, porque la multiplicación de matrices no es conmutativa . Esto significa que Rx*Ry*Rz ≠ Rz*Ry*Rx
. Consideremos un posible orden de rotación, zyx. Cuando se combinan las tres matrices, se obtiene una matriz similar a esta:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
donde Cx
está el coseno del x
ángulo de rotación, Sx
es el seno del x
ángulo de rotación, etc.
Ahora, el reto consiste en extraer el original x
, y
y z
los valores que entraron en la matriz.
Primero saquemos el x
ángulo. Si conocemos el sin(x)
y cos(x)
, podemos usar la función de tangente inversa atan2
para devolvernos nuestro ángulo. Desafortunadamente, esos valores no aparecen por sí mismos en nuestra matriz. Pero, si miramos más de cerca los elementos M[1][2]
y M[2][2]
, podemos ver que sabemos -sin(x)*cos(y)
tan bien como cos(x)*cos(y)
. Dado que la función tangente es la razón de los lados opuestos y adyacentes de un triángulo, al escalar ambos valores en la misma cantidad (en este caso cos(y)
) se obtendrá el mismo resultado. Así,
x = atan2(-M[1][2], M[2][2])
Ahora intentemos llegar y
. Lo sabemos sin(y)
por M[0][2]
. Si tuviéramos cos (y), podríamos usarlo atan2
nuevamente, pero no tenemos ese valor en nuestra matriz. Sin embargo, debido a la identidad pitagórica , sabemos que:
cosY = sqrt(1 - M[0][2])
Entonces, podemos calcular y
:
y = atan2(M[0][2], cosY)
Por último, necesitamos calcular z
. Aquí es donde el enfoque de Mike Day difiere de la respuesta anterior. Como en este punto sabemos la cantidad x
y la y
rotación, podemos construir una matriz de rotación XY y encontrar la cantidad de z
rotación necesaria para que coincida con la matriz objetivo. La RxRy
matriz se ve así:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Como sabemos que RxRy
* Rz
es igual a nuestra matriz de entrada M
, podemos usar esta matriz para volver a Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
La inversa de una matriz de rotación es su transposición , por lo que podemos expandir esto 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 ┘
Ahora podemos resolver sinZ
y cosZ
realizar la multiplicación de matrices. Solo necesitamos calcular los elementos [1][0]
y [1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Aquí hay una implementación completa para referencia:
#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;
}