// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Example: simple vehicle with hierarchical matrix structure #include #include #include #include #include "point.h" #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 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. // 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); //update the projection matrix with the new window properties glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0,aspectRatio,0.1,100000); } // 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(); } // drawCylinder() ////////////////////////////////////////////////////////////// // // Because we have to use a gluQuadric object to draw a cylinder, and that's // less than pretty -- AND, because we have to draw the caps on the ends // of the cylinder ourself, put all that code in its own function. // //////////////////////////////////////////////////////////////////////////////// void drawCylinder(float outerRadius, float innerRadius, float wheelDepth, int resolution) { gluQuadricOrientation(quad, GLU_OUTSIDE); //ensure that winding order is appropriate gluCylinder(quad, outerRadius, outerRadius, wheelDepth, resolution, 1); glPushMatrix(); glTranslatef(0,0,wheelDepth); gluDisk(quad, innerRadius, outerRadius, resolution, 1); glPopMatrix(); gluQuadricOrientation(quad, GLU_INSIDE); gluCylinder(quad, innerRadius, innerRadius, wheelDepth, resolution, 1); //gluCylinder sets the left side of the cylinder at the current Z //position, so no need to translate now gluDisk(quad, innerRadius, outerRadius, resolution, 1); gluQuadricOrientation(quad, GLU_OUTSIDE); //ensure that winding order is appropriate } // drawWheel() ///////////////////////////////////////////////////////////////// // // Draws a wheel at the current OpenGL location. We have this code in its own // function because we're going to be drawing 4 wheels for the car and // they're all going to be the same. // //////////////////////////////////////////////////////////////////////////////// void drawWheel() { //the cylinder is most of the wheel! no lie. drawCylinder(0.5f, 0.4f, 0.1f, 16); //draw some spokes glPushMatrix(); glTranslatef(0,0,0.05f); for(int i = 0; i < 8; i++) { glPushMatrix(); glRotatef(45*i, 0,0,-1); glTranslatef(0,0.25,0); drawSolidBox(0.1,0.4,0.1); glPopMatrix(); } glPopMatrix(); } // drawCar() /////////////////////////////////////////////////////////////////// // // Draws our car! ... at the specified position, and pointing in the direction // of carTheta. Also uses the global variable carAnimation to control // how much its parts should move. // //////////////////////////////////////////////////////////////////////////////// void drawCar(float x, float y, float z, float carTheta) { glPushMatrix(); glTranslatef(x,y,z); glRotatef(carTheta*180/M_PI, 0, -1 ,0); //draw the body of the car drawSolidBox(2,1,1); //and the, uh, cockpit? glPushMatrix(); glTranslatef(-0.5,0.75,0); drawSolidBox(1,0.5,1); //and, like, a helicopter blade. float heliSpeed = 15; //heheh it's moving "heli fast" glPushMatrix(); glTranslatef(0,0.5,0); glRotatef(carAnimationValue*heliSpeed, 0, 1, 0); drawSolidBox(0.1,1,0.1); //move on top of the rotor... glTranslatef(0,0.5,0); drawSolidBox(1.5,0.1,0.1); drawSolidBox(0.1,0.1,1.5); glPopMatrix(); glPopMatrix(); float wheelSpeed = 5.0f; //all four wheels... //wheel one... glPushMatrix(); glTranslatef(-0.75, 0, 0.5); glRotatef(carAnimationValue*wheelSpeed, 0, 0, -1); drawWheel(); glPopMatrix(); //wheel two... glPushMatrix(); glTranslatef(0.75, 0, 0.5); glRotatef(carAnimationValue*wheelSpeed, 0, 0, -1); drawWheel(); glPopMatrix(); //wheel three.... glPushMatrix(); glTranslatef(-0.75, 0, -0.6); glRotatef(carAnimationValue*wheelSpeed, 0, 0, -1); drawWheel(); glPopMatrix(); //wheel the fourth glPushMatrix(); glTranslatef(0.75, 0, -0.6); //this one is the gimp wheel so it rotates 3/4s the speed glRotatef(carAnimationValue*wheelSpeed*0.75, 0, 0, -1); drawWheel(); glPopMatrix(); //and because we're awesome we're gonna do some freaking sparkle.. things... off the back float tailSpeed = 2.0f; glPushMatrix(); glTranslatef(-1.0f, 0.25, 0); glPushMatrix(); glRotatef(15*cosf(carAnimationValue*tailSpeed/2.0),0,1,0); glTranslatef(-0.5f,0,0); //the first tail segment! drawSolidBox(1,0.125,0.125); glTranslatef(-0.5,0,0); glRotatef(15*cosf(carAnimationValue*tailSpeed/2.0),0,1,0); glTranslatef(-0.25,0,0); //the second tail segment! drawSolidBox(0.5,0.10,0.10); glTranslatef(-0.3,0,0); glRotatef(15*cosf(carAnimationValue*tailSpeed/2.0), 0, 1, 0); glTranslatef(-0.1,0,0); //the final tail segment! drawSolidBox(0.3, 0.1, 0.1); glPopMatrix(); glPopMatrix(); 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; recomputeOrientation(); } //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) { 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); } // doUpdateLogic() ///////////////////////////////////////////////////////////// // // This function, which should get called once per preframe, updates the position // of objects in the scene based on what keys the user has held down, if any. // //////////////////////////////////////////////////////////////////////////////// void doUpdateLogic(float secondsElapsed) { //check the key states float movementConstant = secondsElapsed*2; if(arrowKeyStates[2]) { float xMovement = cosf(carTheta)*movementConstant; float zMovement = sinf(carTheta)*movementConstant; carPosition.x += xMovement; carPosition.z += zMovement; } if(arrowKeyStates[3]) { float xMovement = cosf(carTheta)*movementConstant; float zMovement = sinf(carTheta)*movementConstant; carPosition.x -= xMovement; carPosition.z -= zMovement; } if(arrowKeyStates[0]) carTheta += movementConstant; if(arrowKeyStates[1]) carTheta -= movementConstant; //update the car's animation value! and speed it up a bit cause.. why not. carAnimationValue += (arrowKeyStates[2] ^ arrowKeyStates[3]) ? 0.005f : 0.0f; } // 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); float timeDifference = (currentTime - lastTime) / 1000.0f; lastTime = currentTime; doUpdateLogic(timeDifference); //update the modelview matrix based on the camera's position 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); glEnable(GL_NORMALIZE); drawCar(carPosition.x, carPosition.y, carPosition.z, carTheta); drawGrid(); //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("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); //do some basic OpenGL setup initScene(); //and enter the GLUT loop, never to exit. glutMainLoop(); return(0); }