I'm currently designing the rigging system of my game engine.

Currently, all bones and joints store a local position and rotation matrix. These are multiplied (rotation * position) to create a local space matrix. This is then multiplied by the local space matrix of the part it is attached to. This leads all the way back to the base bone, whose local space matrix is multiplied back to the world matrix of the model. Essentially, every joint and bone stores a matrix which represents its position and rotation relative to the part it is attached to.

To clarify: Final world matrix of Bone 1 = Model (world matrix) * Base Bone (local matrix) * Joint (local matrix) * Bone 1 (local matrix).

This is undoubtedly wrong, as the mesh is horribly contorted and the bones seem to be lost in the world space, translating strangely as I adjust the joint's rotation.

Basically I was under the impression that given a matrix that transforms Object A into world space, if you know the matrix that transforms from Object A to Object B, all you have to do to transform Object B into world space is use Object A (world matrix) * Object B (local to Object A matrix).

Little bit of code

Bone.cpp, UpdateMatrices() function

if (socketPtr != nullptr)
    posMatrix = XMMatrixIdentity();
    rotMatrix = XMMatrixRotationRollPitchYawFromVector(socketVector);
    localMatrix = rotMatrix * posMatrix;
    worldMatrix = socketPtr->GetWorldMatrix() * localMatrix;
    posMatrix = XMMatrixTranslationFromVector(rigPtr->GetBasePositionVector());
    rotMatrix = XMMatrixTranslationFromVector(rigPtr->GetBaseRotationVector());
    localMatrix = rotMatrix * posMatrix;
    worldMatrix = rigPtr->GetWorldMatrix() * localMatrix;

Where socketPtr is a pointer to the joint to which the bone is socketed, if this is null then the bone is the base bone of the rig. socketVector represents the pitch, yaw, and roll rotation offset of the bone in the socket.

Joint.cpp, UpdateMatrices() function

if(isAttachedAtEnd) posMatrix = XMMatrixTranslationFromVector(attachPtr->GetEndPosVector());
else posMatrix = XMMatrixIdentity();
XMMATRIX attachRotationMatrix = XMMatrixRotationRollPitchYawFromVector(attachVector);
XMMATRIX jointRotationMatrix = XMMatrixRotationRollPitchYawFromVector(jointRotVector);
rotMatrix = attachRotationMatrix * jointRotationMatrix;
localMatrix = rotMatrix * posMatrix;
worldMatrix = attachPtr->GetWorldMatrix() * localMatrix;

Where attachPtr is a pointer to the Bone that the joint is attached to, attachVector represents the pitch, yaw and roll offset of the attachment to the bone, and the jointRotVector is the pitch, yaw, roll rotation of the joint.

I know it might be hard for you to reproduce this, getting a "minimal working example" is rather difficult for a project as complex as this, because there are hundreds of lines required for interfacing through the WinAPI and getting all the DirectX environment stuff set up.

  • \$\begingroup\$ It sounds like you're doing this correctly at the conceptual level, you might just have a bug in your implementation. For example, you might have written A * B where you needed B * A, since matrix multiplication is non-commutative. Want to show us a minimal complete verifiable example in code so we can help you debug the error? \$\endgroup\$ – DMGregory Dec 16 '20 at 11:12
  • \$\begingroup\$ @DMGregory sorry for the late response, I'm in a different time zone I suspect. Anyways I added some code. Hopefully you can get a better understanding of what I mean, even though it's not really reproduceable. \$\endgroup\$ – ropadene Dec 17 '20 at 5:39
  • \$\begingroup\$ I haven't seen a system where bones have two sets of rotation - their own relative to an attachment socket, and the rotation of the attachment socket relative to the parent bone. Usually I see the rotation of the bone expressed relative to the parent bone directly, with no intermediate. I'm not sure what matrix multiplication convention you're using, but the order in which you're multiplying your position matrix looks inconsistent with the order you're applying your two rotations or the parent world matrix. \$\endgroup\$ – DMGregory Dec 17 '20 at 14:42
  • \$\begingroup\$ @DMGregory Hey thanks for the reply. I'm not sure what you mean by inconsistent. I create the total offset matrix by applying the rotation and then the translation, so (rotation * translation). Then I multiply these offset matrices, in the order of the lowest order first (as in, the model world matrix * the base bone offset matrix * the joint attached to the base bone, and so on). Should I do it differently? Should I just multiply all rotation matrices in that order, and then multiply that by the product of all translation matrices? Thanks \$\endgroup\$ – ropadene Dec 18 '20 at 5:42
  • \$\begingroup\$ If you do world = parent * local then local = translation * rotation, not rotation * translation. We want to rotate around 0, then move that 0 point to our local origin. If we do it in the opposite order, we orbit around the parent's (0,0,0) instead of our local origin point. \$\endgroup\$ – DMGregory Dec 18 '20 at 12:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.