Rotations

Back to index

Head in a spin!!! Rotation is tricky.

To start with, we draw a stick:

1

The code to draw the stick object is:

 public void draw(GL gl)
    {
        gl.glBegin(GL.GL_LINES);
        gl.glColor3d(0,0,1);
        gl.glVertex3d(0.0,0.0,0.0);
        gl.glColor3d(1,0,0);
        gl.glVertex3d(0,length,0);     
        gl.glEnd();      
    }

and the code in display is just

 final GL gl = gLDrawable.getGL();
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();
        glu.gluLookAt(camera.getX(), camera.getY(), ..;
        stick.draw(gl);
    }

We are looking at 0,0,0, and the y axis is up, so the stick points 'upwards'. Now do a rotate:

        gl.glRotatef(90,0,0,1);
        stick.draw(gl);

and we see2

First issue - the first argument to glRotate is the angle in degrees. Aaargh!! What other library function uses degrees? Why? Was there an idea that OGL users were so math naive they couldn't use radians? Ha! Rubbish.

Anyway, so we've rotated it about the z axis ('into the screen') so its turned from upright to lie towards the left - on the negative x axis. That makes sense.

Now lets do two rotations:

        gl.glRotatef(90,0,1,0);
        gl.glRotatef(90,0,0,1);
        stick.draw(gl);

So that rotates on y, then on z. Rotation on y should do nothing, since stick points up the y axis anyway, then rotate on z should turn it to our left again, on negative x. This is what we see

3 Uh? It points towards us, on the negative z axis? How?

Because the code puts the following things in the pipeline:

rotate on y, then rotate on z, then draw it

and you get two matrix multiplications:

RotY X RotZ X drawn vectors

and these are done right to left. So in fact it rotates on z first, pointing it to the left, then rotates on y, swinging it round to point to us. Aha!!

So not only do you have to deal with the fact that rotations do not commute - you also have to think that they are applied in the reverse sequence to how they appear in code. Mmm.

So how can we rotate our stick onto an arbitrary direction? The natural idea is to have 3 rotations, on x, y and z, but.. well that is tricky, and also wasteful, since we should be a able to do a single turn from upright to where we want it. My suggestion (probably not the first) is to maintain a vector with the object representing its 'axis', then draw it by a rotation to this, followed by a draw in its 'upright' position. We need to know the parameters for the rotate - the angle and the axis of rotation. So we need to work out this:

4

First we include an axis vector with our Stick definition:

public class Stick {
    /** spatial coords of the center */
    private float x,y,z;
    /** which way it points */
    private WMVector axis;
    /** how long is the stick */
    private float length;
    
    public Stick()
    {
        x=y=z=0;
        axis=new WMVector(1,1,0);
        length=1.0f;
    }

To draw the stick, we calculate and do the rotation, and then draw it as if it were upright

public void draw(GL gl)
    {
        WMVector up=new WMVector(0,1,0);
        float angle = axis.angleWith(up)*180/(float)Math.PI;
        WMVector turnAxis = up.cross(axis);
        gl.glTranslatef(x, y, z);
        gl.glRotated(angle, turnAxis.getX(), turnAxis.getY(), turnAxis.getZ());
        
        gl.glLineWidth(2.0f);
        gl.glBegin(GL.GL_LINES);
        gl.glColor3d(0,0,1);
        gl.glVertex3d(0.0,0.0,0.0);
        gl.glColor3d(1,0,0);
        gl.glVertex3d(0,length,0);     
        gl.glEnd();      
    }

This means we have to be able to work out the angle between two vectors, which we can do if we also have the dot product:

/**
 * Find the angle between this vector and another.
 * 
 * @param other The other vector
 * @return The angle between them, in radians, in the range 0 to ?
 */
    public float angleWith(WMVector other)
    {
      WMVector thisNorm = normalise();
      WMVector otherNorm = other.normalise();
      float cos = thisNorm.dot(otherNorm);
      return (float)Math.acos(cos);
    }
    /**
     * The dot product.
     * Return the dot product between this vector and another
     * @param other The other vector
     * @return The dot product
     */
    public float dot(WMVector other)
    {
        return (x*other.x+y*other.y+z*other.z);
    }

This works because the dot product of 2 vectors a and b is |a| |b| cos θ, so if we normalise a and b, this is just cos θ, and inverse cosine gives us θ.

This also means it is easy to make the object move in the direction it is pointing:

public void move()
    {
        x+=speed*axis.getX();
        y+=speed*axis.getY();
        z+=speed*axis.getZ();
    }

So in display() we call stick.move() before stick.draw(). The glTranslate in the draw method moves it to the new position.