External Publication
Visit Post

Mixing animations with Ragdoll behaviour

jMonkeyEngine Hub June 2, 2026
Source

Thanks. I got a little stuck with the first part of setting up the ragdoll. I’ve created a test to toggle between standing and dropping it in ragdoll mode. But when it stands up after ragdoll mode the model is orientated totally wrong, not with his head up facing towards the camera.

I tried to isolate the relevant code in a single file: package com.beer;

import com.jme3.anim.Armature;
import com.jme3.anim.SkinningControl;
import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.animation.DynamicAnimControl;
import com.jme3.bullet.animation.RangeOfMotion;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;

import java.io.File;

public class RagdollStandingTest extends SimpleApplication {

    public static void main(String[] args) {
        RagdollStandingTest app = new RagdollStandingTest();
        AppSettings settings = new AppSettings(true);
        settings.setRenderer(AppSettings.LWJGL_OPENGL33);
        settings.setResolution(1280, 720);
        settings.setTitle("Ragdoll Standing Test");
        app.setSettings(settings);
        app.setShowSettings(false);
        app.start();
    }

    private BulletAppState bulletAppState;
    private DynamicAnimControl ragdoll;
    private boolean isRagdoll = false;
    private Node model;
    private Armature armature;

    @Override
    public void simpleInitApp() {
        // IMPORTANT: Update this path to where your YBot.glb is located
        assetManager.registerLocator(
                new File("assets").getAbsolutePath() + "/", FileLocator.class);

        // Setup physics
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);

        // Setup scene
        setupLights();
        setupFloor();
        setupCamera();

        // Load character (Mixamo YBot with 100% armature scale)
        model = (Node) assetManager.loadModel("Models/YBot.glb");
        model.setLocalTranslation(0f, 0f, 0f);
        rootNode.attachChild(model);

        rootNode.updateGeometricState();

        // Find skinning control and armature
        Spatial spatial = findSpatialWithSkinning(model);
        SkinningControl skinning = spatial.getControl(SkinningControl.class);
        skinning.setHardwareSkinningPreferred(false);
        armature = skinning.getArmature();
        armature.update();

        // Create DynamicAnimControl
        ragdoll = new DynamicAnimControl();

        // Basic RangeOfMotion for all joints
        RangeOfMotion rom = new RangeOfMotion(1.0f, 0.01f, 0.8f);

        // Link spine and limbs
        ragdoll.link("mixamorig:Spine", 1f, rom);
        ragdoll.link("mixamorig:Spine1", 1f, rom);
        ragdoll.link("mixamorig:Spine2", 1f, rom);
        ragdoll.link("mixamorig:Neck", 0.5f, rom);
        ragdoll.link("mixamorig:Head", 1f, rom);

        ragdoll.link("mixamorig:LeftUpLeg", 1.5f, rom);
        ragdoll.link("mixamorig:LeftLeg", 1.0f, rom);
        ragdoll.link("mixamorig:RightUpLeg", 1.5f, rom);
        ragdoll.link("mixamorig:RightLeg", 1.0f, rom);

        ragdoll.link("mixamorig:LeftShoulder", 0.5f, rom);
        ragdoll.link("mixamorig:RightShoulder", 0.5f, rom);
        ragdoll.link("mixamorig:LeftArm", 1f, rom);
        ragdoll.link("mixamorig:LeftForeArm", 1f, rom);
        ragdoll.link("mixamorig:RightArm", 1f, rom);
        ragdoll.link("mixamorig:RightForeArm", 1f, rom);

        armature.update();

        spatial.addControl(ragdoll);
        bulletAppState.getPhysicsSpace().add(ragdoll);

        // Start in kinematic mode (standing)
        ragdoll.setKinematicMode();

        // Setup keyboard controls
        inputManager.addMapping("Toggle", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addListener(actionListener, "Toggle");

        System.out.println("=== RAGDOLL STANDING TEST ===");
        System.out.println("Press SPACE to toggle between standing and ragdoll");
        System.out.println("Currently: STANDING (kinematic mode)");
    }

    private final ActionListener actionListener = new ActionListener() {
        @Override
        public void onAction(String name, boolean isPressed, float tpf) {
            if (name.equals("Toggle") && !isPressed && ragdoll != null) {
                if (isRagdoll) {
                    // Try to stand back up
                    System.out.println("\n--- Attempting to stand up ---");

                    // Reset skeleton to bind pose
                    armature.applyBindPose();
                    armature.update();

                    // Switch to kinematic mode
                    ragdoll.setKinematicMode();

                    // Try to reset model position
                    model.setLocalTranslation(Vector3f.ZERO);
                    model.setLocalRotation(Quaternion.IDENTITY);

                    // Try to reset torso physics
                    var torso = ragdoll.getTorsoLink().getRigidBody();
                    torso.setPhysicsLocation(new Vector3f(0, 1, 0));
                    torso.setPhysicsRotation(Quaternion.IDENTITY);
                    torso.setLinearVelocity(Vector3f.ZERO);
                    torso.setAngularVelocity(Vector3f.ZERO);

                    isRagdoll = false;
                    System.out.println("PROBLEM: Character should be standing in T-pose but isn't!");

                } else {
                    // Go ragdoll
                    System.out.println("\n--- Going ragdoll ---");
                    ragdoll.setRagdollMode();

                    // Apply damping
                    for (var body : bulletAppState.getPhysicsSpace().getRigidBodyList()) {
                        if (body.getMass() > 0) {
                            body.setDamping(0.5f, 0.7f);
                        }
                    }

                    isRagdoll = true;
                    System.out.println("Currently: RAGDOLL (falls to ground)");
                }
            }
        }
    };

    private void setupLights() {
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.5f, -1f, -0.3f).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);

        AmbientLight ambient = new AmbientLight();
        ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));
        rootNode.addLight(ambient);
    }

    private void setupFloor() {
        Box floorMesh = new Box(20f, 0.1f, 20f);
        Geometry floorGeo = new Geometry("Floor", floorMesh);
        Material floorMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        floorMat.setBoolean("UseMaterialColors", true);
        floorMat.setColor("Diffuse", ColorRGBA.Green);
        floorGeo.setMaterial(floorMat);
        floorGeo.setLocalTranslation(0f, -0.1f, 0f);
        rootNode.attachChild(floorGeo);

        RigidBodyControl floorPhysics = new RigidBodyControl(
                new BoxCollisionShape(new Vector3f(20f, 0.1f, 20f)), 0f);
        floorGeo.addControl(floorPhysics);
        bulletAppState.getPhysicsSpace().add(floorPhysics);
        floorPhysics.setPhysicsLocation(new Vector3f(0f, -0.1f, 0f));
    }

    private void setupCamera() {
        cam.setLocation(new Vector3f(0f, 2f, 6f));
        cam.lookAt(new Vector3f(0f, 1f, 0f), Vector3f.UNIT_Y);
        flyCam.setMoveSpeed(10f);
    }

    private static Spatial findSpatialWithSkinning(Spatial spatial) {
        if (spatial.getControl(SkinningControl.class) != null) return spatial;
        if (spatial instanceof Node) {
            for (Spatial child : ((Node) spatial).getChildren()) {
                Spatial result = findSpatialWithSkinning(child);
                if (result != null) return result;
            }
        }
        return null;
    }
}

Discussion in the ATmosphere

Loading comments...