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