Procedural controllers compute their value algorithmically rather than interpolate between stored keyframes. Developers may need to retrieve values from these controllers but won't be able to use the IKeyControl interface since no keys are available. In these cases, one must sample the controller to retrieve its value at each frame required. This is done by calling the Control::GetValue() method directly. The following sample code samples the controller for each frame in the current animation interval and DebugPrint()s the values to the VC++ IDE debug window. Note how the parent matrix of the node is passed into the GetValue() call. This allows GetValue() to update the appropriate matrix. In the code below, the position coordinates printed by DebugPrint() will be in world space.
voidUtility::SampleController(INode *n, Control *c) { TimeValue t; Point3 trans; Matrix3 pmat; Interval ivalid; inttpf = GetTicksPerFrame(); ints = ip->GetAnimRange().Start()/tpf, e = ip->GetAnimRange().End()/tpf; // Sample the controller at every frame in the anim range for (int f = s; f <= e; f++) { t = f*tpf; ivalid = FOREVER; pmat = n->GetParentTM(t); c->GetValue(t, &pmat, ivalid, CTRL_RELATIVE); trans = pmat.GetTrans(); DebugPrint(_T("\nPosition at frame: %d of %d=(%.1f, %.1f, %.1f)"), f, e, trans.x, trans.y, trans.z); } } // Display the data of the controller. For TCB, Bezier and Linear // keyframe PRS controllers the position, rotation and scale values // are displayed in the local space of the controller. For // procedural controllers (or those that don't support the // IKeyControl interface), position data is displayed in world space. voidUtility::KeyTest() { inti, numKeys; INode *n; Control *c; Quat newQuat, prevQuat; IKeyControl *ikeys; ITCBPoint3Key tcbPosKey; ITCBRotKey tcbRotKey; ITCBScaleKey tcbScaleKey; IBezPoint3Key bezPosKey; IBezQuatKey bezRotKey; IBezScaleKey bezScaleKey; ILinPoint3Key linPosKey; ILinRotKey linRotKey; ILinScaleKey linScaleKey; // Get the first node in the selection set if (!ip->GetSelNodeCount()) return; n = ip->GetSelNode(0); // --- Process the position keys --- c = n->GetTMController()->GetPositionController(); ikeys = GetKeyControlInterface(c); if (!ikeys) { // No interface available to access the keys... // Just sample the controller to get the position // data at each key... SampleController(n, c); return; } numKeys = ikeys->GetNumKeys(); DebugPrint(_T("\nThere are %d position key(s)"), numKeys); if (c->ClassID() == Class_ID(TCBINTERP_POSITION_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &tcbPosKey); DebugPrint(_T("\nTCB Position Key: %d=(%.1f, %.1f, %.1f)"), i, tcbPosKey.val.x, tcbPosKey.val.y, tcbPosKey.val.z); } } elseif (c->ClassID() == Class_ID(HYBRIDINTERP_POSITION_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &bezPosKey); DebugPrint(_T("\nBezier Position Key: %d=(%.1f, %.1f, %.1f)"), i, bezPosKey.val.x, bezPosKey.val.y, bezPosKey.val.z); } } elseif (c->ClassID() == Class_ID(LININTERP_POSITION_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &linPosKey); DebugPrint(_T("\nLinear Position Key: %d=(%.1f, %.1f, %.1f)"), i, linPosKey.val.x, linPosKey.val.y, linPosKey.val.z); } } // --- Process the rotation keys --- c = n->GetTMController()->GetRotationController(); ikeys = GetKeyControlInterface(c); if (!ikeys) return; numKeys = ikeys->GetNumKeys(); DebugPrint(_T("\nThere are %d rotation key(s)"), numKeys); if (c->ClassID() == Class_ID(TCBINTERP_ROTATION_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &tcbRotKey); newQuat = QFromAngAxis(tcbRotKey.val.angle, tcbRotKey.val.axis); if (i) newQuat = prevQuat * newQuat; prevQuat = newQuat; DebugPrint(_T("\nTCB Rotation Key: %d=(%.1f, %.1f, %.1f, %.1f)"), i, newQuat.x, newQuat.y, newQuat.z, newQuat.w); } } elseif (c->ClassID() == Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &bezRotKey); newQuat = bezRotKey.val; if (i) newQuat = prevQuat * newQuat; prevQuat = newQuat; DebugPrint(_T("\nBezier Rotation Key: %d=(%.1f, %.1f, %.1f, %.1f)"), i, newQuat.x, newQuat.y, newQuat.z, newQuat.w); } } elseif (c->ClassID() == Class_ID(LININTERP_ROTATION_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &linRotKey); newQuat = linRotKey.val; if (i) newQuat = prevQuat * newQuat; prevQuat = newQuat; DebugPrint(_T("\nLinear Rotation Key: %d=(%.1f, %.1f, %.1f, %.1f)"), i, newQuat.x, newQuat.y, newQuat.z, newQuat.w); } } // --- Process the scale keys --- c = n->GetTMController()->GetScaleController(); ikeys = GetKeyControlInterface(c); if (!ikeys) return; numKeys = ikeys->GetNumKeys(); DebugPrint(_T("\nThere are %d scale key(s)"), numKeys); if (c->ClassID() == Class_ID(TCBINTERP_SCALE_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &tcbScaleKey); DebugPrint(_T("\nTCB Scale Key: %2d=(%.1f, %.1f, %.1f)"), i, tcbScaleKey.val.s.x, tcbScaleKey.val.s.y, tcbScaleKey.val.s.z); } } elseif (c->ClassID() == Class_ID(HYBRIDINTERP_SCALE_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &bezScaleKey); DebugPrint(_T("\nBezier Scale Key: %2d=(%.1f, %.1f, %.1f)"), i, bezScaleKey.val.s.x, bezScaleKey.val.s.y, bezScaleKey.val.s.z); } } elseif (c->ClassID() == Class_ID(LININTERP_SCALE_CLASS_ID, 0)) { for (i = 0; i < numKeys; i++) { ikeys->GetKey(i, &linScaleKey); DebugPrint(_T("\nLinear Scale Key: %2d=(%.1f, %.1f, %.1f)"), i, linScaleKey.val.s.x, linScaleKey.val.s.y, linScaleKey.val.s.z); } } }
A developer could also get the relative values from the PRS controller by passing in the identity matrix and not the parent matrix to GetValue(). This is appropriate for the PRS transform controller but not the Look At transform controller. With a Look At controller, its relative values are actually a function of the input matrix. If you pass in the identity to its GetValue() method, it will think that the object is positioned at the center of the world and the results won't be meaningful.
The following sample code demonstrates access to the controller data for the first node in the current selection set. The code checks to see if the controller provides its data via the IKeyControl interface. If it does, this interface is used to get the keys. If it does not, the controller is sampled at each frame to get its data. For the keyframe controllers, note how the Class IDs are checked to ensure the proper classes are used when getting key values. Also note how the rotation keys are derived by multiplying on the left by the previous key. For the procedural controllers, note how the parent matrix of the node is passed to the GetValue() method.
The following code displays the position data of controller in world coordinates for each frame in the animation range:
voidUtility::SampleController(INode *n, Control *c) { TimeValue t; Point3 trans; Matrix3 pmat; Interval ivalid; inttpf = GetTicksPerFrame(); ints = ip->GetAnimRange().Start()/tpf, e = ip->GetAnimRange().End()/tpf; // Sample the controller at every frame in the anim range for (int f = s; f <= e; f++) { t = f*tpf; ivalid = FOREVER; pmat = n->GetParentTM(t); c->GetValue(t, &pmat, ivalid, CTRL_RELATIVE); trans = pmat.GetTrans(); DebugPrint(_T("\nPosition at frame: %d of %d=(%.1f, %.1f, %.1f)"), f, e, trans.x, trans.y, trans.z); } }