[ Previous | Next | Contents | Glossary | Home | Search ]
GL3.2 Version 4.1 for AIX: Programming Concepts

Picking

This section discusses the following aspects of picking:

Picking Introduction

Use picking to identify the figures (drawing primitives) on the screen that appear near the cursor. To use picking, your software must be structured so that you can regenerate the picture on the screen whenever picking is required. When it is, set the system into picking mode with the pick subroutine, redraw the image on the screen, and finish by calling the endpick subroutine. Data recorded during the pick appears in the buffer specified by the pick and endpick subroutines.

When the system is in picking mode, it does not draw anything to the screen. Instead, it checks for hits. A hit occurs every time a drawing primitive intersects the picking region. The picking region is a rectangular area of the screen, centered about the location of the cursor. By default, it is 10 by 10 pixels in size. The size of the picking region can be controlled with the picksize subroutine.

With one exception, all the standard drawing routines cause hits, including those for points, lines, polygons, arcs, circles, curves, and patches. Raster objects, such as character strings and pixels drawn with the charstr subroutine, do not cause hits, but the cmov subroutine does. Thus, to pick the string, the cursor must be near the lower left corner of the string. Note also that because the readpixels and readRGB subroutines are often preceded by a call to the cmov subroutine, these routines can appear to cause hits. The following figure illustrates the picking process.

In picking mode, you can identify the parts of an image that lie near the cursor. The cursor is shown as an arrow. The small box at the tip of the arrow is the picking region. The large shaded circle is picked. The text string whose origin is in the picking region is also picked. The shaded triangle and the other text string are not picked.

Recording Hits

The system records hits by writing data into the picking buffer. The actual data that is recorded is the entire contents of the name stack, preceded by the size of the name stack. The name stack is a stack of 16-bit integers (here referred to as names, not to be confused with the actual GL names of the routines that cause hits).

The application (your program) has complete control over the name stack. Names can be loaded onto the stack; pushed onto the stack; popped off the stack with the loadname, pushname, or popname subroutine; or initialized with the initnames subroutine.

Note that the actual ASCII name of the drawing routine (for example, arc) that caused the hit is not recorded. Rather, you must add 16-bit integer names to the name stack or delete them from the name stack to receive interpretable data back when picking is completed.

It is very important to realize that not every hit is recorded. A hit is recorded only if the name stack has been changed since the last hit. The three name stack subroutines, loadname, pushname, and popname, all touch the name stack. Thus, multiple hits can occur, but only one gets recorded into the buffer if the name stack never changes.

For example, suppose your application draws three points widely spaced on the screen, and you want to find which one is close to the cursor using picking mode. Your point-drawing code (that is executed both to draw points and to redraw them in a picking operation), might look like this:

ortho(<ortho parameters>
);
lookat (<lookat parameters>
);
translate (-x, -y, -z);
rotate(30, 'y');
translate(x, y, z);
loadname(0);
pnt(<point 0>
);
loadname(1);
pnt(<point 1>
);
loadname(2);
pnt(<point 2>
);

Note that the complete specification for drawing the picture must be there, including any viewing and transformation routines. When this code segment is executed in picking mode and the cursor is near point 1, the buffer returned after the endpick subroutine would contain the name 1. If the cursor is near point 2, the buffer would contain the name 2. If the cursor is not near any of the points, an empty buffer would be returned.

The stack is intended for used with hierarchical drawings. For example, suppose you want to draw a car with four instances of a wheel, with each wheel having five instances of a bolt, and you want to pick an individual bolt from the picture. You might have one piece of code to draw each wheel that contained the sequence:

pushname(0);
<draw bolt 0>

popname();
pushname(1);
<draw bolt 1>

popname();
   .
   .
   .

The car-drawing code might look like this:

loadname(0);
<translate>

<draw wheel>

loadname(1);
<translate>

<draw wheel>

   .
   .
   .

Each hit on a bolt would occur with the name stack containing two names, the first of which is the wheel number and the second of which is the bolt number on that wheel. Deeper nesting of the hierarchy is possible.

The names reported on hits are completely application dependent. Many drawing routines can occur between changes to the name stack. If any of those routines cause a hit, the contents of the name stack is reported.

Because the contents of the name stack is reported only when it changes, one hit is reported no matter how many of the drawing routines actually draw something near the cursor. If more accuracy than this is required by the application, it must touch the name stack more often. In the code fragment that follows, if all three points caused hits, three identical name stacks are reported:

loadname(1);
pnt(-);
loadname(1);
pnt(-);
loadname(1);
pnt(-);

pick Subroutine

The pick subroutine puts the system in picking mode. The bufferlen parameter specifies the length of the buffer array. It should be less than or equal to the size of the buffer as measured in 16-bit short integers. Graphical items that intersect the picking region cause hits. If the name stack has changed since the last hit, the length and contents of the name stack are recorded in the buffer. The syntax is as follows:

void pick(Int16 buffer, Int32 bufferlen)

Using the Name Stack

