//============================================================ // STUDENT NAME: Shi Hui Ling // NUS User ID.: E0425158 // COMMENTS TO GRADER: // // ============================================================ #include <stdlib.h> #include <stdio.h> #include <math.h> #ifdef __APPLE__ #define GL_SILENCE_DEPRECATION #include <GLUT/glut.h> #else #include <GL/glut.h> #endif ///////////////////////////////////////////////////////////////////////////// // CONSTANTS ///////////////////////////////////////////////////////////////////////////// #define PI 3.1415926535897932384626433832795 #define PLANET_RADIUS 100.0 #define NUM_CARS 12 // Total number of cars. #define CAR_LENGTH 32.0 #define CAR_WIDTH 16.0 #define CAR_HEIGHT 14.0 #define CAR_MIN_ANGLE_INCR 0.5 // Min degrees to rotate car around planet each frame. #define CAR_MAX_ANGLE_INCR 3.0 // Max degrees to rotate car around planet each frame. #define CAR_TOP_DIST (PLANET_RADIUS + CAR_HEIGHT) // Distance of the top of a car from planet's center. #define EYE_INIT_DIST (3.0 * CAR_TOP_DIST) // Initial distance of eye from planet's center. #define EYE_DIST_INCR (0.1 * CAR_TOP_DIST) // Distance increment when changing eye's distance. #define EYE_MIN_DIST (1.5 * CAR_TOP_DIST) // Min eye's distance from planet's center. #define EYE_LATITUDE_INCR 2.0 // Degree increment when changing eye's latitude. #define EYE_MIN_LATITUDE -85.0 // Min eye's latitude (in degrees). #define EYE_MAX_LATITUDE 85.0 // Max eye's latitude (in degrees). #define EYE_LONGITUDE_INCR 2.0 // Degree increment when changing eye's longitude. #define CLIP_PLANE_DIST (1.1 * CAR_TOP_DIST) // Distance of near or far clipping plane from planet's center. #define VERT_FOV 45.0 // Vertical FOV (in degrees) of the perspective camera. #define DESIRED_FPS 60 // Approximate desired number of frames per second. // Planet's color. const GLfloat planetColor[] = { 0.9, 0.6, 0.4 }; // Car tyre color. const GLfloat tyreColor[] = { 0.2, 0.2, 0.2 }; // Material properties for all objects. const GLfloat materialSpecular[] = { 1.0, 1.0, 1.0, 1.0 }; const GLfloat materialShininess[] = { 100.0 }; const GLfloat materialEmission[] = { 0.0, 0.0, 0.0, 1.0 }; // Light 0. const GLfloat light0Ambient[] = { 0.1, 0.1, 0.1, 1.0 }; const GLfloat light0Diffuse[] = { 0.7, 0.7, 0.7, 1.0 }; const GLfloat light0Specular[] = { 0.9, 0.9, 0.9, 1.0 }; const GLfloat light0Position[] = { 1.0, 1.0, 1.0, 0.0 }; // Light 1. const GLfloat light1Ambient[] = { 0.1, 0.1, 0.1, 1.0 }; const GLfloat light1Diffuse[] = { 0.7, 0.7, 0.7, 1.0 }; const GLfloat light1Specular[] = { 0.9, 0.9, 0.9, 1.0 }; const GLfloat light1Position[] = { -1.0, 0.0, -0.5, 0.0 }; ///////////////////////////////////////////////////////////////////////////// // GLOBAL VARIABLES ///////////////////////////////////////////////////////////////////////////// // Define the cars. typedef struct CarType { float bodyColor[3]; // RGB color of the car body. double angleIncr; // Degrees to rotate car around planet each frame. double angularPos; // Angular position of car around planet (in degrees). double xzAxis[2]; // A vector in the x-z plane. Contains the x and z components respectively. double rotAngle; // Rotation angle about the xzAxis[]. } CarType; CarType car[ NUM_CARS ]; // Array of cars. // Define eye position. // Initial eye position is at [ 0, 0, EYE_INIT_DIST ] in the world frame, // looking at the world origin. // The up-vector is assumed to be [0, 1, 0]. double eyeLatitude = 0; double eyeLongitude = 0; double eyeDistance = EYE_INIT_DIST; // Window's size. int winWidth = 800; // Window width in pixels. int winHeight = 600; // Window height in pixels. // Others. bool pauseAnimation = false; // Freeze the cars iff true. bool drawAxes = true; // Draw world coordinate frame axes iff true. bool drawWireframe = false; // Draw polygons in wireframe if true, otherwise polygons are filled. ///////////////////////////////////////////////////////////////////////////// // Draw a car with its bottom on the z = 0 plane. The car is heading in the // +x direction and its top facing the +z direction. // The z-axis passes through the center of the car. // The car body is drawn using the input color, and its tyres are drawn // using the constant tyreColor. // The car has size CAR_LENGTH x CAR_WIDTH x CAR_HEIGHT. ///////////////////////////////////////////////////////////////////////////// void DrawOneCar( float bodyColor[3] ) { glColor3fv(bodyColor); //**************************** // WRITE YOUR CODE HERE. // // Draw the car body. //**************************** glPushMatrix(); glTranslated(0.0, 0.0, 0.4*CAR_HEIGHT); glScaled(CAR_LENGTH, CAR_WIDTH, 0.4*CAR_HEIGHT); glutSolidCube(1.0); glPopMatrix(); glPushMatrix(); glTranslated(0.0, 0.0, 0.8*CAR_HEIGHT); glScaled(0.7*CAR_LENGTH, CAR_WIDTH, 0.4*CAR_HEIGHT); glutSolidCube(1.0); glPopMatrix(); glColor3fv(tyreColor); //**************************** // WRITE YOUR CODE HERE. // // Draw the four tyres. //**************************** double wheelPosX = CAR_LENGTH/2 - 0.4*CAR_HEIGHT; double tyrePosX[4] = { -wheelPosX, -wheelPosX, wheelPosX, wheelPosX }; double wheelPosY = CAR_WIDTH/2 - 0.2*CAR_HEIGHT; double tyrePosY[4] = { -wheelPosY, wheelPosY, -wheelPosY, wheelPosY }; for (int i = 0; i < 4; i++) { glPushMatrix(); // my wheels are underneath the body so that my car won't exceed the CAR_WIDTH glTranslated(tyrePosX[i], tyrePosY[i], 0.2*CAR_HEIGHT); glRotated(90, 1.0, 0.0, 0.0); // because torus is by default aligned with z-axis, need to rotate glutSolidTorus(0.2*CAR_HEIGHT, 0.15*CAR_HEIGHT, 32, 32); glPopMatrix(); } } ///////////////////////////////////////////////////////////////////////////// // Draw all the cars. Each is put correctly on its great circle. ///////////////////////////////////////////////////////////////////////////// void DrawAllCars( void ) { for ( int i = 0; i < NUM_CARS; i++ ) { //**************************** // WRITE YOUR CODE HERE. //**************************** // xzAxis[0] is the x-component and xzAxis[1] is the z-component // Let C be the great circle in the y = 0 plane, and let v be a vector in the x-z plane, and let // theta be an angle. If we rotate C about v by theta, we get another great circle of the sphere CarType curr = car[i]; glPushMatrix(); glRotated(curr.rotAngle, curr.xzAxis[0], 0, curr.xzAxis[1]); // rotate to another great circle glRotated(curr.angularPos, 0.0, 1.0, 0.0); // rotation in the y=0 plane, i.e. about y-axis glTranslated(0.0, 0.0, PLANET_RADIUS); DrawOneCar(curr.bodyColor); glPopMatrix(); } } ///////////////////////////////////////////////////////////////////////////// // Draw the x, y, z axes. Each is drawn with the input length. // The x-axis is red, y-axis green, and z-axis blue. ///////////////////////////////////////////////////////////////////////////// void DrawAxes( double length ) { glPushAttrib( GL_ALL_ATTRIB_BITS ); glDisable( GL_LIGHTING ); glLineWidth( 3.0 ); glBegin( GL_LINES ); // x-axis. glColor3f( 1.0, 0.0, 0.0 ); glVertex3d( 0.0, 0.0, 0.0 ); glVertex3d( length, 0.0, 0.0 ); // y-axis. glColor3f( 0.0, 1.0, 0.0 ); glVertex3d( 0.0, 0.0, 0.0 ); glVertex3d( 0.0, length, 0.0 ); // z-axis. glColor3f( 0.0, 0.0, 1.0 ); glVertex3d( 0.0, 0.0, 0.0 ); glVertex3d( 0.0, 0.0, length ); glEnd(); glPopAttrib(); } ///////////////////////////////////////////////////////////////////////////// // The display callback function. ///////////////////////////////////////////////////////////////////////////// void MyDisplay( void ) { glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); glMatrixMode( GL_PROJECTION ); glLoadIdentity(); //*********************************************************************** // WRITE YOUR CODE HERE. // // Modify the following line of code to set up a perspective view // frustum using gluPerspective(). The near and far planes should be set // near the planet's surface, yet still do not clip off any part of the // planet and cars. The near and far planes should vary with the eye's // distance from the planet's center. You should make use of the value of // the predefined constant CLIP_PLANE_DIST to position your near and // far planes. //*********************************************************************** gluPerspective( VERT_FOV, (double)winWidth / winHeight, eyeDistance-CLIP_PLANE_DIST, eyeDistance+CLIP_PLANE_DIST ); glMatrixMode( GL_MODELVIEW ); glLoadIdentity(); //*********************************************************************** // WRITE YOUR CODE HERE. // // Modify the following line of code to set up the view transformation. // You may use the gluLookAt() function, but you can use other method. //*********************************************************************** // gluLookAt( 0.0, 0.0, eyeDistance, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0 ); glTranslated(0.0, 0.0, -eyeDistance); glRotated(eyeLatitude, 1.0, 0.0, 0.0); // rotation about x axis glRotated(-eyeLongitude, 0.0, 1.0, 0.0); // rotation about y axis // Set world positions of the two lights. glLightfv( GL_LIGHT0, GL_POSITION, light0Position); glLightfv( GL_LIGHT1, GL_POSITION, light1Position); // Draw axes. if ( drawAxes ) DrawAxes( 2 * PLANET_RADIUS ); // Draw planet. glColor3fv( planetColor ); glutSolidSphere( PLANET_RADIUS, 72, 36 ); // Draw the cars. DrawAllCars(); glutSwapBuffers(); } ///////////////////////////////////////////////////////////////////////////// // Update each car's angular position on the great circle by // the angle increment. ///////////////////////////////////////////////////////////////////////////// void UpdateCars( void ) { for ( int i = 0; i < NUM_CARS; i++ ) { car[i].angularPos += car[i].angleIncr; if ( car[i].angularPos > 360.0 ) car[i].angularPos -= 360.0; } glutPostRedisplay(); } ///////////////////////////////////////////////////////////////////////////// // Initializes each car with a random body color, a random rotation // increment (speed), a random anglular position, and a random great circle. ///////////////////////////////////////////////////////////////////////////// void InitCars( void ) { for ( int i = 0; i < NUM_CARS; i++ ) { car[i].bodyColor[0] = (float)rand() / RAND_MAX; // 0.0 to 1.0. car[i].bodyColor[1] = (float)rand() / RAND_MAX; // 0.0 to 1.0. car[i].bodyColor[2] = (float)rand() / RAND_MAX; // 0.0 to 1.0. car[i].angleIncr = (double)rand() / RAND_MAX * ( CAR_MAX_ANGLE_INCR - CAR_MIN_ANGLE_INCR ) + CAR_MIN_ANGLE_INCR; // CAR_MIN_ANGLE_INCR to CAR_MAX_ANGLE_INCR. car[i].angularPos = (double)rand() / RAND_MAX * 360.0; // 0.0 to 360.0. // The following 3 items defines a random great circle. car[i].xzAxis[0] = (double)rand() / RAND_MAX * 2.0 - 1.0; // -1.0 to 1.0. car[i].xzAxis[1] = (double)rand() / RAND_MAX * 2.0 - 1.0; // -1.0 to 1.0. car[i].rotAngle = (double)rand() / RAND_MAX * 360.0; // 0.0 to 360.0. } } ///////////////////////////////////////////////////////////////////////////// // The timer callback function. ///////////////////////////////////////////////////////////////////////////// void MyTimer( int v ) { if ( !pauseAnimation ) { //**************************** // WRITE YOUR CODE HERE. //**************************** UpdateCars(); glutTimerFunc( 1000.0/DESIRED_FPS, MyTimer, v ); } } ///////////////////////////////////////////////////////////////////////////// // The keyboard callback function. ///////////////////////////////////////////////////////////////////////////// void MyKeyboard( unsigned char key, int x, int y ) { switch ( key ) { // Quit program. case 'q': case 'Q': exit(0); break; // Toggle between wireframe and filled polygons. case 'w': case 'W': drawWireframe = !drawWireframe; if ( drawWireframe ) glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); else glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); glutPostRedisplay(); break; // Toggle axes. case 'x': case 'X': drawAxes = !drawAxes; glutPostRedisplay(); break; // Pause or resume animation. case 'p': case 'P': pauseAnimation = !pauseAnimation; if ( !pauseAnimation ) glutTimerFunc( 0, MyTimer, 0 ); break; // Reset to initial view. case 'r': case 'R': eyeLatitude = 0.0; eyeLongitude = 0.0; eyeDistance = EYE_INIT_DIST; glutPostRedisplay(); break; } } ///////////////////////////////////////////////////////////////////////////// // The special key callback function. ///////////////////////////////////////////////////////////////////////////// void MySpecialKey( int key, int x, int y ) { switch ( key ) { case GLUT_KEY_LEFT: eyeLongitude -= EYE_LONGITUDE_INCR; if ( eyeLongitude < -360.0 ) eyeLongitude += 360.0 ; glutPostRedisplay(); break; case GLUT_KEY_RIGHT: eyeLongitude += EYE_LONGITUDE_INCR; if ( eyeLongitude > 360.0 ) eyeLongitude -= 360.0 ; glutPostRedisplay(); break; case GLUT_KEY_DOWN: eyeLatitude -= EYE_LATITUDE_INCR; if ( eyeLatitude < EYE_MIN_LATITUDE ) eyeLatitude = EYE_MIN_LATITUDE; glutPostRedisplay(); break; case GLUT_KEY_UP: eyeLatitude += EYE_LATITUDE_INCR; if ( eyeLatitude > EYE_MAX_LATITUDE ) eyeLatitude = EYE_MAX_LATITUDE; glutPostRedisplay(); break; case GLUT_KEY_PAGE_UP: eyeDistance -= EYE_DIST_INCR; if ( eyeDistance < EYE_MIN_DIST ) eyeDistance = EYE_MIN_DIST; glutPostRedisplay(); break; case GLUT_KEY_PAGE_DOWN: eyeDistance += EYE_DIST_INCR; glutPostRedisplay(); break; } } ///////////////////////////////////////////////////////////////////////////// // The reshape callback function. ///////////////////////////////////////////////////////////////////////////// void MyReshape( int w, int h ) { winWidth = w; winHeight = h; glViewport( 0, 0, w, h ); } ///////////////////////////////////////////////////////////////////////////// // The init function. It initializes some OpenGL states. ///////////////////////////////////////////////////////////////////////////// void MyInit( void ) { glClearColor( 0.0, 0.0, 0.0, 1.0 ); // Set black background color. glEnable( GL_DEPTH_TEST ); // Use depth-buffer for hidden surface removal. glShadeModel( GL_SMOOTH ); //======================================================================= // The rest of the code below sets up the lighting and // the material properties of all objects. // You can just ignore this part. //======================================================================= // Set Light 0. glLightfv( GL_LIGHT0, GL_AMBIENT, light0Ambient ); glLightfv( GL_LIGHT0, GL_DIFFUSE, light0Diffuse ); glLightfv( GL_LIGHT0, GL_SPECULAR, light0Specular ); glEnable( GL_LIGHT0 ); // Set Light 1. glLightfv( GL_LIGHT1, GL_AMBIENT, light1Ambient ); glLightfv( GL_LIGHT1, GL_DIFFUSE, light1Diffuse ); glLightfv( GL_LIGHT1, GL_SPECULAR, light1Specular ); glEnable( GL_LIGHT1 ); glEnable( GL_LIGHTING ); // Set some global light properties. GLfloat globalAmbient[] = { 0.1, 0.1, 0.1, 1.0 }; glLightModelfv( GL_LIGHT_MODEL_AMBIENT, globalAmbient ); glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE ); glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE ); // Set the universal material properties. // The diffuse and ambient components can be changed using glColor*(). glMaterialfv( GL_FRONT, GL_SPECULAR, materialSpecular ); glMaterialfv( GL_FRONT, GL_SHININESS, materialShininess ); glMaterialfv( GL_FRONT, GL_EMISSION, materialEmission ); glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); glEnable( GL_COLOR_MATERIAL ); glEnable( GL_NORMALIZE ); // Let OpenGL automatically renomarlize all normal vectors. } static void WaitForEnterKeyBeforeExit(void) { printf("Press Enter to exit.\n"); fflush(stdin); getchar(); } ///////////////////////////////////////////////////////////////////////////// // The main function. ///////////////////////////////////////////////////////////////////////////// int main( int argc, char** argv ) { atexit(WaitForEnterKeyBeforeExit); // atexit() is declared in stdlib.h srand(27); // set random seed glutInit( &argc, argv ); glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH ); glutInitWindowSize( winWidth, winHeight ); glutCreateWindow( "main" ); MyInit(); InitCars(); // Register the callback functions. glutDisplayFunc( MyDisplay ); glutReshapeFunc( MyReshape ); glutKeyboardFunc( MyKeyboard ); glutSpecialFunc( MySpecialKey ); glutTimerFunc( 0, MyTimer, 0 ); // Display user instructions in console window. printf( "Press LEFT ARROW to move eye left.\n" ); printf( "Press RIGHT ARROW to move eye right.\n" ); printf( "Press DOWN ARROW to move eye down.\n" ); printf( "Press UP ARROW to move eye up.\n" ); printf( "Press PAGE UP to move closer.\n" ); printf( "Press PAGE DN to move further.\n" ); printf( "Press 'P' to toggle car animation.\n" ); printf( "Press 'W' to toggle wireframe.\n" ); printf( "Press 'X' to toggle axes.\n" ); printf( "Press 'R' to reset to initial view.\n" ); printf( "Press 'Q' to quit.\n\n" ); // Enter GLUT event loop. glutMainLoop(); return 0; }