## Quarternions Ugh

Tutorials concerning the OpenGL® ES cross-platform API for full-function 2D and 3D graphics on the Google-Android platform.

### Quarternions Ugh

Anyone have any experience with quarternions? I have implemented them to handle rotation for a ball in my game. There seems to be a problem when I am doing the quarternion rotation and a translation at the same time. I get skewing of my ball and sometimes the texture goes to black. Not sure what the heck is going on. If I don't do the translation, all seems ok. I have come to the conclusion that I don't know what I am doing.

Note that I got myself all out of kilter in the order to fill my float buffer matrix. I have tried both ways as you can see by the commented out code without much difference except direction of rotation. Both ways experience the skewing.

Here is my Quarternion class.

Using java Syntax Highlighting
1. package com.exit4.lavaball;
2.
3.
4.
5. import java.nio.ByteBuffer;
6.
7. import java.nio.ByteOrder;
8.
9. import java.nio.FloatBuffer;
10.
11.
12.
13. import javax.microedition.khronos.opengles.GL10;
14.
15.
16.
17. public class Quarternion {
18.
19.         public float w,x,y,z;
20.
21.
22.
23.         public Quarternion ()
24.
25.         {
26.
27.                 w = 1;
28.
29.                 x = 0;
30.
31.                 y = 0;
32.
33.                 z = 0;
34.
35.         }
36.
37.
38.
39.         // Note angle is in radians
40.
41.         public Quarternion(float angle, float[] axis)
42.
43.         {
44.
45.                 set(angle,axis);
46.
47.         }
48.
49.
50.
51.         // Note that angle is in radians
52.
53.         public void set (float angle, float[] axis)
54.
55.         {
56.
57.                 w = (float) Math.cos(angle/2);
58.
59.                 float temp = (float) Math.sin(angle/2);
60.
61.                 x = axis[0] * temp;
62.
63.                 y = axis[1] * temp;
64.
65.                 z = axis[2] * temp;
66.
67.         }
68.
69.
70.
71.         public float magnitude()
72.
73.         {
74.
75.                 float mag = (float) Math.sqrt(w*w + x*x + y*y + z*z);
76.
77.
78.
79.                 return mag;
80.
81.         }
82.
83.
84.
85.         public void normalize()
86.
87.         {
88.
89.                 float mag = magnitude();
90.
91.
92.
93.                 w = w/mag;
94.
95.                 x = x/mag;
96.
97.                 y = y/mag;
98.
99.                 z = z/mag;
100.
101.         }
102.
103.
104.
105.         // multiplication is in the order other*this with result stored in
106.
107.         // this.
108.
109.         public void multiply(Quarternion other)
110.
111.         {
112.
113.                 float tw,tx,ty,tz;
114.
115.                 tw = other.w*w - other.x*x - other.y*y - other.z*z;
116.
117.                 tx = other.w*x + other.x*w + other.y*z - other.z*y;
118.
119.                 ty = other.w*y - other.x*z + other.y*w + other.z*x;
120.
121.                 tz = other.w*z + other.x*y - other.y*x + other.z*w;
122.
123.                 w = tw;
124.
125.                 x = tx;
126.
127.                 y = ty;
128.
129.                 z = tz;
130.
131.         }
132.
133.
134.
135.         float[] M = null;
136.
137.         public void fill_matrix()
138.
139.         {
140.
141.                 if (M == null)
142.
143.                         M = new float[16];
144.
145.                 /*/ Column one
146.
147.                 M[0] = 1 - 2*y*y - 2*z*z;
148.
149.                 M[4] = 2*x*y + 2*w*z;
150.
151.                 M[8] = 2*x*z - 2*w*y;
152.
153.                 M[12] = 0.0f;
154.
155.                 // Column two
156.
157.                 M[1] = 2*x*y - 2*w*z;
158.
159.                 M[5] = 1 - 2*x*x - 2*z*z;
160.
161.                 M[9] = 2*y*z - 2*w*x;
162.
163.                 M[13] = 0.0f;
164.
165.                 // Column three
166.
167.                 M[2] = 2*x*z + 2*w*y;
168.
169.                 M[6] = 2*y*z - 2*w*x;
170.
171.                 M[10] = 1 - 2*x*x - 2*y*y;
172.
173.                 M[14] = 0;
174.
175.                 // Column four
176.
177.                 M[3] = 0;
178.
179.                 M[7] = 0;
180.
181.                 M[11] = 0;
182.
183.                 M[15] = 1;
184.
185.                 */
186.
187.                 // Column one
188.
189.                 M[0] = 1 - 2*y*y - 2*z*z;
190.
191.                 M[1] = 2*x*y + 2*w*z;
192.
193.                 M[2] = 2*x*z - 2*w*y;
194.
195.                 M[3] = 0.0f;
196.
197.                 // Column two
198.
199.                 M[4] = 2*x*y - 2*w*z;
200.
201.                 M[5] = 1 - 2*x*x - 2*z*z;
202.
203.                 M[6] = 2*y*z - 2*w*x;
204.
205.                 M[7] = 0.0f;
206.
207.                 // Column three
208.
209.                 M[8] = 2*x*z + 2*w*y;
210.
211.                 M[9] = 2*y*z - 2*w*x;
212.
213.                 M[10] = 1 - 2*x*x - 2*y*y;
214.
215.                 M[11] = 0;
216.
217.                 // Column four
218.
219.                 M[12] = 0;
220.
221.                 M[13] = 0;
222.
223.                 M[14] = 0;
224.
225.                 M[15] = 1;
226.
227.         }
228.
229.
230.
231.         public void glRotate(GL10 gl)
232.
233.         {
234.
235.                 fill_matrix();
236.
237.                 FloatBuffer buf = makeFloatBuffer(M);
238.
239.             gl.glMultMatrixf(buf);
240.
241.         }
242.
243.
244.
245.         protected static FloatBuffer makeFloatBuffer(float[] arr)
246.
247.         {
248.
249.                 ByteBuffer bb = ByteBuffer.allocateDirect(arr.length*4);
250.
251.                 bb.order(ByteOrder.nativeOrder());
252.
253.                 FloatBuffer fb = bb.asFloatBuffer();
254.
255.                 fb.put(arr);
256.
257.                 fb.position(0);
258.
259.                 return fb;
260.
261.         }
262.
263. }
Parsed in 0.016 seconds, using GeSHi 1.0.8.4

