// Alexandri Zavodny // CSE 40166: Computer Graphics, Fall 2010 // Program 3: Terrain rendering #include #include #include #include #include #include #include "point.h" using namespace std; // GLOBAL VARIABLES //////////////////////////////////////////////////////////// static size_t windowWidth = 640; static size_t windowHeight = 480; static float aspectRatio; GLint leftMouseButton, rightMouseButton; //status of the mouse buttons int mouseX = 0, mouseY = 0; //last known X and Y of the mouse float cameraX, cameraY, cameraZ; //camera position in cartesian coordinates float cameraTheta, cameraPhi; //camera DIRECTION in spherical coordinates float dirX, dirY, dirZ; //camera DIRECTION in cartesian coordinates GLuint terrainDisplayList; //display list for the terrain vector terrainPoints; //terrain data, which contains... int terrainLength, terrainWidth; //...terrainWidth*terrainLength points float currentScale = 1.0f; //terrain exaggeration bool enableAnimation = false; //update scale if animation is on! float animationMultiplier = 1.0f; bool keyMap[256] = {false}; //boolean vector which determines //whether a key has been //held 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. enum renderingModes { RENDER_POINTS = 0, RENDER_LINES, RENDER_TRIS, RENDER_QUADS }; enum renderingModes currentRenderingMode = RENDER_POINTS; //these values are used for lookup by the jetColormap() function float jetR[] = {0, 0, 0, 0, 0, 0, 0, 0, 8, 66, 74, 132,189,255,255,255,255,255,255,189,132 }; float jetG[] = {0, 0, 66, 74, 132,189,214,255,255,255,255,255,255,255,189,132,66, 49, 0, 0, 0 }; float jetB[] = {189,255,255,255,255,255,255,255,239,189,181,132,66, 0, 0, 0, 0, 0, 0, 0, 0 }; // jetColormap() /////////////////////////////////////////////////////////////// // // Given a value between 0 and 1, returns a Point with R,G,B that visualizes // how 'hot' or 'cold' it is. Higher values are more red, and lower // values are more blue. // //////////////////////////////////////////////////////////////////////////////// Point jetColormap(float value) { if(value > 1.0f) value = 1.0f; if(value < 0.0f) value = 0.0f; //linear interpolation: X at L, Y at R, interpolation component t ranges from L to R // (X * (1-t) + Y * (t)) without left and right bounds; t ranges from 0 to 1 // newT = (t-L) / (R-L); // (X * (1-newT) + Y * (newT) value *= 20; //scales value to match the size of the jet arrays int iv = (int)value; float t = value-iv; if(t > 0.05f) { return Point( ((1 - t) * jetR[iv] + t*jetR[iv+1]) / 255.0f, ((1 - t) * jetG[iv] + t*jetG[iv+1]) / 255.0f, ((1 - t) * jetB[iv] + t*jetB[iv+1]) / 255.0f ); } else { return Point(jetR[iv]/255.0f, jetG[iv]/255.0f, jetB[iv]/255.0f); } } // generateTerrainDL() ///////////////////////////////////////////////////////// // // This function creates a display list with the code to draw the terrain // points read in from file. // // And yes, it uses a global variable for the display list. // //////////////////////////////////////////////////////////////////////////////// void generateTerrainDL(enum renderingModes renderMode) { terrainDisplayList = glGenLists(1); glNewList(terrainDisplayList, GL_COMPILE); vector colors; float minY, maxY; for(int i = 0; i < terrainPoints.size(); i++) { if(i == 0 || minY > terrainPoints.at(i).y) minY = terrainPoints.at(i).y; if(i == 0 || maxY < terrainPoints.at(i).y) maxY = terrainPoints.at(i).y; } for(int i = 0; i < terrainPoints.size(); i++) colors.push_back(jetColormap( (terrainPoints.at(i).y - minY) / (maxY-minY))); switch(renderMode) { //////// POINT RENDERING /////////////////////////////////////////////// default: case RENDER_POINTS: { //POINT RENDERING glColor3f(1,1,1); glPointSize(1.0f); glBegin(GL_POINTS); for(int i = 0; i < terrainPoints.size(); i++) { Point col = colors.size() > 0 ? colors.at(i) : Point(1,1,1); glColor3f(col.x, col.y, col.z); glVertex3f(terrainPoints.at(i).x, terrainPoints.at(i).y, terrainPoints.at(i).z); } glEnd(); break; } //////// LINE RENDERING //////////////////////////////////////////////// case RENDER_LINES: { glLineWidth(1.0f); //draw lines along the width for(int i = 0; i < terrainLength; i++) { glBegin(GL_LINE_STRIP); for(int j = 0; j < terrainWidth; j++) { int idx = i*terrainWidth+j; glColor3f(colors.at(idx).x, colors.at(idx).y, colors.at(idx).z); glVertex3f(terrainPoints.at(idx).x, terrainPoints.at(idx).y, terrainPoints.at(idx).z); } glEnd(); } //and then along the length for(int j = 0; j < terrainWidth; j++) { glBegin(GL_LINE_STRIP); for(int i = 0; i < terrainLength; i++) { int idx = i*terrainWidth+j; glColor3f(colors.at(idx).x, colors.at(idx).y, colors.at(idx).z); glVertex3f(terrainPoints.at(idx).x, terrainPoints.at(idx).y, terrainPoints.at(idx).z); } glEnd(); } break; } //////// TRIANGLE STRIP RENDERING ////////////////////////////////////// case RENDER_TRIS: { for(int i = 0; i < terrainLength-1; i++) { glBegin(GL_TRIANGLE_STRIP); for(int j = 0; j < terrainWidth; j++) { int idx = i*terrainWidth + j; int idxNext = (i+1)*terrainWidth + j; glColor3f(colors.at(idx).x, colors.at(idx).y, colors.at(idx).z); glVertex3f(terrainPoints.at(idx).x, terrainPoints.at(idx).y, terrainPoints.at(idx).z); glColor3f(colors.at(idxNext).x, colors.at(idxNext).y, colors.at(idxNext).z); glVertex3f(terrainPoints.at(idxNext).x, terrainPoints.at(idxNext).y, terrainPoints.at(idxNext).z); } glEnd(); } break; } //////// QUAD STRIP RENDERING ////////////////////////////////////////// case RENDER_QUADS: { for(int i = 0; i < terrainLength-1; i++) { glBegin(GL_QUAD_STRIP); for(int j = 0; j < terrainWidth; j++) { int idx = i*terrainWidth + j; int idxNext = (i+1)*terrainWidth + j; glColor3f(colors.at(idx).x, colors.at(idx).y, colors.at(idx).z); glVertex3f(terrainPoints.at(idx).x, terrainPoints.at(idx).y, terrainPoints.at(idx).z); glColor3f(colors.at(idxNext).x, colors.at(idxNext).y, colors.at(idxNext).z); glVertex3f(terrainPoints.at(idxNext).x, terrainPoints.at(idxNext).y, terrainPoints.at(idxNext).z); } glEnd(); } break; } } glEndList(); } // 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 //update position if the user presses 'w' or 's' float movementConstant = 90 * secondsElapsed; if(keyMap['w'] || keyMap['W']) { cameraX += dirX*movementConstant*(keyMap['W']?3:1); cameraY += dirY*movementConstant*(keyMap['W']?3:1); cameraZ += dirZ*movementConstant*(keyMap['W']?3:1); } if(keyMap['s'] || keyMap['S']) { cameraX -= dirX*movementConstant*(keyMap['S']?3:1); cameraY -= dirY*movementConstant*(keyMap['S']?3:1); cameraZ -= dirZ*movementConstant*(keyMap['S']?3:1); } //change the scale if the user so desires if(keyMap['r'] || keyMap['R']) { currentScale += (keyMap['r']?1:-1) * 20 * secondsElapsed; if(currentScale <= 0) currentScale = 0; } //if animation is enabled, update the current scale based on time if(enableAnimation) { animationMultiplier = sinf(glutGet(GLUT_ELAPSED_TIME) / 1000.0f); } } // recomputeOrientation() ////////////////////////////////////////////////////// // // This function updates the camera's position in cartesian coordinates based // on its position in spherical coordinates. Should be called every time // cameraTheta, cameraPhi, or cameraRadius is updated. // //////////////////////////////////////////////////////////////////////////////// void recomputeOrientation() { dirX = sinf(cameraTheta)*sinf(cameraPhi); dirZ = -cosf(cameraTheta)*sinf(cameraPhi); dirY = -cosf(cameraPhi); //and normalize this directional vector! float mag = sqrt( dirX*dirX + dirY*dirY + dirZ*dirZ ); dirX /= mag; dirY /= mag; dirZ /= mag; } // 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); } // 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; //and update the last seen X and Y coordinates of the mouse mouseX = thisX; mouseY = thisY; } // mouseMotion() /////////////////////////////////////////////////////////////// // // GLUT callback for mouse movement. We update cameraPhi, cameraTheta, and/or // cameraRadius 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) { cameraTheta += (x - mouseX)*0.005; cameraPhi += (mouseY - y)*0.005; // make sure that phi stays within the range (0, M_PI) if(cameraPhi <= 0) cameraPhi = 0+0.001; if(cameraPhi >= M_PI) cameraPhi = M_PI-0.001; 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); glDisable(GL_LIGHTING); //glShadeModel(GL_SMOOTH); glShadeModel(GL_FLAT); generateTerrainDL(currentRenderingMode); } // renderScene() /////////////////////////////////////////////////////////////// // // GLUT callback for scene rendering. Sets up the modelview matrix, renders // a scene 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(cameraX, cameraY, cameraZ, //camera is located at (x,y,z) cameraX+dirX, cameraY+dirY, cameraZ+dirZ, //camera is looking at at (x,y,z) + (dx,dy,dz) -- straight ahead 0.0f,1.0f,0.0f); //up vector is (0,1,0) (positive Y) //clear the render buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glScalef(1,currentScale*animationMultiplier,1); glCallList(terrainDisplayList); //push the back buffer to the screen glutSwapBuffers(); } // normalKeysUp() ////////////////////////////////////////////////////////////// // // GLUT keyboard callback; gets called when the user releases a key. // //////////////////////////////////////////////////////////////////////////////// void normalKeysUp(unsigned char key, int x, int y) { if(isascii(key)) //if the user was holding down shift when they pressed, but not when the let go... { keyMap[tolower(key)] = false; keyMap[toupper(key)] = false; } else { keyMap[key] = false; } } // normalKeysDown() //////////////////////////////////////////////////////////// // // GLUT keyboard callback; gets called when the user presses a key. // //////////////////////////////////////////////////////////////////////////////// void normalKeysDown(unsigned char key, int x, int y) { if(key == 'q' || key == 'Q') exit(0); keyMap[key] = true; } // readTerrainData() /////////////////////////////////////////////////////////// // // Opens the specified file and reads in terrain data. // //////////////////////////////////////////////////////////////////////////////// void readTerrainData(const char *filename) { FILE *fp = fopen(filename, "r"); if(!fp) { fprintf(stderr, "Error; could not open input file (%s).\n", filename); exit(1); } //first two pieces of information are the dimensions of the terrain fscanf(fp, "%d", &terrainLength); fscanf(fp, "%d", &terrainWidth); //the next terrainLength * terrainWidth pieces of information are the heights for(int row = 0; row < terrainLength; row++) { for(int col = 0; col < terrainWidth; col++) { Point p; p.x = row; fscanf(fp, "%lf", &p.y); p.z = col; terrainPoints.push_back(p); } } fclose(fp); } void mainMenuCallback(int id) { if(id == 0) { enableAnimation = !enableAnimation; if(enableAnimation) glutChangeToMenuEntry(1, "Disable terrain animation", 0); else glutChangeToMenuEntry(1, "Enable terrain animation", 0); } } void renderingMenuCallback(int id) { //just delete the current display list, cause any of these values //will redraw it. also, reset the menu item for the current rendering mode //to not have a marker next to it! glDeleteLists(terrainDisplayList, 1); //change the old menu entry... if(currentRenderingMode == RENDER_POINTS) { glutChangeToMenuEntry(RENDER_POINTS+1, "Render with points", RENDER_POINTS); } else if(currentRenderingMode == RENDER_LINES) { glutChangeToMenuEntry(RENDER_LINES+1, "Render with lines", RENDER_LINES); } else if(currentRenderingMode == RENDER_TRIS) { glutChangeToMenuEntry(RENDER_TRIS+1, "Render with triangles", RENDER_TRIS); } else if(currentRenderingMode == RENDER_QUADS) { glutChangeToMenuEntry(RENDER_QUADS+1, "Render with quads", RENDER_QUADS); } //update the new menu entry... if(id == RENDER_POINTS) { glutChangeToMenuEntry(RENDER_POINTS+1, "Render with points <--", RENDER_POINTS); currentRenderingMode = RENDER_POINTS; } else if(id == RENDER_LINES) { glutChangeToMenuEntry(RENDER_LINES+1, "Render with lines <--", RENDER_LINES); currentRenderingMode = RENDER_LINES; } else if(id == RENDER_TRIS) { glutChangeToMenuEntry(RENDER_TRIS+1, "Render with triangles <--", RENDER_TRIS); currentRenderingMode = RENDER_TRIS; } else if(id == RENDER_QUADS) { glutChangeToMenuEntry(RENDER_QUADS+1, "Render with quads <--", RENDER_QUADS); currentRenderingMode = RENDER_QUADS; } generateTerrainDL(currentRenderingMode); } // main() ////////////////////////////////////////////////////////////////////// // // Program entry point. Does not process command line arguments. // //////////////////////////////////////////////////////////////////////////////// int main(int argc, char **argv) { if(argc != 2) { printf("Please specify a DEM file (e.g., ./tview blah.dem).\n"); exit(1); } readTerrainData(argv[1]); //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("i can see my house from here"); //give the camera a 'pretty' starting point! cameraX = 520; cameraY = 350; cameraZ = 540; cameraTheta = -0.70; cameraPhi = 0.82; recomputeOrientation(); int submenuID= glutCreateMenu(renderingMenuCallback); glutAddMenuEntry("Render with points <--", RENDER_POINTS); glutAddMenuEntry("Render with lines", RENDER_LINES); glutAddMenuEntry("Render with triangles", RENDER_TRIS); glutAddMenuEntry("Render with quads", RENDER_QUADS); int mainMenuID = glutCreateMenu(mainMenuCallback); glutAddMenuEntry("Enable exaggeration animation", 0); glutAddSubMenu("Rendering modes", submenuID); glutAttachMenu(GLUT_RIGHT_BUTTON); //register callback functions... glutSetKeyRepeat(GLUT_KEY_REPEAT_ON); glutKeyboardFunc(normalKeysDown); glutKeyboardUpFunc(normalKeysUp); 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); }