Inverse Kinematics control (WIP)

Hey. I should have done this a long time ago, but i wanted to “do a bit of tweaking” first, which i never came around of doing. So here’s the control in its “as is” form. Hopefully it will come to use for someone, and be improved over time. I’m also attaching a test example. You can select limbs (bones), by clicking on them, and then select where they should move (on a plane) by right clicking. Space resets the scene

Both the example and controller are heavily based on the RagdollController. It’s been quite a while since i did this, and if i have missed to submit something, please tell.

Have fun

Edit: perhaps it should be moved to User Code instead.

IKControl.java
[java]

@Deprecated
[/java]

Patch for KinematicRagdollControl:

[java]

Index: KinematicRagdollControl.java

— KinematicRagdollControl.java (revision 10637)
+++ KinematicRagdollControl.java (working copy)
@@ -52,6 +52,7 @@
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
+import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
@@ -113,11 +114,15 @@
protected float eventDispatchImpulseThreshold = 10;
protected float rootMass = 15;
protected float totalMass = 0;

  • private Map<Bone, Vector3f> ikTargets = new HashMap<Bone, Vector3f>();
  • private float rotSpeed = 7f;
  • private float limbDampening = 0.6f;

public static enum Mode {

Kinematic,

  •    Ragdoll
    
  •    Ragdoll,
    
  •    IK
    

}

public class PhysicsBoneLink implements Savable {
@@ -193,6 +198,8 @@
//if the ragdoll has the control of the skeleton, we update each bone with its position in physic world space.
if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) {
ragDollUpdate(tpf);

  •    } else if (mode == mode.IK &amp;&amp; targetModel.getLocalTranslation().equals(modelPosition)) {
    
  •        ikUpdate(tpf);
    

} else {
kinematicUpdate(tpf);
}
@@ -300,7 +307,83 @@
}
vars.release();
}
+

  • private void ikUpdate(float tpf){

  •    TempVars vars = TempVars.get();
    
  •    Quaternion tmpRot1 = vars.quat1;
    
  •    Quaternion[] tmpRot2 = new Quaternion[]{vars.quat2, new Quaternion()};
    
  •    Iterator i = ikTargets.keySet().iterator();
    
  •    float distance;
    
  •    Bone bone;
    
  •    while (i.hasNext()) {
    
  •        bone = (Bone) i.next();
    
  •        if (!bone.hasUserControl()) {
    
  •            continue;
    
  •        }
    
  •        distance = bone.getModelSpacePosition().distance(ikTargets.get(bone));
    
  •        if (distance &lt; 0.1f) {
    
  •            continue;
    
  •        }
    
  •        updateBone(boneLinks.get(bone.getParent().getName()), tpf * (float) FastMath.sqrt(distance), vars, tmpRot1, tmpRot2, bone, ikTargets.get(bone));
    
  •        Vector3f position = vars.vect1;
    
  •        for (PhysicsBoneLink link2 : boneLinks.values()) {
    
  •            matchPhysicObjectToBone(link2, position, tmpRot1);
    
  •        }
    
  •    }
    
  •    vars.release();
    
  • }

  • public void updateBone(PhysicsBoneLink link, float tpf, TempVars vars, Quaternion tmpRot1, Quaternion[] tmpRot2, Bone tipBone, Vector3f target) {

  •    if (link.bone.getParent() == null) {
    
  •        return;
    
  •    }
    
  •    Quaternion preQuat = link.bone.getLocalRotation();
    
  •    Vector3f vectorAxis;
    
  •    float[] measureDist = new float[]{Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY};
    
  •    for (int dirIndex = 0; dirIndex &lt; 3; dirIndex++) {
    
  •        if (dirIndex == 0) {
    
  •            vectorAxis = Vector3f.UNIT_Z;
    
  •        } else if (dirIndex == 1) {
    
  •            vectorAxis = Vector3f.UNIT_X;
    
  •        } else {
    
  •            vectorAxis = Vector3f.UNIT_Y;
    
  •        }
    
  •        for (int posOrNeg = 0; posOrNeg &lt; 2; posOrNeg++) {
    
  •            tmpRot1.fromAngleAxis(rotSpeed * tpf / (link.rigidBody.getMass() * 2), vectorAxis);
    
  •            tmpRot2[posOrNeg] = link.bone.getLocalRotation().mult(tmpRot1);
    
  •            tmpRot2[posOrNeg].normalizeLocal();
    
  •            rotSpeed = -rotSpeed;
    
  •            link.bone.setLocalRotation(tmpRot2[posOrNeg]);
    
  •            link.bone.update();
    
  •            measureDist[posOrNeg] = tipBone.getModelSpacePosition().distance(target);
    
  •            link.bone.setLocalRotation(preQuat);
    
  •        }
    
  •        if (measureDist[0] &lt; measureDist[1]) {
    
  •            link.bone.setLocalRotation(tmpRot2[0]);
    
  •        } else if (measureDist[0] &gt; measureDist[1]) {
    
  •            link.bone.setLocalRotation(tmpRot2[1]);
    
  •        }
    
  •    }
    
  •    link.bone.getLocalRotation().normalizeLocal();
    
  •    link.bone.update();
    
  •    if (link.bone.getParent() != null) {
    
  •        updateBone(boneLinks.get(link.bone.getParent().getName()), tpf * limbDampening, vars, tmpRot1, tmpRot2, tipBone, target);
    
  •    }
    
  • }

