/ Matrix Transformations

OpenSceneGraph

Knowledge Base

Matrix Transformations

If a position in space is defined by 3 coordinates (aka x,y,z) MatrixTransformations provide a method of converting the coordinate into a different coordinate frame. In OpenGL these are 4 by 4 matrices. Many references describe their use; this is a quick and dirty description.

Example transformations:

Ex 1: I have a vehicle with a position in 3D space. I am looking at the vehicle from a different position, and want to draw the vehicle on a computer screen. There are 2 coordinate systems - the world (where the vehicle is) and the computer screen (x pixels, y pixels and z buffer). A matrix transform relates the world to the screen coordinate, this transformation is often thought of as a camera. Using a 4by4 matrix, the effects of perspective distortion (far things look smaller) can be modelled.

Ex 2: The vehicle moves through the world; the transformation of vehicle to world can also be modelled with a 4 by 4 matrix. Also the transformation of wheel rotation to vehicle coordinates uses a matrix, and the total transfrom from wheel to world to screen can be replaced by a single matrix. This is essentially what a graphics card provides. OpenGL provides a mechanism to define nested transformations such as those from the wheel to the world. Scene Graphs provide one means of defining how the transformations relate to definitions of shapes and polygons.

A 4 by 4 matrix operates on a 3 coordinate by standard matrix multiplication, with an 'artifical' or homogeneous coordinate suppied as the 4th element of the coordinate (in almost all cases this coordinate is 1).

v' = v*M where v is the original position, M the matrix and v' is how the position looks on screen (or in a parent coordinate system).

The matrix can be thought of as a Rotation, Scale and Translation rolled into one matrix. Then

M = S * R * T

or when applied to a coordinate the coordinate is scaled, then rotated and finally translated to its destination. In the matrix the top left 3 by 3 elements represent rotation and scaling; the bottom row represents translation (and the diagonal of the rotation represents scaling).

For most transformations you will use an orthonormal transformation - one where the distance between 2 points is the same before and after transforming. (The exception is the perspective transformation which is handled by 'cameras' 'cameragroups' and so on in Producer, so can generally be regarded as 'different'.)

See also http://www.makegames.com/3drotation/
(note from Robert Osfield, the above text refers to left hand rule, and pre multiplication, whereas as OpenGL uses right hand rule, and the OSG maths classes uses post multiplication.) (GWM I think this is now corrected to right hand, post multiply.)

A useful transformation is rotation or scaling about a position other than the origin. This is performed by 3 operations - move the object so that the pivot point is at the origin, rotate and move the object so that the pivot point is at its original location. This matrix is often written as

M = T' * R * T

T' is translation by minus the pivot point T(-Xp,-Yp,-Zp), it moves the vertex v to be relative to the origin; R is rotation or scale matrix about the origin then T moves the pivot point back to its correct location T(Xp,Yp,Zp).

This matrix only needs to be calculated once (the matrix multiplies are only done once) then applied as a single matrix to all vertices below the transformation.


by Don Burns :

The OSG matrices are treated as row-major matrices, and matrix operations use prefix notation.

So, in effect, this:

V' = M*V;

should not work. (For some reason, it seems that Robert has _made_ it work, which seems confusing as you are currently experiencing).

It should be limited to this for consistency.

V' = V * M;

So, the question is, why does OSG use this convention? The books seem to use column major, prefix notation, including the OpenGL book. However, if you read the OpenGL book then try a matrix in C you might be somewhat surprised by the result.

For example, The OpenGL book says that a A rotation around Z is, (as you describe):

    | cosA  -sinA   0    0 |
    | sinA   cosA   0    0 |
    | 0        0    1    0 |
    | 0        0    0    1 |

However, pass this matrix

    GLfloat ZrotateMatrix[][4] = {
       { cosA, -sinA, 0, 0 },
       { sinA,  cosA, 0, 0 },
       {    0,  0,    1, 0 },
       {    0,  0,    0, 1 }
    };

to glLoadMatrixf(): and see what happens (g'ahead try it, don't take my word for it). Your rotations will be the opposite of what you expect.

Likewise, the OpenGL book tells you that a translation matrix will look like this:

    | 1  0  0  Tx |
    | 0  1  0  Ty |
    | 0  0  1  Tz |
    | 0  0  0  1  |

