CSC 2141 Introduction to Computer Graphics More on Drawing in OpenGL: Examples Recall: Callbacks Programming interface for event-driven input Define a callback function for each type of event the graphics system recognizes This user-supplied function is executed when the event occurs GLUT example: glutMouseFunc(mymouse) mouse callback function void mymouse(GLint button, GLint state, GLint x, GLint y) GLUT Event Loop Recall that the last line in main.c for a program using GLUT must be glutMainLoop(); which puts the program in an infinite event loop In each pass through the event loop, GLUT looks at the events in the queue for each event in the queue, GLUT executes the appropriate callback function if one is defined if no callback is defined for the event, the event is ignored Using the mouse position In the next example, we draw a small square at the location of the mouse each time the left mouse button is clicked This example does not use the display callback but one is required by GLUT; We can use the empty display callback function mydisplay(){} main() function same as before Globals and myInit() Glsizei wh =500, ww=500; /* window dimensions */ GLfloat size = 3.0; /* one-half of side length of square */ myInit(void) { /* set viewing conditions */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D (0.0, (GLdouble) ww, 0.0, (GLdouble) wh ); glMatrixMode(GL_MODELVIEW); /* adjust viewport */ glViewport(0, 0, ww, wh); glClearColor(0.0, 0.0,0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glFlush(); } Drawing squares at cursor location void mymouse(int btn, int state, int x, int y) { if(btn==GLUT_RIGHT_BUTTON && state==GLUT_DOWN) exit(0); /*terminate the program through OpenGL */ if(btn==GLUT_LEFT_BUTTON && state==GLUT_DOWN) drawSquare(x, y); } void drawSquare(int x, int y) /* (x,y) is the center */ { y=wh-y; /* invert y position */ glColor3ub( (char) rand()%256, (char) rand ()%256, (char) rand()%256); /* a random color */ glBegin(GL_POLYGON); glVertex2f(x+size, y+size); glVertex2f(x-size, y+size); glVertex2f(x-size, y-size); glVertex2f(x+size, y-size); glEnd(); glFlush(); } Positioning • The position in the screen window returned by callback functions is with respect to the origin at the top-left corner (GLUT convention) • Consequence of refresh done from top to bottom • OpenGL uses a world coordinate system with origin at the bottom left • Must invert y coordinate returned by mouse callback using the height of the window • y = wh – y; (0,0) y wh h-y ww Obtaining the window size To invert the y position we need the window height Height can change during program execution New height returned to reshape callback Track with a global variable Recall: The Reshape callback glutReshapeFunc(myreshape) void myreshape( int w, int h) Returns width and height of new window (in pixels) A redisplay is posted automatically at end of execution of the callback The reshape callback is good place to put viewing functions because it is invoked when the window is first opened Recall: The Reshape callback Do we redraw all the objects that were in the window before it was resized? We need a mechanism to store and recall them Typically done by encapsulating all drawing in the display callback and reposting the display redraws all. In this example: our drawing is interactive based on mouse input and unless we store the squares drawn, we cannot recall them Let’s choose to clear the window if resized. Recall: The Reshape callback What do we do if the aspect ratio of the new window is different from that of the old window? No single answer Distortions may be okay Or not then set the viewport such that it has the same aspect ratio as the drawing area. Part of the window may not be used. In this example, we clear the window when resized so no distortion to old squares. New squares are drawn with the same fixed size. Example Reshape This reshape preserves shapes by making the viewport and the idealized drawing window have the same aspect ratio void myReshape(GLsizei w, GLsizei h) { ww = w; /* update window dimensions */ wh = h; /* adjust clipping box */ glMatrixMode(GL_PROJECTION); /* switch matrix mode */ glLoadIdentity(); gluOrtho2D (0.0, (GLdouble) w, 0.0, (GLdouble) h ); glMatrixMode(GL_MODELVIEW); /* return to modelview mode */ /* adjust viewport */ glViewport(0, 0, w, h); /* clear the window each time it is resized */ glClear(GL_COLOR_BUFFER_BIT); glFlush(); } Using globals The form of all GLUT callbacks is fixed void mydisplay() void mymouse(GLint button, GLint state, GLint x, GLint y) Must use globals to pass information to callbacks float size; /*global */ void mydisplay() { /* draw something that depends on size } Recall: Using the keyboard glutKeyboardFunc(myKeyboard) void myKeyboard(unsigned char key, int x, int y) Returns ASCII code of key depressed and mouse location void myKeyboard(unsigned char key, int x, int y) { if(key == ‘Q’ || key == ‘q’) exit(0); } Special and Modifier Keys GLUT defines the special keys in glut.h Function key 1: GLUT_KEY_F1 Up arrow key: GLUT_KEY_UP if(key == ‘GLUT_KEY_F1’ …… Can also check of one of the modifiers GLUT_ACTIVE_SHIFT GLUT_ACTIVE_CTRL GLUT_ACTIVE_ALT is depressed by glutGetModifiers() Using the motion callback We can draw squares (or anything else) continuously as long as a mouse button is depressed by using the motion callback glutMotionFunc(drawSquare) We can draw squares without depressing a button using the passive motion callback glutPassiveMotionFunc(drawSquare) Changing and disabling callback functions We can change most callback functions during program execution by specifying a new callback function We can also disable a callback function by setting it to NULL glutIdleFunc(NULL); Using the idle callback The idle callback is executed whenever there are no events in the event queue glutIdleFunc(myidle) Useful for animations void myidle() { /* change something */ t += dt glutPostRedisplay(); } Void mydisplay() { glClear(); /* draw something that depends on t */ glutSwapBuffers(); } Example animation: rotating square x=cosƟ y=sinƟ The point lies on a unit circle regardless of the value of Ɵ myDisplay() void myDisplay() { glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_POLYGON); thetar = theta * (2 * 3.14159) / 360.0; /* convert degrees to radians */ glVertex2f(cos(thetar), sin(thetar)); glVertex2f(-sin(thetar), cos(thetar)); glVertex2f(-cos(thetar), -sin(thetar)); glVertex2f(sin(thetar), -cos(thetar)); glEnd(); glutSwapBuffers(); /* double buffering */ } Change Ɵ as the program runs… In main() function specify callback glutIdleFunc(myIdle); And, define callback function as void myIdle () { theta += 2; if (theta >= 360.0) theta -= 360.0; glutPostRedisplay(); } One further change.. Turn on and off the rotation feature by mouse input Register mouse callback as glutMouseFunc(myMouse); Define mouse callback as void myMouse(int button, int state, int x, int y) { if (button == GLUT_LEFT_BUTTON)&&(state == GLUT_DOWN) glutIdleFunc(myIdle); if (button ==GLUT_RIGHT_BUTTON)&&(state == GLUT_DOWN) glutIdleFunc(NULL); } Too fast? Use a timer callback instead Which in turn will execute the display callback at a fixed rate (e.g. n frames per second) glutTimerFunc(1000/n, myTimer, 0); But no support for cancelling a timer callback. instead you can ignore a callback based on its value. Try running this with single buffering Do you notice partial display of rotated square? Multiple Windows GLUT supports multiple windows id = glutCreateWindow(“Second window”); And select this as the current window by glutSetWindow(id); You can make this window have different properties by invoking glutInitDisplayMode before glutCreateWindow Each window can set its own callback functions Callback registrations refer to the current window. Menus GLUT supports pop-up menus A menu can have submenus Three steps Define entries for the menu Define action for each menu item Action carried out if entry selected Attach menu to a mouse button Defining a simple menu In main.c glutCreateMenu(myMenu); glutAddMenuEntry(“clear Screen”, 1); glutAddMenuEntry(“exit”, 2); glutAttachMenu(GLUT_RIGHT_BUTTON); clear screen exit entries that appear when right button depressed identifiers Menu actions Menu callback void myMenu(int id) { if(id == 1) glClear(); if(id == 2) exit(0); } Note each menu has an id that is returned when it is created Menu actions Hierarchical menus are allowed Add submenus by glutAddSubMenu(char *submenu_name, int submenu_id) int sub_menu; sub_menu=glutCreateMenu(size_menu); //add to the current menu glutAddMenuEntry(“Increase square size”,2); glutAddMenuEntry(“decrease square size”,3); glutCreateMenu(top_menu); glutAddMenuEntry(“Quit”, 1); glutAddSubMenu(“Resize”, sub_menu); glutAttachMenu(GLUT_MIDDLE_BUTTON); Text We often want to control size, color and font Two ways in OpenGL 1. Stroke Text: constructed as other graphics primitives Use vertices to draw line segments or curves outlining the character Advantage: define once and apply transformations to generate any size and orientation Disadv: defining a full character set is complex… Text 2. Raster text Simple and fast Character is defined as rectangular array of bits (0s or 1s) called bit blocks or bitmap A raster character can be placed in frame buffer directly. When you overlay a bitmap on the frame buffer, the pixels that correspond to 1s are set to the current color. You can increase character size only by replicating pixels. Larger characters: blocky appearance Transformations like rotation do not make sense (can’t rotate pixel positions!) Raster text glutBitmapCharacter(GLUT_BITMAP_8_BY_13, c) GLUT_BITMAP_8_BY_13: is a set of bitmaps predefined in GLUT. It’s a fixed width font, i.e. all characters have the same width. c is the integer equivalent of an ASCII character Above function places c at the “current” raster position (part of state) and automatically advances the current position by the character width after the character is drawn. Position can be altered by glRasterPos*(rx, ry); glutBitmapWidth(GLUT_BITMAP_8_BY_13, c) Returns the width of the character c in pixels Typical function to display a string void bitMapString( float x, float y, char s[]) int i = 0; glRasterPos2f( x, y); { while (s[i] != '\0') { glutBitmapCharacter( GLUT_BITMAP_8_BY_13, s[i]); ++i; } } Stroke Text glutStrokeCharacter(GLUT_STROKE_MONO_ROMAN, c) You can use a predefined font as GLUT_STROKE_MONO_ROMAN Or, you can define your own fonts! Be careful: the way this function works may affect OpenGL state (the transformation matrices, you might need to save them) For now use raster text.