Friday 20 January 2012

ngl::Matrix vs Imath::Matrix44

As I've been working with the Alembic file I/O system for a while I've been using some of the IMath functions as Alembic is build upon IMath / OpenEXR base code. IMath is a templated maths library that, to quote the website, "Imath, a math library with support for matrices, 2d- and 3d-transformations, solvers for linear/quadratic/cubic equations, and more".

Half way through using this I started wanting to use my own ngl::Matrix library as this is integrated into my code base, it when that I discovered that the two were not fully compatible.

Whilst both have very similar functions, the one core difference was how the matrix*matrix multiplication worked, (pre / post multiplication of values). In the end I decided to modify how the ngl::Matrix * operator worked so that it is compatible with IMath::Matrix44

Using IMath in ngl::
To start using IMath in ngl (or other programs) we need to set the compiler include paths to the correct place. By default they are installed in /usr/local (with the headers being in a directory OpenEXR)

A number of the classes in IMath are templated header only files so we don't need to add any additional libs, however some of the functions may also need the additional libImath.so (or static .a version)

To add these in a Qt project file add the following lines

INCLUDEPATH+=/usr/include/
LIBS +=-lImath

Once these have been added to the project we need to add the following header to the program
#include <OpenEXR/ImathMatrix.h>
#include <OpenEXR/ImathVec.h>

The IMath::Matrix44 class is a templated class so we need to construct it to be compatible with the ngl::Matrix class using a float. The following code is going to construct both an ngl::Matrix and a IMath::Matrix44
float xRotation=45.0f;
// Imath
Imath::Matrix44 <float> iXMatrix;
iXMatrix.setAxisAngle(Imath::Vec3<float>(1,0,0),ngl::radians(xRotation));
// ngl
ngl::Matrix nXMatrix;
nXMatrix.rotateX(xRotation);

In the above example the Imath matrix is constructed and will be set to the identity matrix as default. We then use the setAxisAngle method to set the matrix as a rotation around the x axis by xRotation degrees. This method is passed a vector for the axis to rotate around and a value for the rotation which must be converted into radians.

The ngl::Matrix class is also set to the identity when it's constructed, and to set the rotation value we use the rotateX method (which expects the rotation values in degrees).

We can check the output of this by using the overloaded << operators as shown

std::cout<<"X rotation "<<xRotation<<"\n"<<nXMatrix<<"\n"<<iXMatrix<<"\n";


X rotation 45
[+1.0000000000000000,+0.0000000000000000,+0.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,-0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +1.0000000000000000   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   -0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)
Another useful feature of the ngl::Matrix class is that is can be constructed from a float [4][4] as shown here
Matrix::Matrix(Real _m[4][4])
{
  for(int y=0; y<4; ++y)
  {
    for(int x=0; x<4; ++x)
    {
      m_m[y][x]=_m[y][x];
    }
  }

}
This means we can construct an ngl::Matrix from an Imath matrix as shown
  
