Example 09

Example 09

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.

ex_09/model.png

Source Code

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);
}

Files