Maya Tutorial Part 2: 
Calculating Secondary Action Based on Skeletal Animation

by Kris Kapp
spudk@texas.net

This is a continuation of last month's tutorial. One thing that will make animating the skeleton a little easier is to add an expression that hides either the Control Leg or the FK Leg depending on FKIK value. In the following expression example I have it set up so that the Control Leg only is visible if the FKIK value is over 5 and the FK Leg is only shows if the FKIK value is less than 5.

if (ControlLegHandle.FKIK > 5)
      {
      ControlHip.visibility = on;
      FKHip.visibility = off;
      }
else
      {
      ControlHip.visibility = off;
      FKHip.visibility = on;
      }

I also have a couple of example movie files you can check out. The first one is at http://spudk.home.texas.net/movies/kick1.avi and is a rendered version and the second one

http://spudk.home.texas.net/movies/kickb1.avi shows the joints and foot controls.

I came up with a way to get the rabbits ears to move as a secondary action in relation to the movement of the main skeleton. By calculating how much an object moves from frame to frame, you can then apply the result to another object. First you have to determine if the properties of the object you are trying to calculate acceleration for is in world space or local space coordinates. The difference between world space and local space is this: Create a sphere at the origin. Now translate it in X five units. The value for X has changed from 0 to 5. This is local space. Return the sphere to the origin. Group it and move the group node in X five units. The sphere had moved 5 units but if you go down the hierarchy to the original sphere node you see that the value for X is 0. The sphere itself hasn’t moved in local space but it has moved in world space. Lots of time you’ll group nodes so that you have more control over their motion. In the above example you might have a group node for translation, a group node for rotation, and a group node for scale. The docs describe world space as your camera view with the origin at the center. Local space is described as the space around an entity with the origin at the center of that entity.

Why is this important? Well if you are going to calculate acceleration in local space it's pretty easy. All you have to do is use the getAttr function in an expression using the frame option.

Ex:

$cuurentpostionx = ‘getAttr -time (frame) ball.tx‘ ; // position of ball on X axis in current frame
$lastpositionx=‘getAttr - time (frame -1) ball.tx‘; //position of ball on X axis in last frame
$deltapositionx=$currentpositionx - $lastpositionx; // change of X from one frame to the other

If you have to calculate acceleration in world space then that’s a little more complicated. To get data from world space, you need to use the "xform -ws -q -t" function. The main problem with this is that xform does not have a time function like getAttr does, so you have to use a variable that acts as a place holder for the value from the frame before. Another problem with this setup is that it forces you to run through the entire animation to get the correct value because it compares the place holder value with the current value. It sorta starts acting like a dynamic simulation, which just means that when you try scrubbing the frame slider you get incorrect results.

How I did this was to first create an attribute for the object. If the object was named ball then I created attributes on ball the for world space coordinates. I called these wsx, wsy, and wsz for the world space x, y, and z coordinates. Then I "filled" that attribute using an expression.

Ex:

float $pos[3] = ‘xform - ws -q -t ball‘; // vector for the xform -t output ball.wsx = $pos[0]; ball.wsy = $pos[1]; ball.wsz = $pos[2];

This gives me the current position in world space for each of the transitional axis. To write the next expression I have to know what is the start frame for the animation. If the range is from 1 to 100 then I will start my expression calculation at 0. I can use the getAttr function to figure out my change in values from one frame to the other. But the getAttr will only return the current frame value of the world space attributes that were created in the steps above. So the ‘getAttr -time (frame -1) ball.wsx‘ has the same value as ‘getAttr -time (frame) ball.wsx‘ has. Because the value for the attribute is made up from world space info, the getAttr command only works for the current frame in this case. That is why the time range has to have a definite start.

Ex:

float $currentx;
float $lastx;
float $deltax;
if (frame < 1) { // if the time slider is less then when the animation starts
then set the value of lastx to 0
     $lastx = 0;
       $currentx = ‘getAttr -time (frame) ball.wsx‘; // position of x
       $deltax = 0;}
else {

$lastx = $currentx
$currentx = ‘getAttr -time (frame) ball.wsx‘; // position of x
$deltax = $currentx - $lastx;}

The deltax value is the change in the world space x value from one frame to the next. It can be positive or negative. You can use this same expression for the other two transitional attributes. I used this method to get the ears of the rabbit to bob when he moved without having to keyframe them.

