Need help with quarternions -> transformation Matrix

Im trying to learn bone animation. My designer has chosen the 3d-format, b3d.
If you want to help me with my problem but dont know the b3d-fileformat here is the specifications http://www.zen54709.zen.co.uk/b3dformat.htm#BB3D

So far I know how to make a glCanvas show a frozen non-animated version of my designers models. Now its time that I learn the animation part.

I have been told to read alot about quaternions and transformation matrices. I have also been told to make a tranformation and rotation matrix for each NODE in the model. I have writen a chunkWriter that println’s some info in each chunk to the screen - just to make me more comfortable with the way a b3d-file is build up. It looks more or less like this:

BB3D - 6184 Bytes - version: 1
TEXS - 43 Bytes - TexturePath: .\snowdrag.png - xpos: 0.0 ypos: 0.0 xscale: -0.0 yscale: -8.8E-44 rotation: 8.8E-44
BRUS - 47 Bytes
NODE - 6066 Bytes - Node name: ROOT. Position: (0.0, 0.0, -0.0). Scale: (1.0, 1.0, 1.0). Rotation: (1.0, 0.0, 0.0, -0.0).
MESH - 4372 Bytes - Master brush: -1
VRTS - 2332 Bytes - Flags: 0, 1, 2. 5 floats/vert.(x,y,z,u,v). All vertices writen to Vertices.txt
TRIS - 2020 Bytes - Brush: -1 - Triangles: 168 - All triangles has been writen to Triangles.txt and Pollies.txt
ANIM - 12 Bytes - Flag: 0 - Frames: 30 - Fps: 0.0
NODE - 1613 Bytes - Node name: joint1. Position: (0.04537487, 0.0, -22.83983). Scale: (1.0, 1.0, 1.0). Rotation: (-4.3711385E-8, -4.3711392E-8, 1.0, 4.3711392E-8).
BONE - 520 Bytes. Vertex1: 0 - Weight1: 1.0. Vertex2: 1 - Weight2: 1.0
KEYS - 84 Bytes - Flag: 1 - Frame: 1 - Position: (0.0453749, -0.3333354, -0.0064964294).
KEYS - 104 Bytes - Flag: 4 - Frame: 1 - Rotation: (-6.1817246E-8, -6.181724E-8, 0.70710695, -0.7071066).
NODE - 826 Bytes - Node name: joint2. Position: (0.0, 0.0, -23.25). Scale: (1.0, 1.0, 1.0). Rotation: (-4.3711488E-8, -3.1391647E-7, -3.1391647E-7, -1.0).
BONE - 408 Bytes. Vertex1: 3 - Weight1: 1.0. Vertex2: 4 - Weight2: 1.0
KEYS - 100 Bytes - Flag: 1 - Frame: 1 - Position: (-1.9895195E-13, -1.9895198E-13, -23.25).
KEYS - 124 Bytes - Flag: 4 - Frame: 1 - Rotation: (-4.3711488E-8, -3.1391647E-7, -3.1391647E-7, -1.0).
NODE - 115 Bytes - Node name: joint3. Position: (0.0, 0.0, -22.75). Scale: (1.0, 1.0, 1.0). Rotation: (1.0, 0.0, 0.0, -0.0).
BONE - 0 Bytes.
KEYS - 20 Bytes - Flag: 1 - Frame: 1 - Position: (0.0, 0.0, -22.75).
KEYS - 24 Bytes - Flag: 4 - Frame: 1 - Rotation: (1.0, 0.0, 0.0, 0.0).

I assume that the 4 floats in Rotation in each node is the quaternoin I have to use to make the matrix, is that right?
Also, the quaternion will only give me a 3x3matrix… isnt it a 4x4matrix I have make?
This is where im standing… this whole quaternion/transformation matrix stuff is quite a mouthfull for me!! So I hope someone can tell me what to do next. Each NODE gives also a position(3 floats) and a scale (3 floats). Where do I use them?

heres some math, whatelse do I need to do?

Matrix = [ 1 - 2y2 - 2z2 2xy - 2wz 2xz + 2wy
2xy + 2wz 1 - 2x2 - 2z2 2yz - 2wx
2xz - 2wy 2yz + 2wx 1 - 2x2 - 2y2 ]
given the quaternion Q (w, x, y, z)

Use javax.vecmath !!! Then it gets really simple:

Matrix4f m = new Matrix4f();
m.set(<the quaternion>); //set the rotation
m.set(<the vector>); //set the position

I don’t know how you can set scaling, but there’s probably also a method for that.

