Bone positions in skeletal animation

Started by
18 comments, last by Steve_Segreto 14 years, 9 months ago
I'm currently trying to create a model viewer for the GLM model format - it's a model format created by Raven Software used in games such as Soldier of Fortune 2 and Star Wars Jedi Knight II: Jedi Outcast. I've been using a header file provided in their Jedi Knight 2 SDK to figure out how to read the model file, and that has turned out well. Also in the same header file, is the format of their animation file (GLA format), and I've been successful in reading the file and getting all the data from it, but I'm having problems orienting the bones and because of this the animations look completely wrong. You can see here (http://i31.tinypic.com/2v83bsg.jpg) what I mean - the lines represent the relationship between bones, the dots represent the actual bone positions. The bone connected downwards from the pelvis bone is the root bone. Using an existing model viewer provided in the same SDK I got the header file from, you can see what the animation should look like: http://i30.tinypic.com/9jdue9.jpg And here's my code for orienting the bones:
// SkeletonJoint::Orientation is a 4x4 matrix (row-major)
// SkeletonJoint::OrientationList is a vector of 4x4 matrices, indexed by bone id
void Ghoul2Animation::CalculateBoneOrientations ( int boneNum, const SkeletonJoint::Orientation& parentBone,
                           SkeletonJoint::OrientationList& newBones, const SkeletonJoint::OrientationList& bones ) const
{
    const SkeletonJoint::JointIndexList& childJoints = joints[boneNum].GetChildJoints(); // joints[] is vector of joint data.
    newBones[boneNum] = parentBone * bones[boneNum];

    for ( SkeletonJoint::JointIndexList::const_iterator it = childJoints.begin();
            it != childJoints.end();
            ++it )
    {
        CalculateBoneOrientations (*it, newBones[boneNum], newBones, bones);
    }
}
The bones parameter gives a vector of matrices read directly from the GLA file - these matrices are already in bone space. Being new to matrix maths I felt this was the most likely place to find the mistake. The vertex skinning is matching the bone positions so I don't think that would be the problem. Can anyone help me? :) [Edited by - Xycaleth on August 7, 2009 3:49:03 AM]
Advertisement
I had a similar problem some time ago while writing skeletal animation.

http://imgur.com/yapVU.jpg

It was because I wasn't loading the skin offset matrix. The skin offset information was stored in the directX (X file) model that I was loading in. So I'd assume there would be something similar in the GLM model format. I then used the skin offset matrix when calculating the final transformation matrix of my bone.

        public void CalculateDerived()        {            if (ParentBone != null) DerivedMatrix = TransformMatrix * ParentBone.DerivedMatrix;            else DerivedMatrix = TransformMatrix ;            FinalTransform = SkinOffsetMatrix * DerivedMatrix;            foreach (Bone pBone in Children) pBone.CalculateDerived();        }


Hopefully this helps. Good luck,

Giawa

Edit: It's worth mentioning that the SkinOffset * Derived is your final bone transformation. However, any children of that bone do *not* use the final bone transformation, they use their parents derived matrix. So I kept the derived matrix and the final transformation matrix separate.

Edit^2: After some looking around, it looks like most formats call it the 'Bone Offset' matrix. However, .x files store the data under 'skinweights', so I chose to call it the skin offset matrix. Sorry for any confusion...

[Edited by - Giawa on August 7, 2009 3:27:13 AM]
I can't find anything like a bone offset in the header file. :( I should probably mention that the bone matrix read from the file might not be in bone space - I'm not sure about the terminology. I need to multiply the matrix read from the file by the base pose matrix for that bone, to get the bone positions you see in the image I posted in my first post. So it's 'relative' to the base pose; would that be bone space?
Nobody has any idea what I might be doing wrong? Or do I need to give any extra information? :(
It may be that the model is a different "handedness" than the system you're using to display it. That is, the model was created in a right-hand system and you're displaying it in a left-hand system. I'm not familiar with GLM or what you're using to display it. Some stuff on the Raven site mentions using XSI, which is generally a right-hand system.

Are you using right- or left-hand system for display?

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 completely sure, but I'm using OpenGL to do all the rendering, which means I'm probably using a left-hand system? The games which used the GLM model format also used OpenGL so I would have thought the model formats were created to be left-hand.

I might have spotted a problem with my code though (not in the code I posted). I've been transforming all the vertices when applying the weights, in world space - am I meant to be doing this in bone space (with parent bone at origin)? It makes more sense to me to be doing this.
If you're using OGL, it's a right-hand based system, so you're good there (if I'm guessing at GLM correctly).

