Finding skeleton joints in world or object space

Started by
7 comments, last by Buckshag 9 years, 6 months ago

I'm trying to experiment with IK on my skinned animations and although I wrote the code, I haven't looked at it in a while and I'm slightly confused by something. What I'm trying to do at the moment as a first step is find the vector between where I want my foot position to be and the thigh joint. This means I need to compute where the pelvis joint is in world space.

My animations are converted and exported in from a COLLADA document (not at runtime) that I have exported from a 3d art package. What I've suddenly become confused about (I don't speak the language of Matrices very well) is what the matrices of the animations for each bone represent. I'm pretty sure I'm doing it the standard way:

Starting from the root bone, I iterate though its children and multiply up the animation matrix with its parent's animation matrix - I call this the world matrix at each level. Then at each level, I multiply the world matrix by the inverse bind matrix and call that the working matrix. This gives me a full pose of working matrices that I pass to my shader - this all works nicely.

So if I wanted to find the position of any joint in world space (or even object space would do at the moment), how would I do that? I thought I could simply multliply up the animation matrices to the level I wanted and that would be that, but when I do that and place a box marker that that point, it's nowhere near the joint on the model. In fact, if I place several boxes at the root, pelvis, lower spine, right thigh, right calf, they all seem to be heading along the x direction with a similar height, not as I expected.

I then tried multiplying each level by the inverse bind matrix (after multiplying by the parent's animation matrix - as per my normal skinning method) and, again, they don't end up in the right positions.

Am I missing something? Do the translation parts of the animation matrices not contain the joint positions (iteratively)?

Thanks for any help

Advertisement

You need to just calculate the world/global space matrices and get the position from those.

If this is what you tried but it doesn't seem to work then you have a bug in your code somewhere smile.png

Maybe your local space matrices are not up to date yet?

You do not need the inverse bind pose matrices, but just the forward kinematics loop.

worldMatrix = localMatrix * parentWorldMatrix; (or other multiplication order, depending on your matrix implementation)

So just the same as how you calculate your world space matrices already (before multiplying with inverse bind pose matrix).

Alternatively you can use your local space transformations (the separate pos/rot/scale) to calculate the world space values, without needing matrices. But that works basically the same.

Thanks for the post.

When you say:

worldMatrix = localMatrix * parentWorldMatrix

Are you talking about the animation matrices? The only two matrices I pull out of Collada are the inverse bind matrices and the animation matrices themselves and this is all I need to get skinning working properly. There is also a matrix against each node of the skeleton in the visual source node (the hierarchy of joint nodes) - but I don't pull that in.

When I examine the animation matrices, the translation part of the matrices don't really look like they relate to the skeleton in the pose. For example, frame 1 of the idle pose which is effectively the character almost in the T-pose, the root bone translation is roughly 0, 85, 0 which is as expected, 85 is about the height of the hips (I actually scale the character when I draw it). The pelvis has the same translation as the root bone but when you look at the right thigh and right calf, their translations are heavy in the x direction with very little in the y direction. I export the model with Y-up and I don't have to transform my model at all to draw him in my engine. I would have expected the thigh and calf bones to have negative y directions.

You should be able to use your animation matrices (if I understand what you mean by "multiply up" - up??) to transform a position (0,0,0,1) to the root-frame space animated position of a particular bone. N.B., for clarification, I'm assuming that the animation matrix for a bone, as "multiplied up" from the root through each child bone to the bone of interest, transforms a position in bone-space to root-bone space. That position needs to be further transformed by the object's world transform to get it in world-space.

You mention, also, that you scale the object. You have to account for that scaling properly.

Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.

I'm not entirely sure what you mean with "animation matrix", but I assume this is the matrix that represents the current frame's offset transformation relative to the parent node.

This is also why you will not see an Y value for your calfs etc. Most likely they are in x or z direction, depending on how the hierarchy has been setup. Depending on the art software (and possibly which plugins) used, the x or z axis will probably point to the child/parent node.

The standard way of doing animation is by decomposing those matrices into pos/rot/scale. So a scale vector, rotation quaternion and scale vector for example. You then put those into key tracks and interpolate between them. Then you rebuild the local space matrices (the ones relative to the parent) from those individually interpolated and blend transform components. Once you have the local space matrices you can calculate the world space matrices as I mentioned.

I never did anything with Collada, so I'm not sure what space the animation data is in there. But it is most likely this relative to parent space. The root node has no parent so it will be the full world transform, which is why you would see 85 in Y on the root.

Most likely you know all this already, but I wanted to be sure so that we're on the same page :)

All you would need to do is find out which nodes you need to follow from the root towards the node you want to calculate the world position of. Then do this for loop with the local * parentWorld matrix and then you can get the translation from that matrix. That will give you the world/global space position.

From what I understand you are already calculating those matrices before multiplying them with the inverse bind pose matrix and passing them to the shader.

If that is the case, it is exactly the same as that.

Thanks for the detailed post. I'm pretty sure we're on the same page. My orientation in the art package (3dsmax) is z up, x to the right and y into the screen. When I export to dae, I specify y as up, which is the coordinate space I use in my (Dx) engine. Either way, it should be z or y that has the additional values for limbs that are 'further away' from the root bone.

I calculate my frame pose in the way we've mentioned above (unskinned at this point) by starting at the root bone animation matrix and iterating down/up through the bones doing this:

this bone's world matrix = animation matrix * parent bone's world matrix

By 'world' I just mean this is what I call my calc variable as I'm iterating. By animation matrix, I mean the matrix that is given per bone, per animation frame (or sample).

Once this pose is complete (and it's at this stage where I blend different poses together), I go through creating the skinned matrix palette by multiplying each by the inverse bind matrix,

All of this works perfectly and I believe is a common solution.

I don't convert my animation matrices to rot/scale/trans at all, I just leave them as matrices. When I blend for either tweening or pose blending, I convert to a quat, slerp and then convert back to a matrix. This may not be the most efficient solution, but it works really well for my current stage (and actually isn't that inefficient really).

So this morning I had another look at the pose matrices (pre-skinned) at a frame of idle animation and strangely, it doesn't look like the translation part of the matrices are changing much at all - and I think I literally just twigged why...

The bone's translation is in local bone coordinate space isn't it, so it isn't going to change. Doesn't it just show where the next bone/joint will be placed in local bone space, and then it is obviously rotated into position. I was under the impression that the translation was in skeleton space, not local bone space.

I think I've just had a slap to the forehead moment...

That's why I wanted to say that the animation values are relative to the parent and that mostly the x or z axis (in DirectX default coordinates) point toward the child node. So it is not always the Y that you expect to move up and down.

You might want to consider changing your blending into local space rather than world space.

Also I'm not sure if blending matrices is very efficient and if that's even correct.

Next to that you have to store a full matrix per keyframe. That's pretty big. Imagine you are only translating 10 units to the right at constant speed.

You would need only two vector3 keyframes basically, assuming you do not split your position into 3 different tracks.

While now you might need say 30 full matrices.

But yeah, that's a bit off-topic now and if it works for your game and doesn't give you any memory or performance issues, then that's all ok :)

My blending is in local space. I blend at the bone level so it's implicitly in bone space. I also don't blend matrices, I convert to quaternion first and then sleep blend and convert back again. I agree this isn't the most efficient or space-saving solution but that's an optimisation I have planned at some point in the future and it's a relatively easy change to make.

Anyway, now that you've helped me figure out my bone spaces again, I'll crack on - thanks

Ah sorry I misunderstood about the blending then, you are right you also mentioned the convert, I was a bit sleepy :)

Let us know how it goes! :)

This topic is closed to new replies.

Advertisement