Aligning feet with ground – inverse kinematics

Inverse kinematics is now a common part of animation systems in games and is a topic that might cover a chapter on its own. In this recipe, we'll look at placing a character's feet in accordance to the ground below. This is useful where an animation would otherwise place them in the air or on sloped ground.

Normally, animations work according to forward kinematics; that is, when a bone near the root of the skeleton rotates, it affects the bones further down the chain. As the name implies, Inverse Kinematics starts at the other end.

Here, we strive toward a desired position for the tip of a chain of bones, and bones further up the chain try to align themselves to suit this.

The most straightforward implementation of this rotates the bones by a small amount on all the axes. It then checks which of the rotations brings the tip closest to the desired position. This is repeated for all the bones in the chain and iterated until it has come close enough.

Getting ready

A model with SkeletonControl is needed for this recipe, and it's recommended that you be familiar with its setup. At the time of writing this recipe, the resident monkey, Jaime, is used.

This recipe uses an experimental feature that at the time of writing this is not part of a core build. To use it, you can build jMonkeyEngine yourself from the sources. You can also get it by enabling nightly builds. Refer to Appendix, Information Fragments, to find out how to change this setting.

How to do it...

Perform the following steps to get the basic IK functionality:

  1. Let's begin by creating a new class that extends AbstractControl, and define a list that will contain the bones we want to use as tip bones.
  2. In the setSpatial method, we add both the feet and toe bones to the list. We also supply some values that KinematicRagdollControl should work with when applying the IK and tell it which bones to work with, as shown in the following code:
    setupJaime(spatial.getControl(KinematicRagdollControl.class));    spatial.getControl(KinematicRagdollControl.class).setIKThreshold(0.01f); spatial.getControl(KinematicRagdollControl.class).setLimbDampening(0.9f); spatial.getControl(KinematicRagdollControl.class).setIkRotSpeed(18);
  3. We create a method called sampleTargetPositions that goes through each of our targets and finds a position the control should try to reach, as shown in the following code:
    public void sampleTargetPositions(){
      float offset = -1.9f;
      for(Bone bone: targets){
        Vector3f targetPosition = bone.getModelSpacePosition().add(spatial.getWorldTranslation());
        CollisionResult closestResult = contactPointForBone(targetPosition, offset);
        if(closestResult != null){
                    spatial.getControl(KinematicRagdollControl.class).setIKTarget(bone, closestResult.getContactPoint().addLocal(0, 0.05f, 0), 2);
        }
      }
  4. Finally, in the created method, we call KinematicRagdollControl and tell it to switch to the Inverse Kinematics mode:
    spatial.getControl(KinematicRagdollControl.class).setIKMode();
  5. To make it reusable, we use the setEnabled method to clear things up when the control is not in use; we make it apply IK when it's enabled again:
    if(enabled){
      sampleTargetPositions();
    } else {
                spatial.getControl(KinematicRagdollControl.class).removeAllIKTargets();
                spatial.getControl(KinematicRagdollControl.class).setKinematicMode();
    }

How it works...

The list we defined contains the bones that we want to have at the end of the chain. These are the bones that the control will try to get as close to the target position as possible. To get the feet at a suitable angle, we not only need the feet but also the toe bones. By trying to align both the feet and the toe bones, we get a better approximation of the ground below.

Unlike most of our controls, we don't actually do anything in the controlUpdate method. This is because the actual alignment is passed on to KinematicRagdollControl. Instead, we do a check each time the control is enabled and see what positions it should try to reach.

For each of the tip bones, we shoot a ray straight up, using an offset to start some way below the ground. The reason we don't just use the bones' position and check below it is because we can't be sure that the model is completely above the ground. Animations might very well push body parts inside the ground, and if we then shot a ray downwards, we wouldn't hit what we want.

Once a target position is found, we supply the bone to KinematicRagdollControl. Along with this is also an integer that defines how long the chain of bones should be that it can modify when trying to reach the target.

There are some more values we supply to KinematicRagdollControl. The IKThreshold value is the distance from the target point where it is okay for it to stop trying.

LimbDampening can be used to effect how much a bone should move in relation to others. Imagine we're stretching out for something on our desk. Our forearms are most likely to perform bigger movements (rotation-wise) than our upper arms. If limbDampening is lower than 1.0, the bones higher up in the chain (and likely bigger) will move less with each update than those closer to the tip bone.

IKRotSpeed defines the rotation steps the control should apply with each turn. A higher value means it'll get closer quicker, but it also means the margin of error becomes higher.

All these values require tweaking to get them right for the application. Implementation is just the first step. The KinematicRagdollControl method also needs some setting up, most importantly, it needs to know the bones it should be able to control.

There's more...

If we've implemented the recipe thus far, we can see that the result is not what we expected. On wobbly legs, resembling rubber or cooked spaghetti, our character slowly adjusts to the ground below it.

The most disturbing thing is probably that the legs seem to go in any direction. Fortunately, this can be remedied with some tweaking. The KinematicRagdollControl function has a method called setJointLimit, which does what it says. It can set the limits to how much rotation can be applied on each axis of a bone. Getting it right for all the bones will take some time though.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.123.126