0
votes

I am having a problem implementing quaternions for my camera. Im creating a 3D space shooter in OpenGL and I need to avoid gimbal lock. If I rotate in only one axis its fine but once I apply both the pitch and yaw they have a weird behavior. My rotation angle is using the input of my mouse.

Here I calculate my ViewMatrix and its updated every frame

Matrix4x4 camera::GetViewMat()
{
    m_rotation.AddX((oInput.MousePosition.GetY() - oInput.PrevMousePosition.GetY()) * m_sensitivity * dt);
    m_rotation.AddY((oInput.MousePosition.GetX() - oInput.PrevMousePosition.GetX()) * -m_sensitivity * dt);

    Matrix4x4 oTranslateOrigin, oRotate, oView;
    oView.SetIdentity();

    //constructor creates a quaternion from an AxisAngle, the constructor will be shown below
    Quaternions pitch = Quaternions(m_rotation.GetX() * Utility::DegToRad(), Vector3(1, 0, 0));
    Quaternions yaw = Quaternions(m_rotation.GetY() * Utility::DegToRad(), Vector3(0, 1, 0));

    //update orientation with the new quaternion times its current orientation
    ori = ori * yaw * pitch;

    //convert quaternion to matrix, also shown below
    oRotate = ori.ToMatrix();
    oTranslateOrigin.BuildTranslate(-m_camPosition.GetX(), -m_camPosition.GetY(), -m_camPosition.GetZ());

    oView = oRotate * oTranslateOrigin;

    return oView;
}

Initialize ProjectionMatrix

Matrix4x4 camera::GetProjMat(float fFieldOfViewY, float fAspectRatio, float fNearZ, float fFarZ)
{
    // Transposed version of D3DXMatrixPerspectiveFovRH
    float fYScale = 1 / tanf( fFieldOfViewY / 2.0f );
    float fXScale = fYScale / fAspectRatio;
    memset( &m_projMat, 0, sizeof( Matrix4x4 ) );
    m_projMat.Set(0, 0, fXScale);
    m_projMat.Set(1, 1, fYScale);
    m_projMat.Set(2, 2, fFarZ / ( fNearZ - fFarZ ));
    m_projMat.Set(2, 3, ( fNearZ * fFarZ ) / ( fNearZ - fFarZ ));
    m_projMat.Set(3, 2, -1.0f);
    return m_projMat;
   }

this is one of the contructors that creates a quaternion to AxisAngle

Quaternions::Quaternions(float angle, Vector3& axis)
{
    FromAxisAngle(angle, axis);
}

void Quaternions::FromAxisAngle(float angle, Vector3& axis)
{
    float halfAngle = angle * ((float)PI/360.0f);
    float sin = sinf(halfAngle);

    this->w = cosf(halfAngle);
    this->x = axis.GetX() * sin;
    this->y = axis.GetY() * sin;
    this->z = axis.GetZ() * sin;
}

Matrix4x4 Quaternions::ToMatrix()
{
    Normalize();
    Matrix4x4 mat;
    mat.SetIdentity();

    mat.Set(0, 0, 1.0f - 2*(this->y * this->y) - 2*(this->z * this->z));
    mat.Set(0, 1, 2*(this->x*this->y) - 2*(this->w*this->z));
    mat.Set(0, 2, 2*(this->x * this->z) + 2*(this->w * this->y));

    mat.Set(1, 0, 2*(this->x * this->y) + 2*(this->w * this->z));
    mat.Set(1, 1, 1.0f - 2*(this->x * this->x) - 2*(this->z * this->z));
    mat.Set(1, 2, 2*(this->y * this->z) - 2*(this->w * this->x));

    mat.Set(2, 0, 2*(this->x * this->z) - 2*(this->w * this->y));
    mat.Set(2, 1, 2*(this->y * this->z) + 2*(this->w * this->x));
    mat.Set(2, 2, 1.0f - 2*(this->x * this->x) - 2*(this->y * this->y));

    return mat;
}

and that is about it that I am doing.

Can you guys lead me in the right direction?

2
Please don't think that using quaternions means you will not see gimbal lock. Gimbal lock is a result of the use of euler angles (pitch, yaw, roll). It makes no difference if use euler angle quaternions or euler angle matrices, the result will be exactly the same.Tim

2 Answers

0
votes

Is it because this line:

ori = ori * yaw * pitch;

Is in the wrong order?

You can always rotate the camera matrix with openGL functions. Just make sure you have the camer matrix on the stack.

glRotate(angle, 1, 0, 0);
glRotate(angle, 0, 1, 0);

You can look at a point with this function:

void gluLookAt( GLdouble eyeX , GLdouble eyeY , GLdouble eyeZ , 
                GLdouble centerX , GLdouble centerY , GLdouble centerZ , 
                GLdouble upX , GLdouble upY , GLdouble upZ );

Where you need the eye position, target (center) position, and the up-vector of the camera.

0
votes

Ok this is what you want to do to avoid gimble lock with quaternions. Plus ive included some Opengl specific code to hopeful head you in the right direction. Ok first things first I'm going to map keys to rotations.

W - Pitch up(x rot)
S - Pitch down(x rot)
A - Yaw left(y rot)
D - Yaw Right(y rot)
Q - Roll left(z rot)
E - Roll right(z rot)

All you want to is when these are pressed call the correct rotation. So for example the user presses W all we need to do is create a quaternion for with a rotation around the x with 1degrees. Do that using your quaternion class:

a = EulerToQuat(angle,0,0);
cameraOri = cameraOri * a;

Obviously do the same for the other controls! Ok final step you need to do is using the rotate commands in Opengl to rotate the world correctly. The way we do this which is nice and easy to read is we convert our quaternion to a angle and axis.(Hopefully your quaternion class can do this!) And then proceed to do a glRotate using this!

m_frustumCamera->getQuat().GetAxisAngle(axis,angle);
angle = (float)(angle * radToDeg);  
glRotatef(angle,axis.x,axis.y,axis.z);  
glTranslatef(-1*m_frustumCamera->getPos().x,-1*m_frustumCamera->getPos().y,-1*m_frustumCamera->getPos().z);

If you dont have a working quaternion class I recommend: http://www.wolframalpha.com/ to make sure all your functions give the correct response!