/**

  • Set the transforms of a rigidBody to match the transforms of a bone. this
  • is used to make the ragdoll follow the skeleton motion while in Kinematic
    @@ -615,23 +698,25 @@
    animControl.setEnabled(mode == Mode.Kinematic);

baseRigidBody.setKinematic(mode == Mode.Kinematic);

  •    TempVars vars = TempVars.get();
    
  •    for (PhysicsBoneLink link : boneLinks.values()) {
    
  •        link.rigidBody.setKinematic(mode == Mode.Kinematic);
    
  •        if (mode == Mode.Ragdoll) {
    
  •            Quaternion tmpRot1 = vars.quat1;
    
  •            Vector3f position = vars.vect1;
    
  •            //making sure that the ragdoll is at the correct place.
    
  •            matchPhysicObjectToBone(link, position, tmpRot1);
    
  •    if (mode != Mode.IK) {
    
  •        TempVars vars = TempVars.get();
    
  •        for (PhysicsBoneLink link : boneLinks.values()) {
    
  •            link.rigidBody.setKinematic(mode == Mode.Kinematic);
    
  •            if (mode == Mode.Ragdoll) {
    
  •                Quaternion tmpRot1 = vars.quat1;
    
  •                Vector3f position = vars.vect1;
    
  •                //making sure that the ragdoll is at the correct place.
    
  •                matchPhysicObjectToBone(link, position, tmpRot1);
    
  •            }
    

}

  •        vars.release();
    
  •        for (Bone bone : skeleton.getRoots()) {
    
  •            RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll || mode == Mode.IK);
    
  •        }
    

}

  •    vars.release();
    
  •    for (Bone bone : skeleton.getRoots()) {
    
  •        RagdollUtils.setUserControl(bone, mode == Mode.Ragdoll);
    
  •    }
    

}

/**
@@ -699,6 +784,16 @@
setMode(Mode.Ragdoll);
}
}
+

  • /**
  • * Sets the control into Inverse Kinematics mode. The affected bones are affected by IK.
    
  • * physics.
    
  • */
    
  • public void setIKMode() {
  •    if (mode != Mode.IK) {
    
  •        setMode(Mode.IK);
    
  •    }
    
  • }

