
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
|