Итак, я пишу программу, где объекты перемещаются по пространству-стилю, чтобы научиться плавно перемещать вещи в 3D-пространстве. Немного поразмыслив с углами Эйлера, кажется, что они не подходят для движения в свободной форме в произвольных направлениях, поэтому я решил перейти к тому, что, по-видимому, лучше всего подходит для работы - кватернионов. Я намерен, чтобы объект вращался вокруг своих локальных осей X-Y-Z во все времена, никогда вокруг глобальных осей X-Y-Z.
Я попытался внедрить систему вращения с использованием кватернионов, но что-то не работает. При вращении объекта вдоль одной оси, если никаких предыдущих поворотов не предпринималось, предмет вращается тонко вдоль данной оси. Однако при применении одного вращения за другим второе вращение не всегда вдоль локальной оси, которую оно должно вращать вдоль - например, после поворота около 90 ° вокруг оси Z поворот вокруг оси Y все еще происходит вокруг глобальной оси Y, а не новой локальной оси Y, которая выровнена с глобальной осью X.
Да. Так что пусть это пройдет шаг за шагом. Ошибка должна быть где-то здесь.
ШАГ 1 - Ввод записи
Я подумал, что лучше использовать углы Эйлера (или схему Pitch-Yaw-Roll) для захвата входа игрока. На данный момент клавиши со стрелками управляют Pitch и Yaw, тогда как Q и E управляют Roll. Таким образом, я захватываю вход игрока (я использую 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);
Как вы можете видеть, я не уверен, что лучше всего построить новый кватернион из обновленных углов Эйлера, или я должен умножить кватернион, представляющий изменение, с кватернионом, представляющим общее текущее вращение, которое это впечатление, которое я получил при чтении этого руководства. Если последний, мой код будет выглядеть так:
//Multiply the new Quaternion by the current one.
m_qRotation = Quaternion(Rotation.x, Rotation.y, Rotation.z) * m_qRotation;
m_Rotation - текущее вращение объекта, хранящееся в формате PYR; Вращение - это изменение, требуемое входным сигналом игрока. В любом случае проблема может быть в моей реализации моего класса Quaternion. Вот и все:
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;
}
Вероятно, что-то там, чтобы заставить кого-то мудрее меня съесть, но я не вижу этого. Для преобразования из углов Эйлера в кватернион я использовал "первый метод" в соответствии с этот источник, что также, по-видимому, предполагает, что уравнение автоматически создает единичный кватернион ( "четко нормированный" ). Для умножения кватернионов я снова обратился к этому руководству С++.
ШАГ 3 - Получение матрицы вращения из кватерниона
Как только это будет сделано, как ответ Р. Мартиньо Фернандеса на этот вопрос, я пытаюсь построить матрицу вращения из кватерниона и использовать ее для обновления моего вращение объекта, используя вышеуказанный код Quaternion:: RotationMatrix() в следующей строке:
m_qRotation.RotationMatrix(m_RotationMatrix);
Следует отметить, что m_RotationMatrix GLfloat m_RotationMatrix[16]
, согласно требуемые параметры glMultMatrix, которые, я считаю, я должен использовать позже при отображении объекта. Он инициализируется как:
m_RotationMatrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
Я считаю, что это "нейтральная" матрица вращения OpenGL (каждые 4 значения вместе представляют столбец, правильно?) Я снова получаю это от страницы glMultMatrix).
ШАГ 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 VBO
glPopMatrix();
Я оставил свои предыдущие неудачные попытки, прокомментировал.
Заключение - Sad panda
Это завершение жизненного цикла входа игрока, от колыбели до могилы, управляемой OpenGL.
Я, очевидно, ничего не понял, поскольку поведение, которое я получаю, - это не то поведение, которое я хочу или ожидаю. Но я не особенно разбираюсь в матричной математике или кватернионах, поэтому у меня нет понимания, чтобы увидеть ошибку в моих путях.
Может кто-нибудь помочь мне здесь?