Home Documentation Download Screenshots Developper

select

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*);
  void init();
  void help();

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

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();
}

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()
{
  // 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 + 10*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 = 1000;
  
  // 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.
  loadProjectionMatrix(false);

  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 << flush;

  // To draw a representation of the intersecting line
  convertClickToLine(e, orig, dir);

  bool found;
  selectedPoint = camera.pointUnderPixel(e->x(), e->y(), found);
  selectedPoint -= 0.01*dir;
  
  if (found != (selected>=0))
    cerr << "Weird : pointUnderPixel and GL_SELECT differ" << endl;
}

void Viewer::help()
{
  cout << endl << "\t\t- -  S e l e c t  - -" << endl << endl;
  cout << "Left click while pressing the shift key to select an object of the scene." << endl << endl;
  cout << "The select() function is called and you can implement a selection." << endl;
  cout << "Here, this is done using the OpenGL GL_SELECT render mode." << endl;
  cout << "Feel free to cut and paste this implementation in your own applications." << endl;
}

Back to the main page

Valid XHTML 1.0! Valid CSS!