Imath::Matrix44 <float> iZMatrix;
iZMatrix.setAxisAngle(Imath::Vec3<float>(0,0,1),ngl::radians(zRotation));
// alternativly we can construct from an Matrix.x array
ngl::Matrix nZMatrix(iZMatrix.x);
The listing below show the complete program and the output
int main()
{
  float xRotation=45.0f;
  float yRotation=35.0f;
  float zRotation=15.0f;


  Imath::Matrix44 <float> iXMatrix;
  iXMatrix.setAxisAngle(Imath::Vec3<float>(1,0,0),ngl::radians(xRotation));
  ngl::Matrix nXMatrix;
  nXMatrix.rotateX(xRotation);

  Imath::Matrix44 <float> iYMatrix;
  iYMatrix.setAxisAngle(Imath::Vec3<float>(0,1,0),ngl::radians(yRotation));
  ngl::Matrix nYMatrix;//(iYMatrix.x);
  nYMatrix.rotateY(yRotation);

  Imath::Matrix44 <float> iZMatrix;
  iZMatrix.setAxisAngle(Imath::Vec3<float>(0,0,1),ngl::radians(zRotation));
  // alternativly we can construct from an Matrix.x array
  ngl::Matrix nZMatrix(iZMatrix.x);

  std::cout<<"X rotation "<<xRotation<<"\n"<<nXMatrix<<"\n"<<iXMatrix<<"\n";
  std::cout<<"y rotation "<<yRotation<<"\n"<<nYMatrix<<"\n"<<iYMatrix<<"\n";
  std::cout<<"z rotation "<<zRotation<<"\n"<<nZMatrix<<"\n"<<iZMatrix<<"\n";

  std::cout<<"ngl mult y*x \n"<<nYMatrix*nXMatrix<<"\n";
  std::cout<<"iMath mult y*x \n"<<iYMatrix*iXMatrix<<"\n";
  ngl::Matrix nxyz=nXMatrix*nYMatrix*nZMatrix;
  std::cout<<"ngl mult x*y*z \n"<<nxyz<<"\n";
  Imath::Matrix44 <float> ixyz=iXMatrix*iYMatrix*iZMatrix;
  std::cout<<"iMath mult x*y*z \n"<<ixyz<<"\n";

  Imath::Matrix44 <float> iInverse=ixyz.inverse();
  std::cout<<"inverse \n"<<iInverse<<"\n";
  ngl::Matrix nInverse=nxyz.inverse();
  std::cout<<"inverse \n"<<nInverse<<"\n";

  Imath::Vec3<float> iPos(1,2,3);
  std::cout<<"i V*M "<<iPos*ixyz<<"\n";
  ngl::Vector nPos(1,2,3,1);
  std::cout<<"n V*M"<<nPos*nxyz<<"\n";
  std::cout<<"n M*V"<<nxyz*nPos<<"\n";
}
Which gives the following output
X rotation 45
[+1.0000000000000000,+0.0000000000000000,+0.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,-0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +1.0000000000000000   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   -0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

y rotation +35.0000000000000000
[+0.8191520571708679,+0.0000000000000000,-0.5735764503479004,+0.0000000000000000]
[+0.0000000000000000,+1.0000000000000000,+0.0000000000000000,+0.0000000000000000]
[+0.5735764503479004,+0.0000000000000000,+0.8191520571708679,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +0.8191520571708679   +0.0000000000000000   -0.5735764503479004   +0.0000000000000000
   +0.0000000000000000   +1.0000000000000000   +0.0000000000000000   +0.0000000000000000
   +0.5735764503479004   +0.0000000000000000   +0.8191520571708679   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

z rotation +15.0000000000000000
[+0.9659258127212524,+0.2588190436363220,+0.0000000000000000,+0.0000000000000000]
[-0.2588190436363220,+0.9659258127212524,+0.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+1.0000000000000000,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

(  +0.9659258127212524   +0.2588190436363220   +0.0000000000000000   +0.0000000000000000
   -0.2588190436363220   +0.9659258127212524   +0.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

ngl mult y*x 
[+0.8191520571708679,+0.4055798053741455,-0.4055798053741455,+0.0000000000000000]
[+0.0000000000000000,+0.7071067690849304,+0.7071067690849304,+0.0000000000000000]
[+0.5735764503479004,-0.5792279839515686,+0.5792279839515686,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

iMath mult y*x 
(  +0.8191520571708679   +0.4055798053741455   -0.4055798053741455   +0.0000000000000000
   +0.0000000000000000   +0.7071067690849304   +0.7071067690849304   +0.0000000000000000
   +0.5735764503479004   -0.5792279839515686   +0.5792279839515686   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

ngl mult x*y*z 
[+0.7912400960922241,+0.2120121568441391,-0.5735764503479004,+0.0000000000000000]
[+0.2087472975254059,+0.7879844307899475,+0.5792279839515686,+0.0000000000000000]
[+0.5747727155685425,-0.5780408978462219,+0.5792279839515686,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

iMath mult x*y*z 
(  +0.7912400960922241   +0.2120121568441391   -0.5735764503479004   +0.0000000000000000
   +0.2087472975254059   +0.7879844307899475   +0.5792279839515686   +0.0000000000000000
   +0.5747727155685425   -0.5780408978462219   +0.5792279839515686   +0.0000000000000000
   +0.0000000000000000   +0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

inverse 
(  +0.7912400960922241   +0.2087472826242447   +0.5747727155685425   +0.0000000000000000
   +0.2120121717453003   +0.7879844903945923   -0.5780409574508667   +0.0000000000000000
   -0.5735764503479004   +0.5792279243469238   +0.5792278647422791   +0.0000000000000000
   +0.0000000000000000   -0.0000000000000000   +0.0000000000000000   +1.0000000000000000)

inverse 
[+0.7912402153015137,+0.2087473124265671,+0.5747727751731873,+0.0000000000000000]
[+0.2120122015476227,+0.7879846096038818,-0.5780410170555115,+0.0000000000000000]
[-0.5735765099525452,+0.5792279839515686,+0.5792279243469238,+0.0000000000000000]
[+0.0000000000000000,+0.0000000000000000,+0.0000000000000000,+1.0000000000000000]

i V*M (+2.9330530166625977 +0.0538582801818848 +2.3225636482238770)
n V*M[+2.9330530166625977,+0.0538582801818848,+2.3225636482238770,+1.0000000000000000]
n M*V[-0.5054649114608765,+3.5224001407623291,+1.1563749313354492,+1.0000000000000000]

The main upshot from these changes occur in the ngl demos where the code to load the matrices to the shader have been modified as shown in the code here
ngl::Matrix MV;
ngl::Matrix MVP;
ngl::Mat3x3 normalMatrix;
ngl::Matrix M;

// load matrix to shader before changes to ngl::Matrix

M=_tx.getCurrentTransform().getMatrix();
MV=m_cam->getViewMatrix() *_tx.getCurrAndGlobal().getMatrix();
MVP=m_cam->getProjectionMatrix()*MV*;

// new version with compatible matrix
M=_tx.getCurrentTransform().getMatrix();
MV=_tx.getCurrAndGlobal().getMatrix()*m_cam->getViewMatrix() ;
MVP=MV*m_cam->getProjectionMatrix();

As you can see when we calculated the previous MVP matrix it was done using the P*V*M calculation order, now we use M*V*P instead. If you are having any issues just swap the matrix order in these type of functions.

No comments:

Post a Comment