How to properly implement a quaternion camera in modern mode?

I am trying to create a uvn quaternion camera in opengl using various tutorials listed below and reading about quaternions and axis rotation. I was left with a particular error that I cannot fix.

Basically the camera works fine until the camera is rotated 45 degrees from + z at this point, tilting the camera up or down seems to tilt the camera around its target axis, rotating the vector up.

By the time the camera is facing forward -z, tilting up or down creates the illusion of the opposite, tilting up and down tilts up.

I have seen other implementations suggesting a non-core system in which quaternions are accumulated into one, which describes the current orientation as a delta from some arbitrary starting angle. It sounds great, but I can't figure out exactly how to implement it, specifically the conversion from this to a view matrix.

Elsewhere on SO I read about splitting rotation into two quaternions, which are yaw and step separately, but I'm not sure if that is the cause of the problem, since in this context, correct me if I'm wrong, but my understanding is that the order is, in which you apply two rotations, it doesn't matter.

Relevant source codes of the code:

Quarter operations

Quaternion<TValue> conjugate() const{
            return Quaternion({ { -m_values[X], -m_values[Y], -m_values[Z], m_values[W] } });
};

Quaternion<TValue>& operator*=(const Quaternion<TValue>& rhs) {
        TValue x, y, z, w;
            w = rhs[W] * m_values[W] - rhs[X] * m_values[X] - rhs[Y] * m_values[Y] - rhs[Z] * m_values[Z];
            x = rhs[W] * m_values[X] + rhs[X] * m_values[W] - rhs[Y] * m_values[Z] + rhs[Z] * m_values[Y];
            y = rhs[W] * m_values[Y] + rhs[X] * m_values[Z] + rhs[Y] * m_values[W] - rhs[Z] * m_values[X];
            z = rhs[W] * m_values[Z] - rhs[X] * m_values[Y] + rhs[Y] * m_values[X] + rhs[Z] * m_values[W];          

            m_values[X] = x;
            m_values[Y] = y;
            m_values[Z] = z;
            m_values[W] = w;
            return *this;
};
static Quaternion<TValue> rotation(Vector<3, TValue> axis, TValue angle){
        float x, y, z, w;
        TValue halfTheta = angle / 2.0f;
        TValue sinHalfTheta = sin(halfTheta);
        return Quaternion<TValue>({ { axis[X] * sinHalfTheta, axis[Y] * sinHalfTheta, axis[Z] * sinHalfTheta, cos(halfTheta) } });
};

      

Vector rotation operation

Vector<dimensions, TValue> rotate(const Vector<3, TValue> axis, float angle){
        Quaternion<TValue> R = Quaternion<TValue>::rotation(axis, angle);
        Quaternion<TValue> V = (*this);
        Vector<dimensions, TValue> result = R * V * R.conjugate();
        return result;
}

      

Camera methods

Camera::Camera(Vector<2, int> windowSize, float fov, float near, float far):
m_uvn(Matrix<4, float>::identity()),
m_translation(Matrix<4, float>::identity()),
m_ar(windowSize[Dimensions::X] / (float)windowSize[Dimensions::Y]),
m_fov(fov),
m_near(near),
m_far(far),
m_position(),
m_forward({ { 0, 0, 1 } }),
m_up({ { 0, 1, 0 } })
{
    setViewMatrix(Matrix<4, float>::identity());
    setProjectionMatrix(Matrix<4, float>::perspective(m_ar, m_near, m_far, m_fov));
};

Matrix<4, float> Camera::getVPMatrix() const{
    return m_vp;
};

const Vector<3, float> Camera::globalY = Vector<3, float>({ { 0, 1, 0 } });

void Camera::setProjectionMatrix(const Matrix<4, float> p){
    m_projection = p;
    m_vp = m_projection * m_view;
};

void Camera::setViewMatrix(const Matrix<4, float> v){
    m_view = v;
    m_vp = m_projection * m_view;
};

void Camera::setTranslationMatrix(const Matrix<4, float> t){
    m_translation = t;
    setViewMatrix(m_uvn * m_translation);
}

void Camera::setPosition(Vector<3, float> position){
    if (position != m_position){
        m_position = position;
        setTranslationMatrix(Matrix<4, float>::translation(-position));
    }
};

void Camera::moveForward(float ammount){
    setPosition(m_position + (m_forward * ammount));
}

void Camera::moveRight(float ammount){
    setPosition(m_position + (getRight() * ammount));
}

void Camera::moveUp(float ammount){
    setPosition(m_position + (m_up * ammount));
}

void Camera::setLookAt(Vector<3, float> target, Vector<3, float> up){
    Vector<3, float> newUp = up.normalize();
    Vector<3, float> newForward = target.normalize();
    if (newUp != m_up || newForward != m_forward){
        m_up = newUp;
        m_forward = newForward;

        Vector<3, float> newLeft = getLeft();
        m_up = newLeft * m_forward;

        m_uvn = generateUVN();
        setViewMatrix(m_uvn * m_translation);
    }
};

void Camera::rotateX(float angle){
    Vector<3, float> hAxis = (globalY * m_forward).normalize();
    m_forward = m_forward.rotate(hAxis, angle).normalize();
    m_up = (m_forward * hAxis).normalize();

    m_uvn = generateUVN();
    setViewMatrix(m_translation * m_uvn);
}

void Camera::rotateY(float angle){
    Vector<3, float> hAxis = (globalY * m_forward).normalize();
    m_forward = m_forward.rotate(globalY, angle).normalize();
    m_up = (m_forward * hAxis).normalize();

    m_uvn = generateUVN();
    setViewMatrix(m_translation * m_uvn);
}

Vector<3, float> Camera::getRight(){
    return (m_forward * m_up).normalize();
}

Vector <3, float> Camera::getLeft(){
    return (m_up * m_forward).normalize();
}

      

};

I guess the problem lies either with my implementation of the quaternion or the way I use it, but due to the complex nature of the system, I can't seem to dwell on this question. Due to the strange errors I am experiencing, I'm not sure if there is something wrong with the way I am trying to implement the camera?

Tutorials

Career / vector mathematics

+3


source to share





All Articles