From Animation to skinned ragdoll

I have a custom animation system, and when an NPC dies, I want to spawn a ragdoll matching its final pose. However, I’m having issues with physics setup:

Bone positioning & mass

  • I take each bone’s finalTransform (using joml.Matrix4f) and apply it to JBullet’s BoxShape, but the collision shapes appear in the wrong place.

The masses into second screenshot to default to zero.

I suspect the issue is converting joml.Matrix4f to JBullet’s javax.vecmath.Matrix4f correctly. How should this be done?

Collision shape choice

  • Should I use BoxShape, CapsuleShape, or something else for ragdoll bones?

Constraints setup

  • I’m struggling with ConeTwistConstraint and Point2PointConstraint.

  • What are typical settings (limits, stiffness) for ragdolls?

Are there working examples for this?

3 Likes

Input values:

  • bones - duplicate bones from the last frame of the animation
  • joml.startMatrix - model matrix for positioning in the world

Render:

I have a custom animation system

If you wrote your own animation system, you’re doubtless aware such systems juggle dozens of coordinate transforms and their inverses:

  • model to world,
  • bone bind to parent (for each bone),
  • bone bind to model (for each bone), and
  • bone local to-bind (for each bone).

I want to spawn a ragdoll matching its final pose.

Converting a character pose to a physics ragdoll requires fluency in combining transforms. The complexity is magnified when coordinate transforms are expressed in diverse ways:

  • matrices,
  • quaternions,
  • Euler angles (6 orderings), and
  • Tait-Bryan angles (6 orderings).

JMonkeyEngine has 2 built-in animation systems, one dating from 2012 and another from 2019. If you used either of those systems, then the DynamicAnimControl class (found in both the jme3-jbullet and Minie libraries) would handle the coordinate transforms for you. There’s even a graphical tool (named DacWizard) to generate and tune Minie ragdolls for any animated model.

Since you’re re-inventing the wheel instead, DynamicAnimControl and DacWizard won’t benefit you directly. However, they’re 100% open source, so perhaps you can benefit from studying the source code.

One trick for troubleshooting ragdoll physics is to start with very simple animated models. Humanoid models involve dozens of bones; their complexity makes them almost impossible to debug. For testing and troubleshooting, start with models having 1-3 bones.

I take each bone’s finalTransform (using joml.Matrix4f) and apply it to JBullet’s BoxShape, but the collision shapes appear in the wrong place.

Is finalTransform defined relative to physics-space coordinates? If not, then it can’t be applied directly to a collision object.

Should I use BoxShape, CapsuleShape, or something else for ragdoll bones?

The best shape depends on the bone you’re trying to simulate. For torsos, I tend to use convex-hull shapes or multi-sphere shapes. For limbs, I generally use convex-hull shapes or capsule shapes.

  • Living creatures do not have sharp corners. Therefore, I would only consider box shapes for particularly blocky robots.
  • Convex-hull shapes can match meshes more closely, but they consume more CPU during simulation.
  • Multi-sphere shapes aren’t available in JBullet because it’s based on a 2010 snapshot of Bullet that pre-dates multi-sphere shapes. For this reason alone, I recommend switching from JBullet to Libbulletjme.

I’m struggling with ConeTwistConstraint and Point2PointConstraint.

DynamicAnimControl uses generic 6-dof constraints exclusively, so I don’t have any advice specific to cone-twist/p2p constraints. When tuning ragdolls, I often start by giving each rotational degree of freedom (DOF) a range of +/- one radian (about 57 degrees of arc) and then reduce the ranges from there.

Are there working examples for this?

All my ragdoll examples use JMonkeyEngine, DynamicAnimControl, and 6-dof constraints. A good starting point might be the TestDac demo app in the MinieExamples project: (see An overview of the demo applications :: The Minie project), which generates and simulates ragdolls for 8 different animated models.

Sorry I’m not good at troubleshooting unfamiliar/undocumented code simply by reading it.

3 Likes

Might be useful for those of us with poor eye sight:

2 Likes

Fun fact, I wrote my own implementation of matrix conversion from javax to joml and vice versa
And that fixed most of the problems with position initialization

For those who will face a similar problem of matrix conversion

public class TransformUtils {

    public static javax.vecmath.Matrix4f toJavaxMatrix4f(org.joml.Matrix4f jomlMatrix) {
        float[] data = new float[16];
        jomlMatrix.get(data);

        javax.vecmath.Matrix4f javaxMatrix = new javax.vecmath.Matrix4f();
        javaxMatrix.set(data);

        javaxMatrix.m03 = jomlMatrix.m30();
        javaxMatrix.m13 = jomlMatrix.m31();
        javaxMatrix.m23 = jomlMatrix.m32();

        return javaxMatrix;
    }

    public static javax.vecmath.Vector3f getOrigin(javax.vecmath.Matrix4f matrix) {
        return new javax.vecmath.Vector3f(matrix.m03, matrix.m13, matrix.m23);
    }

    public static javax.vecmath.Matrix3f getBasis(javax.vecmath.Matrix4f matrix) {
        javax.vecmath.Matrix3f basis = new javax.vecmath.Matrix3f();
        basis.m00 = matrix.m00; basis.m01 = matrix.m01; basis.m02 = matrix.m02;
        basis.m10 = matrix.m10; basis.m11 = matrix.m11; basis.m12 = matrix.m12;
        basis.m20 = matrix.m20; basis.m21 = matrix.m21; basis.m22 = matrix.m22;
        return basis;
    }

    public static void setFull(com.bulletphysics.linearmath.Transform transform, javax.vecmath.Matrix4f matrix) {
        transform.set(matrix);
        transform.origin.set(matrix.m03, matrix.m13, matrix.m23);
    }

    public static org.joml.Matrix4f fromTransformToJomlMatrix(com.bulletphysics.linearmath.Transform transform) {
        javax.vecmath.Matrix3f basis = transform.basis;
        javax.vecmath.Vector3f origin = transform.origin;

        org.joml.Matrix4f result = new org.joml.Matrix4f();
        result.m00(basis.m00); result.m01(basis.m01); result.m02(basis.m02); result.m30(origin.x);
        result.m10(basis.m10); result.m11(basis.m11); result.m12(basis.m12); result.m31(origin.y);
        result.m20(basis.m20); result.m21(basis.m21); result.m22(basis.m22); result.m32(origin.z);
        result.m03(0.0f);
        result.m13(0.0f);
        result.m23(0.0f);
        result.m33(1.0f);

        return result;
    }
}

Dear @sgold
I want to sincerely apologize if my communication on the forum came across as impersonal or unappreciative. That was never my intention. My goal was to keep the discussion concise and fact-based so that others facing similar issues could easily find clear solutions in the future.

However, I realize now that I failed to properly acknowledge the time and effort you put into helping me. While I did “like” your messages as a small gesture of thanks, I should have expressed my gratitude more explicitly. Your guidance was incredibly valuable, and I truly appreciate your patience and expertise in resolving the matrix conversion issue.

Thank you so much for your help — both then and now, with the Libbulletjme collision reference. I’ll make sure to be more mindful of showing appreciation in the future.

5 Likes

I also want to say how much I respect your work. The libraries you’ve created are incredibly valuable to the community, and the fact that you take time to help others—like me—navigate these challenges speaks volumes about your dedication. You’re an outstanding contributor, and I’m genuinely grateful for people like you in open-source development. <3

6 Likes

Well said!

May this incident serve as a reminder to all of us:

  • Courtesy is crucial when dealing with unfamiliar people.
  • Appreciation is vital when dealing with volunteers.
7 Likes