Jose
Jose

Reputation: 127

C++ OpenGL Quaternion for Camera flips it upside down

It does look at the target when I move to the target to the right and looks at it up until it goes 180 degrees of the -zaxis and decides to go the other way.

Matrix4x4 camera::GetViewMat()
{
    Matrix4x4 oRotate, oView;
    oView.SetIdentity();

    Vector3 lookAtDir = m_targetPosition - m_camPosition;
    Vector3 lookAtHorizontal = Vector3(lookAtDir.GetX(), 0.0f, lookAtDir.GetZ());
    lookAtHorizontal.Normalize();

    float angle = acosf(Vector3(0.0f, 0.0f, -1.0f).Dot(lookAtHorizontal));
    Quaternions horizontalOrient(angle, Vector3(0.0f, 1.0f, 0.0f));

    ori = horizontalOrient;
    ori.Conjugate();
    oRotate = ori.ToMatrix();

    Vector3 inverseTranslate = Vector3(-m_camPosition.GetX(), -m_camPosition.GetY(), -m_camPosition.GetZ());
    oRotate.Transform(inverseTranslate);

    oRotate.Set(0, 3, inverseTranslate.GetX());
    oRotate.Set(1, 3, inverseTranslate.GetY());
    oRotate.Set(2, 3, inverseTranslate.GetZ());
    oView = oRotate;

    return oView;
}

Upvotes: 4

Views: 2624

Answers (1)

Invalid
Invalid

Reputation: 1880

As promised, a bit of code showing the way I'd make a camera look at a specific point in space at all times.

First of all, we'd need a method to construct a quaternion from an angle and an axis, I happen to have that on pastebin, the angle input is in radians:

http://pastebin.com/vLcx4Qqh

Make sure you don't input the axis (0,0,0), which wouldn't make any sense whatsoever.

Now the actual update method, we need to get the quaternion rotating the camera from default orientation to pointing towards the target point. PLEASE note I just wrote this out of the top of my head, it probably needs a little debugging and may need a little optimization, but this should at least give you a push in the right direction.

void camera::update()
{
    // First get the direction from the camera's position to the target point
    vec3 lookAtDir = m_targetPoint - m_position;

    // I'm going to divide the vector into two 'components', the Y axis rotation
    // and the Up/Down rotation, like a regular camera would work.

    // First to calculate the rotation around the Y axis, so we zero out the y
    // component:
    vec3 lookAtHorizontal = vec3(lookAtDir.x, 0.0f, lookAtDir.z).normalize();

    // Get the quaternion from 'default' direction to the horizontal direction
    // In this case, 'default' direction is along the -z axis, like most OpenGL
    // programs. Make sure the projection matrix works according to this.
    float angle = acos(vec3(0.0f, 0.0f, -1.0f).dot(lookAtHorizontal));
    quaternion horizontalOrient(angle, vec3(0.0f, 1.0f, 0.0f));

    // Since we already stripped the Y component, we can simply get the up/down
    // rotation from it as well.
    angle = acos(lookAtDir.normalize().dot(lookAtHorizontal));
    if(angle) horizontalOrient *= quaternion(angle, lookAtDir.cross(lookAtHorizontal));

    // ...
    m_orientation = horizontalOrient;
}

Now to actually take m_orientation and m_position and get the world -> camera matrix

// First inverse each element (-position and inverse the quaternion),
// the position is rotated since the position within a matrix is 'added' last
// to the output vector, so it needs to account for rotation of the space.
mat3 rotationMatrix = m_orientation.inverse().toMatrix();
vec3 inverseTranslate = rotationMatrix * -m_position; // Note the minus

mat4 matrix = mat3; // just means the matrix is expanded, the last entry (bottom right of the matrix) is a 1.0f like an identity matrix would be.

// This bit is row-major in my case, you just need to set the translation of the matrix.
matrix[3] = inverseTranslate.x;
matrix[7] = inverseTranslate.y;
matrix[11] = inverseTranslate.z;

EDIT I think it should be obvious but just for completeness, .dot() takes the dot product of the vectors, .cross() takes the cross product, the object executing the method is vector A, and the parameter of the method is vector B.

Upvotes: 2

Related Questions