Using Node Transform Properties

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, how they are used by 3ds Max, and how they can be accessed and manipulated in MAXScript.

The Pivot Point—The Node Transform Matrix

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:

The thing that most users think of as the pivot point is 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.

Construction of the Node Transform Matrix for the PRS Transform Controller

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, and then the scale controller. First the position controller pre-multiplies the input matrix 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.

After the position, rotation, and scale controllers have updated the matrix, it becomes the Node Transform Matrix that 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

The Object-Offset Transform Matrix provides an offset of the geometry of an object from its node.

You 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 can 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.

Construction of the Object-Offset Transform Matrix

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, and 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

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 can be used, for example, if you have a mesh object and want to get the world space coordinate of one of its vertices. You can do this by taking the vertex coordinate in object space and multiplying it by the Object Transform Matrix.

Using the Node Transforms in MAXScript

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:

  1. 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.

  2. Rotation properties and 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, 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 will 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.

  3. 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
    

    This returns a quaternion. Remember 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 will 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, but 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. As 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.

    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.objectoffsetpos*=RotValInv
       obj.objectoffsetrot*=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]