Example 13

Example 13

This OpenGL allows the user to move a 3D wagon on flat surface. Use the left and right arrow keys to rotate the vehicle and the up and down keys to control the speed. Space will stop the vehicle. There are 4 camera modes: first person, bird's eye, third person, orbit. To switch between these camera modes, use keys 1 - 4 respectively. The mouse scroll wheel may be used for zooming if allowed in the particular camera mode. Pressing 0 will reset the vehicle to the origin and in a resting position. A mini-map is included. Toggle the wireframe model of the wagon, press w.

ex_13/wagon.png

Source Code

wagon.cc:

// Peter Bui
// CSE 40166 Computer Graphics (Fall 2010)
// Example 13: wagon 

#include "color.h"
 
#include <cmath>
#include <cstdio>
#include <cstdlib>

#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

using namespace std;

// Constants -------------------------------------------------------------------

#define WHEEL_UP    3
#define WHEEL_DOWN  4

#define CAMERA_DISTANCE_MIN   1.0
#define CAMERA_DISTANCE_MAX 100.0

enum {
    DL_WAGON = 1,
    DL_GRID,
    DL_JENNY_CUB,
    DL_BOX
};

enum {
    CAMERA_FIRST_PERSON,
    CAMERA_BIRDS_EYE,
    CAMERA_THIRD_PERSON,
    CAMERA_ORBIT
};

// Global variables ------------------------------------------------------------

static size_t WindowWidth  = 640;
static size_t WindowHeight = 480;

static GLint  MouseX = 0;
static GLint  MouseY = 0;

static double CameraLatitude  = 45.0; 
static double CameraLongitude = 25.0;
static double CameraDistance  = 50.0;

static double EyeX = -100.0;
static double EyeY = 50.0;
static double EyeZ = 100.0;

static double DirX = 0.0;
static double DirY = 0.0;
static double DirZ = 0.0;

static double WagonOrientation  = 0.0;
static double WagonSpeed        = 0.0;

static double WagonX = 0.0;
static double WagonZ = 0.0;

static double WagonLength    = 10.0;
static double WagonWidth     = 4.0;
static double WagonSideWidth = 0.5;

static double JennyCubOrientation = 0.0;
static double JennyCubOrientationDelta = 2.0;

static GLUquadric *Quadric = NULL;

static int CameraMode = CAMERA_ORBIT;
static bool WireFrameMode = false;

// Update camera ---------------------------------------------------------------

void
update_camera_location()
{
    double L = CameraDistance * cos(M_PI*CameraLongitude/180.0);

    switch (CameraMode) {
        case CAMERA_ORBIT:
            EyeX = WagonX + L * -sin(M_PI*CameraLatitude/180.0);
            EyeY = CameraDistance * sin(M_PI*CameraLongitude/180.0);
            EyeZ = WagonZ + L * cos(M_PI*CameraLatitude/180.0);
            DirX = WagonX;
            DirY = 0.0;
            DirZ = WagonZ;
            break;
        case CAMERA_BIRDS_EYE:
            EyeX = WagonX;
            EyeY = CameraDistance;
            EyeZ = WagonZ;
            DirX = WagonX;
            DirY = 0.0;
            DirZ = WagonZ - 0.01;
            break;
        case CAMERA_FIRST_PERSON:
            EyeX = WagonX;
            EyeY = 6.0;
            EyeZ = WagonZ;
            DirX = WagonX + CameraDistance*cos(WagonOrientation*M_PI/180.0);
            DirY = 6.0;
            DirZ = WagonZ - CameraDistance*sin(WagonOrientation*M_PI/180.0);
            break;
        case CAMERA_THIRD_PERSON:
            EyeX = WagonX - CameraDistance*cos(WagonOrientation*M_PI/180.0);
            EyeY = 10.0;
            EyeZ = WagonZ + CameraDistance*sin(WagonOrientation*M_PI/180.0);
            DirX = WagonX + CameraDistance/2.0*cos(WagonOrientation*M_PI/180.0);
            DirY = 10.0;
            DirZ = WagonZ - CameraDistance/2.0*sin(WagonOrientation*M_PI/180.0);
    }

    glutPostRedisplay();
}

