Example 15

Example 15

This OpenGL allows the user to move a 3D vehicle on flat surface. Use the left and right keys to rotate the vehicle and the up and down keys to move the vehicle forwards and backwards. The camera can be moved using the mouse.

ex_15/vehicle.png

Source Code

main.cpp:

// Alexandri Zavodny
// CSE 40166: Computer Graphics, Fall 2010
// Example: simple vehicle with hierarchical matrix structure


#include <math.h>
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>

#include "point.h"

#define WHEEL_UP 3
#define WHEEL_DOWN 4

// GLOBAL VARIABLES ////////////////////////////////////////////////////////////

static size_t windowWidth = 640;
static size_t windowHeight = 480;
static float aspectRatio;


GLint leftMouseButton, 
        rightMouseButton,
        midMouseButton;                     //status of the mouse buttons
int mouseX = 0, mouseY = 0;                 //last known X and Y of the mouse

Point cameraTPR, cameraXYZ;                 //camera position in spherical and 
                                            //cartesian coordinates

GLUquadric *quad = NULL;                    //allocate this once at the start
                                            //of the program and re-use it!

bool arrowKeyStates[4] = {false};           //boolean vector which determines
                                            //whether an arrow key has been 
                                            //held down.  [0-3] = [left, right, up, down]

Point carPosition;
float carTheta;
float carAnimationValue = 0.0f;             //this value changes whenever the car gets moved, etc.
                                            //and the amount that it changes is based on distance moved.
                                            //we save one value and use it to rotate wheels and such
                                            //just so we don't have to save the rotation of each component
                                            //of the car.

int lastTime = 0;                           //update this each frame with glutGet(GLUT_ELAPSED_TIME),
                                            //and compare it against the current result of glutGet(GLUT_ELAPSED_TIME)
                                            //to see how much time has passed between frame renderings.


