// Gmsh - Copyright (C) 1997-2016 C. Geuzaine, J.-F. Remacle // // See the LICENSE.txt file for license information. Please report all // bugs and problems to the public mailing list <gmsh@onelab.info>. #include <string> #include <stdio.h> #include "Gmsh.h" #include "GmshConfig.h" #include "GmshMessage.h" #include "drawContext.h" #include "Trackball.h" #include "Context.h" #include "Numeric.h" #include "GModel.h" #include "MElement.h" #include "PView.h" #include "PViewOptions.h" #include "VertexArray.h" #include "StringUtils.h" #include "OS.h" #include "gl2ps.h" #if defined(HAVE_FLTK) #include <FL/Fl_JPEG_Image.H> #include <FL/Fl_PNG_Image.H> #include <FL/gl.h> #include "openglWindow.h" #endif #if defined(HAVE_POPPLER) #include "gmshPopplerWrapper.h" #endif drawContextGlobal *drawContext::_global = 0; extern SPoint2 getGraph2dDataPointForTag(unsigned int); drawContext::drawContext(openglWindow *window, drawTransform *transform) : _transform(transform), _openglWindow(window) { // initialize from temp values in global context for(int i = 0; i < 3; i++){ r[i] = CTX::instance()->tmpRotation[i]; t[i] = CTX::instance()->tmpTranslation[i]; s[i] = CTX::instance()->tmpScale[i]; } for(int i = 0; i < 4; i++){ quaternion[i] = CTX::instance()->tmpQuaternion[i]; } viewport[0] = viewport[1] = 0; viewport[2] = CTX::instance()->glSize[0]; viewport[3] = CTX::instance()->glSize[1]; render_mode = GMSH_RENDER; vxmin = vymin = vxmax = vymax = 0.; pixel_equiv_x = pixel_equiv_y = 0.; _bgImageTexture = _bgImageW = _bgImageH = 0; _quadric = 0; // cannot create it here: needs valid opengl context _displayLists = 0; } drawContext::~drawContext() { invalidateQuadricsAndDisplayLists(); } bool drawContext::isHighResolution() { // this must be dynamic: the high resolution can change when a window is moved // across displays #if defined(HAVE_FLTK) if(_openglWindow) return _openglWindow->pixel_w() > _openglWindow->w(); #endif return false; } drawContextGlobal *drawContext::global() { if(!_global) _global = new drawContextGlobal(); // create dummy default return _global; } void drawContext::invalidateQuadricsAndDisplayLists() { if(_quadric){ gluDeleteQuadric(_quadric); _quadric = 0; } if(_displayLists){ glDeleteLists(_displayLists, 3); _displayLists = 0; } } void drawContext::createQuadricsAndDisplayLists() { if(!_quadric) _quadric = gluNewQuadric(); if(!_quadric){ Msg::Error("Could not create quadric"); return; } if(!_displayLists) _displayLists = glGenLists(3); if(!_displayLists){ Msg::Error("Could not generate display lists"); return; } // display list 0 (sphere) glNewList(_displayLists + 0, GL_COMPILE); gluSphere(_quadric, 1., CTX::instance()->quadricSubdivisions, CTX::instance()->quadricSubdivisions); glEndList(); // display list 1 (arrow) glNewList(_displayLists + 1, GL_COMPILE); glTranslated(0., 0., CTX::instance()->arrowRelStemLength); if(CTX::instance()->arrowRelHeadRadius > 0 && CTX::instance()->arrowRelStemLength < 1) gluCylinder(_quadric, CTX::instance()->arrowRelHeadRadius, 0., (1. - CTX::instance()->arrowRelStemLength), CTX::instance()->quadricSubdivisions, 1); if(CTX::instance()->arrowRelHeadRadius > CTX::instance()->arrowRelStemRadius) gluDisk(_quadric, CTX::instance()->arrowRelStemRadius, CTX::instance()->arrowRelHeadRadius, CTX::instance()->quadricSubdivisions, 1); else gluDisk(_quadric, CTX::instance()->arrowRelHeadRadius, CTX::instance()->arrowRelStemRadius, CTX::instance()->quadricSubdivisions, 1); glTranslated(0., 0., -CTX::instance()->arrowRelStemLength); if(CTX::instance()->arrowRelStemRadius > 0 && CTX::instance()->arrowRelStemLength > 0){ gluCylinder(_quadric, CTX::instance()->arrowRelStemRadius, CTX::instance()->arrowRelStemRadius, CTX::instance()->arrowRelStemLength, CTX::instance()->quadricSubdivisions, 1); gluDisk(_quadric, 0, CTX::instance()->arrowRelStemRadius, CTX::instance()->quadricSubdivisions, 1); } glEndList(); // display list 2 (disk) glNewList(_displayLists + 2, GL_COMPILE); gluDisk(_quadric, 0, 1, CTX::instance()->quadricSubdivisions, 1); glEndList(); } void drawContext::buildRotationMatrix() { if(CTX::instance()->useTrackball) { build_rotmatrix(rot, quaternion); setEulerAnglesFromRotationMatrix(); } else { double x = r[0] * M_PI / 180.; double y = r[1] * M_PI / 180.; double z = r[2] * M_PI / 180.; double A = cos(x); double B = sin(x); double C = cos(y); double D = sin(y); double E = cos(z); double F = sin(z); double AD = A * D; double BD = B * D; rot[0] = C*E; rot[1] = BD*E+A*F; rot[2] =-AD*E+B*F; rot[3] = 0.; rot[4] =-C*F; rot[5] =-BD*F+A*E; rot[6] = AD*F+B*E; rot[7] = 0.; rot[8] = D; rot[9] =-B*C; rot[10] = A*C; rot[11] = 0.; rot[12] = 0.; rot[13] = 0.; rot[14] = 0.; rot[15] = 1.; setQuaternionFromEulerAngles(); } } void drawContext::addQuaternion(double p1x, double p1y, double p2x, double p2y) { double quat[4]; trackball(quat, p1x, p1y, p2x, p2y); add_quats(quat, quaternion, quaternion); if (CTX::instance()->camera) camera.rotate(quat); } void drawContext::addQuaternionFromAxisAndAngle(double axis[3], double angle) { double a = angle * M_PI / 180.; double quat[4]; axis_to_quat(axis, a, quat); add_quats(quat, quaternion, quaternion); } void drawContext::setQuaternion(double q0, double q1, double q2, double q3) { quaternion[0] = q0; quaternion[1] = q1; quaternion[2] = q2; quaternion[3] = q3; } void drawContext::setQuaternionFromEulerAngles() { double x = r[0] * M_PI / 180.; double y = r[1] * M_PI / 180.; double z = r[2] * M_PI / 180.; double xx[3] = {1.,0.,0.}; double yy[3] = {0.,1.,0.}; double zz[3] = {0.,0.,1.}; double q1[4], q2[4], q3[4], tmp[4]; axis_to_quat(xx, -x, q1); axis_to_quat(yy, -y, q2); axis_to_quat(zz, -z, q3); add_quats(q1, q2, tmp); add_quats(tmp, q3, quaternion); } void drawContext::setEulerAnglesFromRotationMatrix() { r[1] = asin(rot[8]); // Calculate Y-axis angle double C = cos(r[1]); r[1] *= 180. / M_PI; if(fabs(C) > 0.005){ // Gimball lock? double tmpx = rot[10] / C; // No, so get X-axis angle double tmpy = -rot[9] / C; r[0] = atan2(tmpy, tmpx) * 180. / M_PI; tmpx = rot[0] / C; // Get Z-axis angle tmpy = -rot[4] / C; r[2] = atan2(tmpy, tmpx) * 180. / M_PI; } else{ // Gimball lock has occurred r[0] = 0.; // Set X-axis angle to zero double tmpx = rot[5]; // And calculate Z-axis angle double tmpy = rot[1]; r[2] = atan2(tmpy, tmpx) * 180. / M_PI; } // return only positive angles in [0,360] if(r[0] < 0.) r[0] += 360.; if(r[1] < 0.) r[1] += 360.; if(r[2] < 0.) r[2] += 360.; } static int needPolygonOffset() { GModel *m = GModel::current(); if(m->getMeshStatus() == 2 && (CTX::instance()->mesh.surfacesEdges || CTX::instance()->geom.lines || CTX::instance()->geom.surfaces)) return 1; if(m->getMeshStatus() == 3 && (CTX::instance()->mesh.surfacesEdges || CTX::instance()->mesh.volumesEdges)) return 1; for(unsigned int i = 0; i < PView::list.size(); i++){ PViewOptions *opt = PView::list[i]->getOptions(); if(opt->visible && opt->showElement) return 1; } return 0; } void drawContext::draw3d() { // We can only create this when a valid opengl context exists. (It's cheap to // create so we just do it at each redraw: this makes it much simpler to deal // with option changes, e.g. arrow shape changes) createQuadricsAndDisplayLists(); // We should only enable the polygon offset when there is a mix of lines and // polygons to be drawn; enabling it all the time can lead to very small but // annoying artifacts in the picture. Since there are so many ways in Gmsh to // combine polygons and lines (geometries + meshes + views...), we do our best // here to automatically detect if we should enable it. Note: the formula for // the offset is "offset = factor*DZ+r*units", where DZ is a measurement of // the change in depth relative to the screen area of the polygon, and r is // the smallest value that is guaranteed to produce a resolvable offset for a // given implementation. glPolygonOffset((float)CTX::instance()->polygonOffsetFactor, (float)CTX::instance()->polygonOffsetUnits); if(CTX::instance()->polygonOffsetFactor || CTX::instance()->polygonOffsetUnits) CTX::instance()->polygonOffset = CTX::instance()->polygonOffsetAlways ? 1 : needPolygonOffset(); else CTX::instance()->polygonOffset = 0; // speedup drawing of textured fonts on cocoa mac version #if defined(HAVE_FLTK) && defined(__APPLE__) int numStrings = GModel::current()->getNumVertices(); if(CTX::instance()->mesh.pointsNum) numStrings = std::max(numStrings, GModel::current()->getNumMeshVertices()); if(CTX::instance()->mesh.linesNum || CTX::instance()->mesh.surfacesNum || CTX::instance()->mesh.volumesNum) numStrings = std::max(numStrings, GModel::current()->getNumMeshElements()); numStrings *= 2; // FIXME: restting to 1 each time to workaround bug on recent MacOS versions gl_texture_pile_height(1); if(gl_texture_pile_height() < numStrings) gl_texture_pile_height(numStrings); #endif glDepthFunc(GL_LESS); glEnable(GL_DEPTH_TEST); initProjection(); initRenderModel(); if(!CTX::instance()->camera) initPosition(); drawAxes(); drawGeom(); drawBackgroundImage(true); drawMesh(); drawPost(); drawGraph2d(true); } void drawContext::draw2d() { glDisable(GL_DEPTH_TEST); for(int i = 0; i < 6; i++) glDisable((GLenum)(GL_CLIP_PLANE0 + i)); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho((double)viewport[0], (double)viewport[2], (double)viewport[1], (double)viewport[3], -100., 100.); // in pixels, so we can draw some 3D glyphs // hack to make the 2D primitives appear "in front" in GL2PS glTranslated(0., 0., CTX::instance()->clipFactor > 1. ? 1. / CTX::instance()->clipFactor : CTX::instance()->clipFactor); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); drawGraph2d(false); drawText2d(); if(CTX::instance()->post.draw && !CTX::instance()->stereo) drawScales(); if(CTX::instance()->smallAxes) drawSmallAxes(); } void drawContext::drawBackgroundGradient() { if(CTX::instance()->bgGradient == 1){ // vertical glBegin(GL_QUADS); glColor4ubv((GLubyte *) & CTX::instance()->color.bg); glVertex2i(viewport[0], viewport[1]); glVertex2i(viewport[2], viewport[1]); glColor4ubv((GLubyte *) & CTX::instance()->color.bgGrad); glVertex2i(viewport[2], viewport[3]); glVertex2i(viewport[0], viewport[3]); glEnd(); } else if(CTX::instance()->bgGradient == 2){ // horizontal glBegin(GL_QUADS); glColor4ubv((GLubyte *) & CTX::instance()->color.bg); glVertex2i(viewport[2], viewport[1]); glVertex2i(viewport[2], viewport[3]); glColor4ubv((GLubyte *) & CTX::instance()->color.bgGrad); glVertex2i(viewport[0], viewport[3]); glVertex2i(viewport[0], viewport[1]); glEnd(); } else if(CTX::instance()->bgGradient == 3){ // radial double cx = 0.5 * (viewport[0] + viewport[2]); double cy = 0.5 * (viewport[1] + viewport[3]); double r = 0.5 * std::max(viewport[2] - viewport[0], viewport[3] - viewport[1]); glBegin(GL_TRIANGLE_FAN); glColor4ubv((GLubyte *) & CTX::instance()->color.bgGrad); glVertex2d(cx, cy); glColor4ubv((GLubyte *) & CTX::instance()->color.bg); glVertex2d(cx + r, cy); int ntheta = 36; for(int i = 1; i < ntheta + 1; i ++){ double theta = i * 2 * M_PI / (double)ntheta; glVertex2d(cx + r * cos(theta), cy + r * sin(theta)); } glEnd(); } } void drawContext::invalidateBgImageTexture() { if(_bgImageTexture) glDeleteTextures(1, &_bgImageTexture); _bgImageTexture = 0; } bool drawContext::generateTextureForImage(const std::string &name, int page, GLuint &imageTexture, GLuint &imageW, GLuint &imageH) { if(StatFile(name)){ Msg::Warning("Could not open file `%s'", name.c_str()); return false; } std::string ext = SplitFileName(name)[2]; if(ext == ".pdf" || ext == ".PDF"){ #if defined(HAVE_POPPLER) if(!imageTexture){ if(!gmshPopplerWrapper::instance()->loadFromFile(name)){ Msg::Error("Could not load PDF file '%s'", name.c_str()); return false; } } gmshPopplerWrapper::instance()->setCurrentPage(page); imageTexture = gmshPopplerWrapper::instance()->getTextureForPage(300, 300); imageW = gmshPopplerWrapper::instance()->width(); imageH = gmshPopplerWrapper::instance()->height(); #else Msg::Error("Gmsh must be compiled with Poppler support to load PDFs"); return false; #endif } else{ #if defined(HAVE_FLTK) if(!imageTexture){ Fl_RGB_Image *img = 0; if(ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || ext == ".JPEG") img = new Fl_JPEG_Image(name.c_str()); else if(ext == ".png" || ext == ".PNG") img = new Fl_PNG_Image(name.c_str()); if(!img){ Msg::Error("Could not load background image '%s'", name.c_str()); return false; } Fl_RGB_Image *img2 = (Fl_RGB_Image*)img->copy(2048, 2048); glGenTextures(1, &imageTexture); glBindTexture(GL_TEXTURE_2D, imageTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img2->w(), img2->h(), 0, (img2->d() == 4) ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, img2->array); imageW = img->w(); imageH = img->h(); delete img; delete img2; } #else Msg::Error("Gmsh must be compiled with FLTK support to load JPEGs or PNGs"); return false; #endif } return true; } void drawContext::drawBackgroundImage(bool threeD) { if(CTX::instance()->bgImageFileName.empty() || (CTX::instance()->bgImage3d && !threeD) || (!CTX::instance()->bgImage3d && threeD)) return; std::string name = FixRelativePath(GModel::current()->getFileName(), CTX::instance()->bgImageFileName); double x = CTX::instance()->bgImagePosition[0]; double y = CTX::instance()->bgImagePosition[1]; double w = CTX::instance()->bgImageSize[0]; double h = CTX::instance()->bgImageSize[1]; if(!generateTextureForImage(name, CTX::instance()->bgImagePage, _bgImageTexture, _bgImageW, _bgImageH)){ CTX::instance()->bgImageFileName.clear(); return; } if(!_bgImageTexture) return; if(w < 0 && h < 0){ w = viewport[2] - viewport[0]; h = viewport[3] - viewport[1]; } else if(w < 0 && h == 0){ w = viewport[2] - viewport[0]; h = w * _bgImageH / _bgImageW; } else if(w < 0){ w = viewport[2] - viewport[0]; } else if(w == 0 && h < 0){ h = viewport[3] - viewport[1]; w = h * _bgImageW / _bgImageH; } else if(h < 0){ h = viewport[3] - viewport[1]; } else if(w == 0 && h == 0){ w = _bgImageW; h = _bgImageH; } else if(h == 0){ h = w * _bgImageH / _bgImageW; } else if(w == 0){ w = h * _bgImageW / _bgImageH; } Msg::Debug("Background image: x=%g y=%g w=%g h=%g", x, y, w, h); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, _bgImageTexture); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glBegin(GL_QUADS); if(threeD){ glTexCoord2f(1.0f, 1.0f); glVertex2d(x+w, y); glTexCoord2f(1.0f, 0.0f); glVertex2d(x+w, y+h); glTexCoord2f(0.0f, 0.0f); glVertex2d(x, y+h); glTexCoord2f(0.0f, 1.0f); glVertex2d(x, y); } else{ int c = fix2dCoordinates(&x, &y); // y=0 now means top if(c & 1) x -= w / 2.; if(c & 2) y += h / 2.; if(x < viewport[0]) x = viewport[0]; if(y < viewport[1]) y = viewport[1]; glTexCoord2f(1.0f, 1.0f); glVertex2d(x+w, y-h); glTexCoord2f(1.0f, 0.0f); glVertex2d(x+w, y); glTexCoord2f(0.0f, 0.0f); glVertex2d(x, y); glTexCoord2f(0.0f, 1.0f); glVertex2d(x, y-h); } glEnd(); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); } void drawContext::initProjection(int xpick, int ypick, int wpick, int hpick) { double Va = (double) (viewport[3] - viewport[1]) / (double) (viewport[2] - viewport[0]); double Wa = (CTX::instance()->max[1] - CTX::instance()->min[1]) / (CTX::instance()->max[0] - CTX::instance()->min[0]); // compute the viewport in World coordinates (with margins) if(Va > Wa) { vxmin = CTX::instance()->min[0]; vxmax = CTX::instance()->max[0]; vymin = 0.5 * (CTX::instance()->min[1] + CTX::instance()->max[1] - Va * (CTX::instance()->max[0] - CTX::instance()->min[0])); vymax = 0.5 * (CTX::instance()->min[1] + CTX::instance()->max[1] + Va * (CTX::instance()->max[0] - CTX::instance()->min[0])); } else { vxmin = 0.5 * (CTX::instance()->min[0] + CTX::instance()->max[0] - (CTX::instance()->max[1] - CTX::instance()->min[1]) / Va); vxmax = 0.5 * (CTX::instance()->min[0] + CTX::instance()->max[0] + (CTX::instance()->max[1] - CTX::instance()->min[1]) / Va); vymin = CTX::instance()->min[1]; vymax = CTX::instance()->max[1]; } double fact = CTX::instance()->displayBorderFactor; double xborder = fact * (vxmax - vxmin), yborder = fact * (vymax - vymin); vxmin -= xborder; vxmax += xborder; vymin -= yborder; vymax += yborder; // store what one pixel represents in world coordinates pixel_equiv_x = (vxmax - vxmin) / (viewport[2] - viewport[0]); pixel_equiv_y = (vymax - vymin) / (viewport[3] - viewport[1]); // no initial translation of the model t_init[0] = t_init[1] = t_init[2] = 0.; // set up the near and far clipping planes so that the box is large enough to // manipulate the model and zoom, but not too big (otherwise the z-buffer // resolution e.g. with Mesa can become insufficient) double zmax = std::max(fabs(CTX::instance()->min[2]), fabs(CTX::instance()->max[2])); if(zmax < CTX::instance()->lc) zmax = CTX::instance()->lc; if (CTX::instance()->camera) { // if we use the camera mode glDisable(GL_DEPTH_TEST); glPushMatrix(); glLoadIdentity(); double w = (double)viewport[2]; double h = (double)viewport[3]; double ratio = w / h; double dx = 1.5 * tan(camera.radians) * w * ratio; double dy = 1.5 * tan(camera.radians) * w; double dz = -w * 1.25; glBegin(GL_QUADS); glColor4ubv((GLubyte *) & CTX::instance()->color.bg); glVertex3i((int)-dx, (int)-dy, (int)dz); glVertex3i((int) dx, (int)-dy, (int)dz); glColor4ubv((GLubyte *) & CTX::instance()->color.bgGrad); glVertex3i((int) dx, (int)dy, (int)dz); glVertex3i((int)-dx, (int)dy, (int)dz); glEnd(); glPopMatrix(); glEnable(GL_DEPTH_TEST); } else if(!CTX::instance()->camera){ // if not in camera mode double clip_near, clip_far; if(CTX::instance()->ortho) { clip_near = -zmax * s[2] * CTX::instance()->clipFactor; clip_far = -clip_near; } else { clip_near = 0.75 * CTX::instance()->clipFactor * zmax; clip_far = 75. * CTX::instance()->clipFactor * zmax; } // setup projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); // restrict picking to a rectangular region around xpick,ypick if(render_mode == GMSH_SELECT) gluPickMatrix((GLdouble)xpick, (GLdouble)(viewport[3] - ypick), (GLdouble)wpick, (GLdouble)hpick, (GLint *)viewport); // draw background if not in selection mode if(render_mode != GMSH_SELECT && (CTX::instance()->bgGradient || CTX::instance()->bgImageFileName.size()) && (!CTX::instance()->printing || CTX::instance()->print.background)){ glDisable(GL_DEPTH_TEST); glPushMatrix(); glLoadIdentity(); // the z values and the translation are only needed for GL2PS, which does // not understand "no depth test" (hence we must make sure that we draw // the background behind the rest of the scene) glOrtho((double)viewport[0], (double)viewport[2], (double)viewport[1], (double)viewport[3], clip_near, clip_far); glTranslated(0., 0., -0.99 * clip_far); drawBackgroundGradient(); // hack for GL2PS (to make sure that the image is in front of the // gradient) glTranslated(0., 0., 0.01 * clip_far); drawBackgroundImage(false); glPopMatrix(); glEnable(GL_DEPTH_TEST); } if(CTX::instance()->ortho) { glOrtho(vxmin, vxmax, vymin, vymax, clip_near, clip_far); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } else { // recenter the model such that the perspective is always at the center of // gravity (we should maybe add an option to choose this, as we do for the // rotation center) t_init[0] = CTX::instance()->cg[0]; t_init[1] = CTX::instance()->cg[1]; vxmin -= t_init[0]; vxmax -= t_init[0]; vymin -= t_init[1]; vymax -= t_init[1]; glFrustum(vxmin, vxmax, vymin, vymax, clip_near, clip_far); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); double coef = (clip_far / clip_near) / 3.; glTranslated(-coef * t_init[0], -coef * t_init[1], -coef * clip_near); glScaled(coef, coef, coef); } } } void drawContext::initRenderModel() { glPushMatrix(); glLoadIdentity(); glScaled(s[0], s[1], s[2]); glTranslated(t[0], t[1], t[2]); for(int i = 0; i < 6; i++) { if(CTX::instance()->light[i]) { GLfloat position[4] = {(GLfloat)CTX::instance()->lightPosition[i][0], (GLfloat)CTX::instance()->lightPosition[i][1], (GLfloat)CTX::instance()->lightPosition[i][2], (GLfloat)CTX::instance()->lightPosition[i][3]}; glLightfv((GLenum)(GL_LIGHT0 + i), GL_POSITION, position); GLfloat r = (GLfloat)(CTX::instance()->unpackRed (CTX::instance()->color.ambientLight[i]) / 255.); GLfloat g = (GLfloat)(CTX::instance()->unpackGreen (CTX::instance()->color.ambientLight[i]) / 255.); GLfloat b = (GLfloat)(CTX::instance()->unpackBlue (CTX::instance()->color.ambientLight[i]) / 255.); GLfloat ambient[4] = {r, g, b, 1.0F}; glLightfv((GLenum)(GL_LIGHT0 + i), GL_AMBIENT, ambient); r = (GLfloat)(CTX::instance()->unpackRed (CTX::instance()->color.diffuseLight[i]) / 255.); g = (GLfloat)(CTX::instance()->unpackGreen (CTX::instance()->color.diffuseLight[i]) / 255.); b = (GLfloat)(CTX::instance()->unpackBlue (CTX::instance()->color.diffuseLight[i]) / 255.); GLfloat diffuse[4] = {r, g, b, 1.0F}; glLightfv((GLenum)(GL_LIGHT0 + i), GL_DIFFUSE, diffuse); r = (GLfloat)(CTX::instance()->unpackRed (CTX::instance()->color.specularLight[i]) / 255.); g = (GLfloat)(CTX::instance()->unpackGreen (CTX::instance()->color.specularLight[i]) / 255.); b = (GLfloat)(CTX::instance()->unpackBlue (CTX::instance()->color.specularLight[i]) / 255.); GLfloat specular[4] = {r, g, b, 1.0F}; glLightfv((GLenum)(GL_LIGHT0 + i), GL_SPECULAR, specular); glEnable((GLenum)(GL_LIGHT0 + i)); } else{ glDisable((GLenum)(GL_LIGHT0 + i)); } } glPopMatrix(); // ambient and diffuse material colors track glColor automatically glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); // "white"-only specular material reflection color GLfloat spec[4] = {(GLfloat)CTX::instance()->shine, (GLfloat)CTX::instance()->shine, (GLfloat)CTX::instance()->shine, 1.0F}; glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, spec); // specular exponent in [0,128] (larger means more "focused" // reflection) glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, (GLfloat)CTX::instance()->shineExponent); glShadeModel(GL_SMOOTH); // Normalize the normals automatically. We could use the more efficient // glEnable(GL_RESCALE_NORMAL) instead (since we initially specify unit // normals), but GL_RESCALE_NORMAL does only work with isotropic scalings (and // we allow anistotropic scalings in myZoom). Note that GL_RESCALE_NORMAL is // only available in GL_VERSION_1_2. glEnable(GL_NORMALIZE); // lighting is enabled/disabled for each particular primitive later glDisable(GL_LIGHTING); } void drawContext::initPosition() { glScaled(s[0], s[1], s[2]); glTranslated(t[0], t[1], t[2]); if(CTX::instance()->rotationCenterCg) glTranslated(CTX::instance()->cg[0], CTX::instance()->cg[1], CTX::instance()->cg[2]); else glTranslated(CTX::instance()->rotationCenter[0], CTX::instance()->rotationCenter[1], CTX::instance()->rotationCenter[2]); buildRotationMatrix(); glMultMatrixd(rot); if(CTX::instance()->rotationCenterCg) glTranslated(-CTX::instance()->cg[0], -CTX::instance()->cg[1], -CTX::instance()->cg[2]); else glTranslated(-CTX::instance()->rotationCenter[0], -CTX::instance()->rotationCenter[1], -CTX::instance()->rotationCenter[2]); // store the projection and modelview matrices at this precise moment (so that // we can use them at any later time, even if the context has changed, i.e., // even if we are out of draw()) glGetDoublev(GL_PROJECTION_MATRIX, proj); glGetDoublev(GL_MODELVIEW_MATRIX, model); for(int i = 0; i < 6; i++) glClipPlane((GLenum)(GL_CLIP_PLANE0 + i), CTX::instance()->clipPlane[i]); } // Takes a cursor position in window coordinates and returns the line (given by // a point and a unit direction vector), in real space, that corresponds to that // cursor position void drawContext::unproject(double winx, double winy, double p[3], double d[3]) { if(isHighResolution()){ winx *= 2; // true pixels winy *= 2; } GLint vp[4]; glGetIntegerv(GL_VIEWPORT, vp); winy = vp[3] - winy; GLdouble x0, y0, z0, x1, y1, z1; // we use the stored model and proj matrices instead of directly // getGetDouble'ing the matrices since unproject can be called in or after // draw2d if(!gluUnProject(winx, winy, 0.0, model, proj, vp, &x0, &y0, &z0)) Msg::Warning("unproject1 failed"); if(!gluUnProject(winx, winy, 1.0, model, proj, vp, &x1, &y1, &z1)) Msg::Warning("unproject2 failed"); p[0] = x0; p[1] = y0; p[2] = z0; d[0] = x1 - x0; d[1] = y1 - y0; d[2] = z1 - z0; double len = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); d[0] /= len; d[1] /= len; d[2] /= len; } void drawContext::viewport2World(double vp[3], double xyz[3]) { GLint viewport[4]; GLdouble model[16], proj[16]; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_PROJECTION_MATRIX, proj); glGetDoublev(GL_MODELVIEW_MATRIX, model); gluUnProject(vp[0], vp[1], vp[2], model, proj, viewport, &xyz[0], &xyz[1], &xyz[2]); } void drawContext::world2Viewport(double xyz[3], double vp[3]) { GLint viewport[4]; GLdouble model[16], proj[16]; glGetIntegerv(GL_VIEWPORT, viewport); glGetDoublev(GL_PROJECTION_MATRIX, proj); glGetDoublev(GL_MODELVIEW_MATRIX, model); gluProject(xyz[0], xyz[1], xyz[2], model, proj, viewport, &vp[0], &vp[1], &vp[2]); } class hit{ public: GLuint type, ient, depth, type2, ient2; hit(GLuint t, GLuint i, GLuint d, GLuint t2=0, GLuint i2=0) : type(t), ient(i), depth(d), type2(t2), ient2(i2) {} }; class hitDepthLessThan{ public: bool operator()(const hit &h1, const hit &h2) const { return h1.depth < h2.depth; } }; // returns the element at a given position in a vertex array (element pointers // are not always stored: returning 0 is not an error) static MElement *getElement(GEntity *e, int va_type, int index) { switch(va_type){ case 2: if(e->va_lines && index < e->va_lines->getNumElementPointers()) return *e->va_lines->getElementPointerArray(index); break; case 3: if(e->va_triangles && index < e->va_triangles->getNumElementPointers()) return *e->va_triangles->getElementPointerArray(index); break; } return 0; } bool drawContext::select(int type, bool multiple, bool mesh, int x, int y, int w, int h, std::vector<GVertex*> &vertices, std::vector<GEdge*> &edges, std::vector<GFace*> &faces, std::vector<GRegion*> ®ions, std::vector<MElement*> &elements, std::vector<SPoint2> &points, std::vector<PView*> &views) { vertices.clear(); edges.clear(); faces.clear(); regions.clear(); elements.clear(); points.clear(); views.clear(); // in our case the selection buffer size is equal to between 5 and 7 times the // maximum number of possible hits GModel *m = GModel::current(); int eles = (mesh && CTX::instance()->pickElements) ? 4 * m->getNumMeshElements() : 0; int nviews = PView::list.size() * 100; int size = 7 * (m->getNumVertices() + m->getNumEdges() + m->getNumFaces() + m->getNumRegions() + eles) + nviews; if(!size) return false; // the model is empty, don't bother! // allocate selection buffer size += 1000; // just to make sure GLuint *selectionBuffer = new GLuint[size]; glSelectBuffer(size, selectionBuffer); // do one rendering pass in select mode render_mode = drawContext::GMSH_SELECT; glRenderMode(GL_SELECT); glInitNames(); glPushMatrix(); // 3d stuff initProjection(x, y, w, h); initPosition(); drawGeom(); if(mesh) drawMesh(); drawPost(); drawGraph2d(true); // 2d stuff glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix((GLdouble)x, (GLdouble)(viewport[3] - y), (GLdouble)w, (GLdouble)h, (GLint *)viewport); glOrtho((double)viewport[0], (double)viewport[2], (double)viewport[1], (double)viewport[3], -100., 100.); // in pixels, so we can draw some 3D glyphs glMatrixMode(GL_MODELVIEW); glLoadIdentity(); drawGraph2d(false); drawText2d(); glPopMatrix(); GLint numhits = glRenderMode(GL_RENDER); render_mode = drawContext::GMSH_RENDER; if(!numhits){ // no hits delete [] selectionBuffer; return false; } else if(numhits < 0){ // overflow delete [] selectionBuffer; Msg::Warning("Too many entities selected"); return false; } // decode the hits std::vector<hit> hits; GLuint *ptr = selectionBuffer; for(int i = 0; i < numhits; i++) { // in Gmsh 'names' should always be 0, 2 or 4: // * names == 0 means that there is nothing on the stack // * if names == 2, the first name is the type of the entity (0 for point, 1 // for edge, 2 for face or 3 for volume) and the second is the entity // number; // * if names == 4, the first name is the type of the entity, the second is // the entity number, the third is the type of vertex array (2 for line, 3 // for triangle, 4 for quad) and the fourth is the index of the element in // the vertex array GLuint names = *ptr++; GLuint mindepth = *ptr++; GLuint maxdepth = *ptr++; if(names == 2){ GLuint depth = maxdepth + 0 * mindepth; // could do something with mindepth GLuint type = *ptr++; GLuint ient = *ptr++; hits.push_back(hit(type, ient, depth)); } else if(names == 4){ GLuint depth = maxdepth + 0 * mindepth; // could do something with mindepth GLuint type = *ptr++; GLuint ient = *ptr++; GLuint type2 = *ptr++; GLuint ient2 = *ptr++; hits.push_back(hit(type, ient, depth, type2, ient2)); } } delete [] selectionBuffer; if(!hits.size()){ // no entities return false; } // sort hits to get closest entities first std::sort(hits.begin(), hits.end(), hitDepthLessThan()); // filter result: if type == ENT_NONE, return the closest entity of "lowest // dimension" (point < line < surface < volume). Otherwise, return the closest // entity of type "type" GLuint typmin = 10; for(unsigned int i = 0; i < hits.size(); i++) typmin = std::min(typmin, hits[i].type); for(unsigned int i = 0; i < hits.size(); i++) { if((type == ENT_ALL) || (type == ENT_NONE && hits[i].type == typmin) || (type == ENT_POINT && hits[i].type == 0) || (type == ENT_LINE && hits[i].type == 1) || (type == ENT_SURFACE && hits[i].type == 2) || (type == ENT_VOLUME && hits[i].type == 3)){ switch (hits[i].type) { case 0: { GVertex *v = m->getVertexByTag(hits[i].ient); if(!v){ Msg::Error("Problem in point selection processing"); return false; } vertices.push_back(v); if(!multiple) return true; } break; case 1: { GEdge *e = m->getEdgeByTag(hits[i].ient); if(!e){ Msg::Error("Problem in line selection processing"); return false; } if(hits[i].type2){ MElement *ele = getElement(e, hits[i].type2, hits[i].ient2); if(ele) elements.push_back(ele); } edges.push_back(e); if(!multiple) return true; } break; case 2: { GFace *f = m->getFaceByTag(hits[i].ient); if(!f){ Msg::Error("Problem in surface selection processing"); return false; } if(hits[i].type2){ MElement *ele = getElement(f, hits[i].type2, hits[i].ient2); if(ele) elements.push_back(ele); } faces.push_back(f); if(!multiple) return true; } break; case 3: { GRegion *r = m->getRegionByTag(hits[i].ient); if(!r){ Msg::Error("Problem in volume selection processing"); return false; } if(hits[i].type2){ MElement *ele = getElement(r, hits[i].type2, hits[i].ient2); if(ele) elements.push_back(ele); } regions.push_back(r); if(!multiple) return true; } break; case 4: { int tag = hits[i].ient; SPoint2 p = getGraph2dDataPointForTag(tag); points.push_back(p); if(!multiple) return true; } break; case 5: { int tag = hits[i].ient; if(tag >= 0 && tag < (int)PView::list.size()) views.push_back(PView::list[tag]); if(!multiple) return true; } break; } } } if(vertices.size() || edges.size() || faces.size() || regions.size() || elements.size() || points.size() || views.size()) return true; return false; }