Quaternions, matrices and gluLookAt

Top  Previous  Next

 

Saturday, March 1, 2014

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

Labels: OpenGL Email ThisBlogThis!Share to TwitterShare to FacebookShare to Pinterest

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

Posted by Muhammad Mobeen Movania

at 11:48 PM

0 comments:

 

Post a Comment

Newer Post Older Post Home

Subscribe to: Post Comments (Atom)

Popular Posts

 

  An eye into my world

  PhysX Basics Tutorial: A Simple Bouncing Box

  In this tutorial, I will show you how to create a simple box at a specific position and let it bounce under influence of gravity. We will...

  An eye into my world

  Skeletal Animation and GPU Skinning - The Crux

  Recently, for one of my projects, I had to learn about skeletal animation and GPU skinning. As always, I first went to google which gave me...

  PhysX3: Getting started

  There has been a major revamp of the PhysX API from version 3. I will try to convert all of the existing tutorials into PhysX3 so here I go ...

  PhysX Basics Tutorial: Getting started with NVIDIA PhysX

  I went to see the power of the NVIDIA PhysX sdk. So I downloaded the sdk from the PhysX developer site. While the sdk contains a lot of de...

  An eye into my world

  PhysX3: A simple bouncing box

  In this tutorial, I will show you how to create a simple box at a specific position and let it bounce under influence of gravity. We will be...

 

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

Search This Blog