Vector-Based Wheel Rigging

Hey everyone!

First of all, I’d like to start with announcing I’ll be a Character TD intern this summer at Insomniac Games! I’m so excited for this opportunity!

Anyways, I’ve been working on a lot lately, partly for the Puppeteer Lounge and partly for just regular university stuff, but in my free time I’ve been chipping away at a car solution in Maya.


Wheel Rotation

As a high school geometry refresher, in order to do a full 360 degree rotation, a wheel must travel a distance equal to Pi times its diameter, or C = Pi*D.  Traditional Maya wheel setups will use a simple set-driven-key approach to get things done. For example, for a wheel with diameter 2, you’d go Tau units (6.283) forward on the axis and have that drive a 360 degree rotation on the wheels. That can work for a basic setup, but you’ll still get a lot of wheel sliding since it’s only a one-dimensional driver.

A better way, I found, is to use a vector-based approach. I started with this youtube video as a starting point, but I found some problems with it, including confusing equations and variable names and also the fact that his technique for dot product doesn’t account for the wheel’s individual ry control. Either way, I knew I had to figure this out on my own to figure this out.

Vector Magnitude

The magnitude of a vector is equal to the square root of the sum its squared contents. In simpler words, it’s like the quadratic formula but for n dimensions. So magnitude is a scalar (number) that comes from a vector (“array”) and can be used as distance.

Therefore, at each frame, I knew I’d need to know the previous location and the current location as a vector. This was done using empty group nodes that I parented to a top level, “static” node (could be an XTR group in a rig). This transform node’s job is simply to store the previous frame’s location.

vector $wheel_old_vector = `xform -q -t -ws “wheel_cc_old_vector”`;

I then get the wheel’s current location:

vector $wheel_vector = `xform -q -t -ws “wheel_cc_pos_vector”`;

The current vector – previous vector gives us the movement vector from the previous to the current location, (I remember this as A-B gives B to A). The magnitude of this is the distance traveled.

vector $translate_vector = ($wheel_vector – $wheel_old_vector);

float $distance = mag($translate_vector);

At this point, we know the distance traveled and can calculate from that the rotation that we need to do ( 360 * distance / (Tau*radius) ). However, this doesn’t account for forward and backwards movement and for the lessened rotation that occurs when the direction of motion is oblique to the angle of the wheels.

Wheel Direction

I therefore added two other empty transform nodes to calculate the direction of the wheel. The first was just parented below the “move” control for the wheel and projected onto the zx plane, and the other is set as follows:

matchTransform wheel_cc_forward_vector wheel_cc_pos_vector;
xform -os -r -t 1 0 0 wheel_cc_forward_vector; 

So I’m matching the rotation and translation of the forward vector to the wheel vector and then going forward in object space from there. (I’m determining +as the forward direction here). From that, we get the vector:

vector $wheel_dir_vector = ($forward_vector – $wheel_vector);

This vector “points” in the direction of the wheel.

Vector dot products

Okay, I have to be honest. I had a pretty traditional Linear Algebra professor and I never really understood how dot products were related to cosine. I understood the dot product could give orthogonality and also if two vectors are pointing in similar directions, but cosine? What?

Well, you just have to normalize them, and it all makes sense.

v1 = [1,0,0]     v2 = [0,1,0]        << two normalized (magnitude 1) vectors

v1 dot v2 = 1*0 + 0*1 + 0*0 = 0 (these vectors are orthogonal)

Oh yeah, and acos(0) is pi/2 or 90 degrees!

Another example that made me get this:

v1 = [1,0,0]     v2 = [1, 1, 0]        << v2 is not normalized. Let’s normalize it!

v2 = (1 / √2 ) * v2 = [√2 / 2 , √2 / 2, 0]

v1 dot v2 = 1*√2 / 2 + 0*√2 / 2 n + 0*0 = √2 / 2

acos(√2 / 2) is pi/4 or 45 degrees!

Okay, so how are we going to use this for our wheels? Well, all we need to do is get the dot product of the normalized wheel and wheel direction vectors and we’ll know the cosine of the angle between them. If it’s negative, we know we need to rotate backwards. If it’s 0, we shouldn’t rotate at all because the car is skidding sideways. And then everything in between.

float $dot = dotProduct($translate_vector, $wheel_dir_vector,1);

We then use that in the final equation to determine if we need to rotate forwards or backwards.

wheel_cc_rz_expr_grp.rotateZ = wheel_cc_rz_expr_grp.rotateZ + (360 * (($distance * $dot) / (-6.283 * $radius)));

And that’s it! Or the bulk of it, at least. Here’s the rest of the mel code if you want to use this expression in your own setups.


// Wheel expression
float $radius = 1.000000;
vector $wheel_old_vector = `xform -q -t -ws “wheel_cc_old_vector”`;
vector $wheel_vector = `xform -q -t -ws “wheel_cc_pos_vector”`;
matchTransform wheel_cc_forward_vector wheel_cc_pos_vector;
xform -os -r -t 1 0 0 wheel_cc_forward_vector;
vector $forward_vector = `xform -q -t -ws “wheel_cc_forward_vector”`;
vector $wheel_dir_vector = ($forward_vector – $wheel_vector);
vector $translate_vector = ($wheel_vector – $wheel_old_vector);
float $distance = mag($translate_vector);
float $dot = dotProduct($translate_vector,$wheel_dir_vector,1);
wheel_cc_rz_expr_grp.rotateZ = wheel_cc_rz_expr_grp.rotateZ + (360 * (($distance * $dot) / (-6.283 * $radius)));
xform -t ($wheel_vector.x) ($wheel_vector.y) ($wheel_vector.z) wheel_cc_old_vector;
if (frame == 1){
wheel_cc_rz_expr_grp.rotateZ = 0;

~ Ben


2 thoughts on “Vector-Based Wheel Rigging

  1. Hi Ben, My name is Elliot and I’m teaching Animation and Visual effects in Dorset, England. I have been trying to apply your code to my own setup but am having some trouble. Would it be at all possible to see your outliner so that i can see how to correctly setup. I’ve been banging my head for a while now. Thanks

    I will obviously credit your hard work!!

    Thanks in advance



    1. Hi Elliot!

      For sure. Here’s an example hierarchy:

      Excuse the naming and all that. This was just a test case and not a final rig. Hope that makes sense?

      The important thing is that the _old_vector and _forward_vector nodes aren’t affected by any transforms. In a normal rig I’d put them in something like an XTR group. You also need multiple levels of expression nodes: one for wheel forward rotation and another for wheel turning.

      Hope this helps,


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s