by Richard S. Wright, Jr.
WHAT YOU'LL LEARN IN THIS CHAPTER:
How To | Functions You'll Use |
---|---|
Assign OpenGL selection names to primitives or groups of primitives |
|
Use selection to determine which objects are under the mouse |
|
Use feedback to get information about where objects are drawn |
|
Thus far, you have learned to create some sophisticated 3D graphics using OpenGL, and many applications do no more than generate these scenes. But many graphics applications (notably, games, CAD, 3D modeling, and so on) require more interaction with the scene itself. In addition to menus and dialog boxes, often you need to provide a way for the user to interact with a graphical scene. Typically, this interaction usually happens with a mouse.
Selection, a powerful feature of OpenGL, allows you to take a mouse click at some position over a window and determine which of your objects are beneath it. The act of selecting a specific object on the screen is called picking. With OpenGL's selection feature, you can specify a viewing volume and determine which objects fall within that viewing volume. A powerful utility function, gluPickMatrix
, produces a matrix for you, based purely on screen coordinates and the pixel dimensions you specify; you use this matrix to create a smaller viewing volume placed beneath the mouse cursor. Then you use selection to test this viewing volume to see which objects are contained by it.
Feedback allows you to get information from OpenGL about how your vertices are transformed and illuminated when they are drawn to the frame buffer. You can use this information to transmit rendering results over a network, send them to a plotter, or add other graphics (say with GDI, for Windows programmers) to your OpenGL scene that appear to interact with the OpenGL objects. Feedback does not serve the same purpose as selection, but the mode of operation is similar and they can work productively together. You'll see this teamwork later in the SELECT
sample program.
Selection is actually a rendering mode, but in selection mode, no pixels are actually copied to the frame buffer. Instead, primitives that are drawn within the viewing volume (and thus would normally appear in the frame buffer) produce hit records in a selection buffer. This buffer, unlike other OpenGL buffers, is just an array of integer values.
You must set up this selection buffer in advance and name your primitives or groups of primitives (your objects or models) so they can be identified in the selection buffer. You then parse the selection buffer to determine which objects intersected the viewing volume. One potential use for this capability is for visibility determination. Named objects that do not appear in the selection buffer fell outside the viewing volume and would not have been drawn in render mode. Although selection mode is fast enough for object picking, using it for general-purpose frustum-culling performs significantly slower than any of the techniques we discussed in Chapter 11, “It's All About the Pipeline: Faster Geometry Throughput.” Typically, you modify the viewing volume before entering selection mode and call your drawing code to determine which objects are in some restricted area of your scene. For picking, you specify a viewing volume that corresponds to the mouse pointer and then check which named objects are rendered beneath the mouse.
You can name every single primitive used to render your scene of objects, but doing so is rarely useful. More often, you name groups of primitives, thus creating names for the specific objects or pieces of objects in your scene. Object names, like display list names, are nothing more than unsigned integers.
The names list is maintained on the name stack. After you initialize the name stack, you can push names on the stack or simply replace the name currently on top of the stack. When a hit occurs during selection, all the names currently on the name stack are appended to the end of the selection buffer. Thus, a single hit can return more than one name if needed.
For our first example, we keep things simple. We create a simplified (and not-to-scale) model of the inner planets of the solar system. When the left mouse button is down, we display a message box describing which planet was clicked. Listing 12.1 shows some of the rendering code for our sample program, PLANETS. We have created macro definitions for the sun, Mercury, Venus, Earth, and Mars.
Example 12.1. Naming the Sun and Planets in the PLANETS Program
/////////////////////////////// // Define object names #define SUN 1 #define MERCURY 2 #define VENUS 3 #define EARTH 4 #define MARS 5 /////////////////////////////////////////////////////////// // Called to draw scene void RenderScene(void) { // Clear the window with current clearing color glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Save the matrix state and do the rotations glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Translate the whole scene out and into view glTranslatef(0.0f, 0.0f, -300.0f); // Initialize the names stack glInitNames(); glPushName(0); // Name and draw the Sun glColor3f(1.0f, 1.0f, 0.0f); glLoadName(SUN); DrawSphere(15.0f); // Draw Mercury glColor3f(0.5f, 0.0f, 0.0f); glPushMatrix(); glTranslatef(24.0f, 0.0f, 0.0f); glLoadName(MERCURY); DrawSphere(2.0f); glPopMatrix(); // Draw Venus glColor3f(0.5f, 0.5f, 1.0f); glPushMatrix(); glTranslatef(60.0f, 0.0f, 0.0f); glLoadName(VENUS); DrawSphere(4.0f); glPopMatrix(); // Draw the Earth glColor3f(0.0f, 0.0f, 1.0f); glPushMatrix(); glTranslatef(100.0f,0.0f,0.0f); glLoadName(EARTH); DrawSphere(8.0f); glPopMatrix(); // Draw Mars glColor3f(1.0f, 0.0f, 0.0f); glPushMatrix(); glTranslatef(150.0f, 0.0f, 0.0f); glLoadName(MARS); DrawSphere(4.0f); glPopMatrix(); // Restore the matrix state glPopMatrix(); // Modelview matrix glutSwapBuffers(); }
In PLANETS, the function initializes and clears the name stack, and glPushName
pushes 0 on the stack to put at least one entry on the stack. For the sun and each planet, we call glLoadName
to name the object or objects about to be drawn. This name, in the form of an unsigned integer, is not pushed on the name stack but rather replaces the current name on top of the stack. Later, we discuss keeping an actual stack of names. For now, we just replace the top name of the name stack each time we draw an object (the sun or a particular planet).
As mentioned previously, OpenGL can operate in three different rendering modes. The default mode is GL_RENDER
, in which all the drawing actually occurs onscreen. To use selection, we must change the rendering mode to selection by calling the OpenGL function:
When we actually want to draw again, we use the following call to place OpenGL back in rendering mode:
glRenderMode(GL_RENDER);
The third rendering mode is GL_FEEDBACK
, discussed later in this chapter.
The naming code in Listing 12.1 has no effect unless we first switch the rendering mode to selection mode. Most often, you use the same function to render the scene in both GL_RENDER
mode and GL_SELECTION
mode, as we have done here.
Listing 12.2 provides the GLUT callback code triggered by the clicking of the left mouse button. This code checks for a left button click and then forwards the mouse coordinates to ProcessSelection
, which processes the mouse click for this example.
The selection buffer is filled with hit records during the rendering process. A hit record is generated whenever a primitive or collection of primitives is rendered that would have been contained in the viewing volume. Under normal conditions, this is simply anything that would have appeared onscreen.
The selection buffer is an array of unsigned integers, and each hit record occupies at least four elements of the array. The first array index contains the number of names that are on the name stack when the hit occurs. For the PLANETS example (Listing 12.1), this is always 1. The next two entries contain the minimum and maximum window z coordinates of all the vertices contained by the viewing volume since the last hit record. This value, which ranges from [0,1], is scaled to the maximum size of an unsigned integer for storage in the selection buffer. The fourth entry is the bottom of the name stack. If more than one name appears on the name stack (indicated by the first index element), they follow the fourth element. This pattern, illustrated in Figure 12.1, is then repeated for all the hit records contained in the selection buffer. We explain why this pattern can be useful when we discuss picking.
The format of the selection buffer gives you no way of knowing how many hit records you need to parse. The selection buffer is not actually filled until you switch the rendering mode back to GL_RENDER
. When you do this with the glRenderMode
function, the return value is the number of hit records copied.
Listing 12.3 shows the processing function called when a mouse click occurs for the PLANETS sample program. It shows the selection buffer being allocated and specified with glSelectBuffer
. This function takes two arguments: the length of the buffer and a pointer to the buffer itself.
Example 12.3. Function to Process the Mouse Click
/////////////////////////////////////////////////////////// // Process the selection, which is triggered by a right mouse // click at (xPos, yPos) #define BUFFER_LENGTH 64 void ProcessSelection(int xPos, int yPos) { GLfloat fAspect; // Space for selection buffer static GLuint selectBuff[BUFFER_LENGTH]; // Hit counter and viewport storage GLint hits, viewport[4]; // Setup selection buffer glSelectBuffer(BUFFER_LENGTH, selectBuff); // Get the viewport glGetIntegerv(GL_VIEWPORT, viewport); // Switch to projection and save the matrix glMatrixMode(GL_PROJECTION); glPushMatrix(); // Change render mode glRenderMode(GL_SELECT); // Establish new clipping volume to be unit cube around // mouse cursor point (xPos, yPos) and extending two pixels // in the vertical and horizontal direction glLoadIdentity(); gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); // Apply perspective matrix fAspect = (float)viewport[2] / (float)viewport[3]; gluPerspective(45.0f, fAspect, 1.0, 425.0); // Draw the scene RenderScene(); // Collect the hits hits = glRenderMode(GL_RENDER); // If a single hit occurred, display the info. if(hits == 1) ProcessPlanet(selectBuff[3]); // Restore the projection matrix glMatrixMode(GL_PROJECTION); glPopMatrix(); // Go back to modelview for normal rendering glMatrixMode(GL_MODELVIEW); }
Picking occurs when you use the mouse position to create and use a modified viewing volume during selection. When you create a smaller viewing volume positioned in your scene under the mouse position, only objects that would be drawn within that viewing volume generate hit records. By examining the selection buffer, you can then see which objects, if any, were clicked on by the mouse.
The gluPickMatrix
function is a handy utility that creates a matrix describing the new viewing volume:
void gluPickMatrix(GLdouble x, GLdouble y, GLdouble width, GLdouble height, GLint viewport[4]);
The x
and y
parameters are the center of the desired viewing volume in OpenGL window coordinates. You can plug in the mouse position here, and the viewing volume will be centered directly underneath the mouse. The width
and height
parameters then specify the dimensions of the viewing volume in window pixels. For clicks near an object, use a large value; for clicks next to the object or directly on the object, use a smaller value. The viewport
array contains the window coordinates of the currently defined viewport. You can easily obtain this information by calling
glGetIntegerv(GL_VIEWPORT, viewport);
Remember, as discussed in Chapter 2, “Using OpenGL,” that OpenGL window coordinates are the opposite of most systems' window coordinates with respect to the way pixels are counted vertically. Note in Listing 12.3, we subtract the mouse y coordinate from the viewport's height. This yields the proper vertical window coordinate for OpenGL:
gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport);
To use gluPickMatrix
, you should first save the current projection matrix state (thus saving the current viewing volume). Then call glLoadIdentity
to create a unit-viewing volume. Calling gluPickMatrix
then translates this viewing volume to the correct location. Finally, you must apply any further perspective projections you may have applied to your original scene; otherwise, you won't get a true mapping. Here's how it's done for the PLANETS example (from Listing 12.3):
// Switch to projection and save the matrix glMatrixMode(GL_PROJECTION); glPushMatrix(); // Change render mode glRenderMode(GL_SELECT); // Establish new clipping volume to be unit cube around // mouse cursor point (xPos, yPos) and extending two pixels // in the vertical and horizontal direction glLoadIdentity(); gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); // Apply perspective matrix fAspect = (float)viewport[2] / (float)viewport[3]; gluPerspective(45.0f, fAspect, 1.0, 425.0); // Draw the scene RenderScene(); // Collect the hits hits = glRenderMode(GL_RENDER);
In this segment, the viewing volume is saved first. Then the selection mode is entered, the viewing volume is modified to include only the area beneath the mouse cursor, and the scene is redrawn by calling RenderScene
. After the scene is rendered, we call glRenderMode
again to place OpenGL back into normal rendering mode and get a count of generated hit records.
In the next segment, if a hit occurred (for this example, there is either one hit or none), we pass the entry in the selection buffer that contains the name of the object selected or our ProcessPlanet
function. Finally, we restore the projection matrix (thus, the old viewing volume is restored) and switch the active matrix stack back to the modelview matrix, which is usually the default:
// If a single hit occurred, display the info. if(hits == 1) ProcessPlanet(selectBuff[3]); // Restore the projection matrix glMatrixMode(GL_PROJECTION); glPopMatrix(); // Go back to modelview for normal rendering glMatrixMode(GL_MODELVIEW);
The ProcessPlanet
function simply displays a message in the windows' caption telling which planet was clicked. This code is not shown because it is fairly trivial, consisting of no more than a switch statement and some glutSetWindowTitle
function calls.
The output from PLANETS is shown in Figure 12.2, where you can see the result of clicking the second planet from the sun.
Although we don't go into any great detail here, it is worth discussing briefly the z values from the selection buffer. In the PLANETS example, each object or model was distinct and off alone in its own space. What if you apply this same method to several objects or models that perhaps overlap? You get multiple hit records! How do you know which one the user clicked? This situation can be tricky and requires some forethought. You can use the z values to determine which object was closest to the user in viewspace, which is the most likely object that was clicked. Still, for some shapes and geometry, if you aren't careful, it can be difficult to sort out precisely what the user intended to pick.
For the PLANETS example, we didn't push any names on the stack, but rather just replaced the existing one whenever a new object was to be rendered. This single name residing on the name stack was the only name returned in the selection buffer. We can also get multiple names when a selection hit occurs, by placing more than one name on the name stack. This capability is useful, for instance, in drill-down situations when you need to know not only that a particular bolt was selected, but also that it belonged to a particular wheel, on a particular car, and so forth.
To demonstrate multiple names being returned on the name stack, we stick with the astronomy theme of our previous example. Figure 12.3 shows two planets (okay, so use a little imagination)—a large blue planet with a single moon and a smaller red planet with two moons.
Rather than just identify the planet or moon that is clicked, we want to also identify the planet that is associated with the particular moon. The code in Listing 12.4 shows our new rendering code for this scene. We push the names of the moons onto the name stack so that it contains the name of the planet as well as the name of the moon when selected.
Example 12.4. Rendering Code for the MOONS Sample Program
/////////////////////////////// // Define object names #define EARTH 1 #define MARS 2 #define MOON1 3 #define MOON2 4 /////////////////////////////////////////////////////////// // Called to draw scene void RenderScene(void) { // Clear the window with current clearing color glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Save the matrix state and do the rotations glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Translate the whole scene out and into view glTranslatef(0.0f, 0.0f, -300.0f); // Initialize the names stack glInitNames(); glPushName(0); // Draw the Earth glPushMatrix(); glColor3f(0.0f, 0.0f, 1.0f); glTranslatef(-100.0f,0.0f,0.0f); glLoadName(EARTH); DrawSphere(30.0f); // Draw the Moon glTranslatef(45.0f, 0.0f, 0.0f); glColor3f(0.85f, 0.85f, 0.85f); glPushName(MOON1); DrawSphere(5.0f); glPopName(); glPopMatrix(); // Draw Mars glPushMatrix(); glColor3f(1.0f, 0.0f, 0.0f); glTranslatef(100.0f, 0.0f, 0.0f); glLoadName(MARS); DrawSphere(20.0f); // Draw Moon1 glTranslatef(-40.0f, 40.0f, 0.0f); glColor3f(0.85f, 0.85f, 0.85f); glPushName(MOON1); DrawSphere(5.0f); glPopName(); // Draw Moon2 glTranslatef(0.0f, -80.0f, 0.0f); glPushName(MOON2); DrawSphere(5.0f); glPopName(); glPopMatrix(); // Restore the matrix state glPopMatrix(); // Modelview matrix glutSwapBuffers(); }
Now in our ProcessSelection
function, we still call the ProcessPlanet
function that we wrote, but this time, we pass the entire selection buffer:
// If a single hit occurred, display the info. if(hits == 1) ProcessPlanet(selectBuff);
Listing 12.5 shows the more substantial ProcessPlanet
function for this example. In this instance, the bottom name on the name stack is always the name of the planet because it was pushed on first. If a moon is clicked, it is also on the name stack. This function displays the name of the planet selected, and if it was a moon, that information is also displayed. A sample output is shown in Figure 12.4.
Example 12.5. Code That Parses the Selection Buffer for the MOONS Sample Program
/////////////////////////////////////////////////////////// // Parse the selection buffer to see which // planet/moon was selected void ProcessPlanet(GLuint *pSelectBuff) { int id,count; char cMessage[64]; strcpy(cMessage,"Error, no selection detected"); // How many names on the name stack count = pSelectBuff[0]; // Bottom of the name stack id = pSelectBuff[3]; // Select on earth or mars, whichever was picked switch(id) { case EARTH: strcpy(cMessage,"You clicked Earth."); // If there is another name on the name stack, // then it must be the moon that was selected // This is what was actually clicked on if(count == 2) strcat(cMessage," - Specifically the moon."); break; case MARS: strcpy(cMessage,"You clicked Mars."); // We know the name stack is only two deep. The precise // moon that was selected will be here. if(count == 2) { if(pSelectBuff[4] == MOON1) strcat(cMessage," - Specifically Moon #1."); else strcat(cMessage," - Specifically Moon #2."); } break; } // Display the message about planet and moon selection glutSetWindowTitle(cMessage); }
Feedback, like selection, is a rendering mode that does not produce output in the form of pixels on the screen. Instead, information is written to a feedback buffer indicating how the scene would have been rendered. This information includes transformed vertex data in window coordinates, color data resulting from lighting calculations, and texture data—essentially everything needed to rasterize the primitives.
You enter feedback mode the same way you enter selection mode, by calling glRenderMode
with a GL_FEEDBACK
argument. You must reset the rendering mode to GL_RENDER
to fill the feedback buffer and return to normal rendering mode.
The feedback buffer is an array of floating-point values specified with the glFeedback
function:
void glFeedbackBuffer(GLsizei size, GLenum type, GLfloat *buffer);
This function takes the size of the feedback buffer, the type and amount of drawing information wanted, and a pointer to the buffer itself.
Valid values for type
appear in Table 12.1. The type of data specifies how much data is placed in the feedback buffer for each vertex. Color data is represented by a single value in color index mode or four values for RGBA color mode.
The feedback buffer contains a list of tokens followed by vertex data and possibly color and texture data. You can parse for these tokens (see Table 12.2) to determine the types of primitives that would have been rendered. One limitation of feedback occurs when using multiple texture units. In this case, only texture coordinates from the first texture unit are returned.
The point, bitmap, and pixel tokens are followed by data for a single vertex and possibly color and texture data. This depends on the data type from Table 12.1 specified in the call to glFeedbackBuffer
. The line tokens return two sets of vertex data, and the polygon token is immediately followed by the number of vertices that follow. The user-defined marker (GL_PASS_THROUGH_TOKEN
) is followed by a single floating-point value that is user defined. Figure 12.5 shows an example of a feedback buffer's memory layout if a GL_3D
type were specified. Here, we see the data for a point, token, and polygon rendered in that order.
When your rendering code is executing, the feedback buffer is filled with tokens and vertex data as each primitive is specified. Just as you can in selection mode, you can flag certain primitives by naming them. In feedback mode, you can set markers between your primitives, as well. You do so by calling glPassThrough
:
void glPassThrough(GLfloat token);
This function places a GL_PASS_THROUGH_TOKEN
in the feedback buffer, followed by the value you specify when calling the function. This process is somewhat similar to naming primitives in selection mode. It's the only way of labeling objects in the feedback buffer.
An excellent use of feedback is to obtain window coordinate information regarding any objects that you render. You can then use this information to place controls or labels near the objects in the window or other windows around them.
To demonstrate feedback, we use selection to determine which of two objects on the screen has been clicked by the user. Then we enter feedback mode and render the scene again to obtain the vertex information in window coordinates. Using this data, we determine the minimum and maximum x and y values for the object and use those values to draw a focus rectangle around the object. The result is graphical selection and deselection of one or both objects.
Listing 12.6 shows the rendering code for our sample program, SELECT. Don't confuse this example with a demonstration of selection mode! Even though selection mode is employed in our example to select an object on the screen, we are demonstrating the process of getting enough information about that object—using feedback—to draw a rectangle around it using normal Windows coordinates GDI commands. Notice the use of glPassThrough
to label the objects in the feedback buffer, right after the calls to glLoadName
to label the objects in the selection buffer. Because these functions are ignored when the render mode is GL_RENDER
, they have an effect only when rendering for selection or feedback.
Example 12.6. Rendering Code for the SELECT Sample Program
/////////////////////////// // Object Names #define TORUS 1 #define SPHERE 2 /////////////////////////////////////////////////////////// // Render the torus and sphere void DrawObjects(void) { // Save the matrix state and do the rotations glMatrixMode(GL_MODELVIEW); glPushMatrix(); // Translate the whole scene out and into view glTranslatef(-0.75f, 0.0f, -2.5f); // Initialize the names stack glInitNames(); glPushName(0); // Set material color, Yellow // torus glColor3f(1.0f, 1.0f, 0.0f); glLoadName(TORUS); glPassThrough((GLfloat)TORUS); DrawTorus(40, 20); // Draw Sphere glColor3f(0.5f, 0.0f, 0.0f); glTranslatef(1.5f, 0.0f, 0.0f); glLoadName(SPHERE); glPassThrough((GLfloat)SPHERE); DrawSphere(0.5f); // Restore the matrix state glPopMatrix(); // Modelview matrix } /////////////////////////////////////////////////////////// // Called to draw scene void RenderScene(void) { // Clear the window with current clearing color glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Draw the objects in the scene DrawObjects(); // If something is selected, draw a bounding box around it if(selectedObject != 0) { int viewport[4]; // Get the viewport glGetIntegerv(GL_VIEWPORT, viewport); // Remap the viewing volume to match window coordinates (approximately) glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); // Establish clipping volume (left, right, bottom, top, near, far) glOrtho(viewport[0], viewport[2], viewport[3], viewport[1], -1, 1); glMatrixMode(GL_MODELVIEW); glDisable(GL_LIGHTING); glColor3f(1.0f, 0.0f, 0.0f); glBegin(GL_LINE_LOOP); glVertex2i(boundingRect.left, boundingRect.top); glVertex2i(boundingRect.left, boundingRect.bottom); glVertex2i(boundingRect.right, boundingRect.bottom); glVertex2i(boundingRect.right, boundingRect.top); glEnd(); glEnable(GL_LIGHTING); } glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glutSwapBuffers(); }
For this example, the rendering code is broken into two functions: RenderScene
and DrawObjects
. RenderScene
is our normal top-level rendering function, but we have moved the actual drawing of the objects that we may select to outside this function. The RenderScene
function draws the objects, but it also draws the bounding rectangle around an object if it is selected. selectedObject
is a variable we will use in a moment to indicate which object is currently selected.
Figure 12.6 shows the output from this rendering code, displaying a torus and sphere. When the user clicks one of the objects, the function ProcessSelection
is called (see Listing 12.7). This is similar to the selection code in the previous two examples (in Listings 12.3 and 12.5).
Example 12.7. Selection Processing for the SELECT Sample Program
/////////////////////////////////////////////////////////// // Process the selection, which is triggered by a right mouse // click at (xPos, yPos). #define BUFFER_LENGTH 64 void ProcessSelection(int xPos, int yPos) { // Space for selection buffer static GLuint selectBuff[BUFFER_LENGTH]; // Hit counter and viewport storage GLint hits, viewport[4]; // Setup selection buffer glSelectBuffer(BUFFER_LENGTH, selectBuff); // Get the viewport glGetIntegerv(GL_VIEWPORT, viewport); // Switch to projection and save the matrix glMatrixMode(GL_PROJECTION); glPushMatrix(); // Change render mode glRenderMode(GL_SELECT); // Establish new clipping volume to be unit cube around // mouse cursor point (xPos, yPos) and extending two pixels // in the vertical and horizontal direction glLoadIdentity(); gluPickMatrix(xPos, viewport[3] - yPos, 2,2, viewport); // Apply perspective matrix gluPerspective(60.0f, fAspect, 1.0, 425.0); // Draw the scene DrawObjects(); // Collect the hits hits = glRenderMode(GL_RENDER); // Restore the projection matrix glMatrixMode(GL_PROJECTION); glPopMatrix(); // Go back to modelview for normal rendering glMatrixMode(GL_MODELVIEW); // If a single hit occurred, display the info. if(hits == 1) { MakeSelection(selectBuff[3]); if(selectedObject == selectBuff[3]) selectedObject = 0; else selectedObject = selectBuff[3]; } glutPostRedisplay(); }
Now that we have determined which object was clicked (we saved this in the selectedObject
variable), we set up the feedback buffer and render again in feedback mode. Listing 12.8 shows the code that sets up feedback mode for this example and calls DrawObjects
to redraw just the torus and sphere scene. This time, however, the glPassThrough
functions put markers for the objects in the feedback buffer.
Example 12.8. Load and Parse the Feedback Buffer
/////////////////////////////////////////////////////////// // Go into feedback mode and draw a rectangle around the object #define FEED_BUFF_SIZE 32768 void MakeSelection(int nChoice) { // Space for the feedback buffer static GLfloat feedBackBuff[FEED_BUFF_SIZE]; // Storage for counters, etc. int size,i,j,count; // Initial minimum and maximum values boundingRect.right = boundingRect.bottom = -999999.0f; boundingRect.left = boundingRect.top = 999999.0f; // Set the feedback buffer glFeedbackBuffer(FEED_BUFF_SIZE,GL_2D, feedBackBuff); // Enter feedback mode glRenderMode(GL_FEEDBACK); // Redraw the scene DrawObjects(); // Leave feedback mode size = glRenderMode(GL_RENDER); // Parse the feedback buffer and get the // min and max X and Y window coordinates i = 0; while(i < size) { // Search for appropriate token if(feedBackBuff[i] == GL_PASS_THROUGH_TOKEN) if(feedBackBuff[i+1] == (GLfloat)nChoice) { i+= 2; // Loop until next token is reached while(i < size && feedBackBuff[i] != GL_PASS_THROUGH_TOKEN) { // Just get the polygons if(feedBackBuff[i] == GL_POLYGON_TOKEN) { // Get all the values for this polygon count = (int)feedBackBuff[++i]; // How many vertices i++; for(j = 0; j < count; j++) // Loop for each vertex { // Min and Max X if(feedBackBuff[i] > boundingRect.right) boundingRect.right = feedBackBuff[i]; if(feedBackBuff[i] < boundingRect.left) boundingRect.left = feedBackBuff[i]; i++; // Min and Max Y if(feedBackBuff[i] > boundingRect.bottom) boundingRect.bottom = feedBackBuff[i]; if(feedBackBuff[i] < boundingRect.top) boundingRect.top = feedBackBuff[i]; i++; } } else i++; // Get next index and keep looking } break; } i++; } }
When the feedback buffer is filled, we search it for GL_PASS_THROUGH_TOKEN
. When we find one, we get the next value and determine whether it is the one we are looking for. If so, the only task that remains is to loop through all the polygons for this object and get the minimum and maximum window x and y values. These values are stored in the boundingRect
structure and then used by the RenderScene
function to draw a focus rectangle around the selected object.
Selection and feedback are two powerful features of OpenGL that enable you to facilitate the user's active interaction with a scene. Selection and picking are used to identify an object or region of a scene in OpenGL coordinates rather than just window coordinates. Feedback returns valuable information about how an object or primitive is actually drawn in the window. You can use this information to implement features such as annotations or bounding boxes in your scene.
glInitNames | |
---|---|
Purpose: | |
Include File: |
|
Syntax: | |
void glInitNames(void ); | |
Description: | The name stack is used to allow drawing primitives or groups of primitives to be named with an unsigned integer when rendered in selection mode. Each time a primitive is named, its name is pushed on the name stack with |
Returns: | None. |
See Also: |
glPopName | |
---|---|
Purpose: | |
Include File: |
|
Syntax: | |
void glPopName(void); | |
Description: | The name stack is used during selection to identify drawing commands. This function removes a name from the top of the name stack. The current depth of the name stack can be retrieved by calling |
Returns: | None. |
See Also: |
|
glPushName | |
---|---|
Purpose: | |
Include File: |
|
Syntax: | |
void glPushName(GLuint name);
| |
Description: | The name stack is used during selection to identify drawing commands. This function pushes a name on the name stack to identify any subsequent drawing commands. The name stack's maximum depth can be retrieved by calling |
Parameters: | |
| |
Returns: | None. |
See Also: |
|