========================================
Using the OpenGL Selection Buffer
Ryan Schmidt
ryansc@cpsc.ucalgary.ca
CopyLeft 2000 - no rights restricted
========================================


This file explains how to do selection of OpenGL objects using the mouse in a QT widget. If you find any bugs, let me know. -RMS

=====================
Doing Selection in QT
=====================


To do GL selection with QT we need to know when the mouse is clicked on the viewport. You do this by over-riding the void QTWidget::mousePressEvent(QMouseEvent *event) method. The QMouseEvent object has methods to find out the x and y coordinates of a click (x() and y()) and to find out what button is clicked (eg event->button() & RightButton). You'll need to know the x and y coordinates to do the selection, so keep them in globals or otherwise available somehow. Your mousePressEvent method should probably look like a little state machine that calls the selection code if all the conditions are right. This part is pretty straightforward.

======================
Basic Selection - Code
======================

(explanation of this code follows. Note that this won't directly compile, you have to add some stuff.
The explanation of each numbered block follows...)


// [0x0]
GLuint select_buf[SELECT_BUFSIZE]; //selection buffer
GLuint hits; //number of hits

// [0x1]
glSelectBuffer(SELECT_BUFSIZE, select_buf);
glRenderMode(GL_SELECT);
glInitNames();
glPushName(0);

// [0x2]
//set up view matrix here - ie do whatever transformations
setupView();
glLoadIdentity();

// [0x3]
for(int i = 0; i < numpoints; i++){
  glLoadName(i);
  drawPoint(i);
}

// [0x4]
glFlush(); //flush the pipeline
hits = glRenderMode(GL_RENDER); //switch back to RENDER mode

// [0x5]
GLuint hitnames[10];
unsigned int hi = 0;
GLuint *bufp = selectbuffer;
GLuint name, numnames, z1, z2;

// [0x6]
for(unsigned int j = 0; j < hits; j++){

  numnames = *bufp++;
  z1 = *bufp++;
  z2 = *bufp++;
  while(numnames--){
    name = *bufp++;
    hitnames[hi++] = name;
  }
}

=============================
Basic Selection - Explanation
=============================

0x0
=====


OpenGL fills select_buf with hit information. The structure of the data is variable-length records, and the number of records will be returned into hits. I'll explain these when we use them. BTW, SELECT_BUFSIZE is a constant you can define elsewhere.

0x1
=====


Here we do some basic setup. First we tell OpenGL where the buffer it should fill with hit information is, by calling glSelectBuffer(SELECT_BUFSIZE, select_buf). Next we switch from RENDER rendering mode to SELECT rendering mode with glRenderMode(GL_SELECT). In SELECT rendering mode, nothing we draw will show up on the screen. Instead the vertex information used to fill the selection buffer, select_buf. Next we have to initialize the name stack by calling glInitNames() and glPushName(0).