// Initialize scene ------------------------------------------------------------

void
init_scene()
{
    glEnable(GL_DEPTH_TEST);
 
    GLfloat diffuse0[]  = { 0.8, 0.8, 0.8, 1.0 };
    GLfloat ambient0[]  = { 0.2, 0.2, 0.2, 1.0 };
    GLfloat specular0[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat position0[] = { 100.0, 100.0, 100.0, 1.0 };

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse0);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient0);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular0);
    glLightfv(GL_LIGHT0, GL_POSITION, position0);

    glEnable(GL_COLOR_MATERIAL) ;
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

    glEnable(GL_NORMALIZE);
    glEnable(GL_SMOOTH);

    if (Quadric == NULL)
        Quadric = gluNewQuadric();

    update_camera_location();
}

// Draw wagon ------------------------------------------------------------------

void
draw_wagon_base()
{
    glPushMatrix(); {
        glScalef(WagonLength, 1.0, WagonWidth);
        glutSolidCube(1.0);
    } glPopMatrix();
}

void
draw_wagon_side(double tx, double ty, double tz, double sx, double sy, double sz)
{
    glPushMatrix(); {
        glTranslatef(tx, ty, tz);
        glScalef(sx, sy, sz);
        glutSolidCube(1.0);
    } glPopMatrix();
}

void
draw_wagon_logo(double tx, double ty, double tz, bool reflect)
{
    glPushMatrix(); {
        glPushAttrib(GL_LINE_BIT);
        glColor3f(1.0, 1.0, 1.0);
        glLineWidth(4.0);
        glTranslatef(tx, ty, tz);
        glScalef(reflect ? -0.01 : 0.01, 0.01, 0.01);
        for (const char *c = "Radio Flyer"; *c; c++)
            glutStrokeCharacter(GLUT_STROKE_ROMAN, *c);
        glPopAttrib();
    } glPopMatrix();
}

void
draw_wagon_body()
{
    glColor3f(1.0, 0.0, 0.0);

    draw_wagon_base();

    draw_wagon_side(0.0, 1.0, -(WagonWidth/2.0 - WagonSideWidth/2.0), WagonLength, 1.0, WagonSideWidth);
    draw_wagon_side(0.0, 1.0,  (WagonWidth/2.0 - WagonSideWidth/2.0), WagonLength, 1.0, WagonSideWidth);
    draw_wagon_side( (WagonLength/2.0 - WagonSideWidth/2.0), 1.0, 0.0, WagonSideWidth, 1.0, WagonWidth);
    draw_wagon_side(-(WagonLength/2.0 - WagonSideWidth/2.0), 1.0, 0.0, WagonSideWidth, 1.0, WagonWidth);
        
    draw_wagon_logo(-WagonLength/2.0*0.75, 0.0,  WagonWidth/2.0, false);
    draw_wagon_logo( WagonLength/2.0*0.75, 0.0, -WagonWidth/2.0, true);
}

void
draw_wagon_wheel(double tx, double ty, double tz)
{
    glPushMatrix(); {
        glTranslatef(tx, ty, tz);
        glColor3f(1.0, 1.0, 1.0);
        gluDisk(Quadric, 0.0, 1.0, 24, 24);
        glColor3f(0.0, 0.0, 1.0);
        glutSolidTorus(0.5, 0.9, 24, 24);
    } glPopMatrix();
}

void
draw_wagon_spoke(double tx, double ty, double tz, double length)
{
    glPushMatrix(); {
        glTranslatef(tx, ty, tz);
        glColor3f(0.0, 1.0, 0.0);
        gluCylinder(Quadric, 0.5, 0.5, length, 24, 24);

        glTranslatef(0.0, 0.30, -tz);
        glRotatef(-90.0, 1.0, 0.0, 0.0);
        glutSolidCone(0.25, 1.0, 24, 24);
    } glPopMatrix();
}