You maintain the name stack with the loadname, pushname, popname, and initnames subroutines. Each name in the name stack is 16 bits long. You can store up to 1000 names in the name stack. You can intersperse these routines with drawing routines, or you can insert them into display lists.

loadname Subroutine

The loadname subroutine puts a new name at the top of the name stack and erases what was there before. The syntax is as follows:

void loadname(Int16 name)

pushname Subroutine

The pushname subroutine puts a new name at the top of the stack and pushes all the other names in the stack one level lower. The syntax is as follows:

void pushname(Int16 name)

Before the first loadname subroutine is called, the current name is unpredictable. Calling the pushname subroutine before calling the loadname subroutine can cause unpredictable results.

popname Subroutine

The popname subroutine discards the name at the top of the stack and moves all the other names up one level. The syntax is as follows:

void popname()

initnames Subroutine

The initnames discards all the names in the stack and leaves the stack empty. The syntax is as follows:

void initnames()

endpick Subroutine

The endpick subroutine takes the system out of picking mode and returns the number of times the name stack was dumped into the buffer. If this number is positive, the buffer was large enough to contain all of the name stacks written to it. If the number is negative, the buffer was too small to store all the name lists. The magnitude of the returned number is the number of name stacks that were recorded. The syntax is as follows:

Int32 endpick(Int16 buffer[])

The buffer parameter contains copies of the name stack that were recorded as hits occurred. As explained previously, not every hit causes the name stack to be recorded; only the first hit after the name stack has been touched is recorded. When stored in the buffer, each name stack is preceded by the length of the name stack. If the name stack is empty when a hit occurs, the length is recorded as 0 (zero), and the stack is not recorded.

Defining the Picking Region

Picking loads a projection matrix that makes the picking region fill the entire viewport. This picking matrix replaces the projection transformation matrix that is normally used when drawing routines are called. Therefore, you must restate the original projection transformation after the pick subroutine to ensure that the system maps the objects to be picked to the proper coordinates.

If no projection transformation was originally issued, you must specify the default subroutine, ortho2. When the transformation routine is restated, the product of the transformation matrix and the picking matrix is placed at the top of the matrix stack. If you do not restate the projection transformation, picking does not work properly. Instead, the system typically picks every object, regardless of cursor position and picksize.

picksize Subroutine

The default height and width of the picking region is 10 pixels, centered at the cursor. You can change the picking region with the picksize subroutine. The deltax and deltay parameters specify a rectangle centered at the current cursor position (the origin of the cursor glyph). (See "Creating a Cursor" for a discussion of cursors.) The syntax is as follows:

void picksize(Int16 deltax, Int16 deltay)

Picking Example Program

The following example program draws an object consisting of three shapes; then it loops, until the right mouse button is pressed. Each time the middle mouse button is pressed, the graphics system:

  1. Enters pick mode.
  2. Calls the object.
  3. Records hits for any routines that draw into the picking region.
  4. Prints out the contents of the picking buffer.
    Note: When you call an object in picking mode, the screen does not change. Because the picking matrix is recalculated only when pick is called, the system exits and reenters picking mode to obtain new cursor positions.

When the program is run, there are five possible outcomes for each picking session (the circles can be picked together because they overlap):

#include <gl/gl.h>

#include <gl/device.h>
#define BUFSIZE 50
void
drawit()
{
   color(RED);
   loadname(1);
   rectfi(20,20,100,100);
   loadname(2);
   pushname(21);
   circi(50, 500, 50);
   popname();
   pushname(22);
   circi(50, 530, 60);
   popname();
}
int
main()
{
short dev, val;
short buffer[BUFSIZE];
int hits;
int xsize, ysize;
int i;    
   prefsize(600, 600);
   (void) winopen("pick");
   getsize(&xsize, &ysize);
   color(BLACK);
   clear();
   qdevice(LEFTMOUSE);
   qdevice(ESCKEY);
   for (i = 0;  i < BUFSIZE;  i++)  buffer[i] = 0;
    
   drawit();
   while (1)  {
   dev = qread(&val);
   switch (dev)  {
      case LEFTMOUSE:
      if (val == 0)  break;
      pick(buffer, BUFSIZE);
      ortho2(-0.5, xsize + 0.5, -0.5, ysize + 0.5);
      drawit(); /* no actual drawing takes place */
      hits = endpick(buffer);
      /* display hit information */
      {
      int index, items, h, i;
      printf("hit count: %d   hits:  ", hits);
      index = 0;
      for (h = 0;  h < hits;  h++)  {
         items = buffer[index++];
         printf("(");
         for (i = 0;  i < items;  i++)  {
            if (i != 0)  printf(" ");
            printf("%d", buffer[index++]);
            }
         printf(") ");
         }
      printf("\n");
      }
      break;
      case ESCKEY: /* exit program */
      return 0;
      break;
      }
   }
}

Pick Matrix

The pick and endpick subroutines create the following matrix and load it as the current matrix. This formula is placed here for information only. It is not necessary to understand its meaning to use it successfully.


[ Previous | Next | Contents | Glossary | Home | Search ]