Thz alot, Arne!
That was really helpfull… and suprisingly easy :slight_smile:
I found that the javax.vecmath package comes from the java3D API. So far so good…
I have a few more questions:

  1. Is this matrix the only one I have to make for each node? Do I need one for each axis?

  2. The Matrix4f constructor looks like this: Matrix4f(Quat4f q1, Vector3f t1, float s) .
    So the constructor makes use of the 4 floats in rotation, the 3 floats in position, but only 1 float from scale. Which one? This is why I ask if I have to make a matrix for each axis…

  3. And this question is a little embarasing: now that I have the matrix how exactly do I use it? I know it has to do with cross products and likely the vertices and their weights for each bone. Where do I read about this? If someone could throw me a link to a good page it would be great…

  4. I see I have 2 KEYS for each NODE. One for rotation and one for position of each keyframe. How do I use them?

Thanks in advance - and a great weekend to y’all!

Hmm it seems that only unifom scales are supported. Saying you are only able to set scale x,y,z in same means. I don’t know if it is technically also possible to set scales for different x,y,z.
You could try to google or look at wikipedia.org

if you want to transform a Position you simply multiply it with the matrix. if you want to have a new matrix m that is m2 used on m1. you can simply set m = m1*m2

  • is Matrix4f.mul()

What do you mean by this? Keyboard keys?

Greetings
Arne

Thanks again Arne, this is really helping me alot!

Concerning Question 2, I guess I just have to find out why 3 scale-floats are given for each NODE. In any case as you see in the file output the scale floats seem to be 1.0 most of the times.

In Your answer to my 3. question you say that I use the matrix to transform a position. By position, do you mean the vertices that a given bone affects? Then where do I use the weight of those vertices…

In my 4th. question I talk about 2 KEYS for each NODE. Im refering to the b3d-output in my initial post… I wrote a b3dChunkWriter that lists what chunks are inside a given b3d-file + it gives an illustration of the herachi of the chunks. It also lists some info about whats inside of each chunk. If you look at the output you will see that each NODE (not included the ROOT-NODE) in this particular file contains a BONE chunk, 2 KEYS chunks and maybe a child NODE chunk.
I have only told the writer to print the info about the first keyframe i KEYS-chunks. Take a look!
The first KEYS-chunk gives the position of each keyframe. The second KEYS-chunk gives the rotation of each keyframe.
Well, thats what it says, cus I dont wanna sound like I know haw to use this info. I dont!

Maybe you know about a page where I can read about this particular subject.

Ahhh… Bone animation! That’s hard I myself have tried to code it but I wasn’t succesful :frowning: But I think I can help you out anyways:
For every bone you got a Matrix4f, that represents the bone’s position and rotation in Coordinates, that apply also for your vertices - Important: Your vertices should not be children of Bones, else the Bone will transform them 100% and you can’t get your weights working. The weights for each vertex have to sum up to 1, if not, normalize them!
Let V be a vertex and m1, m2, m3 be the matrices of the transforms you applied to the Bones and k1, k2, k3 the weights of the Bones. Then your transformed Vertex V’ = m1.mul(V).scale(k1)+m2.mul(V).scale(k2)+m3.mul(V).scale(k3);

Note: m1, m2, m3 are not the transform of the bones, but the transformation you apply to the Bones, e.g. when modifing them.

For the keys stuff: There is Matrix4f.set(Vector) and Matrix4f.set(Quaternion). To get your transformation from the old Matrix to the new one (for Boneanimation) you can simply call:


Matrix4f newTrans: the Matrix of the new Frame
Matrix4f oldTrans: the Matrix of the old Frame
Matrix4f trans: the Matrix to be aplied to the vertices:
trans.set(oldTrans);
trans.invert();
trans.mul(newTrans,trans);
// => oldTrans*trans = newTrans :)

Hope this stuff Helps
Arne

Wow… there was alot of new good stuff!

[quote]Important: Your vertices should not be children of Bones
[/quote]
I didnt think that was even posible. I only thought nodes could be children or parents. I think thats the case in b3d anyway!

[quote]Let V be a vertex and m1, m2, m3 be the matrices of the transforms you applied to the Bones and k1, k2, k3 the weights of the Bones. Then your transformed Vertex V’ = m1.mul(V).scale(k1)+m2.mul(V).scale(k2)+m3.mul(V).scale(k3);