Bones should all be transformed before applying the boneweights. I'm not sure I understand all your references to world space vs bone space, etc. Not sure how GLM works but modeling programs should provide all your bone matrices as local (if that's what you mean by "bone space"). All bones (with the exception of the root) are oriented with respect to their parent (which you have in your code). If the parent gets rotated, the child's entire matrix gets rotated, etc. The root matrix gets rotated and translated in the world and all it's children get rotated and translated relative to the root through your CalcBoneOrientation routine (if your root bone is index 0).

You shouldn't have to worry about "parent bone at origin" (not sure what you mean by that). Your original code appears to be correct. You multiply each bone's matrix by it's parent's matrix (which has been multiplied by it's parent, etc., all the way back to the root). All those matrices (I think that's your newBone list in your code) get recalc'd each frame. That gets each bone ready for the vertex weighting.

Sounds like you're on the right track.

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.

Could you (or someone) check that I'm transforming the model mesh vertices correctly?

// VertexVector = std::vector<VertexData>// boneOrientations = std::vector<Matrix>void Ghoul2ModelInstance::OrientVertices ( const SkeletonJoint::OrientationList& boneOrientations,                         const VertexVector& originalVertices, VertexVector& vertices ){    vertices = originalVertices;        Vector3 p, n;    int boneIndex = 0;    float weight = 0.0f;    float totalWeight = 0.0f;    int surfaceID = -1, numWeights = 0;    for ( VertexVector::size_type i = 0; i < originalVertices.size(); ++i )    {        totalWeight = 0.0f;                numWeights = G2_GetVertWeights (&originalVertexData);        surfaceID = vertexSurfaces;                        vertices.position.Clear();        vertices.normal.Clear();                n = originalVertices.normal;        p = originalVertices.position;        for ( int j = 0; j < numWeights; ++j )        {            weight = G2_GetVertBoneWeight (&originalVertexData, j, totalWeight, numWeights);            boneIndex = G2_GetVertBoneIndex (&originalVertexData, j);            boneIndex = boneReferences.at(surfaceID).at(boneIndex);                        const Matrix& m = boneOrientations[boneIndex];                        vertices.normal += m.TransformVector (n) * weight;            vertices.position += m.TransformPoint (p) * weight;        }    }}


[Edited by - Xycaleth on August 9, 2009 2:06:58 PM]
If your array "boneOrientations" are the final bone transforms, you code looks pretty good.

1. Usually, for consistency, the last weight for each vertex should be 1-totalWeight, where totalWeight = sum of weights for all but the last bone influence. You don't update totalWeight in your code. Without knowing what your G2_GetVertBoneWeight function does, that could be a problem.

2. Just a suggestion, but if your vertex weighting routine is getting the vertices ready for display, it would be much faster to do it in a shader rather than in the CPU.

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.

The boneOrientations array is the same as the newBones array that is in my CalculateBoneOrientations function. By "final bone transformation" do you mean the bones relative to the bone's parent matrix? If so that's what I've been doing, but it's giving me weird results (as in the original post).

1. totalWeight is updated by the G2_GetVertBoneWeight function. It's passed by reference. Here's the code (from the Jedi Knight 2 SDK):
static inline float G2_GetVertBoneWeight( const mdxmVertex_t *pVert, const int iWeightNum, float &fTotalWeight, int iNumWeights ){	float fBoneWeight;	if (iWeightNum == iNumWeights-1)	{		fBoneWeight = 1.0f-fTotalWeight;	}	else	{		int iTemp = pVert->BoneWeightings[iWeightNum];			iTemp|= (pVert->uiNmWeightsAndBoneIndexes >> (iG2_BONEWEIGHT_TOPBITS_SHIFT+(iWeightNum*2)) ) & iG2_BONEWEIGHT_TOPBITS_AND;		fBoneWeight = fG2_BONEWEIGHT_RECIPROCAL_MULT * iTemp;		fTotalWeight += fBoneWeight;	}	return fBoneWeight;}


2. I'm only doing this to test something else I want to get done, so speed isn't an issue :)

This topic is closed to new replies.

Advertisement