Here is an example of adding some new rotation to the Quarternion for the ball.

Using java Syntax Highlighting
1.
2.         Quarternion q = new Quarternion();
3.
4.         Quarternion tq = new Quarternion();
5.
6.
7.
8.         float[] rtemp = new float[3];
9.
11.
12.         {
13.
14.                 if (grounded_rotation)
15.
16.                 {
17.
18.                         // rotation axis is the cross of the grounded plane normal
19.
20.                         // and the vector of movement
21.
22.                         rtemp = vector3d.normalize(vector);
23.
24.                         axis = vector3d.cross_product(gnormal,rtemp);
25.
26.                 }
27.
28.                 // amount of rotation is the length of the vector
29.
30.                 rangle = vector3d.length(vector)*2;
31.
32.                 tq.set(rangle, axis);
33.
34.                 q.multiply(tq);
35.
36.                 // Note that I don't actually normalize here.
37.
38.                 // I just added this to this example to show that I am normalizing elsewhere.
39.
40.                 // I am normalizing after all frame rotations are multiplied together.
41.
42.                 q.normalize();
43.
44.         }
Parsed in 0.011 seconds, using GeSHi 1.0.8.4

This is where I do the matrix translations when drawing the ball.

Using java Syntax Highlighting
1.         public void draw(GL10 gl)
2.
3.         {
4.
5.                 gl.glPushMatrix();
6.
7.                 gl.glTranslatef(position[0],position[1],position[2]);
8.
9.                 q.glRotate(gl);
10.
11.                 this.mesh.draw(gl);
12.
13.                 gl.glPopMatrix();
14.
15.         }
Parsed in 0.011 seconds, using GeSHi 1.0.8.4

Any help or suggestions would be greatly appreciated!

