Tue, 14 Feb 2017 14:51:04 +0200
Renamed glRenderer.cpp → glrenderer.cpp
/* * LDForge: LDraw parts authoring CAD * Copyright (C) 2013 - 2017 Teemu Piippo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #define GL_GLEXT_PROTOTYPES #include <GL/glu.h> #include <GL/glext.h> #include <QContextMenuEvent> #include <QToolTip> #include <QTimer> #include <GL/glu.h> #include "main.h" #include "ldDocument.h" #include "glrenderer.h" #include "colors.h" #include "mainwindow.h" #include "miscallenous.h" #include "editHistory.h" #include "glCompiler.h" #include "primitives.h" #include "documentmanager.h" #include "grid.h" const QPen GLRenderer::thinBorderPen {QColor {0, 0, 0, 208}, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin}; /* * Constructs a GL renderer. */ GLRenderer::GLRenderer(const Model* model, QWidget* parent) : QGLWidget {parent}, HierarchyElement {parent}, m_model {model}, m_cameras { {"Top camera", {1, 0, 0, X, Z, false, false, false}}, // top {"Front camera", {0, 0, 1, X, Y, false, true, false}}, // front {"Left camera", {0, 1, 0, Z, Y, true, true, false}}, // left {"Bottom camera", {-1, 0, 0, X, Z, false, true, true}}, // bottom {"Back camera", {0, 0, -1, X, Y, true, true, true}}, // back {"Right camera", {0, -1, 0, Z, Y, false, true, true}}, // right {"Free camera", GLCamera::FreeCamera}, // free } { m_camera = (Camera) m_config->camera(); m_compiler = new GLCompiler (this); m_toolTipTimer = new QTimer (this); m_toolTipTimer->setSingleShot (true); setAcceptDrops (true); connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (showCameraIconTooltip())); resetAllAngles(); m_needZoomToFit = true; // Init camera icons for (Camera camera : iterateEnum<Camera>()) { const char* cameraIconNames[EnumLimits<Camera>::Count] = { "camera-top", "camera-front", "camera-left", "camera-bottom", "camera-back", "camera-right", "camera-free" }; CameraIcon* info = &m_cameraIcons[static_cast<int>(camera)]; info->image = GetIcon (cameraIconNames[static_cast<int>(camera)]); info->camera = camera; } calcCameraIcons(); } /* * Cleans up the axes VBOs when the renderer is destroyed. */ GLRenderer::~GLRenderer() { glDeleteBuffers(1, &m_axesVbo); glDeleteBuffers(1, &m_axesColorVbo); } /* * Calculates the camera icon locations. */ void GLRenderer::calcCameraIcons() { int i = 0; const int columns = 3; const int firstAtLastRow = countof(m_cameras) - (countof(m_cameras) % columns); for (CameraIcon& cameraIcon : m_cameraIcons) { int row = i / columns; int column = i % columns; // Do right-justifying on the last row. if (i >= firstAtLastRow) column += columns - (countof(m_cameras) % columns); int x1 = width() - 48 + (column * 16) - 1; int y1 = (row * 16) + 1; cameraIcon.sourceRect = {0, 0, 16, 16}; cameraIcon.targetRect = {x1, y1, 16, 16}; cameraIcon.hitRect = { cameraIcon.targetRect.x(), cameraIcon.targetRect.y(), cameraIcon.targetRect.width() + 1, cameraIcon.targetRect.height() + 1 }; ++i; } } /* * Returns the camera currently in use. */ GLCamera& GLRenderer::currentCamera() { return m_cameras[static_cast<int>(camera())]; } /* * Returns the camera currently in use. */ const GLCamera& GLRenderer::currentCamera() const { return m_cameras[static_cast<int>(camera())]; } /* * Prepares the GL context for rendering. */ void GLRenderer::initGLData() { glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_POLYGON_OFFSET_FILL); glPolygonOffset (1.0f, 1.0f); glEnable (GL_DEPTH_TEST); glShadeModel (GL_SMOOTH); glEnable (GL_MULTISAMPLE); if (m_config->antiAliasedLines()) { glEnable (GL_LINE_SMOOTH); glEnable (GL_POLYGON_SMOOTH); glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST); } else { glDisable (GL_LINE_SMOOTH); glDisable (GL_POLYGON_SMOOTH); } } /* * Returns the object currently highlighted by the cursor. */ LDObject* GLRenderer::objectAtCursor() const { return m_objectAtCursor; } // ============================================================================= // void GLRenderer::needZoomToFit() { m_needZoomToFit = true; } // ============================================================================= // void GLRenderer::resetAngles() { if (m_initialized) { // Why did I even bother trying to compute this by pen and paper? Let GL figure it out... glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glRotatef(30, 1, 0, 0); glRotatef(330, 0, 1, 0); glGetFloatv(GL_MODELVIEW_MATRIX, m_rotationMatrix.data()); glPopMatrix(); } currentCamera().setPanning(0, 0); needZoomToFit(); } // ============================================================================= // void GLRenderer::resetAllAngles() { Camera oldcam = camera(); for (int i = 0; i < 7; ++i) { setCamera ((Camera) i); resetAngles(); } setCamera (oldcam); } // ============================================================================= // void GLRenderer::initializeGL() { initializeOpenGLFunctions(); setBackground(); glLineWidth (m_config->lineThickness()); glLineStipple (1, 0x6666); setAutoFillBackground (false); setMouseTracking (true); setFocusPolicy (Qt::WheelFocus); m_compiler->initialize(); initializeAxes(); initializeLighting(); m_initialized = true; // Now that GL is initialized, we can reset angles. resetAllAngles(); } void GLRenderer::initializeLighting() { GLfloat materialShininess[] = {5.0}; GLfloat lightPosition[] = {1.0, 1.0, 1.0, 0.0}; GLfloat ambientLightingLevel[] = {0.8, 0.8, 0.8, 1.0}; glShadeModel(GL_SMOOTH); glMaterialfv(GL_FRONT, GL_SHININESS, materialShininess); glMaterialfv(GL_FRONT, GL_AMBIENT, ambientLightingLevel); glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_COLOR_MATERIAL); glEnable(GL_DEPTH_TEST); } // ============================================================================= // void GLRenderer::initializeAxes() { // Definitions for visual axes, drawn on the screen struct { QColor color; Vertex extrema; } axisInfo[3] = { { QColor {192, 96, 96}, Vertex {10000, 0, 0} }, // X { QColor {48, 192, 48}, Vertex {0, 10000, 0} }, // Y { QColor {48, 112, 192}, Vertex {0, 0, 10000} }, // Z }; float axisdata[18]; float colorData[18]; memset (axisdata, 0, sizeof axisdata); for (int i = 0; i < 3; ++i) { const auto& data = axisInfo[i]; for (Axis axis : axes) { axisdata[(i * 6) + axis] = data.extrema[axis]; axisdata[(i * 6) + 3 + axis] = -data.extrema[axis]; } int offset = i * 6; colorData[offset + 0] = colorData[offset + 3] = data.color.red(); colorData[offset + 1] = colorData[offset + 4] = data.color.green(); colorData[offset + 2] = colorData[offset + 5] = data.color.blue(); } glGenBuffers (1, &m_axesVbo); glBindBuffer (GL_ARRAY_BUFFER, m_axesVbo); glBufferData (GL_ARRAY_BUFFER, sizeof axisdata, axisdata, GL_STATIC_DRAW); glGenBuffers (1, &m_axesColorVbo); glBindBuffer (GL_ARRAY_BUFFER, m_axesColorVbo); glBufferData (GL_ARRAY_BUFFER, sizeof colorData, colorData, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, 0); } // ============================================================================= // void GLRenderer::setBackground() { if (not m_isDrawingSelectionScene) { // Otherwise use the background that the user wants. QColor color = m_config->backgroundColor(); if (color.isValid()) { color.setAlpha(255); m_useDarkBackground = luma(color) < 80; m_backgroundColor = color; qglClearColor(color); } } else { // The picking scene requires a black background. glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } } QColor GLRenderer::backgroundColor() const { return m_backgroundColor; } // ============================================================================= // void GLRenderer::refresh() { update(); if (isVisible()) swapBuffers(); } // ============================================================================= // void GLRenderer::resizeGL (int width, int height) { calcCameraIcons(); glViewport (0, 0, width, height); glMatrixMode (GL_PROJECTION); glLoadIdentity(); gluPerspective (45.0f, (double) width / (double) height, 1.0f, 10000.0f); glMatrixMode (GL_MODELVIEW); // Unfortunately Qt does not provide a resized() signal so we have to manually feed the information. for (GLCamera& camera : m_cameras) camera.rendererResized(width, height); } // ============================================================================= // void GLRenderer::drawGLScene() { if (m_needZoomToFit) { m_needZoomToFit = false; zoomAllToFit(); } if (m_config->drawWireframe() and not m_isDrawingSelectionScene) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); if (m_config->lighting()) glEnable(GL_LIGHTING); else glDisable(GL_LIGHTING); if (camera() != Camera::Free) { glMatrixMode (GL_PROJECTION); glPushMatrix(); glLoadIdentity(); const QSizeF& virtualSize = currentCamera().virtualSize(); glOrtho(-virtualSize.width(), virtualSize.width(), -virtualSize.height(), virtualSize.height(), -100.0f, 100.0f); glTranslatef(panning (X), panning (Y), 0.0f); if (camera() != Camera::Front and camera() != Camera::Back) glRotatef(90.0f, currentCamera().glRotate(X), currentCamera().glRotate(Y), 0); // Back camera needs to be handled differently if (camera() == Camera::Back) { glRotatef(180.0f, 1.0f, 0.0f, 0.0f); glRotatef(180.0f, 0.0f, 0.0f, 1.0f); } } else { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glTranslatef(0.0f, 0.0f, -2.0f); glTranslatef(panning (X), panning (Y), -zoom()); glMultMatrixf(m_rotationMatrix.constData()); } glEnableClientState (GL_NORMAL_ARRAY); glEnableClientState (GL_VERTEX_ARRAY); glEnableClientState (GL_COLOR_ARRAY); if (m_isDrawingSelectionScene) { drawVbos (VboClass::Triangles, VboSubclass::PickColors, GL_TRIANGLES); drawVbos (VboClass::Quads, VboSubclass::PickColors, GL_QUADS); drawVbos (VboClass::Lines, VboSubclass::PickColors, GL_LINES); drawVbos (VboClass::ConditionalLines, VboSubclass::PickColors, GL_LINES); } else { if (m_config->bfcRedGreenView()) { glEnable (GL_CULL_FACE); glCullFace (GL_BACK); drawVbos (VboClass::Triangles, VboSubclass::BfcFrontColors, GL_TRIANGLES); drawVbos (VboClass::Quads, VboSubclass::BfcFrontColors, GL_QUADS); glCullFace (GL_FRONT); drawVbos (VboClass::Triangles, VboSubclass::BfcBackColors, GL_TRIANGLES); drawVbos (VboClass::Quads, VboSubclass::BfcBackColors, GL_QUADS); glDisable (GL_CULL_FACE); } else { VboSubclass colors; if (m_config->randomColors()) colors = VboSubclass::RandomColors; else colors = VboSubclass::NormalColors; drawVbos (VboClass::Triangles, colors, GL_TRIANGLES); drawVbos (VboClass::Quads, colors, GL_QUADS); } drawVbos (VboClass::Lines, VboSubclass::NormalColors, GL_LINES); glEnable (GL_LINE_STIPPLE); drawVbos (VboClass::ConditionalLines, VboSubclass::NormalColors, GL_LINES); glDisable (GL_LINE_STIPPLE); if (m_config->drawAxes()) { glDisableClientState (GL_NORMAL_ARRAY); glBindBuffer (GL_ARRAY_BUFFER, m_axesVbo); glVertexPointer (3, GL_FLOAT, 0, NULL); glBindBuffer (GL_ARRAY_BUFFER, m_axesVbo); glColorPointer (3, GL_FLOAT, 0, NULL); glDrawArrays (GL_LINES, 0, 6); glEnableClientState (GL_NORMAL_ARRAY); CHECK_GL_ERROR(); } } glPopMatrix(); glBindBuffer (GL_ARRAY_BUFFER, 0); glDisableClientState (GL_VERTEX_ARRAY); glDisableClientState (GL_COLOR_ARRAY); glDisableClientState (GL_NORMAL_ARRAY); CHECK_GL_ERROR(); glDisable (GL_CULL_FACE); glMatrixMode (GL_MODELVIEW); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); } // ============================================================================= // void GLRenderer::drawVbos (VboClass surface, VboSubclass colors, GLenum type) { // Filter this through some configuration options if ((isOneOf (surface, VboClass::Quads, VboClass::Triangles) and m_config->drawSurfaces() == false) or (surface == VboClass::Lines and m_config->drawEdgeLines() == false) or (surface == VboClass::ConditionalLines and m_config->drawConditionalLines() == false)) { return; } int surfaceVboNumber = m_compiler->vboNumber(surface, VboSubclass::Surfaces); int colorVboNumber = m_compiler->vboNumber(surface, colors); int normalVboNumber = m_compiler->vboNumber(surface, VboSubclass::Normals); m_compiler->prepareVBO(surfaceVboNumber, m_model); m_compiler->prepareVBO(colorVboNumber, m_model); m_compiler->prepareVBO(normalVboNumber, m_model); GLuint surfaceVbo = m_compiler->vbo(surfaceVboNumber); GLuint colorVbo = m_compiler->vbo(colorVboNumber); GLuint normalVbo = m_compiler->vbo(normalVboNumber); GLsizei count = m_compiler->vboSize(surfaceVboNumber) / 3; if (count > 0) { glBindBuffer(GL_ARRAY_BUFFER, surfaceVbo); glVertexPointer(3, GL_FLOAT, 0, nullptr); CHECK_GL_ERROR(); glBindBuffer(GL_ARRAY_BUFFER, colorVbo); glColorPointer(4, GL_FLOAT, 0, nullptr); CHECK_GL_ERROR(); glBindBuffer(GL_ARRAY_BUFFER, normalVbo); glNormalPointer(GL_FLOAT, 0, nullptr); CHECK_GL_ERROR(); glDrawArrays(type, 0, count); CHECK_GL_ERROR(); } } QPen GLRenderer::textPen() const { return {m_useDarkBackground ? Qt::white : Qt::black}; } bool GLRenderer::freeCameraAllowed() const { return true; } void GLRenderer::paintEvent(QPaintEvent*) { makeCurrent(); initGLData(); drawGLScene(); if (isDrawingSelectionScene()) return; QPainter painter {this}; painter.setRenderHint(QPainter::Antialiasing); overpaint(painter); } void GLRenderer::overpaint(QPainter &painter) { // Draw a background for the selected camera painter.setPen(thinBorderPen); painter.setBrush(QBrush {QColor {0, 128, 160, 128}}); painter.drawRect(m_cameraIcons[static_cast<int>(camera())].hitRect); // Draw the camera icons for (const CameraIcon& info : m_cameraIcons) { // Don't draw the free camera icon when we can't use the free camera if (info.camera == Camera::Free and not freeCameraAllowed()) continue; painter.drawPixmap(info.targetRect, info.image, info.sourceRect); } // Draw a label for the current camera in the bottom left corner { QFontMetrics metrics {QFont {}}; int margin = 4; painter.setPen(textPen()); painter.drawText(QPoint {margin, height() - margin - metrics.descent()}, currentCamera().name()); } } // ============================================================================= // void GLRenderer::mouseReleaseEvent(QMouseEvent* event) { bool wasLeft = (m_lastButtons & Qt::LeftButton) and not (event->buttons() & Qt::LeftButton); m_panning = false; // Check if we selected a camera icon if (wasLeft and not mouseHasMoved()) { for (CameraIcon& info : m_cameraIcons) { if (info.targetRect.contains (event->pos())) { setCamera (info.camera); break; } } } update(); m_totalMouseMove = 0; } // ============================================================================= // void GLRenderer::mousePressEvent(QMouseEvent* event) { m_lastButtons = event->buttons(); m_totalMouseMove = 0; } // ============================================================================= // void GLRenderer::mouseMoveEvent(QMouseEvent* event) { int xMove = event->x() - m_mousePosition.x(); int yMove = event->y() - m_mousePosition.y(); m_totalMouseMove += qAbs(xMove) + qAbs(yMove); m_isCameraMoving = false; bool left = event->buttons() & Qt::LeftButton; bool mid = event->buttons() & Qt::MidButton; bool shift = event->modifiers() & Qt::ShiftModifier; if (mid or (left and shift)) { currentCamera().pan(xMove, yMove); m_panning = true; m_isCameraMoving = true; } else if (left and camera() == Camera::Free and (xMove != 0 or yMove != 0)) { // Apply current rotation input to the rotation matrix // ref: https://forums.ldraw.org/thread-22006-post-24426.html#pid24426 glPushMatrix(); glLoadIdentity(); // 0.6 is an arbitrary rotation sensitivity scalar glRotatef(0.6 * hypot(xMove, yMove), yMove, xMove, 0); glMultMatrixf(m_rotationMatrix.constData()); glGetFloatv(GL_MODELVIEW_MATRIX, m_rotationMatrix.data()); glPopMatrix(); m_isCameraMoving = true; } // Start the tool tip timer m_toolTipTimer->start (500); // Update 2d position m_mousePosition = event->pos(); m_globalpos = event->globalPos(); m_mousePositionF = event->localPos(); highlightCursorObject(); update(); event->accept(); } // ============================================================================= // void GLRenderer::keyPressEvent(QKeyEvent* event) { m_currentKeyboardModifiers = event->modifiers(); } // ============================================================================= // void GLRenderer::keyReleaseEvent(QKeyEvent* event) { m_currentKeyboardModifiers = event->modifiers(); update(); } // ============================================================================= // void GLRenderer::wheelEvent(QWheelEvent* ev) { makeCurrent(); currentCamera().zoomNotch(ev->delta() > 0); m_isCameraMoving = true; update(); ev->accept(); } // ============================================================================= // void GLRenderer::leaveEvent(QEvent*) { m_toolTipTimer->stop(); update(); } // ============================================================================= // void GLRenderer::setCamera(Camera camera) { // The edit mode may forbid the free camera. if (freeCameraAllowed() or camera != Camera::Free) { m_camera = camera; m_config->setCamera(static_cast<int>(camera)); } } /* * Returns the set of objects found in the specified pixel area. */ QSet<LDObject*> GLRenderer::pick(const QRect& range) { makeCurrent(); QSet<LDObject*> newSelection; // Paint the picking scene setPicking(true); drawGLScene(); int x0 = range.left(); int y0 = range.top(); int x1 = x0 + range.width(); int y1 = y0 + range.height(); // Clamp the values to ensure they're within bounds x0 = qMax (0, x0); y0 = qMax (0, y0); x1 = qMin (x1, width()); y1 = qMin (y1, height()); const int areawidth = (x1 - x0); const int areaheight = (y1 - y0); const qint32 numpixels = areawidth * areaheight; // Allocate space for the pixel data. QVector<unsigned char> pixelData; pixelData.resize(4 * numpixels); // Read pixels from the color buffer. glReadPixels(x0, height() - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixelData.data()); QSet<qint32> indices; // Go through each pixel read and add them to the selection. // Each pixel maps to an LDObject index injectively. // Note: black is background, those indices are skipped. for (unsigned char *pixelCursor = pixelData.begin(); pixelCursor < pixelData.end(); pixelCursor += 4) { qint32 index = pixelCursor[0] * 0x10000 + pixelCursor[1] * 0x100 + pixelCursor[2] * 0x1; if (index != 0) indices.insert(index); } // For each index read, resolve the LDObject behind it and add it to the selection. for (qint32 index : indices) { LDObject* object = LDObject::fromID(index); if (object != nullptr) newSelection.insert(object); } setPicking(false); repaint(); return newSelection; } /* * Simpler version of GLRenderer::pick which simply picks whatever object on the cursor */ LDObject* GLRenderer::pick(int mouseX, int mouseY) { unsigned char pixel[4]; makeCurrent(); setPicking(true); drawGLScene(); glReadPixels(mouseX, height() - mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); LDObject* object = LDObject::fromID(pixel[0] * 0x10000 + pixel[1] * 0x100 + pixel[2]); setPicking(false); repaint(); return object; } // ============================================================================= // void GLRenderer::setPicking(bool value) { m_isDrawingSelectionScene = value; setBackground(); if (m_isDrawingSelectionScene) { glDisable(GL_DITHER); // Use particularly thick lines while picking ease up selecting lines. glLineWidth(qMax<double>(m_config->lineThickness(), 6.5)); } else { glEnable(GL_DITHER); // Restore line thickness glLineWidth(m_config->lineThickness()); } } // ============================================================================= // void GLRenderer::forgetObject(LDObject* obj) { if (m_objectAtCursor == obj) m_objectAtCursor = nullptr; } // ============================================================================= // QByteArray GLRenderer::capturePixels() { QByteArray result; result.resize (4 * width() * height()); m_takingScreenCapture = true; update(); // Smile! m_takingScreenCapture = false; glReadPixels(0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_BYTE, result.data()); return result; } /* * Show a tooltip if the cursor is currently hovering over a camera icon. */ void GLRenderer::showCameraIconTooltip() { for (CameraIcon & icon : m_cameraIcons) { if (icon.targetRect.contains (m_mousePosition)) { QToolTip::showText(m_globalpos, m_cameras[static_cast<int>(icon.camera)].name()); update(); break; } } } // ============================================================================= // void GLRenderer::zoomToFit() { currentCamera().setZoom(30.0f); bool lastfilled = false; bool firstrun = true; enum { black = 0xFF000000 }; bool inward = true; int runaway = 50; // Use the pick list while drawing the scene, this way we can tell whether borders // are background or not. setPicking (true); while (--runaway) { if (zoom() > 10000.0 or zoom() < 0.0) { // Nothing to draw if we get here. currentCamera().setZoom(30.0); break; } currentCamera().zoomNotch(inward); QVector<unsigned char> capture (4 * width() * height()); drawGLScene(); glReadPixels (0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_BYTE, capture.data()); QImage image (capture.constData(), width(), height(), QImage::Format_ARGB32); bool filled = false; // Check the top and bottom rows for (int i = 0; i < image.width(); ++i) { if (image.pixel (i, 0) != black or image.pixel (i, height() - 1) != black) { filled = true; break; } } // Left and right edges if (filled == false) { for (int i = 0; i < image.height(); ++i) { if (image.pixel (0, i) != black or image.pixel (width() - 1, i) != black) { filled = true; break; } } } if (firstrun) { // If this is the first run, we don't know enough to determine // whether the zoom was to fit, so we mark in our knowledge so // far and start over. inward = not filled; firstrun = false; } else { // If this run filled the screen and the last one did not, the // last run had ideal zoom - zoom a bit back and we should reach it. if (filled and not lastfilled) { currentCamera().zoomNotch(false); break; } // If this run did not fill the screen and the last one did, we've // now reached ideal zoom so we're done here. if (not filled and lastfilled) break; inward = not filled; } lastfilled = filled; } setPicking (false); } // ============================================================================= // void GLRenderer::zoomAllToFit() { zoomToFit(); } // ============================================================================= // void GLRenderer::highlightCursorObject() { if (not m_config->highlightObjectBelowCursor() and objectAtCursor() == nullptr) return; LDObject* newObject = nullptr; LDObject* oldObject = objectAtCursor(); qint32 newIndex; if (m_isCameraMoving or not m_config->highlightObjectBelowCursor()) { newIndex = 0; } else { setPicking (true); drawGLScene(); setPicking (false); unsigned char pixel[4]; glReadPixels (m_mousePosition.x(), height() - m_mousePosition.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel[0]); newIndex = pixel[0] * 0x10000 | pixel[1] * 0x100 | pixel[2]; } if (newIndex != (oldObject ? oldObject->id() : 0)) { if (newIndex != 0) newObject = LDObject::fromID (newIndex); m_objectAtCursor = newObject; if (oldObject) emit objectHighlightingChanged(oldObject); if (newObject) emit objectHighlightingChanged(newObject); } update(); } bool GLRenderer::mouseHasMoved() const { return m_totalMouseMove >= 10; } QPoint const& GLRenderer::mousePosition() const { return m_mousePosition; } QPointF const& GLRenderer::mousePositionF() const { return m_mousePositionF; } Qt::KeyboardModifiers GLRenderer::keyboardModifiers() const { return m_currentKeyboardModifiers; } Camera GLRenderer::camera() const { return m_camera; } double GLRenderer::panning (Axis ax) const { return (ax == X) ? currentCamera().panningX() : currentCamera().panningY(); } double GLRenderer::zoom() { return currentCamera().zoom(); } const QGenericMatrix<4, 4, GLfloat>& GLRenderer::rotationMatrix() const { return m_rotationMatrix; } bool GLRenderer::isDrawingSelectionScene() const { return m_isDrawingSelectionScene; } Qt::MouseButtons GLRenderer::lastButtons() const { return m_lastButtons; } const Model* GLRenderer::model() const { return m_model; }