因此,我正在编写一个程序,其中对象以 spacesim 方式移动,以便学习如何在 3D 空间中平滑地移动对象。在对欧拉角进行了一番研究之后,它们似乎并不真正适合任意方向上的自由形式 3D 运动,因此我决定继续使用似乎最适合这项工作的方法 - 四元数。我打算让对象始终围绕其局部 X-Y-Z 轴旋转,而不是围绕全局 X-Y-Z 轴旋转。
我尝试使用四元数实现旋转系统,但有些东西不起作用。当沿单个轴旋转物体时,如果之前没有进行任何旋转,物体将沿给定轴精细旋转。然而,当执行一次又一次的旋转时,第二次旋转并不总是沿着它应该旋转的局部轴 - 例如,在绕 Z 轴旋转约 90° 后,绕 Y 轴旋转仍然围绕全局 Y 轴发生,而不是与全局 X 轴对齐的新局部 Y 轴。
呵呵。那么让我们一步一步来看看。错误一定就在这里的某个地方。
第 1 步 - 捕获输入
我认为最好使用欧拉角(或俯仰-偏航-滚动方案)来捕获玩家输入。目前,方向键控制俯仰和偏航,而 Q 和 E 控制横滚。我因此捕获玩家输入(我使用 SFML 1.6):
///SPEEDS
float ForwardSpeed = 0.05;
float TurnSpeed = 0.5;
//Rotation
sf::Vector3<float> Rotation;
Rotation.x = 0;
Rotation.y = 0;
Rotation.z = 0;
//PITCH
if (m_pApp->GetInput().IsKeyDown(sf::Key::Up) == true)
{
Rotation.x -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Down) == true)
{
Rotation.x += TurnSpeed;
}
//YAW
if (m_pApp->GetInput().IsKeyDown(sf::Key::Left) == true)
{
Rotation.y -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::Right) == true)
{
Rotation.y += TurnSpeed;
}
//ROLL
if (m_pApp->GetInput().IsKeyDown(sf::Key::Q) == true)
{
Rotation.z -= TurnSpeed;
}
if (m_pApp->GetInput().IsKeyDown(sf::Key::E) == true)
{
Rotation.z += TurnSpeed;
}
//Translation
sf::Vector3<float> Translation;
Translation.x = 0;
Translation.y = 0;
Translation.z = 0;
//Move the entity
if (Rotation.x != 0 ||
Rotation.y != 0 ||
Rotation.z != 0)
{
m_Entity->ApplyForce(Translation, Rotation);
}
m_Entity 是我想要旋转的东西。它还包含表示对象旋转的四元数和旋转矩阵。
第 2 步 - 更新四元数
我不是 100% 确定这是应该完成的方式,但这是我在 Entity::ApplyForce() 中尝试做的事情:
//Rotation
m_Rotation.x += Rotation.x;
m_Rotation.y += Rotation.y;
m_Rotation.z += Rotation.z;
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(m_Rotation.x, m_Rotation.y, m_Rotation.z);// * m_qRotation;
m_qRotation.RotationMatrix(m_RotationMatrix);
正如你所看到的,我不确定是否最好从更新的欧拉角构建一个新的四元数,或者我是否应该将代表变化的四元数与代表当前整体旋转的四元数相乘,这是印象我读书时得到本指南 http://www.cprogramming.com/tutorial/3d/quaternions.html。如果是后者,我的代码将如下所示:
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;
m_Rotation是以PYR格式存储的对象的当前旋转;轮换是玩家输入所要求的改变。但无论如何,问题可能出在我的四元数类的实现上。整个事情是这样的:
Quaternion::Quaternion(float Pitch, float Yaw, float Roll)
{
float Pi = 4 * atan(1);
//Set the values, which came in degrees, to radians for C++ trig functions
float rYaw = Yaw * Pi / 180;
float rPitch = Pitch * Pi / 180;
float rRoll = Roll * Pi / 180;
//Components
float C1 = cos(rYaw / 2);
float C2 = cos(rPitch / 2);
float C3 = cos(rRoll / 2);
float S1 = sin(rYaw / 2);
float S2 = sin(rPitch / 2);
float S3 = sin(rRoll / 2);
//Create the final values
a = ((C1 * C2 * C3) - (S1 * S2 * S3));
x = (S1 * S2 * C3) + (C1 * C2 * S3);
y = (S1 * C2 * C3) + (C1 * S2 * S3);
z = (C1 * S2 * C3) - (S1 * C2 * S3);
}
//Overload the multiplier operator
Quaternion Quaternion::operator* (Quaternion OtherQuat)
{
float A = (OtherQuat.a * a) - (OtherQuat.x * x) - (OtherQuat.y * y) - (OtherQuat.z * z);
float X = (OtherQuat.a * x) + (OtherQuat.x * a) + (OtherQuat.y * z) - (OtherQuat.z * y);
float Y = (OtherQuat.a * y) - (OtherQuat.x * z) - (OtherQuat.y * a) - (OtherQuat.z * x);
float Z = (OtherQuat.a * z) - (OtherQuat.x * y) - (OtherQuat.y * x) - (OtherQuat.z * a);
Quaternion NewQuat = Quaternion(0, 0, 0);
NewQuat.a = A;
NewQuat.x = X;
NewQuat.y = Y;
NewQuat.z = Z;
return NewQuat;
}
//Calculates a rotation matrix and fills Matrix with it
void Quaternion::RotationMatrix(GLfloat* Matrix)
{
//Column 1
Matrix[0] = (a*a) + (x*x) - (y*y) - (z*z);
Matrix[1] = (2*x*y) + (2*a*z);
Matrix[2] = (2*x*z) - (2*a*y);
Matrix[3] = 0;
//Column 2
Matrix[4] = (2*x*y) - (2*a*z);
Matrix[5] = (a*a) - (x*x) + (y*y) - (z*z);
Matrix[6] = (2*y*z) + (2*a*x);
Matrix[7] = 0;
//Column 3
Matrix[8] = (2*x*z) + (2*a*y);
Matrix[9] = (2*y*z) - (2*a*x);
Matrix[10] = (a*a) - (x*x) - (y*y) + (z*z);
Matrix[11] = 0;
//Column 4
Matrix[12] = 0;
Matrix[13] = 0;
Matrix[14] = 0;
Matrix[15] = 1;
}
这里面可能有一些东西会让比我聪明的人感到畏缩,但我看不到。为了从欧拉角转换为四元数,我使用了“第一种方法”这个来源 http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm,这似乎也表明该方程自动创建一个单位四元数(“明显标准化”)。为了乘以四元数,我再次利用这个 C++ 指南 http://www.cprogramming.com/tutorial/3d/quaternions.html.
第 3 步 - 从四元数导出旋转矩阵
一旦完成,按照 R. Martinho Fernandes 的回答这个问题 https://stackoverflow.com/questions/7938373/from-quaternions-to-opengl-rotations,我尝试从四元数构建一个旋转矩阵,并使用它来更新对象的旋转,在下面的行中使用上面的 Quaternion::Rotation Matrix() 代码:
m_qRotation.RotationMatrix(m_RotationMatrix);
我应该注意到 m_RotationMatrix 是GLfloat m_RotationMatrix[16]
, 按照glMultMatrix 所需的参数 http://www.opengl.org/sdk/docs/man/xhtml/glMultMatrix.xml,我相信稍后在显示对象时应该使用它。它被初始化为:
m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
我相信这是“中性”OpenGL 旋转矩阵(每 4 个值一起代表一列,对吗?同样,我从glMultMatrix 页面 http://www.opengl.org/sdk/docs/man/xhtml/glMultMatrix.xml).
第 4 步 - 显示!
最后,我们得到了为应该显示它的对象每个周期运行的函数。
glPushMatrix();
glTranslatef(m_Position.x, m_Position.y, m_Position.z);
glMultMatrixf(m_RotationMatrix);
//glRotatef(m_Rotation.y, 0.0, 1.0, 0.0);
//glRotatef(m_Rotation.z, 0.0, 0.0, 1.0);
//glRotatef(m_Rotation.x, 1.0, 0.0, 0.0);
//glRotatef(m_qRotation.a, m_qRotation.x, m_qRotation.y, m_qRotation.z);
//[...] various code displaying the object's VBO
glPopMatrix();
我把之前失败的尝试留在那里,并注释掉。
结论 - 悲伤的熊猫
这就是玩家输入生命周期的结束,从摇篮到 OpenGL 管理的坟墓。
我显然不明白一些事情,因为我得到的行为不是我想要或期望的行为。但我对矩阵数学或四元数并不是特别有经验,所以我没有足够的洞察力来发现我的方法中的错误。
有人可以帮我吗?