To main page | 3dengine.org

Spectator


Spectator code or free-fly camera is code that let's you look around, it's when you move mouse right to look right, up - to look up and use W-S keys to move back and forwards and A-D to strafe right and left.

The controls can be different, but idea stays the same.

There are quite a few approaches to this. Most of approaches rely on storing some angles and then loading identity matrix each frame and via series of glRotate/glTranslate and vector/matrix math - setting the camera. I have another approach, which doesn't require resetting (loading identity) of camera each frame.

gluLookAt won't help us here as there's a lot of math to calculate looking right or up with mouse.

Looking around

With my approach you need only to store x,y,z of camera. OpenGL will keep track of the rest.

So, let's say mouse moved dx pixels to the right and dy pixels up. (If dx is negative, then mouse moved left, so is with dy).
glRotate(model, -dx, 0,1,0);
glRotate(model, dy, 1,0,0);
update_matrix();
update_matrix is a function which I will describe later. You can change the signs (- or +) to find which feels more natural for you (see Inverting Y of mouse). These two commands are rotating around LOCAL axes of camera - dx around local up-axis, dy around local right-axis.

Move forwards-backwards (W-S keys)

Again supply dx as the amount we want to move forward (negative dx for backward)
GLfloat model[16];
glGetFloatv(GL_MODELVIEW_MATRIX, &model);
x = x - model[2]*dx;
y = y - model[6]*dx;
z = z - model[10]*dx;
update_matrix();
model[2],model[6],model[10] is actually camera's back vector in global coordinates, see extracting right, up and back vectors from modelview matrix for explanation.

model is the modelview matrix, which can be read via glGetFloatv(GL_MODELVIEW_MATRIX, &model) in C++ or model = glGetFloat(GL_MODELVIEW_MATRIX) in PyOpenGL.

x,y,z are the variables we need to store (camera position, actually the center of image plane).

Strafe (A-D keys)

How do we strafe?
GLfloat model[16];
glGetFloatv(GL_MODELVIEW_MATRIX, &model);

x = x - model[0]*dx;
y = y - model[4]*dx;
z = z - model[8]*dx;

x = x - model[1]*dy;
y = y - model[5]*dy;
z = z - model[9]*dy;

update_matrix();

update_matrix()

update_matrix is a function that recalculates position in new coordinates.
GLfloat model[16];
glGetFloatv(GL_MODELVIEW_MATRIX, &model);
model[12]=0;
model[13]=0;
model[14]=0;
model[15]=1;
glTranslate(model, -x,-y,-z);

// compensate roll

glLoadMatrix(model);

Compensating roll

The last thing we need to do is to make sure camera is vertical always (which is a major annoyance and the above method will induce roll). Luckily it's easy to fix. See the "Roll" article, part "Compensating roll" and make corrections to "update_matrix" function above.

Basically you need to:
glRotate(atan2(-model[4], model[5]) * K, 0,0,1);
K can usually be set to 1.0, but depending on the programming language you might need to set it to radians-to-degrees constant.

Full code

You can see example implementation in Spectator (PyOpenGL) page.

Further work

The only "problem" is when camera approaches pitch -90 or 90 (looking exactly up or down) - the swings would become wild around vertical axis if user tries to move mouse further down. You can try to limit him to pitch of -89 to 89 - that won't be very noticeable, but relieve him of headaches. (See pitch for calculation of pitch).