// recomputeOrientation() //////////////////////////////////////////////////////
//
// This function updates the camera's position in cartesian coordinates based 
//  on its position in spherical coordinates. Should be called every time 
//  cameraTPR.x, cameraTPR.y, or cameraTPR.z is updated. 
//
////////////////////////////////////////////////////////////////////////////////
void recomputeOrientation()
{
    cameraXYZ.x = cameraTPR.z *  sinf(cameraTPR.x)*sinf(cameraTPR.y);
    cameraXYZ.z = cameraTPR.z * -cosf(cameraTPR.x)*sinf(cameraTPR.y);
    cameraXYZ.y = cameraTPR.z * -cosf(cameraTPR.y);

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

// drawGrid() //////////////////////////////////////////////////////////////////
//
//  Draws a 10x10 grid around the origin in the XZ plane. Just to give the scene
//      a sense of scale.
//
////////////////////////////////////////////////////////////////////////////////
void drawGrid()
{
    glPushAttrib(GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);


    for(int k = 0; k < 2; k++)
    {
        for(int i = -5; i < 6; i++)
        {
            glBegin(GL_LINE_STRIP);
            for(int j = -5; j < 6; j++)
                glVertex3f(k==0?i:j, 0, k==0?j:i);
            glEnd();
        }
    }

    glPopAttrib();
}


// drawSolidBox() //////////////////////////////////////////////////////////////
//
//  Draws a solid box of the specified dimensions at the current point.
//  Uses glutSolidCube to draw something that has appropriate normals, texture
//      coordinates, etc., but since glutSolidCube can only draw a cube, we push
//      and pop a scale matrix so that the cube gets drawn as a box with 
//      specified dimensions. We do this instead in a function just so that
//      we don't have to worry about glScalef affecting our other matrices later.
//
////////////////////////////////////////////////////////////////////////////////
void drawSolidBox(float xSize, float ySize, float zSize)
{
    glPushMatrix();
        glScalef(xSize, ySize, zSize);
        glutSolidCube(1.0f);
    glPopMatrix();
}


// drawCylinder() //////////////////////////////////////////////////////////////
//
//  Because we have to use a gluQuadric object to draw a cylinder, and that's
//      less than pretty -- AND, because we have to draw the caps on the ends
//      of the cylinder ourself, put all that code in its own function.
//
////////////////////////////////////////////////////////////////////////////////
void drawCylinder(float outerRadius, float innerRadius, float wheelDepth, int resolution)
{
    gluQuadricOrientation(quad, GLU_OUTSIDE);   //ensure that winding order is appropriate
    gluCylinder(quad, outerRadius, outerRadius, wheelDepth, resolution, 1);
    glPushMatrix();
        glTranslatef(0,0,wheelDepth);
        gluDisk(quad, innerRadius, outerRadius, resolution, 1);
    glPopMatrix();

    gluQuadricOrientation(quad, GLU_INSIDE);
    gluCylinder(quad, innerRadius, innerRadius, wheelDepth, resolution, 1);
    //gluCylinder sets the left side of the cylinder at the current Z
    //position, so no need to translate now
    gluDisk(quad, innerRadius, outerRadius, resolution, 1);

    gluQuadricOrientation(quad, GLU_OUTSIDE);   //ensure that winding order is appropriate
}

// drawWheel() /////////////////////////////////////////////////////////////////
//
//  Draws a wheel at the current OpenGL location. We have this code in its own
//      function because we're going to be drawing 4 wheels for the car and
//      they're all going to be the same. 
//
////////////////////////////////////////////////////////////////////////////////
void drawWheel()
{
    //the cylinder is most of the wheel! no lie.
    drawCylinder(0.5f, 0.4f, 0.1f, 16);

    //draw some spokes
    glPushMatrix();
        glTranslatef(0,0,0.05f);

        for(int i = 0; i < 8; i++)
        {
            glPushMatrix();
            glRotatef(45*i, 0,0,-1);
            glTranslatef(0,0.25,0);

            drawSolidBox(0.1,0.4,0.1);
            glPopMatrix();
        }
    glPopMatrix();
}

// drawCar() ///////////////////////////////////////////////////////////////////
//
//  Draws our car! ... at the specified position, and pointing in the direction
//      of carTheta. Also uses the global variable carAnimation to control
//      how much its parts should move.
//
////////////////////////////////////////////////////////////////////////////////
void drawCar(float x, float y, float z, float carTheta)
{
    glPushMatrix();

    glTranslatef(x,y,z);
    glRotatef(carTheta*180/M_PI, 0, -1 ,0);

    //draw the body of the car
    drawSolidBox(2,1,1);

    //and the, uh, cockpit?
    glPushMatrix();
        glTranslatef(-0.5,0.75,0);
        drawSolidBox(1,0.5,1);

        //and, like, a helicopter blade.
        float heliSpeed = 15;              //heheh it's moving "heli fast"
        glPushMatrix();
            glTranslatef(0,0.5,0);
            glRotatef(carAnimationValue*heliSpeed, 0, 1, 0);

            drawSolidBox(0.1,1,0.1);
            
            //move on top of the rotor...
            glTranslatef(0,0.5,0);

            drawSolidBox(1.5,0.1,0.1);
            drawSolidBox(0.1,0.1,1.5);
        glPopMatrix();

    glPopMatrix();


    float wheelSpeed = 5.0f;
    //all four wheels...
    //wheel one...
    glPushMatrix();
        glTranslatef(-0.75, 0, 0.5);
        glRotatef(carAnimationValue*wheelSpeed, 0, 0, -1);
        drawWheel();
    glPopMatrix();

    //wheel two...
    glPushMatrix();
        glTranslatef(0.75, 0, 0.5);
        glRotatef(carAnimationValue*wheelSpeed, 0, 0, -1);
        drawWheel();
    glPopMatrix();
    
    //wheel three....
    glPushMatrix();
        glTranslatef(-0.75, 0, -0.6);
        glRotatef(carAnimationValue*wheelSpeed, 0, 0, -1);
        drawWheel();
    glPopMatrix();

    //wheel the fourth
    glPushMatrix();
        glTranslatef(0.75, 0, -0.6);
        //this one is the gimp wheel so it rotates 3/4s the speed
        glRotatef(carAnimationValue*wheelSpeed*0.75, 0, 0, -1);
        drawWheel();
    glPopMatrix();

    //and because we're awesome we're gonna do some freaking sparkle.. things... off the back
    float tailSpeed = 2.0f;
    glPushMatrix();
        glTranslatef(-1.0f, 0.25, 0);
        
        glPushMatrix();
            glRotatef(15*cosf(carAnimationValue*tailSpeed/2.0),0,1,0);
            glTranslatef(-0.5f,0,0);

            //the first tail segment!
            drawSolidBox(1,0.125,0.125);

            glTranslatef(-0.5,0,0);
            glRotatef(15*cosf(carAnimationValue*tailSpeed/2.0),0,1,0);
            glTranslatef(-0.25,0,0);

            //the second tail segment!
            drawSolidBox(0.5,0.10,0.10);

            glTranslatef(-0.3,0,0);
            glRotatef(15*cosf(carAnimationValue*tailSpeed/2.0), 0, 1, 0);
            glTranslatef(-0.1,0,0);

            //the final tail segment!
            drawSolidBox(0.3, 0.1, 0.1);
        glPopMatrix();
    glPopMatrix();


    glPopMatrix();
}


// 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;
    else if(button == GLUT_MIDDLE_BUTTON)
        midMouseButton = state;

    if(button == WHEEL_UP)
    {
        cameraTPR.z += 0.05;
        recomputeOrientation();
    }

    if(button == WHEEL_DOWN)
    {
        cameraTPR.z -= 0.05;
        recomputeOrientation();
    }

    //and update the last seen X and Y coordinates of the mouse
    mouseX = thisX;
    mouseY = thisY;
}

