= Matrix Transformations =
[[TracNav(TracNav/SupportTOC)]]
[wiki:Support/Maths back]
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. [wiki:Support/KnowledgeBase/OpenGL 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/ [[BR]]
(note from Robert Osfield, the above text refers to left hand rule, and pre multiplication, whereas as [wiki:Support/KnowledgeBase/OpenGL 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 [wiki:Support/KnowledgeBase/OpenGL OpenGL] book. However,
if you read the [wiki:Support/KnowledgeBase/OpenGL OpenGL] book then try a matrix in C you might be somewhat
surprised by the result.
For example, The [wiki:Support/KnowledgeBase/OpenGL 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 [wiki:Support/KnowledgeBase/OpenGL 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 [wiki:Support/KnowledgeBase/OpenGL 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 [wiki:Support/KnowledgeBase/OpenGL 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) [[BR]]
Usenet posts by Mark Segal and Eric Haines (http://steve.hollasch.net/cgindex/math/matrix/column-vec.html)