Search code examples
c++quaternionsglm-math

How to set the pitch, yaw, roll of a quaternion


How to set the pitch, yaw, roll of a quaternion

What I need help with:

Hello! I have a quaternion that stores the orientation of my camera and what I would like to do is set the pitch, yaw and roll of the quaternion relative to the world.


Picture this...

What I mean my that is imagine 4 walls around you with an arrow on each wall pointing to the world up. What I want to do is no matter what the pitch, yaw and roll of the quaternion, if I'm looking at any of the arrows (any yaw) but with a little roll (so the arrow will point anywhere but up). If I use the setRoll function and pass 0*, the camera should see the arrow pointing up. If I pass 90* to the setRoll function, no matter how many times I call the function it should set the camera's rotation to 90* so the arrow will point to the left.


My code:

Here is my full camera class below. The only code that really matters are the setPitch, setYaw and setRoll functions.

Camera.hpp

#ifndef CAMERA_HPP
#define CAMERA_HPP

// GLAD
#include <glad/glad.h>
// GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/quaternion.hpp>

class QuaternionCamera
{
public:
    QuaternionCamera(glm::vec3 position, float zNear, float zFar);
    QuaternionCamera(glm::vec3 position, glm::quat orientation, float zNear, float zFar);
    QuaternionCamera(glm::vec3 position, glm::vec3 orientation, float zNear, float zFar);

    // Set the position in world coords
    void setPosition(glm::vec3 position);
    // Set the orientation relative to the world
    void setOrientation(glm::quat orientation);
    // Set the orientation relative to the world
    void setOrientation(glm::vec3 orientation);
    // Set the pitch relative to the world
    void setPitch(float amount);
    // Set the yaw relative to the world
    void setYaw(float amount);
    // Set the roll relative to the world
    void setRoll(float amount);

    // Set the camera's near plane
    void setZNear(float zNear);
    // Set the camera's far plane
    void setZFar(float zFar);

    // Move the camera relative to it's current location
    void move(glm::vec3 movement);
    // Move the camera relative to it's current location on the world axis
    void moveAxis(glm::vec3 translation);

    // Rotate the camera relative to it's current location
    void rotate(glm::quat rotation);
    // Rotate the camera relative to it's current location
    void rotate(glm::vec3 rotation);
    // Pitch the camera relative to it's current location
    void pitch(float amount);
    // Yaw the camera relative to it's current location
    void yaw(float amount);
    // Roll the camera relative to it's current location
    void roll(float amount);

    // Get the camera's position in world coords
    glm::vec3 getPosition() const;
    // Get the camera's direction relative to the world
    glm::vec3 getDirection() const;
    // Get the camera's right vector
    glm::vec3 getRight() const;
    // Get the camera's up vector
    glm::vec3 getUp() const;
    // Get the camera's rotation relative to the world
    glm::quat getOrientation() const;

    // Get the camera's near plane
    float getZNear() const;
    // Get the camera's far plane
    float getZFar() const;

    // Get the camera's view matrix
    glm::mat4 getViewMatrix() const;
    // Get the camera's projection matrix
    glm::mat4 getProjectionMatrix() const;
    // Get the camera's view projection matrix
    glm::mat4 getViewProjectionMatrix() const;

protected:
    // updateProjectionMatrix is virtual because it will be defined in the two derived camera classes.
    // This is because the camera class does not know if we want a perspective or orthographic viewport.
    // So because the viewport is defined in a derived class, and because we change the projection matrix
    // in the base camera class (zNear and zFar), we need a way to get an updated projection matrix!
    virtual void updateProjectionMatrix() = 0;
    void updateViewProjectionMatrix();

    float zNear;
    float zFar;

    glm::mat4 projectionMatrix;
    glm::mat4 viewProjectionMatrix;

private:
    void updateCameraVectors();
    void updateViewMatrix();
    glm::mat4 viewMatrix;

    glm::vec3 position;
    glm::quat orientation;
    glm::vec3 direction; // Camera Direction / Camera View Facing
    glm::vec3 right;
    glm::vec3 up;
};

#endif

Camera.cpp

QuaternionCamera::QuaternionCamera(glm::vec3 position, float zNear, float zFar)
{
    this->position = position;
    this->orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // glm::quat constructor is (w, x, y, z) but it is stored as (x, y, z, w)

    right     = glm::vec3( 1,  0,  0);
    up        = glm::vec3( 0,  1,  0);
    direction = glm::vec3( 0,  0, -1);

    this->zNear = zNear;
    this->zFar = zFar;

    updateViewMatrix();
    updateViewProjectionMatrix();
}

QuaternionCamera::QuaternionCamera(glm::vec3 position, glm::quat orientation, float zNear, float zFar)
{
    this->position = position;
    this->orientation = orientation;

    right     = glm::vec3( 1,  0,  0);
    up        = glm::vec3( 0,  1,  0);
    direction = glm::vec3( 0,  0, -1);

    this->zNear = zNear;
    this->zFar = zFar;

    updateViewMatrix();
    updateViewProjectionMatrix();
}

