|
1 /* |
|
2 * LDForge: LDraw parts authoring CAD |
|
3 * Copyright (C) 2013 - 2017 Teemu Piippo |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, either version 3 of the License, or |
|
8 * (at your option) any later version. |
|
9 * |
|
10 * This program is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 * GNU General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License |
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 */ |
|
18 |
|
19 #define GL_GLEXT_PROTOTYPES |
|
20 #include <GL/glu.h> |
|
21 #include <GL/glext.h> |
|
22 #include <QContextMenuEvent> |
|
23 #include <QToolTip> |
|
24 #include <QTimer> |
|
25 #include <GL/glu.h> |
|
26 #include "main.h" |
|
27 #include "ldDocument.h" |
|
28 #include "glrenderer.h" |
|
29 #include "colors.h" |
|
30 #include "mainwindow.h" |
|
31 #include "miscallenous.h" |
|
32 #include "editHistory.h" |
|
33 #include "glCompiler.h" |
|
34 #include "primitives.h" |
|
35 #include "documentmanager.h" |
|
36 #include "grid.h" |
|
37 |
|
38 const QPen GLRenderer::thinBorderPen {QColor {0, 0, 0, 208}, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin}; |
|
39 |
|
40 /* |
|
41 * Constructs a GL renderer. |
|
42 */ |
|
43 GLRenderer::GLRenderer(const Model* model, QWidget* parent) : |
|
44 QGLWidget {parent}, |
|
45 HierarchyElement {parent}, |
|
46 m_model {model}, |
|
47 m_cameras { |
|
48 {"Top camera", {1, 0, 0, X, Z, false, false, false}}, // top |
|
49 {"Front camera", {0, 0, 1, X, Y, false, true, false}}, // front |
|
50 {"Left camera", {0, 1, 0, Z, Y, true, true, false}}, // left |
|
51 {"Bottom camera", {-1, 0, 0, X, Z, false, true, true}}, // bottom |
|
52 {"Back camera", {0, 0, -1, X, Y, true, true, true}}, // back |
|
53 {"Right camera", {0, -1, 0, Z, Y, false, true, true}}, // right |
|
54 {"Free camera", GLCamera::FreeCamera}, // free |
|
55 } |
|
56 { |
|
57 m_camera = (Camera) m_config->camera(); |
|
58 m_compiler = new GLCompiler (this); |
|
59 m_toolTipTimer = new QTimer (this); |
|
60 m_toolTipTimer->setSingleShot (true); |
|
61 setAcceptDrops (true); |
|
62 connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (showCameraIconTooltip())); |
|
63 resetAllAngles(); |
|
64 m_needZoomToFit = true; |
|
65 |
|
66 // Init camera icons |
|
67 for (Camera camera : iterateEnum<Camera>()) |
|
68 { |
|
69 const char* cameraIconNames[EnumLimits<Camera>::Count] = |
|
70 { |
|
71 "camera-top", "camera-front", "camera-left", |
|
72 "camera-bottom", "camera-back", "camera-right", |
|
73 "camera-free" |
|
74 }; |
|
75 |
|
76 CameraIcon* info = &m_cameraIcons[static_cast<int>(camera)]; |
|
77 info->image = GetIcon (cameraIconNames[static_cast<int>(camera)]); |
|
78 info->camera = camera; |
|
79 } |
|
80 |
|
81 calcCameraIcons(); |
|
82 } |
|
83 |
|
84 /* |
|
85 * Cleans up the axes VBOs when the renderer is destroyed. |
|
86 */ |
|
87 GLRenderer::~GLRenderer() |
|
88 { |
|
89 glDeleteBuffers(1, &m_axesVbo); |
|
90 glDeleteBuffers(1, &m_axesColorVbo); |
|
91 } |
|
92 |
|
93 /* |
|
94 * Calculates the camera icon locations. |
|
95 */ |
|
96 void GLRenderer::calcCameraIcons() |
|
97 { |
|
98 int i = 0; |
|
99 const int columns = 3; |
|
100 const int firstAtLastRow = countof(m_cameras) - (countof(m_cameras) % columns); |
|
101 |
|
102 for (CameraIcon& cameraIcon : m_cameraIcons) |
|
103 { |
|
104 int row = i / columns; |
|
105 int column = i % columns; |
|
106 |
|
107 // Do right-justifying on the last row. |
|
108 if (i >= firstAtLastRow) |
|
109 column += columns - (countof(m_cameras) % columns); |
|
110 |
|
111 int x1 = width() - 48 + (column * 16) - 1; |
|
112 int y1 = (row * 16) + 1; |
|
113 |
|
114 cameraIcon.sourceRect = {0, 0, 16, 16}; |
|
115 cameraIcon.targetRect = {x1, y1, 16, 16}; |
|
116 cameraIcon.hitRect = { |
|
117 cameraIcon.targetRect.x(), |
|
118 cameraIcon.targetRect.y(), |
|
119 cameraIcon.targetRect.width() + 1, |
|
120 cameraIcon.targetRect.height() + 1 |
|
121 }; |
|
122 |
|
123 ++i; |
|
124 } |
|
125 } |
|
126 |
|
127 /* |
|
128 * Returns the camera currently in use. |
|
129 */ |
|
130 GLCamera& GLRenderer::currentCamera() |
|
131 { |
|
132 return m_cameras[static_cast<int>(camera())]; |
|
133 } |
|
134 |
|
135 /* |
|
136 * Returns the camera currently in use. |
|
137 */ |
|
138 const GLCamera& GLRenderer::currentCamera() const |
|
139 { |
|
140 return m_cameras[static_cast<int>(camera())]; |
|
141 } |
|
142 |
|
143 /* |
|
144 * Prepares the GL context for rendering. |
|
145 */ |
|
146 void GLRenderer::initGLData() |
|
147 { |
|
148 glEnable (GL_BLEND); |
|
149 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
|
150 glEnable (GL_POLYGON_OFFSET_FILL); |
|
151 glPolygonOffset (1.0f, 1.0f); |
|
152 glEnable (GL_DEPTH_TEST); |
|
153 glShadeModel (GL_SMOOTH); |
|
154 glEnable (GL_MULTISAMPLE); |
|
155 |
|
156 if (m_config->antiAliasedLines()) |
|
157 { |
|
158 glEnable (GL_LINE_SMOOTH); |
|
159 glEnable (GL_POLYGON_SMOOTH); |
|
160 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); |
|
161 glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST); |
|
162 } |
|
163 else |
|
164 { |
|
165 glDisable (GL_LINE_SMOOTH); |
|
166 glDisable (GL_POLYGON_SMOOTH); |
|
167 } |
|
168 } |
|
169 |
|
170 /* |
|
171 * Returns the object currently highlighted by the cursor. |
|
172 */ |
|
173 LDObject* GLRenderer::objectAtCursor() const |
|
174 { |
|
175 return m_objectAtCursor; |
|
176 } |
|
177 |
|
178 // ============================================================================= |
|
179 // |
|
180 void GLRenderer::needZoomToFit() |
|
181 { |
|
182 m_needZoomToFit = true; |
|
183 } |
|
184 |
|
185 // ============================================================================= |
|
186 // |
|
187 void GLRenderer::resetAngles() |
|
188 { |
|
189 if (m_initialized) |
|
190 { |
|
191 // Why did I even bother trying to compute this by pen and paper? Let GL figure it out... |
|
192 glMatrixMode(GL_MODELVIEW); |
|
193 glPushMatrix(); |
|
194 glLoadIdentity(); |
|
195 glRotatef(30, 1, 0, 0); |
|
196 glRotatef(330, 0, 1, 0); |
|
197 glGetFloatv(GL_MODELVIEW_MATRIX, m_rotationMatrix.data()); |
|
198 glPopMatrix(); |
|
199 } |
|
200 currentCamera().setPanning(0, 0); |
|
201 needZoomToFit(); |
|
202 } |
|
203 |
|
204 // ============================================================================= |
|
205 // |
|
206 void GLRenderer::resetAllAngles() |
|
207 { |
|
208 Camera oldcam = camera(); |
|
209 |
|
210 for (int i = 0; i < 7; ++i) |
|
211 { |
|
212 setCamera ((Camera) i); |
|
213 resetAngles(); |
|
214 } |
|
215 |
|
216 setCamera (oldcam); |
|
217 } |
|
218 |
|
219 // ============================================================================= |
|
220 // |
|
221 void GLRenderer::initializeGL() |
|
222 { |
|
223 initializeOpenGLFunctions(); |
|
224 setBackground(); |
|
225 glLineWidth (m_config->lineThickness()); |
|
226 glLineStipple (1, 0x6666); |
|
227 setAutoFillBackground (false); |
|
228 setMouseTracking (true); |
|
229 setFocusPolicy (Qt::WheelFocus); |
|
230 m_compiler->initialize(); |
|
231 initializeAxes(); |
|
232 initializeLighting(); |
|
233 m_initialized = true; |
|
234 // Now that GL is initialized, we can reset angles. |
|
235 resetAllAngles(); |
|
236 } |
|
237 |
|
238 void GLRenderer::initializeLighting() |
|
239 { |
|
240 GLfloat materialShininess[] = {5.0}; |
|
241 GLfloat lightPosition[] = {1.0, 1.0, 1.0, 0.0}; |
|
242 GLfloat ambientLightingLevel[] = {0.8, 0.8, 0.8, 1.0}; |
|
243 glShadeModel(GL_SMOOTH); |
|
244 glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess); |
|
245 glMaterialfv(GL_FRONT, GL_AMBIENT, ambientLightingLevel); |
|
246 glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); |
|
247 glEnable(GL_LIGHTING); |
|
248 glEnable(GL_LIGHT0); |
|
249 glEnable(GL_COLOR_MATERIAL); |
|
250 glEnable(GL_DEPTH_TEST); |
|
251 } |
|
252 |
|
253 // ============================================================================= |
|
254 // |
|
255 void GLRenderer::initializeAxes() |
|
256 { |
|
257 // Definitions for visual axes, drawn on the screen |
|
258 struct |
|
259 { |
|
260 QColor color; |
|
261 Vertex extrema; |
|
262 } axisInfo[3] = |
|
263 { |
|
264 { QColor {192, 96, 96}, Vertex {10000, 0, 0} }, // X |
|
265 { QColor {48, 192, 48}, Vertex {0, 10000, 0} }, // Y |
|
266 { QColor {48, 112, 192}, Vertex {0, 0, 10000} }, // Z |
|
267 }; |
|
268 |
|
269 float axisdata[18]; |
|
270 float colorData[18]; |
|
271 memset (axisdata, 0, sizeof axisdata); |
|
272 |
|
273 for (int i = 0; i < 3; ++i) |
|
274 { |
|
275 const auto& data = axisInfo[i]; |
|
276 |
|
277 for (Axis axis : axes) |
|
278 { |
|
279 axisdata[(i * 6) + axis] = data.extrema[axis]; |
|
280 axisdata[(i * 6) + 3 + axis] = -data.extrema[axis]; |
|
281 } |
|
282 |
|
283 int offset = i * 6; |
|
284 colorData[offset + 0] = colorData[offset + 3] = data.color.red(); |
|
285 colorData[offset + 1] = colorData[offset + 4] = data.color.green(); |
|
286 colorData[offset + 2] = colorData[offset + 5] = data.color.blue(); |
|
287 } |
|
288 |
|
289 glGenBuffers (1, &m_axesVbo); |
|
290 glBindBuffer (GL_ARRAY_BUFFER, m_axesVbo); |
|
291 glBufferData (GL_ARRAY_BUFFER, sizeof axisdata, axisdata, GL_STATIC_DRAW); |
|
292 glGenBuffers (1, &m_axesColorVbo); |
|
293 glBindBuffer (GL_ARRAY_BUFFER, m_axesColorVbo); |
|
294 glBufferData (GL_ARRAY_BUFFER, sizeof colorData, colorData, GL_STATIC_DRAW); |
|
295 glBindBuffer (GL_ARRAY_BUFFER, 0); |
|
296 } |
|
297 |
|
298 // ============================================================================= |
|
299 // |
|
300 void GLRenderer::setBackground() |
|
301 { |
|
302 if (not m_isDrawingSelectionScene) |
|
303 { |
|
304 // Otherwise use the background that the user wants. |
|
305 QColor color = m_config->backgroundColor(); |
|
306 |
|
307 if (color.isValid()) |
|
308 { |
|
309 color.setAlpha(255); |
|
310 m_useDarkBackground = luma(color) < 80; |
|
311 m_backgroundColor = color; |
|
312 qglClearColor(color); |
|
313 } |
|
314 } |
|
315 else |
|
316 { |
|
317 // The picking scene requires a black background. |
|
318 glClearColor(0.0f, 0.0f, 0.0f, 1.0f); |
|
319 } |
|
320 } |
|
321 |
|
322 QColor GLRenderer::backgroundColor() const |
|
323 { |
|
324 return m_backgroundColor; |
|
325 } |
|
326 |
|
327 // ============================================================================= |
|
328 // |
|
329 void GLRenderer::refresh() |
|
330 { |
|
331 update(); |
|
332 |
|
333 if (isVisible()) |
|
334 swapBuffers(); |
|
335 } |
|
336 |
|
337 // ============================================================================= |
|
338 // |
|
339 void GLRenderer::resizeGL (int width, int height) |
|
340 { |
|
341 calcCameraIcons(); |
|
342 glViewport (0, 0, width, height); |
|
343 glMatrixMode (GL_PROJECTION); |
|
344 glLoadIdentity(); |
|
345 gluPerspective (45.0f, (double) width / (double) height, 1.0f, 10000.0f); |
|
346 glMatrixMode (GL_MODELVIEW); |
|
347 |
|
348 // Unfortunately Qt does not provide a resized() signal so we have to manually feed the information. |
|
349 for (GLCamera& camera : m_cameras) |
|
350 camera.rendererResized(width, height); |
|
351 } |
|
352 |
|
353 // ============================================================================= |
|
354 // |
|
355 void GLRenderer::drawGLScene() |
|
356 { |
|
357 if (m_needZoomToFit) |
|
358 { |
|
359 m_needZoomToFit = false; |
|
360 zoomAllToFit(); |
|
361 } |
|
362 |
|
363 if (m_config->drawWireframe() and not m_isDrawingSelectionScene) |
|
364 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); |
|
365 |
|
366 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
|
367 glEnable(GL_DEPTH_TEST); |
|
368 |
|
369 if (m_config->lighting()) |
|
370 glEnable(GL_LIGHTING); |
|
371 else |
|
372 glDisable(GL_LIGHTING); |
|
373 |
|
374 if (camera() != Camera::Free) |
|
375 { |
|
376 glMatrixMode (GL_PROJECTION); |
|
377 glPushMatrix(); |
|
378 |
|
379 glLoadIdentity(); |
|
380 const QSizeF& virtualSize = currentCamera().virtualSize(); |
|
381 glOrtho(-virtualSize.width(), virtualSize.width(), -virtualSize.height(), virtualSize.height(), -100.0f, 100.0f); |
|
382 glTranslatef(panning (X), panning (Y), 0.0f); |
|
383 |
|
384 if (camera() != Camera::Front and camera() != Camera::Back) |
|
385 glRotatef(90.0f, currentCamera().glRotate(X), currentCamera().glRotate(Y), 0); |
|
386 |
|
387 // Back camera needs to be handled differently |
|
388 if (camera() == Camera::Back) |
|
389 { |
|
390 glRotatef(180.0f, 1.0f, 0.0f, 0.0f); |
|
391 glRotatef(180.0f, 0.0f, 0.0f, 1.0f); |
|
392 } |
|
393 } |
|
394 else |
|
395 { |
|
396 glMatrixMode(GL_MODELVIEW); |
|
397 glPushMatrix(); |
|
398 glLoadIdentity(); |
|
399 glTranslatef(0.0f, 0.0f, -2.0f); |
|
400 glTranslatef(panning (X), panning (Y), -zoom()); |
|
401 glMultMatrixf(m_rotationMatrix.constData()); |
|
402 } |
|
403 |
|
404 glEnableClientState (GL_NORMAL_ARRAY); |
|
405 glEnableClientState (GL_VERTEX_ARRAY); |
|
406 glEnableClientState (GL_COLOR_ARRAY); |
|
407 |
|
408 if (m_isDrawingSelectionScene) |
|
409 { |
|
410 drawVbos (VboClass::Triangles, VboSubclass::PickColors, GL_TRIANGLES); |
|
411 drawVbos (VboClass::Quads, VboSubclass::PickColors, GL_QUADS); |
|
412 drawVbos (VboClass::Lines, VboSubclass::PickColors, GL_LINES); |
|
413 drawVbos (VboClass::ConditionalLines, VboSubclass::PickColors, GL_LINES); |
|
414 } |
|
415 else |
|
416 { |
|
417 if (m_config->bfcRedGreenView()) |
|
418 { |
|
419 glEnable (GL_CULL_FACE); |
|
420 glCullFace (GL_BACK); |
|
421 drawVbos (VboClass::Triangles, VboSubclass::BfcFrontColors, GL_TRIANGLES); |
|
422 drawVbos (VboClass::Quads, VboSubclass::BfcFrontColors, GL_QUADS); |
|
423 glCullFace (GL_FRONT); |
|
424 drawVbos (VboClass::Triangles, VboSubclass::BfcBackColors, GL_TRIANGLES); |
|
425 drawVbos (VboClass::Quads, VboSubclass::BfcBackColors, GL_QUADS); |
|
426 glDisable (GL_CULL_FACE); |
|
427 } |
|
428 else |
|
429 { |
|
430 VboSubclass colors; |
|
431 |
|
432 if (m_config->randomColors()) |
|
433 colors = VboSubclass::RandomColors; |
|
434 else |
|
435 colors = VboSubclass::NormalColors; |
|
436 |
|
437 drawVbos (VboClass::Triangles, colors, GL_TRIANGLES); |
|
438 drawVbos (VboClass::Quads, colors, GL_QUADS); |
|
439 } |
|
440 |
|
441 drawVbos (VboClass::Lines, VboSubclass::NormalColors, GL_LINES); |
|
442 glEnable (GL_LINE_STIPPLE); |
|
443 drawVbos (VboClass::ConditionalLines, VboSubclass::NormalColors, GL_LINES); |
|
444 glDisable (GL_LINE_STIPPLE); |
|
445 |
|
446 if (m_config->drawAxes()) |
|
447 { |
|
448 glDisableClientState (GL_NORMAL_ARRAY); |
|
449 glBindBuffer (GL_ARRAY_BUFFER, m_axesVbo); |
|
450 glVertexPointer (3, GL_FLOAT, 0, NULL); |
|
451 glBindBuffer (GL_ARRAY_BUFFER, m_axesVbo); |
|
452 glColorPointer (3, GL_FLOAT, 0, NULL); |
|
453 glDrawArrays (GL_LINES, 0, 6); |
|
454 glEnableClientState (GL_NORMAL_ARRAY); |
|
455 CHECK_GL_ERROR(); |
|
456 } |
|
457 } |
|
458 |
|
459 glPopMatrix(); |
|
460 glBindBuffer (GL_ARRAY_BUFFER, 0); |
|
461 glDisableClientState (GL_VERTEX_ARRAY); |
|
462 glDisableClientState (GL_COLOR_ARRAY); |
|
463 glDisableClientState (GL_NORMAL_ARRAY); |
|
464 CHECK_GL_ERROR(); |
|
465 glDisable (GL_CULL_FACE); |
|
466 glMatrixMode (GL_MODELVIEW); |
|
467 glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); |
|
468 } |
|
469 |
|
470 // ============================================================================= |
|
471 // |
|
472 void GLRenderer::drawVbos (VboClass surface, VboSubclass colors, GLenum type) |
|
473 { |
|
474 // Filter this through some configuration options |
|
475 if ((isOneOf (surface, VboClass::Quads, VboClass::Triangles) and m_config->drawSurfaces() == false) |
|
476 or (surface == VboClass::Lines and m_config->drawEdgeLines() == false) |
|
477 or (surface == VboClass::ConditionalLines and m_config->drawConditionalLines() == false)) |
|
478 { |
|
479 return; |
|
480 } |
|
481 |
|
482 int surfaceVboNumber = m_compiler->vboNumber(surface, VboSubclass::Surfaces); |
|
483 int colorVboNumber = m_compiler->vboNumber(surface, colors); |
|
484 int normalVboNumber = m_compiler->vboNumber(surface, VboSubclass::Normals); |
|
485 m_compiler->prepareVBO(surfaceVboNumber, m_model); |
|
486 m_compiler->prepareVBO(colorVboNumber, m_model); |
|
487 m_compiler->prepareVBO(normalVboNumber, m_model); |
|
488 GLuint surfaceVbo = m_compiler->vbo(surfaceVboNumber); |
|
489 GLuint colorVbo = m_compiler->vbo(colorVboNumber); |
|
490 GLuint normalVbo = m_compiler->vbo(normalVboNumber); |
|
491 GLsizei count = m_compiler->vboSize(surfaceVboNumber) / 3; |
|
492 |
|
493 if (count > 0) |
|
494 { |
|
495 glBindBuffer(GL_ARRAY_BUFFER, surfaceVbo); |
|
496 glVertexPointer(3, GL_FLOAT, 0, nullptr); |
|
497 CHECK_GL_ERROR(); |
|
498 glBindBuffer(GL_ARRAY_BUFFER, colorVbo); |
|
499 glColorPointer(4, GL_FLOAT, 0, nullptr); |
|
500 CHECK_GL_ERROR(); |
|
501 glBindBuffer(GL_ARRAY_BUFFER, normalVbo); |
|
502 glNormalPointer(GL_FLOAT, 0, nullptr); |
|
503 CHECK_GL_ERROR(); |
|
504 glDrawArrays(type, 0, count); |
|
505 CHECK_GL_ERROR(); |
|
506 } |
|
507 } |
|
508 |
|
509 QPen GLRenderer::textPen() const |
|
510 { |
|
511 return {m_useDarkBackground ? Qt::white : Qt::black}; |
|
512 } |
|
513 |
|
514 bool GLRenderer::freeCameraAllowed() const |
|
515 { |
|
516 return true; |
|
517 } |
|
518 |
|
519 void GLRenderer::paintEvent(QPaintEvent*) |
|
520 { |
|
521 makeCurrent(); |
|
522 initGLData(); |
|
523 drawGLScene(); |
|
524 |
|
525 if (isDrawingSelectionScene()) |
|
526 return; |
|
527 |
|
528 QPainter painter {this}; |
|
529 painter.setRenderHint(QPainter::Antialiasing); |
|
530 overpaint(painter); |
|
531 } |
|
532 |
|
533 void GLRenderer::overpaint(QPainter &painter) |
|
534 { |
|
535 // Draw a background for the selected camera |
|
536 painter.setPen(thinBorderPen); |
|
537 painter.setBrush(QBrush {QColor {0, 128, 160, 128}}); |
|
538 painter.drawRect(m_cameraIcons[static_cast<int>(camera())].hitRect); |
|
539 |
|
540 // Draw the camera icons |
|
541 for (const CameraIcon& info : m_cameraIcons) |
|
542 { |
|
543 // Don't draw the free camera icon when we can't use the free camera |
|
544 if (info.camera == Camera::Free and not freeCameraAllowed()) |
|
545 continue; |
|
546 |
|
547 painter.drawPixmap(info.targetRect, info.image, info.sourceRect); |
|
548 } |
|
549 |
|
550 // Draw a label for the current camera in the bottom left corner |
|
551 { |
|
552 QFontMetrics metrics {QFont {}}; |
|
553 int margin = 4; |
|
554 painter.setPen(textPen()); |
|
555 painter.drawText(QPoint {margin, height() - margin - metrics.descent()}, currentCamera().name()); |
|
556 } |
|
557 } |
|
558 |
|
559 // ============================================================================= |
|
560 // |
|
561 void GLRenderer::mouseReleaseEvent(QMouseEvent* event) |
|
562 { |
|
563 bool wasLeft = (m_lastButtons & Qt::LeftButton) and not (event->buttons() & Qt::LeftButton); |
|
564 m_panning = false; |
|
565 |
|
566 // Check if we selected a camera icon |
|
567 if (wasLeft and not mouseHasMoved()) |
|
568 { |
|
569 for (CameraIcon& info : m_cameraIcons) |
|
570 { |
|
571 if (info.targetRect.contains (event->pos())) |
|
572 { |
|
573 setCamera (info.camera); |
|
574 break; |
|
575 } |
|
576 } |
|
577 } |
|
578 |
|
579 update(); |
|
580 m_totalMouseMove = 0; |
|
581 } |
|
582 |
|
583 // ============================================================================= |
|
584 // |
|
585 void GLRenderer::mousePressEvent(QMouseEvent* event) |
|
586 { |
|
587 m_lastButtons = event->buttons(); |
|
588 m_totalMouseMove = 0; |
|
589 } |
|
590 |
|
591 // ============================================================================= |
|
592 // |
|
593 void GLRenderer::mouseMoveEvent(QMouseEvent* event) |
|
594 { |
|
595 int xMove = event->x() - m_mousePosition.x(); |
|
596 int yMove = event->y() - m_mousePosition.y(); |
|
597 m_totalMouseMove += qAbs(xMove) + qAbs(yMove); |
|
598 m_isCameraMoving = false; |
|
599 |
|
600 bool left = event->buttons() & Qt::LeftButton; |
|
601 bool mid = event->buttons() & Qt::MidButton; |
|
602 bool shift = event->modifiers() & Qt::ShiftModifier; |
|
603 |
|
604 if (mid or (left and shift)) |
|
605 { |
|
606 currentCamera().pan(xMove, yMove); |
|
607 m_panning = true; |
|
608 m_isCameraMoving = true; |
|
609 } |
|
610 else if (left and camera() == Camera::Free and (xMove != 0 or yMove != 0)) |
|
611 { |
|
612 // Apply current rotation input to the rotation matrix |
|
613 // ref: https://forums.ldraw.org/thread-22006-post-24426.html#pid24426 |
|
614 glPushMatrix(); |
|
615 glLoadIdentity(); |
|
616 // 0.6 is an arbitrary rotation sensitivity scalar |
|
617 glRotatef(0.6 * hypot(xMove, yMove), yMove, xMove, 0); |
|
618 glMultMatrixf(m_rotationMatrix.constData()); |
|
619 glGetFloatv(GL_MODELVIEW_MATRIX, m_rotationMatrix.data()); |
|
620 glPopMatrix(); |
|
621 m_isCameraMoving = true; |
|
622 } |
|
623 |
|
624 // Start the tool tip timer |
|
625 m_toolTipTimer->start (500); |
|
626 |
|
627 // Update 2d position |
|
628 m_mousePosition = event->pos(); |
|
629 m_globalpos = event->globalPos(); |
|
630 m_mousePositionF = event->localPos(); |
|
631 |
|
632 highlightCursorObject(); |
|
633 update(); |
|
634 event->accept(); |
|
635 } |
|
636 |
|
637 // ============================================================================= |
|
638 // |
|
639 void GLRenderer::keyPressEvent(QKeyEvent* event) |
|
640 { |
|
641 m_currentKeyboardModifiers = event->modifiers(); |
|
642 } |
|
643 |
|
644 // ============================================================================= |
|
645 // |
|
646 void GLRenderer::keyReleaseEvent(QKeyEvent* event) |
|
647 { |
|
648 m_currentKeyboardModifiers = event->modifiers(); |
|
649 update(); |
|
650 } |
|
651 |
|
652 // ============================================================================= |
|
653 // |
|
654 void GLRenderer::wheelEvent(QWheelEvent* ev) |
|
655 { |
|
656 makeCurrent(); |
|
657 currentCamera().zoomNotch(ev->delta() > 0); |
|
658 m_isCameraMoving = true; |
|
659 update(); |
|
660 ev->accept(); |
|
661 } |
|
662 |
|
663 // ============================================================================= |
|
664 // |
|
665 void GLRenderer::leaveEvent(QEvent*) |
|
666 { |
|
667 m_toolTipTimer->stop(); |
|
668 update(); |
|
669 } |
|
670 |
|
671 // ============================================================================= |
|
672 // |
|
673 void GLRenderer::setCamera(Camera camera) |
|
674 { |
|
675 // The edit mode may forbid the free camera. |
|
676 if (freeCameraAllowed() or camera != Camera::Free) |
|
677 { |
|
678 m_camera = camera; |
|
679 m_config->setCamera(static_cast<int>(camera)); |
|
680 } |
|
681 } |
|
682 |
|
683 /* |
|
684 * Returns the set of objects found in the specified pixel area. |
|
685 */ |
|
686 QSet<LDObject*> GLRenderer::pick(const QRect& range) |
|
687 { |
|
688 makeCurrent(); |
|
689 QSet<LDObject*> newSelection; |
|
690 |
|
691 // Paint the picking scene |
|
692 setPicking(true); |
|
693 drawGLScene(); |
|
694 |
|
695 int x0 = range.left(); |
|
696 int y0 = range.top(); |
|
697 int x1 = x0 + range.width(); |
|
698 int y1 = y0 + range.height(); |
|
699 |
|
700 // Clamp the values to ensure they're within bounds |
|
701 x0 = qMax (0, x0); |
|
702 y0 = qMax (0, y0); |
|
703 x1 = qMin (x1, width()); |
|
704 y1 = qMin (y1, height()); |
|
705 const int areawidth = (x1 - x0); |
|
706 const int areaheight = (y1 - y0); |
|
707 const qint32 numpixels = areawidth * areaheight; |
|
708 |
|
709 // Allocate space for the pixel data. |
|
710 QVector<unsigned char> pixelData; |
|
711 pixelData.resize(4 * numpixels); |
|
712 |
|
713 // Read pixels from the color buffer. |
|
714 glReadPixels(x0, height() - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data()); |
|
715 |
|
716 QSet<qint32> indices; |
|
717 |
|
718 // Go through each pixel read and add them to the selection. |
|
719 // Each pixel maps to an LDObject index injectively. |
|
720 // Note: black is background, those indices are skipped. |
|
721 for (unsigned char *pixelCursor = pixelData.begin(); pixelCursor < pixelData.end(); pixelCursor += 4) |
|
722 { |
|
723 qint32 index = pixelCursor[0] * 0x10000 + pixelCursor[1] * 0x100 + pixelCursor[2] * 0x1; |
|
724 if (index != 0) |
|
725 indices.insert(index); |
|
726 } |
|
727 |
|
728 // For each index read, resolve the LDObject behind it and add it to the selection. |
|
729 for (qint32 index : indices) |
|
730 { |
|
731 LDObject* object = LDObject::fromID(index); |
|
732 |
|
733 if (object != nullptr) |
|
734 newSelection.insert(object); |
|
735 } |
|
736 |
|
737 setPicking(false); |
|
738 repaint(); |
|
739 return newSelection; |
|
740 } |
|
741 |
|
742 /* |
|
743 * Simpler version of GLRenderer::pick which simply picks whatever object on the cursor |
|
744 */ |
|
745 LDObject* GLRenderer::pick(int mouseX, int mouseY) |
|
746 { |
|
747 unsigned char pixel[4]; |
|
748 makeCurrent(); |
|
749 setPicking(true); |
|
750 drawGLScene(); |
|
751 glReadPixels(mouseX, height() - mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); |
|
752 LDObject* object = LDObject::fromID(pixel[0] * 0x10000 + pixel[1] * 0x100 + pixel[2]); |
|
753 setPicking(false); |
|
754 repaint(); |
|
755 return object; |
|
756 } |
|
757 |
|
758 // ============================================================================= |
|
759 // |
|
760 void GLRenderer::setPicking(bool value) |
|
761 { |
|
762 m_isDrawingSelectionScene = value; |
|
763 setBackground(); |
|
764 |
|
765 if (m_isDrawingSelectionScene) |
|
766 { |
|
767 glDisable(GL_DITHER); |
|
768 |
|
769 // Use particularly thick lines while picking ease up selecting lines. |
|
770 glLineWidth(qMax<double>(m_config->lineThickness(), 6.5)); |
|
771 } |
|
772 else |
|
773 { |
|
774 glEnable(GL_DITHER); |
|
775 |
|
776 // Restore line thickness |
|
777 glLineWidth(m_config->lineThickness()); |
|
778 } |
|
779 } |
|
780 |
|
781 // ============================================================================= |
|
782 // |
|
783 void GLRenderer::forgetObject(LDObject* obj) |
|
784 { |
|
785 if (m_objectAtCursor == obj) |
|
786 m_objectAtCursor = nullptr; |
|
787 } |
|
788 |
|
789 // ============================================================================= |
|
790 // |
|
791 QByteArray GLRenderer::capturePixels() |
|
792 { |
|
793 QByteArray result; |
|
794 result.resize (4 * width() * height()); |
|
795 m_takingScreenCapture = true; |
|
796 update(); // Smile! |
|
797 m_takingScreenCapture = false; |
|
798 glReadPixels(0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_BYTE, result.data()); |
|
799 return result; |
|
800 } |
|
801 |
|
802 /* |
|
803 * Show a tooltip if the cursor is currently hovering over a camera icon. |
|
804 */ |
|
805 void GLRenderer::showCameraIconTooltip() |
|
806 { |
|
807 for (CameraIcon & icon : m_cameraIcons) |
|
808 { |
|
809 if (icon.targetRect.contains (m_mousePosition)) |
|
810 { |
|
811 QToolTip::showText(m_globalpos, m_cameras[static_cast<int>(icon.camera)].name()); |
|
812 update(); |
|
813 break; |
|
814 } |
|
815 } |
|
816 } |
|
817 |
|
818 // ============================================================================= |
|
819 // |
|
820 void GLRenderer::zoomToFit() |
|
821 { |
|
822 currentCamera().setZoom(30.0f); |
|
823 bool lastfilled = false; |
|
824 bool firstrun = true; |
|
825 enum { black = 0xFF000000 }; |
|
826 bool inward = true; |
|
827 int runaway = 50; |
|
828 |
|
829 // Use the pick list while drawing the scene, this way we can tell whether borders |
|
830 // are background or not. |
|
831 setPicking (true); |
|
832 |
|
833 while (--runaway) |
|
834 { |
|
835 if (zoom() > 10000.0 or zoom() < 0.0) |
|
836 { |
|
837 // Nothing to draw if we get here. |
|
838 currentCamera().setZoom(30.0); |
|
839 break; |
|
840 } |
|
841 |
|
842 currentCamera().zoomNotch(inward); |
|
843 QVector<unsigned char> capture (4 * width() * height()); |
|
844 drawGLScene(); |
|
845 glReadPixels (0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_BYTE, capture.data()); |
|
846 QImage image (capture.constData(), width(), height(), QImage::Format_ARGB32); |
|
847 bool filled = false; |
|
848 |
|
849 // Check the top and bottom rows |
|
850 for (int i = 0; i < image.width(); ++i) |
|
851 { |
|
852 if (image.pixel (i, 0) != black or image.pixel (i, height() - 1) != black) |
|
853 { |
|
854 filled = true; |
|
855 break; |
|
856 } |
|
857 } |
|
858 |
|
859 // Left and right edges |
|
860 if (filled == false) |
|
861 { |
|
862 for (int i = 0; i < image.height(); ++i) |
|
863 { |
|
864 if (image.pixel (0, i) != black or image.pixel (width() - 1, i) != black) |
|
865 { |
|
866 filled = true; |
|
867 break; |
|
868 } |
|
869 } |
|
870 } |
|
871 |
|
872 if (firstrun) |
|
873 { |
|
874 // If this is the first run, we don't know enough to determine |
|
875 // whether the zoom was to fit, so we mark in our knowledge so |
|
876 // far and start over. |
|
877 inward = not filled; |
|
878 firstrun = false; |
|
879 } |
|
880 else |
|
881 { |
|
882 // If this run filled the screen and the last one did not, the |
|
883 // last run had ideal zoom - zoom a bit back and we should reach it. |
|
884 if (filled and not lastfilled) |
|
885 { |
|
886 currentCamera().zoomNotch(false); |
|
887 break; |
|
888 } |
|
889 |
|
890 // If this run did not fill the screen and the last one did, we've |
|
891 // now reached ideal zoom so we're done here. |
|
892 if (not filled and lastfilled) |
|
893 break; |
|
894 |
|
895 inward = not filled; |
|
896 } |
|
897 |
|
898 lastfilled = filled; |
|
899 } |
|
900 |
|
901 setPicking (false); |
|
902 } |
|
903 |
|
904 // ============================================================================= |
|
905 // |
|
906 void GLRenderer::zoomAllToFit() |
|
907 { |
|
908 zoomToFit(); |
|
909 } |
|
910 |
|
911 // ============================================================================= |
|
912 // |
|
913 void GLRenderer::highlightCursorObject() |
|
914 { |
|
915 if (not m_config->highlightObjectBelowCursor() and objectAtCursor() == nullptr) |
|
916 return; |
|
917 |
|
918 LDObject* newObject = nullptr; |
|
919 LDObject* oldObject = objectAtCursor(); |
|
920 qint32 newIndex; |
|
921 |
|
922 if (m_isCameraMoving or not m_config->highlightObjectBelowCursor()) |
|
923 { |
|
924 newIndex = 0; |
|
925 } |
|
926 else |
|
927 { |
|
928 setPicking (true); |
|
929 drawGLScene(); |
|
930 setPicking (false); |
|
931 |
|
932 unsigned char pixel[4]; |
|
933 glReadPixels (m_mousePosition.x(), height() - m_mousePosition.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel[0]); |
|
934 newIndex = pixel[0] * 0x10000 | pixel[1] * 0x100 | pixel[2]; |
|
935 } |
|
936 |
|
937 if (newIndex != (oldObject ? oldObject->id() : 0)) |
|
938 { |
|
939 if (newIndex != 0) |
|
940 newObject = LDObject::fromID (newIndex); |
|
941 |
|
942 m_objectAtCursor = newObject; |
|
943 |
|
944 if (oldObject) |
|
945 emit objectHighlightingChanged(oldObject); |
|
946 |
|
947 if (newObject) |
|
948 emit objectHighlightingChanged(newObject); |
|
949 } |
|
950 |
|
951 update(); |
|
952 } |
|
953 |
|
954 bool GLRenderer::mouseHasMoved() const |
|
955 { |
|
956 return m_totalMouseMove >= 10; |
|
957 } |
|
958 |
|
959 QPoint const& GLRenderer::mousePosition() const |
|
960 { |
|
961 return m_mousePosition; |
|
962 } |
|
963 |
|
964 QPointF const& GLRenderer::mousePositionF() const |
|
965 { |
|
966 return m_mousePositionF; |
|
967 } |
|
968 |
|
969 Qt::KeyboardModifiers GLRenderer::keyboardModifiers() const |
|
970 { |
|
971 return m_currentKeyboardModifiers; |
|
972 } |
|
973 |
|
974 Camera GLRenderer::camera() const |
|
975 { |
|
976 return m_camera; |
|
977 } |
|
978 |
|
979 double GLRenderer::panning (Axis ax) const |
|
980 { |
|
981 return (ax == X) ? currentCamera().panningX() : currentCamera().panningY(); |
|
982 } |
|
983 |
|
984 double GLRenderer::zoom() |
|
985 { |
|
986 return currentCamera().zoom(); |
|
987 } |
|
988 |
|
989 const QGenericMatrix<4, 4, GLfloat>& GLRenderer::rotationMatrix() const |
|
990 { |
|
991 return m_rotationMatrix; |
|
992 } |
|
993 |
|
994 bool GLRenderer::isDrawingSelectionScene() const |
|
995 { |
|
996 return m_isDrawingSelectionScene; |
|
997 } |
|
998 |
|
999 Qt::MouseButtons GLRenderer::lastButtons() const |
|
1000 { |
|
1001 return m_lastButtons; |
|
1002 } |
|
1003 |
|
1004 const Model* GLRenderer::model() const |
|
1005 { |
|
1006 return m_model; |
|
1007 } |