Camera Rotation & Translation, and Reference Ground

CS 480 Lecture, Dr. Lawlor

Camera Rotation

It's extremely easy to use glRotatef to make the camera rotate when you move the mouse.  Sadly, it's extremely difficult to make it rotate well--the trouble is that the second rotation is going to be in the frame of the first rotation, which keeps changing.  Hence the "two rotations" method invariably results in the camera moving in weird silly ways.  Go ahead, try it!

A better approach is to explicitly represent the camera's orientation using three axes, like with a class like this:
/* Orthonormal coordinate frame */
class ortho_frame {
public:
/* Unit vectors pointing along axes of our frame.
X cross Y is Z. (right-handed coordinate system)
*/
vec3 x,y,z;
ortho_frame() :x(1,0,0), y(0,1,0), z(0,0,1) {}

/* Reorient this coordinate frame by this far along in the X and Y axes.
"dx" is the distance along the z axis to push the x axis;
"dy" the distance along the z axis to push the y axis.
*/
void nudge(double dx,double dy) {
x+=dx*z;
y+=dy*z;
orthonormalize();
}

/* Reconstruct an orthonormal frame from X and Y axes.
Y is primary, X is secondary, Z is tertiary.
*/
void orthonormalize(void) {
y=normalize(y);
z=normalize(cross(x,y));
x=normalize(cross(y,z));
}
};
ortho_frame orient; /* camera orientation */
This class keeps track of three unit vectors: X, Y, and Z.  In classic camera fashion, you're looking down the negative Z axis, X is to your right, and Y is up.  The key method here is "nudge", which pushes or pulls on the X and Y axes in the Z direction.  If you repeatedly "nudge" the coordinate frame, this makes the camera X and Y axes rotate.  Typically, you rotate the coordinate frame in response to mouse motion, like from these GLUT mouse event handling functions:
int mouse_x=0,mouse_y=0; /* last known mouse position */
void mouse(int button,int state,int x,int y)
{ /* mouse being pressed or released--save position for motion */
mouse_x=x; mouse_y=y;
}
void motion(int x, int y) {
float scale=0.01; /* radians rotation per pixel of mouse motion */
orient.nudge(
scale*(x-mouse_x),
-scale*(y-mouse_y) /* OpenGL Y goes the opposite direction of GLUT Y */
);
mouse_x=x; mouse_y=y; /* save old mouse positions */
}
You'd of course register these methods from your main function like so:
	glutMouseFunc(mouse);
glutMotionFunc(motion);
And finally, to rotate your object to match this coordinate frame, just gluLookAt along the new axes during your display function.
#define VEC3_TO_XYZ(v) (v).x,(v).y,(v).z /* convert a vec3 to three float arguments */	
gluLookAt(VEC3_TO_XYZ(vec3(0.0)),
VEC3_TO_XYZ(-orient.z), /* we're looking down the -Z axis */
VEC3_TO_XYZ(orient.y) /* camera up vector */
);
This approach means that moving the mouse to the left always moves the object the same way, even after a rotation.  This is called a "virtual trackball", and it's common and works well.

Oh, and if you don't want people to be able to tilt their heads, you also need this:
	/* Don't allow head tilting! */
orient.x.y=0.0;
orient.y=cross(orient.z,orient.x);
orient.orthonormalize();

Camera Motion

I like first-person computer games where you move using the "WASD" keys and look around with the mouse.  It's really easy to write these, especially if you do the following:
vec3 camera; /* camera location */
void keyboard(unsigned char key,int x,int y) {
if (key=='w') camera.z++;
if (key=='s') camera.z--;
if (key=='a') camera.x--;
if (key=='d') camera.x++;
}
// in main: glutKeyboardFunc(keyboard);
However, this doesn't work well--the "keyboard" event handling function is only called at the keyboard repeat rate, which is somewhere between 3 and 20 times per second.  This is hopefully far less than our graphics framerate!  The result is hence ugly, stuttery motion.

You can get smooth motion by keeping track of which keys are down, and then moving the camera in the display function based on which keys are currently pressed, like so:
bool key_down[256]; /* if true, the corresponding key is down */
void keyboard(unsigned char key,int x,int y) {
key_down[key]=true;
}
void keyboardUp(unsigned char key,int x,int y) {
key_down[key]=false;
}
// in main: glutKeyboardFunc(keyboard); glutKeyboardUpFunc(keyboardUp);

// in display function:
float vel=3.0*display_dt; /* meters camera motion per frame */
if (key_down[(int)'w']) camera-=vel*orient.z;
if (key_down[(int)'s']) camera+=vel*orient.z;
if (key_down[(int)'a']) camera-=vel*orient.x;
if (key_down[(int)'d']) camera+=vel*orient.x;
This makes the camera move a little bit per frame while the corresponding keys are held down.  If you want even smoother motion, you can slowly ramp up the velocity over time once a key goes down, instead of just instantly changing speed.

Ground with Grid Lines

It's trivially easy to draw in a ground plane, and it sure makes it easier to tell where you are in the world:
	// Draw in a ground plane
float floor=0.0; /* height of ground */
float big=30; /* size of the ground */
glBegin(GL_QUADS);
glColor4f(0.6,0.6,0.6,1.0); /* light, opaque grey */
glVertex3f(-big,floor,-big);
glVertex3f(+big,floor,-big);
glVertex3f(+big,floor,+big);
glVertex3f(-big,floor,+big);
glEnd();
It helps even more to put in a few little grid lines, like so:
	glBegin(GL_LINES);
glColor4f(0.0,0.0,0.0,0.4); /* transparent black -> darker grey */
for (int v=-big;v<=big;v++) {
glVertex3f(v,floor,-big);
glVertex3f(v,floor,+big);
glVertex3f(-big,floor,v);
glVertex3f(+big,floor,v);
}
glEnd();
This all works, but...

Z buffer fighting

Everybody's Z buffer fighting!  (Not Kung Fu Fighting...)   Note that this looks about a zillion times worse when it's animating.  The fix is just to turn off depth writes after we draw the scene, but before we draw the plane--this lets the plane and lines coexist instead of the silly back-and-forth fighting over who's on top.
	glDepthMask(GL_FALSE); /* don't write to depth buffer (no Z fighting!) */
opaque black lines

Much better!  We can make the lines look even better by turning on GL_LINE_SMOOTH, which antialiases the edges of the lines by fading away their alpha.  We also need alpha blending and the right blend function for this to work:
	glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
smooth edged lines

Ah, that looks much better, and it animates beautifully.  Plus, it's insanely fast--so you might as well add some lines to your simulators, at least so you can tell how big 1 unit is!