1 /*
2 * OpenSCAD (www.openscad.org)
3 * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4 * Marius Kintel <marius@kintel.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * As a special exception, you have permission to link this program
12 * with the CGAL library and distribute executables, as long as you
13 * follow the requirements of the GNU GPL in regard to all of the
14 * software in the executable aside from CGAL.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27 #include "qtgettext.h"
28 #include "QGLView.h"
29 #include "Preferences.h"
30 #include "renderer.h"
31 #include "degree_trig.h"
32
33 #include <QApplication>
34 #include <QWheelEvent>
35 #include <QCheckBox>
36 #include <QDialogButtonBox>
37 #include <QMouseEvent>
38 #include <QMessageBox>
39 #include <QPushButton>
40 #include <QTimer>
41 #include <QTextEdit>
42 #include <QVBoxLayout>
43 #include <QErrorMessage>
44 #include "OpenCSGWarningDialog.h"
45 #include "QSettingsCached.h"
46
47
48 #include <stdio.h>
49 #include <sstream>
50
51 #ifdef ENABLE_OPENCSG
52 # include <opencsg.h>
53 #endif
54
QGLView(QWidget * parent)55 QGLView::QGLView(QWidget *parent) :
56 #ifdef USE_QOPENGLWIDGET
57 QOpenGLWidget(parent)
58 #else
59 QGLWidget(parent)
60 #endif
61 {
62 init();
63 }
64
65 #if defined(_WIN32) && !defined(USE_QOPENGLWIDGET)
66 static bool running_under_wine = false;
67 #endif
68
init()69 void QGLView::init()
70 {
71 resetView();
72
73 this->mouse_drag_active = false;
74 this->statusLabel = nullptr;
75
76 setMouseTracking(true);
77
78
79
80 #if defined(_WIN32) && !defined(USE_QOPENGLWIDGET)
81 // see paintGL() + issue160 + wine FAQ
82 #include <windows.h>
83 HMODULE hntdll = GetModuleHandle(L"ntdll.dll");
84 if (hntdll)
85 if ( (void *)GetProcAddress(hntdll, "wine_get_version") )
86 running_under_wine = true;
87 #endif
88 }
89
resetView()90 void QGLView::resetView()
91 {
92 cam.resetView();
93 }
94
viewAll()95 void QGLView::viewAll()
96 {
97 if (auto renderer = this->getRenderer()) {
98 auto bbox = renderer->getBoundingBox();
99 cam.autocenter = true;
100 cam.viewAll(renderer->getBoundingBox());
101 }
102 }
103
initializeGL()104 void QGLView::initializeGL()
105 {
106 auto err = glewInit();
107 if (err != GLEW_OK) {
108 fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
109 }
110 GLView::initializeGL();
111 }
112
getRendererInfo() const113 std::string QGLView::getRendererInfo() const
114 {
115 std::ostringstream info;
116 info << glew_dump();
117 // Don't translate as translated text in the Library Info dialog is not wanted
118 #ifdef USE_QOPENGLWIDGET
119 info << "\nQt graphics widget: QOpenGLWidget";
120 auto qsf = this->format();
121 auto rbits = qsf.redBufferSize();
122 auto gbits = qsf.greenBufferSize();
123 auto bbits = qsf.blueBufferSize();
124 auto abits = qsf.alphaBufferSize();
125 auto dbits = qsf.depthBufferSize();
126 auto sbits = qsf.stencilBufferSize();
127 info << boost::format("\nQSurfaceFormat: RGBA(%d%d%d%d), depth(%d), stencil(%d)\n\n") %
128 rbits % gbits % bbits % abits % dbits % sbits;
129 #else
130 info << "\nQt graphics widget: QGLWidget";
131 #endif
132 info << glew_extensions_dump();
133 return info.str();
134 }
135
136 #ifdef ENABLE_OPENCSG
display_opencsg_warning()137 void QGLView::display_opencsg_warning()
138 {
139 if (Preferences::inst()->getValue("advanced/opencsg_show_warning").toBool()) {
140 QTimer::singleShot(0, this, SLOT(display_opencsg_warning_dialog()));
141 }
142 }
143
display_opencsg_warning_dialog()144 void QGLView::display_opencsg_warning_dialog()
145 {
146 auto dialog = new OpenCSGWarningDialog(this);
147
148 QString message;
149 if (this->is_opencsg_capable) {
150 message += _("Warning: You may experience OpenCSG rendering errors.\n\n");
151 }
152 else {
153 message += _("Warning: Missing OpenGL capabilities for OpenCSG - OpenCSG has been disabled.\n\n");
154 dialog->enableOpenCSGBox->hide();
155 }
156 message += _("It is highly recommended to use OpenSCAD on a system with "
157 "OpenGL 2.0 or later.\n"
158 "Your renderer information is as follows:\n");
159 QString rendererinfo(_("GLEW version %1\n%2 (%3)\nOpenGL version %4\n"));
160 message += rendererinfo.arg((const char *)glewGetString(GLEW_VERSION),
161 (const char *)glGetString(GL_RENDERER),
162 (const char *)glGetString(GL_VENDOR),
163 (const char *)glGetString(GL_VERSION));
164
165 dialog->setText(message);
166 dialog->enableOpenCSGBox->setChecked(Preferences::inst()->getValue("advanced/enable_opencsg_opengl1x").toBool());
167 dialog->exec();
168
169 opencsg_support = this->is_opencsg_capable && Preferences::inst()->getValue("advanced/enable_opencsg_opengl1x").toBool();
170 }
171 #endif
172
resizeGL(int w,int h)173 void QGLView::resizeGL(int w, int h)
174 {
175 GLView::resizeGL(w,h);
176 }
177
paintGL()178 void QGLView::paintGL()
179 {
180 GLView::paintGL();
181
182 if (statusLabel) {
183 auto status = QString("%1 (%2x%3)")
184 .arg(QString::fromStdString(cam.statusText()))
185 .arg(size().rwidth())
186 .arg(size().rheight());
187 statusLabel->setText(status);
188 }
189
190 #if defined(_WIN32) && !defined(USE_QOPENGLWIDGET)
191 if (running_under_wine) swapBuffers();
192 #endif
193 }
194
mousePressEvent(QMouseEvent * event)195 void QGLView::mousePressEvent(QMouseEvent *event)
196 {
197 if (!mouse_drag_active) {
198 mouse_drag_moved = false;
199 }
200
201 mouse_drag_active = true;
202 last_mouse = event->globalPos();
203 }
204
mouseDoubleClickEvent(QMouseEvent * event)205 void QGLView::mouseDoubleClickEvent (QMouseEvent *event) {
206
207 setupCamera();
208
209 int viewport[4];
210 GLdouble modelview[16];
211 GLdouble projection[16];
212
213 glGetIntegerv( GL_VIEWPORT, viewport);
214 glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
215 glGetDoublev(GL_PROJECTION_MATRIX, projection);
216
217 const double dpi = this->getDPI();
218 const double x = event->pos().x() * dpi;
219 const double y = viewport[3] - event->pos().y() * dpi;
220 GLfloat z = 0;
221
222 glGetError(); // clear error state so we don't pick up previous errors
223 glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z);
224 auto glError = glGetError();
225 if (glError != GL_NO_ERROR) {
226 return;
227 }
228
229 if (z == 1) return; // outside object
230
231 GLdouble px, py, pz;
232
233 auto success = gluUnProject(x, y, z, modelview, projection, viewport, &px, &py, &pz);
234
235 if (success == GL_TRUE) {
236 cam.object_trans -= Vector3d(px, py, pz);
237 updateGL();
238 emit doAnimateUpdate();
239 }
240 }
241
normalizeAngle(GLdouble & angle)242 void QGLView::normalizeAngle(GLdouble& angle)
243 {
244 while(angle < 0) angle += 360;
245 while(angle > 360) angle -= 360;
246 }
247
mouseMoveEvent(QMouseEvent * event)248 void QGLView::mouseMoveEvent(QMouseEvent *event)
249 {
250 auto this_mouse = event->globalPos();
251 double dx = (this_mouse.x() - last_mouse.x()) * 0.7;
252 double dy = (this_mouse.y() - last_mouse.y()) * 0.7;
253 if (mouse_drag_active) {
254 mouse_drag_moved = true;
255 if (event->buttons() & Qt::LeftButton
256 #ifdef Q_OS_MAC
257 && !(event->modifiers() & Qt::MetaModifier)
258 #endif
259 ) {
260 // Left button rotates in xz, Shift-left rotates in xy
261 // On Mac, Ctrl-Left is handled as right button on other platforms
262 if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0) {
263 rotate(dy, dx, 0.0, true);
264 } else {
265 rotate(dy, 0.0, dx, true);
266 }
267
268 normalizeAngle(cam.object_rot.x());
269 normalizeAngle(cam.object_rot.y());
270 normalizeAngle(cam.object_rot.z());
271 } else {
272 // Right button pans in the xz plane
273 // Middle button pans in the xy plane
274 // Shift-right and Shift-middle zooms
275 if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0) {
276 zoom(-12.0 * dy, true);
277 } else {
278 double mx = +(dx) * 3.0 * cam.zoomValue() / QWidget::width();
279 double mz = -(dy) * 3.0 * cam.zoomValue() / QWidget::height();
280 double my = 0;
281 if (event->buttons() & Qt::MiddleButton) {
282 my = mz;
283 mz = 0;
284 // actually lock the x-position
285 // (turns out to be easier to use than xy panning)
286 mx = 0;
287 }
288
289 translate(mx, my, mz, true);
290 }
291 }
292 }
293 last_mouse = this_mouse;
294 }
295
mouseReleaseEvent(QMouseEvent * event)296 void QGLView::mouseReleaseEvent(QMouseEvent *event)
297 {
298 mouse_drag_active = false;
299 releaseMouse();
300
301 if (!mouse_drag_moved
302 && (event->button() == Qt::RightButton)) {
303 QPoint point = event->pos();
304 //point.setY(this->height() - point.y());
305 emit doSelectObject(point);
306 }
307 mouse_drag_moved = false;
308 }
309
grabFrame()310 const QImage & QGLView::grabFrame()
311 {
312 // Force reading from front buffer. Some configurations will read from the back buffer here.
313 glReadBuffer(GL_FRONT);
314 this->frame = grabFrameBuffer();
315 return this->frame;
316 }
317
save(const char * filename) const318 bool QGLView::save(const char *filename) const
319 {
320 return this->frame.save(filename, "PNG");
321 }
322
wheelEvent(QWheelEvent * event)323 void QGLView::wheelEvent(QWheelEvent *event)
324 {
325 const auto pos = event->pos();
326 const int v = event->angleDelta().y();
327 if (this->mouseCentricZoom) {
328 zoomCursor(pos.x(), pos.y(), v);
329 } else {
330 zoom(v, true);
331 }
332 }
333
ZoomIn(void)334 void QGLView::ZoomIn(void)
335 {
336 zoom(120, true);
337 }
338
ZoomOut(void)339 void QGLView::ZoomOut(void)
340 {
341 zoom(-120, true);
342 }
343
zoom(double v,bool relative)344 void QGLView::zoom(double v, bool relative)
345 {
346 this->cam.zoom(v, relative);
347 updateGL();
348 }
349
zoomCursor(int x,int y,int zoom)350 void QGLView::zoomCursor(int x, int y, int zoom)
351 {
352 const auto old_dist = cam.zoomValue();
353 this->cam.zoom(zoom, true);
354 const auto dist = cam.zoomValue();
355 const auto ratio = old_dist / dist - 1.0;
356 // screen coordinates from -1 to 1
357 const auto screen_x = 2.0 * (x + 0.5) / this->cam.pixel_width - 1.0;
358 const auto screen_y = 1.0 - 2.0 * (y + 0.5) / this->cam.pixel_height;
359 const auto height = dist * tan_degrees(cam.fov / 2);
360 const auto mx = ratio*screen_x*(aspectratio*height);
361 const auto mz = ratio*screen_y*height;
362 translate(-mx, 0, -mz, true);
363 }
364
setOrthoMode(bool enabled)365 void QGLView::setOrthoMode(bool enabled)
366 {
367 if (enabled) this->cam.setProjection(Camera::ProjectionType::ORTHOGONAL);
368 else this->cam.setProjection(Camera::ProjectionType::PERSPECTIVE);
369 }
370
translate(double x,double y,double z,bool relative,bool viewPortRelative)371 void QGLView::translate(double x, double y, double z, bool relative, bool viewPortRelative)
372 {
373 Matrix3d aax, aay, aaz;
374 aax = angle_axis_degrees(-cam.object_rot.x(), Vector3d::UnitX());
375 aay = angle_axis_degrees(-cam.object_rot.y(), Vector3d::UnitY());
376 aaz = angle_axis_degrees(-cam.object_rot.z(), Vector3d::UnitZ());
377 Matrix3d tm3 = aaz * aay * aax;
378
379 Matrix4d tm = Matrix4d::Identity();
380 if (viewPortRelative) {
381 for (int i = 0; i < 3; ++i) {
382 for (int j = 0; j < 3; ++j) {
383 tm(j, i) = tm3(j, i);
384 }
385 }
386 }
387
388 Matrix4d vec;
389 vec <<
390 0, 0, 0, x,
391 0, 0, 0, y,
392 0, 0, 0, z,
393 0, 0, 0, 1
394 ;
395 tm = tm * vec;
396 double f = relative ? 1 : 0;
397 cam.object_trans.x() = f * cam.object_trans.x() + tm(0, 3);
398 cam.object_trans.y() = f * cam.object_trans.y() + tm(1, 3);
399 cam.object_trans.z() = f * cam.object_trans.z() + tm(2, 3);
400 updateGL();
401 emit doAnimateUpdate();
402 }
403
rotate(double x,double y,double z,bool relative)404 void QGLView::rotate(double x, double y, double z, bool relative)
405 {
406 double f = relative ? 1 : 0;
407 cam.object_rot.x() = f * cam.object_rot.x() + x;
408 cam.object_rot.y() = f * cam.object_rot.y() + y;
409 cam.object_rot.z() = f * cam.object_rot.z() + z;
410 normalizeAngle(cam.object_rot.x());
411 normalizeAngle(cam.object_rot.y());
412 normalizeAngle(cam.object_rot.z());
413 updateGL();
414 emit doAnimateUpdate();
415 }
416
rotate2(double x,double y,double z)417 void QGLView::rotate2(double x, double y, double z)
418 {
419 // This vector describes the rotation.
420 // The direction of the vector is the angle around which to rotate, and
421 // the length of the vector is the angle by which to rotate
422 Vector3d rot = Vector3d(-x, -y, -z);
423
424 // get current rotation matrix
425 Matrix3d aax, aay, aaz, rmx;
426 aax = angle_axis_degrees(-cam.object_rot.x(), Vector3d::UnitX());
427 aay = angle_axis_degrees(-cam.object_rot.y(), Vector3d::UnitY());
428 aaz = angle_axis_degrees(-cam.object_rot.z(), Vector3d::UnitZ());
429 rmx = aaz * (aay * aax);
430
431 // rotate
432 rmx = rmx * angle_axis_degrees(rot.norm(), rot.normalized());
433
434 // back to euler
435 // see: http://staff.city.ac.uk/~sbbh653/publications/euler.pdf
436 double theta, psi, phi;
437 if (abs(rmx(2, 0)) != 1) {
438 theta = -asin_degrees(rmx(2, 0));
439 psi = atan2_degrees(rmx(2, 1) / cos_degrees(theta), rmx(2, 2) / cos_degrees(theta));
440 phi = atan2_degrees(rmx(1, 0) / cos_degrees(theta), rmx(0, 0) / cos_degrees(theta));
441 } else {
442 phi = 0;
443 if (rmx(2, 0) == -1) {
444 theta = 90;
445 psi = phi + atan2_degrees(rmx(0, 1), rmx(0, 2));
446 } else {
447 theta = -90;
448 psi = -phi + atan2_degrees(-rmx(0, 1), -rmx(0, 2));
449 }
450 }
451
452 cam.object_rot.x() = -psi;
453 cam.object_rot.y() = -theta;
454 cam.object_rot.z() = -phi;
455
456 normalizeAngle(cam.object_rot.x());
457 normalizeAngle(cam.object_rot.y());
458 normalizeAngle(cam.object_rot.z());
459
460 updateGL();
461 emit doAnimateUpdate();
462 }
463