// mouseMotion() ///////////////////////////////////////////////////////////////
//
//  GLUT callback for mouse movement. We update cameraTPR.y, cameraTPR.x, and/or
//      cameraTPR.z 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)
    {
        cameraTPR.x += (x - mouseX)*0.005;
        cameraTPR.y += (y - mouseY)*0.005;

        // make sure that phi stays within the range (0, M_PI)
        if(cameraTPR.y <= 0)
            cameraTPR.y = 0+0.001;
        if(cameraTPR.y >= M_PI)
            cameraTPR.y = M_PI-0.001;
        
        
        recomputeOrientation();     //update camera (x,y,z) based on (radius,theta,phi)
    } else if(rightMouseButton == GLUT_DOWN || midMouseButton == GLUT_DOWN) {
        double totalChangeSq = (x - mouseX) + (y - mouseY);
        
        cameraTPR.z += totalChangeSq*0.01;
        
        //limit the camera radius to some reasonable values so the user can't get lost
        if(cameraTPR.z < 1.0) 
            cameraTPR.z = 1.0;
        if(cameraTPR.z > 40.0) 
            cameraTPR.z = 40.0;

        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};
    glLightfv(GL_LIGHT0,GL_DIFFUSE,lightCol);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientCol);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);


    glShadeModel(GL_SMOOTH);
    //glShadeModel(GL_FLAT);
}


// doUpdateLogic() /////////////////////////////////////////////////////////////
//
//  This function, which should get called once per preframe, updates the position
//      of objects in the scene based on what keys the user has held down, if any.
//
////////////////////////////////////////////////////////////////////////////////
void doUpdateLogic(float secondsElapsed)
{
    //check the key states
    float movementConstant = secondsElapsed*2;
    if(arrowKeyStates[2])
    {
        float xMovement = cosf(carTheta)*movementConstant;
        float zMovement = sinf(carTheta)*movementConstant;
        carPosition.x += xMovement;
        carPosition.z += zMovement;
    }

    if(arrowKeyStates[3])
    {
        float xMovement = cosf(carTheta)*movementConstant;
        float zMovement = sinf(carTheta)*movementConstant;
        carPosition.x -= xMovement;
        carPosition.z -= zMovement;
    }

    if(arrowKeyStates[0])
        carTheta += movementConstant;

    if(arrowKeyStates[1])
        carTheta -= movementConstant;

    //update the car's animation value! and speed it up a bit cause.. why not.
    carAnimationValue += (arrowKeyStates[2] ^ arrowKeyStates[3]) ? 0.005f : 0.0f;
}

