Orienting objects in OpenGL
What we are trying to do
We are trying to draw objects (let's call them models) in 3D space with correct position and orientation, using OpenGL. These example do this in Java, using JOGL, but the ideas would be the same in any OpenGL implementation.
We want to do this by drawing the object at 0,0,0, and 'pointing' in a fixed direction ( we use 0,0,1 here). Then we want to use the OpenGL transformation functions (that is, glRotated ) to make the model appear pointing in the correct direction, which means both 'around' and 'up and down'.
Why do we want to do this?
This is a question not usually asked. The obvious way to draw a model is using the drawing primitives like drawing QUADS, with the vertex data calculated by the program to orient the model correctly.
First, a minor reason this is no good - speed. If your program calculates the vertex values, that will be done by the machine CPU, and it is not especially good at it. Whereas if you use the OpenGL transform functions, these will usually be done by the processor in the graphics card, which is what it was designed to do, leaving your CPU to run your programs.
But more signficantly, any model which is at all realistic will have a lot of vertex data, and you simply can't design it using Java code. Instead you use a 3d modelling program like Blender, save the model, then your Java program reads in the vertex data (and normals and materials and textures) and draws those vertices. But in Blender you drew it at 0,0,0 pointing at 0,0,1 - so your Java program has to transform it from there.
What is the problem?
Several problems really
One, how do we represent the orientation? For example, we could use 2 angles, which would be like latitude and longitude, to fix which way it is pointing. Plus a third angle to specify a roll around that axis. That's not a good way, because..
Two, we need a good way to control the orientation by the user. For example if it is a car we want to be able to turn left or right, or for a plane, we want to change the pitch nose down or up. These are relative to the model, not the world - which is why latitude and longitude are not good.
Three, we've got to draw this just using the OpenGL glRotated function. This takes an angle argument, plus 3 vector components fixing the axis about which to rotate. So the last 2 points must fit in with an (angle, axis) set of transforms.
A solution
I'm not arguing this is an optimal soultion, but at least it is a solution. I'm thinking of an aircraft, and I want to control roll and pitch. This uses 4 classes
ExploreOrientation. This contains main(), and keyboard input. Keys QA control pitch, and OP roll
Stick.javaThis is the object model, a simple stick plane
WMVector.java. This is a 3d vector class
Quaternion.java. Quaternions are needed by the vector class to rotate one vector around another
Representing the orientation
I'm using 2 vectors. forward is which way the model is pointing, and up is which is up for the model. This is similar to the camera parameters. In the picture, the blue line is pointing 'into' the screen.
In addition I need to maintain a 'roll' variable which is the angle through which it has rolled. This is redundant, but simple and convenient. Why we need it is described later. Consequently the start of my model class is:
public class Stick {
|
| Java2html |
How to draw it
This seems deceptively simple. We do a first rotation, which turns 0,0,1 to point the correct way (vector forward). This goes through an angle called, (oh dear) angle, and the axis is called turnAxis. Both of these are calulated in a method called reCalc(), called just when the orientation changes.
Then there is a second rotation to produce a correct roll orientation. This is always about 0,0,1, with an angle called angle2, also calculated in reCalc() when needed. angle and angle2 are in radians, so we have to convert to degrees here. fixedDraw() draws the model with forward pointing at 0,0,1. Here is the code:.
public void draw(GL gl)
|
| Java2html |
and this shows turnAxis, angle and forward:
Doing reCalc
OK this is the tricky part. We maintain the following class fields:
private final WMVector zaxis=new WMVector(0,0,1);
|
| Java2html |
and here (big breath) is reCalc()
/** calculations need for altered orientation */
|
| Java2html |
This starts by finding the angle between the z axis and forward, by using the angleWith method of my WMVector class. It then forms turnAxis, as the cross-product of the z axis and forward. No problem.
But suppose forward and z axis are parallel - which in fact they are, initially. Then the cross product is undefined - or here, comes as a zero vector. In this case (that is, is zero ) we just do something simple, rotate about the z axis by the roll angle, which we maintain when we do a roll.
Now the other, general case, when forward and the z axis are not aligned. The issue here is that the first rotation will also rotate the initial up vector by a certain amount - so that the we can't simply rotate by roll angle. I suggest you sit and think about that for a while - it took me about 3 weeks
Ok. So first we just normalise the turnAxis, then we work out where the rotation would have turned the initial up direction, which was the y axis. This is called newUp. Then we work out the angle between newUp and up, giving us angle2, the actual angle we need to do the roll rotation. What could be simpler?
There is a final twist. The angleWith() method that returns the angle between 2 vectors alwyas yields a positive value, in the rangle 0 to π. This is correct - but sometimes we need to rotate the other way. So we check rollAngle, and reverse the sign of angle2 if we need to.
Doing a roll
To do a roll, we just need to change rollAngle, rotate the up vector about the forward vector, and reCalc() the orientation parameters:
public void roll()
|
| Java2html |
This turns around 0.01 of a radian, or about half a degree. The unRoll() method simply uses -0.01.
Altering pitch
This is just a little vector manipulation. We first work out sideways, which is perpendicular to both forward and up. Then we add a small amount of the up vector to the forward vector, to tilt it up. This would make it bigger, so we re-normalise it. And having pitched up, the up vector should tilt back, so we make it the cross product of sideways and forward, and normalise it.
public void pitchUp()
|
| Java2html |
Pitch down is the same except we add a negative amount of up
Rotate one vector about another
Apart from the reCalc() method, this is fairly simple. However it does require rotating one vector about another. Hearn and Baker 'Computer Graphics' second edition, page 419, was my source for this. There are 2 methods - doing it by 2 rotations onto an axis, the 'actual' rotation about that axis, then the inverse rotations to put it back. That is not straightforward, so I used Quaternions - which are also not straightforward - but hey, it works.