Image #1 shows a x-ray view of the rabbit's ears. I’ve created an IK spline handle from the last four joints of the ears. This allows me to control the movement of the bones by manipulating the curve that is created by the IK spline tool instead of moving the end handle. I used the Root onCurve, Auto Parent Curve, and Auto Create Curve options with the IK spline tool and created an IK spline on the rabbit's ear. By moving the curves CV’s, the shape of the curve changes and the joint chain will change also, matching the shape of the curve.

Select the four end CV’s and create a cluster (Deform->Create Cluster.) Display the selection handle for the cluster and move it away from the joint chain to make it easier to select the cluster. Select the spline IK curve and go into component mode. Select the four end CV’s and using the component editor set the cluster weights on the CV’s. Set the weights higher on CV’s that you want to move more. On the end CV (the one closest to the IK Handle) I set to 1.0, the next one down to .66, then third one down to .33 and the fourth one I set to 0.0. (See Image #2) Now when you move the cluster handle, the ear will bend to follow it. (See Image #3). Now that we have the cluster set up and weighted we can use expressions to animate it to simulate delayed secondary motion. I am going to use the end nose bone to calculate how much the head moves from frame to frame. Group the cluster handle to the first ear bone that is not part of the IK Spline chain.

Image #2        

Image #3

I added the following attributes to the NoseEnd joint: wsx, wsy, wsz. The attributes were created as data type Float, attribute type Scalar, and no min/max/default values. (See Image #4)


Image  #4

I used an expression to find the values of the attributes based on the xform statement:

float $posit[3] =‘xform -ws -q -t NoseEnd‘;
NoseEnd.wsx = $posit[0];
NoseEnd.wsy = $posit[1];
NoseEnd.wsz = $posit[2];

Once I have this information I can write an expression that will move the cluster handle (L_EarCHandle) based on how much the NoseEnd joint moves from frame to frame. However, because xform only returns values for the current frame, we have to create a couple of temporary variables to act as place holders for xform values from previous frames. Also this will force us to set a specific start value for the expression. The two IF statements are to determine if the actual animation has started yet. I set up this expression to move the cluster based on the NoseEnd joints movements two frames back in time. I also broke down the x, y, and z movements into different expressions so I have more control over how the cluster will move. I’ll just give an example of the x axis movement, but the concept is the same for the other two axis’.

float $L_CurX; //current value of the left cluster x value
float $L_PosAX; // value of x one frame back
float $L_PosBX; // value of x two frames backfloat $L_DeltaX; // change in x over time
if (frame <= -1) { //determine if animation has started yet
      $L_PosAX = 0; //fills variable with a starter value
      $L_PosBX = 0; //fills variable with a starter value
      $L_CurX = ‘getAttr -time (frame) NoseEnd.wsx‘; //gets value of x
      $L_DeltaX = 0;} //set it to zero since frame is less than 1
else if (frame == 0) { //determine if animation has started yet
      $L_PosBX = $L_PosAX; //sets to last two frames value
      $L_PosAX = $L_CurX; //sets it to last frames value
      $L_CurX = ‘getAttr -time (frame) NoseEnd.wsx‘; // current frame
      $L_DeltaX = 0;} //set it to zero since frame is less than 1
else { //start the animation at frame 1
      $L_PosBX = $L_PosAX; //sets to last two frames value
      $L_PosAX = $L_CurX; //sets it to last frames value
      $L_CurX = ‘getAttr -time (frame) NoseEnd.wsx‘;
      $L_DeltaX = ($L_PosAX - $L_PosBX);} // current frame
$L_DeltaX = ((0 - $L_DeltaX) + rand(-.1,.1)) *.5;
// This gives you a negative value of the NosedEnd change. The random value
// is so that the two ears move a little different from each other to prevent twins
// and the .5 is a drag factor you can set to different values. The lower the number
// the less the ear will move.
L_EarCHandle.translateX = $L_DeltaX; //move the cluster handle

Because we have to set a start value with the if statements, the animation become almost like a dynamics and you will get strange results if you use the scrub bar during playback. Also because we are using info from two frames back we have to give it two frames lead time to figure out how the NoseEnd joint is moving. You can always bake the expressions to get motion curves for the clusters if you need to. This may not be the easiest way to do this but it worked pretty well for me. You can check out the results in the following movies:

http://spudk.home.texas.net/movies/kick1.avi

http://spudk.home.texas.net/movies/kickb1.avi

Let me know if you have any problem's or questions with this set up, or if you have a better way of doing this.

-Kris
spudk@texas.net
www.texas.net/~spudk

 

GIGnews is a publication of GIGnews.com, Inc.
"Get In the Game" is a registered trademark used with permission.

© 1
999- 2005 GIGnews.com, Inc.
Legal