void
draw_wagon_wheels()
{
    draw_wagon_wheel(-WagonLength/2.0*0.75, -1.75, -WagonWidth/2.0 - 0.5);
    draw_wagon_wheel(-WagonLength/2.0*0.75, -1.75,  WagonWidth/2.0 + 0.5);
    draw_wagon_wheel( WagonLength/2.0*0.75, -1.75,  WagonWidth/2.0 + 0.5);
    draw_wagon_wheel( WagonLength/2.0*0.75, -1.75, -WagonWidth/2.0 - 0.5);

    draw_wagon_spoke(-WagonLength/2.0*0.75, -1.75, -WagonWidth/2.0 - 0.5, WagonWidth + 1.0);
    draw_wagon_spoke( WagonLength/2.0*0.75, -1.75, -WagonWidth/2.0 - 0.5, WagonWidth + 1.0);
}

void
draw_wagon()
{
    draw_wagon_body();
    draw_wagon_wheels();
}

// Draw jenny cub --------------------------------------------------------------

void
draw_jenny_cub_body()
{
    glPushMatrix(); {
        glColor3f(0.75, 0.5, 0.0);
        glRotatef(-90.0, 1.0, 0.0, 0.0);
        glutSolidCone(1.5, 2.0, 24, 24);
    } glPopMatrix();
}

void
draw_jenny_cub_ears()
{
    glPushMatrix(); {
        glRotatef(90.0, 0.0, 1.0, 0.0);
        glPushMatrix(); {
            glTranslatef( cos(M_PI/5), sin(M_PI/5), 0.0); 
            glutSolidTorus(0.15, 0.25, 24, 24);
            gluDisk(Quadric, 0.0, 0.25, 24, 24);
        } glPopMatrix();
        glPushMatrix(); {
            glTranslatef(-cos(M_PI/5), sin(M_PI/5), 0.0); 
            glutSolidTorus(0.15, 0.25, 24, 24);
            gluDisk(Quadric, 0.0, 0.25, 24, 24);
        } glPopMatrix();
    } glPopMatrix();
}

void
draw_jenny_cub_eyes()
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glColor3f(0.0, 0.0, 0.0);
    glPushMatrix(); {
        glTranslatef(0.9*cos(M_PI*0.1), 0.3,  0.9*sin(M_PI*0.1)); 
        glutSolidSphere(0.1, 24, 24);
    } glPopMatrix();
    glPushMatrix(); {
        glTranslatef(0.9*cos(M_PI*0.1), 0.3, -0.9*sin(M_PI*0.1)); 
        glutSolidSphere(0.1, 24, 24);
    } glPopMatrix();
    glPopAttrib();
}

void
draw_jenny_cub_nose()
{
    glPushAttrib(GL_ALL_ATTRIB_BITS);
    glColor3f(0.0, 0.0, 0.0);
    glPushMatrix(); {
        glTranslatef(0.5*0.9*cos(M_PI*0.1), 0.5*0.9*sin(M_PI*0.1), 0.0);
        glutSolidSphere(0.15, 24, 24);
    } glPopMatrix();
    glPopAttrib();
}

void
draw_jenny_cub_head()
{
    glPushMatrix(); {
        glColor3f(0.75, 0.5, 0.0);
        glTranslatef(0.0, 2.5, 0.0);
        glutSolidSphere(1.0, 24, 24);

        draw_jenny_cub_ears();
        draw_jenny_cub_eyes();

        glTranslatef(0.75, -0.25, 0.0);
        glutSolidSphere(0.5, 24, 24);
        
        draw_jenny_cub_nose();
    } glPopMatrix();
}

void
draw_jenny_cub()
{
    draw_jenny_cub_body();
    draw_jenny_cub_head();
}

// Draw grid -------------------------------------------------------------------

