src/glRenderer.cpp

changeset 1144
4f226fd97826
parent 1143
2008959603c9
child 1145
02264bf0108d
equal deleted inserted replaced
1143:2008959603c9 1144:4f226fd97826
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 }

mercurial