Note: m1, m2, m3 are not the transform of the bones, but the transformation you apply to the Bones, e.g. when modifing them.
[/quote]
I think I have to look at that some 100 times, before I really grasp whats goin on! :wink: But im confident that I will understand it some day soon!
3 matrices - where do they come from? I thought there was only one…
3 weights of the bones. I dont fully understand. The file-output tells me that there are 1 weight for each vertex in the BONE chunk. Can you explain to me in more details?

Matrix4f newTrans: the Matrix of the new Frame
Matrix4f oldTrans: the Matrix of the old Frame
Matrix4f trans: the Matrix to be aplied to the vertices:
trans.set(oldTrans);
trans.invert();
trans.mul(newTrans,trans);
// => oldTrans*trans = newTrans :)

Wow, that I fully understand. I have the former frames Matrix and I mul it with the next keyframes Matrix to get the new Matrix. In the next frame again I make oldMatrix = newMatrix and do it all over again.
So when I calculate vertices for oldFrame and do the same for nextFrame, isnt this where I apply the weight to get fluent motion between the keyframes?

Well this again is really a giant step foreward for me… Thz alot!
In case of more questions on this subject:
Is it too much to ask, if you are on msn? If you are too busy for that, I fully understand…

I didnt think that was even posible. I only thought nodes could be children or parents. I think thats the case in b3d anyway!
[/quote]
It isn’t actually possible here too, but what I’m saying is, you add the vertices to the root-node directly!

I think I have to look at that some 100 times, before I really grasp whats goin on! :wink: But im confident that I will understand it some day soon!
3 matrices - where do they come from? I thought there was only one…
3 weights of the bones. I dont fully understand. The file-output tells me that there are 1 weight for each vertex in the BONE chunk. Can you explain to me in more details?
[/quote]
When using Bones, Vertices are affected by more than one Bone. In my example above you’ve got 3 Bones that affect the Vertex! Bone 1 got m1 and affects V with weight k1 and so on.

You could ICQ me.

I have had my designer setup ultra-simple test-cases for me, which is great! :slight_smile:

He send me a file with the simplest imaginalbe situation with some animation in it. The test-case consists of only 3 joints. Its like an arm - starting at joint1 (shoulder) no animation here so only 1 keyframe - joint2 (albow) has some animation and hence 2 keyframes. - the last is joint3 (the wrist) has no animation. The total mesh is one single triangle placed on joint3.

From the file I have extracted the mesh (3 vertices):
(0.0, 22.909605, 17.545069)
(0.0, 4.600046, 20.834839)
(0.0, 15.823223, 36.61981)

Also I have asked my designer to read the the position of each vertex at each keyframe - x =0:

KF 1 (frame1)

v 1: y30, z21
v 2: y26, z1
v 3: y12, z13

KF2 (frame10)

v 1: y20.5, z-26.75
v 2: y0.25, z-28
v 3: y8.5, z-11.75

As I see it this is good, because I have the starting position and the resulting positions. Now all I need to get right is the calculations that transform the vertices in the mesh.
To start with I want to calculate the positions of the vertices in frame 1.

Here is what I did:

 
                 Quat4f qFrame1_joint1 = new Quat4f(0.0f, 0.9567119f, -0.29103667f, 0.0f);
	      Vector3f vFrame1_joint1 = new Vector3f(0.0f, -20.0f, -18.75f);
	      Matrix4f mFrame1_joint1 = new Matrix4f(qFrame1_joint1, vFrame1_joint1, 1.0f);
	      mFrame1_joint1.invert();	      
	      
	      Quat4f qFrame1_joint2 = new Quat4f(0.0f, 0.3604212f, -0.9327897f, 0.0f);
	      Vector3f vFrame1_joint2 = new Vector3f(0.0f, 0.0f, -26.487024f);
	      Matrix4f mFrame1_joint2 = new Matrix4f(qFrame1_joint2, vFrame1_joint2, 1.0f);
	      mFrame1_joint2.invert();
	      
	      Quat4f qFrame1_joint3 = new Quat4f(0.0f, -0.9244745f, -0.3812439f, 0.0f);
	      Vector3f vFrame1_joint3 = new Vector3f(0.0f, 0.0f, -26.487024f);
	      Matrix4f mFrame1_joint3 = new Matrix4f(qFrame1_joint3, vFrame1_joint3, 1.0f);
	      mFrame1_joint3.invert();
	      
	      mFrame1_joint1.mul(mFrame1_joint2);
	      mFrame1_joint1.mul(mFrame1_joint3);
	      
	      Point3f pStart = new Point3f(0.0f, 22.909605f, 17.545069f);
	      Point3f pEnd = new Point3f();      
	      
	      mFrame1_joint1.transform(pStart, pEnd);
	      System.out.println(pEnd);

