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

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 aCINEMA 4Dobject. 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 4Dwill 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