Freeze Transformation

Table of Contents

Since R12 Cinema 4D uses Freeze Transformation and BaseObject no longer has members like GetPos(), but instead twice as many: BaseObject::GetRelPos() and BaseObject::GetAbsPos().

For character animation it is important to be able to 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 Transformation allows to "freeze" this information, but reset position/rotation to 0.0 and scale to 1.0.
This is great as it allows at any time to go back to the original (reset all position and angle information to 0.0) and object has reached its rest state again.

The same could be achieved by adding for every single object in a hierarchy a parent null object, but Freeze Transformation let do this without the need to change the hierarchical structure.
Technically however 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 it is possible to 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.
For those purposes use the routines that are labeled "Abs".

As long as an object has no Freeze Transformation assigned (which is always the default case when a new object is created) there is no difference between "Abs" or "Rel".
So e.g. after calling BaseObject::Alloc() it does not matter which routine is called.

BaseObject::GetMl()/BaseObject::SetMl() and BaseObject::GetMg/BaseObject::SetMg always include the frozen information, thus are "Abs" versions.

Example

After so much theory a practical example. Take a look at the Null object and Cube in the picture below.
The Null object has a position of (0/200/0) and rotation of (0°/45°/0°). Cube has a position of (0/100/0).

freezetransformation_null.png

The same with one single object:

freezetransformation.png

The Null object's position and 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.

cube->GetAbsPos() returns (0/270.711/-70.711) and cube->GetRelPos() returns (0/100/0). The Coordinate Manager offers exactly those two display modes: Object (Abs) and Object (Rel).

If BaseObject::GetParameter() and BaseObject::SetParameter() are used to access the position the same distinction exists: there is ID_BASEOBJECT_REL_POSITION and ID_BASEOBJECT_ABS_POSITION (along with ID_BASEOBJECT_FROZEN_POSITION).

To copy position, rotation and scale data from one object to another use BaseObject::CopyMatrixTo() that copies all position, rotation and scale data (including the frozen states).

Mathematical Background

Let's take a look at the mathematical background.
In Cinema 4D the matrix multiplication order for two matrices A and B is defined as the rule "A after B":

[C] = [A] * [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]

or

Matrix mg = 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]

or

Matrix ml = MatrixMove(op->GetFrozenPos()) * HPBToMatrix(op->GetFrozenRot()) * MatrixMove(op->GetRelPos()) * HPBToMatrix(op->GetRelRot()) * MatrixScale(op->GetFrozenScale) * MatrixScale(op->GetRelScale());

or

Matrix ml = MatrixMove(op->GetAbsPos()) * HPBToMatrix(op->GetAbsRot()) * MatrixScale(op->GetAbsScale);
Note
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 the local matrix always stays a rectangular system; distorted systems are too complicated to handle.