Example 17

Example 17

This OpenGL program allows the user to apply a series of transformations to a cube in 3D space. Transformations will be displayed on the left in a sidebar and will be applied to the cube on the right. You can rotate the camera about the scene using the normal arcball camera movements. To add, remove, and modify transformations, you may use the following keys:

ex_17/ctmVisualizer.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 <vector>
#include <string>
#include <sstream>

#include "point.h"

using namespace std;

#define WHEEL_UP 3
#define WHEEL_DOWN 4

#define TEXT(x,y,str) { glRasterPos2f(x,y); for(const char *c = str; *c != '\0'; c++) { glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *c); }}
#define TEXT_FONT(x,y,str, font) { glRasterPos2f(x,y); for(const char *c = str; *c != '\0'; c++) { glutBitmapCharacter(font, *c); }}

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

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

static float sidebarPercent = 0.3;


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.

int selectedCommand = -1;

enum gl_command_name
{
    GL_TRANSLATE = 0,
    GL_ROTATE,
    GL_SCALE,
};

typedef struct _glCommand
{
    _glCommand(enum gl_command_name cn, float a1, float a2, float a3, float a4)
    : commandName(cn), arg1(a1), arg2(a2), arg3(a3), arg4(a4)
    {}

    enum gl_command_name commandName;
    float arg1, arg2, arg3, arg4;
} glCommand;


vector<glCommand> commandStack;


void executeGLCommand(glCommand c)
{
    if(c.commandName == GL_TRANSLATE) {
        glTranslatef(c.arg1, c.arg2, c.arg3);
    } else if(c.commandName == GL_ROTATE) {
        glRotatef(c.arg1, c.arg2, c.arg3, c.arg4);
    } else if(c.commandName == GL_SCALE) {
        glScalef(c.arg1, c.arg2, c.arg3);
    }
}

const char *glCommandString(enum gl_command_name cn)
{
    switch(cn)
    {
        case GL_TRANSLATE:
            return "glTranslatef";
        case GL_ROTATE:
            return "glRotatef";
        case GL_SCALE:
            return "glScalef";
        default:
            return "undefined";
    }
}

string glCommandFullString(glCommand c)
{
    stringstream str(stringstream::in | stringstream::out);

    string def = glCommandString(c.commandName);
    
    if(c.commandName == GL_TRANSLATE || c.commandName == GL_SCALE)
    {
        str << def << "(" << c.arg1 << ", " << c.arg2 << ", " << c.arg3 << ")";
    } else if(c.commandName == GL_ROTATE) {
        str << def << "(" << c.arg1 << ", " << c.arg2 << ", " << c.arg3 << ", " << c.arg4 << ")";
    }

    return str.str();
}

// 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 * (1 - sidebarPercent);

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

// drawAxes() //////////////////////////////////////////////////////////////////
//
//  Draw the set of axes at the origin, and of unit scale.
//
////////////////////////////////////////////////////////////////////////////////
void drawAxes(bool makeGrey)
{
    glPushAttrib(GL_LIGHTING_BIT);
    glDisable(GL_LIGHTING);

    glBegin(GL_LINES);
        if(makeGrey)    glColor3f(0.5,0.25,0.25);
        else            glColor3f(1,0,0);
        glVertex3f(0,0,0); glVertex3f(1,0,0);
        if(makeGrey)    glColor3f(0.25,0.125,0.125);
        else            glColor3f(0.5,0,0);
        glVertex3f(0,0,0); glVertex3f(-1,0,0);

        if(makeGrey)    glColor3f(0.25,0.5,0.25);
        else            glColor3f(0,1,0);
        glVertex3f(0,0,0); glVertex3f(0,1,0);
        if(makeGrey)    glColor3f(0.125,0.25,0.125);
        else            glColor3f(0,0.5,0);
        glVertex3f(0,0,0); glVertex3f(0,-1,0);

        if(makeGrey)    glColor3f(0.25,0.25,0.5);
        else            glColor3f(0,0,1);
        glVertex3f(0,0,0); glVertex3f(0,0,1);
        if(makeGrey)    glColor3f(0.125,0.125,0.25);
        else            glColor3f(0,0,0.5);
        glVertex3f(0,0,0); glVertex3f(0,0,-1);
    glEnd();

    glPopAttrib();
}

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


// 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;
        if(cameraTPR.z < 1) cameraTPR.z = 1;
        recomputeOrientation();
    }

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

void passiveMouseMotion(int x, int y)
{
    if(x < windowWidth*sidebarPercent)
    {
        int elementUnderMouse = -1;
        float yp = (windowHeight-y) / (float)windowHeight;
        elementUnderMouse = (int)((yp*100.0 - 2.5f) / 5.0f);
        if(elementUnderMouse < (int)commandStack.size())
            selectedCommand = elementUnderMouse;
        else
            selectedCommand = -1;
    } else {
        selectedCommand = -1;
    }
}

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

