Intermediate wheel rig.

loocas | 3ds Max,maxscript,technical | Thursday, June 26th, 2008

A friend of mine asked me how I’d go about rigging a wheel so that it spins no matter which direction it travels and stick to a ground as well. At first I thought this’d be a piece of cake as all I really needed was a direction vector and its magnitude to add to the rotation of the wheel. Well, the solution turned out NOT to be that simple in the end.

(the video lags a bit, but the entire rig is actually faster than real-time)

Get the Flash Player to see this content.

Aside from bumping and dealing with a few 3ds Max “quirks”, I actually had to solve quite an interesting problem. When I start setting up a system/rig or working out a tricky problem I begin with the simplest possible scenario, so in this case I started with a simple one dimensional solution to the rotational problem. I simply sent the wheel master (that dragged all the helpers along) in one direction I wanted the wheel to spin. Obviously, this was the “forward” direction so that the wheel could actually turn. You can read about this simple solution at Paul Neale’s web, I simply based my solution on his procedure. However, as I said, this is only a one dimensional approach, which means, the system spins the wheel no matter which direction it travels. Besides, Paul’s example doesn’t take local rotation of the wheel into account. I needed a more robust solution: first of all, I needed a system that’d spin the wheel only when travelling forward or backward relatively to its orientation, so that it wouldn’t spin if, for example, the wheel was oriented perpendicular to the direction of travel. Secondly, I needed the wheel to “stick” to any given surface as it’d be used in an animation where the animator would, preferrably, only setup a direction of travel and the rig would take care of the rest of the secondary animation for them. This brought a new problem up as I’ll describe later.

So, back to the one dimensional solution. Essentially, all you need is a value about which the wheel center will spin. You can calculate that quite easily by multiplying the travelled distance by the circumference of the wheel. The circumference is 2*Pi*r, where r is the radius of the wheel. However, you subdivide the circumference by 6.28319 in Paul’s example, so, you can simply skip the 2*Pi calculation as that’s essentially the same value. In the end, all you need is the total distance travelled ‘v’ (which is NOT a vector!) and the radius of the wheel. The formula is super simple: rotation = v/r. That’s it! :) Really, there’s no catch to that, it simply works. Another note to take into account is that the whole system is history dependant, which means it has to start at a specific time and it has to be provided (updated) with a constant Dt (delta t = time step). So, go ahead and prepare your scene like in Paul’s tutorial.

Done? Good. You should now end up with a wheel that spins when moved in its “forward” or “backward” direction. The problems start when you move the wheel sideways, it still spins! Why is that? Well, the whole system is based on a position of the wheel in space, which, in other words, means a vector pointing from the origin to the wheel’s pivot. The other problem, directly associated with this approach, is that it doesn’t respect the local orientation of the wheel. I wanted to find a solution to this problem as well.

A local vector pointing in the direction where the wheel will be travelling was the obvious choice. But how do you get such a vector? You can’t derive the local direction of travel from the object itself as is. A vector is a direction with a magnitude, which is very important to realize, especially if your system is heavily dependant on both. So, to get the correct vector pointing in the direction where the wheel will be travelling AND having the correct magnitude of the actual speed of the wheel, you need to compare two points in time-space (master being a point helper controlling the position of the wheel):


local posIncrement = 0.0 as float
posIncrement = master.pos.y - (at time (currentTime-1) master.pos.y)
rotation = (posIncrement+storedDistance)/(wheelRadius)

I just used the at time context to get the object’s previous x position and subtracted it from the current object’s x position. However, this method yields only the magnitude I was after, not the direction I also needed. So how do you go about this? First the theory: if you need to get a direction vector between two points, you need to subtract the two points in space, then, you can ask for the vector’s length to obtain its magnitude. So, basically, I subtract from a position at time t position at time t-1 and get the difference vector, which yields the direction the wheel will be travelling. The problem is, this doesn’t solve the problem with the wheel’s local orientation :) To cover that, you need to multiply this resulting vector by the direction vector, which in my case was the Y axis as the wheel was originally aligned to the identity matrix when I was rigging it. The final snipped for this step looks like this:


local posIncrement = 0.0 as float
posIncrement = length((master.pos - (at time (currentTime-1) master.pos)) * master.transform.row2)
rotation = (posIncrement+storedDistance)/(wheelRadius)

This gets me the positional incremet in the wheel’s local Y axis (row2 of the transform matrix is the projection vector of the object’s Y axis), if the Y axis is fully aligned to the direction it travels, the multiplication ratio will be 1:1 (if normalized! you should never scale control objects, due to this behavior!). So, this was it, I solved the problem in 1 dimension in respect to the local orientation of the wheel!

However, there is another problem when you extend the system to the point that the wheel actually sticks to a surface. The problem lies in the wheel’s Z direction it can travel as it adds to the final rotation. Why should I be concerned about the Z axis? Well, imagine a very rough terrain the wheel would be travelling on. If you move forward, it spins correctly without any “sliding”, however, if there’s a big bump in the way, the wheel actually spins slower than it should, because it only counts on the translation along the Y axis (in my case), not in the Z axis! A wheel is actually a two-dimensional object, like a circle, which can spin when touching any of the two world planes in the 2D space. Sphere, for example, is a fully three-dimensional object that can spin in any direction when touching any of the three world planes. You can see the result of the 2D solution on the video above where the wheel nicely sticks to the rough terrain no matter which direction it travels (as long as it’s aligned to the direction). So, how do you go about that? Well, you need to extend the whole formula into another dimension. In this case you need a two dimensional solution that’d take care of the translation in Y and Z axis. The script snippet would then look like this:


