Skeletal animation: from quaternions to matrices

Started by
-1 comments, last by consequence 13 years, 10 months ago
Edit: False alarm. The first bit of code I posted is, in fact, correct, but is the transpose of what I needed for the matrix operations I am using. I suppose that should have been clear much earlier, but I've only just begun to learn linear algebra.

I'm using the MD5 format for animated meshes, so until recently I've been skinning on the CPU with quaternions and weight vectors. However, I want to move the skinning to the GPU, so I want to work with matrices instead. I've been trying to make the change via the methods discussed in this thread, but I can't get things to work properly.

I think I must be creating the matrices incorrectly. The MD5 format gives joints as a quaternion and a position vector, so I assumed it was just VQS without any scaling. Therefore, I'm using the following code to convert a joint to a transformation matrix:

  float x = j.orient.x, y = j.orient.y, z = j.orient.z, w = j.orient.w;  float mat[] = { 1-2*y*y-2*z*z, 2*x*y-2*w*z, 2*x*z+2*w*y, j.pos.x,                  2*x*y+2*w*z, 1-2*x*x-2*z*z, 2*y*z-2*w*x, j.pos.y,                  2*x*z-2*w*y, 2*y*z+2*w*x, 1-2*x*x-2*y*y, j.pos.z,                  0,           0,             0,           1        };


j.orient is the quaternion, and j.pos is the position vector. This produces incorrect results for both translations and rotations, but if I invert pos.y it seems to translate properly. I've also tried a different approach, using the joint's position rather than the origin as the center for rotation:

  float sqw = j.orient.w*j.orient.w;  float sqx = j.orient.x*j.orient.x;  float sqy = j.orient.y*j.orient.y;  float sqz = j.orient.z*j.orient.z;  mat[0] =  sqx - sqy - sqz + sqw; // since sqw + sqx + sqy + sqz =1  mat[5] = -sqx + sqy - sqz + sqw;  mat[10] = -sqx - sqy + sqz + sqw;  double tmp1 = j.orient.x*j.orient.y;  double tmp2 = j.orient.z*j.orient.w;  mat[4] = 2.0 * (tmp1 + tmp2);  mat[1] = 2.0 * (tmp1 - tmp2);  tmp1 = j.orient.x*j.orient.z;  tmp2 = j.orient.y*j.orient.w;  mat[8] = 2.0 * (tmp1 - tmp2);  mat[2] = 2.0 * (tmp1 + tmp2);  tmp1 = j.orient.y*j.orient.z;  tmp2 = j.orient.x*j.orient.w;  mat[9] = 2.0 * (tmp1 + tmp2);  mat[6] = 2.0 * (tmp1 - tmp2);  float a1,a2,a3;  a1 = j.pos.x; a2 = j.pos.y; a3 = j.pos.z;  mat[3] = a1 - a1 * mat[0] - a2 * mat[1] - a3 * mat[2];  mat[7] = a2 - a1 * mat[4] - a2 * mat[5] - a3 * mat[6];  mat[11] = a3 - a1 * mat[8] - a2 * mat[9] - a3 * mat[10];  mat[12] = mat[13] = mat[14] = 0.0;  mat[15] = 1.0;


(That code is taken from here.) Using this method, I get correct rotation, but translation is either inverted or completely negated depending on the axis.

At initialization, I create a matrix from each joint in its bind position using the code above, and then invert it; I also create a matrix from each joint in its animated position in each frame. During animation, for each vertex, I:
- multiply the inverted bind position matrix of a joint affecting this vertex by its animated matrix,
- multiply the vertex's bind position vector by the resulting matrix,
- multiply the resulting vector by the weight associated with this joint for the vertex,
- and increment the final vertex position (which was 0, 0, 0 at the beginning of the frame) by the product.

I do this for every joint affecting each vertex. I think that covers it. I'm testing with very simple animations, too - either translating or rotating, and only on one axis at a time, with joints that have no parents. So, what am I doing horribly wrong? I would be very grateful if anyone could tell me, and hopefully I haven't been too long-winded.

[Edited by - consequence on July 12, 2010 2:06:14 AM]

This topic is closed to new replies.

Advertisement