/**

  • retruns the mode of this control
    @@ -783,6 +878,9 @@
    return null;
    }
  • public Bone getBone(String boneName) {
  •    return boneLinks.get(boneName).bone;
    
  • }
    /**
  • For internal use only specific render for the ragdoll(if debugging)

@@ -801,7 +899,60 @@
control.setApplyPhysicsLocal(applyLocal);
return control;
}
+

  • public void setIKTarget(Bone bone, Vector3f worldPoint) {

  •    ikTargets.put(bone, worldPoint.subtract(targetModel.getWorldTranslation()));
    
  •    while (bone.getParent() != null) {
    
  •        Quaternion tmpRot1 = new Quaternion();
    
  •        Vector3f position = new Vector3f();
    
  •        if (!bone.hasUserControl()) {
    
  •            matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1);
    
  •            bone.setUserControl(true);
    
  •        }
    
  •        bone = bone.getParent();
    
  •    }
    
  •    setIKMode();
    
  • }

  • public void removeIKTarget(Bone bone) {

  •    ikTargets.remove(bone);
    
  •    applyUserControl();
    
  • }

  • public void removeAllIKTargets(){

  •    ikTargets.clear();
    
  •    applyUserControl();
    
  • }

  • public void applyUserControl() {

  •    for (Bone bone : skeleton.getRoots()) {
    
  •        RagdollUtils.setUserControl(bone, false);
    
  •    }
    
  •    if (ikTargets.isEmpty()) {
    
  •        setKinematicMode();
    
  •    } else {
    
  •        Iterator iterator = ikTargets.keySet().iterator();
    
  •        TempVars vars = TempVars.get();
    
  •        while (iterator.hasNext()) {
    
  •            Bone bone = (Bone) iterator.next();
    
  •            while (bone.getParent() != null) {
    
  •                Quaternion tmpRot1 = vars.quat1;
    
  •                Vector3f position = vars.vect1;
    
  •                matchPhysicObjectToBone(boneLinks.get(bone.getName()), position, tmpRot1);
    
  •                bone.setUserControl(true);
    
  •                bone = bone.getParent();
    
  •            }
    
  •        }
    
  •        vars.release();
    
  •    }
    
  • }

/**

  • serialize this control

@@ -828,6 +979,8 @@
oc.write(eventDispatchImpulseThreshold, “eventDispatchImpulseThreshold”, 10);
oc.write(rootMass, “rootMass”, 15);
oc.write(totalMass, “totalMass”, 0);

  •    oc.write(rotSpeed, "rotSpeed", 7f);
    
  •    oc.write(limbDampening, "limbDampening", 0.6f);
    

}

/**
@@ -860,5 +1013,7 @@
eventDispatchImpulseThreshold = ic.readFloat(“eventDispatchImpulseThreshold”, 10);
rootMass = ic.readFloat(“rootMass”, 15);
totalMass = ic.readFloat(“totalMass”, 0);

  •    rotSpeed = ic.readFloat("rotSpeed", 7f);
    
  •    limbDampening = ic.readFloat("limbDampening", 0.6f);
    

}
}

[/java]

TestIK2.java
[java]
/*

  • Copyright © 2009-2010 jMonkeyEngine
  • All rights reserved.
  • Redistribution and use in source and binary forms, with or without
  • modification, are permitted provided that the following conditions are
  • met:
    • Redistributions of source code must retain the above copyright
  • notice, this list of conditions and the following disclaimer.
    • Redistributions in binary form must reproduce the above copyright
  • notice, this list of conditions and the following disclaimer in the
  • documentation and/or other materials provided with the distribution.
    • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
  • may be used to endorse or promote products derived from this software
  • without specific prior written permission.
  • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  • “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    */
    package ik;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.Bone;
import com.jme3.animation.LoopMode;
import com.jme3.bullet.BulletAppState;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.RagdollCollisionListener;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.KinematicRagdollControl;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Plane;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.debug.SkeletonDebugger;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import mygame.PhysicsTestHelper;

