Java applet for axis angles, rotation matrices and quaternions

hi everyone!

when i started to play with the jmonkeyengine some weeks ago, i quickly realized that i have some serious problems with angles in 3D. and judging by the topics in the forum… heck i am not the only one.

so, i decided to invest some of my time into an exercise: write a java applet that displays something (in the moment a “log”), that can be rotated either by angle axis, or by a rotational matrix, or by a quaternion, hopefully learning something about the topic in the process… in short, i learned loads, although mostly about different topics, not so much about angles (ironic, isn’t it?).

the fruits of my labor can be observed by anyone at, at least if your willing to to trust me far enough to give my applet the right to start an opengl thread:



http://www.myjavasite.cwsurf.de/



so anyone who did trust me far enough: thank you!

nevertheless, i am sure you quickly realized that some things are still behaving rather strangly: for instance while yaw and role seem to work fine, pitch produces some kind of problem when you intend to increase the value above pi/2. or if you increase the values of the quaternion, at some point the model simply plainly disappears…

anyone being able to shed some light on this weird behaviours, i would be very thankfull! you can find the entire source code in the jar file from which the applet is started, but i doubt that any sane person would go through the trouble, so let me just explain the gist about how i dealt with the angles so far:

from the values entered in either of the three systems (angle axes, rotational matrix or quaternion), i construct a quaternion (odd, isn’t it?)

Code:
float[] a = new float[3]; a[0] = ((SpinnerNumberModel) yawt.getModel()).getNumber().floatValue(); a[1] = ((SpinnerNumberModel) rollt.getModel()).getNumber().floatValue(); a[2] = ((SpinnerNumberModel) pitcht.getModel()).getNumber().floatValue(); Quaternion quat = computeFromAngles(a);
Matrix3f m = new Matrix3f(
		  ((SpinnerNumberModel) m11.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m12.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m13.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m21.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m22.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m23.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m31.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m32.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) m33.getModel()).getNumber().floatValue());
Quaternion quat = computeFromMatrix(m);


Quaternion quat = new Quaternion(
		  ((SpinnerNumberModel) it.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) jt.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) kt.getModel()).getNumber().floatValue(),
		  ((SpinnerNumberModel) rt.getModel()).getNumber().floatValue());</div>
now, with that quaternion i can update the input fields:
Code:
public void applyNewAngle(Quaternion quat) { setQuaternion(quat); setMatrix(quat.toRotationMatrix()); setAngles(computeAngles(quat)); }
public float[] computeAngles(Quaternion q) {
	float[] v = new float[3];
	q.toAngles(v);
	return v;
}</div>
in my jmonkey application, i can also use this quaternion to change the rotation of the node, my model is attached to. i do this by using callables. thank you again, norman, for your advice in my last thread! it was remarkable easy to do this with the snippet you pointed me to!

http://hub.jmonkeyengine.org/groups/gui/forum/topic/com-jme3-system-nullcontext-cannot-be-cast-to-com-jme3-system-jmecanvascontext-yet-another-swing-problem-from-a-jme-newbie/

so what do i do? in the jmonkey application i first apply the inverse of any former rotation that i might have been applied, then i apply the new rotation:
Code:
public void setNewRotation(Quaternion q) { if (this.oldRotation != null) this.reverseOldRotation(); this.oldRotation = q; this.n.rotate(q); }
private void reverseOldRotation() {
	this.n.rotate(this.oldRotation.inverse());
}</div>
i hope these where the important parts any knowledgable person might need to see!
for any idea, advice, reference that might help, i wouldf be very thankfull! of course any comment about the applet is also most welcome :)

and of course, let me know if you should have any troubles running the applet at all!

thanks!

ok, the first problem was grown from my own stupidity… i shouldn’t try to update that angle input system, that had been just use by the user… so the angle axes seem to work fine now, however, the other problems remain… of course the java applet i put online is updated.

thanks again

https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:math_for_dummies

http://hub.jmonkeyengine.org/groups/graphics/forum/topic/trackball-samples/

Euler angles - Wikipedia

Gimbal lock - Wikipedia

from your comment i conclude that you think, that one or several of my problems are caused by gimbal locks. although i had heard the expression before, i have to admit, i wasn’t very aware of that problem…

as i already wrote, rotation matrices and quaternions always seemed a little bit like black magic to me, so i consulted your math for dummies guide beforehand (i stumbled over the link in the forum at some point). however, when i read it, i actually interpreted slide 32 were you say “Quaternions define a relative rotation” as relative to the axes of the current Spatial/Node, as opposed to the global coordinates.

i now conclude that i missinterpreted and that quaternions seem to somehow define their own axes, and that with an ill defined quaternion, you could actually produce overlapping / parallel axes, and thus eliminate the possibilty to rotate around one of the global axes… that is a gimbal lock, if i understood that correctly (i am pretty much lost in the moment…).

the thing that is really beyond me, is that the model displayed in my applet seems to fail not at once but partially as i increase the value of one of my abstract quaternion parts (i, j or k). usually first one of my object axes after another one will not be displayed anymore, until all of them are gone, and then the actual object will just disappear from the screen, completely without any exception being thrown…

i doubt that i will easily understand the topic on a deeper level soon, thus i decided that for the moment it would be smarter to treat it as a black box and follow your advice to use the lookAt method. i added the possibility to manipulate two vectors for that purpose to the applet (updated version is of course online), and i have now two questions to everyone, who bothered to read so far (thank you):

first: although it is really easy to compute a quaternion from a direction and from an up vector, for the other way around there seems no clear cut way (at least i could not find it). i ended up doing this:

Code:
public Vector3f[] computeLookAt(Quaternion q) { Node n = new Node(); Geometry d = new Geometry("direction", new Arrow(new Vector3f(1,0,0))); Geometry u = new Geometry("up", new Arrow(new Vector3f(0,0,1))); n.attachChild(d); n.attachChild(u); n.rotate(q); Vector3f direction = (((Geometry)n.getChild("direction")).getWorldBound()).getCenter(); Vector3f up = (((Geometry)n.getChild("up")).getWorldBound()).getCenter(); System.out.println((n.getChild("direction").getWorldBound())); System.out.println((n.getChild("up").getWorldBound())); Vector3f[] lu = new Vector3f[2]; lu[0] = direction; lu[1] = up; return lu; }
is there a better way to do this?
second: lookAt needs two vectors, a direction vector (where to look) and an up vector (somwhow defining upwards, downwards, left and right in the direction you are looking(sorry, my english gets a little bit fuzzy at this point)). from a naive point of view, up must be a vector orthogonal to the direction vector. but orthoganlity is no necessity, because as long as the vectors are no linear combination of each other, up can still be interpreted meaningfull (they are still a spanning system for a plane). however what does the method do, if direction and up are linear combinations of each other (for example direction is (1,0,0) and up is (1,0,0))? i expected the same error, that i observe with my ill defined quaternions, yet... no, everything is still nicely displayed, and in addition, i again observe no exception. am i missunderstanding soemthing important?
thanks again!
morin

lookAt is explained in the “math for dummies” too, the up and direction components should always be perpendicular/orthogonal. To make a default Y-up and Z-forward (upVector=new Vector(0,1,0) / forwardVector=new Vector(0,0,1)) vector point at a spatials up and forward vector, just do localRotation.mult(upVector) and localRotation.mult(forwardVector), then you can use these for the lookAt antics.