Open GL Super Bible

Previous Table of Contents Next


Chapter 19
Interactive Graphics

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) will require more interaction with the scene itself. In addition to the menu and dialog boxes, you’ll need to provide a way for the user to interact with a graphical scene. Under Windows, this is usually done with a mouse.

Selection, a very 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 Open GL’s selection feature, you can specify a viewing volume and determine which objects fall within that viewing volume. A powerful utility function 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 framebuffer. You can use this information to transmit rendering results over a network, send them to a plotter, or add GDI graphics 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 very similar and they work productively together. You’ll see this teamwork later in a specific example.

Selection

Selection is actually a rendering mode, but in selection mode no pixels are actually copied to the framebuffer. Instead, primitives that are drawn within the viewing volume (and thus would normally appear in the framebuffer) produce “hit” records in a selection buffer.

You must set up this selection buffer in advance, and name your primitives or groups of primitives (your objects) so they can be identified in the selection buffer. You then parse the selection buffer to determine which objects intersected the viewing volume. This has marginal value unless you modify the viewing volume before entering selection mode and calling your drawing code to determine which objects are in some restricted area of your scene. In one common scenario, you specify a viewing volume that corresponds to the mouse pointer, and then check to see which named objects the mouse is pointing to.

Naming Your Primitives

You can name every single primitive used to render your scene of objects, but this is rarely useful. More often you will 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 on the names stack are copied into the selection buffer. Thus, a single hit can return more than one name if needed.

For our first example, we’ll keep things simple. We’ll create a simplified (and not to scale) model of the inner planets of the solar system. When the left mouse button is down, we’ll display a message box describing which planet was clicked on. Listing 19-1 shows some of the rendering code for our example program, PLANETS. We have created macro definitions for the Sun, Mercury, Venus, Earth, and Mars.

Listing 19-1 Naming the Sun and planets in the PLANETS program

#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);

        // Set material color, Yellow
        // Sun
        glRGB(255, 255, 0);
        glLoadName(SUN);
        auxSolidSphere(15.0f);

        // Draw Mercury
        glRGB(128,0,0);
        glPushMatrix();
        glTranslatef(24.0f, 0.0f, 0.0f);
        glLoadName(MERCURY);
        auxSolidSphere(2.0f);
        glPopMatrix();

        // Draw Venus
        glPushMatrix();
        glRGB(128,128,255);
        glTranslatef(60.0f, 0.0f, 0.0f);
        glLoadName(VENUS);
        auxSolidSphere(4.0f);
        glPopMatrix();

        ...
        ...     Other planets
        ...

        // Restore the matrix state
        glPopMatrix(); // Modelview matrix

        // Flush drawing commands
        glFlush();
        }

In PLANETS, the glInitNames function initializes and clears the names 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’ll 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).


Previous Table of Contents Next