I should note that I think I have ruled out problems in my quarternion multiply because I get the same skewing if
I just use a fixed quarternion rather than my accumulated one. Also, I have checked my quarternion to matrix formulas so many times it hurts against two sources. They look right. And, as I mentioned, I have filled my matrix both ways. Just for kicks, I tried rotating by the quarternion before the translation. As expected, this rotated around the world origin which is not right of course. I am missing something fundamental I fear.

Thanks,
Seed
Visit Exit 4 Gaming - http://www.exit4games.com/
Home of LavaBall - http://exit4games.com/?page_id=3
Home of Rebound - http://exit4games.com/?page_id=138
Home of Tap Crazy - http://exit4games.com/?page_id=219
seed
Senior Developer

Posts: 103
Joined: Mon Mar 15, 2010 3:22 pm

I take back some of what I said. I modified the draw routing to load the identity prior to doing the matrix multiple for the quarternion rotation. I still get the same skewing problems. So, it doesn't seem to have anything to do with the combination of quarternion rotation and translation together. I removed the translation.

Damn. My class is just wrong. I can't see why.

Frustrated in NJ!
Visit Exit 4 Gaming - http://www.exit4games.com/
Home of LavaBall - http://exit4games.com/?page_id=3
Home of Rebound - http://exit4games.com/?page_id=138
Home of Tap Crazy - http://exit4games.com/?page_id=219
seed
Senior Developer

Posts: 103
Joined: Mon Mar 15, 2010 3:22 pm

This is really frustrating.

Is I use 0,0;1 as my axis, no problem.
Same with 0,1,0. Same with 0,1,1.

Only when I haxe some x to my axis is there an issue. 1,0,0 does not work. It rotates but askews too.

There must be s typo in my matrix conversion. But for the life of me I can't find it.

Argh.
Visit Exit 4 Gaming - http://www.exit4games.com/
Home of LavaBall - http://exit4games.com/?page_id=3
Home of Rebound - http://exit4games.com/?page_id=138
Home of Tap Crazy - http://exit4games.com/?page_id=219
seed
Senior Developer

Posts: 103
Joined: Mon Mar 15, 2010 3:22 pm

Per usual, I am posting to myself;)

I figured it out.

The problem was in the fill_matrix routine. I was referencing a web site that had a sign error in one of the formulas. The comment has the reference to the site that has it wrong.

Using java Syntax Highlighting
1.
2.         // This website had it wrong.  It was the basis of my original code -
3.
4.         //    http://www.cprogramming.com/tutorial/3d ... nions.html
5.
6.         // These two had it right -
7.
8.         //    http://gpwiki.org/index.php/OpenGL:Tuto ... t_rotation
9.
10.         //    http://nehe.gamedev.net/data/lessons/le ... mera_Class
11.
12.         // Value M[6] was wrong.  Changed from - to + and all is now well.
13.
14.         float[] M = null;
15.
16.         public void fill_matrix()
17.
18.         {
19.
20.                 if (M == null)
21.
22.                         M = new float[16];
23.
24.                 // Column one
25.
26.                 M[0] = 1 - 2*y*y - 2*z*z;
27.
28.                 M[1] = 2*x*y + 2*w*z;
29.
30.                 M[2] = 2*x*z - 2*w*y;
31.
32.                 M[3] = 0.0f;
33.
34.                 // Column two
35.
36.                 M[4] = 2*x*y - 2*w*z;
37.
38.                 M[5] = 1 - 2*x*x - 2*z*z;
39.
40.                 M[6] = 2*y*z + 2*w*x;
41.
42.                 M[7] = 0.0f;
43.
44.                 // Column three
45.
46.                 M[8] = 2*x*z + 2*w*y;
47.
48.                 M[9] = 2*y*z - 2*w*x;
49.
50.                 M[10] = 1 - 2*x*x - 2*y*y;
51.
52.                 M[11] = 0;
53.
54.                 // Column four
55.
56.                 M[12] = 0;
57.
58.                 M[13] = 0;
59.
60.                 M[14] = 0;
61.
62.                 M[15] = 1;
63.
64.         }
65.
66.
Parsed in 0.013 seconds, using GeSHi 1.0.8.4

