// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: bezier curve interpolation #include #include #include #include #include #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 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); }