8 minutes
Control Rig: An axes-agnostic Basic IK Solver
Recently, I stumbled upon an interesting problem about the Basic IK solver in Unreal Engine’s Control Rig. Basically, I had to build a rig for a humanoid skeleton but, as soon as I set up a simple Basic IK solver for such character’s legs, I realized something was wrong: even in the initial T-pose, the activation of the Basic IK solver would move my character’s leg in a weird way, producing an unnatural (and visibly painful) pose. That was because, as I found out later, the primary and secondary axes (required as input by the Basic IK solver) could not be defined univocally for both the bones of the leg. I ended up building what I call an axes-agnostic Basic IK solver, i.e., a solver that does not require a Primary and a Secondary Axis to be specified, since it would compute such axes by itself.
Some preliminary concepts
Okay, let’s take a step back for a moment: what are we talking about?
The Basic IK solver is the simplest and most common IK node for Control Rig. It does nothing more than what the Two-Bone IK node for animation blueprint does: solving an IK problem by using the Law of Cosines. The only exception is in the input parameters required by this node: differently from the animation blueprint solution, the Control Rig solver requires specifying the Primary and Secondary axes manually. But what are such axes?
Most Control Rig tutorials just show the users inputting the same axes blindly, assuming all the skeletal meshes are rigged in the same way. The only one who spent some time actually explaining how the bones of a character should be oriented was Kevin Freeman in his basic IK tutorial video. Huge shout-out to this channel, I recommend it to anyone looking for deep insights into Control Rig. To summarize what he says in the tutorial:
- The Primary Axis is the direction (in each bone’s local space) from each bone to the next one in the IK chain. For example, if we are solving for a leg, it would be the direction from the thigh to the calf, expressed in thigh space. But beware! This axis must be the same for the calf-foot bone. That’s because the Basic IK solver will apply such primary axis to all the joints making up the chain, and it will break the limb if such axis differs between the two.
- The Secondary Axis is an axis that lays on the same plane as the pole vector, in each bone’s local space. Even in this case, this axis must be the same for all the bones involved in the IK chain. The easiest way to visualize it is by picturing the plane that contains all the three bones of the chain, which is univocally defined. When your character is in the base pose, the pole vector will lay inside this plane. This axis usually is the bone’s Y axis, which typically comes out of the skeletal mesh’s knee (if you are dealing with legs).
The problem
As soon as I finally understood the meaning of primary and secondary axes, the problem with my skeletal mesh became clear: each bone of the chain had a primary or secondary axis different from the other bones. That meant that I could not define a pair of axes that worked correctly for all the bones.
Now, in this kind of situation, the best thing to do would be to fix the bones’ orientation in the FBX file itself. Opening Maya/Blender, adjusting the orientations, and reimporting the skeleton. My problem, however, was that I already had a lot of animations already made for those characters, carefully organized in folders and subfolders, and linked by montages referenced here and there in the project’s blueprint. Fixing the issue in the skeleton would have meant making a new skeleton, retargeting the old animations to it, replacing all the references, and painfully recreating all the folder structures for the retargeted animations. I could not afford that, so I ended up building this axes-agnostic solver.
The solver
The idea behind this solver is to build a chain of correctly oriented temporary bones on top of the “real” ones, implement a basic IK solver on such bones and update the actual ones according to the respective temporary bones’ transforms. This worked pretty well and clean, saving me from an endless and painful retargeting.
Spawning the temporary bones
Let’s say we are trying to implement a solver for the left leg, which is typically made of a thigh, a calf, and a foot bone. We need to define three temporary bones: tmp_ik_thigh_l, tmp_ik_calf_l, and tmp_ik_foot_l.
To do that, let’s go to the Construction Event in our Control Rig and let’s invoke the SpawnBone node three times, one for each bone. The first one (tmp_ik_thigh_l) will have the pelvis as a parent, while the other ones will create a chain starting from the thigh itself. We name the thigh and the calf bones as item A and item B, while the last one in the chain is the effector bone.
Item A:
Item B:
Effector:
The temporary bones will share the same initial location as their non-tmp versions. Their spawn rotation, though, will be specific for each one of them, as it will need to be computed in such a way that tmp_ik_thigh_l and tmp_ik_calf_l will share their primary and secondary axes. In particular, we want to give (-1,0,0) as the primary and (0,1,0) as the secondary axis, so we need the initial rotation of each tmp bone to be computed in such a way that matches with such axes. Let’s remember that, in order to calculate a rotation, we just need two axes out of three, since the third one is automatically determined.
Computing the X axis
The primary axis of each pair of connected bones is pretty straightforward to compute since it’s the direction from a bone to the next one in the IK chain. In the case of the left leg, that usually matches the bone’s local X-axis, so we can compute it (in global space) by subtracting the first bone location from the second bone location (e.g., calf location minus thigh location) and normalizing the result.
A utility function for computing the vector frome one rig element to another:
A function to calculate the actual primary axis:
Computing the Z axis
The IK chain secondary axis is less trivial than the primary one, but not too complicated to find if you know basic vector math. As we mentioned before, this axis lays on the plane that contains the locations of the three bones that make up the IK chain: let’s lazily name this plane p. We already have computed the primary axis of each pair of tmp bones, so the rotation of each tmp bone can be defined as the rotation whose X-axis matches the primary axis, and the Z-axis matches p’s normal. That’s why we are looking for the Z-axis rather than the Y: if we have it and we combine it with the previously calculated primary axis, we have our pole axis univocally determined!
So how do we calculate p’s normal? As vector math teaches us, the cross product of two vectors a and b always produces a third vector c which is perpendicular to the plane defined by a and b. So, let’s calculate the plane’s normal as the cross product between the thigh-calf and the calf-foot direction.
A function for calculating the normal of the IK chain plane:
Computing each tmp bone initial rotation
We finally have each tmp bone’s X and Z axes, so we can calculate the initial rotation of such bones. Let’s just call the MakeRotFromXZ function to… oh wait, there is no such function in Control Rig. My solution to such inconvenient is using the AimMath node:
- The transform that needs to aim is the identity transform;
- the primary axis is our computed X axis;
- the secondary axis is our computed Z axis.
Function for calculating a quaternion given the X and the Z axes:
The result of such logic would be a quaternion whose X and Z axes matches the one we provided as input.
We finally spawned our three temporary bones. We just need two controls now.
The IK Foot Control
The IK foot control will just match the tmp_ik_foot_l initial transform.
The Pole Vector Control
I will follow Kevin Freeman’s approach here and spawn a Null, named tmp_pole_vector_ref, 50cm away (along the Y axis) from the calf bone. This will give us the ideal initial location for our Pole Vector Control. Let’s create this control as a child of the foot control and set its initial transform like that.
Finally, the Basic IK solver
We are finally ready to solve our IK chain. If we did everythign correctly, the tmp bones should not move at all as soon as we connect our Forward Solve event to the Basic IK node. If the tmp chain pose changes even a little bit, then you should review all the process, since a clean IK solver should not break the T pose.
Updating the real bones’ transforms
Our last step is to finally update the real thigh, calf and foot transforms. Of course, we can’t feed them with the same exact transforms as the tmp bones: that would lead us to the initial problem. What we need to do is to apply the same offset we have between each bone’s and the respective tmp bone’s initial transforms to the new tmp bone transform. This process requires the ProjectToNewParent node.
And that concludes our axes-agnostic forward IK solver.
The Backwards Solver
Implementing a backwards solver is quite trivial, since we just need to call ProjectToNewParent in the same way as we just did, except that we swap parents and children.
We now have the tmp bones located as we expect them to be after the IK solver has done its job. We now just need to make the IK Foot Control match the tmp foot transform and to make the Pole Vector Control match the tmp_pole_vector_ref transform.
1686 Words
2024-04-21 12:00
Did you like this post? Did you hate it? Wanna talk about it? Contact me at alessandro.tironi7 at gmail dot com.