This page will guide you through some of the most important uses of matrices in 3D graphics.
A matrix is basically an array of number arranged in a rectangular pattern. The matrices used in Cinema 4D,
represented by the class
Matrix, consist of three rows and four columns, i.e. they
have the dimension 3x4. The first row is always “1, 0, 0, 0”, which means that there are 12 numbers that
are used. These numbers are grouped into four vectors, one for the remaining numbers in each column. The
four vectors are called off, v1, v2 and v3:
Together these four vectors can be used to represent the coordinate system for a Cinema 4D object. A coordinate system consists of three axes, one for each coordinate (X, Y and Z). The system also has a base position, from which the three axes originate. This base position is stored in off, and the three axis vectors are stored in v1, v2 and v3.
A matrix is enough to store all the three important properties of an object: position, rotation and scale. (The position is stored in the first vector, the rotation is given by the direction of the three axis vectors and the scale is given by their length.) Therefore each object has a matrix that you can get with
This surely seems like a convenient way to group position, rotation and scale. But
BaseObject.SetRelScale() work just fine already. Why do we need to use matrices?
To compare two objects in different hierarchies, what we need is a way to get their global coordinates. This is where matrix multiplication comes in. Multiplying two matrices is not just a matter of multiplying the corresponding numbers; it is much more complicated than that. In fact, matrix multiplication is not even commutative: a*b is not the same as b*a. Fortunately you do not have to know how to multiply two matrices to do it.
If matrix Mp is the parent’s matrix, and Mc is the child’s matrix, then the product Mp*Mc gives a matrix that represents the child’s position and rotation, but in the coordinate system of the parent. This means that getting a child’s global matrix is just a matter of multiplying it with each of its parents’ matrices.
Fortunately you do not have to do this multiplication yourself. Each object has these predefined functions:
GetMl() # Returns the matrix in local coordinates GetMg() # Returns the matrix in global coordinates GetUpMg() # Returns the parent's matrix in global coordinates
For example, if you have an object named ‘MyObject’ that’s hidden deep within the object hierarchy, then the following code will print its global position:
obj = doc.SearchObject("MyObject") mat = obj.GetMg() # the global matrix globalPos = mat.off #get the position from the matrix print "Global pos= ", globalPos
Since matrix contains the global matrix, the position stored in off will be the global position. Easy!
To calculate the distance between two objects in different hierarchies, just use their global positions:
p1 = op1.GetMg().off p2 = op2.GetMg().off dist = (p2-p1).GetLength()
If you remember to always do your calculations in global world coordinates, then your users will never get any unforseen problems with hierarchies.
What if you have a position in global coordinates and want to move an object to that position? Then you need a transformation back from global coordinates to the object’s local position coordinates. Mathematically this is done by multiplication by the global matrix’ inverse. If Mg is a matrix in global space and Mag is object a’s global matrix, then Mag-1*Mg equals the first matrix’ in a’s local coordinates.
Fortunately you do not have to do this manually either, since these functions do it for you:
SetMl(ml) # Sets the object's local matrix SetMg(mg) # Sets the object's global matrix
If you use
BaseObject.SetMg(), Cinema 4D will automatically transform the matrix to the local coordinate system before setting it.
For example if you wanted to write a hierarchy safe midpoint plugin you could do like this:
p1 = op1.GetMg().off # The global position of the first object p2 = op2.GetMg().off # The global position of the second object mid = (p1 + p2) / 2.0 # The midpoint (in global coordinates still) m = obj.GetMg() # The global matrix of the object to place in between m.off = mid # Set its position to the midpoint (global coordinates) obj.SetMg(m) # Sets the global matrix of op to m, placing it between op1 and op2
Rotation and Scale¶
OK, so the position was easy to get with Matrix.off. But what about the rotation and the scale. How do we get and set the global rotation and scale from a global matrix? The answer is that this is slightly more complicated, but that one can easily write functions that make it easy.
Getting the scale is just a matter of measuring the length of each of the axis vectors:
m = obj.GetMg() scale = c4d.Vector( m.v1.GetNormalized(), m.v2.GetNormalized(), m.v3.GetNormalized())
Setting the scale is done by normalizing each vector and the multiplying it with the wanted scale:
m = obj.GetMg() m.v1 = m.v1 * scale.x m.v2 = m.v2 * scale.y m.v3 = m.v3 * scale.z obj.SetMg(m)
To get the rotation from a matrix there is the member function
m = obj.GetMg() rot = utils.MatrixToHPB(m)
Setting the rotation is done with the member function
Please remember, Cinema 4D handles rotation in radians. The rotation must be in radians.
Unfortunately this function returns the position and scale, so those need to be stored away:
m = obj.GetMg() pos = m.off scale = c4d.Vector( m.v1.GetLength(), m.v2.GetLength(), m.v3.GetLength()) m = utils.HPBToMatrix(rot) m.off = pos m.v1 = m.v1.GetNormalized() * scale.x m.v2 = m.v2.GetNormalized() * scale.y m.v3 = m.v3.GetNormalized() * scale.z obj.SetMg(m)
Collecting everything we have learned so far we can write these six convenience functions for setting and getting global position, rotation and scale data:
GetGlobalPosition(obj) # Returns the global position of obj GetGlobalRotation(obj) # Returns the global rotation of obj GetGlobalScale(obj) # Returns the global scale of obj SetGlobalPosition(obj, pos) # Sets the global position of obj to pos SetGlobalRotation(obj, rot) # Sets the global rotation of obj to rot SetGlobalScale(obj, scale) # Sets the global scale of obj to scale def GetGlobalPosition(obj): return obj.GetMg().off def GetGlobalRotation(obj): return utils.MatrixToHPB(obj.GetMg()) def GetGlobalScale(obj): m = obj.GetMg() return c4d.Vector( m.v1.GetLength(), m.v2.GetLength(), m.v3.GetLength()) def SetGlobalPosition(obj, pos): m = obj.GetMg() m.off = pos obj.SetMg(m) def SetGlobalRotation(obj, rot): """ Please remember, Cinema 4D handles rotation in radians. Example for H=10, P=20, B=30: import c4d from c4d import utils #... hpb = c4d.Vector(utils.Rad(10), utils.Rad(20), utils.Rad(30)) SetGlobalRotation(obj, hpb) #object's rotation is 10, 20, 30 """ m = obj.GetMg() pos = m.off scale = c4d.Vector( m.v1.GetLength(), m.v2.GetLength(), m.v3.GetLength()) m = utils.HPBToMatrix(rot) m.off = pos m.v1 = m.v1.GetNormalized() * scale.x m.v2 = m.v2.GetNormalized() * scale.y m.v3 = m.v3.GetNormalized() * scale.z obj.SetMg(m) def SetGlobalScale(obj, scale): m = obj.GetMg() m.v1 = m.v1.GetNormalized() * scale.x m.v2 = m.v2.GetNormalized() * scale.y m.v3 = m.v3.GetNormalized() * scale.z obj.SetMg(m)
The Matrix Has You¶
Single precision matrix mathematics - A matrix is basically an array of number arranged in a rectangular pattern. The matrices used in Cinema 4D, represented by the class Matrix, consist of three rows and four columns, i.e. they have the dimension 3x4. The first row is always “1, 0, 0, 0”, which means that there are 12 numbers that are used. These numbers are grouped into four vectors, one for the remaining numbers in each column. The four vectors are called off, v1, v2 and v3, together these four vectors can be used to represent the coordinate system for a Cinema 4D object. A coordinate system consists of three axes, one for each coordinate (X, Y and Z). The system also has a base position, from which the three axes originate. This base position is stored in off, and the three axis vectors are stored in v1, v2 and v3 respectively. Also check out Matrix Fundamentals. So now we can follow the white rabbit - knock knock - neo.
Cinema 4D uses a left-handed coordinate system.
Here is a small example how to use the type Matrix
v_1 = Vector(30) v_2 = Vector(1.5, 9.87, 2) v_3 = Vector(0) off = Vector() #Vector with components: 0 mat = Matrix(off, v_1, v_2, v_3) #create new instance of Matrix print mat.v1.x #return 30
To understand what’s going on I’ll quickly introduce you to Freeze Transformations.
For (character) animation it is important that you can define rest poses. Think e.g. of the typical CG human that has arms and legs stretched. Most objects carry all kinds of complicated position, scale and rotation information. Freeze Transformations allow you to “freeze” this information, but reset position/rotation to 0.0 and scale to 1.0. This is great as it allows you at any time to go back to the original - just reset all position and angle information to 0.0 - and your object has reached its rest state again. You could achieve the same by adding for every single object in your hierarchy a parent null object, but Freeze Transformations let you do this without the need to change the hieararchical structure. Technically however you can think of it as a “parent null object” without the actual body of a physical object.
All parts of the application that deal with keyframing only access values without the freeze transformation part. This way you can conveniently animate (the rest pose is always 0.0 for position and rotation) and avoid rotation singularities. The correct routines for animation and keyframing are labeled “Rel”.
Other parts of the application however (e.g. like a target expression) need to get the absolute local position of a target - the position that consists of frozen and regular matrix. For those purposes you need to use the routines that are labeled “Abs”.
To clarify: as long as an object has no Freeze transformation assigned (which is always the default case when you create a new object) there is no difference between the Abs or Rel. So e.g. after creating an object it doesn’t matter which routine you choose. If you however want to read the position of an object (and that object uses freeze transformations) there is a difference.
After so much theory a practical example. Take a look at the cube and null object in the picture below. The null object has a position of (0/200/0) and rotation of (0°/45°/0°). The cube has a position of (0/100/0). You can see to the left its global position in space.
Now let’s try to do the same with one single object:
You can see that the Null object’s position / rotation have been transferred to the Cube’s frozen position and frozen rotation. The result is exactly the same as before, just without the need for an extra null object.
Now if you call cube.GetAbsPos the return value will be (0/270.711/-70.711). When you call cube.GetRelPos however you’ll get (0/100/0) as a result. The coordinate manager offers exactly those two display modes: Object (Abs) and Object (Rel).
As copying position, rotation and scale data from one object to another is much more complex than before there is a new routine
BaseObject.CopyMatrixTo() that copies all position, rotation and scale data (including the frozen states) over to another object.
Let’s take a look at the mathematical background: In Cinema 4D the matrix multiplication order for two matrices A and B
[C] = [A] * [B]
is defined as the rule “A after B”… so the following two lines are equivalent
transformed_point = original_point * [C] transformed_point = (original_point * [B]) * [A]
(the original point is first transformed by matrix [B] and then later by [A])
An object’s global matrix is calculated this way
[Global Matrix] = [Global Matrix of Parent] * [Local Matrix]
[Global Matrix] = op.GetUpMg() * op.GetMl()
To calculate the local matrix of an object the formula is
[Local Matrix] = [Frozen Translation] * [Frozen Rotation] * [Relative Translation] * [Relative Rotation] * [Frozen Scale] * [Relative Scale]
[Local Matrix] = MatrixMove(op.GetFrozenPos()) * HPBToMatrix(op.GetFrozenRot()) * MatrixMove(op.GetRelPos()) * HPBToMatrix(op.GetRelRot()) * MatrixScale(op.GetFrozenScale) * MatrixScale(op.GetRelScale())
[Local Matrix] = MatrixMove(op.GetAbsPos()) * HPBToMatrix(op.GetAbsRot()) * MatrixScale(op.GetAbsScale)
Please note that the both scales are applied at the very beginning, so the local matrix is not the same as [Frozen Matrix] * [Relative Matrix]. This is necessary in order to guarantee that your local matrix always stays a rectangular system; distorted sy