🔸IK Animation System

In this section you will learn more about IK Animation

IK Animation System update consists of 3 main parts:

  • IK Retargeting

  • Animation Layers Update

  • Post Animation Update

Here's a code reference from the CoreAnimComponent:

private void LateUpdate()
{
    // IK Retargeting
    ikRigData.Retarget();
    UpdateWeaponBone();
    
    // Animation Layers Update
    PreUpdateLayers();
    UpdateLayers();
    ApplyIK();
    
    // Post Animation Update
    PostUpdateLayers();
    
    var pivotOffset = isPivotValid ? weaponTransformData.pivotPoint.localPosition : Vector3.zero;
    ikRigData.AlignWeaponBone(-pivotOffset);
    
    OnPostAnimUpdate?.Invoke();
}

IK Retargeting

At this stage all IK targets (such as MasterIK, RightHandIK, LeftHandIK, etc.) will copy the world transforms of their targets. It's required to preserve the original animation, so the procedural motions do not break the pose from the Animator.

Example: the RightHandIK will copy the position of the right hand, LeftFootIK will copy the transform of the left foot bone.

Let's dive into the code and see how the retargeting is implemented:

CoreAnimComponent.cs
private void LateUpdate()
{
    // IK Retargeting
    ikRigData.Retarget(); // Retargets the character's feet
    UpdateWeaponBone(); // Retargets MasterIK, hands and Weapon Bone
    ...
}

Let's take a look at every step in the UpdateWeaponBone():

Step 1 - Adjust the spine rotation

CoreAnimComponent.cs
private void UpdateWeaponBone()
{
    var spineRoot = ikRigData.spineRoot;
    var rootRot = ikRigData.rootBone.rotation;
    
    Quaternion hipsRotCached = ikRigData.pelvis.rotation;
    ikRigData.pelvis.rotation = rootRot * pelvisPoseMS;
    
    var spineRot = spineRoot.rotation;
    ikRigData.pelvis.rotation = hipsRotCached;
    
    spineRoot.rotation = Quaternion.Slerp(spineRoot.rotation, spineRot, animGraph.graphWeight);
    CoreToolkitLib.RotateInBoneSpace(rootRot, spineRoot, animGraph.GetSpineOffset(), 1f);
    ...
}

pelvisPoseMS is a rotation of the pelvis in mesh space. It's set in the OnPoseSampled() method, when a weapon is equipped and a static pose is sampled.

animGraph.graphWeight is the Playables Sub-System alpha. It's controlled by the Locomotion Layer by default.

Step 2 - Adjust Weapon Bone

CoreAnimComponent.cs
private void UpdateWeaponBone()
{
    ...
    // Parented to the right or left hand
    if (ikRigData.weaponBoneWeight > 0f)
    {
        LocRot basePose = weaponBonePose.FromSpace(ikRigData.rootBone);
        LocRot combinedPose = weaponBoneSpinePose.FromSpace(spineRoot);
        
        combinedPose.position -= basePose.position;
        combinedPose.rotation = Quaternion.Inverse(basePose.rotation) * combinedPose.rotation;
        
        ikRigData.weaponBone.position += combinedPose.position;
        ikRigData.weaponBone.rotation *= combinedPose.rotation;
    }
    ...
}

weaponBonePose - mesh space pose of the Weapon Bone when a stati pose is sampled.

weaponBoneSpinePose - the same Weapon Bone pose, but in spine root space (the first spine bone).

In this step, the system adjusts current Weapon Bone transform based on the original pose, so the IK and Weapon can be aligned perfectly.

Step 3 - Finalize the retargeting

CoreAnimComponent.cs
private void UpdateWeaponBone()
{
    ...
    ikRigData.masterDynamic.Retarget();
    ikRigData.UpdateWeaponParent();
    
    var rotOffset = isAssetValid ? weaponAsset.rotationOffset :         gunData.rotationOffset;
    ikRigData.masterDynamic.Rotate(rotOffset, 1f);
    
    var pivotOffset = isPivotValid ? weaponTransformData.pivotPoint.localPosition : Vector3.zero;
    ikRigData.masterDynamic.Move(pivotOffset, 1f);
    
    ikRigData.rightHand.Retarget();
    ikRigData.leftHand.Retarget();
}

In this step, all what's left to do is to update the MasterIK and Hands IK transforms. The system also adjusts the MasterIK rotation and position based on the weapon pivot point socket.

Tip: MasterIK is retargeted before hands, this allows us moving it safely without affecting the child objects.

Once the IK objects have the right transforms, the system is ready for procedural animation.

Animation Layers Update

CoreAnimComponent.cs
private void LateUpdate()
{
    ...
    PreUpdateLayers();
    UpdateLayers();
    ApplyIK();
    ...
}

PreUpdateLayers() - used to update all animation layers before executing actual procedural logic. This is useful for updating blending values.

UpdateLayers() - used to update all animation layers and execute the procedural logic.

ApplyIK() - applies inverse kinematics.

Tip: UpdateLayers() also adjusts Elbows IK before and after applying the layer.

Post Animation Update

CoreAnimComponent.cs
private void LateUpdate()
{
    ...
    PostUpdateLayers();
    
    var pivotOffset = isPivotValid ? weaponTransformData.pivotPoint.localPosition : Vector3.zero;
    ikRigData.AlignWeaponBone(-pivotOffset);
    
    OnPostAnimUpdate?.Invoke();
    ...
}

PostUpdateLayers() - updates all animation layers after the IK pass.

ikRigData.AlignWeaponBone(-pivotOffset) - aligns weapon bone with the final transform.

OnPostAnimUpdate?.Invoke() - invokes the bound callback function. In the demo, it's used to align the camera.

Last updated