For those that are interested, quarternions make it really easy to do rotation around an axis, a very tough problem in 3D graphics, especially if you are accumulating rotations around a series of different arbitrary axis, as in the case of a rolling/deflecting ball. You accumulate all of your rotations in the quarternion, then prior to rendering, you convert the quarternion into a matrix. Unit quarternions have the advantage of not accumulating error nearly as fast, AND when they do, it is really simple to do a normalization to get rid of it. Quaternions are also used for things like flying cameras and skeletal animation - stuff that I haven't used them for but they are supposed to be very good at.

Anyway, here is my quarternion class for anyone who is interested. Note that makeFloatBuffer is not optimized. It should be rewritten to use the same buffer each frame rather than generating so much garbage. I will do this at some point but am being lazy at the moment and just adding it to my todo list.

Using java Syntax Highlighting
1. package com.exit4.lavaball;
2.
3.
4.
5. import java.nio.ByteBuffer;
6.
7. import java.nio.ByteOrder;
8.
9. import java.nio.FloatBuffer;
10.
11.
12.
13. import javax.microedition.khronos.opengles.GL10;
14.
15.
16.
17. public class Quarternion {
18.
19.         public float w,x,y,z;
20.
21.
22.
23.         public Quarternion ()
24.
25.         {
26.
27.                 w = 1;
28.
29.                 x = 0;
30.
31.                 y = 0;
32.
33.                 z = 0;
34.
35.         }
36.
37.
38.
39.         // Note angle is in radians
40.
41.         public Quarternion(float angle, float[] axis)
42.
43.         {
44.
45.                 set(angle,axis);
46.
47.         }
48.
49.
50.
51.         // Note that angle is in radians
52.
53.         public void set (float angle, float[] axis)
54.
55.         {
56.
57.                 w = (float) Math.cos(angle/2);
58.
59.                 float temp = (float) Math.sin(angle/2);
60.
61.                 x = axis[0] * temp;
62.
63.                 y = axis[1] * temp;
64.
65.                 z = axis[2] * temp;
66.
67.         }
68.
69.
70.
71.         public float magnitude()
72.
73.         {
74.
75.                 float mag = (float) Math.sqrt(w*w + x*x + y*y + z*z);
76.
77.
78.
79.                 return mag;
80.
81.         }
82.
83.
84.
85.         public void normalize()
86.
87.         {
88.
89.                 float mag = magnitude();
90.
91.
92.
93.                 w = w/mag;
94.
95.                 x = x/mag;
96.
97.                 y = y/mag;
98.
99.                 z = z/mag;
100.
101.         }
102.
103.
104.
105.         // multiplication is in the order other*this with result stored in
106.
107.         // this.
108.
109.         public void multiply(Quarternion other)
110.
111.         {
112.
113.                 float tw,tx,ty,tz;
114.
115.                 tw = other.w*w - other.x*x - other.y*y - other.z*z;
116.
117.                 tx = other.w*x + other.x*w + other.y*z - other.z*y;
118.
119.                 ty = other.w*y - other.x*z + other.y*w + other.z*x;
120.
121.                 tz = other.w*z + other.x*y - other.y*x + other.z*w;
122.
123.                 w = tw;
124.
125.                 x = tx;
126.
127.                 y = ty;
128.
129.                 z = tz;
130.
131.         }
132.
133.
134.
135.         float[] M = null;
136.
137.         public void fill_matrix()
138.
139.         {
140.
141.                 if (M == null)
142.
143.                         M = new float[16];
144.
145.                 // Column one
146.
147.                 M[0] = 1 - 2*y*y - 2*z*z;
148.
149.                 M[1] = 2*x*y + 2*w*z;
150.
151.                 M[2] = 2*x*z - 2*w*y;
152.
153.                 M[3] = 0.0f;
154.
155.                 // Column two
156.
157.                 M[4] = 2*x*y - 2*w*z;
158.
159.                 M[5] = 1 - 2*x*x - 2*z*z;
160.
161.                 M[6] = 2*y*z + 2*w*x;
162.
163.                 M[7] = 0.0f;
164.
165.                 // Column three
166.
167.                 M[8] = 2*x*z + 2*w*y;
168.
169.                 M[9] = 2*y*z - 2*w*x;
170.
171.                 M[10] = 1 - 2*x*x - 2*y*y;
172.
173.                 M[11] = 0;
174.
175.                 // Column four
176.
177.                 M[12] = 0;
178.
179.                 M[13] = 0;
180.
181.                 M[14] = 0;
182.
183.                 M[15] = 1;
184.
185.         }
186.
187.
188.
189.         public void glRotate(GL10 gl)
190.
191.         {
192.
193.                 fill_matrix();
194.
195.                 FloatBuffer buf = makeFloatBuffer(M);
196.
197.         gl.glMultMatrixf(buf);
198.
199.         }
200.
201.
202.
203.         protected static FloatBuffer makeFloatBuffer(float[] arr)
204.
205.         {
206.
207.                 ByteBuffer bb = ByteBuffer.allocateDirect(arr.length*4);
208.
209.                 bb.order(ByteOrder.nativeOrder());
210.
211.                 FloatBuffer fb = bb.asFloatBuffer();
212.
213.                 fb.put(arr);
214.
215.                 fb.position(0);
216.
217.                 return fb;
218.
219.         }
220.
221. }
Parsed in 0.016 seconds, using GeSHi 1.0.8.4

