Home | Documentation | Download | Screenshots | Developper |
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.
#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; };
#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(); }
#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