I am trying to implement a quaternion camera in OpenGL 3.3.
I have so far used these tutorials below however I have a bug which I cannot work out how to fix.
- http://ogldev.atspace.co.uk/www/tutorial13/tutorial13.html
- http://www.gamedev.net/page/resources/_/technical/math-and-physics/a-simple-quaternion-based-camera-r1997
So as I understand it the camera defines it's own co-ordinate space with target, up and right vectors which are relative to the view. Using some matrix trickery these three vectors can be used to construct a matrix which transforms the vector positions to the correct place.
The second of the two tutorials linked above seems to use quaternions to rotate the up, left and target vectors although it renames them to view, up and axis.
On my vector class I have a rotate method which takes an axis and angle as a parameter and creates the rotation quaternion before applying it to the vector.
The problem I face is that as I rotate the camera around the y axis to face negative z, looking up or down (rotating around the camera's right vector) appears to tilt the camera around the global x axis in the opposite direction, rather than translating relative to the camera.
Also when facing in the negative Z direction rotation around the cameras right vector is completely inverted.
I am guessing that this is something to do with the way that quarternion rotation works and that when I face towards the negative z my camera's right vector's direction is inverted thereby inverting the rotation axis. I have tried to remedy this with a messy if statement but that seemed to clamp the camera in strange places and wasn't really effective.
Some help or guidance would be greatly appreciated.
// Quaternion Normalize Method.
Quaternion<TValue> normalize() const {
TValue magnitude = getMagnitude();
auto normalizedValues = array<TValue, 4>();
for (int i = 0; i < 4; i++){
normalizedValues[i] = m_values[i] / magnitude;
}
return Vector<dimensions, TValue>(normalizedValues);
}
// Quaternion multiplication method.
Quaternion<TValue>& operator*=(const Quaternion<TValue>& rhs) {
float x, y, z, w;
x = m_values[W] * rhs[X] + m_values[X] * rhs[W] + m_values[Y] * rhs[Z] - m_values[Z] * rhs[Y];
y = m_values[W] * rhs[Y] - m_values[X] * rhs[Z] + m_values[Y] * rhs[W] + m_values[Z] * rhs[X];
z = m_values[W] * rhs[Z] + m_values[X] * rhs[Y] - m_values[Y] * rhs[X] + m_values[Z] * rhs[W];
w = m_values[W] * rhs[W] - m_values[X] * rhs[X] - m_values[Y] * rhs[Y] - m_values[Z] * rhs[Z];
m_values[X] = x;
m_values[Y] = y;
m_values[Z] = z;
m_values[W] = w;
return *this;
};
Quaternion<TValue> conjugate() const{
return Quaternion({ { -m_values[X], -m_values[Y], -m_values[Z], m_values[W] } });
};
// Quaternion rotation method
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 rotate method
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 rotate method.
void Camera::rotate(Vector<3,float> axis, float angle){
setLookAt(m_target.rotate(axis, angle), m_up.rotate(axis, angle));
}
// Camera setLookAt method.
void Camera::setLookAt(Vector<3, float> target, Vector<3, float> up){
Vector<3, float> newUp = up.normalize();
Vector<3, float> newTarget = target.normalize();
if (newUp != m_up || newTarget != m_target){
m_up = newUp;
m_target = newTarget;
m_right = (m_up * m_target).normalize();
m_up = m_target * m_right;
m_uvn = Matrix<4, float>::identity();
m_uvn[0][0] = m_right[Dimensions::X];
m_uvn[1][0] = m_right[Dimensions::Y];
m_uvn[2][0] = m_right[Dimensions::Z];
m_uvn[0][1] = m_up[Dimensions::X];
m_uvn[1][1] = m_up[Dimensions::Y];
m_uvn[2][1] = m_up[Dimensions::Z];
m_uvn[0][2] = m_target[Dimensions::X];
m_uvn[1][2] = m_target[Dimensions::Y];
m_uvn[2][2] = m_target[Dimensions::Z];
setViewMatrix(m_uvn * m_translation);
}
};
// Taken from game update method.
Vector<2, double> offset = getMousePosition() - m_screenCentre;
if (offset.getMagnitudeSquared() > 0.00001){
float dy = offset[Dimensions::X] * (3.1415),
dx = offset[Dimensions::Y] * (3.1415);
m_camera.rotate(Vector<3, float>({ { 0, -1, 0 } }), dy);
m_camera.rotate(Vector<3, float>(m_camera.getXAxis()), dx);
setMousePosition(m_screenCentre);
}