Index : Tutorials :

Using matrices

This tutorial will guide you through some of the most important uses of matrices in 3D graphics.

Contents

Fundamentals
Mastering hierarchies
Going backwards
Rotation and scale
Points and vectors
Transformations
Custom matrices

Fundamentals

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 four rows and four columns, i.e. they have the dimension 4x4. 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 v0, 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 v0, and the three axis vectors are stored in v1, v2 and v3 respectively:

Vector Contains
v0 position
v1 X-axis
v2 Y-axis
v3 Z-axis

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 obj->GetMl().

This surely seems like a convenient way to group position, rotation and scale. But obj->SetRotation() and obj->SetScale() work just fine already. Why do we need to use matrices?

The answer is: to handle object hierarchies. If you want to compare the position of two objects that are both deeply hidden within several layers of parents, obj->GetPosition() won't help much; it'll only give you their local coordinates. The next section will deal with this very problem.

Mastering hierarchies

If we only look at the coordinate systems, which is what matrices are all about, then object hierarchies is only a matter of placing coordinate systems within each other. If I rotate the parent's coordinate system the child's will follow. This is because the child's matrix is given in local coordinates, just as you can set the Coordinate Manager to display the local coordinates of an object.

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 isn't just a matter of multiplying the corresponding numbers; it's much more complicated than that. In fact, matrix multiplication isn't even commutative: a*b isn't the same as b*a. Fortunately you don't 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 don't 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 it's global position:
  
  var obj = doc->FindObject("MyObject");
  var matrix = obj->GetMg(); // Get the global matrix
  var globalPos = matrix->GetV0(); // Get the position from the matrix
  println("Global pos = ", globalPos);
  

Since matrix contains the global matrix, the position stored in v0 will be the global position. Easy!

To calculate the distance between two objects in different hierarchies, just use their global positions:
  
  var p1 = op1->GetMg()->GetV0();
  var p2 = op2->GetMg()->GetV0();
  var dist = vlen(p2 - p1);
  

If you remember to always do your calculations in global world coordinates, then your users will never get any unforseen problems with hierarchies.

Going backwards

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 don't 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 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:
  
  var p1 = op1->GetMg()->GetV0(); // The global position of the first object
  var p2 = op2->GetMg()->GetV0(); // The global position of the second object
  
  var mid = (p1 + p2) / 2.0; // The midpoint (in global coordinates still)
  
  var m = op->GetMg(); // The global matrix of the object to place in between
  m->SetV0(mid);       // Set its position to the midpoint (global coordinates);
  
  op->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 m->GetV0(). 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:
  
  var m = op->GetMg();
  var scale = vector(vlen(m->GetV1()),
                     vlen(m->GetV2()),
                     vlen(m->GetV3()));
  

Setting the scale is done by normalizing each vector and the multiplying it with the wanted scale:
  
  var m = op->GetMg();
  m->SetV1(vnorm(m->GetV1()) * scale.x);
  m->SetV2(vnorm(m->GetV2()) * scale.y);
  m->SetV3(vnorm(m->GetV3()) * scale.z);
  op->SetMg(m);
  

To get the rotation from a matrix there is the member function GetHPB():
  
  var m = op->GetMg();
  var rot = m->GetHPB();

Setting the rotation is done with the member function SetRotHPB(). Unfortunately this function overwrites the position and scale, so those need to be stored away:
  
  var m = op->GetMg();
  var pos = m->GetV0();
  var scale = vector(vlen(m->GetV1()),
                     vlen(m->GetV2()),
                     vlen(m->GetV3()));
                    
  m->SetRotHPB(rot);
  
  m->SetV0(pos);
  m->SetV1(vnorm(m->GetV1()) * scale.x);
  m->SetV2(vnorm(m->GetV2()) * scale.y);
  m->SetV3(vnorm(m->GetV3()) * scale.z);
  
  op->SetMg();
  

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
  
  GetGlobalPosition(obj)
  {
    return obj->GetMg()->GetV0();
  }
  
  GetGlobalRotation(obj)
  {
    return obj->GetMg()->GetHPB();
  }
  
  GetGlobalScale(obj)
  {
    var m = obj->GetMg();
    return vector(vlen(m->GetV1()),
                  vlen(m->GetV2()),
                  vlen(m->GetV3()));
  }
  
  SetGlobalPosition(obj, pos)
  {
    var m = obj->GetMg();
    m->SetV0(pos);
    obj->SetMg(m);
  }
  
  SetGlobalRotation(obj, rot)
  {
    var m = obj->GetMg();
    var pos = m->GetV0();
    var scale = vector(vlen(m->GetV1()),
                       vlen(m->GetV2()),
                       vlen(m->GetV3()));
                      
    m->SetRotHPB(rot);
    
    m->SetV0(pos);
    m->SetV1(vnorm(m->GetV1()) * scale.x);
    m->SetV2(vnorm(m->GetV2()) * scale.y);
    m->SetV3(vnorm(m->GetV3()) * scale.z);
    
    obj->SetMg();
  }
    
  SetGlobalScale(obj, scale)
  {
    var m = obj->GetMg();
    
    m->SetV1(vnorm(m->GetV1()) * scale.x);
    m->SetV2(vnorm(m->GetV2()) * scale.y);
    m->SetV3(vnorm(m->GetV3()) * scale.z);
    
    obj->SetMg(m);
  }
  


