// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: simple vehicle with hierarchical matrix structure #include #include #include #include #include #include #include #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 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); }