World Space Modifiers and Object Transformations

When world space modifiers are applied to an object the points of the object may have already been transformed into world space. In this case, the points should not be transformed into world space again when they are drawn.

This is handled in the 3ds Max SDK by storing some state information with the node. Before the system calls BaseObject::Display(), BaseObject::itTest(), etc. it sets a flag on the node. The flag indicates if the object has already been transformed into world space. The INode::GetObjectTM() method looks at this flag to determine the proper matrix to return. In this way, when INode::GetObjectTM() is called, it returns the matrix the object needs to be multiplied by in order to get into world space. If the object is already in world space it will return the identity matrix. If it's not in world space, it will return the matrix to get it there. So all any objects need to do in their BaseObject::Display() methods is call INode::GetObjectTM() and use whatever matrix is returned.

There may be times when a developer needs to access the full object transformation matrix regardless of whether the points of the object have been transformed into world space already. For example, if a developer was creating a utility plug-in to align two objects. In this case, the developer would need to get the node transformation matrix and the object offset transformation matrix. It would not matter if the points of the object had already been transformed into world space. In this case INode::GetObjectTM() would not work. This is because it returns the identity matrix if the object is already in world space.

To solve this problem 3ds Max provides two other methods:

Example

Using the methods INode::GetObjTMBeforeWSM() and INode::GetObjTMAfterWSM() a developer has complete access to any transformation matrix they require. Below is a code example that uses all these methods. This function computes the bounding box of the first object in the current selection set at the current time. It removes anything but scaling from the object transformation matrix. In this way rotation of the node will not affect the bounding box.

To do this we first need to determine if the object is in world space or in object space. Since we are after the object space bounding box (and will later apply scaling) we need to convert the transformation matrix back into object space if it is in world space. To check if the object is in world space we call INode::GetObjTMAfterWSM(). If this matrix is the identity we know we are in world space. This is because when the points of the object get transformed by the ObjectState transformation matrix to put them into world space the ObjectState TM is set to the identity.

If the object is in world space we need to compute the object space transformation matrix. We can do this by taking the inverse of the world space transformation matrix. If the object is not in world space we just need to get its object TM by calling INode::GetObjectTM().

Once we have the object space transformation matrix we want to extract just the scaling portion of the matrix. 3ds Max provides a set of APIs that make this easy. This is done by calling decomp_affine(). This function decomposes a matrix into its translation, rotation and scaling components. See Structure AffineParts for more details.

Once we have the scaling portion of the matrix we can get the bounding box and apply the scaling by calling GetDeformBBox().

void Utility::ComputeBBox(Interface *ip)
{
   if (ip->GetSelNodeCount())
   {
     INode *node = ip->GetSelNode(0);
     Box3 box; // The computed box
     Matrix3 mat; // The Object TM
     Matrix3 sclMat(1); // This will be used to apply the scaling
 
     // Get the result of the pipeline at the current time
     TimeValue t = ip->GetTime();
     Object *obj = node->EvalWorldState(t).obj;
 
     // Determine if the object is in world space or object space
     // so we can get the correct TM. We can check this by getting
     // the Object TM after the world space modifiers have been
     // applied. It the matrix returned is the identity matrix the
     // points of the object have been transformed into world space.
     if (node->GetObjTMAfterWSM(t).IsIdentity())
     {
      // It's in world space, so put it back into object
      // space. We can do this by computing the inverse
      // of the matrix returned before any world space
      // modifiers were applied.
      mat = Inverse(node->GetObjTMBeforeWSM(t));
     }
     else
     {
      // It's in object space, get the Object TM.
      mat = node->GetObjectTM(t);
     }
 
     // Extract just the scaling part from the TM
     AffineParts parts;
     decomp_affine(mat, &parts);
     ApplyScaling(sclMat, ScaleValue(parts.k*parts.f, parts.u));
 
     // Get the bound box, and affect it by just
     // the scaling portion
     obj->GetDeformBBox(t, box, &sclMat);
 
     // Show the size and frame number
     float sx = box.pmax.x-box.pmin.x;
     float sy = box.pmax.y-box.pmin.y;
     float sz = box.pmax.z-box.pmin.z;
 
     TSTR title;
     title.printf(_T("Result at frame %d"), t/GetTicksPerFrame());
     TSTR buf;
     buf.printf(_T("The size is: (%.1f, %.1f, %.1f)"), sx, sy, sz);
     MessageBox(NULL, buf, title, MB_ICONINFORMATION|MB_OK);
   }
}