Feel free to use these functions in your own code.

Points and vectors

Now you know how to use object matrices to work seamlessly with objects in different hierarchies. But the uses of matrices doesn't stop with just position, rotation and scale data. What if you wanted to move an object to the first point of a spline?

The solution is to use multiplication between a matrix and a point. If M is the global matrix of an object and p is a point in that object, then M*p is the point in global coordinates. This multiplication is done with the GetMulP() function:
  
  var p = spline->GetPoint(0); // Get the first point
  var globalP = spline->GetMg()->GetMulP(p); // Transform it to global coordinates
  

And the rest is trivial using the functions we created previously:
  
  SetGlobalPosition(obj, globalP);
  

What if we want to do the reverse, move the point to the object? First we would get the global position of the object:
  
  var globalP = GetGlobalPosition(obj);
  

Then we'd have to use the inverted matrix. If p is a global point and M is the global matrix of an object, then M^(-1) * p is p in the object's local coordinates (M^(-1) means M inverted):
  
  var m = spline->GetMg();
  m->Invert(); // Now we can use m backwards
  
  var p = m->GetMulP(globalP); // Transform the global point to a local
  spline->SetPoint(0, p); // Set the local point
  

If you want to transform vectors instead of points, just use the GetMulV() function instead.

Transformations

Instead of just using object matrices to transform, you can use these member functions of the Matrix class to build your own transformation matrices:
  
  SetTrans(t); // Moves by t
  SetScale(s); // Scales by s
  SetRotX(w);  // Rotates around X by w
  SetRotY(w);  // Rotates around Y by w
  SetRotZ(w);  // Rotates around Z by w
  
  SetRotHPB(hpb); // Rotates using hpb angles
  SetRotAxis(axis, w); // Rotates around axis by w
  

For example, this code will rotate a point, vector and matrix around the Y axis by 20:
  
  var m = new(Matrix);
  m->SetRotY(Radians(20.0));
  
  var newP = m->GetMulP(oldP);
  var newV = m->GetMulV(oldV);
  var newM = m->GetMulM(oldM);
  

Custom matrices

You might even sometimes want to build your own matrix. For example, if you want to map a 2D sine wave function, so that it goes between two points in world space, you would do like this:
  
  // Get the points
  var p1 = doc->FindObject("First Object")->GetMg()->GetV0();
  var p2 = doc->FindObject("Second Object")->GetMg()->GetV0();
  
  // Construct the matrix
  var m = new(Matrix);
  
  m->SetV0(p1); // The base is at the first point
  m->SetV1(vnorm(p2 - p1)); // The X-axis points towards the second point
  m->SetV2(vnorm(vcross(p2-p1, vector(0,1,0)))); // The Y-axis is perpendicular to the X-axis
  // m->SetV3() isn't needed, as the function is 2D
  
  // Print out the transformed sine coordinates
  var totLength = vlen(m->GetV1());
  var deltaX = totLength / 100.0;
  var x;
  
  for (x = 0.0; x < totLength; x += deltaX)
  {
    // Calculate the function
    var p = vector(x, sin(x / 10.0), 0.0);
    
    // Transform the point
    var newP = m->GetMulP(p);
    
    // Print it (or set a spline point, whatever)
    println(newP);
  }
  


Copyright 2014 MAXON. See Plugin Cafe for the latest version. Last modified: 2014-10-03