Map rigid body's transform to bone

Started by
6 comments, last by Pateman 7 years, 4 months ago

Hello everyone,

I have the following ragdoll structure (green dots - bones, pinkish lines - bone links):

Pp79NC9.jpg

The black boxes are Bullet's rigid bodies (boxes) constructed by creating an AABB of all mesh vertices that are influenced by the bones in a particular, let's say, body part. So for example, if you look at the chest area, there are five bones that make up that body part - I take the vertices, calculate their AABB and construct a rigid body. (In the picture, the head is a circle, but in my code, it's actually a box, too :))

These rigid bodies are linked to each other using six-DOF constraints. The ragdoll itself is working fine, but now, when I run the simulation, I want to somehow map a rigid body's transformation to the corresponding bones to animate the mesh. To continue with the example of the chest area - whenever it is moved by Bullet, I want to apply its center of mass transform to all the bones that are within the chest area.

I was thinking of using a bone's world bind matrix and transforming it like so:


destMatrix = bodyCenterOfMassTransform * boneWorldBindMatrix

but it didn't quite cut the mustard.

Do you have any other ideas?

Thanks,

Patryk

I copied the question from StackExchange in hope of reaching a broader audience

Regards,

Patryk Nusbaum.

www.nusbaum.pl

Advertisement

I was able to figure it out. The process is divided in two parts - the initialization phase and update phase.

During initialization (for every rigid body):

a) Calculate its inverse transform matrix


Utils.transformToMatrix(vars.tempMat4x42, this.rigidBody.getCenterOfMassTransform(vars.vecmathTransform));
vars.tempMat4x42.invert();

b) For each bone assigned to the body, transform the bone's world bind position by the inverse transform matrix and save it for later. Also, save the bone's world bind rotation, and the inverse of rigid body's initial rotation quaternion.


private void initializeBone(final Bone bone, final Matrix4f invRigidBodyTransform) {
    final TempVars vars = TempVars.get();
    final TransformComponents transformComponents = new TransformComponents();

    RagdollUtils.getMatrixForBone(vars.tempMat4x41, bone); // <-- this returns the bone's world bind matrix without scaling applied

    final Vector3f boneTranslation = vars.tempMat4x41.getTranslation(vars.vect3d1);
    invRigidBodyTransform.transformPosition(boneTranslation, transformComponents.getTranslation());
    vars.tempMat4x41.getNormalizedRotation(transformComponents.getRotation());

    this.initialBoneTransforms.put(bone.getIndex(), transformComponents);

    vars.release();
}

During update (for every rigid body):

a) Get the current rigid body's transform matrix, transform the initial position by it.

b) Multiply the inverse of the rigid body's initial rotation by the current rotation.

c) Multiply the initial bone's rotation by the result of the previous operation.

d) Multiply the resulting transformation matrix by the inverse transformation matrix of the model (the owner of the skeleton)


// This method performs stuff described in the points a-c.
void getTransformedBone(final Bone bone, final Vector3f outVec, final Quaternionf outQuat) {
    final TempVars vars = TempVars.get();
    final TransformComponents initialTrans = this.initialBoneTransforms.get(bone.getIndex());


    Utils.transformToMatrix(vars.tempMat4x41, this.rigidBody.getCenterOfMassTransform(vars.vecmathTransform));
    Utils.convert(vars.quat1, vars.vecmathTransform.getRotation(vars.vecmathQuat));
    vars.tempMat4x41.transformPosition(initialTrans.getTranslation(), outVec);


    final Quaternionf rot = this.initialRotation.invert(vars.quat2).mul(vars.quat1, vars.quat1);
    initialTrans.getRotation().mul(rot, outQuat);


    vars.release();
}

The above works well, as you can see here:

ltXrOUF.png

However, when I apply the matrices computed like that to the skin, it gets messed up. Any idea what might be causing the problem? I'm guessing that the matrices are in world space, maybe that's what causing the problem?

Thanks for any suggestions,

Patryk

Regards,

Patryk Nusbaum.

www.nusbaum.pl

Anyone have any ideas? I can provide all the details you need just in case my original description is not detailed enough. :)

Regards,

Patryk Nusbaum.

www.nusbaum.pl

Do not use AABB's. By definition, they will not work. Axis Aligned Bounding Boxes are axis aligned. That means they can never be rotated. I remember spending at least a day figuring this out the hard way.

So, right off the top, your first correction should be to use OBB's instead. Oriented Bounding Boxes can be rotated however you need.

The boxes in your picture are not axis aligned. So maybe that's not the problem.

For rigid animation, it's simply a matter of multiplying the matrices together. You maintain all the matrices separately, But to determine the child's orientation and position, you multiply the parent matrix times the child matrix and the result is the matrix describing the position and orientation of the child.

Quaternions are often used instead of matrices for skinned animation because you need to use SLERP and matrices are terrible at SLERP. But the main difference between quaternions and matrices is that they are like a 3 by 3 matrix and can't hold a position. So, you have to maintain that information separately.

I haven't done animation with quaternions. The closest I've come is playing back Blender animations using the built in humanoid armature. The code for that was fairly complicated. But for the orientations, quaternions should work just like matrices as far as multiplying the parent times the child (although I think quaternion multiplication is backwards which might make it child times parent). Then you have to apply the positions as well or it would just rotate iin place.

