Chapter 12. Interactive Graphics

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

glInitNames/glPushName/glPopName

Use selection to determine which objects are under the mouse

glSelectBuffer/glRenderMode

Use feedback to get information about where objects are drawn

glFeedbackBuffer/gluPickMatrix

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

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.

Naming Your Primitives

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).

Working with Selection Mode

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:

glRenderMode(GL_SELECTION);

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.

Example 12.2. Code That Responds to the Left Mouse Button Click

///////////////////////////////////////////////////////////
// Process the mouse click
void MouseCallback(int button, int state, int x, int y)
    {
    if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
        ProcessSelection(x, y);
    }

The Selection Buffer

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.

Hit record for the selection buffer.

Figure 12.1. Hit record for the selection buffer.

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

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.

Output from PLANETS after clicking a planet.

Figure 12.2. Output from PLANETS after clicking a planet.

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.

Hierarchical Picking

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.

Two planets with their respective moons.

Figure 12.3. Two planets with their respective 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);
    }
Sample output from the MOONS sample program.

Figure 12.4. Sample output from the MOONS sample program.

Feedback

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

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.

Table 12.1. Feedback Buffer Types

Color Data

Vertex Texture Data

Total Values

Type

Coordinates

GL_2D

x, y

N/A

N/A

2

GL_3D

x, y, z

N/A

N/A

3

GL_3D_COLOR

x, y, z

C

N/A

3 + C

GL_3D_COLOR_TEXTURE

x, y, z

C

4

7 + C

GL_4D_COLOR_TEXTURE

x, y, z, w

C

4

8 + C

Feedback Data

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.

Table 12.2. Feedback Buffer Tokens

Token

Primitive

GL_POINT_TOKEN

Points

GL_LINE_TOKEN

Line

GL_LINE_RESET_TOKEN

Line segment when line stipple is reset

GL_POLYGON_TOKEN

Polygon

GL_BITMAP_TOKEN

Bitmap

GL_DRAW_PIXEL_TOKEN

Pixel rectangle drawn

GL_COPY_PIXEL_TOKEN

Pixel rectangle copied

GL_PASS_THROUGH_TOKEN

User-defined marker

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.

A sample memory layout for a feedback buffer.

Figure 12.5. A sample memory layout for a feedback buffer.

Passthrough Markers

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.

A Feedback Example

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.

Label the Objects for Feedback

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.

Step 1: Select the Object

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();
    }
Output from the SELECT program after the sphere has been clicked.

Figure 12.6. Output from the SELECT program after the sphere has been clicked.

Step 2: Get Feedback on the Object

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.

Summary

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.

Reference

glFeedbackBuffer

Purpose:

Sets the buffer to be used for feedback data.

Include File:

<gl.h>

Syntax:

void glFeedbackBuffer(GLsizei size, GLenum type, 
ReferenceGLfloat *buffer);

Description:

This function establishes the feedback buffer and type of vertex information desired. Feedback is a rendering mode; rather than render to the frame buffer, OpenGL sends vertex data to the buffer specified by *buffer. These blocks of data can include x, y, z, and w coordinate positions (in window coordinates); color data for color index mode or RGBA color mode; and texture coordinates. The amount and type of information desired are specified by the type argument.

Parameters:

size

GLsizeiThe maximum number of entries allocated for *buffer. If a block of data written to the feedback would overflow the amount of space allocated, only the part of the block that will fit in the buffer is written.

type

GLenumSpecifies the kind of vertex data to be returned in the feedback buffer. Each vertex generates a block of data in the feedback buffer. For each of the following types, the block of data contains a primitive token identifier followed by the vertex data. The vertex data specifically includes the following:

GL_2Dx and y coordinate pairs.

GL_3Dx, y, and z coordinate triplets.

  • GL_3D_COLORx, y, z coordinates and color data (one value for color index, four for RGBA).

  • GL_3D_COLOR_TEXTUREx, y, z coordinates; color data (one or four values); and four texture coordinates. If multiple texture units are employed, only coordinates from the first texture unit are returned.

  • GL_4D_COLOR_TEXTUREx, y, z, and w coordinates; color data (one or four values); and four texture coordinates.

buffer

GLfloat*Buffer where feedback data will be stored.

Returns:

None.

See Also:

glPassThrough, glRenderMode, glSelectBuffer

glInitNames

Purpose:

Initializes the name stack.

Include File:

<gl.h>

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 glPushName, or the current name is replaced with glLoadName. This function sets the name stack to its initial condition with no names on the stack.

Returns:

None.

See Also:

glInitNames, glPushName, glRenderMode, glSelectBuffer

glLoadName

Purpose:

Loads a name onto the name stack.

Include File:

<gl.h>

Syntax:

void glLoadName(GLuint name);

Description:

This function places the name specified in the top entry of the name stack. The name stack is used to name primitives or groups of primitives when rendered in selection mode. The current name on the name stack is actually replaced by the name specified with this function.

Parameters:

name

GLuintSpecifies the name to be placed on the name stack. Selection names are unsigned integers.

Returns:

None.

See Also:

glInitNames, glPushName, glRenderMode, glSelectBuffer

glPassThrough

