0
\$\begingroup\$

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.

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);
}
\$\endgroup\$
  • \$\begingroup\$ I'm sorry I cannot post an answer. But this link will prove usefull. I is in french, but google translate. openclassrooms.com/courses/… \$\endgroup\$ – HgMerk Jul 6 '15 at 17:59
3
\$\begingroup\$

As someone who has implemented an entire Math library including Quaternions let me say that just following a tutorial will not get you anywhere with this. To iron out bugs like this you really need to understand how Quaternions work, what they are and how to use them. It's probably some of the most complicated math in Game development and I don't recommend trying to just wing it with an OpenGL camera to try to learn it all.

euclideanspace has a lot of really good resources but it's a terribly organized site so I can point you to a page to get you started (can't post more than 2 links right now).

http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm

It might be better to ask some more specific questions related to rotational mathematics and quaternions over a the math stack exchange as you learn about this concept. It might be better right now to just stick with an Euler angle camera with a matrix representation under the hood.

It's also worth noting that just having a quaternion implementation will not solve gimbal lock on its own (assuming that's your goal). https://mathoverflow.net/questions/95902/the-gimbal-lock-shows-up-in-my-quaternions

\$\endgroup\$
  • \$\begingroup\$ Thanks, the first page you linked seems to be a good resource, I shall endeavor to improve my understanding of quaternions in the hope I can debug this and report back when I find the problem. \$\endgroup\$ – Edward J Brown Jul 6 '15 at 19:22
  • 2
    \$\begingroup\$ It turned out in the end that the problem was not being caused by the quaternions but rather, amongst other things, errors in the way I was assembling the UVN matrix and the order I was applying the matrices. \$\endgroup\$ – Edward J Brown Jul 10 '15 at 20:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.