void
draw_grid()
{
    glPushMatrix(); glPushAttrib(GL_LIGHTING_BIT); {
        glDisable(GL_LIGHTING);
        glScalef(5.0, 5.0, 5.0);
        for(int k = 0; k < 2; k++) {
            for(int i = -50; i < 51; i++) {
                glBegin(GL_LINE_STRIP); {
                    for(int j = -50; j < 51; j++) {
                        glColor3fv(color_rotate());
                        glVertex3f(k == 0 ? i : j, 0, k == 0 ? j : i);
                    }
                } glEnd();
            }
        }

    } glPopAttrib(); glPopMatrix();
}

// Make display lists ----------------------------------------------------------

void
make_display_lists()
{
    glNewList(DL_WAGON, GL_COMPILE); {
        draw_wagon();
    }; glEndList();
    
    glNewList(DL_JENNY_CUB, GL_COMPILE); {
        draw_jenny_cub();
    }; glEndList();
    
    glNewList(DL_GRID, GL_COMPILE); {
        draw_grid();
    }; glEndList();
    
    glNewList(DL_BOX, GL_COMPILE); {
        glBegin(GL_POLYGON); {
            glVertex2f(-0.5, -0.5);
            glVertex2f(-0.5,  0.5);
            glVertex2f( 0.5,  0.5);
            glVertex2f( 0.5, -0.5);
        } glEnd();
    }; glEndList();
}

// Draw scene ------------------------------------------------------------------

void
draw_scene()
{
    glViewport(0, 0, WindowWidth, WindowHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (GLdouble)WindowWidth/(GLdouble)WindowHeight, 1.0, 750.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(EyeX, EyeY, EyeZ, DirX, DirY, DirZ, 0.0, 1.0, 0.0);
    
    glPolygonMode(GL_FRONT_AND_BACK, WireFrameMode ? GL_LINE : GL_FILL);
    glEnable(GL_LIGHTING);
    glShadeModel(GL_SMOOTH);
    glPushMatrix(); {
        glTranslatef(WagonX, 3.0, WagonZ);
        glRotatef(WagonOrientation, 0.0, 1.0, 0.0);
        glCallList(DL_WAGON);

        glTranslatef(-WagonLength/4.0, 0.0, 0.0);
        glRotatef(JennyCubOrientation, 0.0, 1.0, 0.0);
        glCallList(DL_JENNY_CUB);
    } glPopMatrix();

    glCallList(DL_GRID);
}

// Draw minimap ----------------------------------------------------------------

void
draw_minimap()
{
    glViewport(0, 0, WindowWidth/3, WindowHeight/3);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-250.0, 250.0, -250.0, 250.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_LIGHTING);
    glShadeModel(GL_FLAT);
    
    glPushMatrix(); {
        glTranslatef(0.0, 0.0, -0.1);
        glRotatef(90.0, 1.0, 0.0, 0.0);
        glCallList(DL_GRID);
    } glPopMatrix();

    glPushMatrix(); {
        glTranslatef(WagonX, -WagonZ, 0.0);
        glColor3f(1.0, 0.0, 0.0);
        glScalef(25.0, 25.0, 1.0);
        glCallList(DL_BOX);
    } glPopMatrix();
}

// Draw text -------------------------------------------------------------------

void
draw_text()
{
    char buffer[1024];

    glViewport(0, 0, WindowWidth, WindowHeight);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, WindowWidth, 0.0, WindowHeight);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_LIGHTING);
    glShadeModel(GL_SMOOTH);

    glTranslatef(WindowWidth*0.4, 10.0, 0.0);
    glScalef(0.1, 0.1, 1.0);
    glColor3f(1.0, 1.0, 0.0);

    snprintf(buffer, 1024, "x: %0.2lf, z: %0.2lf, ort: %0.2lf, speed: %0.2lf", WagonX, WagonZ, WagonOrientation, WagonSpeed);
    for (char *c = buffer; *c; c++)
        glutStrokeCharacter(GLUT_STROKE_ROMAN, *c);
}

// Display callback ------------------------------------------------------------

void
display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    draw_scene();
    draw_minimap();
    draw_text();

    glutSwapBuffers();
}

