Mike Day ma świetny opis tego procesu:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Jest również teraz zaimplementowany w glm, począwszy od wersji 0.9.7.0, 02/08/2015. Sprawdź wdrożenie .
Aby zrozumieć matematykę, powinieneś przyjrzeć się wartościom, które znajdują się w macierzy rotacji. Ponadto musisz znać kolejność stosowania rotacji, aby utworzyć matrycę w celu prawidłowego wyodrębnienia wartości.
Macierz obrotu z kątów Eulera powstaje przez połączenie obrotów wokół osi x, y i z. Na przykład obracanie o θ stopni wokół Z można wykonać za pomocą macierzy
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Istnieją podobne macierze do obracania się wokół osi X i Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Możemy pomnożyć te macierze razem, aby utworzyć jedną macierz, która jest wynikiem wszystkich trzech rotacji. Należy zauważyć, że kolejność mnożenia tych macierzy jest ważna, ponieważ mnożenie macierzy nie jest przemienne . To znaczy że Rx*Ry*Rz ≠ Rz*Ry*Rx
. Rozważmy jeden możliwy porządek obrotu, zyx. Po połączeniu trzech macierzy powstaje macierz, która wygląda następująco:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
gdzie Cx
jest cosinusx
kąta obrotu, Sx
sinus x
kąta obrotu itp.
Teraz wyzwaniem jest wyodrębnienie oryginału x
, y
oraz z
wartości, które poszedł do matrycy.
Najpierw ustalmy x
kąt. Jeśli znamy sin(x)
i cos(x)
, możemy użyć funkcji odwrotnej stycznej, atan2
aby przywrócić nam kąt. Niestety wartości te nie pojawiają się same w naszej matrycy. Ale jeśli przyjrzymy się bliżej elementom M[1][2]
i M[2][2]
zobaczymy, że wiemy -sin(x)*cos(y)
równie dobrze cos(x)*cos(y)
. Ponieważ funkcja styczna jest stosunkiem przeciwnych i sąsiednich boków trójkąta, skalowanie obu wartości o tę samą wartość (w tym przypadku cos(y)
) da ten sam wynik. A zatem,
x = atan2(-M[1][2], M[2][2])
Teraz spróbujmy zdobyć y
. Wiemy sin(y)
z M[0][2]
. Gdybyśmy mieli cos (y), moglibyśmy użyć atan2
ponownie, ale nie mamy tej wartości w naszej macierzy. Jednak ze względu na tożsamość pitagorejską wiemy, że:
cosY = sqrt(1 - M[0][2])
Możemy więc obliczyć y
:
y = atan2(M[0][2], cosY)
Na koniec musimy obliczyć z
. Tutaj podejście Mike'a Day różni się od poprzedniej odpowiedzi. Ponieważ w tym momencie znamy wielkość x
i y
obrót, możemy zbudować macierz obrotu XY i znaleźć wielkość z
obrotu niezbędną do dopasowania do macierzy docelowej. RxRy
Matryca wygląda tak:
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Ponieważ wiemy, że RxRy
* Rz
jest równe naszej macierzy wejściowej M
, możemy użyć tej macierzy, aby wrócić doRz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
Odwrotność macierzy rotacji jest jego transpozycji , dzięki czemu mogą się rozszerzyć do tego:
┌ 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 ┘
Możemy teraz rozwiązać dla sinZ
i cosZ
przez mnożenie macierzy. Musimy tylko obliczyć elementy [1][0]
i[1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Oto pełna implementacja w celach informacyjnych:
#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;
}