Example 16

Example 16

This OpenGL program allows the user to build a series of 2D bezier curves. To place the control points right click anywhere on the screen. To modify the location of the control points left click and drag using the mouse.

ex_16/bezier2d.png

Source Code

main.cpp:

// Alexandri Zavodny
// CSE 40166: Computer Graphics, Fall 2010
// Example: bezier curve interpolation


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

#include "point.h"

using namespace std;

#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

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

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.

vector<Point> controlPoints;
int bezierResolution = 20;
int selectedControlPoint = -1;
const size_t SELECTBUF_SIZE = 512;
GLuint selectBuf[SELECTBUF_SIZE];

float viewportWidth, viewportHeight;

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

    viewportWidth = 20*aspectRatio;
    viewportHeight = 20;

    //update the projection matrix with the new window properties
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-viewportWidth/2.0, viewportWidth/2.0, -viewportHeight/2.0, viewportHeight/2.0);
}

// 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, k==0?j:i, 0);
            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;

    //add a new control point if the user has clicked with the right mouse button
    if(button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    {
        float xp = thisX / (float)windowWidth;
        float yp = thisY / (float)windowHeight;
        controlPoints.push_back(Point(xp*viewportWidth - viewportWidth/2.0, viewportHeight/2.0 - yp*viewportHeight, 0));
    }

    
    //if the user has clicked with the left moust button, see if there's a 
    //control point within range and set it as selected if there is!
    if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        int viewport[4];
        
        //tell OpenGL about our hit buffer and set the rendering mode appropriately
        glSelectBuffer(SELECTBUF_SIZE, selectBuf);
        glRenderMode(GL_SELECT);

        //create a new projection matrix, same as before, but preface it with a call to glutPickMatrix.
        //what this really does is only render a small (5x5 pixel) area around the cursor -- and whatever
        //gets rendered gets added to the hit buffer. 
        glMatrixMode(GL_PROJECTION);
        glPushMatrix();
        glLoadIdentity();
        glGetIntegerv(GL_VIEWPORT,viewport);
        gluPickMatrix(thisX, viewport[3]-thisY, 5, 5, viewport);
        gluOrtho2D(-viewportWidth/2.0, viewportWidth/2.0, -viewportHeight/2.0, viewportHeight/2.0);

        
        glMatrixMode(GL_MODELVIEW);
        glInitNames();

        //ok so render the things now
        //protip: this should really be in a separate function which also gets called during the normal
        //rendering pass, but with a different flag (say, not to push/pop names). that way we know that
        //we're actually intersecting with what gets rendered. here it's simple enough that we just
        //duplicate the drawing code.
        for(size_t i = 0; i < controlPoints.size(); i++)
        {
            glPushName(i);
            glPushMatrix();
                glTranslatef(controlPoints.at(i).x, controlPoints.at(i).y, controlPoints.at(i).z);
                glutSolidSphere(0.5, 32, 32);
            glPopMatrix();
            glPopName();
        }

        //ensure that the scene gets rendered
        glFlush();

        //reset the rendering mode -- and since we're returning from GL_SELECT, this function
        //returns the number of hits we had. see the man page for glRenderMode for more info.
        int hits = glRenderMode(GL_RENDER);
        if(hits >= 1)
        {
            //just grab the closest hit -- assuming it has a name (and it should!)
            if(selectBuf[0] == 0)
            {
                printf("Warning -- hit doesn't have a name!\n");
                selectedControlPoint = -1;
            } else {
                //see http://www.lighthouse3d.com/opengl/picking/index.php?openglway3 for more 
                //information about the structure of the selection buffer. since we know we have
                //at least one hit, and it has a name, selectBuf[1] will contain the minimum depth
                //for the first hit, selectBuf[2] will contain the max depth for the first hit,
                //and selectBuf[3] will contain the actual name that we want. note that there may
                //be more, but for simplicity we're just grabbing the first one.
                selectedControlPoint = selectBuf[3];
           }
        } else {
            //if the user clicked blank space, deselect!
            selectedControlPoint = -1;
        }

        //reset the projection matrix
        glMatrixMode(GL_PROJECTION);
        glPopMatrix();
    } else if(button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
        //if the user's lifted up the left mouse button, don't allow them to keep
        //dragging a control point!
        selectedControlPoint = -1;
    }


    //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 && selectedControlPoint >= 0)
    {
        Point *p = &controlPoints.at(selectedControlPoint);
        float xp = x / (float)windowWidth;
        float yp = y / (float)windowHeight;

        p->x = xp*viewportWidth - viewportWidth/2.0f;
        p->y = viewportHeight/2.0f - yp*viewportHeight;
    }

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


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

    //the modelview matrix is empty because our 2D orthographic
    //projection matrix says it all.
    glMatrixMode(GL_MODELVIEW);         //make sure we aren't changing the projection matrix!
    glLoadIdentity();

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

    //draw a grid in the background, because it's, uh, pretty?
    glColor3f(0.75f, 0.75f, 0.75f);
    glLineWidth(1.0f);
    drawGrid();

    //make sure that we can set the current color just using glColor3f
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

    //draw a sphere for each of the control points!
    glEnable(GL_LIGHTING);
    for(size_t i = 0; i < controlPoints.size(); i++)
    {
        glPushMatrix();

        glTranslatef(controlPoints.at(i).x, controlPoints.at(i).y, controlPoints.at(i).z);
        if(selectedControlPoint == (int)i)
            glColor3f(1.0, 0.3, 0.3);
        else 
            glColor3f(0.0f,0.2f,1.0f);
        glutSolidSphere(0.5, 32, 32);

        glPopMatrix();
    }

    //draw the cage for the bezier curve -- just a set of lines
    //between consecutive control points
    glDisable(GL_LIGHTING);
    glColor3f(1,1,0.3);
    glLineWidth(2.0f);
    glBegin(GL_LINE_STRIP);
    for(size_t i = 0; i < controlPoints.size(); i++)
        glVertex3f(controlPoints.at(i).x, controlPoints.at(i).y, controlPoints.at(i).z);
    glEnd();


    //draw the bezier curve!
    glColor3f(1,1,1);
    glLineWidth(2.0f);

    //special case 3 points; 1 point has no lines 
    //and 2 points is just the straight line of the cage...
    if(controlPoints.size() == 3)
    {
        //just use the quadratic form of Bezier interpolation
        Point p0 = controlPoints.at(0);
        Point p1 = controlPoints.at(1);
        Point p2 = controlPoints.at(2);

        glBegin(GL_LINE_STRIP);
        for(int i = 0; i < bezierResolution; i++)
        {
            float t = i / (float)(bezierResolution-1);
            Point location = powf(1.0f - t, 2) * p0 +
                                2*(1.0f - t)*t * p1 +
                                powf(t, 2)*p2;
            glVertex3f(location.x, location.y, location.z);
        }

        glEnd();
    } else if(controlPoints.size() > 3) {
        //create the line!
        //create a separate Bezier curve for each set of 4 points
        for(size_t startingIndex = 0; startingIndex < controlPoints.size()-3; startingIndex+=3)
        {
            glBegin(GL_LINE_STRIP);
            for(int i = 0; i < bezierResolution; i++)
            {
                float t = i / (float)(bezierResolution-1);
                Point p0 = controlPoints.at(startingIndex+0);
                Point p1 = controlPoints.at(startingIndex+1);
                Point p2 = controlPoints.at(startingIndex+2);
                Point p3 = controlPoints.at(startingIndex+3);

                //plug and chug using the cubic form of bezier interpolation!
                Point location = powf((1.0-t), 3.0f)*p0 +
                                3*(1.0f-t)*(1.0f-t)*t*p1 +
                                3*(1.0f-t)*t*t*p2 +
                                t*t*t*p3;

                glVertex3f(location.x, location.y, location.z);
            }
            glEnd();
        }
    }


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

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

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