Использование кватернионов для вращений OpenGL

поэтому я пишу программу, в которой объекты перемещаются вокруг стиля spacesim, чтобы узнать, как плавно перемещать вещи через 3D-пространство. Немного повозившись с углами Эйлера, кажется, что они не подходят для 3D-движения в произвольных направлениях, поэтому я решил перейти к тому, что кажется лучшим для работы - кватернионам. Я намерен, чтобы объект постоянно вращался вокруг своих локальных осей X-Y-Z, а не вокруг глобальных осей X-Y-Z.

Я пробовал чтобы реализовать систему вращения с использованием кватернионов, но что-то не работает. При вращении объекта вдоль одной оси, Если предыдущие вращения не были предприняты, вещь вращается нормально вдоль данной оси. Однако при применении одного вращения за другим второе вращение не всегда происходит вдоль локальной оси, вдоль которой оно должно вращаться - например, после вращения около 90° вокруг оси Z вращение вокруг оси Y по-прежнему происходит вокруг оси Z глобальная ось Y, а не новая локальная ось Y, которая выровнена с глобальной осью X.

да. Давайте пройдем через это шаг за шагом. Ошибка должна быть где-то здесь.

Шаг 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);

как вы можете видеть, я не уверен, лучше ли просто построить новый кватернион из обновленных углов Эйлера, или должен ли я умножать кватернион, представляющий изменение, с кватернионом, представляющим общее текущее вращение, которое я получил при чтении данное руководство. Если последнее, мой код будет выглядеть так:

//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;
}
этот источник, что также предполагает, что уравнение автоматически создает единичный кватернион ("четко нормированный"). Для умножения кватернионов я снова рисовал на это руководство C++.

Шаг 3-Вывод a матрица вращения от кватерниона

как только это будет сделано, в соответствии с ответом Р. Мартиньо Фернандеса на этот вопрос, я пытаюсь построить матрицу вращения из кватерниона и использовать ее для обновления вращения моего объекта, используя приведенный выше код 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's VBO

glPopMatrix();

Я оставил свою предыдущую неудачу попытки есть, прокомментировал.

вывод - грустная панда

это завершение жизненного цикла ввода игрока, от колыбели до могилы, управляемой OpenGL.

Я, очевидно, что-то не понял, так как поведение, которое я получаю, не является поведением, которое я хочу или ожидаю. Но у меня нет особого опыта в матричных математиках или кватернионах, поэтому у меня нет понимания, необходимого, чтобы увидеть ошибку в моих путях.

может кто-нибудь помочь мне здесь?

2 ответов


все, что вы сделали эффективно реализовать углы Эйлера с кватернионами. Это не помогает.

проблема с углами Эйлера заключается в том, что при вычислении матриц каждый угол относительно вращения матрицы, которая была до него. То, что вы хотите, это взять текущий объект ориентация, и приложите вращение вдоль некоторой оси, производящ новую ориентацию.

вы не можете сделать это с углами Эйлера. Вы можете с матрицами, и вы можете с кватернионами (поскольку они являются только частью вращения матрицы). Но вы не можете сделать это, притворяясь, что это углы Эйлера.

Это сделано путем не хранить углы на все. Вместо этого у вас просто есть кватернион, который представляет текущую ориентацию объекта. Когда вы решаете применить к нему вращение (некоторого угла по некоторой оси), вы строите кватернион, который представляет это вращение на угол вокруг этой оси. Тогда вы правы-умножьте этот кватернион на текущая ориентация кватерниона, производящая новую текущую ориентацию.

при визуализации объекта используется текущая ориентация as... ориентация.


кватернионы представляют собой ориентации вокруг 3D составных осей. Но они также могут представлять "Дельта-вращения".

чтобы "повернуть ориентацию", нам нужна ориентация (quat) и вращение (также quat), и мы умножаем их вместе, в результате чего (вы догадались) quat.

вы заметили, что они не являются коммутативными, что означает порядок их умножения в абсолютных материях, как и для матриц. Порядок зависит от реализации вашей математики библиотека, но на самом деле, есть только два возможных способа сделать это, поэтому вам не потребуется слишком много времени, чтобы понять, какой из них правильный - если вещи "вращаются", а не "вращаются", то вы их неправильно.

для вашего примера рыскания и тангажа я бы построил свой кватернион "Дельта-вращения" из углов рыскания, тангажа и крена с нулевым значением крена, а затем применил бы это к моему кватерниону "ориентации", а не делал бы вращения по одной оси за раз.