It does not seem to give me the right result. What am I doing wrong here. I know the Quaternions and Matrices are build right.
If I could get some help on this one it would be great! :-\

Why do you invert the Matrices? Have you tried it without inverting the matrices?

Well, to be honest I started out with an even more simple case; 1 joint with 1 triangle centered on it. Animation consisted of rotation around the center only. Again my designer gave me the positions of triangles vertices on each keyframe. Even this simple example toke me some time to figure out. It was a lucky shot - I tried to invert the matrix as a last try - and the resulting positions were exactly the same as the positions I got from my designer. :smiley:

Here are the calculations from that setup:

The mesh - extracted from the 3dfile:
v1: (0.0, 36.0, 4.142857)
v2: (0.0, 15.999998, 4.142857)
v3: (0.0, 25.999994, 24.142862)

The vertex info of each keyframe - from my designer:

(aproximations)
KF 1 (frame1):
v 1: y26,z24,x0
v 2: y36,z4,x0
v 3: y16,z4,x0

KF 2 (frame5):
v 1: y-6,z35,x0
v 2: y16,z32,x0
v 3: y5,z16,x0

My calculations that gave the right result:

                 Quat4f q1 = new Quat4f(0.0f, 0.0f, 0.0f, 1.0f);
	      Vector3f v1 = new Vector3f(0.0f, 0.0f, 0.0f);
	      Matrix4f m1 = new Matrix4f(q1, v1, 1.0f);
	      m1.invert();

                Quat4f q5 = new Quat4f(-0.47715867f, 0.0f, 0.0f, 0.87881714f);
	      Vector3f v5 = new Vector3f(0.0f, 0.0f, 0.0f);
	      Matrix4f m5 = new Matrix4f(q5, v5, 1.0f);
	      m5.invert();

                 //transforming a vertex from frame 1.
	      Point3f pStart1 = new Point3f(0.0f, 15.999998f, 4.142857f);
	      Point3f pEnd1 = new Point3f();      
	      
	      m1.transform(pStart1, pEnd1);
	      System.out.println(pEnd1);
	      
	      //transforming a vertex from frame 5.
	      Point3f pStart5 = new Point3f(0.0f, 15.999998f, 4.142857f);
	      Point3f pEnd5 = new Point3f();      
	      
	      m5.transform(pStart5, pEnd5);
	      System.out.println(pEnd5);


Console result:
(0.0, 15.999998, 4.142857)
(0.0, 5.2397346, 15.675087)

The calculations of frame1 are obvious. Its an identitymatrix which is the reason why the vertices from the mesh equals the values I got from my designer even before transformation (take a look). The calculations of frame 5 is less obvious. Before I got the idea of inverting the matrix nothing worked. After I inverted everything fell into place. I got the idea of inverting from one of your earlier replies in this thread :wink:

mmh strange…

[quote]Animation consisted of rotation around the center only
[/quote]
What does happen, when you’ve also got a translation - or only a translation?

That was a good question… I tried a simple translation 5 units down the z-azis. It does not work inverted… Well it does but then it changes the position -5 down the z-azis instead.
This is truely wierd. So to get rotation right I have to invert but then translation doesnt work - and the other way around when I dont invert… Its beginning to sound like a notational problem.

To begin with I couldnt get anything to work… then I found out that the file lists a quaternion w,x,y,z while the java-class Quat4f reads in a quaternion x,y,z,w… so I changed the entries around and then it worked!

I cant figure out what is the problem - you got some ideas?


Well I could bypass the problem by doing this:

Psedocode


Matrix.set(rotation);
Matrix.invert();
Matrix.set(translation);

But then again… That would be assuming that the problem lies in the Matrix4f-class! :-\

The designer works in MilkShape - he then exports the work to a Blitz3d file (b3d). I know that coordinatesystems can differ - for example have I found out that I have to change the sign of the z values my designer read off MilkShape when giving me the resulting vertex info. I have also heard that matrices can differ as well. The entries in the top row in a Matrix in java lies in the first colomn in OpenGL for example - at least so Ive heard. Maybe its in this we can find our problem. when exporting as b3d he is building a format that works in Blitz3d programs. Maybe the matrix is different in Blitz an hence the quaternion. Just guessing…


