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 *> ®ions,
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