1 // Gmsh - Copyright (C) 1997-2021 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file in the Gmsh root directory for license information.
4 // Please report all issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5 
6 #include <string>
7 #include <stdio.h>
8 #include "GmshGlobal.h"
9 #include "GmshConfig.h"
10 #include "GmshMessage.h"
11 #include "drawContext.h"
12 #include "Trackball.h"
13 #include "Context.h"
14 #include "Numeric.h"
15 #include "GModel.h"
16 #include "MElement.h"
17 #include "PView.h"
18 #include "PViewOptions.h"
19 #include "VertexArray.h"
20 #include "StringUtils.h"
21 #include "OS.h"
22 #include "gl2ps.h"
23 
24 #if defined(HAVE_FLTK)
25 #include <FL/Fl_JPEG_Image.H>
26 #include <FL/Fl_PNG_Image.H>
27 #include <FL/gl.h>
28 #include "openglWindow.h"
29 #endif
30 
31 #if defined(HAVE_POPPLER)
32 #include "gmshPopplerWrapper.h"
33 #endif
34 
35 drawContextGlobal *drawContext::_global = nullptr;
36 void (*drawContext::drawGeomTransient)(void *) = nullptr;
37 
setDrawGeomTransientFunction(void (* fct)(void *))38 void drawContext::setDrawGeomTransientFunction(void (*fct)(void *))
39 {
40   drawGeomTransient = fct;
41 }
42 
43 extern SPoint2 getGraph2dDataPointForTag(unsigned int);
44 
drawContext(openglWindow * window,drawTransform * transform)45 drawContext::drawContext(openglWindow *window, drawTransform *transform)
46   : _transform(transform), _openglWindow(window)
47 {
48   // initialize from temp values in global context
49   for(int i = 0; i < 3; i++) {
50     r[i] = CTX::instance()->tmpRotation[i];
51     t[i] = CTX::instance()->tmpTranslation[i];
52     s[i] = CTX::instance()->tmpScale[i];
53   }
54   for(int i = 0; i < 4; i++) {
55     quaternion[i] = CTX::instance()->tmpQuaternion[i];
56   }
57   viewport[0] = viewport[1] = 0;
58   viewport[2] = CTX::instance()->glSize[0];
59   viewport[3] = CTX::instance()->glSize[1];
60 
61   render_mode = GMSH_RENDER;
62   vxmin = vymin = vxmax = vymax = 0.;
63   pixel_equiv_x = pixel_equiv_y = 0.;
64 
65   _bgImageTexture = _bgImageW = _bgImageH = 0;
66 
67   _quadric = nullptr; // cannot create it here: needs valid opengl context
68   _displayLists = 0;
69 }
70 
~drawContext()71 drawContext::~drawContext() { invalidateQuadricsAndDisplayLists(); }
72 
highResolutionPixelFactor()73 double drawContext::highResolutionPixelFactor()
74 {
75   // this must be dynamic: the high resolution can change when a window is moved
76   // across displays
77 #if defined(HAVE_FLTK)
78   if(_openglWindow && _openglWindow->w()) {
79     return (double)_openglWindow->pixel_w() / (double)_openglWindow->w();
80   }
81 #endif
82   return 1.0;
83 }
84 
global()85 drawContextGlobal *drawContext::global()
86 {
87   if(!_global) _global = new drawContextGlobal(); // create dummy default
88   return _global;
89 }
90 
invalidateQuadricsAndDisplayLists()91 void drawContext::invalidateQuadricsAndDisplayLists()
92 {
93   if(_quadric) {
94     gluDeleteQuadric(_quadric);
95     _quadric = nullptr;
96   }
97   if(_displayLists) {
98     glDeleteLists(_displayLists, 3);
99     _displayLists = 0;
100   }
101 }
102 
createQuadricsAndDisplayLists()103 void drawContext::createQuadricsAndDisplayLists()
104 {
105   if(!_quadric) _quadric = gluNewQuadric();
106   if(!_quadric) {
107     Msg::Error("Could not create quadric");
108     return;
109   }
110 
111   if(!_displayLists) _displayLists = glGenLists(3);
112   if(!_displayLists) {
113     Msg::Error("Could not generate display lists");
114     return;
115   }
116 
117   // display list 0 (sphere)
118   glNewList(_displayLists + 0, GL_COMPILE);
119   gluSphere(_quadric, 1., CTX::instance()->quadricSubdivisions,
120             CTX::instance()->quadricSubdivisions);
121   glEndList();
122 
123   // display list 1 (arrow)
124   glNewList(_displayLists + 1, GL_COMPILE);
125   glTranslated(0., 0., CTX::instance()->arrowRelStemLength);
126   if(CTX::instance()->arrowRelHeadRadius > 0 &&
127      CTX::instance()->arrowRelStemLength < 1)
128     gluCylinder(_quadric, CTX::instance()->arrowRelHeadRadius, 0.,
129                 (1. - CTX::instance()->arrowRelStemLength),
130                 CTX::instance()->quadricSubdivisions, 1);
131   if(CTX::instance()->arrowRelHeadRadius > CTX::instance()->arrowRelStemRadius)
132     gluDisk(_quadric, CTX::instance()->arrowRelStemRadius,
133             CTX::instance()->arrowRelHeadRadius,
134             CTX::instance()->quadricSubdivisions, 1);
135   else
136     gluDisk(_quadric, CTX::instance()->arrowRelHeadRadius,
137             CTX::instance()->arrowRelStemRadius,
138             CTX::instance()->quadricSubdivisions, 1);
139   glTranslated(0., 0., -CTX::instance()->arrowRelStemLength);
140   if(CTX::instance()->arrowRelStemRadius > 0 &&
141      CTX::instance()->arrowRelStemLength > 0) {
142     gluCylinder(_quadric, CTX::instance()->arrowRelStemRadius,
143                 CTX::instance()->arrowRelStemRadius,
144                 CTX::instance()->arrowRelStemLength,
145                 CTX::instance()->quadricSubdivisions, 1);
146     gluDisk(_quadric, 0, CTX::instance()->arrowRelStemRadius,
147             CTX::instance()->quadricSubdivisions, 1);
148   }
149   glEndList();
150 
151   // display list 2 (disk)
152   glNewList(_displayLists + 2, GL_COMPILE);
153   gluDisk(_quadric, 0, 1, CTX::instance()->quadricSubdivisions, 1);
154   glEndList();
155 }
156 
buildRotationMatrix()157 void drawContext::buildRotationMatrix()
158 {
159   if(CTX::instance()->useTrackball) {
160     build_rotmatrix(rot, quaternion);
161     setEulerAnglesFromRotationMatrix();
162   }
163   else {
164     double x = r[0] * M_PI / 180.;
165     double y = r[1] * M_PI / 180.;
166     double z = r[2] * M_PI / 180.;
167     double A = cos(x);
168     double B = sin(x);
169     double C = cos(y);
170     double D = sin(y);
171     double E = cos(z);
172     double F = sin(z);
173     double AD = A * D;
174     double BD = B * D;
175     rot[0] = C * E;
176     rot[1] = BD * E + A * F;
177     rot[2] = -AD * E + B * F;
178     rot[3] = 0.;
179     rot[4] = -C * F;
180     rot[5] = -BD * F + A * E;
181     rot[6] = AD * F + B * E;
182     rot[7] = 0.;
183     rot[8] = D;
184     rot[9] = -B * C;
185     rot[10] = A * C;
186     rot[11] = 0.;
187     rot[12] = 0.;
188     rot[13] = 0.;
189     rot[14] = 0.;
190     rot[15] = 1.;
191     setQuaternionFromEulerAngles();
192   }
193 }
194 
addQuaternion(double p1x,double p1y,double p2x,double p2y)195 void drawContext::addQuaternion(double p1x, double p1y, double p2x, double p2y)
196 {
197   double quat[4];
198   trackball(quat, p1x, p1y, p2x, p2y);
199   add_quats(quat, quaternion, quaternion);
200   if(CTX::instance()->camera) camera.rotate(quat);
201 }
202 
addQuaternionFromAxisAndAngle(double axis[3],double angle)203 void drawContext::addQuaternionFromAxisAndAngle(double axis[3], double angle)
204 {
205   double a = angle * M_PI / 180.;
206   double quat[4];
207   axis_to_quat(axis, a, quat);
208   add_quats(quat, quaternion, quaternion);
209 }
210 
setQuaternion(double q0,double q1,double q2,double q3)211 void drawContext::setQuaternion(double q0, double q1, double q2, double q3)
212 {
213   quaternion[0] = q0;
214   quaternion[1] = q1;
215   quaternion[2] = q2;
216   quaternion[3] = q3;
217 }
218 
setQuaternionFromEulerAngles()219 void drawContext::setQuaternionFromEulerAngles()
220 {
221   double x = r[0] * M_PI / 180.;
222   double y = r[1] * M_PI / 180.;
223   double z = r[2] * M_PI / 180.;
224   double xx[3] = {1., 0., 0.};
225   double yy[3] = {0., 1., 0.};
226   double zz[3] = {0., 0., 1.};
227   double q1[4], q2[4], q3[4], tmp[4];
228   axis_to_quat(xx, -x, q1);
229   axis_to_quat(yy, -y, q2);
230   axis_to_quat(zz, -z, q3);
231   add_quats(q1, q2, tmp);
232   add_quats(tmp, q3, quaternion);
233 }
234 
setEulerAnglesFromRotationMatrix()235 void drawContext::setEulerAnglesFromRotationMatrix()
236 {
237   r[1] = asin(rot[8]); // Calculate Y-axis angle
238   double C = cos(r[1]);
239   r[1] *= 180. / M_PI;
240   if(fabs(C) > 0.005) { // Gimball lock?
241     double tmpx = rot[10] / C; // No, so get X-axis angle
242     double tmpy = -rot[9] / C;
243     r[0] = atan2(tmpy, tmpx) * 180. / M_PI;
244     tmpx = rot[0] / C; // Get Z-axis angle
245     tmpy = -rot[4] / C;
246     r[2] = atan2(tmpy, tmpx) * 180. / M_PI;
247   }
248   else { // Gimball lock has occurred
249     r[0] = 0.; // Set X-axis angle to zero
250     double tmpx = rot[5]; // And calculate Z-axis angle
251     double tmpy = rot[1];
252     r[2] = atan2(tmpy, tmpx) * 180. / M_PI;
253   }
254   // return only positive angles in [0,360]
255   if(r[0] < 0.) r[0] += 360.;
256   if(r[1] < 0.) r[1] += 360.;
257   if(r[2] < 0.) r[2] += 360.;
258 }
259 
needPolygonOffset()260 static int needPolygonOffset()
261 {
262   GModel *m = GModel::current();
263   if(m->getMeshStatus() == 2 &&
264      (CTX::instance()->mesh.surfaceEdges || CTX::instance()->geom.curves ||
265       CTX::instance()->geom.surfaces))
266     return 1;
267   if(m->getMeshStatus() == 3 && (CTX::instance()->mesh.surfaceEdges ||
268                                  CTX::instance()->mesh.volumeEdges))
269     return 1;
270   for(std::size_t i = 0; i < PView::list.size(); i++) {
271     PViewOptions *opt = PView::list[i]->getOptions();
272     if(opt->visible && opt->showElement) return 1;
273   }
274   return 0;
275 }
276 
draw3d()277 void drawContext::draw3d()
278 {
279   // We can only create this when a valid opengl context exists. (It's cheap to
280   // create so we just do it at each redraw: this makes it much simpler to deal
281   // with option changes, e.g. arrow shape changes)
282   createQuadricsAndDisplayLists();
283 
284   // We should only enable the polygon offset when there is a mix of lines and
285   // polygons to be drawn; enabling it all the time can lead to very small but
286   // annoying artifacts in the picture. Since there are so many ways in Gmsh to
287   // combine polygons and lines (geometries + meshes + views...), we do our best
288   // here to automatically detect if we should enable it. Note: the formula for
289   // the offset is "offset = factor*DZ+r*units", where DZ is a measurement of
290   // the change in depth relative to the screen area of the polygon, and r is
291   // the smallest value that is guaranteed to produce a resolvable offset for a
292   // given implementation.
293   glPolygonOffset((float)CTX::instance()->polygonOffsetFactor,
294                   (float)CTX::instance()->polygonOffsetUnits);
295   if(CTX::instance()->polygonOffsetFactor ||
296      CTX::instance()->polygonOffsetUnits)
297     CTX::instance()->polygonOffset =
298       CTX::instance()->polygonOffsetAlways ? 1 : needPolygonOffset();
299   else
300     CTX::instance()->polygonOffset = 0;
301 
302     // speedup drawing of textured fonts on cocoa mac version
303 #if defined(HAVE_FLTK) && defined(__APPLE__)
304   std::size_t numStrings = GModel::current()->getNumVertices();
305   if(CTX::instance()->mesh.nodeLabels)
306     numStrings = std::max(numStrings, GModel::current()->getNumMeshVertices());
307   if(CTX::instance()->mesh.lineLabels || CTX::instance()->mesh.surfaceLabels ||
308      CTX::instance()->mesh.volumeLabels)
309     numStrings = std::max(numStrings, GModel::current()->getNumMeshElements());
310   numStrings *= 2;
311   if(gl_texture_pile_height() < numStrings) gl_texture_pile_height(numStrings);
312 #endif
313 
314   glDepthFunc(GL_LESS);
315   glEnable(GL_DEPTH_TEST);
316   initProjection();
317   initRenderModel();
318 
319   if(!CTX::instance()->camera) initPosition(true);
320   drawAxes();
321   drawGeom();
322   drawBackgroundImage(true);
323   drawMesh();
324   drawPost();
325   // drawAxes();
326   drawGraph2d(true);
327 }
328 
draw2d()329 void drawContext::draw2d()
330 {
331   glDisable(GL_DEPTH_TEST);
332   for(int i = 0; i < 6; i++) glDisable((GLenum)(GL_CLIP_PLANE0 + i));
333 
334   glMatrixMode(GL_PROJECTION);
335   glLoadIdentity();
336 
337   glOrtho((double)viewport[0], (double)viewport[2], (double)viewport[1],
338           (double)viewport[3], -100.,
339           100.); // in pixels, so we can draw some 3D glyphs
340 
341   // hack to make the 2D primitives appear "in front" in GL2PS
342   glTranslated(0., 0.,
343                CTX::instance()->clipFactor > 1. ?
344                  1. / CTX::instance()->clipFactor :
345                  CTX::instance()->clipFactor);
346   glMatrixMode(GL_MODELVIEW);
347 
348   glLoadIdentity();
349   drawGraph2d(false);
350   drawText2d();
351   if(CTX::instance()->post.draw && !CTX::instance()->stereo) drawScales();
352   if(CTX::instance()->smallAxes) drawSmallAxes();
353 }
354 
drawBackgroundGradient()355 void drawContext::drawBackgroundGradient()
356 {
357   if(CTX::instance()->bgGradient == 1) { // vertical
358     glBegin(GL_QUADS);
359     glColor4ubv((GLubyte *)&CTX::instance()->color.bg);
360     glVertex2i(viewport[0], viewport[1]);
361     glVertex2i(viewport[2], viewport[1]);
362     glColor4ubv((GLubyte *)&CTX::instance()->color.bgGrad);
363     glVertex2i(viewport[2], viewport[3]);
364     glVertex2i(viewport[0], viewport[3]);
365     glEnd();
366   }
367   else if(CTX::instance()->bgGradient == 2) { // horizontal
368     glBegin(GL_QUADS);
369     glColor4ubv((GLubyte *)&CTX::instance()->color.bg);
370     glVertex2i(viewport[2], viewport[1]);
371     glVertex2i(viewport[2], viewport[3]);
372     glColor4ubv((GLubyte *)&CTX::instance()->color.bgGrad);
373     glVertex2i(viewport[0], viewport[3]);
374     glVertex2i(viewport[0], viewport[1]);
375     glEnd();
376   }
377   else if(CTX::instance()->bgGradient == 3) { // radial
378     double cx = 0.5 * (viewport[0] + viewport[2]);
379     double cy = 0.5 * (viewport[1] + viewport[3]);
380     double r =
381       0.5 * std::max(viewport[2] - viewport[0], viewport[3] - viewport[1]);
382     glBegin(GL_TRIANGLE_FAN);
383     glColor4ubv((GLubyte *)&CTX::instance()->color.bgGrad);
384     glVertex2d(cx, cy);
385     glColor4ubv((GLubyte *)&CTX::instance()->color.bg);
386     glVertex2d(cx + r, cy);
387     int ntheta = 36;
388     for(int i = 1; i < ntheta + 1; i++) {
389       double theta = i * 2 * M_PI / (double)ntheta;
390       glVertex2d(cx + r * cos(theta), cy + r * sin(theta));
391     }
392     glEnd();
393   }
394 }
395 
invalidateBgImageTexture()396 void drawContext::invalidateBgImageTexture()
397 {
398   if(_bgImageTexture) glDeleteTextures(1, &_bgImageTexture);
399   _bgImageTexture = 0;
400 }
401 
generateTextureForImage(const std::string & name,int page,GLuint & imageTexture,GLuint & imageW,GLuint & imageH)402 bool drawContext::generateTextureForImage(const std::string &name, int page,
403                                           GLuint &imageTexture, GLuint &imageW,
404                                           GLuint &imageH)
405 {
406   if(StatFile(name)) {
407     Msg::Error("Could not open file `%s'", name.c_str());
408     return false;
409   }
410 
411   std::string ext = SplitFileName(name)[2];
412   if(ext == ".pdf" || ext == ".PDF") {
413 #if defined(HAVE_POPPLER)
414     if(!imageTexture) {
415       if(!gmshPopplerWrapper::instance()->loadFromFile(name)) {
416         Msg::Error("Could not load PDF file '%s'", name.c_str());
417         return false;
418       }
419     }
420     gmshPopplerWrapper::instance()->setCurrentPage(page);
421     imageTexture = gmshPopplerWrapper::instance()->getTextureForPage(300, 300);
422     imageW = gmshPopplerWrapper::instance()->width();
423     imageH = gmshPopplerWrapper::instance()->height();
424 #else
425     Msg::Error("Gmsh must be compiled with Poppler support to load PDFs");
426     return false;
427 #endif
428   }
429   else {
430 #if defined(HAVE_FLTK)
431     if(!imageTexture) {
432       Fl_RGB_Image *img = nullptr;
433       if(ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || ext == ".JPEG")
434         img = new Fl_JPEG_Image(name.c_str());
435       else if(ext == ".png" || ext == ".PNG")
436         img = new Fl_PNG_Image(name.c_str());
437       if(!img) {
438         Msg::Error("Could not load background image '%s'", name.c_str());
439         return false;
440       }
441       Fl_RGB_Image *img2 = (Fl_RGB_Image *)img->copy(2048, 2048);
442       glPixelStorei(GL_UNPACK_ROW_LENGTH, img2->w());
443       glGenTextures(1, &imageTexture);
444       glBindTexture(GL_TEXTURE_2D, imageTexture);
445       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
446       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
447       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img2->w(), img2->h(), 0,
448                    (img2->d() == 4) ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE,
449                    img2->array);
450       glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
451       imageW = img->w();
452       imageH = img->h();
453       delete img;
454       delete img2;
455     }
456 #else
457     Msg::Error("Gmsh must be compiled with FLTK support to load JPEGs or PNGs");
458     return false;
459 #endif
460   }
461   return true;
462 }
463 
drawBackgroundImage(bool threeD)464 void drawContext::drawBackgroundImage(bool threeD)
465 {
466   if(CTX::instance()->bgImageFileName.empty() ||
467      (CTX::instance()->bgImage3d && !threeD) ||
468      (!CTX::instance()->bgImage3d && threeD))
469     return;
470 
471   std::string name = FixRelativePath(GModel::current()->getFileName(),
472                                      CTX::instance()->bgImageFileName);
473 
474   double x = CTX::instance()->bgImagePosition[0];
475   double y = CTX::instance()->bgImagePosition[1];
476   double w = CTX::instance()->bgImageSize[0];
477   double h = CTX::instance()->bgImageSize[1];
478 
479   if(!generateTextureForImage(name, CTX::instance()->bgImagePage,
480                               _bgImageTexture, _bgImageW, _bgImageH)) {
481     CTX::instance()->bgImageFileName.clear();
482     return;
483   }
484 
485   if(!_bgImageTexture) return;
486 
487   if(w < 0 && h < 0) {
488     w = viewport[2] - viewport[0];
489     h = viewport[3] - viewport[1];
490   }
491   else if(w < 0 && h == 0) {
492     w = viewport[2] - viewport[0];
493     h = w * _bgImageH / _bgImageW;
494   }
495   else if(w < 0) {
496     w = viewport[2] - viewport[0];
497   }
498   else if(w == 0 && h < 0) {
499     h = viewport[3] - viewport[1];
500     w = h * _bgImageW / _bgImageH;
501   }
502   else if(h < 0) {
503     h = viewport[3] - viewport[1];
504   }
505   else if(w == 0 && h == 0) {
506     w = _bgImageW;
507     h = _bgImageH;
508   }
509   else if(h == 0) {
510     h = w * _bgImageH / _bgImageW;
511   }
512   else if(w == 0) {
513     w = h * _bgImageW / _bgImageH;
514   }
515 
516   Msg::Debug("Background image: x=%g y=%g w=%g h=%g", x, y, w, h);
517 
518   glEnable(GL_BLEND);
519   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
520   glEnable(GL_TEXTURE_2D);
521   glBindTexture(GL_TEXTURE_2D, _bgImageTexture);
522   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
523   glBegin(GL_QUADS);
524   if(threeD) {
525     glTexCoord2f(1.0f, 1.0f);
526     glVertex2d(x + w, y);
527     glTexCoord2f(1.0f, 0.0f);
528     glVertex2d(x + w, y + h);
529     glTexCoord2f(0.0f, 0.0f);
530     glVertex2d(x, y + h);
531     glTexCoord2f(0.0f, 1.0f);
532     glVertex2d(x, y);
533   }
534   else {
535     int c = fix2dCoordinates(&x, &y); // y=0 now means top
536     if(c & 1) x -= w / 2.;
537     if(c & 2) y += h / 2.;
538     if(x < viewport[0]) x = viewport[0];
539     if(y < viewport[1]) y = viewport[1];
540     glTexCoord2f(1.0f, 1.0f);
541     glVertex2d(x + w, y - h);
542     glTexCoord2f(1.0f, 0.0f);
543     glVertex2d(x + w, y);
544     glTexCoord2f(0.0f, 0.0f);
545     glVertex2d(x, y);
546     glTexCoord2f(0.0f, 1.0f);
547     glVertex2d(x, y - h);
548   }
549   glEnd();
550   glDisable(GL_TEXTURE_2D);
551   glDisable(GL_BLEND);
552 }
553 
initProjection(int xpick,int ypick,int wpick,int hpick)554 void drawContext::initProjection(int xpick, int ypick, int wpick, int hpick)
555 {
556   double Va =
557     (double)(viewport[3] - viewport[1]) / (double)(viewport[2] - viewport[0]);
558   double Wa = (CTX::instance()->max[1] - CTX::instance()->min[1]) /
559               (CTX::instance()->max[0] - CTX::instance()->min[0]);
560 
561   // compute the viewport in World coordinates (with margins)
562   if(Va > Wa) {
563     vxmin = CTX::instance()->min[0];
564     vxmax = CTX::instance()->max[0];
565     vymin = 0.5 * (CTX::instance()->min[1] + CTX::instance()->max[1] -
566                    Va * (CTX::instance()->max[0] - CTX::instance()->min[0]));
567     vymax = 0.5 * (CTX::instance()->min[1] + CTX::instance()->max[1] +
568                    Va * (CTX::instance()->max[0] - CTX::instance()->min[0]));
569   }
570   else {
571     vxmin = 0.5 * (CTX::instance()->min[0] + CTX::instance()->max[0] -
572                    (CTX::instance()->max[1] - CTX::instance()->min[1]) / Va);
573     vxmax = 0.5 * (CTX::instance()->min[0] + CTX::instance()->max[0] +
574                    (CTX::instance()->max[1] - CTX::instance()->min[1]) / Va);
575     vymin = CTX::instance()->min[1];
576     vymax = CTX::instance()->max[1];
577   }
578   double fact = CTX::instance()->displayBorderFactor;
579   double xborder = fact * (vxmax - vxmin), yborder = fact * (vymax - vymin);
580   vxmin -= xborder;
581   vxmax += xborder;
582   vymin -= yborder;
583   vymax += yborder;
584 
585   // Put the origin of World coordinates at center of viewport
586   // (this is necessary for the scaling to be applied at center of viewport
587   // instead of at initial position of center of gravity)
588   vxmin -= CTX::instance()->cg[0];
589   vxmax -= CTX::instance()->cg[0];
590   vymin -= CTX::instance()->cg[1];
591   vymax -= CTX::instance()->cg[1];
592 
593   // store what one pixel represents in world coordinates
594   pixel_equiv_x = (vxmax - vxmin) / (viewport[2] - viewport[0]);
595   pixel_equiv_y = (vymax - vymin) / (viewport[3] - viewport[1]);
596 
597   // no initial translation of the model
598   t_init[0] = t_init[1] = t_init[2] = 0.;
599 
600   // set up the near and far clipping planes so that the box is large enough to
601   // manipulate the model and zoom, but not too big (otherwise the z-buffer
602   // resolution e.g. with Mesa can become insufficient)
603   double zmax =
604     std::max(fabs(CTX::instance()->min[2]), fabs(CTX::instance()->max[2]));
605   if(zmax < CTX::instance()->lc) zmax = CTX::instance()->lc;
606 
607   if(CTX::instance()->camera) { // if we use the camera mode
608     glDisable(GL_DEPTH_TEST);
609     glPushMatrix();
610     glLoadIdentity();
611     double w = (double)viewport[2];
612     double h = (double)viewport[3];
613     double ratio = w / h;
614     double dx = 1.5 * tan(camera.radians) * w * ratio;
615     double dy = 1.5 * tan(camera.radians) * w;
616     double dz = -w * 1.25;
617     glBegin(GL_QUADS);
618     glColor4ubv((GLubyte *)&CTX::instance()->color.bg);
619     glVertex3i((int)-dx, (int)-dy, (int)dz);
620     glVertex3i((int)dx, (int)-dy, (int)dz);
621     glColor4ubv((GLubyte *)&CTX::instance()->color.bgGrad);
622     glVertex3i((int)dx, (int)dy, (int)dz);
623     glVertex3i((int)-dx, (int)dy, (int)dz);
624     glEnd();
625     glPopMatrix();
626     glEnable(GL_DEPTH_TEST);
627   }
628   else if(!CTX::instance()->camera) { // if not in camera mode
629 
630     double clip_near, clip_far;
631     if(CTX::instance()->ortho) {
632       clip_near = -zmax * s[2] * CTX::instance()->clipFactor;
633       clip_far = -clip_near;
634     }
635     else {
636       clip_near = 0.75 * CTX::instance()->clipFactor * zmax;
637       clip_far = 75. * CTX::instance()->clipFactor * zmax;
638     }
639     // setup projection matrix
640     glMatrixMode(GL_PROJECTION);
641     glLoadIdentity();
642 
643     // restrict picking to a rectangular region around xpick,ypick
644     if(render_mode == GMSH_SELECT)
645       gluPickMatrix((GLdouble)xpick, (GLdouble)(viewport[3] - ypick),
646                     (GLdouble)wpick, (GLdouble)hpick, (GLint *)viewport);
647 
648     // draw background if not in selection mode
649     if(render_mode != GMSH_SELECT &&
650        (CTX::instance()->bgGradient ||
651         CTX::instance()->bgImageFileName.size()) &&
652        (!CTX::instance()->printing || CTX::instance()->print.background)) {
653       glDisable(GL_DEPTH_TEST);
654       glPushMatrix();
655       glLoadIdentity();
656       // the z values and the translation are only needed for GL2PS, which does
657       // not understand "no depth test" (hence we must make sure that we draw
658       // the background behind the rest of the scene)
659       glOrtho((double)viewport[0], (double)viewport[2], (double)viewport[1],
660               (double)viewport[3], clip_near, clip_far);
661       glTranslated(0., 0., -0.99 * clip_far);
662       drawBackgroundGradient();
663       // hack for GL2PS (to make sure that the image is in front of the
664       // gradient)
665       glTranslated(0., 0., 0.01 * clip_far);
666       drawBackgroundImage(false);
667       glPopMatrix();
668       glEnable(GL_DEPTH_TEST);
669     }
670 
671     if(CTX::instance()->ortho) {
672       glOrtho(vxmin, vxmax, vymin, vymax, clip_near, clip_far);
673       glMatrixMode(GL_MODELVIEW);
674       glLoadIdentity();
675     }
676     else {
677       // recenter the model such that the perspective is always at the center of
678       // gravity (we should maybe add an option to choose this, as we do for the
679       // rotation center)
680       t_init[0] = CTX::instance()->cg[0];
681       t_init[1] = CTX::instance()->cg[1];
682       vxmin -= t_init[0];
683       vxmax -= t_init[0];
684       vymin -= t_init[1];
685       vymax -= t_init[1];
686       glFrustum(vxmin, vxmax, vymin, vymax, clip_near, clip_far);
687       glMatrixMode(GL_MODELVIEW);
688       glLoadIdentity();
689       double coef = (clip_far / clip_near) / 3.;
690       glTranslated(-coef * t_init[0], -coef * t_init[1], -coef * clip_near);
691       glScaled(coef, coef, coef);
692     }
693   }
694 }
695 
initRenderModel()696 void drawContext::initRenderModel()
697 {
698   glPushMatrix();
699   glLoadIdentity();
700   glScaled(s[0], s[1], s[2]);
701   glTranslated(t[0], t[1], t[2]);
702 
703   for(int i = 0; i < 6; i++) {
704     if(CTX::instance()->light[i]) {
705       GLfloat position[4] = {(GLfloat)CTX::instance()->lightPosition[i][0],
706                              (GLfloat)CTX::instance()->lightPosition[i][1],
707                              (GLfloat)CTX::instance()->lightPosition[i][2],
708                              (GLfloat)CTX::instance()->lightPosition[i][3]};
709       glLightfv((GLenum)(GL_LIGHT0 + i), GL_POSITION, position);
710 
711       GLfloat r = (GLfloat)(
712         CTX::instance()->unpackRed(CTX::instance()->color.ambientLight[i]) /
713         255.);
714       GLfloat g = (GLfloat)(
715         CTX::instance()->unpackGreen(CTX::instance()->color.ambientLight[i]) /
716         255.);
717       GLfloat b = (GLfloat)(
718         CTX::instance()->unpackBlue(CTX::instance()->color.ambientLight[i]) /
719         255.);
720       GLfloat ambient[4] = {r, g, b, 1.0F};
721       glLightfv((GLenum)(GL_LIGHT0 + i), GL_AMBIENT, ambient);
722 
723       r = (GLfloat)(
724         CTX::instance()->unpackRed(CTX::instance()->color.diffuseLight[i]) /
725         255.);
726       g = (GLfloat)(
727         CTX::instance()->unpackGreen(CTX::instance()->color.diffuseLight[i]) /
728         255.);
729       b = (GLfloat)(
730         CTX::instance()->unpackBlue(CTX::instance()->color.diffuseLight[i]) /
731         255.);
732       GLfloat diffuse[4] = {r, g, b, 1.0F};
733       glLightfv((GLenum)(GL_LIGHT0 + i), GL_DIFFUSE, diffuse);
734 
735       r = (GLfloat)(
736         CTX::instance()->unpackRed(CTX::instance()->color.specularLight[i]) /
737         255.);
738       g = (GLfloat)(
739         CTX::instance()->unpackGreen(CTX::instance()->color.specularLight[i]) /
740         255.);
741       b = (GLfloat)(
742         CTX::instance()->unpackBlue(CTX::instance()->color.specularLight[i]) /
743         255.);
744       GLfloat specular[4] = {r, g, b, 1.0F};
745       glLightfv((GLenum)(GL_LIGHT0 + i), GL_SPECULAR, specular);
746 
747       glEnable((GLenum)(GL_LIGHT0 + i));
748     }
749     else {
750       glDisable((GLenum)(GL_LIGHT0 + i));
751     }
752   }
753 
754   glPopMatrix();
755 
756   // ambient and diffuse material colors track glColor automatically
757   glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
758   glEnable(GL_COLOR_MATERIAL);
759   // "white"-only specular material reflection color
760   GLfloat spec[4] = {(GLfloat)CTX::instance()->shine,
761                      (GLfloat)CTX::instance()->shine,
762                      (GLfloat)CTX::instance()->shine, 1.0F};
763   glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, spec);
764   // specular exponent in [0,128] (larger means more "focused"
765   // reflection)
766   glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS,
767               (GLfloat)CTX::instance()->shineExponent);
768 
769   glShadeModel(GL_SMOOTH);
770 
771   // Normalize the normals automatically. Using glEnable(GL_RESCALE_NORMAL)
772   // instead of glEnable(GL_NORMALIZE) (since we initially specify unit normals)
773   // is more efficient, but will only work with isotropic scalings (and we allow
774   // anistotropic scalings in myZoom...). Note that GL_RESCALE_NORMAL is only
775   // available in GL_VERSION_1_2.
776 #if defined(WIN32)
777   glEnable(GL_NORMALIZE);
778 #else
779   glEnable(GL_RESCALE_NORMAL);
780 #endif
781 
782   // lighting is enabled/disabled for each particular primitive later
783   glDisable(GL_LIGHTING);
784 }
785 
initPosition(bool saveMatrices)786 void drawContext::initPosition(bool saveMatrices)
787 {
788   // NB: Those operations are applied to the model in the view coordinates
789   // (in opposite order)
790   glScaled(s[0], s[1], s[2]);
791   glTranslated(t[0] - CTX::instance()->cg[0], t[1] - CTX::instance()->cg[1],
792                t[2] - CTX::instance()->cg[2]);
793   if(CTX::instance()->rotationCenterCg)
794     glTranslated(CTX::instance()->cg[0], CTX::instance()->cg[1],
795                  CTX::instance()->cg[2]);
796   else
797     glTranslated(CTX::instance()->rotationCenter[0],
798                  CTX::instance()->rotationCenter[1],
799                  CTX::instance()->rotationCenter[2]);
800 
801   buildRotationMatrix();
802   glMultMatrixd(rot);
803 
804   if(CTX::instance()->rotationCenterCg)
805     glTranslated(-CTX::instance()->cg[0], -CTX::instance()->cg[1],
806                  -CTX::instance()->cg[2]);
807   else
808     glTranslated(-CTX::instance()->rotationCenter[0],
809                  -CTX::instance()->rotationCenter[1],
810                  -CTX::instance()->rotationCenter[2]);
811 
812   // store the projection and modelview matrices at this precise moment (so that
813   // we can use them at any later time, even if the context has changed, i.e.,
814   // even if we are out of draw())
815   if(saveMatrices) {
816     glGetDoublev(GL_PROJECTION_MATRIX, proj);
817     glGetDoublev(GL_MODELVIEW_MATRIX, model);
818   }
819 
820   for(int i = 0; i < 6; i++)
821     glClipPlane((GLenum)(GL_CLIP_PLANE0 + i), CTX::instance()->clipPlane[i]);
822 }
823 
824 // Takes a cursor position in window coordinates and returns the line (given by
825 // a point and a unit direction vector), in real space, that corresponds to that
826 // cursor position
unproject(double winx,double winy,double p[3],double d[3])827 void drawContext::unproject(double winx, double winy, double p[3], double d[3])
828 {
829   // get true pixels
830   double fact = highResolutionPixelFactor();
831   winx *= fact;
832   winy *= fact;
833 
834   GLint vp[4];
835   glGetIntegerv(GL_VIEWPORT, vp);
836 
837   winy = vp[3] - winy;
838 
839   GLdouble x0, y0, z0, x1, y1, z1;
840 
841   // we use the stored model and proj matrices instead of directly
842   // getGetDouble'ing the matrices since unproject can be called in or after
843   // draw2d
844   if(!gluUnProject(winx, winy, 0.0, model, proj, vp, &x0, &y0, &z0))
845     Msg::Warning("unproject1 failed");
846   if(!gluUnProject(winx, winy, 1.0, model, proj, vp, &x1, &y1, &z1))
847     Msg::Warning("unproject2 failed");
848 
849   p[0] = x0;
850   p[1] = y0;
851   p[2] = z0;
852   d[0] = x1 - x0;
853   d[1] = y1 - y0;
854   d[2] = z1 - z0;
855   double len = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
856   d[0] /= len;
857   d[1] /= len;
858   d[2] /= len;
859 }
860 
viewport2World(double vp[3],double xyz[3])861 void drawContext::viewport2World(double vp[3], double xyz[3])
862 {
863   GLint viewport[4];
864   GLdouble model[16], proj[16];
865   glGetIntegerv(GL_VIEWPORT, viewport);
866   glGetDoublev(GL_PROJECTION_MATRIX, proj);
867   glGetDoublev(GL_MODELVIEW_MATRIX, model);
868   gluUnProject(vp[0], vp[1], vp[2], model, proj, viewport, &xyz[0], &xyz[1],
869                &xyz[2]);
870 }
871 
world2Viewport(double xyz[3],double vp[3])872 void drawContext::world2Viewport(double xyz[3], double vp[3])
873 {
874   GLint viewport[4];
875   GLdouble model[16], proj[16];
876   glGetIntegerv(GL_VIEWPORT, viewport);
877   glGetDoublev(GL_PROJECTION_MATRIX, proj);
878   glGetDoublev(GL_MODELVIEW_MATRIX, model);
879   gluProject(xyz[0], xyz[1], xyz[2], model, proj, viewport, &vp[0], &vp[1],
880              &vp[2]);
881 }
882 
883 class hit {
884 public:
885   GLuint type, ient, depth, type2, ient2;
hit(GLuint t,GLuint i,GLuint d,GLuint t2=0,GLuint i2=0)886   hit(GLuint t, GLuint i, GLuint d, GLuint t2 = 0, GLuint i2 = 0)
887     : type(t), ient(i), depth(d), type2(t2), ient2(i2)
888   {
889   }
890 };
891 
892 class hitDepthLessThan {
893 public:
operator ()(const hit & h1,const hit & h2) const894   bool operator()(const hit &h1, const hit &h2) const
895   {
896     return h1.depth < h2.depth;
897   }
898 };
899 
900 // returns the element at a given position in a vertex array (element pointers
901 // are not always stored: returning 0 is not an error)
getElement(GEntity * e,int va_type,int index)902 static MElement *getElement(GEntity *e, int va_type, int index)
903 {
904   switch(va_type) {
905   case 2:
906     if(e->va_lines && index < e->va_lines->getNumElementPointers())
907       return *e->va_lines->getElementPointerArray(index);
908     break;
909   case 3:
910     if(e->va_triangles && index < e->va_triangles->getNumElementPointers())
911       return *e->va_triangles->getElementPointerArray(index);
912     break;
913   }
914   return nullptr;
915 }
916 
select(int type,bool multiple,bool mesh,bool post,int x,int y,int w,int h,std::vector<GVertex * > & vertices,std::vector<GEdge * > & edges,std::vector<GFace * > & faces,std::vector<GRegion * > & regions,std::vector<MElement * > & elements,std::vector<SPoint2> & points,std::vector<PView * > & views)917 bool drawContext::select(int type, bool multiple, bool mesh, bool post, int x,
918                          int y, int w, int h, std::vector<GVertex *> &vertices,
919                          std::vector<GEdge *> &edges,
920                          std::vector<GFace *> &faces,
921                          std::vector<GRegion *> &regions,
922                          std::vector<MElement *> &elements,
923                          std::vector<SPoint2> &points,
924                          std::vector<PView *> &views)
925 {
926   vertices.clear();
927   edges.clear();
928   faces.clear();
929   regions.clear();
930   elements.clear();
931   points.clear();
932   views.clear();
933 
934   // in our case the selection buffer size is equal to between 5 and 7 times the
935   // maximum number of possible hits
936   GModel *m = GModel::current();
937   int eles =
938     (mesh && CTX::instance()->pickElements) ? 4 * m->getNumMeshElements() : 0;
939   int nviews = PView::list.size() * 100;
940   int size = 7 * (m->getNumVertices() + m->getNumEdges() + m->getNumFaces() +
941                   m->getNumRegions() + eles) +
942              nviews;
943   if(!size) return false; // the model is empty, don't bother!
944 
945   // allocate selection buffer
946   size += 1000; // just to make sure
947   GLuint *selectionBuffer = new GLuint[size];
948   glSelectBuffer(size, selectionBuffer);
949 
950   // do one rendering pass in select mode
951   render_mode = drawContext::GMSH_SELECT;
952   glRenderMode(GL_SELECT);
953   glInitNames();
954   glPushMatrix();
955 
956   // 3d stuff
957   initProjection(x, y, w, h);
958   initPosition(false);
959   drawGeom();
960   if(mesh) drawMesh();
961   if(post) drawPost();
962   drawGraph2d(true);
963 
964   // 2d stuff
965   glMatrixMode(GL_PROJECTION);
966   glLoadIdentity();
967   gluPickMatrix((GLdouble)x, (GLdouble)(viewport[3] - y), (GLdouble)w,
968                 (GLdouble)h, (GLint *)viewport);
969   glOrtho((double)viewport[0], (double)viewport[2], (double)viewport[1],
970           (double)viewport[3], -100.,
971           100.); // in pixels, so we can draw some 3D glyphs
972   glMatrixMode(GL_MODELVIEW);
973   glLoadIdentity();
974   drawGraph2d(false);
975   drawText2d();
976 
977   glPopMatrix();
978 
979   GLint numhits = glRenderMode(GL_RENDER);
980   render_mode = drawContext::GMSH_RENDER;
981 
982   if(!numhits) { // no hits
983     delete[] selectionBuffer;
984     return false;
985   }
986   else if(numhits < 0) { // overflow
987     delete[] selectionBuffer;
988     Msg::Warning("Too many entities selected");
989     return false;
990   }
991 
992   // decode the hits
993   std::vector<hit> hits;
994   GLuint *ptr = selectionBuffer;
995   for(int i = 0; i < numhits; i++) {
996     // in Gmsh 'names' should always be 0, 2 or 4:
997     // * names == 0 means that there is nothing on the stack
998     // * if names == 2, the first name is the type of the entity (0 for point, 1
999     //   for edge, 2 for face or 3 for volume) and the second is the entity
1000     //   number;
1001     // * if names == 4, the first name is the type of the entity, the second is
1002     //   the entity number, the third is the type of vertex array (2 for line, 3
1003     //   for triangle, 4 for quad) and the fourth is the index of the element in
1004     //   the vertex array
1005     GLuint names = *ptr++;
1006     GLuint mindepth = *ptr++;
1007     GLuint maxdepth = *ptr++;
1008     if(names == 2) {
1009       GLuint depth =
1010         maxdepth + 0 * mindepth; // could do something with mindepth
1011       GLuint type = *ptr++;
1012       GLuint ient = *ptr++;
1013       hits.push_back(hit(type, ient, depth));
1014     }
1015     else if(names == 4) {
1016       GLuint depth =
1017         maxdepth + 0 * mindepth; // could do something with mindepth
1018       GLuint type = *ptr++;
1019       GLuint ient = *ptr++;
1020       GLuint type2 = *ptr++;
1021       GLuint ient2 = *ptr++;
1022       hits.push_back(hit(type, ient, depth, type2, ient2));
1023     }
1024   }
1025 
1026   delete[] selectionBuffer;
1027 
1028   if(!hits.size()) { // no entities
1029     return false;
1030   }
1031 
1032   // sort hits to get closest entities first
1033   std::sort(hits.begin(), hits.end(), hitDepthLessThan());
1034 
1035   // filter result: if type == ENT_NONE, return the closest entity of "lowest
1036   // dimension" (point < line < surface < volume). Otherwise, return the closest
1037   // entity of type "type"
1038   GLuint typmin = 10;
1039   for(std::size_t i = 0; i < hits.size(); i++)
1040     typmin = std::min(typmin, hits[i].type);
1041 
1042   for(std::size_t i = 0; i < hits.size(); i++) {
1043     if((type == ENT_ALL) || (type == ENT_NONE && hits[i].type == typmin) ||
1044        (type == ENT_POINT && hits[i].type == 0) ||
1045        (type == ENT_CURVE && hits[i].type == 1) ||
1046        (type == ENT_SURFACE && hits[i].type == 2) ||
1047        (type == ENT_VOLUME && hits[i].type == 3)) {
1048       switch(hits[i].type) {
1049       case 0: {
1050         GVertex *v = m->getVertexByTag(hits[i].ient);
1051         if(!v) {
1052           Msg::Error("Problem in point selection processing");
1053           return false;
1054         }
1055         vertices.push_back(v);
1056         if(!multiple) return true;
1057       } break;
1058       case 1: {
1059         GEdge *e = m->getEdgeByTag(hits[i].ient);
1060         if(!e) {
1061           Msg::Error("Problem in line selection processing");
1062           return false;
1063         }
1064         if(hits[i].type2) {
1065           MElement *ele = getElement(e, hits[i].type2, hits[i].ient2);
1066           if(ele) elements.push_back(ele);
1067         }
1068         edges.push_back(e);
1069         if(!multiple) return true;
1070       } break;
1071       case 2: {
1072         GFace *f = m->getFaceByTag(hits[i].ient);
1073         if(!f) {
1074           Msg::Error("Problem in surface selection processing");
1075           return false;
1076         }
1077         if(hits[i].type2) {
1078           MElement *ele = getElement(f, hits[i].type2, hits[i].ient2);
1079           if(ele) elements.push_back(ele);
1080         }
1081         faces.push_back(f);
1082         if(!multiple) return true;
1083       } break;
1084       case 3: {
1085         GRegion *r = m->getRegionByTag(hits[i].ient);
1086         if(!r) {
1087           Msg::Error("Problem in volume selection processing");
1088           return false;
1089         }
1090         if(hits[i].type2) {
1091           MElement *ele = getElement(r, hits[i].type2, hits[i].ient2);
1092           if(ele) elements.push_back(ele);
1093         }
1094         regions.push_back(r);
1095         if(!multiple) return true;
1096       } break;
1097       case 4: {
1098         int tag = hits[i].ient;
1099         SPoint2 p = getGraph2dDataPointForTag(tag);
1100         points.push_back(p);
1101         if(!multiple) return true;
1102       } break;
1103       case 5: {
1104         int tag = hits[i].ient;
1105         if(tag >= 0 && tag < (int)PView::list.size())
1106           views.push_back(PView::list[tag]);
1107         if(!multiple) return true;
1108       } break;
1109       }
1110     }
1111   }
1112 
1113   if(vertices.size() || edges.size() || faces.size() || regions.size() ||
1114      elements.size() || points.size() || views.size())
1115     return true;
1116   return false;
1117 }
1118 
recenterForRotationCenterChange(SPoint3 newRotationCenter)1119 void drawContext::recenterForRotationCenterChange(SPoint3 newRotationCenter)
1120 {
1121   // Recompute model translation so that the view is not changed
1122   SPoint3 &p = newRotationCenter;
1123   double vp[3];
1124   gluProject(p.x(), p.y(), p.z(), model, proj, viewport, &vp[0], &vp[1],
1125              &vp[2]);
1126   double wnr[3]; // look at mousePosition::recenter()
1127   const double &width = viewport[2];
1128   const double &height = viewport[3];
1129   wnr[0] =
1130     (vxmin + vp[0] / width * (vxmax - vxmin)) / s[0] - t[0] + t_init[0] / s[0];
1131   wnr[1] =
1132     (vymin + vp[1] / height * (vymax - vymin)) / s[1] - t[1] + t_init[1] / s[1];
1133   t[0] += wnr[0] + CTX::instance()->cg[0] - p.x();
1134   t[1] += wnr[1] + CTX::instance()->cg[1] - p.y();
1135 }
1136