This OpenGL program displays a cityscape and allows the user to fly around it using a free camera. Pressing 'w' and 's' moves the camera forward and backwards respectively, while dragging the mouse will change its orientation.
freecam.cpp:
// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: Fly around a cityscape using a free camera #include <math.h> #ifdef __APPLE__ #include <GLUT/glut.h> #else #include <GL/glut.h> #endif #include <stdio.h> #include <stdlib.h> #include <ctype.h> // GLOBAL VARIABLES //////////////////////////////////////////////////////////// static size_t windowWidth = 640; static size_t windowHeight = 480; static float aspectRatio; GLint leftMouseButton, rightMouseButton; //status of the mouse buttons int mouseX = 0, mouseY = 0; //last known X and Y of the mouse float cameraX, cameraY, cameraZ; //camera position in cartesian coordinates float cameraTheta, cameraPhi; //camera DIRECTION in spherical coordinates float dirX, dirY, dirZ; //camera DIRECTION in cartesian coordinates GLuint environmentDL; //display list for the 'city' // getRand() /////////////////////////////////////////////////////////////////// // // Simple helper function to return a random number between 0.0f and 1.0f. // //////////////////////////////////////////////////////////////////////////////// float getRand() { return rand() / (float)RAND_MAX; } // generateEnvironmentDL() ///////////////////////////////////////////////////// // // This function creates a display list with the code to draw a simple // environment for the user to navigate through. // // And yes, it uses a global variable for the display list. // I know, I know! Kids: don't try this at home. There's something to be said // for object-oriented programming after all. // //////////////////////////////////////////////////////////////////////////////// void generateEnvironmentDL() { int gridX = 100; int gridY = 100; float spacing = 1.1f; environmentDL = glGenLists(1); glNewList(environmentDL, GL_COMPILE); //psych! everything's on a grid. for(int i = 0; i < gridX; i++) { for(int j = 0; j < gridY; j++) { //don't just draw a building ANYWHERE. if(i % 2 && j % 2 && getRand() < 0.4f) { glPushMatrix(); glTranslatef((i - gridX/2.0f)*spacing, 0.0f, (j - gridY/2.0f)*spacing); float maxVal = gridX/2.0f; float height = 1 - (fabs(i-gridX/2.0f) + fabs(j-gridY/2.0f))/maxVal; height = powf(height, 2.5)*10 + 1; //center buildings are bigger! a'doi. glTranslatef(0, height/2.0f, 0); glScalef(1, height, 1); glutSolidCube(1.0); glPopMatrix(); } } } glEndList(); } // recomputeOrientation() ////////////////////////////////////////////////////// // // This function updates the camera's position in cartesian coordinates based // on its position in spherical coordinates. Should be called every time // cameraTheta, cameraPhi, or cameraRadius is updated. // //////////////////////////////////////////////////////////////////////////////// void recomputeOrientation() { dirX = sinf(cameraTheta)*sinf(cameraPhi); dirZ = -cosf(cameraTheta)*sinf(cameraPhi); dirY = -cosf(cameraPhi); //and normalize this directional vector! float mag = sqrt( dirX*dirX + dirY*dirY + dirZ*dirZ ); dirX /= mag; dirY /= mag; dirZ /= mag; glutPostRedisplay(); } // resizeWindow() ////////////////////////////////////////////////////////////// // // GLUT callback for window resizing. Resets GL_PROJECTION matrix and viewport. // //////////////////////////////////////////////////////////////////////////////// void resizeWindow(int w, int h) { aspectRatio = w / (float)h; windowWidth = w; windowHeight = h; //update the viewport to fill the window glViewport(0, 0, w, h); //update the projection matrix with the new window properties glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0,aspectRatio,0.1,100000); } // mouseCallback() ///////////////////////////////////////////////////////////// // // GLUT callback for mouse clicks. We save the state of the mouse button // when this is called so that we can check the status of the mouse // buttons inside the motion callback (whether they are up or down). // //////////////////////////////////////////////////////////////////////////////// void mouseCallback(int button, int state, int thisX, int thisY) { //update the left and right mouse button states, if applicable if(button == GLUT_LEFT_BUTTON) leftMouseButton = state; else if(button == GLUT_RIGHT_BUTTON) rightMouseButton = state; //and update the last seen X and Y coordinates of the mouse mouseX = thisX; mouseY = thisY; } // mouseMotion() /////////////////////////////////////////////////////////////// // // GLUT callback for mouse movement. We update cameraPhi, cameraTheta, and/or // cameraRadius based on how much the user has moved the mouse in the // X or Y directions (in screen space) and whether they have held down // the left or right mouse buttons. If the user hasn't held down any // buttons, the function just updates the last seen mouse X and Y coords. // //////////////////////////////////////////////////////////////////////////////// void mouseMotion(int x, int y) { if(leftMouseButton == GLUT_DOWN) { cameraTheta += (x - mouseX)*0.005; cameraPhi += (mouseY - y)*0.005; // make sure that phi stays within the range (0, M_PI) if(cameraPhi <= 0) cameraPhi = 0+0.001; if(cameraPhi >= M_PI) cameraPhi = M_PI-0.001; recomputeOrientation(); //update camera (x,y,z) based on (radius,theta,phi) } mouseX = x; mouseY = y; } // initScene() ///////////////////////////////////////////////////////////////// // // A basic scene initialization function; should be called once after the // OpenGL context has been created. Doesn't need to be called further. // //////////////////////////////////////////////////////////////////////////////// void initScene() { glEnable(GL_DEPTH_TEST); float lightCol[4] = { 1, 1, 1, 1}; float ambientCol[4] = {0.3, 0.3, 0.3, 1.0}; float lPosition[4] = { 10, 10, 10, 1 }; glLightfv(GL_LIGHT0,GL_POSITION,lPosition); glLightfv(GL_LIGHT0,GL_DIFFUSE,lightCol); glLightfv(GL_LIGHT0, GL_AMBIENT, ambientCol); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); //glShadeModel(GL_SMOOTH); glShadeModel(GL_FLAT); generateEnvironmentDL(); } // renderScene() /////////////////////////////////////////////////////////////// // // GLUT callback for scene rendering. Sets up the modelview matrix, renders // a scene to the back buffer, and switches the back buffer with the // front buffer (what the user sees). // //////////////////////////////////////////////////////////////////////////////// void renderScene(void) { //update the modelview matrix based on the camera's position glMatrixMode(GL_MODELVIEW); //make sure we aren't changing the projection matrix! glLoadIdentity(); gluLookAt(cameraX, cameraY, cameraZ, //camera is located at (x,y,z) cameraX+dirX, cameraY+dirY, cameraZ+dirZ, //camera is looking at at (x,y,z) + (dx,dy,dz) -- straight ahead 0.0f,1.0f,0.0f); //up vector is (0,1,0) (positive Y) //clear the render buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glCallList(environmentDL); //push the back buffer to the screen glutSwapBuffers(); } // normalKeysDown() //////////////////////////////////////////////////////////// // // GLUT keyboard callback; gets called when the user presses a key. // //////////////////////////////////////////////////////////////////////////////// void normalKeysDown(unsigned char key, int x, int y) { if(key == 'q' || key == 'Q') exit(0); //because the direction vector is unit length, and we probably don't want //to move one full unit every time a button is pressed, just create a constant //to keep track of how far we want to move at each step. you could make //this change w.r.t. the amount of time the button's held down for //simple scale-sensitive movement! float movementConstant = 0.3f; //move forward! if(key == 'w' || key == 'W') { //that's as simple as just moving along the direction. cameraX += dirX*movementConstant; cameraY += dirY*movementConstant; cameraZ += dirZ*movementConstant; } //move backwards! if(key == 's' || key == 'S') { //just move BACKWARDS along the direction. cameraX -= dirX*movementConstant; cameraY -= dirY*movementConstant; cameraZ -= dirZ*movementConstant; } glutPostRedisplay(); } // main() ////////////////////////////////////////////////////////////////////// // // Program entry point. Does not process command line arguments. // //////////////////////////////////////////////////////////////////////////////// int main(int argc, char **argv) { //create a double-buffered GLUT window at (50,50) with predefined windowsize glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(50,50); glutInitWindowSize(windowWidth,windowHeight); glutCreateWindow("flight simulator 0.31"); //give the camera a scenic starting point. cameraX = 60; cameraY = 40; cameraZ = 30; cameraTheta = -M_PI / 3.0f; cameraPhi = M_PI / 2.8f; recomputeOrientation(); //register callback functions... glutSetKeyRepeat(GLUT_KEY_REPEAT_ON); glutKeyboardFunc(normalKeysDown); glutDisplayFunc(renderScene); glutReshapeFunc(resizeWindow); glutMouseFunc(mouseCallback); glutMotionFunc(mouseMotion); //do some basic OpenGL setup initScene(); //and enter the GLUT loop, never to exit. glutMainLoop(); return(0); }