QuaternionCamera::QuaternionCamera(glm::vec3 position, glm::vec3 orientation, float zNear, float zFar)
{
    this->position = position;
    this->orientation = glm::quat(glm::vec3(glm::radians(orientation.x), glm::radians(orientation.y), glm::radians(orientation.z)));

    right     = glm::vec3( 1,  0,  0);
    up        = glm::vec3( 0,  1,  0);
    direction = glm::vec3( 0,  0, -1);

    this->zNear = zNear;
    this->zFar = zFar;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setPosition(glm::vec3 position)
{
    this->position = position;
}

void QuaternionCamera::setOrientation(glm::quat orientation)
{
    this->orientation = orientation;
}

void QuaternionCamera::setOrientation(glm::vec3 orientation)
{
    glm::quat orientationQuat = glm::quat(orientation);
    this->orientation = orientationQuat;
}

void QuaternionCamera::setPitch(float amount)
{
    // TODO:

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setYaw(float amount)
{
    // TODO:

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setRoll(float amount)
{
    // TODO:

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::setZNear(float zNear)
{
    this->zNear = zNear;

    updateProjectionMatrix();
}

void QuaternionCamera::setZFar(float zFar)
{
    this->zFar = zFar;

    updateProjectionMatrix();
}

void QuaternionCamera::move(glm::vec3 movement)
{
    position += (orientation * glm::vec3(1, 0, 0)) * movement.x + (orientation * glm::vec3(0, 1, 0)) * movement.y + (orientation * glm::vec3(0, 0, -1)) * movement.z;
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::moveAxis(glm::vec3 translation)
{
    position += translation;
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::rotate(glm::quat rotation)
{
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::rotate(glm::vec3 rotation)
{
    glm::quat rotationQuat = glm::quat(rotation);
    orientation *= rotationQuat;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::pitch(float amount)
{
    glm::quat rotation = glm::angleAxis(glm::radians(amount), glm::vec3(1, 0, 0));
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::yaw(float amount)
{
    glm::quat rotation = glm::angleAxis(glm::radians(-amount), glm::vec3(0, 1, 0));
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

void QuaternionCamera::roll(float amount)
{
    glm::quat rotation = glm::angleAxis(glm::radians(amount), glm::vec3(0, 0, -1));
    orientation *= rotation;

    updateCameraVectors();
    updateViewMatrix();
    updateViewProjectionMatrix();
}

glm::vec3 QuaternionCamera::getPosition() const
{
    return position;
}

glm::vec3 QuaternionCamera::getDirection() const
{
    return direction;
}

glm::vec3 QuaternionCamera::getRight() const
{
    return right;
}

glm::vec3 QuaternionCamera::getUp() const
{
    return up;
}

glm::quat QuaternionCamera::getOrientation() const
{
    return orientation;
}

float QuaternionCamera::getZNear() const
{
    return zNear;
}

float QuaternionCamera::getZFar() const
{
    return zFar;
}

glm::mat4 QuaternionCamera::getViewMatrix() const
{
    return viewMatrix;
}

glm::mat4 QuaternionCamera::getProjectionMatrix() const
{
    return projectionMatrix;
}

glm::mat4 QuaternionCamera::getViewProjectionMatrix() const
{
    return viewProjectionMatrix;
}

void QuaternionCamera::updateViewProjectionMatrix()
{
    viewProjectionMatrix = getProjectionMatrix() * getViewMatrix();
}

void QuaternionCamera::updateCameraVectors()
{
    right = glm::normalize(orientation * glm::vec3(1, 0, 0));
    up = glm::normalize(orientation * glm::vec3(0, 1, 0));
    direction = glm::normalize(orientation * glm::vec3(0, 0, -1));

    if (glm::dot(up, glm::cross(right, direction)) < 0)
    {
        up *= -1;
    }
}

void QuaternionCamera::updateViewMatrix()
{
    viewMatrix = glm::lookAt(position, position + direction, up/*glm::cross(right, direction)*/);
}



PerspectiveCamera::PerspectiveCamera(glm::vec3 position, float fov, float aspectRatio, float zNear, float zFar) : QuaternionCamera(position, zNear, zFar)
{
    this->fov = fov;
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}

PerspectiveCamera::PerspectiveCamera(glm::vec3 position, glm::quat rotation, float fov, float aspectRatio, float zNear, float zFar) : QuaternionCamera(position, rotation, zNear, zFar)
{
    this->fov = fov;
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}


PerspectiveCamera::PerspectiveCamera(glm::vec3 position, glm::vec3 rotation, float fov, float aspectRatio, float zNear, float zFar) : QuaternionCamera(position, rotation, zNear, zFar)
{
    this->fov = fov;
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}

void PerspectiveCamera::setFOV(float fov)
{
    this->fov = fov;

    updateProjectionMatrix();
}

void PerspectiveCamera::setAspectRatio(float aspectRatio)
{
    this->aspectRatio = aspectRatio;

    updateProjectionMatrix();
}

void PerspectiveCamera::setAspectRatio(float width, float height)
{
    this->aspectRatio = height / width;

    updateProjectionMatrix();
}

float PerspectiveCamera::getFOV() const
{
    return fov;
}

float PerspectiveCamera::getAspectRatio() const
{
    return aspectRatio;
}

void PerspectiveCamera::updateProjectionMatrix()
{
    projectionMatrix = glm::perspective(glm::radians(fov), aspectRatio, zNear, zFar);
}


Solution

  • In general, changing the pitch, yaw, or roll will change all the components of a quaternion. So I would just transform the quaternion into euler angles, set the appropriate angle, and transform that back into the quaternion. Something like this:

    glm::vec3 eulerAngles = glm::eulerAngles(orientation);
    eulerAngles.x = amount; // .y for yaw, .z for roll
    orientation = glm::quat(eulerAngles);