Wednesday, August 8, 2012

The Mech Leg Problem: Rigging an Offset Ball Joint

As part of a project I'm working on, I'm going to be rigging a cool frog-like mech. Originally, each joint in the mech's leg was going to be a sort of hinge, but in order for the character to be able to shift its weight and adduct or abduct its legs, we needed to replace the hinges at the hips and ankles with ball joints. However, for the hip joint, instead of simply swapping the hinge for a ball joint, and placing it at the top of the leg, we decided to attach the ball joint to the side of the existing hinge, since it moved the leg out away from the body and gave us a wider range of motion with less interpenetration of the geometry.

That posed an interesting problem of how to rig an offset ball joint so that the leg behaved in a way that looked mechanically correct. Here is the setup I ended up piecing together:

Here's an approximation of the geometry for the mech's leg.

As you can see from the picture, the mech's leg is  much like a quadrupedal hind leg - it's a digitigrade limb. You can also see how the top of the leg (the hip's hinge) is attached to the side of the new ball joint. In this case, the ball joint is centered over the origin, with 0 values in X and Z translation.

 The basic joint chain to control the leg.

To start the rig off, create the basic joint chain. I just eyeballed the placement of the joints from the side view,  but for the final rig I would make sure the joints were centered at the pivot points for the hinges and ball joints. After placing the joints, I just translated them in X to make them line up with the geometry.

I started with the entire chain in a single hierarchy, then I duplicated the foot/ankle joint (the second to last joint) and unparented it from the hierarchy to make a separate foot hierarchy. I then deleted the end joint on the original hierarchy, so the leg was one separate chain, and the foot another. Make sure to name everything for clarity!

 Setting up the foot control and the ankle locator.

To attach the foot's joint hierarchy to the leg, create a locator named ankle_pos_loc and snap its position to the ankle joint. Create a curve shape named foot_control, position it at the foot, and snap its pivot to the ankle joint as well. Then make sure to freeze transforms on the control. By placing the pivot at the ankle, when you rotate the foot, the leg doesn't move. Now parent ankle_pos_loc and the foot joints to foot_control, as seen below.

 The foot control and foot joints.

Now we'll set up the IK chain that will drive the leg. We'll use a spring solver, which is a somewhat hidden IK solver. It handles these quadrupedal style joint chains quite nicely. I create a an IK handle named leg_spring_ikHandle from the top of the leg (leg_a_jnt in my case) to the bottom (leg_jnt_end). The solver type doesn't really matter, since we'll enable the spring solver next, by typing
ikSpringSolver
into the MEL command line. Now if you check the attribute editor, under IK Solver Attributes, you'll see the IK Solver option menu has the 'ikSpringSolver' option.

 Selecting the spring solver.

Point constrain leg_spring_ikHandle to ankle_pos_loc. If you attach the geometry to the joints at this point (I just used parent constraints with Maintain Offset checked on), you'll see something like the image below when you compress or extend the leg.

 From the side, the leg behavior looks good.

 Unfortunately, it doesn't work so well from the front view.

You'll see the problem inherent to the offset ball joint when you articulate the leg from side to side. Since the top of the leg hierarchy is at the hinge joint, that's where the leg pivots from. You can see the hinge interpenetrating with the ball joint, and it doesn't look like the ball joint is moving at all. The behavior we actually want is sketched out in blue. The angle of the hinge should be tangent to the ball joint's surface in order to look correct.

 Adding the ball joint's joint.

To get the hinge to be tangent to the ball joint, it needs to move around the ball joint as the leg moves out to the side. Create a new joint chain for the ball joint by duplicating the top joint of the leg and deleting all of its children. Rename it to ball_jnt and zero out its joint orient fields in the attribute editor. This ensures that the joint is aligned to the world. Duplicate ball_jnt and name it ball_jnt_end. Translate ball_jnt back to 0 in X, so that it is in the center of the ball joint geometry. Now parent ball_jnt_end to ball_jnt.

Create another locator named leg_pos_loc and snap it to ball_jnt_end's position. Parent it to ball_jnt_end, and point constrain the top joint of the leg hierarchy to the locator.

 The leg position locator.

If you manually rotate ball_jnt when the leg is out to the side, you can line it up so that the leg looks correct.

 The ball joint manually adjusted to match the orientation of the leg.

Notice that the angle of ball_jnt and the leg make a right angle, so that the hinge is tangent to the ball joint.

You could just create a control for ball_jnt and call it a day, but that's sort of an inconvenient setup for the animators, who would have to constantly adjust that control to match the leg. It would be nice to automate the rotation of ball_jnt. Fortunately, we can do that with an aim constraint.

 The aim locator in its proper location.

Create a new locator named ball_joint_lootAt_loc and snap it to ball_jnt. Then move it down until it is at the same height as the ankle. Create an empty group, name it ball_joint_aim_grp, and point constrain it with no offset to ankle_pos_loc. Then parent ball_joint_lookAt_loc to ball_joint_aim_grp, and now it follows along with the foot.

Create yet another locator, call it ball_joint_aimUp_loc and snap it to ball_jnt as well. Then translate it out in front of the ball joint (in this case that's a positive Z translation). This locator will serve as the up object for the aim constraint.

 Placement for the aim up locator.

Now make an aim constraint from ball_jnt to ball_joint_lookAt_loc. The settings I used were a negative Y  vector for the aim axis, positive Z for the up axis, and object up for the up type. The up object should of course be ball_joint_aimUp_loc. After the aim constraint is applied, ball_jnt should still have a rotation of (0, 0, 0).

If you move the foot control at this point, you should see that the ball joint and leg work in almost perfect harmony. However, if you lift the foot to be all the way out to the side and at the same level as the ball joint, the hinge joint at the top of the leg is a little out of alignment. This occurs because the angle of the leg is different from the angle between the ball joint and the aim locator. If the aim locator were to adjust its location as the foot moved, such that it more closely copied the angle of the leg, this problem would not be noticeable.

To get this sort of behavior to work, we need to drive the the position of the aim locator using the angle of the leg. Unfortunately, this would create a dependency cycle, since changing the angle of the leg would affect the aim locator, which would affect the ball joint, which would affect the position of the leg, which would affect the angle of the leg again. So we need a new joint chain that mimics the angle of the leg as close as possible. I haven't managed to figure out a way to EXACTLY duplicate the angle of the leg without running into some sort of dependency, but the solution I've come up with works close enough that you shouldn't be able to see the difference.

 The aim orient driver joint chain.

Create a new joint in the perspective viewport and snap it to the ball_joint_lookAt_loc. Call it aim_orient_driver_jnt. By creating a fresh joint in the perspective view, it will be aligned to the world, which is important for a later step. Duplicate this joint and move it up a little bit, then parent it to the driver joint. This will be the aim_orient_driver_jnt_end. Point constrain this joint chain to the foot control WITH offset. You can see in the image above that the joint chain is properly positioned and constrained to the foot control.

Now create a single chain IK solver from aim_orient_driver_jnt to aim_orient_driver_jnt_end. Name it aim_orient_driver_ikHandle and point constrain it WITHOUT offset to ball_jnt. This joint chain now closely reflects the orientation of the leg as you move the foot control. 

We've already set up a group, centered on the ankle, that can drive the ball_joint_lookAt_loc around the ankle. Simply use the connection editor to wire the Z rotation from aim_orient_driver_jnt to the Z rotation of ball_joint_aim_grp. Since both the group and the driver joint were created in alignment with world space, their axes correspond perfectly.

The completed offset ball joint setup.

Now, as you move the foot around, the aim locator rotates around the ankle, so that the angle of the leg closely matches the angle between the ball joint and the aim locator. As you can see from the final image above, the ball joint appears to have the proper behavior, even in very extreme poses.

This was a fun challenge to work out, and I hope anyone reading this can get some ideas about how to solve similar problems with their own rigs.