Purpose:

Places a marker in the feedback buffer.

Include File:

<gl.h>

Syntax:

void glPassThrough(GLfloat token);

Description:

When OpenGL is placed in feedback mode, no pixels are drawn to the frame buffer. Instead, information about the drawing primitives is placed in a feedback buffer. This function allows you to place the token GL_PASS_THROUGH_TOKEN in the midst of the feedback buffer data, which is followed by the floating-point value specified by token. This function is called in your rendering code and has no effect unless in feedback mode.

Parameters:

token

GLfloatA value to be placed in the feedback buffer following the GL_PASS_THROUGH_TOKEN.

Returns:

None.

See Also:

glFeedbackBuffer, glRenderMode

glPopName

Purpose:

Pops (removes) the top entry from the name stack.

Include File:

<gl.h>

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 glGet with GL_NAME_STACK_DEPTH. Popping off an empty name stack generates an OpenGL error (see glGetError).

Returns:

None.

See Also:

glInitNames, glLoadName, glRenderMode, glSelectBuffer, glPushName

glPushName

Purpose:

Specifies a name that will be pushed on the name stack.

Include File:

<gl.h>

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 glGet with GL_MAX_NAME_STACK_DEPTH and the current depth by calling glGet with GL_NAME_STACK_DEPTH. The maximum depth of the name stack can vary with implementation, but all implementations must support at least 64 entries. Pushing past the end of the name stack generates an OpenGL error and will most likely be ignored by the implementation.

Parameters:

name

GLuintThe name to be pushed onto the name stack.

Returns:

None.

See Also:

glInitNames, glLoadName, glRenderMode, glSelectBuffer, glPopName

glRenderMode

Purpose:

Sets one of three rasterization modes.

Include File:

<gl.h>

Syntax:

GLint glRenderMode(GLenum mode);

Description:

OpenGL operates in three modes when you call your drawing functions:

GL_RENDERRender mode (the default). Drawing functions result in pixels in the frame buffer.

GL_SELECTSelection mode. No changes to the frame buffer are made. Rather, hit records written to the selection buffer record primitives that would have been drawn within the viewing volume. The selection buffer must be allocated and specified first with a call to glSelectBuffer.

GL_FEEDBACKFeedback mode. No changes to the frame buffer are made. Instead, coordinates and attributes of vertices that would have been rendered in render mode are written to a feedback buffer. The feedback buffer must be allocated and specified first with a call to glFeedbackBuffer.

Parameters:

mode

GLenumSpecifies the rasterization mode. May be any one of GL_RENDER, GL_SELECT, or GL_FEEDBACK. The default value is GL_RENDER.

Returns:

The return value depends on the rasterization mode that was set the last time this function was called:

GL_RENDERZero.

GL_SELECTThe number of hit records written to the selection buffer.

GL_FEEDBACKThe number of values written to the feedback buffer. Note that this is not the same as the number of vertices written.

See Also:

glFeedbackBuffer, glInitNames, glLoadName, glPassThrough, glPushName, glSelectBuffer

glSelectBuffer

Purpose:

Sets the buffer to be used for selection values.

Include File:

<gl.h>

Syntax:

void glSelectBuffer(GLsizei size, GLuint *buffer);

Description:

When OpenGL is in selection mode (GL_SELECT), drawing commands do not produce pixels in the frame buffer. Instead, they produce hit records that are written to the selection buffer that is established by this function. Each hit record consists of the following data:

  • The number of names on the name stack when the hit occurred.

  • The minimum and maximum z values of all the vertices of the primitives that intersected the viewing volume. This value is scaled to range from 0.0 to 1.0.

  • The contents of the name stack at the time of the hit, starting with the bottom element.

Parameters:

size

GLsizeThe number of values that can be written into the buffer established by *buffer.

buffer

GLuint*A pointer to memory that will contain the selection hit records.

Returns:

None.

See Also:

glFeedbackBuffer, glInitNames, glLoadName, glPushName, glRenderMode

gluPickMatrix

Purpose:

Defines a picking region that can be used to identify user selections.

Include File:

<glu.h>

Syntax:

void gluPickMatrix(GLdouble x, GLdouble y, 
GLuint*:GLdouble width, GLdouble height, GLint viewport[4]);

Description:

This function creates a matrix that will define a smaller viewing volume based on screen coordinates for the purpose of selection. By using the mouse coordinates with this function in selection mode, you can determine which of your objects are under or near the mouse cursor. The matrix created is multiplied by the current projection matrix. Typically, you should call glLoadIdentity before calling this function and then multiply the perspective matrix that you used to create the viewing volume in the first place. If you are using gluPickMatrix to pick NURBS surfaces, you must turn off the NURBS property GLU_AUTO_LOAD_MATRIX before using this function.

Parameters:

x, y

GLdoubleThe center of the picking region in window coordinates.

width, height

GLdoubleThe width and height of the desired picking region in window coordinates.

viewport

GLint[4]The current viewport. You can get the current viewport by calling glGetIntegerv with GL_VIEWPORT.

Returns:

None.

See Also:

glGet, glLoadIdentity, glMultMatrix, glRenderMode, gluPerspective

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset