Mike Day在此过程中撰写了大量文章:https :
//d3cw3dd2w32x2b.cloudfront.net/wp-content/uploads/2012/07/euler-angles1.pdf
从0.9.7.0版本(2015年2月8日)起,现在也可以在glm中实现该功能。 检查实施。
要了解数学,您应该查看旋转矩阵中的值。另外,您必须知道应用旋转创建矩阵的顺序,以便正确提取值。
来自欧拉角的旋转矩阵是通过组合围绕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)
and 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
。这就是Mike Day的方法与先前答案不同的地方。由于在这一点上,我们知道的量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;
}