[WIP] Inverse Kinematics

@Kaelthas here you go. Sorry I didn’t have time to make it as generic as you wanted (and very tired atm), but hopefully how I did it can at least help.

Once again:

Is what I modeled it against

TestIKControl.java
[java]package mygame;

import com.jme3.app.FlyCamAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

//
// @author wezrule
//
public class TestIKControl extends SimpleApplication {

private final int NUM_BONES = 4;     // How many bones in the chain?
private final int NUM_AFFECTED = 4;  // How many bones (starting from the end) are affected

public static void main(String[] args) {
    TestIKControl app = new TestIKControl();
    app.start();
}

public TestIKControl() {
    super(new FlyCamAppState());
}

@Override
public void simpleInitApp() {
    setupCamera();

    // Create rootBone placeholder node
    Node rootBone = new Node("RootBone");
    rootNode.attachChild(rootBone);

    Node lastBone = createBoneChain(rootBone);

    // Add IK control
    rootBone.addControl(new IKControl(lastBone, NUM_AFFECTED, this));
}

private void setupCamera() {
    flyCam.setMoveSpeed(10);
    setPauseOnLostFocus(false);
    flyCam.setDragToRotate(true);
    cam.setLocation(new Vector3f(0, 0, 20));
}

private Node createBoneChain(Node rootBone) {

    Node previousBone = rootBone;
    for (int i = 0; i < NUM_BONES; ++i) {
        previousBone = addBone(previousBone);

        if (i > 0) {
            previousBone.move(0, 1, 0);     // This should probably use the extents, but I know they are 1 unit tall
        }
    }

    return previousBone;
}

private Node addBone(Node previousBone) {
    Node bone = new Node("bone");
    previousBone.attachChild(bone);

    // Add the graphical bone
    Spatial boneSpatial = assetManager.loadModel("Models/bone.j3o");
    Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    m.setColor("Color", ColorRGBA.randomColor());
    boneSpatial.setMaterial(m);
    bone.attachChild(boneSpatial);

    return bone;
}

}[/java]

IKControl.java
[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.input.InputManager;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;

//
// @author wezrule
//
public class IKControl extends AbstractControl {

private Node lastBone;
private SimpleApplication myApp;
private int numBonesAffected;

public IKControl(Node lastBone, int numBonesAffected, SimpleApplication app) {
    this.lastBone = lastBone;
    this.myApp = app;
    this.numBonesAffected = numBonesAffected;
}

@Override
protected void controlUpdate(float tpf) {

    Camera cam = myApp.getCamera();
    InputManager inputManager = myApp.getInputManager();

    float viewToProjectionZ = cam.getViewToProjectionZ(20); // Project 20 units in front of the camera
    Vector3f mousePos = cam.getWorldCoordinates(inputManager.getCursorPosition(), viewToProjectionZ);

    int iterations = 0;
    Node parent = lastBone;
    while (parent != spatial && iterations < numBonesAffected) {    // spatial is the root bone
        Vector3f e = getEffectorPosition();                // effector
        Vector3f j = parent.getWorldTranslation().clone(); // current join position
        Vector3f t = mousePos; //target

        Vector3f currentDir = e.subtract(j).normalizeLocal();
        Vector3f target = t.subtract(j).normalizeLocal();

        float angle = currentDir.angleBetween(target);
        Vector3f cross = currentDir.cross(target);

        Quaternion q = new Quaternion().fromAngleAxis(angle, cross);
        Quaternion targetWorld = q.mult(parent.getParent().getWorldRotation());
        Quaternion diff = parent.getParent().getWorldRotation().inverse().mult(targetWorld);
        parent.setLocalRotation(diff.mult(parent.getLocalRotation()));

        parent = parent.getParent();
        ++iterations;
    }
}

private Vector3f getEffectorPosition() {
    // Get the last one
    return lastBone.getWorldTranslation().add(lastBone.getWorldRotation().mult(Vector3f.UNIT_Y));
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}

}
[/java]

// Bone blend file
https://dl.dropboxusercontent.com/u/53339759/bone.blend

2 Likes