// renderScene() ///////////////////////////////////////////////////////////////
//
//  GLUT callback for scene rendering. Sets up the modelview matrix, renders
//      a teapot to the back buffer, and switches the back buffer with the
//      front buffer (what the user sees).
//
////////////////////////////////////////////////////////////////////////////////
void renderScene(void) 
{
    int currentTime = glutGet(GLUT_ELAPSED_TIME);
    float timeDifference = (currentTime - lastTime) / 1000.0f;
    lastTime = currentTime;
    doUpdateLogic(timeDifference);

    //update the modelview matrix based on the camera's position
    glMatrixMode(GL_MODELVIEW);         //make sure we aren't changing the projection matrix!
    glLoadIdentity();
    gluLookAt(cameraXYZ.x, cameraXYZ.y, cameraXYZ.z,    //camera is located at (x,y,z)
                0, 0, 0,                                //camera is looking at (0,0,0)
                0.0f,1.0f,0.0f);                        //up vector is (0,1,0) (positive Y)

    float lPosition[4] = { 10, 10, 10, 1 };
    glLightfv(GL_LIGHT0,GL_POSITION,lPosition);

    //clear the render buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_NORMALIZE);
    drawCar(carPosition.x, carPosition.y, carPosition.z, carTheta);

    drawGrid();


    //push the back buffer to the screen
    glutSwapBuffers();
}


// normalKeys() ////////////////////////////////////////////////////////////////
//
//  GLUT keyboard callback.
//
////////////////////////////////////////////////////////////////////////////////
void normalKeys(unsigned char key, int x, int y) 
{
    if(key == 'q' || key == 'Q')
        exit(0);
}


// specialKeys() ///////////////////////////////////////////////////////////////
//
//  The GLUT callback for when arrow keys are pressed. Updates the status
//      array appropriately.
//
////////////////////////////////////////////////////////////////////////////////
void specialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_LEFT)
        arrowKeyStates[0] = true;

    if(key == GLUT_KEY_RIGHT)
        arrowKeyStates[1] = true;

    if(key == GLUT_KEY_UP)
        arrowKeyStates[2] = true;

    if(key == GLUT_KEY_DOWN)
        arrowKeyStates[3] = true;

    glutPostRedisplay();
}

// specialKeysUp() /////////////////////////////////////////////////////////////
//
//  The GLUT callback for when arrow keys are released. Updates the status
//      array appropriately.
//
////////////////////////////////////////////////////////////////////////////////
void specialKeysUp(int key, int x, int y)
{
    if(key == GLUT_KEY_LEFT)
        arrowKeyStates[0] = false;

    if(key == GLUT_KEY_RIGHT)
        arrowKeyStates[1] = false;

    if(key == GLUT_KEY_UP)
        arrowKeyStates[2] = false;

    if(key == GLUT_KEY_DOWN)
        arrowKeyStates[3] = false;
}


// 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("beep beep! it's not a jeep.");

    //give the camera a 'pretty' starting point!
    cameraTPR.z = 10.0f;
    cameraTPR.x = 2.80;
    cameraTPR.y = 2.0;
    recomputeOrientation();

    carPosition = Point(0,0.5,0);
    carTheta = 0.0f;
    quad = gluNewQuadric();

    //register callback functions...
    glutKeyboardFunc(normalKeys);
    glutSpecialFunc(specialKeys);
    glutSpecialUpFunc(specialKeysUp);
    glutDisplayFunc(renderScene);
    glutIdleFunc(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