Heres a whole different question that has poped resently. Until now I have had the idea that in bone animation you have to run the entire pool of vertices through differnt matrices every frame. My designer just told me that this is wrong. He say that the idea in bone animation is that you run all the vertices affected by a given bone in one run. To begin with I couldnt understand how that could be posible, but now I see it must be posible if you change the local coordinatesystem in OpenGL according to a transformation matrix and just run the vertices as they are given in the file. If this is so, then I have had a whole twisted look upon bone/skeletal animaiton. Please comment on this to… :-[

[quote]To begin with I couldnt get anything to work… then I found out that the file lists a quaternion w,x,y,z while the java-class Quat4f reads in a quaternion x,y,z,w… so I changed the entries around and then it worked!

I cant figure out what is the problem - you got some ideas?
[/quote]
Aren’t you answering you’re question already yourself? Simply rebuild you loader.
If you do something like this (assuming s is a String containing you’re quat):


String[] arr = s.split(",");
w = Float.paresInt(arr[0].trim());
x = Float.paresInt(arr[1].trim());
y = Float.paresInt(arr[2].trim());
z = Float.paresInt(arr[3].trim());
Quat q = new Quat(x,y,z,w);

so what’s the problem?

Your designer is right. You don’t have to go through all vertices, you only have to go through the vertices whose Bones got modified. Or: when you transform a bone you change the vertices of that bone. This has nothing to do with local coordinate system. You simply transform your vertices by the same transform, with which you transform also the bone. This is independent of the coordinate system. (a rotation about 90° is a rotation about 90° in any coordinate system)

No I have not aswered my own problem. I have already extracted the 4 floats of the quaternions and interchanged them in order to get rotation work. I create Quat4f instances right, so much I know. So its not the Quat thats giving problems - well it could be if a given rotation is create with a different Quaternion in Blitz3D than in java…

[quote]Or: when you transform a bone you change the vertices of that bone. This has nothing to do with local coordinate system. You simply transform your vertices by the same transform, with which you transform also the bone.
[/quote]
But dont that also potentially mean that all vertices of all bones during animation has to go through a transform? Isnt that what you call “mesh animation”? I have read that the whole idea of bone animation is that you dont have to transform each and every vertex. And I see how that is posible if you just run the vertices as they are in the mesh and after just rotating the coordinate system appropriately for each frame.

Ohh then I’ve missunderstood you.

[quote]for example have I found out that I have to change the sign of the z values my designer read off MilkShape when giving me the resulting vertex info
[/quote]
Ahh that’s it!! Easy to overread!! With changing the sign you don’t simply rotate the coordinate system, but you mirror it, so if you’ve got a rotation in milkshape that goes +90° you now have to make a rotation that goes -90° So inverting you’re rotation is exactly the correct thing!! But I would suggest to invert the Quat, because I think this takes computationally not as much as inverting a Matrix.

But dont that also potentially mean that all vertices of all bones during animation has to go through a transform? Isnt that what you call “mesh animation”? I have read that the whole idea of bone animation is that you dont have to transform each and every vertex. And I see how that is posible if you just run the vertices as they are in the mesh and after just rotating the coordinate system appropriately for each frame.
[/quote]
When you only rotate the coordinate system you can’t have fancy stuff like Vertices that are affected by more than bone - or how would you do that? No - the advantage is: You only have to change the Vertices whose bones you also change, so if you only have your model making an arm movement you only have to update the vertices of the arm and not the vertices of e.g. the legs.

There is a bug in the open-source version of vecmath.jar, with Quaternions, and maybe Matrices (I saw this on the Fuze3D website).
The Fuze3D author (Dave Lloyd) has fixed this problem, but I don’t know where to grab the modified version.
(He say he had a lot of pain with that bug).

Got a link by any chance? This would be a rather large problem.

Will.

I think so… but unfortunately the shortfuze site seems to be offline.
If you can access it later, there’s a sort of blog on the site and a little research can bring you the corresponding news. I think for the modified version of vecmath.jar you have to download the whole Fuze3D pack.

It’s unbelievable, the bug is still there, it was reported in 2002!

[quote]Got a link by any chance? This would be a rather large problem.
[/quote]
I also remember that there was an explanation of this bug, but the shortfuze site is still down. But looking at the Fuze3d source code, I think that original version (not working due to bug) was:

bone.getTransformMatrix ().transform (v);

which he worked around to:


CalCoreBone.transform (v, bone.getRotationBoneSpace ());

he introduced new method CalCoreBode::transform


public static void transform (Vector3f v, Quat4f q)
{
        Quat4f qq = new Quat4f (-q.x, -q.y, -q.z, q.w);
        mul (qq, v);
        qq.mul (q);
        v.x = qq.x;
        v.y = qq.y;
        v.z = qq.z;
}

I don’t understand why it works, but it works