Note that quarternion multiplication is backwards. If you have two quarternions, q that represents the current rotational state of your ball, and fq that represents the new rotation for this frame, and you want to multiply the quarternions to accumulate the frame rotation into q, you do q=fq*q which is backwards from matrix multiplication. In my class this is q.multiply(fq);

Hope this is useful to someone.

Regards
Visit Exit 4 Gaming - http://www.exit4games.com/
Home of LavaBall - http://exit4games.com/?page_id=3
Home of Rebound - http://exit4games.com/?page_id=138
Home of Tap Crazy - http://exit4games.com/?page_id=219
seed
Senior Developer

Posts: 103
Joined: Mon Mar 15, 2010 3:22 pm

Thanks man!

I'm new to OpenGL. After a day of problems with stacked rotations I stumbled upon your post. I'm now using your class and all my problems are fixed (toke me a half hour to understand and build in your class).

Thanks!

Kind regards Ben
benhul
Once Poster

Posts: 1
Joined: Fri May 14, 2010 3:25 pm

No problem. I hoped that someone could use it. Quarternions are great for accumulated rotations without gimble lock. When I stumbled upon them, I was amazed how easy a complicated problem became also.

BTW, definitely change the makeFloatBuffer to something like this to save yourself some GC.

Using java Syntax Highlighting
1.
2.
3.       public void glRotate(GL10 gl)
4.
5.         {
6.
7.                 fill_matrix();
8.
9.                 FloatBuffer buf = fillFloatBuffer(M);
10.
11.             gl.glMultMatrixf(buf);
12.
13.         }
14.
15.
16.
17.         // The first time this Float Buffer is filled, the buffer is
18.
19.         // created.  Each additional time, it is just updated.
20.
21.         FloatBuffer fb = null;
22.
23.         protected FloatBuffer fillFloatBuffer(float[] arr)
24.
25.         {
26.
27.                 if (fb == null)
28.
29.                 {
30.
31.                         ByteBuffer bb = ByteBuffer.allocateDirect(arr.length*4);
32.
33.                         bb.order(ByteOrder.nativeOrder());
34.
35.                         fb = bb.asFloatBuffer();
36.
37.                 }
38.
39.                 fb.put(arr);
40.
41.                 fb.position(0);
42.
43.                 return fb;
44.
45.         }
Parsed in 0.012 seconds, using GeSHi 1.0.8.4
Visit Exit 4 Gaming - http://www.exit4games.com/
Home of LavaBall - http://exit4games.com/?page_id=3
Home of Rebound - http://exit4games.com/?page_id=138
Home of Tap Crazy - http://exit4games.com/?page_id=219
seed
Senior Developer

Posts: 103
Joined: Mon Mar 15, 2010 3:22 pm

### Re: Quarternions Ugh

Thoroughly enjoying these threads and trying to catch up myself with some eye-candy! Nice seed!
torpor
Junior Developer

Posts: 13
Joined: Tue Feb 24, 2009 12:23 pm