Saturday, March 1, 2014

Making an OpenGL object look at another object in three different ways: quaternions, matrices and gluLookAt

Recently, I was trying to see how I could make one object look at another object in OpenGL. As always, I started out with google which give me the first link which listed to use quaternions. The second link showed me to use vector and matrices. I wanted to use gluLookAt but to my surprise none of the google links seem to provide me the answer. After some time, I got it working so here are the three methods that I have found and how they worked for me. I will be using glm library for math utilities. 

Method 1: Using Quaternions
Main reference: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/
You first need to provide a function RotationBetweenVectors that returns the rotation as a quaternion. We define this function as given in the link above.

glm::quat RotationBetweenVectors(glm::vec3 start, glm::vec3 dest){
   start = glm::normalize(start);
   dest = glm::normalize(dest);

   float cosTheta = glm::dot(start, dest);
   glm::vec3 rotationAxis;

   if (cosTheta < -1 + 0.001f){
     rotationAxis = glm::cross(glm::vec3(0.0f, 0.0f, 1.0f), start);
     if (glm::length(rotationAxis) < 0.01 ) 

      rotationAxis = glm::cross(glm::vec3(1.0f, 0.0f, 0.0f), start);
      rotationAxis = glm::normalize(rotationAxis);
      return glm::angleAxis(180.0f, rotationAxis);
    }

    rotationAxis = glm::cross(start, dest);

    float s = sqrt( (1+cosTheta)*2 );
    float invs = 1 / s;

    return glm::quat(
        s * 0.5f,
        rotationAxis.x * invs,
        rotationAxis.y * invs,
        rotationAxis.z * invs
    );
}


After the RotationBetweenVectors function is defined, we can use it as follows. Target is the object you want to look at and object position is the position of the object that is going to look at the target.

glm::vec3 delta =  (targetPosition-objectPosition);
glm::vec3 desiredUp(0,1,0.00001);
glm::quat rot1 = RotationBetweenVectors(glm::vec3(0,0,1), delta);
glm::vec3 right = glm::cross(delta, desiredUp);
desiredUp = glm::cross(right, delta);
glm::vec3 newUp = rot1 * glm::vec3(0.0f, 1.0f, 0.0f);
glm::quat rot2 = RotationBetweenVectors(newUp, desiredUp);
glm::quat targetOrientation = rot2 * rot1;
glm::mat4 M=glm::toMat4(targetOrientation);

M[3][0]=objectPosition.x;
M[3][1]=objectPosition.y;
M[3][2]=objectPosition.z;

Now the matrix M is the desired matrix. To use this matrix, you multiply the matrix M with the current modelview matrix. I do it as follows.

glPushMatrix();
   glMultMatrix(glm::value_ptr(M));
glPopMatrix(); 

Method 2: Matrix based approach
Main Reference: http://stackoverflow.com/questions/6992541/opengl-rotation-in-given-direction.
This method uses basic vector calcualtion as follows.

glm::vec3 delta = targetPosition-objectPosition;
glm::vec3 up;
glm::vec3 direction(glm::normalize(delta));
if(abs(direction.x)< 0.00001 && abs(direction.z) < 0.00001){ 

   if(direction.y > 0)
     up = glm::vec3(0.0, 0.0, -1.0); //if direction points in +y
   else
        up = glm::vec3(0.0, 0.0, 1.0); //if direction points in -y 
   } else {
        up = glm::vec3(0.0, 1.0, 0.0); //y-axis is the general up
   }

up=glm::normalize(up);
glm::vec3 right = glm::normalize(glm::cross(up,direction));
up= glm::normalize(glm::cross(direction, right));
   
return glm::mat4(right.x, right.y, right.z, 0.0f,     
        up.x, up.y, up.z, 0.0f,                      
        direction.x, direction.y, direction.z, 0.0f, 
       
objectPosition.x, objectPosition.y, objectPosition.z, 1.0f);   
Now the matrix M is the desired matrix. To use this matrix, you multiply the matrix M with the current modelview matrix. I do it as follows.

glPushMatrix();
   glMultMatrix(glm::value_ptr(M));
glPopMatrix();

Once again, rather than me explaining the theory, I would ask you to go to the original link given above for details.

