/* IS2780 Graphics Assignment 3 Summer 2008 * * Modifiy this template code at the four XXXX places to... * 1) Implement the 3D to 2D perspective_project() function (see line 134) * 2&3) complete the rotate_y and rotate_z functions (see lines 151 & 156) * 4) add a 3D wireframe object of your own design and add it to the draw list * ... a simple pyramid would be fine (see line 89) * * Item 1, perspective projection, is the most important lesson of the week!!! * Be sure to ask questions if something about it is not clear. * * You can test it most easily as discussed in class using the cube display. * The cube is 4*4*4 centered on XYZ 0,0,0 so it extends -2,-2,-2 to 2,2,2 * * These are the keyboard and mouse commands * R = reset state * r = change rotate mode = none, x, y, z, 3 axis * d = change object = cube, your obj, striped ball, sinc, your obj * x = activate rotation axis x * y = activate rotation axis y * z = activate rotation axis z * + = incriment rotation of current axis (its really '=') * - = decriment rotation of current axis * > = increase viewplane z (its really '.') * < = decrease viewplane z (its really ',') * i = move eye Z inward toward screen * o = move eye Z outward away from screen * p = pause * mouse left -> move xy eye * mouse right -> drag the figure in xy * Hit the ESC (escape) key to exit */ /* required headers */ #include #include #include #include #include /* Constants */ #define WINDOW_X 800 #define WINDOW_Y 800 #define PIX_PER_UNIT 100.0 // a convenient world2screen XY size scaling #define PI 3.14159265358979323846 #define MILLISEC 25 // target update rate /* Object line descriptions as RGBcolor, XYZstart, XYZend */ float cube[][3] = { // outline of a 4*4*4 cube centered at 0,0,0 /* {r,g,b} {x1,y1,z1}, {x2,y2,z2} */ {0,0,1}, {-2,-2, 2}, { 2,-2, 2}, // 'bottom horiz front' {0,0,1}, {-2, 2, 2}, { 2, 2, 2}, // 'top horiz front' {0,0,1}, {-2,-2, 2}, {-2, 2, 2}, // 'left vert front' {0,0,1}, { 2,-2, 2}, { 2, 2, 2}, // 'right vert front' {0.7,0,.7}, {-2,-2,-2}, { 2,-2,-2}, // 'bottom horiz back' {0.7,0,.7}, {-2, 2,-2}, { 2, 2,-2}, // 'top horiz back' {0.7,0,.7}, {-2,-2,-2}, {-2, 2,-2}, // 'left vert back' {0.7,0,.7}, { 2,-2,-2}, { 2, 2,-2}, // 'right vert back' {1,1,0}, {-2,-2, 2}, {-2,-2,-2}, // 'bottom left side' {1,1,0}, { 2,-2, 2}, { 2,-2,-2}, // 'bottom right side' {1,1,0}, {-2, 2, 2}, {-2, 2,-2}, // 'top left side' {1,1,0}, { 2, 2, 2}, { 2, 2,-2}, // 'top right side' {0,1,0}, {-2,-2,0}, { 2,-2,0}, // middle section on Z=0 plane {0,1,0}, {-2, 2,0}, { 2, 2,0}, {0,1,0}, {-2,-2,0}, {-2, 2,0}, {0,1,0}, { 2,-2,0}, { 2, 2,0}, {0.75,0.75,0}, {0,0,0}, {0,1.5,0}, // Pitt P standing at center origin {0.75,0.75,0}, {0, 1.5,0}, {.75,1.5,0}, {0.75,0.75,0}, {.75, 1.5,0}, {.75,0.75,0}, {0.75,0.75,0}, {.75, 0.75,0}, {0,0.75,0}, {0,0.7,0.7}, {0,-1,-2}, {0,1,-2}, // back lines {0,0.7,0.7}, {-1,0,-2}, {1,0,-2}, // back lines {0,1,1}, {0,-1, 2}, {0,1, 2}, // front line {0,1,1}, {-1,0, 2}, {1,0, 2}, // front line {-1,-1,-1} // negative color as termination to avoid counting elements }; float my_obj[][3] = { // XXXX 4. Add some additional object of your own here (a pyramid perhaps) {-1,-1,-1} // negative color as termination to avoid counting elements }; float axes[][3] = { // origin centered axis lines in RGB for XYZ directions /* {r,g,b} {x1,y1,z1}, {x2,y2,z2} */ {1,0,0}, {0,0,0}, {2,0,0}, {0.5,0,0}, {0,0,0}, {-2,0,0}, {0,1,0} , {0,0,0}, {0,2,0}, {0,0.5,0}, {0,0,0}, {0,-2,0}, {0,0,1}, {0,0,0}, {0,0,2}, {0,0,0.5}, {0,0,0}, {0,0,-2}, {-1,-1,-1} // negative color as termination to avoid counting elements }; /* Globals */ int mouse_button, mouse_state; int viewport_x, viewport_y; float screen_xmin, screen_xmax, screen_ymin, screen_ymax; // See reshape() float mouse_screen_x = 0, mouse_screen_y = 0; float thetax = 0, thetay = 0, thetaz = 0; float *theta_to_update = &thetay; float delta_theta = PI/48 ; // make exactly 48 steps per half turn int draw_mode = 0, rotate_mode = 0; float view_plane_z = 0; int go = 1; float cx = 0, cy = 0, cz = 0 ; // rotation center float tx = 0, ty = 0, tz = 0 ; // current translation int mouse_pt_x, mouse_pt_y; #define DEF_EYEZ 16. // the default initial eye distance float eyex = 0, eyey = 0, eyez = DEF_EYEZ; void perspective_project(float x, float y, float z, float eyex, float eyey, float eyez, float *xp, float *yp) { float mag; // tmp variable to hold the perspective "magnification" // XXXX 1. Produce perspective transformed x and y of an arbitrary XYZ // point using view_plane_z in conjunction with eye position. // Although this is discussed on book pages 119-121 we'd like you // to implement the technique that is presented in class following // these 3 steps, A B C according the the comments below // A. convert xyz to eye relative coordinates // B. compute perspective "magnification" using eyez, z and view_plane_z // C. generate transformed XY by applying mag and adding back eyeXY // Notice that the final values need to be stored using *xp and *yp } // Apply the given rotation matrix to the specified point void rotate(float mat[9], float pt[3]) { float tp[3]; // temp copy to complete everything in one step tp[0] = pt[0]; tp[1] = pt[1]; tp[2] = pt[2]; // matrix * point pt[0] = mat[0]*tp[0] + mat[1]*tp[1] + mat[2]*tp[2]; pt[1] = mat[3]*tp[0] + mat[4]*tp[1] + mat[5]*tp[2]; pt[2] = mat[6]*tp[0] + mat[7]*tp[1] + mat[8]*tp[2]; } // XXXX 2&3 complete the rotate_x and rotate_y functions below... void rotate_x(float rad, float point[3]) { float m[9]; // XXXX your code here } void rotate_y(float rad, float point[3]) { float m[9]; // XXXX your code here } void rotate_z(float rad, float point[3]) { float m[9]; // z axis rotation matrix m[0] = cos(rad); m[1] = -sin(rad); m[2] = 0; m[3] = sin(rad); m[4] = cos(rad); m[5] = 0; m[6] = 0; m[7] = 0; m[8] = 1; rotate(m, point); } /*** XXXXX NO ASSIGNED CHANGES BEYOND HERE XXXX ***/ void transform(float point[3]) { // Apply the current global translation point[0] += tx; point[1] += ty; point[2] += tz; // translate to rotational center origin (its always 0 in this program) point[0] -= cx; point[1] -= cy; point[2] -= cz; /* Apply the three axis rotations. The order of their use matters */ rotate_x(thetax,point); rotate_y(thetay,point); rotate_z(thetaz,point); // add back the rotational center point[0] += cx; point[1] += cy; point[2] += cz; } void inverse_rotation(float point[3]) { /* The order of rotation matters and must be reverse from transform(). * Note that for the purpose of mouse drag of the figure on the * screen we don't use translation */ rotate_z(-thetaz,point); rotate_y(-thetay,point); rotate_x(-thetax,point); } void draw_object(float o[][3]) { // draw a series of lines as a wireframe object int i; float xp, yp, point[3]; for(i = 0; o[i][0] >= 0; i+=3) { // a negative color means we're done glBegin(GL_LINES); // draw lines specified by end pts // independently transform and project each end point glColor3f(o[i][0], o[i][1], o[i][2]); point[0] = o[i+1][0]; point[1] = o[i+1][1]; point[2] = o[i+1][2]; transform(point); perspective_project(point[0], point[1], point[2], eyex,eyey,eyez,&xp,&yp); glVertex2f(xp,yp); point[0] = o[i+2][0]; point[1] = o[i+2][1]; point[2] = o[i+2][2]; transform(point); perspective_project(point[0], point[1], point[2], eyex,eyey,eyez,&xp,&yp); glVertex2f(xp,yp); glEnd(); } } void draw_sombrero(void) { // mathematically known as the sinc() function float r, theta, point[3], xp, yp; for(r = 5; r >= 0; r -= 0.025) { glColor3f(0.2,0.2,1.0- r/5.0); glBegin(GL_LINE_LOOP); for(theta = 0; theta < 2.0*PI; theta += PI/32.0) { point[0] = r*cos(theta); point[1] = r*sin(theta); point[2] = cos(r*5.0)/(1+(r/2)*(r/2)); transform(point); perspective_project(point[0], point[1], point[2], eyex,eyey,eyez,&xp,&yp); glVertex2f(xp,yp); } glEnd(); } } void draw_sphere(void) { float p[3]; float xp,yp; float r,theta,phi; float c = 0; r = 3.0f; glLineWidth(2); // note that 1 sweep over the sphere is PI radians // so this loop sweeps over the sphere twice. for(phi = 0, c = 0; phi <= 2.0*PI; phi += PI/16.0f, c += 1.0/64.0) { if( phi < PI ) glColor3f(0.75,0.75,0); else glColor3f(0,0.5,1); glBegin(GL_LINE_LOOP); for(theta = 0; theta < 2.0*PI; theta += PI/64.0) { float myphi; // impose a sin wave on phi myphi = phi + sin(theta*12.0)/16.0; // spherical coordinates p[0] = r * sin(myphi) * cos(theta); p[1] = r * sin(myphi) * sin(theta); p[2] = r * cos(myphi); transform(p); perspective_project(p[0], p[1], p[2], eyex,eyey,eyez,&xp,&yp); glVertex2f(xp,yp); } glEnd(); } glLineWidth(1); } void draw_rotation_point(void) { float p[3]; float xp,yp; // draw rotation point which is always at 0,0,0 in this program! p[0] = cx; p[1] = cy; p[2] = cz; perspective_project(p[0], p[1], p[2],eyex,eyey,eyez,&xp,&yp); glPointSize(3); glBegin(GL_POINTS); glColor3f(1,0.2,0.2); glVertex2f(xp,yp); glEnd(); } void draw_eye_position(void) { float k = 0.2; glBegin(GL_LINES); glColor3f(1,1,1); glVertex2f(eyex,eyey-k); glVertex2f(eyex,eyey+k); glVertex2f(eyex-k,eyey); glVertex2f(eyex+k,eyey); glEnd(); } void draw_string(float x, float y, char *s) { glColor3f(0,1,1); glRasterPos2f(x, y) ; // place left baseline text start position while(*s) glutBitmapCharacter(GLUT_BITMAP_9_BY_15, *s++); } void draw_info(void) { char sbuf[256]; // local buffer to hold draw_string text sprintf(sbuf, "rotation %7.3f %7.3f %7.3f radians", thetax,thetay,thetaz); draw_string(screen_xmin+0.01, screen_ymin+0.05, sbuf); sprintf(sbuf, "translation %7.3f %7.3f %7.3f world units", tx,ty,tz); draw_string(screen_xmin+0.01, screen_ymin+0.2, sbuf); sprintf(sbuf, "eye %7.2f %7.2f %7.2f world units", eyex,eyey,eyez); draw_string(screen_xmin+0.01, screen_ymin+0.35, sbuf); sprintf(sbuf, "z view plane %6.2f", view_plane_z) ; draw_string(screen_xmin+0.01, screen_ymin+0.50, sbuf); } void draw(void) { switch(draw_mode) { // could condense via function ptrs or other ways case 0: //draw_object(axes) ; // optionally put axes on cube draw_object(cube); break; case 1: draw_object(axes); draw_object(my_obj); break; case 2: draw_object(axes); draw_sphere(); break; case 3: draw_object(axes); draw_sombrero(); break; } draw_eye_position(); draw_rotation_point(); draw_info(); } /*** GLUT Callback Stubs ***/ void display(void) { glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); draw(); glutSwapBuffers(); glutReportErrors(); } void timer(int value) { switch( rotate_mode ) { case 1: thetax += delta_theta; break; case 2: thetay += delta_theta; break; case 3: thetaz += delta_theta; break; case 4: // not a physically possible rotation but ... thetax += delta_theta; thetay += delta_theta; thetaz += delta_theta; break; } if( go ) glutTimerFunc( MILLISEC, timer, value++); glutPostRedisplay(); } void reshape(int w, int h) { float fw = w/PIX_PER_UNIT, fh = h/PIX_PER_UNIT; // floating wid&ht viewport_x = w; viewport_y = h; screen_xmin = -fw/2; // this is our new world scaled view screen_xmax = fw/2; screen_ymin = -fh/2; screen_ymax = fh/2; glViewport(0,0,viewport_x,viewport_y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(screen_xmin,screen_xmax,screen_ymin,screen_ymax,0,10); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0,0,7, 0,0,0, 0,1,0); // Will explain in class } void keyboard( unsigned char key, int x, int y ) { switch( key ) { case 27: // escape key exit(0); break; case 'i': // move in inward toward the origin eyez -= 0.25; break; case 'o': // move in outward away from origin eyez += 0.25; break; case 'r': // change the rotate mode rotate_mode++; rotate_mode = rotate_mode%5; thetax = thetay = thetaz = 0; break; case 'R': // cap R resets everything rotate_mode = 0; draw_mode = 0; thetax = thetay = thetaz = 0; cx = cy = cz = 0; tx = ty = tz = 0; go = 1; eyex = eyey = 0 ; eyez = DEF_EYEZ; view_plane_z = 0; break; case 'd': // change the draw mode draw_mode++; draw_mode = draw_mode%4 ; // objects 0 to 3 break; case 'x': theta_to_update = &thetax; break; case 'y': theta_to_update = &thetay; break; case 'z': theta_to_update = &thetaz; break; case '-': *theta_to_update -= delta_theta; break; case '=': *theta_to_update += delta_theta; break; case ',': view_plane_z -= 0.25; break; case '.': view_plane_z += 0.25; break; case 'p': go = !go; fprintf(stderr,"%s\n", go?"run":"pause"); if( go ) glutTimerFunc( MILLISEC, timer, 0); break; default: //fprintf(stderr, "Unknown keystroke\n"); break; } glutPostRedisplay(); } void mouse(int button, int state, int mousex, int mousey) { mouse_button = button; mouse_state = state; switch( mouse_button ) { case GLUT_RIGHT_BUTTON: if( mouse_state == GLUT_DOWN ) { // remember position for right button drag. mouse_pt_x = mousex; mouse_pt_y = mousey; } break; } glutPostRedisplay(); } void mouse_motion(int mousex, int mousey) { switch( mouse_button ) { case GLUT_LEFT_BUTTON: if( mouse_state == GLUT_DOWN ) { // compute 2D mouse pos in screen coords float mx,my; mx = mousex/(float)viewport_x; my = (viewport_y-mousey)/(float)viewport_y; mouse_screen_x = screen_xmin + mx*(float)(screen_xmax-screen_xmin); mouse_screen_y = screen_ymin + my*(float)(screen_ymax-screen_ymin); // use mouse to set eye position in draw object eyex = mouse_screen_x; eyey = mouse_screen_y; } break; case GLUT_RIGHT_BUTTON: if( mouse_state == GLUT_DOWN ) { /* Drag correctly in XY regardless of * obj rotation by applying inverse rotation to * XY reference vectors & using them for XY * translation. Its left to you to follow the * mouse exactly for objects on the Z=0 plane. * Hint: Recall from asn00 that mouse callbacks * are fast, but not fast enough to hit every * pixel passed over during a drag. */ float xaxis[3], yaxis[3]; xaxis[0] = (mousex - mouse_pt_x)/PIX_PER_UNIT; xaxis[1] = 0; xaxis[2] = 0; yaxis[0] = 0; yaxis[1] = -(mousey - mouse_pt_y)/PIX_PER_UNIT; yaxis[2] = 0; inverse_rotation(xaxis); inverse_rotation(yaxis); tx += xaxis[0]; ty += xaxis[1]; tz += xaxis[2]; tx += yaxis[0]; ty += yaxis[1]; tz += yaxis[2]; mouse_pt_x = mousex; // be ready for next call mouse_pt_y = mousey; } break; } glutPostRedisplay(); } void init_gl(int argc, char **argv) { glClearColor(0.25, 0.25, 0.25, 1); // gray glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); } void init_glut(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB|GLUT_DEPTH|GLUT_DOUBLE); glutInitWindowSize(WINDOW_X,WINDOW_Y); glutCreateWindow("Hello Glut"); glutDisplayFunc( display ); glutReshapeFunc( reshape ); glutMouseFunc( mouse ); glutMotionFunc( mouse_motion ); glutKeyboardFunc( keyboard ); glutTimerFunc( MILLISEC, timer, 0); } int main(int argc, char **argv) { init_glut(argc, argv); init_gl(argc, argv); glutReportErrors(); glutMainLoop(); return(0); // should never get here!!! }