// Revo2.cpp // CSC 480/580 Spring 2006 - Kirby // --------------------------------------------------------------------- // Draws various wireframe surfaces of revolution for functions f(x). // // New features in this version 2 (upgraded from Revo.cpp): // -- menu for selecting colors and shapes // -- keys for changing polygon level and pausing animation // -- text output to graphics window // // This new version doesn't introduce any new OpenGL techniques, just some // nice-to-know glut*() functionality. // // It also uses the class Stopwatch (see eponymous header file) to allow // animations to pause and be restarted at the same point. // --------------------------------------------------------------------- #include #include #include #include #include #include "Stopwatch.h" using namespace std ; const double PI= 3.1415926535897 ; // Inventory of functions for surface of revolution. typedef double (*Func)( double ) ; double chalice( double x ) ; double sphereTube( double x ) ; double spindle( double x ) ; double accordion( double x ) ; double megaphone( double x ) ; double kiss( double x ) ; // Menu names and associated data for shape functions and colors. const Func SHAPE_FUNCS[]= { chalice, sphereTube, spindle, accordion, megaphone, kiss, NULL } ; const char* SHAPE_NAMES[]= { "chalice", "sphere tube", "spindle", "accordion", "megaphone", "kiss", NULL } ; // NULL req'd as end marker const double COLOR_DATA[][3]= {{1,1,0}, {0.5,1,0.5}, {0,1,1}, {0,0,0} } ; const char* COLOR_NAMES[]= { "yellow", "light green", "cyan", NULL } ; // NULL req'd as end marker // Global State Variables Stopwatch G_stopwatch ; // for coupling rotation state (in animation) to time int G_shapeID = 0 ; // selects shape function f(x) for surface of revolution int G_colorID= 0 ; // selects RGB color data for surface of revolution int G_nIntervalsX = 32 ; // number of slices along x direction for surface int G_nIntervalsTheta = 32 ; // number of angular slices for surface // // Some sample functions to use to generate surfaces of revolution. // double chalice( double x ) // A nice outline of a chalice. { return 0.5 + 0.12 * ( sin( 6*x ) + cos( 8*x - 1 ) ) ; } double sphereTube( double x ) // A sphere with the ends sliced off. { const double L = 0.5 ; assert( L*L - x*x > 0 ) ; return 0.001 + sqrt( L*L - x*x ) ; } double spindle( double x ) // A Gaussian curve. { return 0.5* exp( - 16*x*x ) ; } double accordion( double x ) // A simple wiggle. { double c= cos( 12*x ) ; return 0.3 + 0.2*c*c ; } double megaphone( double x ) // A line segment { return 0.3 + 0.4*x ; } double kiss( double x ) // Flat line going to 0 at each end. { return 0.20 + 0.2* min( tanh(60*(x+0.4)), -tanh(5*(x-0.2)) ) ; } // // Make a menu of the names and shapes. // void onMenuShape( int value ) // Callback for responding to shape menu selections. { G_shapeID= value ; glutPostRedisplay() ; } void onMenuColor( int value ) // Callback for responding to color menu selections. { G_colorID= value ; glutPostRedisplay() ; } void makeMenus() // Build menus to allow user to select shapes and colors. { // Submenu of shapes int idMenuShape= glutCreateMenu( onMenuShape ) ; for ( int i=0 ; SHAPE_NAMES[i] ; ++i ) glutAddMenuEntry( SHAPE_NAMES[i], i ) ; // Submenu of colors int idMenuColor= glutCreateMenu( onMenuColor ) ; for ( int i=0 ; COLOR_NAMES[i] ; ++i ) glutAddMenuEntry( COLOR_NAMES[i], i ) ; // Top level menu int idMenuTop= glutCreateMenu( 0 ) ; glutAddSubMenu( "Shape", idMenuShape ) ; glutAddSubMenu( "Color", idMenuColor ) ; glutAttachMenu( GLUT_RIGHT_BUTTON ) ; } void surfaceOfRevolution( double xStart, double xStop, int nIntervalsX, int nIntervalsTheta, double (*f)(double) ) // Draw a surface of revolution for f(x) from x=xStart to xStop. // Assumes f(x)>0. // Each slice is divided into nIntervalsTheta angular intervals. // The number of slices is nIntervalsX. { assert( nIntervalsX > 0 ) ; assert( nIntervalsTheta > 0 ) ; assert( xStart >= -1 ) ; assert( xStop <= 1 ) ; assert( xStart < xStop ) ; const double DX= ( xStop - xStart ) / nIntervalsX ; // step size along x const double DTHETA = 2*PI / nIntervalsTheta ; // step size around circle of revolution // Initialize values - first slice. double x= xStart ; double r= f( xStart ) ; assert( r > 0 ) ; for ( int ix=0 ; ix < nIntervalsX ; ++ix ) { double xNext= x + DX ; double rNext= f( xNext ) ; assert( rNext > 0 ) ; glBegin( GL_QUAD_STRIP ) ; { for ( int ia=0 ; ia <= nIntervalsTheta ; ++ia ) { // Update the angle as we move around the circle. double theta= ia * DTHETA ; double c= cos( theta ) ; double s= sin( theta ) ; // Get the points on the circle at x and xNext. double y= r * c ; double z= r * s ; double yNext= rNext * c ; double zNext= rNext * s ; // Add edge at this angle from x to xNext (one boundary of quad strip). glVertex3d( x, y, z ) ; glVertex3d( xNext, yNext, zNext ) ; } // Prepare for next pass through the x loop x= xNext ; r= rNext ; } glEnd() ; } } void textLabel( const char* szLabel, double x, double y ) // Write a C string at given x,y position in view volume (z=0). { glRasterPos2f( x, y ) ; while ( *szLabel ) glutBitmapCharacter( GLUT_BITMAP_HELVETICA_12, *szLabel++ ) ; } void onDisplay() // Main display callback: show a rotating surface of revolution. { // Prepare to draw. glClear( GL_COLOR_BUFFER_BIT ) ; glLoadIdentity() ; // Write a little white caption giving the shape name. glColor3d( 1,1,1 ) ; ostringstream os ; os << SHAPE_NAMES[ G_shapeID ] << " " << G_nIntervalsX << " x " << G_nIntervalsTheta ; textLabel( os.str().c_str(), -0.8, -0.8 ) ; // Set rotation angle based on stopwatch value (time in milliseconds). const double SECONDS_PER_ROT= 8 ; double degrees= G_stopwatch.getValue() * 0.001 * 360 / SECONDS_PER_ROT ; glRotated( degrees, 1, 1, 1 ) ; // Draw, using color and shape from global state (which user selected via menus). glColor3dv( COLOR_DATA[ G_colorID ] ) ; surfaceOfRevolution( -0.45, 0.45, G_nIntervalsX, G_nIntervalsTheta, SHAPE_FUNCS[ G_shapeID ] ) ; // Display the result. glutSwapBuffers() ; } void onIdleAnimate() // One frame of animation. { glutPostRedisplay() ; } void toggleAnimation() // Toggle animation on and off. { if ( G_stopwatch.isStopped() ) { G_stopwatch.start() ; glutIdleFunc( onIdleAnimate ) ; } else { G_stopwatch.stop() ; glutIdleFunc( NULL ) ; } } void onSpecialKey( int key, int x, int y ) // Respond to key presses of special keys: // Up/down arrows: double/halve x-resolution of surface of revolution // Left/right arrows: double/halve angular resolution of surface of revolution // F9: toggle animation on/off { const int MINRES= 4 ; // don't go below this number of intervals const int MAXRES= 64 ; // don't go above this number of intervals switch( key ) { case GLUT_KEY_LEFT : if ( G_nIntervalsTheta > MINRES ) G_nIntervalsTheta /=2 ; break ; case GLUT_KEY_RIGHT : if ( G_nIntervalsTheta < MAXRES ) G_nIntervalsTheta *=2 ; break ; case GLUT_KEY_DOWN : if ( G_nIntervalsX > MINRES ) G_nIntervalsX /=2 ; break ; case GLUT_KEY_UP : if ( G_nIntervalsX < MAXRES ) G_nIntervalsX *=2 ; break ; case GLUT_KEY_F9 : toggleAnimation() ; break ; } glutPostRedisplay() ; } int main( int argc, char** argv ) { // Initialize OpenGL. glutInit( &argc, argv ) ; glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH ) ; // Window position and caption. glutInitWindowSize( 500, 500 ) ; glutInitWindowPosition( 100, 100 ) ; glutCreateWindow( "Revolution Number Two!" ) ; // Polygons will be wireframe, rather than solid. glPolygonMode( GL_FRONT, GL_LINE ) ; glPolygonMode( GL_BACK, GL_LINE ) ; // Add a menu. makeMenus() ; // Setup keyboard callbacks. glutSpecialFunc( onSpecialKey ) ; // Main display loop. glutDisplayFunc( onDisplay ) ; glutIdleFunc( onIdleAnimate ) ; glutMainLoop() ; return 0 ; }