|
OpenGL Programming Guide (Addison-Wesley Publishing Company) |
Chapter Objectives
After reading this chapter, you'll be able to do the following:
Some graphics applications simply draw static images of two- and three-dimensional objects. Other applications allow the user to identify objects on the screen and then to move, modify, delete, or otherwise manipulate those objects. OpenGL is designed to support exactly such interactive applications. Since objects drawn on the screen typically undergo multiple rotations, translations, and perspective transformations, it can be difficult for you to determine which object a user is selecting in a three-dimensional scene. To help you, OpenGL provides a selection mechanism that automatically tells you which objects are drawn inside a specified region of the window. You can use this mechanism together with a special utility routine to determine which object within the region the user is specifying, or picking, with the cursor.
Selection is actually a mode of operation for OpenGL; feedback is another such mode. In feedback mode, you use your graphics hardware and OpenGL to perform the usual rendering calculations. Instead of using the calculated results to draw an image on the screen, however, OpenGL returns (or feeds back) the drawing information to you. For example, if you want to draw three-dimensional objects on a plotter rather than the screen, you would draw the items in feedback mode, collect the drawing instructions, and then convert them to commands the plotter can understand.
In both selection and feedback modes, drawing information is returned to the application rather than being sent to the framebuffer, as it is in rendering mode. Thus, the screen remains frozen - no drawing occurs - while OpenGL is in selection or feedback mode. In these modes, the contents of the color, depth, stencil, and accumulation buffers are not affected. This chapter explains each of these modes in its own section:
Typically, when you're planning to use OpenGL's selection mechanism, you first draw your scene into the framebuffer, and then you enter selection mode and redraw the scene. However, once you're in selection mode, the contents of the framebuffer don't change until you exit selection mode. When you exit selection mode, OpenGL returns a list of the primitives that intersect the viewing volume (remember that the viewing volume is defined by the current modelview and projection matrices and any additional clipping planes, as explained in Chapter 3.) Each primitive that intersects the viewing volume causes a selection hit. The list of primitives is actually returned as an array of integer-valued names and related data - the hit records - that correspond to the current contents of the name stack. You construct the name stack by loading names onto it as you issue primitive drawing commands while in selection mode. Thus, when the list of names is returned, you can use it to determine which primitives might have been selected on the screen by the user.
In addition to this selection mechanism, OpenGL provides a utility routine designed to simplify selection in some cases by restricting drawing to a small region of the viewport. Typically, you use this routine to determine which objects are drawn near the cursor, so that you can identify which object the user is picking. (You can also delimit a selection region by specifying additional clipping planes. Remember that these planes act in world space, not in screen space.) Since picking is a special case of selection, selection is described first in this chapter, and then picking.
To use the selection mechanism, you need to perform the following steps.
The following paragraphs describe glSelectBuffer() and glRenderMode(). In the next section, the commands to manipulate the name stack are described.
As mentioned in the previous section, the name stack forms the basis for the selection information that's returned to you. To create the name stack, first initialize it with glInitNames(), which simply clears the stack, and then add integer names to it while issuing corresponding drawing commands. As you might expect, the commands to manipulate the stack allow you to push a name onto it (glPushName()), pop a name off of it (glPopName()), and replace the name on the top of the stack with a different one (glLoadName()). Example 13-1 shows what your name-stack manipulation code might look like with these commands.
Example 13-1 : Creating a Name Stack
glInitNames(); glPushName(0); glPushMatrix(); /* save the current transformation state */ /* create your desired viewing volume here */ glLoadName(1); drawSomeObject(); glLoadName(2); drawAnotherObject(); glLoadName(3); drawYetAnotherObject(); drawJustOneMoreObject(); glPopMatrix (); /* restore the previous transformation state*/
In this example, the first two objects to be drawn have their own names, and the third and fourth objects share a single name. With this setup, if either or both of the third and fourth objects causes a selection hit, only one hit record is returned to you. You can have multiple objects share the same name if you don't need to differentiate between them when processing the hit records.
Calls to glPushName(), glPopName(), and glLoadName() are ignored if you're not in selection mode. You might find that it simplifies your code to use these calls throughout your drawing code, and then use the same drawing code for both selection and normal rendering modes.
In selection mode, a primitive that intersects the viewing volume causes a selection hit. Whenever a name-stack manipulation command is executed or glRenderMode() is called, OpenGL writes a hit record into the selection array if there's been a hit since the last time the stack was manipulated or glRenderMode() was called. With this process, objects that share the same name - for example, an object that's composed of more than one primitive - don't generate multiple hit records. Also, hit records aren't guaranteed to be written into the array until glRenderMode() is called.
Note: In addition to primitives, valid coordinates produced by glRasterPos() can cause a selection hit. Also, in the case of polygons, no hit occurs if the polygon would have been culled.
Each hit record consists of four items, in order.
When you enter selection mode, OpenGL initializes a pointer to the beginning of the selection array. Each time a hit record is written into the array, the pointer is updated accordingly. If writing a hit record would cause the number of values in the array to exceed the size argument specified with glSelectBuffer(), OpenGL writes as much of the record as fits in the array and sets an overflow flag. When you exit selection mode with glRenderMode(), this command returns the number of hit records that were written (including a partial record if there was one), clears the name stack, resets the overflow flag, and resets the stack pointer. If the overflow flag had been set, the return value is -1.
In Example 13-2, four triangles (green, red, and two yellow triangles, created by calling drawTriangle()) and a wireframe box representing the viewing volume (drawViewVolume()) are drawn to the screen. Then the triangles are rendered again (selectObjects()), but this time in selection mode. The corresponding hit records are processed in processHits(), and the selection array is printed out. The first triangle generates a hit, the second one doesn't, and the third and fourth ones together generate a single hit.
Example 13-2 : Selection Example: select.c
#include <GL/gl.h> #include <GL/glu.h> #include <GL/glut.h> #include <stdlib.h> #include <stdio.h> void drawTriangle (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, GLfloat x3, GLfloat y3, GLfloat z) { glBegin (GL_TRIANGLES); glVertex3f (x1, y1, z); glVertex3f (x2, y2, z); glVertex3f (x3, y3, z); glEnd (); } void drawViewVolume (GLfloat x1, GLfloat x2, GLfloat y1, GLfloat y2, GLfloat z1, GLfloat z2) { glColor3f (1.0, 1.0, 1.0); glBegin (GL_LINE_LOOP); glVertex3f (x1, y1, -z1); glVertex3f (x2, y1, -z1); glVertex3f (x2, y2, -z1); glVertex3f (x1, y2, -z1); glEnd (); glBegin (GL_LINE_LOOP); glVertex3f (x1, y1, -z2); glVertex3f (x2, y1, -z2); glVertex3f (x2, y2, -z2); glVertex3f (x1, y2, -z2); glEnd (); glBegin (GL_LINES); /* 4 lines */ glVertex3f (x1, y1, -z1); glVertex3f (x1, y1, -z2); glVertex3f (x1, y2, -z1); glVertex3f (x1, y2, -z2); glVertex3f (x2, y1, -z1); glVertex3f (x2, y1, -z2); glVertex3f (x2, y2, -z1); glVertex3f (x2, y2, -z2); glEnd (); } void drawScene (void) { glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluPerspective (40.0, 4.0/3.0, 1.0, 100.0); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); gluLookAt (7.5, 7.5, 12.5, 2.5, 2.5, -5.0, 0.0, 1.0, 0.0); glColor3f (0.0, 1.0, 0.0); /* green triangle */ drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0); glColor3f (1.0, 0.0, 0.0); /* red triangle */ drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0); glColor3f (1.0, 1.0, 0.0); /* yellow triangles */ drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0); drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0); drawViewVolume (0.0, 5.0, 0.0, 5.0, 0.0, 10.0); } void processHits (GLint hits, GLuint buffer[]) { unsigned int i, j; GLuint names, *ptr; printf ("hits = %d\n", hits); ptr = (GLuint *) buffer; for (i = 0; i < hits; i++) { /* for each hit */ names = *ptr; printf (" number of names for hit = %d\n", names); ptr++; printf(" z1 is %g;", (float) *ptr/0x7fffffff); ptr++; printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++; printf (" the name is "); for (j = 0; j < names; j++) { /* for each name */ printf ("%d ", *ptr); ptr++; } printf ("\n"); } } #define BUFSIZE 512 void selectObjects(void) { GLuint selectBuf[BUFSIZE]; GLint hits; glSelectBuffer (BUFSIZE, selectBuf); (void) glRenderMode (GL_SELECT); glInitNames(); glPushName(0); glPushMatrix (); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glOrtho (0.0, 5.0, 0.0, 5.0, 0.0, 10.0); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glLoadName(1); drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0); glLoadName(2); drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0); glLoadName(3); drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0); drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0); glPopMatrix (); glFlush (); hits = glRenderMode (GL_RENDER); processHits (hits, selectBuf); } void init (void) { glEnable(GL_DEPTH_TEST); glShadeModel(GL_FLAT); } void display(void) { glClearColor (0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawScene (); selectObjects (); glFlush(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize (200, 200); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); init(); glutDisplayFunc(display); glutMainLoop(); return 0; }
As an extension of the process described in the previous section, you can use selection mode to determine if objects are picked. To do this, you use a special picking matrix in conjunction with the projection matrix to restrict drawing to a small region of the viewport, typically near the cursor. Then you allow some form of input, such as clicking a mouse button, to initiate selection mode. With selection mode established and with the special picking matrix used, objects that are drawn near the cursor cause selection hits. Thus, during picking you're typically determining which objects are drawn near the cursor.
Picking is set up almost exactly like regular selection mode is, with the following major differences.
glMatrixMode (GL_PROJECTION); glPushMatrix (); glLoadIdentity (); gluPickMatrix (...); gluPerspective, glOrtho, gluOrtho2D, or glFrustum /* ... draw scene for picking ; perform picking ... */ glPopMatrix();
Another completely different way to perform picking is described in "Object Selection Using the Back Buffer" in Chapter 14. This technique uses color values to identify different components of an object.
glGetIntegerv(GL_VIEWPORT, GLint *viewport);
Advanced
The net result of the matrix created by gluPickMatrix() is to transform the clipping region into the unit cube -1 ≤ (x, y, z) ≤ 1 (or -w ≤ (wx, wy, wz) ≤ w). The picking matrix effectively performs an orthogonal transformation that maps a subregion of this unit cube to the unit cube. Since the transformation is arbitrary, you can make picking work for different sorts of regions - for example, for rotated rectangular portions of the window. In certain situations, you might find it easier to specify additional clipping planes to define the picking region.
Example 13-3 illustrates simple picking. It also demonstrates how to use multiple names to identify different components of a primitive, in this case the row and column of a selected object. A 3 × 3 grid of squares is drawn, with each square a different color. The board[3][3] array maintains the current amount of blue for each square. When the left mouse button is pressed, the pickSquares() routine is called to identify which squares were picked by the mouse. Two names identify each square in the grid - one identifies the row, and the other the column. Also, when the left mouse button is pressed, the color of all squares under the cursor position changes.
Example 13-3 : Picking Example: picksquare.c
#include <GL/gl.h> #include <GL/glu.h> #include <stdlib.h> #include <stdio.h> #include <GL/glut.h> int board[3][3]; /* amount of color for each square */ /* Clear color value for every square on the board */ void init(void) { int i, j; for (i = 0; i < 3; i++) for (j = 0; j < 3; j ++) board[i][j] = 0; glClearColor (0.0, 0.0, 0.0, 0.0); } void drawSquares(GLenum mode) { GLuint i, j; for (i = 0; i < 3; i++) { if (mode == GL_SELECT) glLoadName (i); for (j = 0; j < 3; j ++) { if (mode == GL_SELECT) glPushName (j); glColor3f ((GLfloat) i/3.0, (GLfloat) j/3.0, (GLfloat) board[i][j]/3.0); glRecti (i, j, i+1, j+1); if (mode == GL_SELECT) glPopName (); } } } /* processHits prints out the contents of the * selection array. */ void processHits (GLint hits, GLuint buffer[]) { unsigned int i, j; GLuint ii, jj, names, *ptr; printf ("hits = %d\n", hits); ptr = (GLuint *) buffer; for (i = 0; i < hits; i++) { /* for each hit */ names = *ptr; printf (" number of names for this hit = %d\n", names); ptr++; printf(" z1 is %g;", (float) *ptr/0x7fffffff); ptr++; printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++; printf (" names are "); for (j = 0; j < names; j++) { /* for each name */ printf ("%d ", *ptr); if (j == 0) /* set row and column */ ii = *ptr; else if (j == 1) jj = *ptr; ptr++; } printf ("\n"); board[ii][jj] = (board[ii][jj] + 1) % 3; } } #define BUFSIZE 512 void pickSquares(int button, int state, int x, int y) { GLuint selectBuf[BUFSIZE]; GLint hits; GLint viewport[4]; if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN) return; glGetIntegerv (GL_VIEWPORT, viewport); glSelectBuffer (BUFSIZE, selectBuf); (void) glRenderMode (GL_SELECT); glInitNames(); glPushName(0); glMatrixMode (GL_PROJECTION); glPushMatrix (); glLoadIdentity (); /* create 5x5 pixel picking region near cursor location */ gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y), 5.0, 5.0, viewport); gluOrtho2D (0.0, 3.0, 0.0, 3.0); drawSquares (GL_SELECT); glMatrixMode (GL_PROJECTION); glPopMatrix (); glFlush (); hits = glRenderMode (GL_RENDER); processHits (hits, selectBuf); glutPostRedisplay(); } void display(void) { glClear(GL_COLOR_BUFFER_BIT); drawSquares (GL_RENDER); glFlush(); } void reshape(int w, int h) { glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D (0.0, 3.0, 0.0, 3.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (100, 100); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); init (); glutMouseFunc (pickSquares); glutReshapeFunc (reshape); glutDisplayFunc(display); glutMainLoop(); return 0; }
Multiple names can also be used to choose parts of a hierarchical object in a scene. For example, if you were rendering an assembly line of automobiles, you might want the user to move the mouse to pick the third bolt on the left front tire of the third car in line. A different name can be used to identify each level of hierarchy: which car, which tire, and finally which bolt. As another example, one name can be used to describe a single molecule among other molecules, and additional names can differentiate individual atoms within that molecule.
Example 13-4 is a modification of Example 3-4, which draws an automobile with four identical wheels, each of which has five identical bolts. Code has been added to manipulate the name stack with the object hierarchy.
Example 13-4 : Creating Multiple Names
draw_wheel_and_bolts() { long i; draw_wheel_body(); for (i = 0; i < 5; i++) { glPushMatrix(); glRotate(72.0*i, 0.0, 0.0, 1.0); glTranslatef(3.0, 0.0, 0.0); glPushName(i); draw_bolt_body(); glPopName(); glPopMatrix(); } } draw_body_and_wheel_and_bolts() { draw_car_body(); glPushMatrix(); glTranslate(40, 0, 20); /* first wheel position*/ glPushName(1); /* name of wheel number 1 */ draw_wheel_and_bolts(); glPopName(); glPopMatrix(); glPushMatrix(); glTranslate(40, 0, -20); /* second wheel position */ glPushName(2); /* name of wheel number 2 */ draw_wheel_and_bolts(); glPopName(); glPopMatrix(); /* draw last two wheels similarly */ }
Example 13-5 uses the routines in Example 13-4 to draw three different cars, numbered 1, 2, and 3.
Example 13-5 : Using Multiple Names
draw_three_cars() { glInitNames(); glPushMatrix(); translate_to_first_car_position(); glPushName(1); draw_body_and_wheel_and_bolts(); glPopName(); glPopMatrix(); glPushMatrix(); translate_to_second_car_position(); glPushName(2); draw_body_and_wheel_and_bolts(); glPopName(); glPopMatrix(); glPushMatrix(); translate_to_third_car_position(); glPushName(3); draw_body_and_wheel_and_bolts(); glPopName(); glPopMatrix(); }
Assuming that picking is performed, the following are some possible name-stack return values and their interpretations. In these examples, at most one hit record is returned; also, d1 and d2 are depth values.
2 d1d2 2 1 Car 2, wheel 1
1 d1d2 3 Car 3 body
3 d1d2 1 1 0 Bolt 0 on wheel 1 on car 1
empty The pick was outside all cars
The last interpretation assumes that the bolt and wheel don't occupy the same picking region. A user might well pick both the wheel and the bolt, yielding two hits. If you receive multiple hits, you have to decide which hit to process, perhaps by using the depth values to determine which picked object is closest to the viewpoint. The use of depth values is explored further in the next section.
Example 13-6 demonstrates how to use depth values when picking to determine which object is picked. This program draws three overlapping rectangles in normal rendering mode. When the left mouse button is pressed, the pickRects() routine is called. This routine returns the cursor position, enters selection mode, initializes the name stack, and multiplies the picking matrix onto the stack before the orthographic projection matrix. A selection hit occurs for each rectangle the cursor is over when the left mouse button is clicked. Finally, the contents of the selection buffer are examined to identify which named objects were within the picking region near the cursor.
The rectangles in this program are drawn at different depth, or z, values. Since only one name is used to identify all three rectangles, only one hit can be recorded. However, if more than one rectangle is picked, that single hit has different minimum and maximum z values.
Example 13-6 : Picking with Depth Values: pickdepth.c
#include <GL/gl.h> #include <GL/glu.h> #include <GL/glut.h> #include <stdlib.h> #include <stdio.h> void init(void) { glClearColor(0.0, 0.0, 0.0, 0.0); glEnable(GL_DEPTH_TEST); glShadeModel(GL_FLAT); glDepthRange(0.0, 1.0); /* The default z mapping */ } void drawRects(GLenum mode) { if (mode == GL_SELECT) glLoadName(1); glBegin(GL_QUADS); glColor3f(1.0, 1.0, 0.0); glVertex3i(2, 0, 0); glVertex3i(2, 6, 0); glVertex3i(6, 6, 0); glVertex3i(6, 0, 0); glEnd(); if (mode == GL_SELECT) glLoadName(2); glBegin(GL_QUADS); glColor3f(0.0, 1.0, 1.0); glVertex3i(3, 2, -1); glVertex3i(3, 8, -1); glVertex3i(8, 8, -1); glVertex3i(8, 2, -1); glEnd(); if (mode == GL_SELECT) glLoadName(3); glBegin(GL_QUADS); glColor3f(1.0, 0.0, 1.0); glVertex3i(0, 2, -2); glVertex3i(0, 7, -2); glVertex3i(5, 7, -2); glVertex3i(5, 2, -2); glEnd(); } void processHits(GLint hits, GLuint buffer[]) { unsigned int i, j; GLuint names, *ptr; printf("hits = %d\n", hits); ptr = (GLuint *) buffer; for (i = 0; i < hits; i++) { /* for each hit */ names = *ptr; printf(" number of names for hit = %d\n", names); ptr++; printf(" z1 is %g;", (float) *ptr/0x7fffffff); ptr++; printf(" z2 is %g\n", (float) *ptr/0x7fffffff); ptr++; printf(" the name is "); for (j = 0; j < names; j++) { /* for each name */ printf("%d ", *ptr); ptr++; } printf("\n"); } } #define BUFSIZE 512 void pickRects(int button, int state, int x, int y) { GLuint selectBuf[BUFSIZE]; GLint hits; GLint viewport[4]; if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN) return; glGetIntegerv(GL_VIEWPORT, viewport); glSelectBuffer(BUFSIZE, selectBuf); (void) glRenderMode(GL_SELECT); glInitNames(); glPushName(0); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); /* create 5x5 pixel picking region near cursor location */ gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3] - y), 5.0, 5.0, viewport); glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5); drawRects(GL_SELECT); glPopMatrix(); glFlush(); hits = glRenderMode(GL_RENDER); processHits(hits, selectBuf); } void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawRects(GL_RENDER); glFlush(); } void reshape(int w, int h) { glViewport(0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize (200, 200); glutInitWindowPosition (100, 100); glutCreateWindow(argv[0]); init(); glutMouseFunc(pickRects); glutReshapeFunc(reshape); glutDisplayFunc(display); glutMainLoop(); return 0; }
Try This
Most programs that allow a user to interactively edit some geometry provide a mechanism for the user to pick items or groups of items for editing. For two-dimensional drawing programs (for example, text editors, page-layout programs, and circuit-design programs), it might be easier to do your own picking calculations instead of using the OpenGL picking mechanism. Often, it's easy to find bounding boxes for two-dimensional objects and to organize them in some hierarchical data structure to speed up searches. For example, picking that uses the OpenGL style in a VLSI layout program containing millions of rectangles can be relatively slow. However, using simple bounding-box information when rectangles are typically aligned with the screen could make picking in such a program extremely fast. The code is probably simpler to write, too.
As another example, since only geometric objects cause hits, you might want to create your own method for picking text. Setting the current raster position is a geometric operation, but it effectively creates only a single pickable point at the current raster position, which is typically at the lower-left corner of the text. If your editor needs to manipulate individual characters within a text string, some other picking mechanism must be used. You could draw little rectangles around each character during picking mode, but it's almost certainly easier to handle text as a special case.
If you decide to use OpenGL picking, organize your program and its data structures so that it's easy to draw appropriate lists of objects in either selection or normal drawing mode. This way, when the user picks something, you can use the same data structures for the pick operation that you use to display the items on the screen. Also, consider whether you want to allow the user to select multiple objects. One way to do this is to store a bit for each item indicating whether it's selected (however, this method requires traversing your entire list of items to find the selected items). You might find it useful to maintain a list of pointers to selected items to speed up this search. It's probably a good idea to keep the selection bit for each item as well, since when you're drawing the entire picture, you might want to draw selected items differently (for example, in a different color or with a selection box around them). Finally, consider the selection user interface. You might want to allow the user to do the following:
A typical solution for a two-dimensional drawing program might work as follows.
Different rules can apply in particular situations. In a text editor, you probably don't have to worry about characters on top of each other, and selections of multiple characters are always contiguous characters in the document. Thus, you need to mark only the first and last selected characters to identify the complete selection. With text, often the best way to handle selection is to identify the positions between characters rather than the characters themselves. This allows you to have an empty selection when the beginning and end of the selection are between the same pair of characters; it also allows you to put the cursor before the first character in the document or after the final one with no special-case code.
In three-dimensional editors, you might provide ways to rotate and zoom between selections, so sophisticated schemes for cycling through the possible selections might be unnecessary. On the other hand, selection in three dimensions is difficult because the cursor's position on the screen usually gives no indication of its depth.
Feedback is similar to selection in that once you're in either mode, no pixels are produced and the screen is frozen. Drawing does not occur; instead, information about primitives that would have been rendered is sent back to the application. The key difference between selection and feedback modes is what information is sent back. In selection mode, assigned names are returned to an array of integer values. In feedback mode, information about transformed primitives is sent back to an array of floating-point values. The values sent back to the feedback array consist of tokens that specify what type of primitive (point, line, polygon, image, or bitmap) has been processed and transformed, followed by vertex, color, or other data for that primitive. The values returned are fully transformed by lighting and viewing operations. Feedback mode is initiated by calling glRenderMode() with GL_FEEDBACK as the argument.
Here's how you enter and exit feedback mode.
type Argument | Coordinates | Color | Texture | Total Values |
---|---|---|---|---|
GL_2D |
x, y |
- |
- |
2 |
GL_3D |
x, y, z |
- |
- |
3 |
GL_3D_COLOR |
x, y, z |
k |
- |
3 + k |
GL_3D_COLOR_TEXTURE |
x, y, z |
k |
4 |
7 + k |
GL_4D_COLOR_TEXTURE |
x, y, z, w |
k |
4 |
8 + k |
In feedback mode, each primitive that would be rasterized (or each call to glBitmap(), glDrawPixels(), or glCopyPixels(), if the raster position is valid) generates a block of values that's copied into the feedback array. The number of values is determined by the type argument to glFeedbackBuffer(), as listed in Table 13-1. Use the appropriate value for the type of primitives you're drawing: GL_2D or GL_3D for unlit two- or three-dimensional primitives, GL_3D_COLOR for lit, three-dimensional primitives, and GL_3D_COLOR_TEXTURE or GL_4D_COLOR_TEXTURE for lit, textured, three- or four-dimensional primitives.
Each block of feedback values begins with a code indicating the primitive type, followed by values that describe the primitive's vertices and associated data. Entries are also written for pixel rectangles. In addition, pass-through markers that you've explicitly created can be returned in the array; the next section explains these markers in more detail. Table 13-2 shows the syntax for the feedback array; remember that the data associated with each returned vertex is as described in Table 13-1. Note that a polygon can have n vertices returned. Also, the x, y, z coordinates returned by feedback are window coordinates; if w is returned, it's in clip coordinates. For bitmaps and pixel rectangles, the coordinates returned are those of the current raster position. In the table, note that GL_LINE_RESET_TOKEN is returned only when the line stipple is reset for that line segment.
Primitive Type | Code | Associated Data |
---|---|---|
Point |
GL_POINT_TOKEN |
vertex |
Line |
GL_LINE_TOKEN or GL_LINE_RESET_TOKEN |
vertex vertex |
Polygon |
GL_POLYGON_TOKEN |
n vertex vertex ... vertex |
Bitmap |
GL_BITMAP_TOKEN |
vertex |
Pixel Rectangle |
GL_DRAW_PIXEL_TOKEN or GL_COPY_PIXEL_TOKEN |
vertex |
Pass-through |
GL_PASS_THROUGH_TOKEN |
a floating-point number |
Feedback occurs after transformations, lighting, polygon culling, and interpretation of polygons by glPolygonMode(). It might also occur after polygons with more than three edges are broken up into triangles (if your particular OpenGL implementation renders polygons by performing this decomposition). Thus, it might be hard for you to recognize the primitives you drew in the feedback data you receive. To help parse the feedback data, call glPassThrough() as needed in your sequence of drawing commands to insert a marker. You might use the markers to separate the feedback values returned from different primitives, for example. This command causes GL_PASS_THROUGH_TOKEN to be written into the feedback array, followed by the floating-point value you pass in as an argument.
Example 13-7 demonstrates the use of feedback mode. This program draws a lit, three-dimensional scene in normal rendering mode. Then, feedback mode is entered, and the scene is redrawn. Since the program draws lit, untextured, three-dimensional objects, the type of feedback data is GL_3D_COLOR. Since RGBA mode is used, each unclipped vertex generates seven values for the feedback buffer: x, y, z, r, g, b, and a.
In feedback mode, the program draws two lines as part of a line strip and then inserts a pass-through marker. Next, a point is drawn at (-100.0, -100.0, -100.0), which falls outside the orthographic viewing volume and thus doesn't put any values into the feedback array. Finally, another pass-through marker is inserted, and another point is drawn.
Example 13-7 : Feedback Mode: feedback.c
#include <GL/gl.h> #include <GL/glu.h> #include <GL/glut.h> #include <stdlib.h> #include <stdio.h> void init(void) { glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); } void drawGeometry (GLenum mode) { glBegin (GL_LINE_STRIP); glNormal3f (0.0, 0.0, 1.0); glVertex3f (30.0, 30.0, 0.0); glVertex3f (50.0, 60.0, 0.0); glVertex3f (70.0, 40.0, 0.0); glEnd (); if (mode == GL_FEEDBACK) glPassThrough (1.0); glBegin (GL_POINTS); glVertex3f (-100.0, -100.0, -100.0); /* will be clipped */ glEnd (); if (mode == GL_FEEDBACK) glPassThrough (2.0); glBegin (GL_POINTS); glNormal3f (0.0, 0.0, 1.0); glVertex3f (50.0, 50.0, 0.0); glEnd (); } void print3DcolorVertex (GLint size, GLint *count, GLfloat *buffer) { int i; printf (" "); for (i = 0; i < 7; i++) { printf ("%4.2f ", buffer[size-(*count)]); *count = *count - 1; } printf ("\n"); } void printBuffer(GLint size, GLfloat *buffer) { GLint count; GLfloat token; count = size; while (count) { token = buffer[size-count]; count--; if (token == GL_PASS_THROUGH_TOKEN) { printf ("GL_PASS_THROUGH_TOKEN\n"); printf (" %4.2f\n", buffer[size-count]); count--; } else if (token == GL_POINT_TOKEN) { printf ("GL_POINT_TOKEN\n"); print3DcolorVertex (size, &count, buffer); } else if (token == GL_LINE_TOKEN) { printf ("GL_LINE_TOKEN\n"); print3DcolorVertex (size, &count, buffer); print3DcolorVertex (size, &count, buffer); } else if (token == GL_LINE_RESET_TOKEN) { printf ("GL_LINE_RESET_TOKEN\n"); print3DcolorVertex (size, &count, buffer); print3DcolorVertex (size, &count, buffer); } } } void display(void) { GLfloat feedBuffer[1024]; GLint size; glMatrixMode (GL_PROJECTION); glLoadIdentity (); glOrtho (0.0, 100.0, 0.0, 100.0, 0.0, 1.0); glClearColor (0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); drawGeometry (GL_RENDER); glFeedbackBuffer (1024, GL_3D_COLOR, feedBuffer); (void) glRenderMode (GL_FEEDBACK); drawGeometry (GL_FEEDBACK); size = glRenderMode (GL_RENDER); printBuffer (size, feedBuffer); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (100, 100); glutInitWindowPosition (100, 100); glutCreateWindow(argv[0]); init(); glutDisplayFunc(display); glutMainLoop(); return 0; }
Running this program generates the following output:
GL_LINE_RESET_TOKEN 30.00 30.00 0.00 0.84 0.84 0.84 1.00 50.00 60.00 0.00 0.84 0.84 0.84 1.00 GL_LINE_TOKEN 50.00 60.00 0.00 0.84 0.84 0.84 1.00 70.00 40.00 0.00 0.84 0.84 0.84 1.00 GL_PASS_THROUGH_TOKEN 1.00 GL_PASS_THROUGH_TOKEN 2.00 GL_POINT_TOKEN 50.00 50.00 0.00 0.84 0.84 0.84 1.00
Thus, the line strip drawn with these commands results in two primitives:
glBegin(GL_LINE_STRIP); glNormal3f (0.0, 0.0, 1.0); glVertex3f (30.0, 30.0, 0.0); glVertex3f (50.0, 60.0, 0.0); glVertex3f (70.0, 40.0, 0.0); glEnd();
The first primitive begins with GL_LINE_RESET_TOKEN, which indicates that the primitive is a line segment and that the line stipple is reset. The second primitive begins with GL_LINE_TOKEN, so it's also a line segment, but the line stipple isn't reset and hence continues from where the previous line segment left off. Each of the two vertices for these lines generates seven values for the feedback array. Note that the RGBA values for all four vertices in these two lines are (0.84, 0.84, 0.84, 1.0), which is a very light gray color with the maximum alpha value. These color values are a result of the interaction of the surface normal and lighting parameters.
Since no feedback data is generated between the first and second pass-through markers, you can deduce that any primitives drawn between the first two calls to glPassThrough() were clipped out of the viewing volume. Finally, the point at (50.0, 50.0, 0.0) is drawn, and its associated data is copied into the feedback array.
Note: In both feedback and selection modes, information on objects is returned prior to any fragment tests. Thus, objects that would not be drawn due to failure of the scissor, alpha, depth, or stencil tests may still have their data processed and returned in both feedback and selection modes.
Try This
Make changes to Example 13-7 and see how they affect the feedback values that are returned. For example, change the coordinate values of glOrtho(). Change the lighting variables, or eliminate lighting altogether and change the feedback type to GL_3D. Or add more primitives to see what other geometry (such as filled polygons) contributes to the feedback array.
OpenGL Programming Guide (Addison-Wesley Publishing Company) |