diff -r 09150d027e8c -r d79083b9f74d src/glRenderer.cc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/glRenderer.cc Sat Mar 29 05:38:03 2014 +0200
@@ -0,0 +1,2082 @@
+/*
+ * 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 .
+ */
+
+#define GL_GLEXT_PROTOTYPES
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 },
+ {{ 0, 0, 0 }, X, Y, false, true },
+ {{ 0, 1, 0 }, Z, Y, true, true },
+ {{ -1, 0, 0 }, X, Z, false, true },
+ {{ 0, 0, 0 }, X, Y, true, true },
+ {{ 0, -1, 0 }, Z, Y, false, true },
+};
+
+// 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)
+
+// 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 (255, 0, 0), Vertex (10000, 0, 0) }, // X
+ { QColor (80, 192, 0), Vertex (0, 10000, 0) }, // Y
+ { QColor (0, 160, 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)
+ {
+ QString 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::resetAngles()
+{
+ rot (X) = 30.0f;
+ rot (Y) = 325.f;
+ pan (X) = pan (Y) = rot (Z) = 0.0f;
+ zoomToFit();
+}
+
+// =============================================================================
+//
+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 (int j = 0; j < 3; ++j)
+ {
+ axesdata[(i * 6) + j] = g_GLAxes[i].vert.getCoordinate (j);
+ axesdata[(i * 6) + 3 + j] = -g_GLAxes[i].vert.getCoordinate (j);
+ }
+
+ 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 (!col.isValid())
+ return QColor (0, 0, 0);
+
+ col.setAlpha (gl_maincolor_alpha * 255.f);
+ return col;
+}
+
+// =============================================================================
+//
+void GLRenderer::setBackground()
+{
+ QColor col (gl_bgcolor);
+
+ if (!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 (gl_wireframe && !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
+ {
+ drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES);
+ drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS);
+ }
+
+ drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES);
+ drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES);
+
+ 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->vboCount (surfacevbo);
+
+ 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[axisX] = cx;
+ pos3d[axisY] = cy;
+ pos3d[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[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3];
+ transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7];
+ transformed[Z] = (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 (isPicking() == false)
+ {
+ QString 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.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.
+ QString 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 (!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 QString label = QString::number (poly3d[i].distanceTo (poly3d[j]));
+ 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);
+
+ QString label = QString::number (angle) + QString::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 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 ringpoints, circlepoints, circle2points;
+
+ getRelativeAxes (relX, relY);
+
+ // Calculate the preview positions of vertices
+ for (int i = 0; i < segs; ++i)
+ {
+ Vertex v = g_origin;
+ v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0);
+ v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0);
+ verts << v;
+
+ if (dist1 != -1)
+ {
+ v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1);
+ v[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]);
+ QString label = QString::number (dist0);
+ paint.setPen (textpen);
+ paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label);
+
+ if (m_drawedVerts.size() >= 2)
+ {
+ label = QString::number (dist1);
+ paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label);
+ }
+ }
+ }
+ }
+ }
+
+ // Camera icons
+ if (!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);
+ }
+
+ QString formatstr = tr ("%1 Camera");
+
+ // Draw a label for the current camera in the bottom left corner
+ {
+ const int margin = 4;
+
+ QString 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 (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
+ m_drawToolTip = false;
+ else
+ {
+ QString 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 && !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) && ! (ev->buttons() & Qt::LeftButton),
+ wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton),
+ wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton);
+
+ if (m_panning)
+ m_panning = false;
+
+ if (wasLeft)
+ {
+ // Check if we selected a camera icon
+ if (!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 (!isDrawOnly())
+ {
+ if (m_totalmove < 10)
+ m_rangepick = false;
+
+ if (!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 mindist = 1024.0f;
+ Vertex closest;
+ bool valid = false;
+
+ QPoint curspos = coordconv3_2 (m_hoverpos);
+
+ for (const Vertex& pos3d: m_knownVerts)
+ {
+ QPoint pos2d = coordconv3_2 (pos3d);
+
+ // Measure squared distance
+ const double dx = abs (pos2d.x() - curspos.x()),
+ dy = abs (pos2d.y() - curspos.y()),
+ distsq = (dx * dx) + (dy * dy);
+
+ if (distsq >= 1024.0f) // 32.0f ** 2
+ continue; // too far away
+
+ if (distsq < mindist)
+ {
+ mindist = distsq;
+ closest = pos3d;
+ valid = true;
+
+ // If it's only 4 pixels away, I think we found our vertex now.
+ if (distsq <= 16.0f) // 4.0f ** 2
+ break;
+ }
+ }
+
+ if (valid)
+ addDrawnVertex (closest);
+ }
+
+ if (wasRight && !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 && !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 (!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 (gl_linethickness, 6.5f));
+
+ // Clear the selection if we do not wish to add to it.
+ if (!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)
+ {
+ 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 (!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 (currentDocumentData().init == false)
+ {
+ resetAllAngles();
+ currentDocumentData().init = 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& 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 (new LDTriangle) :
+ static_cast (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 (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 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[relX] = x0;
+ templ[relY] = y0;
+ templ[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[relX] += c0[i].x1();
+ v0[relY] += c0[i].y1();
+ v1[relX] += c0[i].x2();
+ v1[relY] += c0[i].y2();
+ v2[relX] += c1[i].x2();
+ v2[relY] += c1[i].y2();
+ v3[relX] += c1[i].x1();
+ 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;
+}
+
+// =============================================================================
+//
+static QList getVertices (LDObject* obj)
+{
+ QList 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 (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);
+
+ // Mark in known vertices of this object
+ /*
+ QList verts = getVertices (obj);
+ m_knownVerts << verts;
+ removeDuplicates (m_knownVerts);
+ */
+
+ obj->setGLInit (true);
+}
+
+// =============================================================================
+//
+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, QString 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[x2d] = - (info.ox * info.lw * negXFac) / img->width();
+ info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height();
+ info.v1[x2d] = info.v0[x2d] + info.lw;
+ info.v1[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()
+{
+ print ("zooming %1 to fit..\n", camera());
+ zoom() = 30.0f;
+
+ if (document() == null || m_width == -1 || m_height == -1)
+ {
+ print ("document is invalid!\n");
+ 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 (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 = !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 && !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 (!filled && lastfilled)
+ break;
+
+ inward = !filled;
+ }
+
+ lastfilled = filled;
+ }
+
+ setBackground();
+ setPicking (false);
+ print ("zoom to fit done.\n");
+}
+
+// =============================================================================
+//
+void GLRenderer::zoomAllToFit()
+{
+ EFixedCamera oldcam = camera();
+
+ for (int i = 0; i < 7; ++i)
+ {
+ setCamera ((EFixedCamera) i);
+ zoomToFit();
+ }
+
+ setCamera (oldcam);
+}
+
+// =============================================================================
+//
+void GLRenderer::updateRectVerts()
+{
+ if (!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][az] = getDepthValue();
+
+ m_rectverts[0][ax] = v0[ax];
+ m_rectverts[0][ay] = v0[ay];
+ m_rectverts[1][ax] = v1[ax];
+ m_rectverts[1][ay] = v0[ay];
+ m_rectverts[2][ax] = v1[ax];
+ m_rectverts[2][ay] = v1[ay];
+ m_rectverts[3][ax] = v0[ax];
+ m_rectverts[3][ay] = v1[ay];
+}
+
+// =============================================================================
+//
+void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev)
+{
+ if (!(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 (obj)->camera() == cam)
+ {
+ ovlobj = static_cast (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 (!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 && ovlobj)
+ {
+ 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();
+}