Extracting camera position from a ModelView Matrix

Here are some notes on extracting a camera position from a model-view matrix that I have been playing with.
All code here uses the GLM math library.

1) Simple extraction
If you can assume there is no scaling in the matrix, you can simply:


vec3 ExtractCameraPos_NoScale(const mat4 & a_modelView)
{
  mat3 rotMat(a_modelView);
  vec3 d(a_modelView[3]);

  vec3 retVec = -d * rotMat;
  return retVec;
}

2) Inverse Matrix Extraction
If you don’t know if there is scaling in the matrix, you can invert the matrix then pull out the final column.


  mat4 viewModel = inverse(modelView);
  vec3 cameraPos(viewModel[3]); // Might have to divide by w if you can't assume w == 1

3) Plane intersection extraction
If you have to deal with scaling, but can assume the last row is (0,0,0,1) (every model-view matrix I have seen)


vec3 ExtractCameraPos(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
  
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);

  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);

  // Get the intersection of these 3 planes
  // http://paulbourke.net/geometry/3planes/
  vec3 n2n3 = cross(n2, n3);
  vec3 n3n1 = cross(n3, n1);
  vec3 n1n2 = cross(n1, n2);

  vec3 top = (n2n3 * d1) + (n3n1 * d2) + (n1n2 * d3);
  float denom = dot(n1, n2n3);

  return top / -denom;
}

This code probably is the same as hacking an inverse method to not extract all values and assume the last row is 0,0,0,1.

Here is the complete code used in testing


#include "../glm/glm.hpp"
#include "../glm/gtc/matrix_transform.hpp"

using namespace glm;

vec3 ExtractCameraPos(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
  
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);

  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);

  // Get the intersection of these 3 planes
  // http://paulbourke.net/geometry/3planes/
  vec3 n2n3 = cross(n2, n3);
  vec3 n3n1 = cross(n3, n1);
  vec3 n1n2 = cross(n1, n2);

  vec3 top = (n2n3 * d1) + (n3n1 * d2) + (n1n2 * d3);
  float denom = dot(n1, n2n3);

  return top / -denom;
}

vec3 ExtractCameraPos_NoScale(const mat4 & a_modelView)
{
  mat3 rotMat(a_modelView);
  vec3 d(a_modelView[3]);

  vec3 retVec = -d * rotMat;
  return retVec;
}

int main(int argc, char * argv[])
{
  vec3 cameraPos(1.234f, 2.657f, 7.865f);

  // Create a model view matrix with translations and scales
  mat4 modelView(1.0f);
  modelView = rotate(modelView, 0.5f, vec3(1.0f, 0.0f, 0.0f));
  modelView = rotate(modelView, 0.2f, vec3(0.0f, 1.0f, 0.0f));
  modelView = rotate(modelView, 0.3f, vec3(0.0f, 0.0f, 1.0f));
  modelView = scale(modelView, vec3(2.0f, 3.0f, 4.0f));

  modelView = translate(modelView, -cameraPos);

  vec3 cameraPos2 = ExtractCameraPos(modelView);

  mat4 viewModel = inverse(modelView);
  vec3 cameraPos3(viewModel[3]);

  // Do not add scale if using this
  //vec3 cameraPos4 = ExtractCameraPos_NoScale(modelView);

    return 0;
}


Very nice! I should probably consider adding this to GLM. I create a ticket for that https://github.com/Groovounet/glm/issues/11.

Thanks!

I was doing some reading of “RealTime Collision Detection” and I think I found a small optimization

4) Plane intersection extraction - optimization


vec3 ExtractCameraPos2(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
 
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);
 
  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);
 
  // Get the intersection of these 3 planes 
  // (uisng math from RealTime Collision Detection by Christer Ericson)
  vec3 n2n3 = cross(n2, n3);
  float denom = dot(n1, n2n3);

  vec3 top = (n2n3 * d1) + cross(n1, (d3*n2) - (d2*n3));
  return top / -denom;
}

5) Plane intersection extraction - alternate
There are two ways of doing 3 plane intersection tests in RealTime Collision Detection - here is the other way - but it seems that this produces more inaccurate results? (unless I have a bug in the code - or it is a problem with the test values I am using)


vec3 ExtractCameraPos3(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
 
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);
 
  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);
 
  // Get the intersection of these 3 planes 
  // (uisng math from RealTime Collision Detection by Christer Ericson)
  vec3 n2n3 = cross(n2, n3);
  float denom = dot(n1, n2n3);

  vec3 d(d1,d2,d3);
  vec3 v = cross(n1, d);
  
  vec3 top;
  top.x = dot(d, n2n3);
  top.y = dot(n3, v);
  top.z = -dot(n2, v);

  return top / -denom;
}


Results are (VS2008 - precise float point model used in project settings):