/**

  • PHYSICS RAGDOLLS ARE NOT WORKING PROPERLY YET!

  • @author normenhansen
    */
    public class TestIK2 extends SimpleApplication implements RagdollCollisionListener, AnimEventListener {

    private BulletAppState bulletAppState;
    Material matBullet;
    Node model;
    Node model2;
    KinematicRagdollControl ikControl;
    float bulletSize = 1f;
    Material mat;
    Material mat3;
    private Sphere bullet;
    private SphereCollisionShape bulletCollisionShape;

    Node targetNode = new Node("");
    Vector3f targetPoint;
    Bone mouseBone;
    Vector3f oldMousePos;

    Plane plane;

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

    public void simpleInitApp() {
    initCrossHairs();
    initMaterial();

     cam.setLocation(new Vector3f(0.26924422f, 6.646658f, 22.265987f));
     cam.setRotation(new Quaternion(-2.302544E-4f, 0.99302495f, -0.117888905f, -0.0019395084f));
    
    
     bulletAppState = new BulletAppState();
     bulletAppState.setEnabled(true);
     
     stateManager.attach(bulletAppState);
     bullet = new Sphere(32, 32, 1.0f, true, false);
     bullet.setTextureMode(TextureMode.Projected);
     bulletCollisionShape = new SphereCollisionShape(1.0f);
    

// bulletAppState.getPhysicsSpace().enableDebug(assetManager);
PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());
setupLight();

    model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");

    plane = new Plane();

    AnimControl control = model.getControl(AnimControl.class);
    SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton());
    Material mat2 = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
    mat2.setColor("Color", ColorRGBA.Green);
    skeletonDebug.setMaterial(mat2);
    skeletonDebug.setLocalTranslation(model.getLocalTranslation());
    
    Geometry g = new Geometry("", new Box(0.1f,0.1f,0.1f));
    g.setMaterial(mat2);
    targetNode.attachChild(g);
    rootNode.attachChild(targetNode);
    
    ikControl = new KinematicRagdollControl(0.5f);
    setupSinbad(ikControl);
    ikControl.addCollisionListener(this);
    model.addControl(ikControl);

    getPhysicsSpace().add(ikControl);
    speed = 1.3f;

    rootNode.attachChild(model);
    flyCam.setMoveSpeed(50);

    animChannel = control.createChannel();
    
    animChannel.setAnim("IdleTop");
    control.addListener(this);

    inputManager.addListener(new ActionListener() {

        public void onAction(String name, boolean isPressed, float tpf) {
            if (name.equals("toggle") && isPressed) {
                animChannel.setAnim("IdleTop");
                ikControl.blendToKinematicMode(0.5f);
            }

            if (name.equals("stop") && isPressed) {
                ikControl.setEnabled(!ikControl.isEnabled());
                ikControl.setIKMode();
            }

            if (name.equals("one") && isPressed) {
                //ragdoll.setKinematicMode();
                targetPoint = model.getWorldTranslation().add(new Vector3f(0,2,4));
                targetNode.setLocalTranslation(targetPoint);
                ikControl.setIKTarget(ikControl.getBone("Hand.L"), targetPoint);
                ikControl.setIKMode();
            }
            if (name.equals("two") && isPressed) {
                //ragdoll.setKinematicMode();
                targetPoint = model.getWorldTranslation().add(new Vector3f(-3,3,0));
                targetNode.setLocalTranslation(targetPoint);
                ikControl.setIKTarget(ikControl.getBone("Hand.R"), targetPoint);
                ikControl.setIKMode();
            }
            
            if (name.equals("shoot") && !isPressed) {
                ikControl.setRagdollMode();
            } 
        }
    }, "toggle", "feet", "rag","shoot", "drag", "stop", "bullet+", "bullet-", "boom", "one", "two", "three", "four");
    inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("feet", new KeyTrigger(KeyInput.KEY_F));
    inputManager.addMapping("one", new KeyTrigger(KeyInput.KEY_1));
    inputManager.addMapping("two", new KeyTrigger(KeyInput.KEY_2));
    inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_H));
}

private void setupLight() {
    DirectionalLight dl = new DirectionalLight();
    dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
    dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
    rootNode.addLight(dl);
}

private PhysicsSpace getPhysicsSpace() {
    return bulletAppState.getPhysicsSpace();
}

public void initMaterial() {

    matBullet = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    TextureKey key2 = new TextureKey("Textures/Terrain/Rock/Rock.PNG");
    key2.setGenerateMips(true);
    Texture tex2 = assetManager.loadTexture(key2);
    matBullet.setTexture("ColorMap", tex2);
}

