How to control a 3D camera using a single quaternion representation for its orientation?

2.4k views Asked by At

I am creating a 3D camera for my game engine and I want to represent the camera by a single quaternion for its orientation. The local up,front and side camera axis are generated by converting this quaternion into a rotation matrix. The first row of this matrix gives me the camera's X axis(side vector), the 2nd row gives me the UP vector and the 3rd row gives me the FRONT vector. To move the camera my function takes in the direction to move in and the amount and the position is updated and the view matrix() is updated. To rotate the camera, my function gets the axis to rotate around and the angle. A temporary quaternion is created from this axis and angle and is applied to my camera's current orientation. I rotate about the world Y axis for yaw and about the camera's side vector to pitch. This pitching doesn't work properly when my camera's axis are not aligned with world axis. If I instead pitch about global X axis then it works fine but I know that's not what I want. Also when my camera's axis are not aligned with world axis the movement also screws up. I am using the basic "wasd" movement. My movement and pitch functions use the local axis and hence I have a hunch that the way I extract the local axis from the quaternion is not correct but I am not able to figure out what is going wrong.

Camera class:

#include "stdafx.h"

Camera * Camera::m_currentCamera = NULL;
    Camera::Camera(vec3 position , double distToNear , double distToFar , double fovy)
{
    m_position = position;
    m_distToNear = distToNear;
    m_distToFar = distToFar;
    m_fovy = fovy;
    // generate the view and projection matrices
    updateViewMatrix();
    projection = glm::perspective(m_fovy, 4.0/3.0, m_distToNear, m_distToFar);
    // in radians
    m_deltaAngle = 10.0 * PIOVER180;

    if(m_currentCamera != NULL)
    {
        delete m_currentCamera;
    }

    m_currentCamera = this;
}

Camera::Camera()
{

}

Camera::~Camera()
{
    // empty for now
}

Camera* Camera::getCurrentCamera()
{
    return m_currentCamera;
}

/** set and get camera position */
vec3 Camera::getPosition()
{
    return m_position;
}

void Camera::setPosition(vec3 pos)
{
    m_position = pos;
}


/** set and get near plane distance */
double Camera::getNearPlaneDist()
{
    return m_distToNear;
}

void Camera::setNearPlaneDist(double dist)
{
    m_distToNear = dist;
}

/** set and get far plane distance */
double Camera::getFarPlaneDist()
{
    return m_distToFar;
}

void Camera::setFarPlaneDist(double dist)
{
    m_distToFar = dist;
}

/** set and get camera field of view */
double Camera::getFovy()
{
    return m_fovy;
}

void Camera::setFovy(double angle)
{
    m_fovy = angle;
}

mat4 Camera::getViewMatrix()
{
    return view;
}

mat4 Camera::getProjectionMatrix()
{
    return projection;
}

Quaternion Camera::getOrientation()
{
    return m_rotation;
}

void Camera::setOrientation(Quaternion *q)
{
    m_rotation.setQuaternion(q);
}

/** this function will be called whenever the camera's position, up vector or lookat vector changes */
void Camera::updateViewMatrix()
{
    mat4 orientation = m_rotation.toMatrix();
    mat4 translation = translate(mat4(1.0f),vec3(-m_position.x,-m_position.y,-m_position.z));
    view = orientation * translation;
}

/** Control Functions */

void Camera::rollCamera(float theta)
{
    Quaternion tempRotation;
    tempRotation.quaternionFromAxis(getFront(), theta);
    tempRotation.normalise();
    m_rotation = m_rotation * &tempRotation;
    m_rotation.normalise();
    updateViewMatrix();
}

void Camera::pitchCamera(float theta)
{
    Quaternion tempRotation;
    // INSTEAD OF LOCAL AXIS IF (1.0,0.0,0.0) GLOBAL X AXIS IS USED THIS WORKS FINE!!!   
    tempRotation.quaternionFromAxis(getSide(), theta);
    tempRotation.normalise();
    m_rotation = m_rotation * &tempRotation;
    m_rotation.normalise();
    updateViewMatrix();
}

void Camera::yawCamera(float theta)
{
    Quaternion tempRotation;
    tempRotation.quaternionFromAxis(vec3(0.0,1.0,0.0), theta);
    tempRotation.normalise();
    m_rotation = m_rotation * &tempRotation;
    m_rotation.normalise();
    updateViewMatrix();
}

void Camera::moveCamera(vec3 dir, float amt)
{
    normalize(dir);
    m_position += dir * amt;
    updateViewMatrix();
}

vec3 Camera::getFront()
{
    m_rotation.normalise();
    mat4 orientation = m_rotation.toMatrix();
    vec3 front(orientation[2][0], orientation[2][1], orientation[2][2]);
    normalize(front);
    cout << "FRONT " << front.x << " " << front.y << " " << front.z << endl;
    return front;
}

vec3 Camera::getSide()
{
    m_rotation.normalise();
    mat4 orientation = m_rotation.toMatrix();
    vec3 side(orientation[0][0], orientation[0][1], orientation[0][2]);
    normalize(side);
    cout << "SIDE " << side.x << " " << side.y << " " << side.z << endl;
    return side;
}

vec3 Camera::getUp()
{
    m_rotation.normalise();
    mat4 orientation = m_rotation.toMatrix();
    vec3 up(orientation[1][0], orientation[1][1], orientation[1][2]);
    normalize(up);
    cout << "UP " << up.x << " " << up.y << " " << up.z << endl;
    return up;
}

Function's to move and rotate the camera:

void specialKeyboard(int key, int x, int y)
{
    switch(key)
    {
    case GLUT_KEY_LEFT: 
        Camera::getCurrentCamera()->yawCamera(1.0 * PIOVER180);
        break;

    case GLUT_KEY_RIGHT:
        Camera::getCurrentCamera()->yawCamera(-1.0 * PIOVER180);
        break;

    case GLUT_KEY_UP:
        Camera::getCurrentCamera()->pitchCamera(1.0 * PIOVER180);
        break;
    case GLUT_KEY_DOWN:
        Camera::getCurrentCamera()->pitchCamera(-1.0 * PIOVER180);
        break;
    }

    glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
{
    switch(key)
    {
    case 'w':
        Camera::getCurrentCamera()->moveCamera(Camera::getCurrentCamera()->getFront(), -0.1f);
        break;
    case 's':
        Camera::getCurrentCamera()->moveCamera(Camera::getCurrentCamera()->getFront(), 0.1f);
        break;
    case 'a':
        Camera::getCurrentCamera()->moveCamera(Camera::getCurrentCamera()->getSide(), -0.1f);
        break;
    case 'd':
        Camera::getCurrentCamera()->moveCamera(Camera::getCurrentCamera()->getSide(), 0.1f);
        break;
    }
    glutPostRedisplay();
}

This is my first post on stack overflow so please let me know if any more information is needed. Please help if you find any errors in my code and approach. Thanks!

0

There are 0 answers