// Reshape callback ------------------------------------------------------------

void
reshape(GLsizei nw, GLsizei nh)
{
    WindowWidth  = nw;
    WindowHeight = nh;
    
    init_scene();
}

// Keyboard callback -----------------------------------------------------------

void
keyboard(unsigned char key, int x, int y)
{
    switch (key) {
        case 'q':
        case 'Q':
            exit(EXIT_SUCCESS);
            break;
        case 'w':
            WireFrameMode = !WireFrameMode;
            break;
        case ' ':
            WagonSpeed = 0.0;
            break;
        case '0':
            CameraMode = CAMERA_ORBIT;
            WagonSpeed = 0;
            WagonOrientation = 0.0;
            WagonX = 0.0;
            WagonZ = 0.0;
            JennyCubOrientation = 0.0;
            break;
        case '1':
            CameraMode = CAMERA_FIRST_PERSON;
            break;
        case '2':
            CameraMode = CAMERA_BIRDS_EYE;
            break;
        case '3':
            CameraMode = CAMERA_THIRD_PERSON;
            break;
        case '4':
            CameraMode = CAMERA_ORBIT;
            break;
    }
    
    update_camera_location();
}

// Special callback -----------------------------------------------------------

void
special(int key, int x, int y)
{
    switch (key) {
        case GLUT_KEY_LEFT:  WagonOrientation += 10.0; break;
        case GLUT_KEY_RIGHT: WagonOrientation -= 10.0; break;
        case GLUT_KEY_UP:    WagonSpeed += 0.1; break;
        case GLUT_KEY_DOWN:  WagonSpeed -= 0.1; break;
    }
    
    update_camera_location();
}

// Mouse callback --------------------------------------------------------------

void
mouse(int button, int state, int x, int y)
{
    MouseX = x;
    MouseY = y;

    switch (button) {
        case WHEEL_UP:
            CameraDistance = (CameraDistance > CAMERA_DISTANCE_MIN ? CameraDistance - 1.0 : CAMERA_DISTANCE_MIN);
            break;
        case WHEEL_DOWN:
            CameraDistance = (CameraDistance < CAMERA_DISTANCE_MAX ? CameraDistance + 1.0 : CAMERA_DISTANCE_MAX);
            break;
    }
    
    update_camera_location();
}

// Motion callback -------------------------------------------------------------

void
motion(int x, int y)
{
    CameraLatitude  += 180.0*(double)(x - MouseX)/WindowWidth;
    CameraLongitude += 180.0*(double)(y - MouseY)/WindowHeight;

    if (CameraLongitude < -90.0)
        CameraLongitude = -90.0;
    if (CameraLongitude > 90.0)
        CameraLongitude = 90.0;
    
    MouseX = x;
    MouseY = y;
    
    update_camera_location();
}

// Timer callback --------------------------------------------------------------

void
timer(int value)
{
    WagonX += WagonSpeed * cos(WagonOrientation*M_PI/180.0);
    WagonZ -= WagonSpeed * sin(WagonOrientation*M_PI/180.0);

    if (fabs(WagonSpeed) > 0.01) {
        if (JennyCubOrientation > 90.0)
            JennyCubOrientationDelta = -fabs(WagonSpeed)*10.0;

        if (JennyCubOrientation < -90.0)
            JennyCubOrientationDelta =  fabs(WagonSpeed)*10.0;

        JennyCubOrientation += JennyCubOrientationDelta;
    }

    update_camera_location();

    glutTimerFunc(100, timer, 0);
}


// Main execution --------------------------------------------------------------

int
main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(WindowWidth, WindowHeight);
    glutCreateWindow("wagon");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(special);
    glutMouseFunc(mouse);
    glutTimerFunc(100, timer, 0);
    glutMotionFunc(motion);

    init_scene();
    make_display_lists();

    glutMainLoop();

    return (EXIT_SUCCESS);
}

// vim: sts=4 sw=4 ts=8 ft=cpp

Files