local posIncrement = [0.0,0.0] as point2
posIncrement.x = length((master.pos - (at time (currentTime-1) master.pos)) * master.transform.row2)
posIncrement.y = length((master.pos - (at time (currentTime-1) master.pos)) * master.transform.row3)
rotation = (length (posIncrement+storedDistance))/(wheelRadius)

Notice the use of a point2 data type in this case. You need to store the positional increment for the Y axis as well as for the Z axis. You then sum it up with the stored distance value (also of a point2 data type) and ask for the length of the resulting vector, which gives you the magnitude, which is the distance the wheel travelled in the last time step.

That’s pretty much it, well, at least the most difficult part, there are other problems I had to face, which I don’t consider that difficult to solve, like the actuall snapping on the surface or dealing with negative time increments (i.e.: scrubbing through the time slider backwards) or dealing with Max’s script controllers not updating custom attribute variables correctly, which is an annoying bug. Well, I hope you’ve enjoyed this article at least as much as I enjoyed solving this little puzzle ;)

14 Comments »

  1. Great work man!

    Comment by yyzl_113 — June 30, 2008 @ 05:54

  2. very interesting read, and a good solution, apart from that i must say that your video is totally funny. its some compression problem i guess, but first time i looked at it, i thought its not about correct wheel rotation on a terrain, but instead about soft body simulation!
    the reason is = when u look closely the edges of weal are moving cos of compression, so i thought u are doing something like a big big wheel and its constructed out of thin metal so it all vibrates internally when it moves :))))
    but then i sow a “moving” edges of terrain :)

    Comment by mantas — August 24, 2008 @ 11:45

  3. mantas: Yeah, the compression as well as the initial screen recording got screwed up a bit, so the final video doesn’t look as impressive as it should :D

    Anyways, I hope it helps somebody who’s looking into automatic wheel rotation rigs ;)

    Comment by loocas — August 24, 2008 @ 15:36

  4. Very cool, but like how do I implement it? I did Paul Neale’s tutorial and that worked cool, but I’m unclear where your stuff fits in. Your tutorial could use a little more tutorial.

    Comment by pb — February 10, 2009 @ 21:32

  5. Heya, pb,

    well, it’s simple, just put the code I described in a scripted controller that you might want to link your wheel to.

    Comment by loocas — February 10, 2009 @ 22:18

  6. I’m new to all this custom attribute and script controller jazz.

    I think I conceptually understand your method, but stuff like multiplying vectors makes my brow furrow.

    Would it be possible to explain how to incorporate your technique into Paul Neale’s? I mean, literally, how exactly does his code change? Also, I’m not really concerned with the sticking to the surface deal, just the wheel rotating when it’s supposed to.

    A specific question regarding your code snippet above: what is “storedDistance”?

    That would be sweet.

    Comment by pb — February 12, 2009 @ 22:21

  7. pb, unfortunately I’m not sure what exact technique of Paul Neale you’re talking about? I know he did a very nice, advanced, car rig, which is available for commercial use.

    The storedDistance is a variable that simply stores the travelled distance from previous to the current frame. It’s used for the vector multiplication later on. I believe it’s also used for reverse rotation if the time slider goes backwards.

    But I did this some time ago and I don’t remember the exact steps I did, I’d have to open the scene and look through it.

    Comment by loocas — February 12, 2009 @ 23:04

  8. Funny. The Paul Neale technique I’m talking about is the one you link to in your article:
    http://paulneale.com/tutorials/custAttributes/customAttributes.htm

    You say to setup your scene like his, but don’t really get into the specifics of applying your technique to his code…at least not at a skill-level that I comprehend.

    I was just hoping for a straightforward way to modify his code to work with yours.

    Thanks!

    Comment by pb — February 12, 2009 @ 23:28

  9. Ah, right, yes. Well, that’s why this is a INTERMEDIATE rig :) I don’t go into every specific detail. I present a TECHNIQUE not a complete TUTORIAL on what buttons to press.

    If you know script controllers and layered controllers, then this should be enough information for you to setup your own rig.

    Comment by loocas — February 12, 2009 @ 23:47

  10. Gotcha. I’ll see what I can figure out.

    Comment by pb — February 16, 2009 @ 22:05

  11. Well, i try to calculate motion vectors with magnitude and direction by using real time spring script
    by http://www.seageo.com

    Comment by kiran — July 23, 2009 @ 12:31

  12. Interesting, will take a look, thanks for the tip, kiran.

    Comment by loocas — July 23, 2009 @ 12:47

  13. Here is my question.. I think I have wrapped my brain around this, but I am still cloudy on where the storedDistance value is coming from. Where did that come in? Is that a variable you declare, or is attached to a node somewhere?

    Thanks again for the additional help

    Comment by gumby — August 28, 2009 @ 23:01

  14. Heya, gumby,

    the storedDistance variable can be a global variable or it can be stored in a custom attribute, anywhere. In my example, I think I made it a persistent global variable, so it’s accessible from anywhere, anytime in the scene.

    Comment by loocas — August 29, 2009 @ 09:04

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress | Theme by Roy Tanck