Method 3: Using gluLookAt
Main Reference: None :( I found it myself

Now this is the method I was trying to find online but none of the references seem to provide the details. I wanted to use the gluLookAt function to find the look at matrix. So here is how I implemented it. In order to get the matrix from OpenGL, I store the current matrix (glPushMatrix) clear it to identitfy (glLoadIdentity), then call the gluLookAt function. Then I extract the current modelview matrix as follows

glPushMatrix(); //store the current MV matrix
   glLoadIdentity(); //clear current MV matrix
   gluLookAt(objectPosition.x,objectPosition.y,objectPosition.z,
             targetPosition.x,targetPosition.y,targetPosition.z,
             0,1,0);
   GLfloat MV[16];
   glGetFloatv(GL_MODELVIEW_MATRIX, MV);
glPopMatrix();

The gluLookAt function calculates the orientation matrix I want but it is to orient my object in eye space. In order to get the object space matrix, I need to find the inverse of this matrix. I use glm library to calculate the inverse by passing it my MV matrix as follows (please note how the MV matrix is passed to glm)

glm::mat4 M(MV[0], MV[1], MV[2], MV[3],
            MV[4], MV[5], MV[6], MV[7],
            MV[8], MV[9], MV[10], MV[11],
            MV[12], MV[13], MV[14], MV[15]);

M = glm::inverse(M);


Another thing we need to do is to invert the X and Z axes which points in the -ve X and -ve Z axis in object space after the inverse. Thus, I do the following calls.

M[0][0] = -M[0][0];
M[0][1] = -M[0][1];
M[0][2] = -M[0][2];


M[2][0] = -M[2][0];
M[2][1] = -M[2][1];
M[2][2] = -M[2][2];


Now the matrix M is the desired matrix. To use this matrix, you multiply the matrix M with the current modelview matrix. I do it as follows.

glPushMatrix();
   glMultMatrix(glm::value_ptr(M));
glPopMatrix(); 

This produces the same result as the previous two methods.  For your convenience, here are the three methods in their separate functions.

glm::mat4 GetMatrixMethod1(const glm::vec3& object, const glm::vec3& target) {
    glm::vec3 delta =  (target-object);
    glm::vec3 desiredUp(0,1,0.00001);
    glm::quat rot1 = RotationBetweenVectors(glm::vec3(0,0,1), delta);
    glm::vec3 right = glm::cross(delta, desiredUp);
    desiredUp = glm::cross(right, delta);
 
    glm::vec3 newUp = rot1 * glm::vec3(0.0f, 1.0f, 0.0f);
    glm::quat rot2 = RotationBetweenVectors(newUp, desiredUp);
    glm::quat targetOrientation = rot2 * rot1;
    glm::mat4 M=glm::toMat4(targetOrientation);
    M[3][0] = object.x;
    M[3][1] = object.y;
    M[3][2] = object.z;
    return M;
}

glm::mat4 GetMatrixMethod2(const glm::vec3& object, const glm::vec3& target) {
    //second method
    glm::vec3 up;
    glm::vec3 direction(glm::normalize(target-object));
    if(abs(direction.x)< 0.00001 && abs(direction.z) < 0.00001){  

    if(direction.y > 0)
        up = glm::vec3(0.0, 0.0, -1.0);
    else
        up = glm::vec3(0.0, 0.0, 1.0); 

} else {
        up = glm::vec3(0.0, 1.0, 0.0);      }
    up=glm::normalize(up);
   
   glm::vec3 right = glm::normalize(glm::cross(up,direction));
   up= glm::normalize(glm::cross(direction, right));
   
   return glm::mat4(right.x, right.y, right.z, 0.0f,     
        up.x, up.y, up.z, 0.0f,                      
        direction.x, direction.y, direction.z, 0.0f, 
        object.x, object.y, object.z, 1.0f);  
}

glm::mat4 GetMatrixMethod3(const glm::vec3& object, const glm::vec3& target) {
    //assuming that the current matrix mode is modelview matrix
    glPushMatrix();
        glLoadIdentity();
        gluLookAt(object.x, object.y, object.z,
                  target.x, target.y, target.z,
                  0,1,0);

        GLfloat MV[16];
        glGetFloatv(GL_MODELVIEW_MATRIX, MV);       
    glPopMatrix();

    glm::mat4 T(MV[0], MV[1], MV[2], MV[3],
                MV[4], MV[5], MV[6], MV[7],
                MV[8], MV[9], MV[10], MV[11],
                MV[12], MV[13], MV[14], MV[15]);
    T = glm::inverse(T);
    T[2][0] = -T[2][0];
    T[2][1] = -T[2][1];
    T[2][2] = -T[2][2];



    T[0][0] = -T[0][0];
    T[0][1] = -T[
0][1];
    T[0][2] = -T[0][2];
    

    return  T ;
}


And their usage is as follows.

glm::mat4 M = GetMatrixMethod1(boxPosition, spherePosition);
//glm::mat4 M = GetMatrixMethod2(boxPosition, spherePosition);
//glm::mat4 M = GetMatrixMethod3(boxPosition, spherePosition);

glPushMatrix();
  glTranslatef(spherePosition[0], spherePosition[1], spherePosition[2]);
    DrawAxes();
    glutWireSphere(1,10,10);
glPopMatrix();


glPushMatrix();
  glMultMatrixf(glm::value_ptr(M));
           
  DrawAxes();
  glutWireCube(1);
glPopMatrix();


Thanks,
Mobeen

0 comments:

Popular Posts

Copyright (C) 2011 - Movania Muhammad Mobeen. Awesome Inc. theme. Powered by Blogger.