Home Documentation Download Screenshots Developer

The select example

select

Selection of objects of the scene using select() and an OpenGL GL_SELECT render mode.

Use the select() callback function to implement your object selection function. This examples is based on a generic GL_SELECT implementation that can easily be cut and pasted in your applications.

Analytic intersection computations are also possible once the screen coordinates have be converted to a half line using convertClickToLine(). Make a selection and then move the camera to see a representation of the intersection line.

select.h

#include "qglviewer.h"

class Viewer : public QGLViewer
{  
protected :
  void draw();
  void mousePressEvent(QMouseEvent *e);
  void select(QMouseEvent*);
  QString helpString() const;
  void init();

private :
  qglviewer::Vec orig, dir, selectedPoint;
};

select.cpp

#include "select.h"
#include <math.h>

using namespace std;

// The id of the selected object. Should be encapsulated.
// -1 means no object is selected.
static int selected;

static void drawSpiral(const bool specialColor = false)
{
  const float nbSteps = 100.0;
  glBegin(GL_QUAD_STRIP);
  for (float i=0; i<nbSteps; ++i)
    {
      if (specialColor)
	glColor3f((nbSteps-i)/nbSteps, .8 , i/nbSteps/2.0);
      else
	glColor3f((nbSteps-i)/nbSteps, .2 , i/nbSteps);
      float angle = i/4.0;
      float c = cos(angle);
      float s = sin(angle);
      float r1 = 0.5 - i/(3.f*nbSteps);
      float r2 = 0.3 - i/(3.f*nbSteps);
      float alt = i/nbSteps - 0.5;
      const float nor = .5;
      const float up = sqrt(1.0-nor*nor);
      glNormal3f(nor*c, nor*s, up);
      glVertex3f(r1*c, r1*s, alt);
      glVertex3f(r2*c, r2*s, alt+0.05);
    }
  glEnd();
}

static void drawScene(bool pushId = false)
{
  // Draw the scene, with a possible pushName for selection

  // Consider using several stack levels for different objects, or to separate
  // the triangles, edges and vertices of the same object. Example :
  // glPushName(0)
  //   for all triangles i, glPushName(i), draw triangle, glPopName()
  // glPopName()
  //
  // glPushName(1)
  //   for all edges i, glPushName(i), draw edge, glPopName()
  // glPopName()
  //
  // glPushName(2)
  //   for all vertex i, glPushName(i), draw vertex, glPopName()
  // glPopName()
  // As a result, you have a two level stack, with a type id (0,1 or 2 here)
  // which indicates the type of the primitive, and then the id of the primitive.
  // See the man page of glSelectBuffer() for details.
  
  const int nb = 10;
  for (int i=0; i<nb; ++i)
    {
      glPushMatrix();
      
      glTranslatef(cos(2.0*i*M_PI/nb), sin(2.0*i*M_PI/nb), 0.);
      
      if (pushId)
	{
	  glPushName(i);
	  drawSpiral();
	  glPopName();
	}
      else
	drawSpiral(i==selected);
	  
      glPopMatrix();
    }
}

void Viewer::init()
{
  restoreFromFile();

  // Means no object is selected.
  selected = -1;

  glLineWidth(3.0);
  glPointSize(10.0);

  help();
}

void Viewer::draw()
{
  drawScene();

  // Draw the previous intersection line
  glBegin(GL_LINES);
  glVertex3fv(orig.address());
  glVertex3fv((orig + 100.0*dir).address());
  glEnd();

  if (selected >= 0)
    {
      glColor3f(.9, .2, .1);
      glBegin(GL_POINTS);
      glVertex3fv(selectedPoint.address());
      glEnd();
    }
}


void Viewer::mousePressEvent(QMouseEvent *e)
{
  if (((e->state() & ~Qt::MouseButtonMask) == Qt::ShiftButton) && (e->button() == Qt::LeftButton))
    {
      select(e);
      updateGL();
    }
  else
    QGLViewer::mousePressEvent(e);
}

void Viewer::select(QMouseEvent* e)
{
  // Make openGL context current
  makeCurrent();
  
  const int SENSITIVITY = 4;
  const int NB_HITS_MAX = 50;

  
  // Prepare the selection mode
  static GLuint hits[NB_HITS_MAX];
  
  glSelectBuffer(NB_HITS_MAX, hits);
  glRenderMode(GL_SELECT);
  glInitNames();

  // Loads the matrices
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  GLint viewport[4];
  glGetIntegerv(GL_VIEWPORT,viewport);
  gluPickMatrix(static_cast<GLdouble>(e->x()), static_cast<GLdouble>(viewport[3] - e->y()), SENSITIVITY, SENSITIVITY, viewport);

  // Don't use loadProjectionMatrix() directly as it clears the GL_PROJECTION matrix with a glLoadIdentity.
  // The false flag indicates that no glLoadIdentity should be called, in order to combine the matrices.
  camera()->loadProjectionMatrix(false);

  camera()->loadModelViewMatrix();
  
  // Render scene with objects ids
  drawScene(true);
  glFlush();

  // Get the results
  GLint nb_hits = glRenderMode(GL_RENDER);
  
  // Interpret results
  unsigned int zMin = UINT_MAX;
  selected = -1;
  for (int i=0; i<nb_hits; ++i)
    if (hits[i*4+1] < zMin)
      {
	zMin = hits[i*4+1];
	selected = hits[i*4+3];
      }
  
  cout << nb_hits << " spiral" << ((nb_hits>1)?"s":"") << " under the cursor";
  if (selected >= 0)
    cout << ", selected = " << selected;
  cout << endl;

  // To draw a representation of the intersecting line
  camera()->convertClickToLine(e->x(), e->y(), orig, dir);
  cout << "e->x() = " << e->x() << endl;
  cout << "e->y() = " << e->y() << endl;
  
  bool found;
  selectedPoint = camera()->pointUnderPixel(e->x(), e->y(), found);
  selectedPoint -= 0.01*dir;  
  // if (found != (selected>=0)), it is because of SENSITIVITY.
}

QString Viewer::helpString() const
{
  QString text("<h2>S e l e c t</h2>");
  text += "Left click while pressing the <b>Shift</b> key to select an object of the scene.<br>";
  text += "Selection is performed using the OpenGL <i>GL_SELECT</i> render mode. ";
  text += "A line is drawn between the selected point and the camera selection position. ";
  text += "using <i>convertClickToLine()</i>, a useful function for analytical intersections.<br>";
  text += "Feel free to cut and paste this implementation in your own applications.";
  return text;
}

main.cpp

#include "select.h"
#include <qapplication.h>

int main(int argc, char** argv)
{
  // Read command lines arguments.
  QApplication application(argc,argv);

  // Instantiate the viewer.
  Viewer v;

  // Make the viewer window visible on screen.
  v.show();

  // Set the viewer as the application main widget.
  application.setMainWidget(&v);
  
  // Run main loop.
  return application.exec();
}

Back to the main page

Valid XHTML 1.0! Valid CSS!