..and ill show you how deep the rabbit hole goes" - Morpheus.
One of the coolest lines from one of my favorite movies, The Matrix. So why do I bring this up? Well In the process of writing Equinox I had to implement transformations, which of course means dealing with matrices and linear algebra.
I have used matrices in the past as they are one of those things that you will have to deal with as a TD, specially a shader TD. If like me, you did not have the luxury (or the debt) of a university education on computer science or engineering, then matrices might be intimidating and it might be one of those things that you avoid constantly until you are cornered and you can not dodge them any more.
As I stated earlier, matrices are a necessity in the TD world, but do not dispair, you can use them without really understanding the math theory behind them. To use matrices all you need to do is understand the output and uses of the most common matrix operations. Lets examine matrices a little closer.
What is a Matrix?
From wikipedia: "In mathematics, a matrix (plural matrices, or less commonly matrixes) is a rectangular array of numbers, symbols, or expressions. The individual items in a matrix are called its elements or entries. The horizontal and vertical lines in a matrix are called rows and columns, respectively". The following is a picture of a generic definition of a matrix.
Matrices are used a lot in day to day activities. Perhaps one of the most unseen use of matrices are spreadsheets, where if you select a large range of cells and perform an operation on them, the application is performing matrix operations.
How Are they Used in CG?
One of the most common uses of matrices in CG are transformations. Matrices, specially homogeneous matrices, are elegant data types for these kinds of operations. They are used extensively because a 4x4 matrix (homogeneous) can represent any of the most used afine transformations such as translation, rotation, scale, skew and reflection.
This means that software developers have to deal with a single "entity" to handle all their transformation needs instead of having to use vectors for translation, scalars (floats) for scales and 3x3 matrices for rotations.
We will not get into much of the math behind matrices on this tutorial, and we will not look at the different matrices used in CG transformations. There is, however, one matrix that we must cover as it is extremely important for the current tutorial to make sense. This matrix is also essential in many transformation manipulations.
Give this matrix a good look:
You see it has 0 on every spot of the matrix except for the numbers that make up the diagonal from top left to bottom right. This matrix is known as the identity matrix and it is special because it represents a transformation set to Translate(0,0,0), Rotate(0,0,0), Scale(1,1,1). So when you create an object at the origin of the world, its transformation matrix is the identity. If an object is parented to another, and its transformation is the exact same as the transformation of the parent, then the child's local matrix is also the identity.
Multiplying a matrix by its inverse will always result in the identity matrix, so to move an object to the origin you would multiply the objects world matrix by the inverse of the object's world matrix.
We will not go into the details of how matrices are calculated as I usually like to go over how something "can be used" before I go into "how something works". Let's take a look into some of the uses for matrices.
Note: All code examples are done in Blender, but the concepts should be applicable to any CG software.
Apply the transformation of an object to another
This is probably one of the simplest uses of transformation matrices. Just store the value of the source in world space and apply it to the destination object.
# # dst.matrix_world = src.matrix_world # #
Find the position of an object in relation to another
Need to find an object's position in relation to, let's say the rendering camera? Then just invert the world matrix of the camera and multiply it by the world of the object.
# # Make sure to perform this operations in this order as # A*B != B*A # camera.matrix_world.inverted() * object.matrix_world # #
Convert Z-up to Y-up Coordinate Systems.
This is something I had to deal with when writing a RenderMan exporter for 3DS Max. Back in those days, I did a lot of "juggling" to make things work. For instance, when I got the transformation of an object in the form [x,z,y], I would shift the elements to [x,y,z]. This might seem easy but once rotations, and position of vertices had to be exported, the code got a lot uglier.
I ran into the exact same issue lately when writing BtoA. I am pretty certain that the previous versions of Blender used to use Y-up, but on the latest (2.5.x) it is using Z-up. Arnold, on the other hand, allows you to specify left or right handed worlds, but they are both Y-up. If you analyze the problem, you will realize that rotating the object 90 degrees on the X axis will make Y point up. Depending if you are converting to a left or right-handed coordsys, you might need to rotate by -90 degrees. With a simple matrix multiplication, we can solve the issue.
# # Create a new matrix that is rotated -90 degrees # on the X axis # mrot90 = Matrix.Rotation(math.radians(-90),4,'X') # multiply the rotated matrix by the world matrix of # object to be exported # newMatrix = mrot90 * object.matrix_world
Maya and several other 3D apps provide a way to freeze or reset the transformations of an object. Sometimes this is very useful, especially if you want to make sure that your object has a "starting point" in the scene that is an identity transform matrix and not some random numbers. A freeze transform on a mesh can be applied by:
- Multiplying the object's world matrix to each point in the mesh. This will give the appearance that the object has moved, but its pivot point is still in the same location because we actually moved the points, not the coordinate system of the object.
- We then multiply the objects matrix by its inverse matrix to get rid of the double transformation and move the points back to their original location. Now you will see that the transformations are back to the identity.
# Capture object to an easier variable p = bpy.data.objects['Plane'] # Get the matrix and the inverse matrix m = p.matrix_world mi = m.inverted() # Apply the matrix to every point in the mesh. # This will apply a "double" transformation # to the whole mesh for i in p.data.vertices: i.co = m * i.co # Multiply the object matrix by the inverse, # officially setting the xform of the object # to the identity and getting rid of the # double transformation p.matrix_world *= mi
but wait a minute, the pivot point of the object is no longer where it was, let's take a look at how we can change the pivot point.
To center the pivot point to an object we need to follow the following steps:
- Get the center of the object's bounding box in world space (cworld)
- Find the position cworld in relation to the pivot point of the object (cwToPiv)
- Multiply every point in the mesh by the inverse of cwToPiv. This will center the mesh points to the pivot point.
- Apply cworld as the matrix of the object to move it back to its original location
# quick access variable c = bpy.data.objects['Cube'] # get the bounding box. Blender calculates it # in object space b = c.bound_box # get the min and max of the bounding box. # through exploration I see that the min is # at b and the max is at b vmin = Vector([b,b,b]) vmax = Vector([b,b,b]) # get the center of the bounding box. This # is still in object space cent = (vmin + vmax) * 0.5 # make a matrix with the center point cm = Matrix.Translation(cent) # make a new matrix of the point in world # space. cworld = c.matrix_world * cm # Get the position of cworld in relationship # to the pivot point. See above how to do this cwToPiv = c.matrix_world.inverted() * cworld # multiply every point by the inverse of # cwToPiv for i in c.data.vertices: i.co = cwToPiv.inverted() * i.co # Now apply the bounding box center # in world space to the world space # of the object c.matrix_world = cworld
Of course, we can use this technique to set the pivot point to anywhere in the bounding box of the object. All we need to do is use a little math to set the value of cent (rename the variable to be easier to read). Everything else should after that point should be the same