I use AABBs only to construct the rigid bodies. They're not involved in the calculations later on. :)

I've got the animation working fine. Like I stated, the problem is that the bones seem to be oriented fine (as you can see in the last picture), so the bone matrices are okay. The problem is that when I apply them to the skin, the mesh is broken.

My understanding is that Bullet returns the rigid bodies' transformation in world space and I use it to orient the bones accordingly. The issue is with the skin. I've tried to multiply the resulting matrices by the corresponding inverse bind matrices, but it hasn't helped.

I'm not sure where the problem is. Probably my reasoning is wrong somewhere, but I can't pinpoint my mistake.

If you guys need more information, I can give you more insights.

Regards,

Patryk Nusbaum.

www.nusbaum.pl

For anyone wondering what I mean by "the mesh is broken", I attached a preview of what's going on:

x1L3G25.png

It looks like there's some weird offset applied, but I can't figure out why.

Regards,

Patryk Nusbaum.

www.nusbaum.pl

Last time I checked Bullet required you that the rigid body center of mass is at its origin. The bone origins are not necessarily at the center of mass. The best example would be the upper arm in your initial picture. So you would need to spawn the rigid bodies at the centers of your AABB and I would probably store the offsets from the rigid body to the bone in the local space of the body. Then before you set the bone transform you can find the offset position as:

NewBoneTranslation = RigidBodyOrientation * LocalBoneOffset + RigidBodyPosition

Another neat trick to fight stretching with ragdolls is to only write back the rigid body orientation into bone rotation. This might also simplify writing back the correct bone translation as the bone translation now only dependents on the skeleton hierarchy.

I am not too much into Bullet, but I assume it would work something along these lines. In general I suggest asking this question on the Bullet forum. Erwin just installed two new moderators which should be able to help you.

HTH,

-Dirk

Hi Dirk,

Thanks for your reply. You are absolutely right, but the problem was elsewhere and I managed to finally figure it out. :)

The first mistake was in the initialization part. It is indeed correct to use the world bind matrix of the bone with scaling applied. Scaling was breaking transformation matrices passed to Bullet, so I assumed that I should get rid of it as well when computing the initial transforms of bones. Second thing was the incorrect usage of getNormalizedRotation(), but that's specific to the math library that I'm using (JOML - https://github.com/JOML-CI/JOML). The third problem was getting the rotation from the bone's world bind matrix after it was multiplied by the inverse transformation matrix of the rigid body. It turned out that it's only needed for the translation part. Here's the modified code:


private void initializeBone(final Bone bone, final Matrix4f invRigidBodyTransform) {
    final TempVars vars = TempVars.get();
    final TransformComponents transformComponents = new TransformComponents();

    //  Get the bone's world bind matrix.
    vars.tempMat4x41.set(bone.getWorldBindMatrix());

    //  Set the unnormalized rotation taken from the world bind matrix as the initial rotation of the bone.
    vars.tempMat4x41.getUnnormalizedRotation(transformComponents.getRotation());

    //  Get the scale from the world bind matrix as well.
    vars.tempMat4x41.getScale(transformComponents.getScale());

    //  In order to get the translation of the bone, we need to convert the world bind matrix into the
    //  rigid body's sppace and get the translation then.
    invRigidBodyTransform.mul(vars.tempMat4x41, vars.tempMat4x41);
    vars.tempMat4x41.getTranslation(transformComponents.getTranslation());

    this.initialBoneTransforms.put(bone.getIndex(), transformComponents);

    vars.release();
}

The update part was flawed as well. Here's the modified code with comments:


void getTransformedBone(final Bone bone, final Vector3f outPos, final Quaternionf outRot,
                        final Vector3f outScale) {
    final TempVars vars = TempVars.get();
    final TransformComponents initialTrans = this.initialBoneTransforms.get(bone.getIndex());

    //  Start by getting the current rigid body's transformation matrix.
    Utils.transformToMatrix(vars.tempMat4x41, this.rigidBody.getCenterOfMassTransform(vars.vecmathTransform));

    //  Transform the initial bone's position by the current rigid body's transformation matrix.
    vars.tempMat4x41.transformPosition(initialTrans.getTranslation(), outPos);

    //  In order to get the transformed rotation, we need to calculate the difference between the
    //  ** rigid body's ** initial rotation and its current rotation, and then multiply it by the ** bone's **
    //  initial rotation.
    vars.tempMat4x41.getUnnormalizedRotation(vars.quat1);
    final Quaternionf diff = this.initialRotation.invert(vars.quat2).mul(vars.quat1, vars.quat3);
    diff.mul(initialTrans.getRotation(), outRot);

    //  Scale doesn't change.
    outScale.set(initialTrans.getScale());

    vars.release();
}

Finally, during the update, the inverse transformation matrix of the entity needs to be multiplied by the bone matrix to orient it correctly.

EDIT

Oh, and of course, don't forget to multiply the matrix by the inverse bind pose, but you should already have that ;)

And that's pretty much it :) Hope it helps someone.

Regards,

Patryk Nusbaum.

www.nusbaum.pl

This topic is closed to new replies.

Advertisement