1.234000 2.657000 7.865000 - Base Camera
1.234000 2.657000 7.865000 - Plane intersection extraction
1.234000 2.657000 7.865001 - Plane intersection extraction - optimize
1.282042 2.507463 7.897709 - Plane intersection extraction alternate
1.234000 2.657000 7.865001 - Inverse Matrix

Updated complete code listing


#include "../glm/glm.hpp"
#include "../glm/gtc/matrix_transform.hpp"
 
using namespace glm;
 
vec3 ExtractCameraPos(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
 
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);
 
  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);
 
  // Get the intersection of these 3 planes
  // http://paulbourke.net/geometry/3planes/
  vec3 n2n3 = cross(n2, n3);
  vec3 n3n1 = cross(n3, n1);
  vec3 n1n2 = cross(n1, n2);
 
  vec3 top = (n2n3 * d1) + (n3n1 * d2) + (n1n2 * d3);
  float denom = dot(n1, n2n3);
 
  return top / -denom;
}

vec3 ExtractCameraPos2(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
 
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);
 
  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);
 
  // Get the intersection of these 3 planes 
  // (uisng math from RealTime Collision Detection by Christer Ericson)
  vec3 n2n3 = cross(n2, n3);
  float denom = dot(n1, n2n3);

  vec3 top = (n2n3 * d1) + cross(n1, (d3*n2) - (d2*n3));
  return top / -denom;
}


vec3 ExtractCameraPos3(const mat4 & a_modelView)
{
  // Get the 3 basis vector planes at the camera origin and transform them into model space.
  //  
  // NOTE: Planes have to be transformed by the inverse transpose of a matrix
  //       Nice reference here: http://www.opengl.org/discussion_boards/showthread.php/159564-Clever-way-to-transform-plane-by-matrix
  //
  //       So for a transform to model space we need to do:
  //            inverse(transpose(inverse(MV)))
  //       This equals : transpose(MV) - see Lemma 5 in http://mathrefresher.blogspot.com.au/2007/06/transpose-of-matrix.html
  //
  // As each plane is simply (1,0,0,0), (0,1,0,0), (0,0,1,0) we can pull the data directly from the transpose matrix.
  //  
  mat4 modelViewT = transpose(a_modelView);
 
  // Get plane normals 
  vec3 n1(modelViewT[0]);
  vec3 n2(modelViewT[1]);
  vec3 n3(modelViewT[2]);
 
  // Get plane distances
  float d1(modelViewT[0].w);
  float d2(modelViewT[1].w);
  float d3(modelViewT[2].w);
 
  // Get the intersection of these 3 planes 
  // (uisng math from RealTime Collision Detection by Christer Ericson)
  vec3 n2n3 = cross(n2, n3);
  float denom = dot(n1, n2n3);

  vec3 d(d1,d2,d3);
  vec3 v = cross(n1, d);
  
  vec3 top;
  top.x = dot(d, n2n3);
  top.y = dot(n3, v);
  top.z = -dot(n2, v);

  return top / -denom;
}


vec3 ExtractCameraPos_NoScale(const mat4 & a_modelView)
{
  mat3 rotMat(a_modelView);
  vec3 d(a_modelView[3]);
 
  vec3 retVec = -d * rotMat;
  return retVec;
}
 

void PrintVec3(const vec3 & a_vec)
{
  printf("%f %f %f", a_vec.x, a_vec.y, a_vec.z); 
}

int main(int argc, char * argv[])
{
  vec3 cameraPos(1.234f, 2.657f, 7.865f);
 
  // Create a model view matrix with translations and scales
  mat4 modelView(1.0f);
  modelView = rotate(modelView, 0.5f, vec3(1.0f, 0.0f, 0.0f));
  modelView = rotate(modelView, 0.2f, vec3(0.0f, 1.0f, 0.0f));
  modelView = rotate(modelView, 0.3f, vec3(0.0f, 0.0f, 1.0f));
  modelView = scale(modelView, vec3(2.0f, 3.0f, 4.0f));
 
  modelView = translate(modelView, -cameraPos);
 
  vec3 cameraPos2 = ExtractCameraPos(modelView);
  vec3 cameraPos2a = ExtractCameraPos2(modelView); 
  
  // ExtractCameraPos3 does not seem as accurate as the previous 2 methods
  vec3 cameraPos2b = ExtractCameraPos3(modelView); 

  mat4 viewModel = inverse(modelView);
  vec3 cameraPos3(viewModel[3]);
 
  // Do not add scale if using this
  //vec3 cameraPos4 = ExtractCameraPos_NoScale(modelView);
 
  PrintVec3(cameraPos); printf(" - Base Camera
");
  PrintVec3(cameraPos2); printf(" - Plane intersection extraction
");
  PrintVec3(cameraPos2a); printf(" - Plane intersection extraction - optimize
");
  PrintVec3(cameraPos2b); printf(" - Plane intersection extraction alternate
");
  PrintVec3(cameraPos3); printf(" - Inverse Matrix
");

  return 0;
}