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