Mon, 28 Apr 2014 17:00:53 +0300
- vertex snapping is now smarter, does a depth compare to favor vertices closer to camera and does a short-range 2d check + out-of-range 2d check
/* * LDForge: LDraw parts authoring CAD * Copyright (C) 2013, 2014 Santeri 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 <QGLWidget> #include <QWheelEvent> #include <QMouseEvent> #include <QContextMenuEvent> #include <QInputDialog> #include <QToolTip> #include <qtextdocument.h> #include <QTimer> #include <GL/glu.h> #include "main.h" #include "configuration.h" #include "ldDocument.h" #include "glRenderer.h" #include "colors.h" #include "mainWindow.h" #include "miscallenous.h" #include "editHistory.h" #include "dialogs.h" #include "addObjectDialog.h" #include "messageLog.h" #include "primitives.h" #include "misc/ringFinder.h" #include "glCompiler.h" static const LDFixedCameraInfo g_FixedCameras[6] = { {{ 1, 0, 0 }, X, Z, false, false, false }, // top {{ 0, 0, 0 }, X, Y, false, true, false }, // front {{ 0, 1, 0 }, Z, Y, true, true, false }, // left {{ -1, 0, 0 }, X, Z, false, true, true }, // bottom {{ 0, 0, 0 }, X, Y, true, true, true }, // back {{ 0, -1, 0 }, Z, Y, false, true, true }, // right }; // Matrix templates for circle drawing. 2 is substituted with // the scale value, 1 is inverted to -1 if needed. static const Matrix g_circleDrawMatrixTemplates[3] = { { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, }; cfg (String, gl_bgcolor, "#FFFFFF") cfg (String, gl_maincolor, "#A0A0A0") cfg (Float, gl_maincolor_alpha, 1.0) cfg (Int, gl_linethickness, 2) cfg (Bool, gl_colorbfc, false) cfg (Int, gl_camera, GLRenderer::EFreeCamera) cfg (Bool, gl_blackedges, false) cfg (Bool, gl_axes, false) cfg (Bool, gl_wireframe, false) cfg (Bool, gl_logostuds, false) cfg (Bool, gl_aa, true) cfg (Bool, gl_linelengths, true) cfg (Bool, gl_drawangles, false) cfg (Bool, gl_randomcolors, false) // argh const char* g_CameraNames[7] = { QT_TRANSLATE_NOOP ("GLRenderer", "Top"), QT_TRANSLATE_NOOP ("GLRenderer", "Front"), QT_TRANSLATE_NOOP ("GLRenderer", "Left"), QT_TRANSLATE_NOOP ("GLRenderer", "Bottom"), QT_TRANSLATE_NOOP ("GLRenderer", "Back"), QT_TRANSLATE_NOOP ("GLRenderer", "Right"), QT_TRANSLATE_NOOP ("GLRenderer", "Free") }; const GL::EFixedCamera g_Cameras[7] = { GL::ETopCamera, GL::EFrontCamera, GL::ELeftCamera, GL::EBottomCamera, GL::EBackCamera, GL::ERightCamera, GL::EFreeCamera }; struct LDGLAxis { const QColor col; const Vertex vert; }; // Definitions for visual axes, drawn on the screen static const LDGLAxis g_GLAxes[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 }; static GLuint g_GLAxes_VBO; static GLuint g_GLAxes_ColorVBO; // ============================================================================= // GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) { m_isPicking = m_rangepick = false; m_camera = (EFixedCamera) gl_camera; m_drawToolTip = false; m_editMode = ESelectMode; m_rectdraw = false; m_panning = false; m_compiler = new GLCompiler; setDocument (null); setDrawOnly (false); setMessageLog (null); m_width = m_height = -1; m_hoverpos = g_origin; m_compiler = new GLCompiler; m_toolTipTimer = new QTimer (this); m_toolTipTimer->setSingleShot (true); connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); m_thinBorderPen = m_thickBorderPen; m_thinBorderPen.setWidth (1); // Init camera icons for (const GL::EFixedCamera cam : g_Cameras) { String iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower()); CameraIcon* info = &m_cameraIcons[cam]; info->img = new QPixmap (getIcon (iconname)); info->cam = cam; } calcCameraIcons(); } // ============================================================================= // GLRenderer::~GLRenderer() { for (int i = 0; i < 6; ++i) delete currentDocumentData().overlays[i].img; for (CameraIcon& info : m_cameraIcons) delete info.img; delete m_compiler; } // ============================================================================= // Calculates the "hitboxes" of the camera icons so that we can tell when the // cursor is pointing at the camera icon. // void GLRenderer::calcCameraIcons() { int i = 0; for (CameraIcon& info : m_cameraIcons) { // MATH const long x1 = (m_width - (info.cam != EFreeCamera ? 48 : 16)) + ((i % 3) * 16) - 1, y1 = ((i / 3) * 16) + 1; info.srcRect = QRect (0, 0, 16, 16); info.destRect = QRect (x1, y1, 16, 16); info.selRect = QRect ( info.destRect.x(), info.destRect.y(), info.destRect.width() + 1, info.destRect.height() + 1 ); ++i; } } // ============================================================================= // 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 (gl_aa) { 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); } } // ============================================================================= // void GLRenderer::needZoomToFit() { if (document() != null) currentDocumentData().needZoomToFit = true; } // ============================================================================= // void GLRenderer::resetAngles() { rot (X) = 30.0f; rot (Y) = 325.f; pan (X) = pan (Y) = rot (Z) = 0.0f; needZoomToFit(); } // ============================================================================= // void GLRenderer::resetAllAngles() { EFixedCamera oldcam = camera(); for (int i = 0; i < 7; ++i) { setCamera ((EFixedCamera) i); resetAngles(); } setCamera (oldcam); } // ============================================================================= // void GLRenderer::initializeGL() { setBackground(); glLineWidth (gl_linethickness); glLineStipple (1, 0x6666); setAutoFillBackground (false); setMouseTracking (true); setFocusPolicy (Qt::WheelFocus); compiler()->initialize(); initializeAxes(); } // ============================================================================= // void GLRenderer::initializeAxes() { float axesdata[18]; float colordata[18]; memset (axesdata, 0, sizeof axesdata); for (int i = 0; i < 3; ++i) { for_axes (ax) { axesdata[(i * 6) + ax] = g_GLAxes[i].vert[ax]; axesdata[(i * 6) + 3 + ax] = -g_GLAxes[i].vert[ax]; } for (int j = 0; j < 2; ++j) { colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red(); colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green(); colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue(); } } glGenBuffers (1, &g_GLAxes_VBO); glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO); glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW); glGenBuffers (1, &g_GLAxes_ColorVBO); glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_ColorVBO); glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, 0); } // ============================================================================= // QColor GLRenderer::getMainColor() { QColor col (gl_maincolor); if (not col.isValid()) return QColor (0, 0, 0); col.setAlpha (gl_maincolor_alpha * 255.f); return col; } // ============================================================================= // void GLRenderer::setBackground() { QColor col (gl_bgcolor); if (not col.isValid()) return; col.setAlpha (255); m_darkbg = luma (col) < 80; m_bgcolor = col; qglClearColor (col); } // ============================================================================= // void GLRenderer::refresh() { update(); swapBuffers(); } // ============================================================================= // void GLRenderer::hardRefresh() { compiler()->compileDocument (getCurrentDocument()); refresh(); glLineWidth (gl_linethickness); // TODO: ...? } // ============================================================================= // void GLRenderer::resizeGL (int w, int h) { m_width = w; m_height = h; calcCameraIcons(); glViewport (0, 0, w, h); glMatrixMode (GL_PROJECTION); glLoadIdentity(); gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f); glMatrixMode (GL_MODELVIEW); } // ============================================================================= // void GLRenderer::drawGLScene() { if (document() == null) return; if (currentDocumentData().needZoomToFit) { currentDocumentData().needZoomToFit = false; zoomAllToFit(); } if (gl_wireframe && not isPicking()) glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable (GL_DEPTH_TEST); if (camera() != EFreeCamera) { glMatrixMode (GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); glTranslatef (pan (X), pan (Y), 0.0f); if (camera() != EFrontCamera && camera() != EBackCamera) { glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0], g_FixedCameras[camera()].glrotate[1], g_FixedCameras[camera()].glrotate[2]); } // Back camera needs to be handled differently if (camera() == EBackCamera) { 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 (pan (X), pan (Y), -zoom()); glRotatef (rot (X), 1.0f, 0.0f, 0.0f); glRotatef (rot (Y), 0.0f, 1.0f, 0.0f); glRotatef (rot (Z), 0.0f, 0.0f, 1.0f); } glEnableClientState (GL_VERTEX_ARRAY); glEnableClientState (GL_COLOR_ARRAY); if (isPicking()) { drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES); drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS); drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES); drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES); } else { if (gl_colorbfc) { glEnable (GL_CULL_FACE); glCullFace (GL_BACK); drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES); drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS); glCullFace (GL_FRONT); drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES); drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS); glDisable (GL_CULL_FACE); } else { if (gl_randomcolors) { drawVBOs (VBOSF_Triangles, VBOCM_RandomColors, GL_TRIANGLES); drawVBOs (VBOSF_Quads, VBOCM_RandomColors, GL_QUADS); } else { drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES); drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS); } } drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES); glEnable (GL_LINE_STIPPLE); drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES); glDisable (GL_LINE_STIPPLE); if (gl_axes) { glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO); glVertexPointer (3, GL_FLOAT, 0, NULL); glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO); glColorPointer (3, GL_FLOAT, 0, NULL); glDrawArrays (GL_LINES, 0, 6); checkGLError(); } } glPopMatrix(); glBindBuffer (GL_ARRAY_BUFFER, 0); glDisableClientState (GL_VERTEX_ARRAY); glDisableClientState (GL_COLOR_ARRAY); checkGLError(); glDisable (GL_CULL_FACE); glMatrixMode (GL_MODELVIEW); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); } // ============================================================================= // void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type) { int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces); int colornum = m_compiler->vboNumber (surface, colors); m_compiler->prepareVBO (surfacenum); m_compiler->prepareVBO (colornum); GLuint surfacevbo = m_compiler->vbo (surfacenum); GLuint colorvbo = m_compiler->vbo (colornum); GLsizei count = m_compiler->vboSize (surfacenum) / 3; if (count > 0) { glBindBuffer (GL_ARRAY_BUFFER, surfacevbo); glVertexPointer (3, GL_FLOAT, 0, null); checkGLError(); glBindBuffer (GL_ARRAY_BUFFER, colorvbo); glColorPointer (4, GL_FLOAT, 0, null); checkGLError(); glDrawArrays (type, 0, count); checkGLError(); } } // ============================================================================= // This converts a 2D point on the screen to a 3D point in the model. If 'snap' // is true, the 3D point will snap to the current grid. // Vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const { assert (camera() != EFreeCamera); Vertex pos3d; const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; const Axis axisX = cam->axisX; const Axis axisY = cam->axisY; const int negXFac = cam->negX ? -1 : 1, negYFac = cam->negY ? -1 : 1; // Calculate cx and cy - these are the LDraw unit coords the cursor is at. double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); if (snap) { cx = Grid::snap (cx, (Grid::Config) axisX); cy = Grid::snap (cy, (Grid::Config) axisY); } cx *= negXFac; cy *= negYFac; roundToDecimals (cx, 4); roundToDecimals (cy, 4); // Create the vertex from the coordinates pos3d.setCoordinate (axisX, cx); pos3d.setCoordinate (axisY, cy); pos3d.setCoordinate ((Axis) (3 - axisX - axisY), getDepthValue()); return pos3d; } // ============================================================================= // // Inverse operation for the above - convert a 3D position to a 2D screen // position. Don't ask me how this code manages to work, I don't even know. // QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const { GLfloat m[16]; const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; const Axis axisX = cam->axisX; const Axis axisY = cam->axisY; const int negXFac = cam->negX ? -1 : 1, negYFac = cam->negY ? -1 : 1; glGetFloatv (GL_MODELVIEW_MATRIX, m); const double x = pos3d.x(); const double y = pos3d.y(); const double z = pos3d.z(); Vertex transformed; transformed.setX ((m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]); transformed.setY ((m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]); transformed.setZ ((m[8] * x) + (m[9] * y) + (m[10] * z) + m[11]); double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth); double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight); return QPoint (rx, -ry); } // ============================================================================= // void GLRenderer::paintEvent (QPaintEvent* ev) { Q_UNUSED (ev) makeCurrent(); m_virtWidth = zoom(); m_virtHeight = (m_height * m_virtWidth) / m_width; initGLData(); drawGLScene(); const QPen textpen (m_darkbg ? Qt::white : Qt::black); const QBrush polybrush (QColor (64, 192, 0, 128)); QPainter paint (this); QFontMetrics metrics = QFontMetrics (QFont()); paint.setRenderHint (QPainter::HighQualityAntialiasing); // If we wish to only draw the brick, stop here if (isDrawOnly()) return; #ifndef RELEASE if (not isPicking()) { String text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6", rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom()); QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); paint.setPen (textpen); paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(), textSize.height(), Qt::AlignCenter, text); } #endif if (camera() != EFreeCamera && !isPicking()) { // Paint the overlay image if we have one const LDGLOverlay& overlay = currentDocumentData().overlays[camera()]; if (overlay.img != null) { QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0), v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1); QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())), srcRect (0, 0, overlay.img->width(), overlay.img->height()); paint.drawImage (targRect, *overlay.img, srcRect); } // Paint the coordinates onto the screen. String text = format (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); QFontMetrics metrics = QFontMetrics (font()); QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); paint.setPen (textpen); paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(), textSize.height(), Qt::AlignCenter, text); QPen linepen = m_thinBorderPen; linepen.setWidth (2); linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black); // Mode-specific rendering if (editMode() == EDrawMode) { QPoint poly[4]; Vertex poly3d[4]; int numverts = 4; // Calculate polygon data if (not m_rectdraw) { numverts = m_drawedVerts.size() + 1; int i = 0; for (Vertex& vert : m_drawedVerts) poly3d[i++] = vert; // Draw the cursor vertex as the last one in the list. if (numverts <= 4) poly3d[i] = m_hoverpos; else numverts = 4; } else { // Get vertex information from m_rectverts if (m_drawedVerts.size() > 0) for (int i = 0; i < numverts; ++i) poly3d[i] = m_rectverts[i]; else poly3d[0] = m_hoverpos; } // Convert to 2D for (int i = 0; i < numverts; ++i) poly[i] = coordconv3_2 (poly3d[i]); if (numverts > 0) { // Draw the polygon-to-be paint.setBrush (polybrush); paint.drawPolygon (poly, numverts); // Draw vertex blips for (int i = 0; i < numverts; ++i) { QPoint& blip = poly[i]; paint.setPen (linepen); drawBlip (paint, blip); // Draw their coordinates paint.setPen (textpen); paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true)); } // Draw line lenghts and angle info if appropriate if (numverts >= 2) { int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1; paint.setPen (textpen); for (int i = 0; i < numlines; ++i) { const int j = (i + 1 < numverts) ? i + 1 : 0; const int h = (i - 1 >= 0) ? i - 1 : numverts - 1; if (gl_linelengths) { const String label = String::number ((poly3d[j] - poly3d[i]).length()); QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint(); paint.drawText (origin, label); } if (gl_drawangles) { QLineF l0 (poly[h], poly[i]), l1 (poly[i], poly[j]); double angle = 180 - l0.angleTo (l1); if (angle < 0) angle = 180 - l1.angleTo (l0); String label = String::number (angle) + String::fromUtf8 (QByteArray ("\302\260")); QPoint pos = poly[i]; pos.setY (pos.y() + metrics.height()); paint.drawText (pos, label); } } } } } elif (editMode() == ECircleMode) { // If we have not specified the center point of the circle yet, preview it on the screen. if (m_drawedVerts.isEmpty()) drawBlip (paint, coordconv3_2 (m_hoverpos)); else { QVector<Vertex> verts, verts2; const double dist0 = getCircleDrawDist (0), dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; const int segs = g_lores; const double angleUnit = (2 * pi) / segs; Axis relX, relY; QVector<QPoint> ringpoints, circlepoints, circle2points; getRelativeAxes (relX, relY); // Calculate the preview positions of vertices for (int i = 0; i < segs; ++i) { Vertex v = g_origin; v.setCoordinate (relX, m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0)); v.setCoordinate (relY, m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0)); verts << v; if (dist1 != -1) { v.setCoordinate (relX, m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1)); v.setCoordinate (relY, m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1)); verts2 << v; } } int i = 0; for (const Vertex& v : verts + verts2) { // Calculate the 2D point of the vertex QPoint point = coordconv3_2 (v); // Draw a green blip at where it is drawBlip (paint, point); // Add it to the list of points for the green ring fill. ringpoints << point; // Also add the circle points to separate lists if (i < verts.size()) circlepoints << point; else circle2points << point; ++i; } // Insert the first point as the seventeenth one so that // the ring polygon is closed properly. if (ringpoints.size() >= 16) ringpoints.insert (16, ringpoints[0]); // Same for the outer ring. Note that the indices are offset by 1 // because of the insertion done above bumps the values. if (ringpoints.size() >= 33) ringpoints.insert (33, ringpoints[17]); // Draw the ring paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush); paint.setPen (Qt::NoPen); paint.drawPolygon (QPolygon (ringpoints)); // Draw the circles paint.setBrush (Qt::NoBrush); paint.setPen (linepen); paint.drawPolygon (QPolygon (circlepoints)); paint.drawPolygon (QPolygon (circle2points)); { // Draw the current radius in the middle of the circle. QPoint origin = coordconv3_2 (m_drawedVerts[0]); String label = String::number (dist0); paint.setPen (textpen); paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); if (m_drawedVerts.size() >= 2) { label = String::number (dist1); paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label); } } } } } // Camera icons if (not isPicking()) { // Draw a background for the selected camera paint.setPen (m_thinBorderPen); paint.setBrush (QBrush (QColor (0, 128, 160, 128))); paint.drawRect (m_cameraIcons[camera()].selRect); // Draw the actual icons for (CameraIcon& info : m_cameraIcons) { // Don't draw the free camera icon when in draw mode if (&info == &m_cameraIcons[GL::EFreeCamera] && editMode() != ESelectMode) continue; paint.drawPixmap (info.destRect, *info.img, info.srcRect); } String formatstr = tr ("%1 Camera"); // Draw a label for the current camera in the bottom left corner { const int margin = 4; String label; label = format (formatstr, tr (g_CameraNames[camera()])); paint.setPen (textpen); paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); } // Tool tips if (m_drawToolTip) { if (not m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos)) m_drawToolTip = false; else { String label = format (formatstr, tr (g_CameraNames[m_toolTipCamera])); QToolTip::showText (m_globalpos, label); } } } // Message log if (messageLog()) { int y = 0; const int margin = 2; QColor penColor = textpen.color(); for (const MessageManager::Line& line : messageLog()->getLines()) { penColor.setAlphaF (line.alpha); paint.setPen (penColor); paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); y += metrics.height(); } } // If we're range-picking, draw a rectangle encompassing the selection area. if (m_rangepick && not isPicking() && m_totalmove >= 10) { int x0 = m_rangeStart.x(), y0 = m_rangeStart.y(), x1 = m_pos.x(), y1 = m_pos.y(); QRect rect (x0, y0, x1 - x0, y1 - y0); QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); fillColor.setAlphaF (0.2f); paint.setPen (m_thickBorderPen); paint.setBrush (QBrush (fillColor)); paint.drawRect (rect); } } // ============================================================================= // void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const { QPen pen = m_thinBorderPen; const int blipsize = 8; pen.setWidth (1); paint.setPen (pen); paint.setBrush (QColor (64, 192, 0)); paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); } // ============================================================================= // void GLRenderer::clampAngle (double& angle) const { while (angle < 0) angle += 360.0; while (angle > 360.0) angle -= 360.0; } // ============================================================================= // void GLRenderer::addDrawnVertex (Vertex pos) { // If we picked an already-existing vertex, stop drawing if (editMode() == EDrawMode) { for (Vertex& vert : m_drawedVerts) { if (vert == pos) { endDraw (true); return; } } } m_drawedVerts << pos; } // ============================================================================= // void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) { const bool wasLeft = (m_lastButtons & Qt::LeftButton) && not (ev->buttons() & Qt::LeftButton), wasRight = (m_lastButtons & Qt::RightButton) && not (ev->buttons() & Qt::RightButton), wasMid = (m_lastButtons & Qt::MidButton) && not (ev->buttons() & Qt::MidButton); if (m_panning) m_panning = false; if (wasLeft) { // Check if we selected a camera icon if (not m_rangepick) { for (CameraIcon & info : m_cameraIcons) { if (info.destRect.contains (ev->pos())) { setCamera (info.cam); goto end; } } } switch (editMode()) { case EDrawMode: { if (m_rectdraw) { if (m_drawedVerts.size() == 2) { endDraw (true); return; } } else { // If we have 4 verts, stop drawing. if (m_drawedVerts.size() >= 4) { endDraw (true); return; } if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier) { m_rectdraw = true; updateRectVerts(); } } addDrawnVertex (m_hoverpos); break; } case ECircleMode: { if (m_drawedVerts.size() == 3) { endDraw (true); return; } addDrawnVertex (m_hoverpos); break; } case ESelectMode: { if (not isDrawOnly()) { if (m_totalmove < 10) m_rangepick = false; if (not m_rangepick) m_addpick = (m_keymods & Qt::ControlModifier); if (m_totalmove < 10 || m_rangepick) pick (ev->x(), ev->y()); } break; } } m_rangepick = false; } if (wasMid && editMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10) { // Find the closest vertex to our cursor double minimumDistance = 1024.0; const Vertex* closest = null; Vertex cursorPosition = coordconv2_3 (m_pos, false); QPoint cursorPosition2D (m_pos); const Axis relZ = getRelativeZ(); QList<Vertex> vertices; for (auto it = document()->vertices().begin(); it != document()->vertices().end(); ++it) vertices << it.key(); // Sort the vertices in order of distance to camera std::sort (vertices.begin(), vertices.end(), [&](const Vertex& a, const Vertex& b) -> bool { if (g_FixedCameras[camera()].negatedDepth) return a[relZ] > b[relZ]; return a[relZ] < b[relZ]; }); for (const Vertex& vrt : vertices) { // If the vertex in 2d space is very close to the cursor then we use // it regardless of depth. QPoint vect2d = coordconv3_2 (vrt) - cursorPosition2D; const double distance2DSquared = std::pow (vect2d.x(), 2) + std::pow (vect2d.y(), 2); if (distance2DSquared < 16.0 * 16.0) { closest = &vrt; break; } // Check if too far away from the cursor. if (distance2DSquared > 64.0 * 64.0) continue; // Not very close to the cursor. Compare using true distance, // including depth. const double distanceSquared = (vrt - cursorPosition).lengthSquared(); if (distanceSquared < minimumDistance) { minimumDistance = distanceSquared; closest = &vrt; } } if (closest != null) addDrawnVertex (*closest); } if (wasRight && not m_drawedVerts.isEmpty()) { // Remove the last vertex m_drawedVerts.removeLast(); if (m_drawedVerts.isEmpty()) m_rectdraw = false; } end: update(); m_totalmove = 0; } // ============================================================================= // void GLRenderer::mousePressEvent (QMouseEvent* ev) { m_totalmove = 0; if (ev->modifiers() & Qt::ControlModifier) { m_rangepick = true; m_rangeStart.setX (ev->x()); m_rangeStart.setY (ev->y()); m_addpick = (m_keymods & Qt::AltModifier); ev->accept(); } m_lastButtons = ev->buttons(); } // ============================================================================= // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // ============================================================================= void GLRenderer::mouseMoveEvent (QMouseEvent* ev) { int dx = ev->x() - m_pos.x(); int dy = ev->y() - m_pos.y(); m_totalmove += abs (dx) + abs (dy); const bool left = ev->buttons() & Qt::LeftButton, mid = ev->buttons() & Qt::MidButton, shift = ev->modifiers() & Qt::ShiftModifier; if (mid || (left && shift)) { pan (X) += 0.03f * dx * (zoom() / 7.5f); pan (Y) -= 0.03f * dy * (zoom() / 7.5f); m_panning = true; } elif (left && not m_rangepick && camera() == EFreeCamera) { rot (X) = rot (X) + dy; rot (Y) = rot (Y) + dx; clampAngle (rot (X)); clampAngle (rot (Y)); } // Start the tool tip timer if (not m_drawToolTip) m_toolTipTimer->start (500); // Update 2d position m_pos = ev->pos(); m_globalpos = ev->globalPos(); // Calculate 3d position of the cursor m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin; // Update rect vertices since m_hoverpos may have changed updateRectVerts(); update(); } // ============================================================================= // void GLRenderer::keyPressEvent (QKeyEvent* ev) { m_keymods = ev->modifiers(); } // ============================================================================= // void GLRenderer::keyReleaseEvent (QKeyEvent* ev) { m_keymods = ev->modifiers(); } // ============================================================================= // void GLRenderer::wheelEvent (QWheelEvent* ev) { makeCurrent(); zoomNotch (ev->delta() > 0); zoom() = clamp (zoom(), 0.01, 10000.0); update(); ev->accept(); } // ============================================================================= // void GLRenderer::leaveEvent (QEvent* ev) { (void) ev; m_drawToolTip = false; m_toolTipTimer->stop(); update(); } // ============================================================================= // void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) { g_win->spawnContextMenu (ev->globalPos()); } // ============================================================================= // void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam) { m_camera = cam; gl_camera = (int) cam; g_win->updateEditModeActions(); } // ============================================================================= // void GLRenderer::pick (int mouseX, int mouseY) { makeCurrent(); // Use particularly thick lines while picking ease up selecting lines. glLineWidth (max<double> (gl_linethickness, 6.5f)); // Clear the selection if we do not wish to add to it. if (not m_addpick) { LDObjectList oldsel = selection(); getCurrentDocument()->clearSelection(); for (LDObject* obj : oldsel) compileObject (obj); } setPicking (true); // Paint the picking scene glDisable (GL_DITHER); glClearColor (1.0f, 1.0f, 1.0f, 1.0f); drawGLScene(); int x0 = mouseX, y0 = mouseY; int x1, y1; // Determine how big an area to read - with range picking, we pick by // the area given, with single pixel picking, we use an 1 x 1 area. if (m_rangepick) { x1 = m_rangeStart.x(); y1 = m_rangeStart.y(); } else { x1 = x0 + 1; y1 = y0 + 1; } // x0 and y0 must be less than x1 and y1, respectively. if (x0 > x1) qSwap (x0, x1); if (y0 > y1) qSwap (y0, y1); // Clamp the values to ensure they're within bounds x0 = max (0, x0); y0 = max (0, y0); x1 = min (x1, m_width); y1 = min (y1, m_height); const int areawidth = (x1 - x0); const int areaheight = (y1 - y0); const qint32 numpixels = areawidth * areaheight; // Allocate space for the pixel data. uchar* const pixeldata = new uchar[4 * numpixels]; uchar* pixelptr = &pixeldata[0]; // Read pixels from the color buffer. glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); LDObject* removedObj = null; // Go through each pixel read and add them to the selection. for (qint32 i = 0; i < numpixels; ++i) { QList<float> selfloats; selfloats << ((float) pixelptr[0]) / 255.0f; selfloats << ((float) pixelptr[1]) / 255.0f; selfloats << ((float) pixelptr[2]) / 255.0f; selfloats << ((float) pixelptr[3]) / 255.0f; qint32 idx = (*(pixelptr + 0) * 0x10000) + (*(pixelptr + 1) * 0x100) + *(pixelptr + 2); pixelptr += 4; if (idx == 0xFFFFFF) continue; // White is background; skip LDObject* obj = LDObject::fromID (idx); assert (obj != null); // If this is an additive single pick and the object is currently selected, // we remove it from selection instead. if (not m_rangepick && m_addpick) { if (obj->isSelected()) { obj->unselect(); removedObj = obj; break; } } obj->select(); } delete[] pixeldata; // Update everything now. g_win->updateSelection(); // Recompile the objects now to update their color for (LDObject* obj : selection()) compileObject (obj); if (removedObj) compileObject (removedObj); // Restore line thickness glLineWidth (gl_linethickness); setPicking (false); m_rangepick = false; glEnable (GL_DITHER); setBackground(); repaint(); } // ============================================================================= // void GLRenderer::setEditMode (EditMode const& a) { m_editMode = a; switch (a) { case ESelectMode: { unsetCursor(); setContextMenuPolicy (Qt::DefaultContextMenu); } break; case EDrawMode: case ECircleMode: { // Cannot draw into the free camera - use top instead. if (camera() == EFreeCamera) setCamera (ETopCamera); // Disable the context menu - we need the right mouse button // for removing vertices. setContextMenuPolicy (Qt::NoContextMenu); // Use the crosshair cursor when drawing. setCursor (Qt::CrossCursor); // Clear the selection when beginning to draw. LDObjectList priorsel = selection(); getCurrentDocument()->clearSelection(); for (LDObject* obj : priorsel) compileObject (obj); g_win->updateSelection(); m_drawedVerts.clear(); } break; } g_win->updateEditModeActions(); update(); } // ============================================================================= // void GLRenderer::setDocument (LDDocument* const& a) { m_document = a; if (a != null) { initOverlaysFromObjects(); if (not currentDocumentData().init) { resetAllAngles(); currentDocumentData().init = true; } currentDocumentData().needZoomToFit = true; } } // ============================================================================= // Matrix GLRenderer::getCircleDrawMatrix (double scale) { Matrix transform = g_circleDrawMatrixTemplates[camera() % 3]; for (int i = 0; i < 9; ++i) { if (transform[i] == 2) transform[i] = scale; elif (transform[i] == 1 && camera() >= 3) transform[i] = -1; } return transform; } // ============================================================================= // void GLRenderer::endDraw (bool accept) { (void) accept; // Clean the selection and create the object QList<Vertex>& verts = m_drawedVerts; LDObjectList objs; switch (editMode()) { case EDrawMode: { if (m_rectdraw) { LDQuad* quad = new LDQuad; // Copy the vertices from m_rectverts updateRectVerts(); for (int i = 0; i < quad->vertices(); ++i) quad->setVertex (i, m_rectverts[i]); quad->setColor (maincolor); objs << quad; } else { switch (verts.size()) { case 1: { // 1 vertex - add a vertex object LDVertex* obj = new LDVertex; obj->pos = verts[0]; obj->setColor (maincolor); objs << obj; } break; case 2: { // 2 verts - make a line LDLine* obj = new LDLine (verts[0], verts[1]); obj->setColor (edgecolor); objs << obj; } break; case 3: case 4: { LDObject* obj = (verts.size() == 3) ? static_cast<LDObject*> (new LDTriangle) : static_cast<LDObject*> (new LDQuad); obj->setColor (maincolor); for (int i = 0; i < obj->vertices(); ++i) obj->setVertex (i, verts[i]); objs << obj; } break; } } } break; case ECircleMode: { const int segs = g_lores, divs = g_lores; // TODO: make customizable double dist0 = getCircleDrawDist (0), dist1 = getCircleDrawDist (1); LDDocument* refFile = null; Matrix transform; bool circleOrDisc = false; if (dist1 < dist0) std::swap<double> (dist0, dist1); if (dist0 == dist1) { // If the radii are the same, there's no ring space to fill. Use a circle. refFile = ::getDocument ("4-4edge.dat"); transform = getCircleDrawMatrix (dist0); circleOrDisc = true; } elif (dist0 == 0 || dist1 == 0) { // If either radii is 0, use a disc. refFile = ::getDocument ("4-4disc.dat"); transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); circleOrDisc = true; } elif (g_RingFinder.findRings (dist0, dist1)) { // The ring finder found a solution, use that. Add the component rings to the file. for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) { // Get a ref file for this primitive. If we cannot find it in the // LDraw library, generate it. if ((refFile = ::getDocument (radialFileName (::Ring, g_lores, g_lores, cmp.num))) == null) { refFile = generatePrimitive (::Ring, g_lores, g_lores, cmp.num); refFile->setImplicit (false); } LDSubfile* ref = new LDSubfile; ref->setFileInfo (refFile); ref->setTransform (getCircleDrawMatrix (cmp.scale)); ref->setPosition (m_drawedVerts[0]); ref->setColor (maincolor); objs << ref; } } else { // Ring finder failed, last resort: draw the ring with quads QList<QLineF> c0, c1; Axis relX, relY, relZ; getRelativeAxes (relX, relY); relZ = (Axis) (3 - relX - relY); double x0 = m_drawedVerts[0][relX], y0 = m_drawedVerts[0][relY]; Vertex templ; templ.setCoordinate (relX, x0); templ.setCoordinate (relY, y0); templ.setCoordinate (relZ, getDepthValue()); // Calculate circle coords makeCircle (segs, divs, dist0, c0); makeCircle (segs, divs, dist1, c1); for (int i = 0; i < segs; ++i) { Vertex v0, v1, v2, v3; v0 = v1 = v2 = v3 = templ; v0.setCoordinate (relX, v0[relX] + c0[i].x1()); v0.setCoordinate (relY, v0[relY] + c0[i].y1()); v1.setCoordinate (relX, v1[relX] + c0[i].x2()); v1.setCoordinate (relY, v1[relY] + c0[i].y2()); v2.setCoordinate (relX, v2[relX] + c1[i].x2()); v2.setCoordinate (relY, v2[relY] + c1[i].y2()); v3.setCoordinate (relX, v3[relX] + c1[i].x1()); v3.setCoordinate (relY, v3[relY] + c1[i].y1()); LDQuad* q = new LDQuad (v0, v1, v2, v3); q->setColor (maincolor); // Ensure the quads always are BFC-front towards the camera if (camera() % 3 <= 0) q->invert(); objs << q; } } if (circleOrDisc) { LDSubfile* ref = new LDSubfile; ref->setFileInfo (refFile); ref->setTransform (transform); ref->setPosition (m_drawedVerts[0]); ref->setColor (maincolor); objs << ref; } } break; case ESelectMode: { // this shouldn't happen assert (false); return; } break; } if (objs.size() > 0) { for (LDObject* obj : objs) { document()->addObject (obj); compileObject (obj); } g_win->refresh(); g_win->endAction(); } m_drawedVerts.clear(); m_rectdraw = false; } // ============================================================================= // double GLRenderer::getCircleDrawDist (int pos) const { assert (m_drawedVerts.size() >= pos + 1); const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; Axis relX, relY; getRelativeAxes (relX, relY); const double dx = m_drawedVerts[0][relX] - v1[relX]; const double dy = m_drawedVerts[0][relY] - v1[relY]; return sqrt ((dx * dx) + (dy * dy)); } // ============================================================================= // void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const { const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; relX = cam->axisX; relY = cam->axisY; } // ============================================================================= // Axis GLRenderer::getRelativeZ() const { const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; return (Axis) (3 - cam->axisX - cam->axisY); } // ============================================================================= // static QList<Vertex> getVertices (LDObject* obj) { QList<Vertex> verts; if (obj->vertices() >= 2) { for (int i = 0; i < obj->vertices(); ++i) verts << obj->vertex (i); } elif (obj->type() == LDObject::ESubfile) { LDSubfile* ref = static_cast<LDSubfile*> (obj); LDObjectList objs = ref->inlineContents (true, false); for (LDObject* obj : objs) { verts << getVertices (obj); obj->destroy(); } } return verts; } // ============================================================================= // void GLRenderer::compileObject (LDObject* obj) { compiler()->stageForCompilation (obj); } // ============================================================================= // void GLRenderer::forgetObject (LDObject* obj) { compiler()->dropObject (obj); } // ============================================================================= // uchar* GLRenderer::getScreencap (int& w, int& h) { w = m_width; h = m_height; uchar* cap = new uchar[4 * w * h]; m_screencap = true; update(); m_screencap = false; // Capture the pixels glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); return cap; } // ============================================================================= // void GLRenderer::slot_toolTipTimer() { // We come here if the cursor has stayed in one place for longer than a // a second. Check if we're holding it over a camera icon - if so, draw // a tooltip. for (CameraIcon & icon : m_cameraIcons) { if (icon.destRect.contains (m_pos)) { m_toolTipCamera = icon.cam; m_drawToolTip = true; update(); break; } } } // ============================================================================= // Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid) { if (camid == (GL::EFixedCamera) - 1) camid = camera(); const LDFixedCameraInfo* cam = &g_FixedCameras[camid]; return (y) ? cam->axisY : cam->axisX; } // ============================================================================= // bool GLRenderer::setupOverlay (EFixedCamera cam, String file, int x, int y, int w, int h) { QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32)); LDGLOverlay& info = getOverlay (cam); if (img->isNull()) { critical (tr ("Failed to load overlay image!")); currentDocumentData().overlays[cam].invalid = true; delete img; return false; } delete info.img; // delete the old image info.fname = file; info.lw = w; info.lh = h; info.ox = x; info.oy = y; info.img = img; info.invalid = false; if (info.lw == 0) info.lw = (info.lh * img->width()) / img->height(); elif (info.lh == 0) info.lh = (info.lw * img->height()) / img->width(); const Axis x2d = getCameraAxis (false, cam), y2d = getCameraAxis (true, cam); const double negXFac = g_FixedCameras[cam].negX ? -1 : 1, negYFac = g_FixedCameras[cam].negY ? -1 : 1; info.v0 = info.v1 = g_origin; info.v0.setCoordinate (x2d, -(info.ox * info.lw * negXFac) / img->width()); info.v0.setCoordinate (y2d, (info.oy * info.lh * negYFac) / img->height()); info.v1.setCoordinate (x2d, info.v0[x2d] + info.lw); info.v1.setCoordinate (y2d, info.v0[y2d] + info.lh); // Set alpha of all pixels to 0.5 for (long i = 0; i < img->width(); ++i) for (long j = 0; j < img->height(); ++j) { uint32 pixel = img->pixel (i, j); img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF)); } updateOverlayObjects(); return true; } // ============================================================================= // void GLRenderer::clearOverlay() { if (camera() == EFreeCamera) return; LDGLOverlay& info = currentDocumentData().overlays[camera()]; delete info.img; info.img = null; updateOverlayObjects(); } // ============================================================================= // void GLRenderer::setDepthValue (double depth) { assert (camera() < EFreeCamera); currentDocumentData().depthValues[camera()] = depth; } // ============================================================================= // double GLRenderer::getDepthValue() const { assert (camera() < EFreeCamera); return currentDocumentData().depthValues[camera()]; } // ============================================================================= // const char* GLRenderer::getCameraName() const { return g_CameraNames[camera()]; } // ============================================================================= // LDGLOverlay& GLRenderer::getOverlay (int newcam) { return currentDocumentData().overlays[newcam]; } // ============================================================================= // void GLRenderer::zoomNotch (bool inward) { if (zoom() > 15) zoom() *= inward ? 0.833f : 1.2f; else zoom() += inward ? -1.2f : 1.2f; } // ============================================================================= // void GLRenderer::zoomToFit() { zoom() = 30.0f; if (document() == null || m_width == -1 || m_height == -1) return; bool lastfilled = false; bool firstrun = true; const uint32 white = 0xFFFFFFFF; bool inward = true; const int w = m_width, h = m_height; int runaway = 50; glClearColor (1.0, 1.0, 1.0, 1.0); glDisable (GL_DITHER); // 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 || zoom() < 0.0) { // Obviously, there's nothing to draw if we get here. // Default to 30.0f and break out. zoom() = 30.0; break; } zoomNotch (inward); uchar* cap = new uchar[4 * w * h]; drawGLScene(); glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); uint32* imgdata = reinterpret_cast<uint32*> (cap); bool filled = false; // Check the top and bottom rows for (int i = 0; i < w; ++i) { if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) { filled = true; break; } } // Left and right edges if (filled == false) { for (int i = 0; i < h; ++i) { if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) { filled = true; break; } } } delete[] cap; 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 && not lastfilled) { 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 && lastfilled) break; inward = not filled; } lastfilled = filled; } setBackground(); setPicking (false); } // ============================================================================= // void GLRenderer::zoomAllToFit() { EFixedCamera oldcam = camera(); for (int i = 0; i < 7; ++i) { setCamera ((EFixedCamera) i); zoomToFit(); } setCamera (oldcam); } // ============================================================================= // void GLRenderer::updateRectVerts() { if (not m_rectdraw) return; if (m_drawedVerts.isEmpty()) { for (int i = 0; i < 4; ++i) m_rectverts[i] = m_hoverpos; return; } Vertex v0 = m_drawedVerts[0], v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos; const Axis ax = getCameraAxis (false), ay = getCameraAxis (true), az = (Axis) (3 - ax - ay); for (int i = 0; i < 4; ++i) m_rectverts[i].setCoordinate (az, getDepthValue()); m_rectverts[0].setCoordinate (ax, v0[ax]); m_rectverts[0].setCoordinate (ay, v0[ay]); m_rectverts[1].setCoordinate (ax, v1[ax]); m_rectverts[1].setCoordinate (ay, v0[ay]); m_rectverts[2].setCoordinate (ax, v1[ax]); m_rectverts[2].setCoordinate (ay, v1[ay]); m_rectverts[3].setCoordinate (ax, v0[ax]); m_rectverts[3].setCoordinate (ay, v1[ay]); } // ============================================================================= // void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) { if (not (ev->buttons() & Qt::LeftButton) || editMode() != ESelectMode) return; pick (ev->x(), ev->y()); if (selection().isEmpty()) return; LDObject* obj = selection().first(); AddObjectDialog::staticDialog (obj->type(), obj); g_win->endAction(); ev->accept(); } // ============================================================================= // LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) { LDOverlay* ovlobj = null; for (LDObject* obj : document()->objects()) { if (obj->type() == LDObject::EOverlay && static_cast<LDOverlay*> (obj)->camera() == cam) { ovlobj = static_cast<LDOverlay*> (obj); break; } } return ovlobj; } // ============================================================================= // // Read in overlays from the current file and update overlay info accordingly. // void GLRenderer::initOverlaysFromObjects() { for (EFixedCamera cam : g_Cameras) { if (cam == EFreeCamera) continue; LDGLOverlay& meta = currentDocumentData().overlays[cam]; LDOverlay* ovlobj = findOverlayObject (cam); if (not ovlobj && meta.img) { delete meta.img; meta.img = null; } elif (ovlobj && (meta.img == null || meta.fname != ovlobj->fileName()) && meta.invalid == false) setupOverlay (cam, ovlobj->fileName(), ovlobj->x(), ovlobj->y(), ovlobj->width(), ovlobj->height()); } } // ============================================================================= // void GLRenderer::updateOverlayObjects() { for (EFixedCamera cam : g_Cameras) { if (cam == EFreeCamera) continue; LDGLOverlay& meta = currentDocumentData().overlays[cam]; LDOverlay* ovlobj = findOverlayObject (cam); if (meta.img == null && ovlobj != null) { // If this is the last overlay image, we need to remove the empty space after it as well. LDObject* nextobj = ovlobj->next(); if (nextobj && nextobj->type() == LDObject::EEmpty) nextobj->destroy(); // If the overlay object was there and the overlay itself is // not, remove the object. ovlobj->destroy(); } elif (meta.img != null && ovlobj == null) { // Inverse case: image is there but the overlay object is // not, thus create the object. ovlobj = new LDOverlay; // Find a suitable position to place this object. We want to place // this into the header, which is everything up to the first scemantic // object. If we find another overlay object, place this object after // the last one found. Otherwise, place it before the first schemantic // object and put an empty object after it (though don't do this if // there was no schemantic elements at all) int i, lastOverlay = -1; bool found = false; for (i = 0; i < document()->getObjectCount(); ++i) { LDObject* obj = document()->getObject (i); if (obj->isScemantic()) { found = true; break; } if (obj->type() == LDObject::EOverlay) lastOverlay = i; } if (lastOverlay != -1) document()->insertObj (lastOverlay + 1, ovlobj); else { document()->insertObj (i, ovlobj); if (found) document()->insertObj (i + 1, new LDEmpty); } } if (meta.img != null && ovlobj != null) { ovlobj->setCamera (cam); ovlobj->setFileName (meta.fname); ovlobj->setX (meta.ox); ovlobj->setY (meta.oy); ovlobj->setWidth (meta.lw); ovlobj->setHeight (meta.lh); } } if (g_win->R() == this) g_win->refresh(); }