protected void initCrossHairs() {
    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
    BitmapText ch = new BitmapText(guiFont, false);
    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
    ch.setText("+"); // crosshairs
    ch.setLocalTranslation( // center
            settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
            settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
    guiNode.attachChild(ch);
}

private void setupSinbad(KinematicRagdollControl ragdoll) {
    ragdoll.addBoneName("Ulna.L");
    ragdoll.addBoneName("Ulna.R");
    ragdoll.addBoneName("Chest");
    ragdoll.addBoneName("Foot.L");
    ragdoll.addBoneName("Foot.R");
    ragdoll.addBoneName("Hand.R");
    ragdoll.addBoneName("Hand.L");
    ragdoll.addBoneName("Neck");
    ragdoll.addBoneName("Root");
    ragdoll.addBoneName("Stomach");
    ragdoll.addBoneName("Waist");
    ragdoll.addBoneName("Humerus.L");
    ragdoll.addBoneName("Humerus.R");
    ragdoll.addBoneName("Thigh.L");
    ragdoll.addBoneName("Thigh.R");
    ragdoll.addBoneName("Calf.L");
    ragdoll.addBoneName("Calf.R");
    ragdoll.addBoneName("Clavicle.L");
    ragdoll.addBoneName("Clavicle.R");

}

float elTime = 0;
boolean forward = true;
AnimControl animControl;
AnimChannel animChannel;
Vector3f direction = new Vector3f(0, 0, 1);
Quaternion rotate = new Quaternion().fromAngleAxis(FastMath.PI / 8, Vector3f.UNIT_Y);
boolean dance = true;

int collisionCounter = 100;

public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {

    if (channel.getAnimationName().equals("StandUpBack") || channel.getAnimationName().equals("StandUpFront")) {
        channel.setLoopMode(LoopMode.DontLoop);
        channel.setAnim("IdleTop", 5);
        channel.setLoopMode(LoopMode.Loop);
    }
}

public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
}

@Override
public void collide(Bone bone, PhysicsCollisionObject object, PhysicsCollisionEvent event) {
}

}

[/java]

Bone.java (patch):
[java]
Index: Bone.java

— Bone.java (revision 10637)
+++ Bone.java (working copy)
@@ -380,7 +380,7 @@
/**

  • Updates world transforms for this bone and it’s children.
    */
  • final void update() {
  • public final void update() {
    this.updateWorldVectors();

for (int i = children.size() - 1; i >= 0; i–) {
@@ -688,4 +688,23 @@
output.write(initialScale, “initialScale”, new Vector3f(1.0f, 1.0f, 1.0f));
output.writeSavableArrayList(children, “children”, null);
}
+

  • public Vector3f getInitialPos() {
  • return initialPos;
  • }
  • public Quaternion getInitialRot() {
  • return initialRot;
  • }
  • public void setLocalRotation(Quaternion rot){
  • if (!userControl) {
  • throw new IllegalStateException(“User control must be on bone to allow user transforms”);
  • }
  • this.localRot = rot;
  • }
  • public boolean hasUserControl(){
  • return userControl;
  • }
    }
    [/java]

PhysicsTestHelper.java (patch):

[java]
Index: PhysicsTestHelper.java

— PhysicsTestHelper.java (revision 10637)
+++ PhysicsTestHelper.java (working copy)
@@ -42,8 +42,10 @@
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
@@ -106,6 +108,49 @@

}