// drawSidebar() ///////////////////////////////////////////////////////////////
//
//  Draw the sidebar overlay, which contains a list of the current transforms.
//
////////////////////////////////////////////////////////////////////////////////
void drawSidebar()
{
    glDisable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, 20, 0, 100);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    glViewport(0,0, windowWidth*sidebarPercent, windowHeight);

    glDisable(GL_LIGHTING);
    glColor3f(1,1,1);
    glBegin(GL_QUADS);
        glVertex2f(0,0);
        glVertex2f(0,100);
        glVertex2f(20,100);
        glVertex2f(20,0);
    glEnd();
    glColor3f(0.2,0.2,0.2);
    glBegin(GL_QUADS);
        glVertex2f(0,0);
        glVertex2f(0,100);
        glVertex2f(19.8,100);
        glVertex2f(19.8,0);
    glEnd();

    glColor3f(0.6,0.6,0.6);
    glBegin(GL_QUADS);
        glVertex2f(0,0);
        glVertex2f(0,100);
        glVertex2f(19.7,100);
        glVertex2f(19.7,0);
    glEnd();



    //draw the current stack contents.
    for(size_t i = 0; i < commandStack.size(); i++)
    {
        float startingY = i*5 + 5;
        //TEXT(2, startingY, glCommandString(commandStack.at(i).commandName));

        if((int)i == selectedCommand)    glColor3f(1,0,0);
        else                        glColor3f(0,0,0);
        TEXT(1.2, startingY, glCommandFullString(commandStack.at(i)).c_str());
    }


    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);
    glViewport(0, 0, windowWidth, windowHeight);
    glEnable(GL_DEPTH_TEST);
}

// 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);
    lastTime = currentTime;

    //update the modelview matrix based on the camera's position
    glMatrixMode(GL_PROJECTION);
    glViewport(windowWidth*sidebarPercent, 0, (1-sidebarPercent)*windowWidth, windowHeight);
    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);

    glColor3f(1,1,1);
    glLineWidth(1.0f);
    drawGrid();
    glClear(GL_DEPTH_BUFFER_BIT);

    glLineWidth(5.0f);
    drawAxes(false);

    //now, uh. go through and draw the axes after each transformation in the stack.
    glPushMatrix();
    for(int i = 0; i < (int)commandStack.size(); i++)
    {
        bool grey = false;
        if(selectedCommand != -1 && selectedCommand != i) grey = true;
        executeGLCommand(commandStack.at(i));
        drawAxes(grey);
    }

    glEnable(GL_NORMALIZE);
    glEnable(GL_LIGHTING);
    glColor3f(1,1,1);
    glutSolidCube(1.0f);
    glPopMatrix();


    drawSidebar();

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

    if(key == 'w' || key == 'W')
    {
        size_t s = commandStack.size();
        if(s >= 2)
        {
            glCommand c = commandStack.at(s-2);
            commandStack.at(s-2) = commandStack.at(s-1);
            commandStack.at(s-1) = c;
        }
    }

    if(key == 't' || key == 'T')
        commandStack.push_back(glCommand(GL_TRANSLATE, 0, 0, 0, 0));

    if(key == 's' || key == 'S')
        commandStack.push_back(glCommand(GL_SCALE, 1, 1, 1, 0));

    if(key == 'r' || key == 'R')
        commandStack.push_back(glCommand(GL_ROTATE, 0, 1, 0, 0));

    if(key == 'x' || key == 'X')
    {
        if(!commandStack.empty()) 
        { 
            commandStack.pop_back();
            selectedCommand = -1;
        }
    }

    if(selectedCommand != -1)
    {
        glCommand *c = &commandStack.at(selectedCommand);
        if(c->commandName == GL_ROTATE)
        {
            if(key == 'y' || key == 'Y')    c->arg1 += 1 * (key == 'Y' ? 10 : 1);
            if(key == 'h' || key == 'H')    c->arg1 -= 1 * (key == 'H' ? 10 : 1);

            if(key == 'u' || key == 'U' || key == 'j' || key == 'J')    { c->arg2 = 1; c->arg3 = c->arg4 = 0; }
            if(key == 'i' || key == 'I' || key == 'k' || key == 'K')    { c->arg3 = 1; c->arg2 = c->arg4 = 0; }
            if(key == 'o' || key == 'O' || key == 'l' || key == 'L')    { c->arg4 = 1; c->arg2 = c->arg3 = 0; }
        } else {
            if(key == 'y' || key == 'Y')    c->arg1 += 0.1 * (key == 'Y' ? 10 : 1);
            if(key == 'h' || key == 'H')    c->arg1 -= 0.1 * (key == 'H' ? 10 : 1);

            if(key == 'u' || key == 'U')    c->arg2 += 0.1 * (key == 'U' ? 10 : 1);
            if(key == 'j' || key == 'J')    c->arg2 -= 0.1 * (key == 'J' ? 10 : 1);

            if(key == 'i' || key == 'I')    c->arg3 += 0.1 * (key == 'I' ? 10 : 1);
            if(key == 'k' || key == 'K')    c->arg3 -= 0.1 * (key == 'K' ? 10 : 1);

            if(key == 'o' || key == 'O')    c->arg4 += 0.1 * (key == 'O' ? 10 : 1);
            if(key == 'l' || key == 'L')    c->arg4 -= 0.1 * (key == 'L' ? 10 : 1);
        }
    }
}


// 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);
    glutPassiveMotionFunc(passiveMouseMotion);

    //do some basic OpenGL setup
    initScene();

    //and enter the GLUT loop, never to exit.
    glutMainLoop();

    return(0);
}

Files