But if you build a C matrix that looks like that, the results will be most amusing. Prove this to yourself. Write a little OpenGL program that translates an object along the X axis by 'X':

// This will work:
    GLfloat mat[][4] = {
        { 1.0, 0.0, 0.0, 0.0},
        { 0.0, 1.0, 0.0, 0.0},
        { 0.0, 0.0, 1.0, 0.0},
        {   X, 0.0, 0.0, 1.0}
    };

    glPushMatrix();
    glMultMatrixf( &mat[0][0] );
    glutSolidTeapot( 1.0 );
    glPopMatrix();

    X += 0.01;

/// But this....

    GLfloat mat[][4] = {
        { 1.0, 0.0, 0.0, X},
        { 0.0, 1.0, 0.0, 0.0},
        { 0.0, 0.0, 1.0, 0.0},
        { 0.0, 0.0, 0.0, 1.0}
    };

Likewise, when we are dealing with vector operations, the text books will use this notation:

V' = M * V

and in abbreviated long hand:

    V' =  M * |Vx|
              |Vy|
              |Vz|

Here's a challenge for you... try creating a a single column vector matrix in C or C++. :)

What is familiar to us is this:

    GLfloat vector[3] = { Vx, Vy, Vz };

Now, the difference between OSG and OpenGL when it comes to vector/matrix operations is that OpenGL doesn't have any exposed matrix or vector math. In essence you have:

    glLoadMatrix*()
    glMultMatrix*()

But the implementation of these is "under the covers" so OpenGL can have the luxury of using textbook notation when discussion matrix and vector operations in the textbook. The programmer, however, will need to transpose his matrix in his mind when he is passing a C++ array to glLoadMatrix().

In OSG we deliberately chose (at least I thought we did) early on to maintain a row-major, prefix notation of matrix operations to better match C++ arrays. This has implications for order of matrices and vector operations. I'm not sure what Robert intended by adding V' = M * V, but if I were you, I'd simply pretend it didn't exist so as to not confuse the issue.

One more note on this issue. Order of matrix operations is a bit more "comfortable" (my opinion) with row-major prefix notation. Let me explain here. Say we want to make our beloved cow, sit up and beg. In the default orientation, the cow appears standing up, facing to the right (down the X axis). To make the cow sit up and beg, we'll need to first, rotate her -PI/2 around the Z axis, so she's facing us (looking down the -Y axis), then we'll need to rotate here -PI/2 around the X axis so she's sitting, head up with her hoofs extended toward us.

With prefix notation, this is simply:

    osg::Matrix A = osg::Matrix::rotate( -M_PI*0.5, 0, 0, 1 );
    osg::Matrix B = osg::Matrix::rotate( -M_PI*0.5, 1, 0, 0 );

    osg::Matrix R = A * B;

That is, in "English" notation, and reading from left to right, "First rotate around Z (A matrix), then rotate around X (B matrix)".

However, in postfix notation, the operation would be:

    osg::Matrix R = B * A;

which might have a complex translation to the "English" notation, as it is certainly _not_ "rotate around X, then rotate around Z", as this would provide us with a view of our cow after having experienced the dubious sport of cow-tipping.


Robert adds :

In OSG we deliberately chose (at least I thought we did) early on to maintain a row-major, prefix notation of matrix operations to better match C++ arrays. This has implications for order of matrices and vector operations. I'm not sure what Robert intended by adding V' = M * V, but if I were you, I'd simply pretend it didn't exist so as to not confuse the issue.

V' = V*M and M* V are defined....

the first, as Don points out is the standard one you should use, and the later is just for special occasions...

And the special occasion is when you'd want to do V' = V* Mtranspose where you only have M to hand and would have to do a transpose of M.

This can be rewritten V' = M * V;

The particular time when you'll do this would be when transforming normals and planes by the inverse transpose. This is done all the time in the cull traversal, where the inverse just happens to be the accumulated modelview matrix.

This is all a bit crafty, but certainly helps remove all those extra ops in transposing.

--

Gerrick Bivins provides a link to the OpenGL Transformations FAQ (http://www.opengl.org/resources/faq/technical/transformations.htm)
Usenet posts by Mark Segal and Eric Haines (http://steve.hollasch.net/cgindex/math/matrix/column-vec.html)