+ public static void createPhysicsIKTestWorld(Node rootNode, AssetManager assetManager, PhysicsSpace space) {

  • AmbientLight light = new AmbientLight();
  • light.setColor(ColorRGBA.LightGray);
  • rootNode.addLight(light);
  • Material material = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);
  • material.setTexture(“ColorMap”, assetManager.loadTexture(“Interface/Logo/Monkey.jpg”));
  • Box floorBox = new Box(140, 0.25f, 140);
  • Geometry floorGeometry = new Geometry(“Floor”, floorBox);
  • floorGeometry.setMaterial(material);
  • floorGeometry.setLocalTranslation(0, -5, 0);
  • floorGeometry.rotate(0.0f, 0, 0.2f);
    +// Plane plane = new Plane();
    +// plane.setOriginNormal(new Vector3f(0, 0.25f, 0), Vector3f.UNIT_Y);
    +// floorGeometry.addControl(new RigidBodyControl(new PlaneCollisionShape(plane), 0));
  • floorGeometry.addControl(new RigidBodyControl(0));
  • rootNode.attachChild(floorGeometry);
  • space.add(floorGeometry);
  • //movable boxes
  • for (int i = 0; i < 3; i++) {
  • Box box = new Box(0.25f, 0.25f, 0.25f);
  • Geometry boxGeometry = new Geometry(“Box”, box);
  • boxGeometry.setMaterial(material);
  • boxGeometry.setLocalTranslation(i, 5, -3);
  • //RigidBodyControl automatically uses box collision shapes when attached to single geometry with box mesh
  • boxGeometry.addControl(new RigidBodyControl(2));
  • rootNode.attachChild(boxGeometry);
  • space.add(boxGeometry);
  • }
  • //immovable sphere with mesh collision shape
  • Sphere sphere = new Sphere(8, 8, 1);
  • Geometry sphereGeometry = new Geometry(“Sphere”, sphere);
  • sphereGeometry.setMaterial(material);
  • sphereGeometry.setLocalTranslation(4, -4, 2);
  • sphereGeometry.addControl(new RigidBodyControl(new MeshCollisionShape(sphere), 0));
  • rootNode.attachChild(sphereGeometry);
  • space.add(sphereGeometry);
  • }
    }
    [/java]
6 Likes

The methos is missing from the Orginal JmeTest :



PhysicsTestHelper.createPhysicsIKTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace());



:stuck_out_tongue:

Do you foresee this becoming some kind of general purpose solution to IK? Similar to how the ragdoll currently works? Perhaps those two solutions could be merged?

@rickard nice!!

Cool,



if this works maybe I could use it in blender importer to properly load Inverse Kinematics Constraint :slight_smile:

Yes please!

Looks very cool.

However when I try to compile this but I get several errors. Bones seem to be missing some functions like bone.hasUserControl, bone.setLocalRotation, bone.update.



I have updated to nightly without improvements. Do you have any idea where I am messing this up?

Woah. I just dug up this thread because i had lost my source code. Didn’t know there were questions, and thought the forums would have notified me.
I might do some additional work on this now as i want it for VR experiments.

Yes, i set out with the intention of a general IK solution, and based it off the ragdollcontrol because it suited my purposes. It’s been long since i did any work on it, but i think they should be able to be merged.

I missed to inform i had done changes to Bone.java. Here’s a patch, in case someone feels like using it ( i really should get that bitbucket repo up and running);

[java]
Index: Bone.java

— Bone.java (revision 10637)
+++ Bone.java (working copy)
@@ -380,7 +380,7 @@
/**
* Updates world transforms for this bone and it’s children.
*/

  • final void update() {
  • public final void update() {
    this.updateWorldVectors();

    for (int i = children.size() - 1; i >= 0; i--) {
    

@@ -688,4 +688,23 @@
output.write(initialScale, “initialScale”, new Vector3f(1.0f, 1.0f, 1.0f));
output.writeSavableArrayList(children, “children”, null);
}
+

  • public Vector3f getInitialPos() {
  •    return initialPos;
    
  • }
  • public Quaternion getInitialRot() {
  •    return initialRot;
    
  • }
  • public void setLocalRotation(Quaternion rot){
  •    if (!userControl) {
    
  •        throw new IllegalStateException("User control must be on bone to allow user transforms");
    
  •    }
    
  •    this.localRot = rot;
    
  • }
  • public boolean hasUserControl(){
  •    return userControl;
    
  • }
    }
    [/java]

Edit: also added a patch for PhysicsTestHelper to the topic

1 Like

Ok, so the merging into KinematicRagdollControl was easier than i thought.
It all seems to be working all right now, except for the blending back from IK mode to kinematic. For some reason the physicscharacter is falling while it’s in IK mode ( as if in ragdoll mode), and then blends back from its position on the ground to standing.
I’ll dig into it more tomorrow, but if anyone knows how to fix it, it should speed things up.

2 Likes

There. Patch for KinematicRagdollControl in topic. Still got to add iktargets to read/write methods, but otherwise it seems to work as intended.

3 Likes