Майк Дэй написал отличную статью об этом процессе:
https://d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
Это также теперь реализовано в glm, начиная с версии 0.9.7.0, 02.08.2015. Проверьте реализацию .
Чтобы понять математику, вы должны посмотреть на значения, которые находятся в вашей матрице вращения. Кроме того, вы должны знать порядок, в котором были применены повороты для создания матрицы, чтобы правильно извлечь значения.
Матрица вращения из углов Эйлера формируется путем объединения вращений вокруг осей x, y и z. Например, вращение θ градусов вокруг Z можно сделать с помощью матрицы
┌ cosθ -sinθ 0 ┐
Rz = │ sinθ cosθ 0 │
└ 0 0 1 ┘
Подобные матрицы существуют для вращения вокруг осей X и Y:
┌ 1 0 0 ┐
Rx = │ 0 cosθ -sinθ │
└ 0 sinθ cosθ ┘
┌ cosθ 0 sinθ ┐
Ry = │ 0 1 0 │
└ -sinθ 0 cosθ ┘
Мы можем умножить эти матрицы вместе, чтобы создать одну матрицу, которая является результатом всех трех вращений. Важно отметить, что порядок умножения этих матриц важен, поскольку умножение матриц не является коммутативным . Это значит что Rx*Ry*Rz ≠ Rz*Ry*Rx
. Давайте рассмотрим один из возможных порядков вращения, zyx. Когда три матрицы объединены, это приводит к матрице, которая выглядит следующим образом:
┌ CyCz -CySz Sy ┐
RxRyRz = │ SxSyCz + CxSz -SxSySz + CxCz -SxCy │
└ -CxSyCz + SxSz CxSySz + SxCz CxCy ┘
где Cx
косинус x
угла поворота, Sx
синус x
угла поворота и т. д.
Теперь задача состоит в том, чтобы извлечь оригинал x
, y
и z
значения , которые вошли в матрицу.
Давайте сначала выясним x
угол. Если мы знаем sin(x)
и cos(x)
, мы можем использовать обратную функцию тангенса, atan2
чтобы вернуть нам наш угол. К сожалению, эти значения не появляются сами по себе в нашей матрице. Но, если мы более внимательно посмотрим на элементы M[1][2]
и M[2][2]
, мы увидим, что мы знаем, -sin(x)*cos(y)
а также cos(x)*cos(y)
. Поскольку касательная функция представляет собой отношение противоположных и смежных сторон треугольника, масштабирование обоих значений на одну и ту же величину (в данном случае cos(y)
) даст одинаковый результат. Таким образом,
x = atan2(-M[1][2], M[2][2])
Теперь попробуем получить y
. Мы знаем sin(y)
из M[0][2]
. Если бы у нас было cos (y), мы могли бы использовать atan2
снова, но у нас нет этого значения в нашей матрице. Однако из-за пифагорейской идентичности мы знаем, что:
cosY = sqrt(1 - M[0][2])
Итак, мы можем рассчитать y
:
y = atan2(M[0][2], cosY)
Наконец, нам нужно рассчитать z
. В этом подход Майка Дея отличается от предыдущего ответа. Поскольку в этот момент мы знаем величину x
и y
вращение, мы можем построить матрицу вращения XY и найти величину z
вращения, необходимую для соответствия целевой матрице. RxRy
Матрица выглядит следующим образом :
┌ Cy 0 Sy ┐
RxRy = │ SxSy Cx -SxCy │
└ -CxSy Sx CxCy ┘
Поскольку мы знаем, что RxRy
* Rz
равен нашей входной матрице M
, мы можем использовать эту матрицу, чтобы вернуться к Rz
:
M = RxRy * Rz
inverse(RxRy) * M = Rz
Обратной матрицы вращения является транспонированной , поэтому мы можем расширить это:
┌ 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 ┘
Теперь мы можем решить для sinZ
и cosZ
путем выполнения умножения матриц. Нам нужно только рассчитать элементы [1][0]
и [1][1]
.
sinZ = cosX * M[1][0] + sinX * M[2][0]
cosZ = coxX * M[1][1] + sinX * M[2][1]
z = atan2(sinZ, cosZ)
Вот полная реализация для справки:
#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;
}