This topic provides information on three transforms related to nodes—the Node Transform Matrix, the Object-Offset Transform Matrix, and the Object Transform Matrix. The Node and Object-Offset Transform Matrices are stored by 3ds Max. The Object Transform Matrix is derived from these two matrices. This topic also presents information on how these transforms are constructed and how they are used by 3ds Max, and how they can be accessed and manipulated in MAXScript.
The transform center, or pivot point, is the location about which a rotation takes place, or to and from which a scale occurs. All nodes in 3ds Max have a pivot point. You can think of the pivot point as representing a node's local center and local coordinate system. The pivot point of a node is used for a number of purposes:
As the center for rotation and scaling.
Defines the transform origin for linked children.
Defines the joint location for Inverse Kinematics.
The thing that most users think of as the pivot point—graphically represented in 3ds Max by the axis tripod that is displayed when a node is selected and the coordinate system is set to local—is actually just a visual representation of the node's transform matrix.
The node's transform matrix is what is controlled by the transform controller that places the node in the scene. The transform controller controls the transform relative to the node's parent.
The PRS controller uses sub-controllers to create the final transform. There are three sub-controllers—one for Position, one for Rotation, and one for Scale. The PRS controller is passed the transform matrix of the node's parent. If the node has no parent, the matrix starts out as the identity matrix. The PRS controller first calls the position controller, then the rotation controller, then the scale controller. First the position controller pre-multiplies the input matrix is by the position value. If the matrix passed is the identity matrix, this is equivalent to setting the bottom row (the translation row) of the matrix. Next, the rotation controller pre-multiplies the matrix by the rotation value. Finally, the scale controller pre-multiplies the matrix by the scale value.
Some position controllers can actually apply more than just position to the matrix. For example, the Path Controller, when the Follow switch is active, actually applies some rotation to have the node remain tangent to the path it is following. Thus when the rotation controller receives the matrix, the matrix already has some rotation applied.
The matrix, after the position, rotation and scale controllers have updated it, becomes the Node Transform Matrix 3ds Max uses to position, rotate and scale nodes in the scene. Thus, the entire transform is:
Node Transform Matrix = Controller Scale * Controller Rotation *
Controller Position * Parent Transform Matrix
The Object-Offset Transform Matrix provides an offset of the geometry of an object from its node.
One can see a node's pivot point graphically represented in 3ds Max by selecting a node, going to the Hierarchy branch of the Hierarchy panel, selecting the 'Pivot' button, and choosing either the 'Affect Pivot Only' or 'Affect Object Only' button. This displays a large axis tripod that shows the location and orientation of the node's pivot point. By choosing one of these buttons, and using the Move/Rotate/Scale toolbar controls, a user can manipulate the position of the geometry of the object independent of the pivot point. Or they may manipulate the pivot point independent of the geometry of the object.
The way the user is able to independently manipulate the pivot and the object is managed internally using the Object-Offset Transform Matrix. The Object-Offset Transform Matrix affects how the geometry of the object is related to the node. The Object-Offset Transform Matrix transforms the geometry of the object itself some amount from the node.
To understand how the Object-Offset Transform Matrix is used, consider the following example from the 3ds Max user interface. In the Hierarchy branch under 'Pivot', when the user has chosen the 'Affect Object Only' button they are free to move the geometry of the object around independent of the node. The pivot point does not move—only how the geometry of the object is oriented relative to the pivot. What is happening internally is that the Object-Offset Transform Matrix is being manipulated. This transform is simply an additional offset that is applied to the geometry of the object that is independent of the node. The Object-Offset Transform Matrix is not inherited by any child nodes.
As another example consider the use of the 'Affect Pivot Only' button. This mode lets the user move the pivot without affecting the position of the geometry of the object. When the user moves the pivot point, what is actually happening is the Node Transform Matrix is being altered (to re-orient the pivot point), then the Object-Offset Transform Matrix is adjusted to counter the node transform. This lets the geometry of the object stay in the same place while the pivot point moves around.
The Object-Offset Transform Matrix consists of separate position, rotation and scale transforms. Like the Node Transform Matrix, these are applied by pre-multiplying position, then rotation, then scale. Thus the Object-Offset Transform Matrix is:
Object-Offset Transform Matrix = Offset Scale * Offset Rotation * Offset Position
Unlike the Node Transform Matrix, the Object-Offset Transform Matrix is not inherited by children of the node.
The Object Transform Matrix is the transform matrix an object's geometry needs to be multiplied by to transform it into world space. This transform matrix includes the parent transform, the Node Transform, and the Object Offset Transform. Thus, the entire transform used to transform the points of any object is:
Object Transform Matrix = Object-Offset Transform Matrix * Node Transform Matrix
This matrix could be used, for example, if you have a mesh object and wanted to get the world space coordinate of one of its vertices. You could do this by taking the vertex coordinate in object space and multiplying it by the Object Transform Matrix.
The node transform properties are derived from and modify the values of the transform controllers associated with the node, they are not associated directly with the node's main transformation matrix. This has a number of consequences:
Assigning to a transform property (such as pos or rotation) with animation enabled plants keyframes only in the associated controller. If you modify the node's transform matrix directly, keyframes are generated for all the transform controllers for that node.
Rotation properties and in fact all rotation-related functions in MAXScript reflect the direction convention used in the 3ds Max user interface. This convention is the right-hand rule, namely, positive angles rotate counter-clockwise about positive axes. Internally, however, 3ds Max stores rotations in node matrices using the left-hand rule. It is important to remember this inversion when working directly with node transform matrices using the transform and objectTransform properties. You'll need to invert rotations (for example, by multiplying axes by [-1,-1,-1] or using the inverse() function on quaternions) when mixing them with 3ds Max and MAXScript standard rotations.
Certain controllers, such as the path controller with bank or follow enabled, affect the rotation of a node by adjusting the node's transform matrix directly and this rotation is not reflected in rotation controller values. This means, in this example for instance, that bank and follow rotations are not directly accessible through the rotation property, you need to access them directly in the node's transformation matrix using something like:
r = $foo.transform.rotationPart
which returns a quaternion. Remember, however, that the 3ds Max rotation direction convention is not reflected when directly accessing a transform matrix, so to use this value in other rotation operations you would need to invert it:
$baz.rotation = inverse r
A node's transform property contains the Node Transform Matrix, and reflects the position, rotation, and scale of the node's pivot point. Accessing the pivot property will return the same value as the pos property. Setting the pivot property will move the pivot point location to the specified coordinates, however the node's geometry will not be moved.
A node's objectoffsetpos, objectoffsetrot, and objectoffsetscale properties contain the component parts of the Object-Offset Transform Matrix. A node's objecttransform property contains the Object Transform Matrix. Since this matrix is a combination of the Node Transform and Object-Offset Transform Matrices, this property is read only. Note that these properties are always returned in the World coordinate system context.
The following script shows the initial node transform properties for a 25x25x25 box created at [75,75,0], after moving the pivot point to [50,50,0], and after rotating only the pivot point 35 degrees about the Z axis. Also shown is the position of one of the box's vertices after each transform.
Script
fn DumpXForms obj =
( -- output node transform properties
format "%:\t%\n" "transform" obj.transform
format "%:\t%\n" "position " obj.pos
format "%:\t%\n" "rotation " obj.rotation
-- output node's pivot point location
format "%:\t%\n" "pivot " obj.pivot
-- output object offsets
format "%:\t%\n" "objectoffsetpos " obj.objectoffsetpos
format "%:\t%\n" "objectoffsetrot " obj.objectoffsetrot
format "%:\t%\n" "objectoffsetscale" obj.objectoffsetscale
-- output object transform
format "%:\t%\n" "objecttransform " obj.objecttransform
-- output vertex position in local and world coordinates
format "%:\t%\n" "vert 1 (local) " (in coordsys local getvert obj 1)
format "%:\t%\n" "vert 1 (world1) " (in coordsys world getvert obj 1)
-- calculate and output vertex position in world coordinates
local v_pos=(in coordsys local getvert obj 1)* obj.objecttransform
format "%:\t%\n" "vert 1 (world2) " v_pos
)
-- define function for rotating only the pivot point
fn RotatePivotOnly obj rotation= ( local rotValInv=inverse (rotation as quat)
animate off in coordsys local obj.rotation*=RotValInv
obj.objectoffsetrot*=RotValInv
obj.objectoffsetpos*=RotValInv
)
--
(
b=box pos:[75,75,0] -- create a 25x25x25 box, vertex 1 at [62.5,62.5,0] (world)
convertToMesh b -- convert box to mesh so we can access the vertex location
DumpXForms b -- print transforms
b.pivot=[50,50,0] -- move pivot only to [50,50,0]
DumpXForms b -- print transforms
RotatePivotOnly b (EulerAngles 0 0 35) -- rotate pivot only 35 degrees about local Z
DumpXForms b -- print transforms
)
Output
DumpXForms() -- function definition
RotatePivotOnly() -- function definition
transform: (matrix3 [1,0,0], [0,1,0], [0,0,1], [75,75,0]) -- initial transforms
position : [75,75,0]
rotation : (quat 0 0 0 1)
pivot : [75,75,0]
objectoffsetpos : [0,0,0]
objectoffsetrot : (quat 0 0 0 1)
objectoffsetscale: [1,1,1]
objecttransform : (matrix3 [1,0,0], [0,1,0], [0,0,1], [75,75,0])
vert 1 (local) : [-12.5,-12.5,0]
vert 1 (world1) : [62.5,62.5,0]
vert 1 (world2) : [62.5,62.5,0]
transform: (matrix3 [1,0,0], [0,1,0], [0,0,1], [50,50,0]) -- transforms after move
position : [50,50,0]
rotation : (quat 0 0 0 1)
pivot : [50,50,0]
objectoffsetpos : [25,25,0]
objectoffsetrot : (quat 0 0 0 1)
objectoffsetscale: [1,1,1]
objecttransform : (matrix3 [1,0,0], [0,1,0], [0,0,1], [75,75,0])
vert 1 (local) : [-12.5,-12.5,0]
vert 1 (world1) : [62.5,62.5,0]
vert 1 (world2) : [62.5,62.5,0]
transform: (matrix3 [0.819152,0.573576,0], [-0.573576,0.819152,0], [0,0,1], [50,50,0]) -- transforms after rotate
position : [50,50,0]
rotation : (quat 0 0 0.300706 0.953717)
pivot : [50,50,0]
objectoffsetpos : [34.8182,6.13939,0]
objectoffsetrot : (quat 0 0 0.300706 0.953717)
objectoffsetscale: [1,1,1]
objecttransform : (matrix3 [1,0,0], [0,1,0], [0,0,1], [75,75,0])
vert 1 (local) : [-12.5,-12.5,0]
vert 1 (world1) : [62.5,62.5,0]
vert 1 (world2) : [62.5,62.5,0]