#include "select.h" #include <math.h> #include <limits.h> // UINT_MAX 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), raster 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::select(const QMouseEvent* e) { // You should cut-paste this rather tricky function as your own "select()" function. // You need a drawing function that pushes and pops names to tag objects' id (see drawSpiral() above). // You have to change the call to the drawWithPush (here a simple drawScene()). // "selected" is set to the closest object that was picked (or -1 if no object was selected) // Make openGL context current makeCurrent(); const int SENSITIVITY = 4; const int NB_HITS_MAX = 64; // 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]; camera()->getViewport(viewport); gluPickMatrix(static_cast<GLdouble>(e->x()), static_cast<GLdouble>(e->y()), SENSITIVITY, SENSITIVITY, viewport); // loadProjectionMatrix() first resets the GL_PROJECTION matrix with a glLoadIdentity. // Give false as a parameter in order to prevent this and to combine the matrices. camera()->loadProjectionMatrix(false); camera()->loadModelViewMatrix(); // Render scene with objects ids. // Change here to call your own "drawWithPush()" function. drawScene(true); glFlush(); // Get the number of objects were seen through the pick matrix frustum. Reset GL_RENDER mode. GLint nb_hits = glRenderMode(GL_RENDER); // Compute orig and dir, used to draw a representation of the intersecting line camera()->convertClickToLine(e->x(), e->y(), orig, dir); // Display of selection results cout << "(" << e->x() << "," << e->y() << ") : "; if (nb_hits <= 0) { selected = -1; cout << "No object selected" << endl; return; } cout << nb_hits << " spiral" << ((nb_hits>1)?"s":"") << " under the cursor" << endl; // Interpret results : each object created 4 values in the "hits" array : // hits[4*i+1] is the object minimum depth value, while hits[4*i+3] is the id pushed on the stack. // Of all the objects that were projected in the pick region, we select the closest one (zMin comparaison). // This code needs to be modified if you use several stack levels. See glSelectBuffer() man page. unsigned int zMin = hits[1]; selected = hits[3]; for (int i=1; i<nb_hits; ++i) if (hits[i*4+1] < zMin) { zMin = hits[i*4+1]; selected = hits[i*4+3]; } // Find the selectedPoint coordinates, using camera()->pointUnderPixel(). bool found; selectedPoint = camera()->pointUnderPixel(e->x(), e->y(), found); selectedPoint -= 0.01*dir; // Small offset to make point clearly visible. // Note that "found" is different from (selected>=0) because of SENSITIVITY. } 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(); } } 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; }