OpenGL uses the name stack to differentiate between the different objects in the scene. Hits are discovered on a per-vertex basis, OpenGL doesn't know that the next 8 vertices you draw are part of a cube. GL uses the unsigned integer at the top of the Name stack as the name of the current object. So for each new object we start drawing, we push a new name on the stack. THE NAMES HAVE TO BE UNIQUE OR WE WON'T BE ABLE TO TELL WHAT WAS HIT LATER ON! (that's important...trust me)

0x2
=====


Here we have to set up our view of the scene. Basically, this means you have to do all the transformations you did to the currently drawn scene before you started drawing it. Otherwise the objects you are trying to hit are going to be in different positions internally then they are on the screen. The call to setupView() is something you have to fill in that does this. There is an explanation of what should happen here later in this file. The call to glLoadIdentity may not be necessary in your program, depending on whether or not you do any transformations to the MODELVIEW matrix before you start drawing your objects. If you do, leave that call out!

0x3
=====


Here we draw our objects, giving each one of them a name. You'll probably have to change this for your program, but what you need to do here should be clear. In our curve-drawing program, we probably only want to be selecting points. If you only have one curve, this all becomes really easy. Just give each point in the curve a number. Use that number as the name, ie pass it as the argument to glLoadName(). Then draw that point. If you have more than one curve you'll need some sort of encoding scheme for names, I'll give an example for that later. An important point to make here is that the GL name stack has a depth limit, at minimum it can hold 64 names. I'll also give an example about how to get around this later.

0x4
=====


Now that we've drawn all our objects, we can figure out which ones were hit. First we glFlush() the pipeline so all our commands are actually carried out. Then we change the rendering mode back to GL_RENDER. This call returns the number of hits that occurred.

0x5
=====


Here we create some variables to hold hit information. We make an array hitnames for the names of objects that were clicked on. I'll assume that there are 10 max, but you could dynamically size this array. Note that the hits value returned from glRenderMode() is _NOT_ the same as the number of names hit! This is explained below. We index this array with hi. The pointer bufp points to the selection buffer, which contains all the selection/hit information. Finally, the last four GLuint's are fields from the selection buffer.

0x6
====


Now we're going to examine the selection buffer and figure out what objects were hit. The selection buffer is full of variable-length records that look like this:

  GLuint number_of_names_in_this_hit
  GLuint z_buffer_1
  GLuint z_buffer_2
  GLuint name1
  GLuint name2
  ...
  GLuint nameN


So the first field is the number of names in the current hit. You might wonder how you can have more then one name per hit - think about what happens if two points are at the same spot, or if one is 'behind' another. The two z-buffer values define the closest and furthest z-values for the names, you probably don't need these. Finally we have a list of the names that were hit. The code there is kind of cryptic pointer arithmetic, but it does what we need. After this runs the hitnames array will contain the unsigned integer names of any objects that were clicked on.

Now that you know which points were clicked, you have to pick one to be 'selected'. The easiest way is to just select the first one returned. You could also keep track of the current selection, and cycle through the possibilities on each mouse click. Your point data structure will probably need a 'selected' flag so you can draw selected points differently from normal points.

==============================================
Setting up the PROJECTION matrix for selection
==============================================

The way selection works is we define some clipping boundaries in our viewport, then draw all our objects. Objects outside of these boundaries are discarded, objects inside are added to the selection buffer as 'hits'. We use the name stack to differentiate between objects. You can define these clipping planes yourself, but GL already has calls to do it for you. This has to be done before you create your PROJECTION matrix. Right now the code in our setupView() function looks something like this:

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  //create PROJECTION matrix
  gluPerspective(...);
  ...
  //create MODELVIEW matrix
  glMatrixMode(GL_MODELVIEW);
  //apply view-changing transformations here
  ...

If we are picking, we need to add a step before we create the PROJECTION matrix:

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();

  //new code starts here
  GLint viewport[4];
  glGetIntegerv(GL_VIEWPORT, viewport);
  gluPickMatrix((GLdouble)mousedownx,
                (GLDouble)(viewport[3]-mousedowny),
                PICK_REGION_SIZE, PICK_REGION_SIZE,
                viewport);

  //create PROJECTION matrix
  gluPerspective(...);
  ...
  //create MODELVIEW matrix
  glMatrixMode(GL_MODELVIEW);
  //apply view-changing transformations here
  ...

The important bit here is the gluPickMatrix function. This creates the clip boundaries so that when you draw the objects in SELECTION mode, only the ones within the picking region are hit. PICK_REGION_SIZE is a float you need to define that determines how big the hit region is. You might need to do all this more then once if you are picking from objects of non-uniform size. The hit region is essentially a rectangle that stretches from +infin to -infin in the Z plane. The values mousedownx and mousedowny need to be the coordinates of a mouse click in the viewport, you should have these saved somehow from your mousePressEvent method.

==================
Advanced Selection
==================

Here are a few things you might have to deal with...

>64 Names
==========


If you need more then 64 names, you have to loop through the selection code more then once. Basically, you want an algorithm that will let you iterate through all your points, touching each point once. Then you loop through the selection rendering code (numpoints mod 64) times. If you don't do this, only the first 64 objects will ever be selectable. If the name stack is full, any new pushed names are discarded.

More then one Curve
===================


If you have more then one curve, you might have some trouble with point names. You can try to figure out some way to index all your points, or you can number your curves and encode the curve number into the object name. An easy way to do this is to set the name as

  glLoadName(( (curvenum) << 16) + pointnum );

then decode it as

  curvenum = name >> 16; //index of curve that was hit
  pointnum = name & 0xFFFF; //nth point

This limits a curve to 65536 points, which should be enough. if not you could give more bits to the point number...



Comments? Suggestions? Flames? Flounder? let me know: ryansc@cpsc.ucalgary.ca

Back to my graphics page...