| 1 /* |
|
| 2 * LDForge: LDraw parts authoring CAD |
|
| 3 * Copyright (C) 2013, 2014 Santeri 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 <QGLWidget> |
|
| 23 #include <QWheelEvent> |
|
| 24 #include <QMouseEvent> |
|
| 25 #include <QContextMenuEvent> |
|
| 26 #include <QInputDialog> |
|
| 27 #include <QToolTip> |
|
| 28 #include <qtextdocument.h> |
|
| 29 #include <QTimer> |
|
| 30 #include "Main.h" |
|
| 31 #include "Configuration.h" |
|
| 32 #include "Document.h" |
|
| 33 #include "GLRenderer.h" |
|
| 34 #include "Colors.h" |
|
| 35 #include "MainWindow.h" |
|
| 36 #include "Misc.h" |
|
| 37 #include "EditHistory.h" |
|
| 38 #include "Dialogs.h" |
|
| 39 #include "AddObjectDialog.h" |
|
| 40 #include "MessageLog.h" |
|
| 41 #include "GLCompiler.h" |
|
| 42 #include "Primitives.h" |
|
| 43 #include "misc/RingFinder.h" |
|
| 44 |
|
| 45 static const LDFixedCameraInfo g_FixedCameras[6] = |
|
| 46 { |
|
| 47 {{ 1, 0, 0 }, X, Z, false, false }, |
|
| 48 {{ 0, 0, 0 }, X, Y, false, true }, |
|
| 49 {{ 0, 1, 0 }, Z, Y, true, true }, |
|
| 50 {{ -1, 0, 0 }, X, Z, false, true }, |
|
| 51 {{ 0, 0, 0 }, X, Y, true, true }, |
|
| 52 {{ 0, -1, 0 }, Z, Y, false, true }, |
|
| 53 }; |
|
| 54 |
|
| 55 // Matrix templates for circle drawing. 2 is substituted with |
|
| 56 // the scale value, 1 is inverted to -1 if needed. |
|
| 57 static const Matrix g_circleDrawMatrixTemplates[3] = |
|
| 58 { |
|
| 59 { 2, 0, 0, 0, 1, 0, 0, 0, 2 }, |
|
| 60 { 2, 0, 0, 0, 0, 2, 0, 1, 0 }, |
|
| 61 { 0, 1, 0, 2, 0, 0, 0, 0, 2 }, |
|
| 62 }; |
|
| 63 |
|
| 64 cfg (String, gl_bgcolor, "#FFFFFF") |
|
| 65 cfg (String, gl_maincolor, "#A0A0A0") |
|
| 66 cfg (Float, gl_maincolor_alpha, 1.0) |
|
| 67 cfg (Int, gl_linethickness, 2) |
|
| 68 cfg (Bool, gl_colorbfc, false) |
|
| 69 cfg (Int, gl_camera, GLRenderer::EFreeCamera) |
|
| 70 cfg (Bool, gl_blackedges, false) |
|
| 71 cfg (Bool, gl_axes, false) |
|
| 72 cfg (Bool, gl_wireframe, false) |
|
| 73 cfg (Bool, gl_logostuds, false) |
|
| 74 cfg (Bool, gl_aa, true) |
|
| 75 cfg (Bool, gl_linelengths, true) |
|
| 76 cfg (Bool, gl_drawangles, false) |
|
| 77 |
|
| 78 // argh |
|
| 79 const char* g_CameraNames[7] = |
|
| 80 { |
|
| 81 QT_TRANSLATE_NOOP ("GLRenderer", "Top"), |
|
| 82 QT_TRANSLATE_NOOP ("GLRenderer", "Front"), |
|
| 83 QT_TRANSLATE_NOOP ("GLRenderer", "Left"), |
|
| 84 QT_TRANSLATE_NOOP ("GLRenderer", "Bottom"), |
|
| 85 QT_TRANSLATE_NOOP ("GLRenderer", "Back"), |
|
| 86 QT_TRANSLATE_NOOP ("GLRenderer", "Right"), |
|
| 87 QT_TRANSLATE_NOOP ("GLRenderer", "Free") |
|
| 88 }; |
|
| 89 |
|
| 90 const GL::EFixedCamera g_Cameras[7] = |
|
| 91 { |
|
| 92 GL::ETopCamera, |
|
| 93 GL::EFrontCamera, |
|
| 94 GL::ELeftCamera, |
|
| 95 GL::EBottomCamera, |
|
| 96 GL::EBackCamera, |
|
| 97 GL::ERightCamera, |
|
| 98 GL::EFreeCamera |
|
| 99 }; |
|
| 100 |
|
| 101 struct LDGLAxis |
|
| 102 { |
|
| 103 const QColor col; |
|
| 104 const Vertex vert; |
|
| 105 }; |
|
| 106 |
|
| 107 // Definitions for visual axes, drawn on the screen |
|
| 108 static const LDGLAxis g_GLAxes[3] = |
|
| 109 { |
|
| 110 { QColor (255, 0, 0), Vertex (10000, 0, 0) }, // X |
|
| 111 { QColor (80, 192, 0), Vertex (0, 10000, 0) }, // Y |
|
| 112 { QColor (0, 160, 192), Vertex (0, 0, 10000) }, // Z |
|
| 113 }; |
|
| 114 |
|
| 115 static GLuint g_GLAxes_VBO; |
|
| 116 static GLuint g_GLAxes_ColorVBO; |
|
| 117 |
|
| 118 // ============================================================================= |
|
| 119 // |
|
| 120 GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) |
|
| 121 { |
|
| 122 m_isPicking = m_rangepick = false; |
|
| 123 m_camera = (EFixedCamera) gl_camera; |
|
| 124 m_drawToolTip = false; |
|
| 125 m_editMode = ESelectMode; |
|
| 126 m_rectdraw = false; |
|
| 127 m_panning = false; |
|
| 128 m_compiler = new GLCompiler; |
|
| 129 setDocument (null); |
|
| 130 setDrawOnly (false); |
|
| 131 setMessageLog (null); |
|
| 132 m_width = m_height = -1; |
|
| 133 m_hoverpos = g_origin; |
|
| 134 m_compiler = new GLCompiler; |
|
| 135 m_toolTipTimer = new QTimer (this); |
|
| 136 m_toolTipTimer->setSingleShot (true); |
|
| 137 connect (m_toolTipTimer, SIGNAL (timeout()), this, SLOT (slot_toolTipTimer())); |
|
| 138 |
|
| 139 m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); |
|
| 140 m_thinBorderPen = m_thickBorderPen; |
|
| 141 m_thinBorderPen.setWidth (1); |
|
| 142 |
|
| 143 // Init camera icons |
|
| 144 for (const GL::EFixedCamera cam : g_Cameras) |
|
| 145 { |
|
| 146 QString iconname = format ("camera-%1", tr (g_CameraNames[cam]).toLower()); |
|
| 147 |
|
| 148 CameraIcon* info = &m_cameraIcons[cam]; |
|
| 149 info->img = new QPixmap (getIcon (iconname)); |
|
| 150 info->cam = cam; |
|
| 151 } |
|
| 152 |
|
| 153 calcCameraIcons(); |
|
| 154 } |
|
| 155 |
|
| 156 // ============================================================================= |
|
| 157 // |
|
| 158 GLRenderer::~GLRenderer() |
|
| 159 { |
|
| 160 for (int i = 0; i < 6; ++i) |
|
| 161 delete currentDocumentData().overlays[i].img; |
|
| 162 |
|
| 163 for (CameraIcon& info : m_cameraIcons) |
|
| 164 delete info.img; |
|
| 165 |
|
| 166 delete m_compiler; |
|
| 167 } |
|
| 168 |
|
| 169 // ============================================================================= |
|
| 170 // Calculates the "hitboxes" of the camera icons so that we can tell when the |
|
| 171 // cursor is pointing at the camera icon. |
|
| 172 // |
|
| 173 void GLRenderer::calcCameraIcons() |
|
| 174 { |
|
| 175 int i = 0; |
|
| 176 |
|
| 177 for (CameraIcon& info : m_cameraIcons) |
|
| 178 { |
|
| 179 // MATH |
|
| 180 const long x1 = (m_width - (info.cam != EFreeCamera ? 48 : 16)) + ((i % 3) * 16) - 1, |
|
| 181 y1 = ((i / 3) * 16) + 1; |
|
| 182 |
|
| 183 info.srcRect = QRect (0, 0, 16, 16); |
|
| 184 info.destRect = QRect (x1, y1, 16, 16); |
|
| 185 info.selRect = QRect ( |
|
| 186 info.destRect.x(), |
|
| 187 info.destRect.y(), |
|
| 188 info.destRect.width() + 1, |
|
| 189 info.destRect.height() + 1 |
|
| 190 ); |
|
| 191 |
|
| 192 ++i; |
|
| 193 } |
|
| 194 } |
|
| 195 |
|
| 196 // ============================================================================= |
|
| 197 // |
|
| 198 void GLRenderer::initGLData() |
|
| 199 { |
|
| 200 glEnable (GL_BLEND); |
|
| 201 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
|
| 202 glEnable (GL_POLYGON_OFFSET_FILL); |
|
| 203 glPolygonOffset (1.0f, 1.0f); |
|
| 204 |
|
| 205 glEnable (GL_DEPTH_TEST); |
|
| 206 glShadeModel (GL_SMOOTH); |
|
| 207 glEnable (GL_MULTISAMPLE); |
|
| 208 |
|
| 209 if (gl_aa) |
|
| 210 { |
|
| 211 glEnable (GL_LINE_SMOOTH); |
|
| 212 glEnable (GL_POLYGON_SMOOTH); |
|
| 213 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); |
|
| 214 glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST); |
|
| 215 } else |
|
| 216 { |
|
| 217 glDisable (GL_LINE_SMOOTH); |
|
| 218 glDisable (GL_POLYGON_SMOOTH); |
|
| 219 } |
|
| 220 } |
|
| 221 |
|
| 222 // ============================================================================= |
|
| 223 // |
|
| 224 void GLRenderer::resetAngles() |
|
| 225 { |
|
| 226 rot (X) = 30.0f; |
|
| 227 rot (Y) = 325.f; |
|
| 228 pan (X) = pan (Y) = rot (Z) = 0.0f; |
|
| 229 zoomToFit(); |
|
| 230 } |
|
| 231 |
|
| 232 // ============================================================================= |
|
| 233 // |
|
| 234 void GLRenderer::resetAllAngles() |
|
| 235 { |
|
| 236 EFixedCamera oldcam = camera(); |
|
| 237 |
|
| 238 for (int i = 0; i < 7; ++i) |
|
| 239 { |
|
| 240 setCamera ((EFixedCamera) i); |
|
| 241 resetAngles(); |
|
| 242 } |
|
| 243 |
|
| 244 setCamera (oldcam); |
|
| 245 } |
|
| 246 |
|
| 247 // ============================================================================= |
|
| 248 // |
|
| 249 void GLRenderer::initializeGL() |
|
| 250 { |
|
| 251 setBackground(); |
|
| 252 glLineWidth (gl_linethickness); |
|
| 253 glLineStipple (1, 0x6666); |
|
| 254 setAutoFillBackground (false); |
|
| 255 setMouseTracking (true); |
|
| 256 setFocusPolicy (Qt::WheelFocus); |
|
| 257 compiler()->initialize(); |
|
| 258 initializeAxes(); |
|
| 259 } |
|
| 260 |
|
| 261 // ============================================================================= |
|
| 262 // |
|
| 263 void GLRenderer::initializeAxes() |
|
| 264 { |
|
| 265 float axesdata[18]; |
|
| 266 float colordata[18]; |
|
| 267 memset (axesdata, 0, sizeof axesdata); |
|
| 268 |
|
| 269 for (int i = 0; i < 3; ++i) |
|
| 270 { |
|
| 271 for (int j = 0; j < 3; ++j) |
|
| 272 { |
|
| 273 axesdata[(i * 6) + j] = g_GLAxes[i].vert.getCoordinate (j); |
|
| 274 axesdata[(i * 6) + 3 + j] = -g_GLAxes[i].vert.getCoordinate (j); |
|
| 275 } |
|
| 276 |
|
| 277 for (int j = 0; j < 2; ++j) |
|
| 278 { |
|
| 279 colordata[(i * 6) + (j * 3) + 0] = g_GLAxes[i].col.red(); |
|
| 280 colordata[(i * 6) + (j * 3) + 1] = g_GLAxes[i].col.green(); |
|
| 281 colordata[(i * 6) + (j * 3) + 2] = g_GLAxes[i].col.blue(); |
|
| 282 } |
|
| 283 } |
|
| 284 |
|
| 285 glGenBuffers (1, &g_GLAxes_VBO); |
|
| 286 glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO); |
|
| 287 glBufferData (GL_ARRAY_BUFFER, sizeof axesdata, axesdata, GL_STATIC_DRAW); |
|
| 288 glGenBuffers (1, &g_GLAxes_ColorVBO); |
|
| 289 glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_ColorVBO); |
|
| 290 glBufferData (GL_ARRAY_BUFFER, sizeof colordata, colordata, GL_STATIC_DRAW); |
|
| 291 glBindBuffer (GL_ARRAY_BUFFER, 0); |
|
| 292 } |
|
| 293 |
|
| 294 // ============================================================================= |
|
| 295 // |
|
| 296 QColor GLRenderer::getMainColor() |
|
| 297 { |
|
| 298 QColor col (gl_maincolor); |
|
| 299 |
|
| 300 if (!col.isValid()) |
|
| 301 return QColor (0, 0, 0); |
|
| 302 |
|
| 303 col.setAlpha (gl_maincolor_alpha * 255.f); |
|
| 304 return col; |
|
| 305 } |
|
| 306 |
|
| 307 // ============================================================================= |
|
| 308 // |
|
| 309 void GLRenderer::setBackground() |
|
| 310 { |
|
| 311 QColor col (gl_bgcolor); |
|
| 312 |
|
| 313 if (!col.isValid()) |
|
| 314 return; |
|
| 315 |
|
| 316 col.setAlpha (255); |
|
| 317 |
|
| 318 m_darkbg = luma (col) < 80; |
|
| 319 m_bgcolor = col; |
|
| 320 qglClearColor (col); |
|
| 321 } |
|
| 322 |
|
| 323 // ============================================================================= |
|
| 324 // |
|
| 325 void GLRenderer::refresh() |
|
| 326 { |
|
| 327 update(); |
|
| 328 swapBuffers(); |
|
| 329 } |
|
| 330 |
|
| 331 // ============================================================================= |
|
| 332 // |
|
| 333 void GLRenderer::hardRefresh() |
|
| 334 { |
|
| 335 compiler()->compileDocument (getCurrentDocument()); |
|
| 336 refresh(); |
|
| 337 glLineWidth (gl_linethickness); // TODO: ...? |
|
| 338 } |
|
| 339 |
|
| 340 // ============================================================================= |
|
| 341 // |
|
| 342 void GLRenderer::resizeGL (int w, int h) |
|
| 343 { |
|
| 344 m_width = w; |
|
| 345 m_height = h; |
|
| 346 |
|
| 347 calcCameraIcons(); |
|
| 348 |
|
| 349 glViewport (0, 0, w, h); |
|
| 350 glMatrixMode (GL_PROJECTION); |
|
| 351 glLoadIdentity(); |
|
| 352 gluPerspective (45.0f, (double) w / (double) h, 1.0f, 10000.0f); |
|
| 353 glMatrixMode (GL_MODELVIEW); |
|
| 354 } |
|
| 355 |
|
| 356 // ============================================================================= |
|
| 357 // |
|
| 358 void GLRenderer::drawGLScene() |
|
| 359 { |
|
| 360 if (document() == null) |
|
| 361 return; |
|
| 362 |
|
| 363 if (gl_wireframe && !isPicking()) |
|
| 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 (camera() != EFreeCamera) |
|
| 370 { |
|
| 371 glMatrixMode (GL_PROJECTION); |
|
| 372 glPushMatrix(); |
|
| 373 |
|
| 374 glLoadIdentity(); |
|
| 375 glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f); |
|
| 376 glTranslatef (pan (X), pan (Y), 0.0f); |
|
| 377 |
|
| 378 if (camera() != EFrontCamera && camera() != EBackCamera) |
|
| 379 { |
|
| 380 glRotatef (90.0f, g_FixedCameras[camera()].glrotate[0], |
|
| 381 g_FixedCameras[camera()].glrotate[1], |
|
| 382 g_FixedCameras[camera()].glrotate[2]); |
|
| 383 } |
|
| 384 |
|
| 385 // Back camera needs to be handled differently |
|
| 386 if (camera() == EBackCamera) |
|
| 387 { |
|
| 388 glRotatef (180.0f, 1.0f, 0.0f, 0.0f); |
|
| 389 glRotatef (180.0f, 0.0f, 0.0f, 1.0f); |
|
| 390 } |
|
| 391 } |
|
| 392 else |
|
| 393 { |
|
| 394 glMatrixMode (GL_MODELVIEW); |
|
| 395 glPushMatrix(); |
|
| 396 glLoadIdentity(); |
|
| 397 |
|
| 398 glTranslatef (0.0f, 0.0f, -2.0f); |
|
| 399 glTranslatef (pan (X), pan (Y), -zoom()); |
|
| 400 glRotatef (rot (X), 1.0f, 0.0f, 0.0f); |
|
| 401 glRotatef (rot (Y), 0.0f, 1.0f, 0.0f); |
|
| 402 glRotatef (rot (Z), 0.0f, 0.0f, 1.0f); |
|
| 403 } |
|
| 404 |
|
| 405 glEnableClientState (GL_VERTEX_ARRAY); |
|
| 406 glEnableClientState (GL_COLOR_ARRAY); |
|
| 407 |
|
| 408 if (isPicking()) |
|
| 409 { |
|
| 410 drawVBOs (VBOSF_Triangles, VBOCM_PickColors, GL_TRIANGLES); |
|
| 411 drawVBOs (VBOSF_Quads, VBOCM_PickColors, GL_QUADS); |
|
| 412 drawVBOs (VBOSF_Lines, VBOCM_PickColors, GL_LINES); |
|
| 413 drawVBOs (VBOSF_CondLines, VBOCM_PickColors, GL_LINES); |
|
| 414 } |
|
| 415 else |
|
| 416 { |
|
| 417 if (gl_colorbfc) |
|
| 418 { |
|
| 419 glEnable (GL_CULL_FACE); |
|
| 420 glCullFace (GL_BACK); |
|
| 421 drawVBOs (VBOSF_Triangles, VBOCM_BFCFrontColors, GL_TRIANGLES); |
|
| 422 drawVBOs (VBOSF_Quads, VBOCM_BFCFrontColors, GL_QUADS); |
|
| 423 glCullFace (GL_FRONT); |
|
| 424 drawVBOs (VBOSF_Triangles, VBOCM_BFCBackColors, GL_TRIANGLES); |
|
| 425 drawVBOs (VBOSF_Quads, VBOCM_BFCBackColors, GL_QUADS); |
|
| 426 glDisable (GL_CULL_FACE); |
|
| 427 } |
|
| 428 else |
|
| 429 { |
|
| 430 drawVBOs (VBOSF_Triangles, VBOCM_NormalColors, GL_TRIANGLES); |
|
| 431 drawVBOs (VBOSF_Quads, VBOCM_NormalColors, GL_QUADS); |
|
| 432 } |
|
| 433 |
|
| 434 drawVBOs (VBOSF_Lines, VBOCM_NormalColors, GL_LINES); |
|
| 435 drawVBOs (VBOSF_CondLines, VBOCM_NormalColors, GL_LINES); |
|
| 436 |
|
| 437 if (gl_axes) |
|
| 438 { |
|
| 439 glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO); |
|
| 440 glVertexPointer (3, GL_FLOAT, 0, NULL); |
|
| 441 glBindBuffer (GL_ARRAY_BUFFER, g_GLAxes_VBO); |
|
| 442 glColorPointer (3, GL_FLOAT, 0, NULL); |
|
| 443 glDrawArrays (GL_LINES, 0, 6); |
|
| 444 checkGLError(); |
|
| 445 } |
|
| 446 } |
|
| 447 |
|
| 448 glPopMatrix(); |
|
| 449 glBindBuffer (GL_ARRAY_BUFFER, 0); |
|
| 450 glDisableClientState (GL_VERTEX_ARRAY); |
|
| 451 glDisableClientState (GL_COLOR_ARRAY); |
|
| 452 checkGLError(); |
|
| 453 glDisable (GL_CULL_FACE); |
|
| 454 glMatrixMode (GL_MODELVIEW); |
|
| 455 glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); |
|
| 456 } |
|
| 457 |
|
| 458 // ============================================================================= |
|
| 459 // |
|
| 460 void GLRenderer::drawVBOs (EVBOSurface surface, EVBOComplement colors, GLenum type) |
|
| 461 { |
|
| 462 int surfacenum = m_compiler->vboNumber (surface, VBOCM_Surfaces); |
|
| 463 int colornum = m_compiler->vboNumber (surface, colors); |
|
| 464 |
|
| 465 m_compiler->prepareVBO (surfacenum); |
|
| 466 m_compiler->prepareVBO (colornum); |
|
| 467 GLuint surfacevbo = m_compiler->vbo (surfacenum); |
|
| 468 GLuint colorvbo = m_compiler->vbo (colornum); |
|
| 469 GLsizei count = m_compiler->vboCount (surfacevbo); |
|
| 470 |
|
| 471 if (count > 0) |
|
| 472 { |
|
| 473 glBindBuffer (GL_ARRAY_BUFFER, surfacevbo); |
|
| 474 glVertexPointer (3, GL_FLOAT, 0, null); |
|
| 475 checkGLError(); |
|
| 476 glBindBuffer (GL_ARRAY_BUFFER, colorvbo); |
|
| 477 glColorPointer (4, GL_FLOAT, 0, null); |
|
| 478 checkGLError(); |
|
| 479 glDrawArrays (type, 0, count); |
|
| 480 checkGLError(); |
|
| 481 } |
|
| 482 } |
|
| 483 |
|
| 484 // ============================================================================= |
|
| 485 // This converts a 2D point on the screen to a 3D point in the model. If 'snap' |
|
| 486 // is true, the 3D point will snap to the current grid. |
|
| 487 // |
|
| 488 Vertex GLRenderer::coordconv2_3 (const QPoint& pos2d, bool snap) const |
|
| 489 { |
|
| 490 assert (camera() != EFreeCamera); |
|
| 491 |
|
| 492 Vertex pos3d; |
|
| 493 const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; |
|
| 494 const Axis axisX = cam->axisX; |
|
| 495 const Axis axisY = cam->axisY; |
|
| 496 const int negXFac = cam->negX ? -1 : 1, |
|
| 497 negYFac = cam->negY ? -1 : 1; |
|
| 498 |
|
| 499 // Calculate cx and cy - these are the LDraw unit coords the cursor is at. |
|
| 500 double cx = (-m_virtWidth + ((2 * pos2d.x() * m_virtWidth) / m_width) - pan (X)); |
|
| 501 double cy = (m_virtHeight - ((2 * pos2d.y() * m_virtHeight) / m_height) - pan (Y)); |
|
| 502 |
|
| 503 if (snap) |
|
| 504 { |
|
| 505 cx = Grid::snap (cx, (Grid::Config) axisX); |
|
| 506 cy = Grid::snap (cy, (Grid::Config) axisY); |
|
| 507 } |
|
| 508 |
|
| 509 cx *= negXFac; |
|
| 510 cy *= negYFac; |
|
| 511 |
|
| 512 roundToDecimals (cx, 4); |
|
| 513 roundToDecimals (cy, 4); |
|
| 514 |
|
| 515 // Create the vertex from the coordinates |
|
| 516 pos3d[axisX] = cx; |
|
| 517 pos3d[axisY] = cy; |
|
| 518 pos3d[3 - axisX - axisY] = getDepthValue(); |
|
| 519 return pos3d; |
|
| 520 } |
|
| 521 |
|
| 522 // ============================================================================= |
|
| 523 // |
|
| 524 // Inverse operation for the above - convert a 3D position to a 2D screen |
|
| 525 // position. Don't ask me how this code manages to work, I don't even know. |
|
| 526 // |
|
| 527 QPoint GLRenderer::coordconv3_2 (const Vertex& pos3d) const |
|
| 528 { |
|
| 529 GLfloat m[16]; |
|
| 530 const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; |
|
| 531 const Axis axisX = cam->axisX; |
|
| 532 const Axis axisY = cam->axisY; |
|
| 533 const int negXFac = cam->negX ? -1 : 1, |
|
| 534 negYFac = cam->negY ? -1 : 1; |
|
| 535 |
|
| 536 glGetFloatv (GL_MODELVIEW_MATRIX, m); |
|
| 537 |
|
| 538 const double x = pos3d.x(); |
|
| 539 const double y = pos3d.y(); |
|
| 540 const double z = pos3d.z(); |
|
| 541 |
|
| 542 Vertex transformed; |
|
| 543 transformed[X] = (m[0] * x) + (m[1] * y) + (m[2] * z) + m[3]; |
|
| 544 transformed[Y] = (m[4] * x) + (m[5] * y) + (m[6] * z) + m[7]; |
|
| 545 transformed[Z] = (m[8] * x) + (m[9] * y) + (m[10] * z) + m[11]; |
|
| 546 |
|
| 547 double rx = (((transformed[axisX] * negXFac) + m_virtWidth + pan (X)) * m_width) / (2 * m_virtWidth); |
|
| 548 double ry = (((transformed[axisY] * negYFac) - m_virtHeight + pan (Y)) * m_height) / (2 * m_virtHeight); |
|
| 549 |
|
| 550 return QPoint (rx, -ry); |
|
| 551 } |
|
| 552 |
|
| 553 // ============================================================================= |
|
| 554 // |
|
| 555 void GLRenderer::paintEvent (QPaintEvent* ev) |
|
| 556 { |
|
| 557 Q_UNUSED (ev) |
|
| 558 |
|
| 559 makeCurrent(); |
|
| 560 m_virtWidth = zoom(); |
|
| 561 m_virtHeight = (m_height * m_virtWidth) / m_width; |
|
| 562 |
|
| 563 initGLData(); |
|
| 564 drawGLScene(); |
|
| 565 |
|
| 566 const QPen textpen (m_darkbg ? Qt::white : Qt::black); |
|
| 567 const QBrush polybrush (QColor (64, 192, 0, 128)); |
|
| 568 QPainter paint (this); |
|
| 569 QFontMetrics metrics = QFontMetrics (QFont()); |
|
| 570 paint.setRenderHint (QPainter::HighQualityAntialiasing); |
|
| 571 |
|
| 572 // If we wish to only draw the brick, stop here |
|
| 573 if (isDrawOnly()) |
|
| 574 return; |
|
| 575 |
|
| 576 #ifndef RELEASE |
|
| 577 if (isPicking() == false) |
|
| 578 { |
|
| 579 QString text = format ("Rotation: (%1, %2, %3)\nPanning: (%4, %5), Zoom: %6", |
|
| 580 rot(X), rot(Y), rot(Z), pan(X), pan(Y), zoom()); |
|
| 581 QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); |
|
| 582 |
|
| 583 paint.drawText ((width() - textSize.width()) / 2, height() - textSize.height(), textSize.width(), |
|
| 584 textSize.height(), Qt::AlignCenter, text); |
|
| 585 } |
|
| 586 #endif |
|
| 587 |
|
| 588 if (camera() != EFreeCamera && !isPicking()) |
|
| 589 { |
|
| 590 // Paint the overlay image if we have one |
|
| 591 const LDGLOverlay& overlay = currentDocumentData().overlays[camera()]; |
|
| 592 |
|
| 593 if (overlay.img != null) |
|
| 594 { |
|
| 595 QPoint v0 = coordconv3_2 (currentDocumentData().overlays[camera()].v0), |
|
| 596 v1 = coordconv3_2 (currentDocumentData().overlays[camera()].v1); |
|
| 597 |
|
| 598 QRect targRect (v0.x(), v0.y(), abs (v1.x() - v0.x()), abs (v1.y() - v0.y())), |
|
| 599 srcRect (0, 0, overlay.img->width(), overlay.img->height()); |
|
| 600 paint.drawImage (targRect, *overlay.img, srcRect); |
|
| 601 } |
|
| 602 |
|
| 603 // Paint the coordinates onto the screen. |
|
| 604 QString text = format (tr ("X: %1, Y: %2, Z: %3"), m_hoverpos[X], m_hoverpos[Y], m_hoverpos[Z]); |
|
| 605 QFontMetrics metrics = QFontMetrics (font()); |
|
| 606 QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text); |
|
| 607 paint.setPen (textpen); |
|
| 608 paint.drawText (m_width - textSize.width(), m_height - 16, textSize.width(), |
|
| 609 textSize.height(), Qt::AlignCenter, text); |
|
| 610 |
|
| 611 QPen linepen = m_thinBorderPen; |
|
| 612 linepen.setWidth (2); |
|
| 613 linepen.setColor (luma (m_bgcolor) < 40 ? Qt::white : Qt::black); |
|
| 614 |
|
| 615 // Mode-specific rendering |
|
| 616 if (editMode() == EDrawMode) |
|
| 617 { |
|
| 618 QPoint poly[4]; |
|
| 619 Vertex poly3d[4]; |
|
| 620 int numverts = 4; |
|
| 621 |
|
| 622 // Calculate polygon data |
|
| 623 if (!m_rectdraw) |
|
| 624 { |
|
| 625 numverts = m_drawedVerts.size() + 1; |
|
| 626 int i = 0; |
|
| 627 |
|
| 628 for (Vertex& vert : m_drawedVerts) |
|
| 629 poly3d[i++] = vert; |
|
| 630 |
|
| 631 // Draw the cursor vertex as the last one in the list. |
|
| 632 if (numverts <= 4) |
|
| 633 poly3d[i] = m_hoverpos; |
|
| 634 else |
|
| 635 numverts = 4; |
|
| 636 } |
|
| 637 else |
|
| 638 { |
|
| 639 // Get vertex information from m_rectverts |
|
| 640 if (m_drawedVerts.size() > 0) |
|
| 641 for (int i = 0; i < numverts; ++i) |
|
| 642 poly3d[i] = m_rectverts[i]; |
|
| 643 else |
|
| 644 poly3d[0] = m_hoverpos; |
|
| 645 } |
|
| 646 |
|
| 647 // Convert to 2D |
|
| 648 for (int i = 0; i < numverts; ++i) |
|
| 649 poly[i] = coordconv3_2 (poly3d[i]); |
|
| 650 |
|
| 651 if (numverts > 0) |
|
| 652 { |
|
| 653 // Draw the polygon-to-be |
|
| 654 paint.setBrush (polybrush); |
|
| 655 paint.drawPolygon (poly, numverts); |
|
| 656 |
|
| 657 // Draw vertex blips |
|
| 658 for (int i = 0; i < numverts; ++i) |
|
| 659 { |
|
| 660 QPoint& blip = poly[i]; |
|
| 661 paint.setPen (linepen); |
|
| 662 drawBlip (paint, blip); |
|
| 663 |
|
| 664 // Draw their coordinates |
|
| 665 paint.setPen (textpen); |
|
| 666 paint.drawText (blip.x(), blip.y() - 8, poly3d[i].toString (true)); |
|
| 667 } |
|
| 668 |
|
| 669 // Draw line lenghts and angle info if appropriate |
|
| 670 if (numverts >= 2) |
|
| 671 { |
|
| 672 int numlines = (m_drawedVerts.size() == 1) ? 1 : m_drawedVerts.size() + 1; |
|
| 673 paint.setPen (textpen); |
|
| 674 |
|
| 675 for (int i = 0; i < numlines; ++i) |
|
| 676 { |
|
| 677 const int j = (i + 1 < numverts) ? i + 1 : 0; |
|
| 678 const int h = (i - 1 >= 0) ? i - 1 : numverts - 1; |
|
| 679 |
|
| 680 if (gl_linelengths) |
|
| 681 { |
|
| 682 const QString label = QString::number (poly3d[i].distanceTo (poly3d[j])); |
|
| 683 QPoint origin = QLineF (poly[i], poly[j]).pointAt (0.5).toPoint(); |
|
| 684 paint.drawText (origin, label); |
|
| 685 } |
|
| 686 |
|
| 687 if (gl_drawangles) |
|
| 688 { |
|
| 689 QLineF l0 (poly[h], poly[i]), |
|
| 690 l1 (poly[i], poly[j]); |
|
| 691 |
|
| 692 double angle = 180 - l0.angleTo (l1); |
|
| 693 |
|
| 694 if (angle < 0) |
|
| 695 angle = 180 - l1.angleTo (l0); |
|
| 696 |
|
| 697 QString label = QString::number (angle) + QString::fromUtf8 (QByteArray ("\302\260")); |
|
| 698 QPoint pos = poly[i]; |
|
| 699 pos.setY (pos.y() + metrics.height()); |
|
| 700 |
|
| 701 paint.drawText (pos, label); |
|
| 702 } |
|
| 703 } |
|
| 704 } |
|
| 705 } |
|
| 706 } |
|
| 707 elif (editMode() == ECircleMode) |
|
| 708 { |
|
| 709 // If we have not specified the center point of the circle yet, preview it on the screen. |
|
| 710 if (m_drawedVerts.isEmpty()) |
|
| 711 drawBlip (paint, coordconv3_2 (m_hoverpos)); |
|
| 712 else |
|
| 713 { |
|
| 714 QVector<Vertex> verts, verts2; |
|
| 715 const double dist0 = getCircleDrawDist (0), |
|
| 716 dist1 = (m_drawedVerts.size() >= 2) ? getCircleDrawDist (1) : -1; |
|
| 717 const int segs = g_lores; |
|
| 718 const double angleUnit = (2 * pi) / segs; |
|
| 719 Axis relX, relY; |
|
| 720 QVector<QPoint> ringpoints, circlepoints, circle2points; |
|
| 721 |
|
| 722 getRelativeAxes (relX, relY); |
|
| 723 |
|
| 724 // Calculate the preview positions of vertices |
|
| 725 for (int i = 0; i < segs; ++i) |
|
| 726 { |
|
| 727 Vertex v = g_origin; |
|
| 728 v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist0); |
|
| 729 v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist0); |
|
| 730 verts << v; |
|
| 731 |
|
| 732 if (dist1 != -1) |
|
| 733 { |
|
| 734 v[relX] = m_drawedVerts[0][relX] + (cos (i * angleUnit) * dist1); |
|
| 735 v[relY] = m_drawedVerts[0][relY] + (sin (i * angleUnit) * dist1); |
|
| 736 verts2 << v; |
|
| 737 } |
|
| 738 } |
|
| 739 |
|
| 740 int i = 0; |
|
| 741 for (const Vertex& v : verts + verts2) |
|
| 742 { |
|
| 743 // Calculate the 2D point of the vertex |
|
| 744 QPoint point = coordconv3_2 (v); |
|
| 745 |
|
| 746 // Draw a green blip at where it is |
|
| 747 drawBlip (paint, point); |
|
| 748 |
|
| 749 // Add it to the list of points for the green ring fill. |
|
| 750 ringpoints << point; |
|
| 751 |
|
| 752 // Also add the circle points to separate lists |
|
| 753 if (i < verts.size()) |
|
| 754 circlepoints << point; |
|
| 755 else |
|
| 756 circle2points << point; |
|
| 757 |
|
| 758 ++i; |
|
| 759 } |
|
| 760 |
|
| 761 // Insert the first point as the seventeenth one so that |
|
| 762 // the ring polygon is closed properly. |
|
| 763 if (ringpoints.size() >= 16) |
|
| 764 ringpoints.insert (16, ringpoints[0]); |
|
| 765 |
|
| 766 // Same for the outer ring. Note that the indices are offset by 1 |
|
| 767 // because of the insertion done above bumps the values. |
|
| 768 if (ringpoints.size() >= 33) |
|
| 769 ringpoints.insert (33, ringpoints[17]); |
|
| 770 |
|
| 771 // Draw the ring |
|
| 772 paint.setBrush ((m_drawedVerts.size() >= 2) ? polybrush : Qt::NoBrush); |
|
| 773 paint.setPen (Qt::NoPen); |
|
| 774 paint.drawPolygon (QPolygon (ringpoints)); |
|
| 775 |
|
| 776 // Draw the circles |
|
| 777 paint.setBrush (Qt::NoBrush); |
|
| 778 paint.setPen (linepen); |
|
| 779 paint.drawPolygon (QPolygon (circlepoints)); |
|
| 780 paint.drawPolygon (QPolygon (circle2points)); |
|
| 781 |
|
| 782 { // Draw the current radius in the middle of the circle. |
|
| 783 QPoint origin = coordconv3_2 (m_drawedVerts[0]); |
|
| 784 QString label = QString::number (dist0); |
|
| 785 paint.setPen (textpen); |
|
| 786 paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y(), label); |
|
| 787 |
|
| 788 if (m_drawedVerts.size() >= 2) |
|
| 789 { |
|
| 790 label = QString::number (dist1); |
|
| 791 paint.drawText (origin.x() - (metrics.width (label) / 2), origin.y() + metrics.height(), label); |
|
| 792 } |
|
| 793 } |
|
| 794 } |
|
| 795 } |
|
| 796 } |
|
| 797 |
|
| 798 // Camera icons |
|
| 799 if (!isPicking()) |
|
| 800 { |
|
| 801 // Draw a background for the selected camera |
|
| 802 paint.setPen (m_thinBorderPen); |
|
| 803 paint.setBrush (QBrush (QColor (0, 128, 160, 128))); |
|
| 804 paint.drawRect (m_cameraIcons[camera()].selRect); |
|
| 805 |
|
| 806 // Draw the actual icons |
|
| 807 for (CameraIcon& info : m_cameraIcons) |
|
| 808 { |
|
| 809 // Don't draw the free camera icon when in draw mode |
|
| 810 if (&info == &m_cameraIcons[GL::EFreeCamera] && editMode() != ESelectMode) |
|
| 811 continue; |
|
| 812 |
|
| 813 paint.drawPixmap (info.destRect, *info.img, info.srcRect); |
|
| 814 } |
|
| 815 |
|
| 816 QString formatstr = tr ("%1 Camera"); |
|
| 817 |
|
| 818 // Draw a label for the current camera in the bottom left corner |
|
| 819 { |
|
| 820 const int margin = 4; |
|
| 821 |
|
| 822 QString label; |
|
| 823 label = format (formatstr, tr (g_CameraNames[camera()])); |
|
| 824 paint.setPen (textpen); |
|
| 825 paint.drawText (QPoint (margin, height() - (margin + metrics.descent())), label); |
|
| 826 } |
|
| 827 |
|
| 828 // Tool tips |
|
| 829 if (m_drawToolTip) |
|
| 830 { |
|
| 831 if (m_cameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false) |
|
| 832 m_drawToolTip = false; |
|
| 833 else |
|
| 834 { |
|
| 835 QString label = format (formatstr, tr (g_CameraNames[m_toolTipCamera])); |
|
| 836 QToolTip::showText (m_globalpos, label); |
|
| 837 } |
|
| 838 } |
|
| 839 } |
|
| 840 |
|
| 841 // Message log |
|
| 842 if (messageLog()) |
|
| 843 { |
|
| 844 int y = 0; |
|
| 845 const int margin = 2; |
|
| 846 QColor penColor = textpen.color(); |
|
| 847 |
|
| 848 for (const MessageManager::Line& line : messageLog()->getLines()) |
|
| 849 { |
|
| 850 penColor.setAlphaF (line.alpha); |
|
| 851 paint.setPen (penColor); |
|
| 852 paint.drawText (QPoint (margin, y + margin + metrics.ascent()), line.text); |
|
| 853 y += metrics.height(); |
|
| 854 } |
|
| 855 } |
|
| 856 |
|
| 857 // If we're range-picking, draw a rectangle encompassing the selection area. |
|
| 858 if (m_rangepick && !isPicking() && m_totalmove >= 10) |
|
| 859 { |
|
| 860 int x0 = m_rangeStart.x(), |
|
| 861 y0 = m_rangeStart.y(), |
|
| 862 x1 = m_pos.x(), |
|
| 863 y1 = m_pos.y(); |
|
| 864 |
|
| 865 QRect rect (x0, y0, x1 - x0, y1 - y0); |
|
| 866 QColor fillColor = (m_addpick ? "#40FF00" : "#00CCFF"); |
|
| 867 fillColor.setAlphaF (0.2f); |
|
| 868 |
|
| 869 paint.setPen (m_thickBorderPen); |
|
| 870 paint.setBrush (QBrush (fillColor)); |
|
| 871 paint.drawRect (rect); |
|
| 872 } |
|
| 873 } |
|
| 874 |
|
| 875 // ============================================================================= |
|
| 876 // |
|
| 877 void GLRenderer::drawBlip (QPainter& paint, QPoint pos) const |
|
| 878 { |
|
| 879 QPen pen = m_thinBorderPen; |
|
| 880 const int blipsize = 8; |
|
| 881 pen.setWidth (1); |
|
| 882 paint.setPen (pen); |
|
| 883 paint.setBrush (QColor (64, 192, 0)); |
|
| 884 paint.drawEllipse (pos.x() - blipsize / 2, pos.y() - blipsize / 2, blipsize, blipsize); |
|
| 885 } |
|
| 886 |
|
| 887 // ============================================================================= |
|
| 888 // |
|
| 889 void GLRenderer::clampAngle (double& angle) const |
|
| 890 { |
|
| 891 while (angle < 0) |
|
| 892 angle += 360.0; |
|
| 893 |
|
| 894 while (angle > 360.0) |
|
| 895 angle -= 360.0; |
|
| 896 } |
|
| 897 |
|
| 898 // ============================================================================= |
|
| 899 // |
|
| 900 void GLRenderer::addDrawnVertex (Vertex pos) |
|
| 901 { |
|
| 902 // If we picked an already-existing vertex, stop drawing |
|
| 903 if (editMode() == EDrawMode) |
|
| 904 { |
|
| 905 for (Vertex& vert : m_drawedVerts) |
|
| 906 { |
|
| 907 if (vert == pos) |
|
| 908 { |
|
| 909 endDraw (true); |
|
| 910 return; |
|
| 911 } |
|
| 912 } |
|
| 913 } |
|
| 914 |
|
| 915 m_drawedVerts << pos; |
|
| 916 } |
|
| 917 |
|
| 918 // ============================================================================= |
|
| 919 // |
|
| 920 void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) |
|
| 921 { |
|
| 922 const bool wasLeft = (m_lastButtons & Qt::LeftButton) && ! (ev->buttons() & Qt::LeftButton), |
|
| 923 wasRight = (m_lastButtons & Qt::RightButton) && ! (ev->buttons() & Qt::RightButton), |
|
| 924 wasMid = (m_lastButtons & Qt::MidButton) && ! (ev->buttons() & Qt::MidButton); |
|
| 925 |
|
| 926 if (m_panning) |
|
| 927 m_panning = false; |
|
| 928 |
|
| 929 if (wasLeft) |
|
| 930 { |
|
| 931 // Check if we selected a camera icon |
|
| 932 if (!m_rangepick) |
|
| 933 { |
|
| 934 for (CameraIcon & info : m_cameraIcons) |
|
| 935 { |
|
| 936 if (info.destRect.contains (ev->pos())) |
|
| 937 { |
|
| 938 setCamera (info.cam); |
|
| 939 goto end; |
|
| 940 } |
|
| 941 } |
|
| 942 } |
|
| 943 |
|
| 944 switch (editMode()) |
|
| 945 { |
|
| 946 case EDrawMode: |
|
| 947 { |
|
| 948 if (m_rectdraw) |
|
| 949 { |
|
| 950 if (m_drawedVerts.size() == 2) |
|
| 951 { |
|
| 952 endDraw (true); |
|
| 953 return; |
|
| 954 } |
|
| 955 } else |
|
| 956 { |
|
| 957 // If we have 4 verts, stop drawing. |
|
| 958 if (m_drawedVerts.size() >= 4) |
|
| 959 { |
|
| 960 endDraw (true); |
|
| 961 return; |
|
| 962 } |
|
| 963 |
|
| 964 if (m_drawedVerts.isEmpty() && ev->modifiers() & Qt::ShiftModifier) |
|
| 965 { |
|
| 966 m_rectdraw = true; |
|
| 967 updateRectVerts(); |
|
| 968 } |
|
| 969 } |
|
| 970 |
|
| 971 addDrawnVertex (m_hoverpos); |
|
| 972 } break; |
|
| 973 |
|
| 974 case ECircleMode: |
|
| 975 { |
|
| 976 if (m_drawedVerts.size() == 3) |
|
| 977 { |
|
| 978 endDraw (true); |
|
| 979 return; |
|
| 980 } |
|
| 981 |
|
| 982 addDrawnVertex (m_hoverpos); |
|
| 983 } break; |
|
| 984 |
|
| 985 case ESelectMode: |
|
| 986 { |
|
| 987 if (!isDrawOnly()) |
|
| 988 { |
|
| 989 if (m_totalmove < 10) |
|
| 990 m_rangepick = false; |
|
| 991 |
|
| 992 if (!m_rangepick) |
|
| 993 m_addpick = (m_keymods & Qt::ControlModifier); |
|
| 994 |
|
| 995 if (m_totalmove < 10 || m_rangepick) |
|
| 996 pick (ev->x(), ev->y()); |
|
| 997 } |
|
| 998 } break; |
|
| 999 } |
|
| 1000 |
|
| 1001 m_rangepick = false; |
|
| 1002 } |
|
| 1003 |
|
| 1004 if (wasMid && editMode() != ESelectMode && m_drawedVerts.size() < 4 && m_totalmove < 10) |
|
| 1005 { |
|
| 1006 // Find the closest vertex to our cursor |
|
| 1007 double mindist = 1024.0f; |
|
| 1008 Vertex closest; |
|
| 1009 bool valid = false; |
|
| 1010 |
|
| 1011 QPoint curspos = coordconv3_2 (m_hoverpos); |
|
| 1012 |
|
| 1013 for (const Vertex& pos3d: m_knownVerts) |
|
| 1014 { |
|
| 1015 QPoint pos2d = coordconv3_2 (pos3d); |
|
| 1016 |
|
| 1017 // Measure squared distance |
|
| 1018 const double dx = abs (pos2d.x() - curspos.x()), |
|
| 1019 dy = abs (pos2d.y() - curspos.y()), |
|
| 1020 distsq = (dx * dx) + (dy * dy); |
|
| 1021 |
|
| 1022 if (distsq >= 1024.0f) // 32.0f ** 2 |
|
| 1023 continue; // too far away |
|
| 1024 |
|
| 1025 if (distsq < mindist) |
|
| 1026 { |
|
| 1027 mindist = distsq; |
|
| 1028 closest = pos3d; |
|
| 1029 valid = true; |
|
| 1030 |
|
| 1031 // If it's only 4 pixels away, I think we found our vertex now. |
|
| 1032 if (distsq <= 16.0f) // 4.0f ** 2 |
|
| 1033 break; |
|
| 1034 } |
|
| 1035 } |
|
| 1036 |
|
| 1037 if (valid) |
|
| 1038 addDrawnVertex (closest); |
|
| 1039 } |
|
| 1040 |
|
| 1041 if (wasRight && !m_drawedVerts.isEmpty()) |
|
| 1042 { |
|
| 1043 // Remove the last vertex |
|
| 1044 m_drawedVerts.removeLast(); |
|
| 1045 |
|
| 1046 if (m_drawedVerts.isEmpty()) |
|
| 1047 m_rectdraw = false; |
|
| 1048 } |
|
| 1049 |
|
| 1050 end: |
|
| 1051 update(); |
|
| 1052 m_totalmove = 0; |
|
| 1053 } |
|
| 1054 |
|
| 1055 // ============================================================================= |
|
| 1056 // |
|
| 1057 void GLRenderer::mousePressEvent (QMouseEvent* ev) |
|
| 1058 { |
|
| 1059 m_totalmove = 0; |
|
| 1060 |
|
| 1061 if (ev->modifiers() & Qt::ControlModifier) |
|
| 1062 { |
|
| 1063 m_rangepick = true; |
|
| 1064 m_rangeStart.setX (ev->x()); |
|
| 1065 m_rangeStart.setY (ev->y()); |
|
| 1066 m_addpick = (m_keymods & Qt::AltModifier); |
|
| 1067 ev->accept(); |
|
| 1068 } |
|
| 1069 |
|
| 1070 m_lastButtons = ev->buttons(); |
|
| 1071 } |
|
| 1072 |
|
| 1073 // ============================================================================= |
|
| 1074 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
|
| 1075 // ============================================================================= |
|
| 1076 void GLRenderer::mouseMoveEvent (QMouseEvent* ev) |
|
| 1077 { |
|
| 1078 int dx = ev->x() - m_pos.x(); |
|
| 1079 int dy = ev->y() - m_pos.y(); |
|
| 1080 m_totalmove += abs (dx) + abs (dy); |
|
| 1081 |
|
| 1082 const bool left = ev->buttons() & Qt::LeftButton, |
|
| 1083 mid = ev->buttons() & Qt::MidButton, |
|
| 1084 shift = ev->modifiers() & Qt::ShiftModifier; |
|
| 1085 |
|
| 1086 if (mid || (left && shift)) |
|
| 1087 { |
|
| 1088 pan (X) += 0.03f * dx * (zoom() / 7.5f); |
|
| 1089 pan (Y) -= 0.03f * dy * (zoom() / 7.5f); |
|
| 1090 m_panning = true; |
|
| 1091 } elif (left && !m_rangepick && camera() == EFreeCamera) |
|
| 1092 { |
|
| 1093 rot (X) = rot (X) + dy; |
|
| 1094 rot (Y) = rot (Y) + dx; |
|
| 1095 |
|
| 1096 clampAngle (rot (X)); |
|
| 1097 clampAngle (rot (Y)); |
|
| 1098 } |
|
| 1099 |
|
| 1100 // Start the tool tip timer |
|
| 1101 if (!m_drawToolTip) |
|
| 1102 m_toolTipTimer->start (500); |
|
| 1103 |
|
| 1104 // Update 2d position |
|
| 1105 m_pos = ev->pos(); |
|
| 1106 m_globalpos = ev->globalPos(); |
|
| 1107 |
|
| 1108 // Calculate 3d position of the cursor |
|
| 1109 m_hoverpos = (camera() != EFreeCamera) ? coordconv2_3 (m_pos, true) : g_origin; |
|
| 1110 |
|
| 1111 // Update rect vertices since m_hoverpos may have changed |
|
| 1112 updateRectVerts(); |
|
| 1113 |
|
| 1114 update(); |
|
| 1115 } |
|
| 1116 |
|
| 1117 // ============================================================================= |
|
| 1118 // |
|
| 1119 void GLRenderer::keyPressEvent (QKeyEvent* ev) |
|
| 1120 { |
|
| 1121 m_keymods = ev->modifiers(); |
|
| 1122 } |
|
| 1123 |
|
| 1124 // ============================================================================= |
|
| 1125 // |
|
| 1126 void GLRenderer::keyReleaseEvent (QKeyEvent* ev) |
|
| 1127 { |
|
| 1128 m_keymods = ev->modifiers(); |
|
| 1129 } |
|
| 1130 |
|
| 1131 // ============================================================================= |
|
| 1132 // |
|
| 1133 void GLRenderer::wheelEvent (QWheelEvent* ev) |
|
| 1134 { |
|
| 1135 makeCurrent(); |
|
| 1136 |
|
| 1137 zoomNotch (ev->delta() > 0); |
|
| 1138 zoom() = clamp (zoom(), 0.01, 10000.0); |
|
| 1139 |
|
| 1140 update(); |
|
| 1141 ev->accept(); |
|
| 1142 } |
|
| 1143 |
|
| 1144 // ============================================================================= |
|
| 1145 // |
|
| 1146 void GLRenderer::leaveEvent (QEvent* ev) |
|
| 1147 { |
|
| 1148 (void) ev; |
|
| 1149 m_drawToolTip = false; |
|
| 1150 m_toolTipTimer->stop(); |
|
| 1151 update(); |
|
| 1152 } |
|
| 1153 |
|
| 1154 // ============================================================================= |
|
| 1155 // |
|
| 1156 void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) |
|
| 1157 { |
|
| 1158 g_win->spawnContextMenu (ev->globalPos()); |
|
| 1159 } |
|
| 1160 |
|
| 1161 // ============================================================================= |
|
| 1162 // |
|
| 1163 void GLRenderer::setCamera (const GLRenderer::EFixedCamera cam) |
|
| 1164 { |
|
| 1165 m_camera = cam; |
|
| 1166 gl_camera = (int) cam; |
|
| 1167 g_win->updateEditModeActions(); |
|
| 1168 } |
|
| 1169 |
|
| 1170 // ============================================================================= |
|
| 1171 // |
|
| 1172 void GLRenderer::pick (int mouseX, int mouseY) |
|
| 1173 { |
|
| 1174 makeCurrent(); |
|
| 1175 |
|
| 1176 // Use particularly thick lines while picking ease up selecting lines. |
|
| 1177 glLineWidth (max<double> (gl_linethickness, 6.5f)); |
|
| 1178 |
|
| 1179 // Clear the selection if we do not wish to add to it. |
|
| 1180 if (!m_addpick) |
|
| 1181 { |
|
| 1182 LDObjectList oldsel = selection(); |
|
| 1183 getCurrentDocument()->clearSelection(); |
|
| 1184 |
|
| 1185 for (LDObject* obj : oldsel) |
|
| 1186 compileObject (obj); |
|
| 1187 } |
|
| 1188 |
|
| 1189 setPicking (true); |
|
| 1190 |
|
| 1191 // Paint the picking scene |
|
| 1192 glDisable (GL_DITHER); |
|
| 1193 glClearColor (1.0f, 1.0f, 1.0f, 1.0f); |
|
| 1194 drawGLScene(); |
|
| 1195 |
|
| 1196 int x0 = mouseX, |
|
| 1197 y0 = mouseY; |
|
| 1198 int x1, y1; |
|
| 1199 |
|
| 1200 // Determine how big an area to read - with range picking, we pick by |
|
| 1201 // the area given, with single pixel picking, we use an 1 x 1 area. |
|
| 1202 if (m_rangepick) |
|
| 1203 { |
|
| 1204 x1 = m_rangeStart.x(); |
|
| 1205 y1 = m_rangeStart.y(); |
|
| 1206 } |
|
| 1207 else |
|
| 1208 { |
|
| 1209 x1 = x0 + 1; |
|
| 1210 y1 = y0 + 1; |
|
| 1211 } |
|
| 1212 |
|
| 1213 // x0 and y0 must be less than x1 and y1, respectively. |
|
| 1214 if (x0 > x1) |
|
| 1215 qSwap (x0, x1); |
|
| 1216 |
|
| 1217 if (y0 > y1) |
|
| 1218 qSwap (y0, y1); |
|
| 1219 |
|
| 1220 // Clamp the values to ensure they're within bounds |
|
| 1221 x0 = max (0, x0); |
|
| 1222 y0 = max (0, y0); |
|
| 1223 x1 = min (x1, m_width); |
|
| 1224 y1 = min (y1, m_height); |
|
| 1225 const int areawidth = (x1 - x0); |
|
| 1226 const int areaheight = (y1 - y0); |
|
| 1227 const qint32 numpixels = areawidth * areaheight; |
|
| 1228 |
|
| 1229 // Allocate space for the pixel data. |
|
| 1230 uchar* const pixeldata = new uchar[4 * numpixels]; |
|
| 1231 uchar* pixelptr = &pixeldata[0]; |
|
| 1232 |
|
| 1233 // Read pixels from the color buffer. |
|
| 1234 glReadPixels (x0, m_height - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata); |
|
| 1235 |
|
| 1236 LDObject* removedObj = null; |
|
| 1237 |
|
| 1238 // Go through each pixel read and add them to the selection. |
|
| 1239 for (qint32 i = 0; i < numpixels; ++i) |
|
| 1240 { |
|
| 1241 qint32 idx = |
|
| 1242 (*(pixelptr + 0) * 0x10000) + |
|
| 1243 (*(pixelptr + 1) * 0x100) + |
|
| 1244 *(pixelptr + 2); |
|
| 1245 pixelptr += 4; |
|
| 1246 |
|
| 1247 if (idx == 0xFFFFFF) |
|
| 1248 continue; // White is background; skip |
|
| 1249 |
|
| 1250 LDObject* obj = LDObject::fromID (idx); |
|
| 1251 assert (obj != null); |
|
| 1252 |
|
| 1253 // If this is an additive single pick and the object is currently selected, |
|
| 1254 // we remove it from selection instead. |
|
| 1255 if (!m_rangepick && m_addpick) |
|
| 1256 { |
|
| 1257 if (obj->isSelected()) |
|
| 1258 { |
|
| 1259 obj->unselect(); |
|
| 1260 removedObj = obj; |
|
| 1261 break; |
|
| 1262 } |
|
| 1263 } |
|
| 1264 |
|
| 1265 obj->select(); |
|
| 1266 } |
|
| 1267 |
|
| 1268 delete[] pixeldata; |
|
| 1269 |
|
| 1270 // Update everything now. |
|
| 1271 g_win->updateSelection(); |
|
| 1272 |
|
| 1273 // Recompile the objects now to update their color |
|
| 1274 for (LDObject* obj : selection()) |
|
| 1275 compileObject (obj); |
|
| 1276 |
|
| 1277 if (removedObj) |
|
| 1278 compileObject (removedObj); |
|
| 1279 |
|
| 1280 // Restore line thickness |
|
| 1281 glLineWidth (gl_linethickness); |
|
| 1282 |
|
| 1283 setPicking (false); |
|
| 1284 m_rangepick = false; |
|
| 1285 glEnable (GL_DITHER); |
|
| 1286 |
|
| 1287 setBackground(); |
|
| 1288 repaint(); |
|
| 1289 } |
|
| 1290 |
|
| 1291 // ============================================================================= |
|
| 1292 // |
|
| 1293 void GLRenderer::setEditMode (EditMode const& a) |
|
| 1294 { |
|
| 1295 m_editMode = a; |
|
| 1296 |
|
| 1297 switch (a) |
|
| 1298 { |
|
| 1299 case ESelectMode: |
|
| 1300 { |
|
| 1301 unsetCursor(); |
|
| 1302 setContextMenuPolicy (Qt::DefaultContextMenu); |
|
| 1303 } break; |
|
| 1304 |
|
| 1305 case EDrawMode: |
|
| 1306 case ECircleMode: |
|
| 1307 { |
|
| 1308 // Cannot draw into the free camera - use top instead. |
|
| 1309 if (camera() == EFreeCamera) |
|
| 1310 setCamera (ETopCamera); |
|
| 1311 |
|
| 1312 // Disable the context menu - we need the right mouse button |
|
| 1313 // for removing vertices. |
|
| 1314 setContextMenuPolicy (Qt::NoContextMenu); |
|
| 1315 |
|
| 1316 // Use the crosshair cursor when drawing. |
|
| 1317 setCursor (Qt::CrossCursor); |
|
| 1318 |
|
| 1319 // Clear the selection when beginning to draw. |
|
| 1320 LDObjectList priorsel = selection(); |
|
| 1321 getCurrentDocument()->clearSelection(); |
|
| 1322 |
|
| 1323 for (LDObject* obj : priorsel) |
|
| 1324 compileObject (obj); |
|
| 1325 |
|
| 1326 g_win->updateSelection(); |
|
| 1327 m_drawedVerts.clear(); |
|
| 1328 } break; |
|
| 1329 } |
|
| 1330 |
|
| 1331 g_win->updateEditModeActions(); |
|
| 1332 update(); |
|
| 1333 } |
|
| 1334 |
|
| 1335 // ============================================================================= |
|
| 1336 // |
|
| 1337 void GLRenderer::setDocument (LDDocument* const& a) |
|
| 1338 { |
|
| 1339 m_document = a; |
|
| 1340 |
|
| 1341 if (a != null) |
|
| 1342 { |
|
| 1343 initOverlaysFromObjects(); |
|
| 1344 |
|
| 1345 if (currentDocumentData().init == false) |
|
| 1346 { |
|
| 1347 resetAllAngles(); |
|
| 1348 currentDocumentData().init = true; |
|
| 1349 } |
|
| 1350 } |
|
| 1351 } |
|
| 1352 |
|
| 1353 // ============================================================================= |
|
| 1354 // |
|
| 1355 Matrix GLRenderer::getCircleDrawMatrix (double scale) |
|
| 1356 { |
|
| 1357 Matrix transform = g_circleDrawMatrixTemplates[camera() % 3]; |
|
| 1358 |
|
| 1359 for (int i = 0; i < 9; ++i) |
|
| 1360 { |
|
| 1361 if (transform[i] == 2) |
|
| 1362 transform[i] = scale; |
|
| 1363 elif (transform[i] == 1 && camera() >= 3) |
|
| 1364 transform[i] = -1; |
|
| 1365 } |
|
| 1366 |
|
| 1367 return transform; |
|
| 1368 } |
|
| 1369 |
|
| 1370 // ============================================================================= |
|
| 1371 // |
|
| 1372 void GLRenderer::endDraw (bool accept) |
|
| 1373 { |
|
| 1374 (void) accept; |
|
| 1375 |
|
| 1376 // Clean the selection and create the object |
|
| 1377 QList<Vertex>& verts = m_drawedVerts; |
|
| 1378 LDObjectList objs; |
|
| 1379 |
|
| 1380 switch (editMode()) |
|
| 1381 { |
|
| 1382 case EDrawMode: |
|
| 1383 { |
|
| 1384 if (m_rectdraw) |
|
| 1385 { |
|
| 1386 LDQuad* quad = new LDQuad; |
|
| 1387 |
|
| 1388 // Copy the vertices from m_rectverts |
|
| 1389 updateRectVerts(); |
|
| 1390 |
|
| 1391 for (int i = 0; i < quad->vertices(); ++i) |
|
| 1392 quad->setVertex (i, m_rectverts[i]); |
|
| 1393 |
|
| 1394 quad->setColor (maincolor); |
|
| 1395 objs << quad; |
|
| 1396 } |
|
| 1397 else |
|
| 1398 { |
|
| 1399 switch (verts.size()) |
|
| 1400 { |
|
| 1401 case 1: |
|
| 1402 { |
|
| 1403 // 1 vertex - add a vertex object |
|
| 1404 LDVertex* obj = new LDVertex; |
|
| 1405 obj->pos = verts[0]; |
|
| 1406 obj->setColor (maincolor); |
|
| 1407 objs << obj; |
|
| 1408 } break; |
|
| 1409 |
|
| 1410 case 2: |
|
| 1411 { |
|
| 1412 // 2 verts - make a line |
|
| 1413 LDLine* obj = new LDLine (verts[0], verts[1]); |
|
| 1414 obj->setColor (edgecolor); |
|
| 1415 objs << obj; |
|
| 1416 } break; |
|
| 1417 |
|
| 1418 case 3: |
|
| 1419 case 4: |
|
| 1420 { |
|
| 1421 LDObject* obj = (verts.size() == 3) ? |
|
| 1422 static_cast<LDObject*> (new LDTriangle) : |
|
| 1423 static_cast<LDObject*> (new LDQuad); |
|
| 1424 |
|
| 1425 obj->setColor (maincolor); |
|
| 1426 |
|
| 1427 for (int i = 0; i < obj->vertices(); ++i) |
|
| 1428 obj->setVertex (i, verts[i]); |
|
| 1429 |
|
| 1430 objs << obj; |
|
| 1431 } break; |
|
| 1432 } |
|
| 1433 } |
|
| 1434 } break; |
|
| 1435 |
|
| 1436 case ECircleMode: |
|
| 1437 { |
|
| 1438 const int segs = g_lores, divs = g_lores; // TODO: make customizable |
|
| 1439 double dist0 = getCircleDrawDist (0), |
|
| 1440 dist1 = getCircleDrawDist (1); |
|
| 1441 LDDocument* refFile = null; |
|
| 1442 Matrix transform; |
|
| 1443 bool circleOrDisc = false; |
|
| 1444 |
|
| 1445 if (dist1 < dist0) |
|
| 1446 std::swap<double> (dist0, dist1); |
|
| 1447 |
|
| 1448 if (dist0 == dist1) |
|
| 1449 { |
|
| 1450 // If the radii are the same, there's no ring space to fill. Use a circle. |
|
| 1451 refFile = ::getDocument ("4-4edge.dat"); |
|
| 1452 transform = getCircleDrawMatrix (dist0); |
|
| 1453 circleOrDisc = true; |
|
| 1454 } |
|
| 1455 elif (dist0 == 0 || dist1 == 0) |
|
| 1456 { |
|
| 1457 // If either radii is 0, use a disc. |
|
| 1458 refFile = ::getDocument ("4-4disc.dat"); |
|
| 1459 transform = getCircleDrawMatrix ((dist0 != 0) ? dist0 : dist1); |
|
| 1460 circleOrDisc = true; |
|
| 1461 } |
|
| 1462 elif (g_RingFinder.findRings (dist0, dist1)) |
|
| 1463 { |
|
| 1464 // The ring finder found a solution, use that. Add the component rings to the file. |
|
| 1465 for (const RingFinder::Component& cmp : g_RingFinder.bestSolution()->getComponents()) |
|
| 1466 { |
|
| 1467 // Get a ref file for this primitive. If we cannot find it in the |
|
| 1468 // LDraw library, generate it. |
|
| 1469 if ((refFile = ::getDocument (radialFileName (::Ring, g_lores, g_lores, cmp.num))) == null) |
|
| 1470 { |
|
| 1471 refFile = generatePrimitive (::Ring, g_lores, g_lores, cmp.num); |
|
| 1472 refFile->setImplicit (false); |
|
| 1473 } |
|
| 1474 |
|
| 1475 LDSubfile* ref = new LDSubfile; |
|
| 1476 ref->setFileInfo (refFile); |
|
| 1477 ref->setTransform (getCircleDrawMatrix (cmp.scale)); |
|
| 1478 ref->setPosition (m_drawedVerts[0]); |
|
| 1479 ref->setColor (maincolor); |
|
| 1480 objs << ref; |
|
| 1481 } |
|
| 1482 } |
|
| 1483 else |
|
| 1484 { |
|
| 1485 // Ring finder failed, last resort: draw the ring with quads |
|
| 1486 QList<QLineF> c0, c1; |
|
| 1487 Axis relX, relY, relZ; |
|
| 1488 getRelativeAxes (relX, relY); |
|
| 1489 relZ = (Axis) (3 - relX - relY); |
|
| 1490 double x0 = m_drawedVerts[0][relX], |
|
| 1491 y0 = m_drawedVerts[0][relY]; |
|
| 1492 |
|
| 1493 Vertex templ; |
|
| 1494 templ[relX] = x0; |
|
| 1495 templ[relY] = y0; |
|
| 1496 templ[relZ] = getDepthValue(); |
|
| 1497 |
|
| 1498 // Calculate circle coords |
|
| 1499 makeCircle (segs, divs, dist0, c0); |
|
| 1500 makeCircle (segs, divs, dist1, c1); |
|
| 1501 |
|
| 1502 for (int i = 0; i < segs; ++i) |
|
| 1503 { |
|
| 1504 Vertex v0, v1, v2, v3; |
|
| 1505 v0 = v1 = v2 = v3 = templ; |
|
| 1506 v0[relX] += c0[i].x1(); |
|
| 1507 v0[relY] += c0[i].y1(); |
|
| 1508 v1[relX] += c0[i].x2(); |
|
| 1509 v1[relY] += c0[i].y2(); |
|
| 1510 v2[relX] += c1[i].x2(); |
|
| 1511 v2[relY] += c1[i].y2(); |
|
| 1512 v3[relX] += c1[i].x1(); |
|
| 1513 v3[relY] += c1[i].y1(); |
|
| 1514 |
|
| 1515 LDQuad* q = new LDQuad (v0, v1, v2, v3); |
|
| 1516 q->setColor (maincolor); |
|
| 1517 |
|
| 1518 // Ensure the quads always are BFC-front towards the camera |
|
| 1519 if (camera() % 3 <= 0) |
|
| 1520 q->invert(); |
|
| 1521 |
|
| 1522 objs << q; |
|
| 1523 } |
|
| 1524 } |
|
| 1525 |
|
| 1526 if (circleOrDisc) |
|
| 1527 { |
|
| 1528 LDSubfile* ref = new LDSubfile; |
|
| 1529 ref->setFileInfo (refFile); |
|
| 1530 ref->setTransform (transform); |
|
| 1531 ref->setPosition (m_drawedVerts[0]); |
|
| 1532 ref->setColor (maincolor); |
|
| 1533 objs << ref; |
|
| 1534 } |
|
| 1535 } break; |
|
| 1536 |
|
| 1537 case ESelectMode: |
|
| 1538 { |
|
| 1539 // this shouldn't happen |
|
| 1540 assert (false); |
|
| 1541 return; |
|
| 1542 } break; |
|
| 1543 } |
|
| 1544 |
|
| 1545 if (objs.size() > 0) |
|
| 1546 { |
|
| 1547 for (LDObject* obj : objs) |
|
| 1548 { |
|
| 1549 document()->addObject (obj); |
|
| 1550 compileObject (obj); |
|
| 1551 } |
|
| 1552 |
|
| 1553 g_win->refresh(); |
|
| 1554 g_win->endAction(); |
|
| 1555 } |
|
| 1556 |
|
| 1557 m_drawedVerts.clear(); |
|
| 1558 m_rectdraw = false; |
|
| 1559 } |
|
| 1560 |
|
| 1561 // ============================================================================= |
|
| 1562 // |
|
| 1563 double GLRenderer::getCircleDrawDist (int pos) const |
|
| 1564 { |
|
| 1565 assert (m_drawedVerts.size() >= pos + 1); |
|
| 1566 const Vertex& v1 = (m_drawedVerts.size() >= pos + 2) ? m_drawedVerts[pos + 1] : m_hoverpos; |
|
| 1567 Axis relX, relY; |
|
| 1568 getRelativeAxes (relX, relY); |
|
| 1569 |
|
| 1570 const double dx = m_drawedVerts[0][relX] - v1[relX]; |
|
| 1571 const double dy = m_drawedVerts[0][relY] - v1[relY]; |
|
| 1572 return sqrt ((dx * dx) + (dy * dy)); |
|
| 1573 } |
|
| 1574 |
|
| 1575 // ============================================================================= |
|
| 1576 // |
|
| 1577 void GLRenderer::getRelativeAxes (Axis& relX, Axis& relY) const |
|
| 1578 { |
|
| 1579 const LDFixedCameraInfo* cam = &g_FixedCameras[camera()]; |
|
| 1580 relX = cam->axisX; |
|
| 1581 relY = cam->axisY; |
|
| 1582 } |
|
| 1583 |
|
| 1584 // ============================================================================= |
|
| 1585 // |
|
| 1586 static QList<Vertex> getVertices (LDObject* obj) |
|
| 1587 { |
|
| 1588 QList<Vertex> verts; |
|
| 1589 |
|
| 1590 if (obj->vertices() >= 2) |
|
| 1591 { |
|
| 1592 for (int i = 0; i < obj->vertices(); ++i) |
|
| 1593 verts << obj->vertex (i); |
|
| 1594 } |
|
| 1595 elif (obj->type() == LDObject::ESubfile) |
|
| 1596 { |
|
| 1597 LDSubfile* ref = static_cast<LDSubfile*> (obj); |
|
| 1598 LDObjectList objs = ref->inlineContents (true, false); |
|
| 1599 |
|
| 1600 for (LDObject* obj : objs) |
|
| 1601 { |
|
| 1602 verts << getVertices (obj); |
|
| 1603 obj->destroy(); |
|
| 1604 } |
|
| 1605 } |
|
| 1606 |
|
| 1607 return verts; |
|
| 1608 } |
|
| 1609 |
|
| 1610 // ============================================================================= |
|
| 1611 // |
|
| 1612 void GLRenderer::compileObject (LDObject* obj) |
|
| 1613 { |
|
| 1614 compiler()->stageForCompilation (obj); |
|
| 1615 |
|
| 1616 // Mark in known vertices of this object |
|
| 1617 /* |
|
| 1618 QList<Vertex> verts = getVertices (obj); |
|
| 1619 m_knownVerts << verts; |
|
| 1620 removeDuplicates (m_knownVerts); |
|
| 1621 */ |
|
| 1622 |
|
| 1623 obj->setGLInit (true); |
|
| 1624 } |
|
| 1625 |
|
| 1626 // ============================================================================= |
|
| 1627 // |
|
| 1628 void GLRenderer::forgetObject (LDObject* obj) |
|
| 1629 { |
|
| 1630 compiler()->dropObject (obj); |
|
| 1631 } |
|
| 1632 |
|
| 1633 // ============================================================================= |
|
| 1634 // |
|
| 1635 uchar* GLRenderer::getScreencap (int& w, int& h) |
|
| 1636 { |
|
| 1637 w = m_width; |
|
| 1638 h = m_height; |
|
| 1639 uchar* cap = new uchar[4 * w * h]; |
|
| 1640 |
|
| 1641 m_screencap = true; |
|
| 1642 update(); |
|
| 1643 m_screencap = false; |
|
| 1644 |
|
| 1645 // Capture the pixels |
|
| 1646 glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); |
|
| 1647 |
|
| 1648 return cap; |
|
| 1649 } |
|
| 1650 |
|
| 1651 // ============================================================================= |
|
| 1652 // |
|
| 1653 void GLRenderer::slot_toolTipTimer() |
|
| 1654 { |
|
| 1655 // We come here if the cursor has stayed in one place for longer than a |
|
| 1656 // a second. Check if we're holding it over a camera icon - if so, draw |
|
| 1657 // a tooltip. |
|
| 1658 for (CameraIcon & icon : m_cameraIcons) |
|
| 1659 { |
|
| 1660 if (icon.destRect.contains (m_pos)) |
|
| 1661 { |
|
| 1662 m_toolTipCamera = icon.cam; |
|
| 1663 m_drawToolTip = true; |
|
| 1664 update(); |
|
| 1665 break; |
|
| 1666 } |
|
| 1667 } |
|
| 1668 } |
|
| 1669 |
|
| 1670 // ============================================================================= |
|
| 1671 // |
|
| 1672 Axis GLRenderer::getCameraAxis (bool y, GLRenderer::EFixedCamera camid) |
|
| 1673 { |
|
| 1674 if (camid == (GL::EFixedCamera) - 1) |
|
| 1675 camid = camera(); |
|
| 1676 |
|
| 1677 const LDFixedCameraInfo* cam = &g_FixedCameras[camid]; |
|
| 1678 return (y) ? cam->axisY : cam->axisX; |
|
| 1679 } |
|
| 1680 |
|
| 1681 // ============================================================================= |
|
| 1682 // |
|
| 1683 bool GLRenderer::setupOverlay (EFixedCamera cam, QString file, int x, int y, int w, int h) |
|
| 1684 { |
|
| 1685 QImage* img = new QImage (QImage (file).convertToFormat (QImage::Format_ARGB32)); |
|
| 1686 LDGLOverlay& info = getOverlay (cam); |
|
| 1687 |
|
| 1688 if (img->isNull()) |
|
| 1689 { |
|
| 1690 critical (tr ("Failed to load overlay image!")); |
|
| 1691 currentDocumentData().overlays[cam].invalid = true; |
|
| 1692 delete img; |
|
| 1693 return false; |
|
| 1694 } |
|
| 1695 |
|
| 1696 delete info.img; // delete the old image |
|
| 1697 |
|
| 1698 info.fname = file; |
|
| 1699 info.lw = w; |
|
| 1700 info.lh = h; |
|
| 1701 info.ox = x; |
|
| 1702 info.oy = y; |
|
| 1703 info.img = img; |
|
| 1704 info.invalid = false; |
|
| 1705 |
|
| 1706 if (info.lw == 0) |
|
| 1707 info.lw = (info.lh * img->width()) / img->height(); |
|
| 1708 elif (info.lh == 0) |
|
| 1709 info.lh = (info.lw * img->height()) / img->width(); |
|
| 1710 |
|
| 1711 const Axis x2d = getCameraAxis (false, cam), |
|
| 1712 y2d = getCameraAxis (true, cam); |
|
| 1713 const double negXFac = g_FixedCameras[cam].negX ? -1 : 1, |
|
| 1714 negYFac = g_FixedCameras[cam].negY ? -1 : 1; |
|
| 1715 |
|
| 1716 info.v0 = info.v1 = g_origin; |
|
| 1717 info.v0[x2d] = - (info.ox * info.lw * negXFac) / img->width(); |
|
| 1718 info.v0[y2d] = (info.oy * info.lh * negYFac) / img->height(); |
|
| 1719 info.v1[x2d] = info.v0[x2d] + info.lw; |
|
| 1720 info.v1[y2d] = info.v0[y2d] + info.lh; |
|
| 1721 |
|
| 1722 // Set alpha of all pixels to 0.5 |
|
| 1723 for (long i = 0; i < img->width(); ++i) |
|
| 1724 for (long j = 0; j < img->height(); ++j) |
|
| 1725 { |
|
| 1726 uint32 pixel = img->pixel (i, j); |
|
| 1727 img->setPixel (i, j, 0x80000000 | (pixel & 0x00FFFFFF)); |
|
| 1728 } |
|
| 1729 |
|
| 1730 updateOverlayObjects(); |
|
| 1731 return true; |
|
| 1732 } |
|
| 1733 |
|
| 1734 // ============================================================================= |
|
| 1735 // |
|
| 1736 void GLRenderer::clearOverlay() |
|
| 1737 { |
|
| 1738 if (camera() == EFreeCamera) |
|
| 1739 return; |
|
| 1740 |
|
| 1741 LDGLOverlay& info = currentDocumentData().overlays[camera()]; |
|
| 1742 delete info.img; |
|
| 1743 info.img = null; |
|
| 1744 |
|
| 1745 updateOverlayObjects(); |
|
| 1746 } |
|
| 1747 |
|
| 1748 // ============================================================================= |
|
| 1749 // |
|
| 1750 void GLRenderer::setDepthValue (double depth) |
|
| 1751 { |
|
| 1752 assert (camera() < EFreeCamera); |
|
| 1753 currentDocumentData().depthValues[camera()] = depth; |
|
| 1754 } |
|
| 1755 |
|
| 1756 // ============================================================================= |
|
| 1757 // |
|
| 1758 double GLRenderer::getDepthValue() const |
|
| 1759 { |
|
| 1760 assert (camera() < EFreeCamera); |
|
| 1761 return currentDocumentData().depthValues[camera()]; |
|
| 1762 } |
|
| 1763 |
|
| 1764 // ============================================================================= |
|
| 1765 // |
|
| 1766 const char* GLRenderer::getCameraName() const |
|
| 1767 { |
|
| 1768 return g_CameraNames[camera()]; |
|
| 1769 } |
|
| 1770 |
|
| 1771 // ============================================================================= |
|
| 1772 // |
|
| 1773 LDGLOverlay& GLRenderer::getOverlay (int newcam) |
|
| 1774 { |
|
| 1775 return currentDocumentData().overlays[newcam]; |
|
| 1776 } |
|
| 1777 |
|
| 1778 // ============================================================================= |
|
| 1779 // |
|
| 1780 void GLRenderer::zoomNotch (bool inward) |
|
| 1781 { |
|
| 1782 if (zoom() > 15) |
|
| 1783 zoom() *= inward ? 0.833f : 1.2f; |
|
| 1784 else |
|
| 1785 zoom() += inward ? -1.2f : 1.2f; |
|
| 1786 } |
|
| 1787 |
|
| 1788 // ============================================================================= |
|
| 1789 // |
|
| 1790 void GLRenderer::zoomToFit() |
|
| 1791 { |
|
| 1792 print ("zooming %1 to fit..\n", camera()); |
|
| 1793 zoom() = 30.0f; |
|
| 1794 |
|
| 1795 if (document() == null || m_width == -1 || m_height == -1) |
|
| 1796 { |
|
| 1797 print ("document is invalid!\n"); |
|
| 1798 return; |
|
| 1799 } |
|
| 1800 |
|
| 1801 bool lastfilled = false; |
|
| 1802 bool firstrun = true; |
|
| 1803 const uint32 white = 0xFFFFFFFF; |
|
| 1804 bool inward = true; |
|
| 1805 const int w = m_width, h = m_height; |
|
| 1806 int runaway = 50; |
|
| 1807 |
|
| 1808 glClearColor (1.0, 1.0, 1.0, 1.0); |
|
| 1809 glDisable (GL_DITHER); |
|
| 1810 |
|
| 1811 // Use the pick list while drawing the scene, this way we can tell whether borders |
|
| 1812 // are background or not. |
|
| 1813 setPicking (true); |
|
| 1814 |
|
| 1815 while (--runaway) |
|
| 1816 { |
|
| 1817 if (zoom() > 10000.0 || zoom() < 0.0) |
|
| 1818 { |
|
| 1819 // Obviously, there's nothing to draw if we get here. |
|
| 1820 // Default to 30.0f and break out. |
|
| 1821 zoom() = 30.0; |
|
| 1822 break; |
|
| 1823 } |
|
| 1824 |
|
| 1825 zoomNotch (inward); |
|
| 1826 |
|
| 1827 uchar* cap = new uchar[4 * w * h]; |
|
| 1828 drawGLScene(); |
|
| 1829 glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap); |
|
| 1830 uint32* imgdata = reinterpret_cast<uint32*> (cap); |
|
| 1831 bool filled = false; |
|
| 1832 |
|
| 1833 // Check the top and bottom rows |
|
| 1834 for (int i = 0; i < w; ++i) |
|
| 1835 { |
|
| 1836 if (imgdata[i] != white || imgdata[((h - 1) * w) + i] != white) |
|
| 1837 { |
|
| 1838 filled = true; |
|
| 1839 break; |
|
| 1840 } |
|
| 1841 } |
|
| 1842 |
|
| 1843 // Left and right edges |
|
| 1844 if (filled == false) |
|
| 1845 { |
|
| 1846 for (int i = 0; i < h; ++i) |
|
| 1847 { |
|
| 1848 if (imgdata[i * w] != white || imgdata[(i * w) + w - 1] != white) |
|
| 1849 { |
|
| 1850 filled = true; |
|
| 1851 break; |
|
| 1852 } |
|
| 1853 } |
|
| 1854 } |
|
| 1855 |
|
| 1856 delete[] cap; |
|
| 1857 |
|
| 1858 if (firstrun) |
|
| 1859 { |
|
| 1860 // If this is the first run, we don't know enough to determine |
|
| 1861 // whether the zoom was to fit, so we mark in our knowledge so |
|
| 1862 // far and start over. |
|
| 1863 inward = !filled; |
|
| 1864 firstrun = false; |
|
| 1865 } |
|
| 1866 else |
|
| 1867 { |
|
| 1868 // If this run filled the screen and the last one did not, the |
|
| 1869 // last run had ideal zoom - zoom a bit back and we should reach it. |
|
| 1870 if (filled && !lastfilled) |
|
| 1871 { |
|
| 1872 zoomNotch (false); |
|
| 1873 break; |
|
| 1874 } |
|
| 1875 |
|
| 1876 // If this run did not fill the screen and the last one did, we've |
|
| 1877 // now reached ideal zoom so we're done here. |
|
| 1878 if (!filled && lastfilled) |
|
| 1879 break; |
|
| 1880 |
|
| 1881 inward = !filled; |
|
| 1882 } |
|
| 1883 |
|
| 1884 lastfilled = filled; |
|
| 1885 } |
|
| 1886 |
|
| 1887 setBackground(); |
|
| 1888 setPicking (false); |
|
| 1889 print ("zoom to fit done.\n"); |
|
| 1890 } |
|
| 1891 |
|
| 1892 // ============================================================================= |
|
| 1893 // |
|
| 1894 void GLRenderer::zoomAllToFit() |
|
| 1895 { |
|
| 1896 EFixedCamera oldcam = camera(); |
|
| 1897 |
|
| 1898 for (int i = 0; i < 7; ++i) |
|
| 1899 { |
|
| 1900 setCamera ((EFixedCamera) i); |
|
| 1901 zoomToFit(); |
|
| 1902 } |
|
| 1903 |
|
| 1904 setCamera (oldcam); |
|
| 1905 } |
|
| 1906 |
|
| 1907 // ============================================================================= |
|
| 1908 // |
|
| 1909 void GLRenderer::updateRectVerts() |
|
| 1910 { |
|
| 1911 if (!m_rectdraw) |
|
| 1912 return; |
|
| 1913 |
|
| 1914 if (m_drawedVerts.isEmpty()) |
|
| 1915 { |
|
| 1916 for (int i = 0; i < 4; ++i) |
|
| 1917 m_rectverts[i] = m_hoverpos; |
|
| 1918 |
|
| 1919 return; |
|
| 1920 } |
|
| 1921 |
|
| 1922 Vertex v0 = m_drawedVerts[0], |
|
| 1923 v1 = (m_drawedVerts.size() >= 2) ? m_drawedVerts[1] : m_hoverpos; |
|
| 1924 |
|
| 1925 const Axis ax = getCameraAxis (false), |
|
| 1926 ay = getCameraAxis (true), |
|
| 1927 az = (Axis) (3 - ax - ay); |
|
| 1928 |
|
| 1929 for (int i = 0; i < 4; ++i) |
|
| 1930 m_rectverts[i][az] = getDepthValue(); |
|
| 1931 |
|
| 1932 m_rectverts[0][ax] = v0[ax]; |
|
| 1933 m_rectverts[0][ay] = v0[ay]; |
|
| 1934 m_rectverts[1][ax] = v1[ax]; |
|
| 1935 m_rectverts[1][ay] = v0[ay]; |
|
| 1936 m_rectverts[2][ax] = v1[ax]; |
|
| 1937 m_rectverts[2][ay] = v1[ay]; |
|
| 1938 m_rectverts[3][ax] = v0[ax]; |
|
| 1939 m_rectverts[3][ay] = v1[ay]; |
|
| 1940 } |
|
| 1941 |
|
| 1942 // ============================================================================= |
|
| 1943 // |
|
| 1944 void GLRenderer::mouseDoubleClickEvent (QMouseEvent* ev) |
|
| 1945 { |
|
| 1946 if (!(ev->buttons() & Qt::LeftButton) || editMode() != ESelectMode) |
|
| 1947 return; |
|
| 1948 |
|
| 1949 pick (ev->x(), ev->y()); |
|
| 1950 |
|
| 1951 if (selection().isEmpty()) |
|
| 1952 return; |
|
| 1953 |
|
| 1954 LDObject* obj = selection().first(); |
|
| 1955 AddObjectDialog::staticDialog (obj->type(), obj); |
|
| 1956 g_win->endAction(); |
|
| 1957 ev->accept(); |
|
| 1958 } |
|
| 1959 |
|
| 1960 // ============================================================================= |
|
| 1961 // |
|
| 1962 LDOverlay* GLRenderer::findOverlayObject (EFixedCamera cam) |
|
| 1963 { |
|
| 1964 LDOverlay* ovlobj = null; |
|
| 1965 |
|
| 1966 for (LDObject* obj : document()->objects()) |
|
| 1967 { |
|
| 1968 if (obj->type() == LDObject::EOverlay && static_cast<LDOverlay*> (obj)->camera() == cam) |
|
| 1969 { |
|
| 1970 ovlobj = static_cast<LDOverlay*> (obj); |
|
| 1971 break; |
|
| 1972 } |
|
| 1973 } |
|
| 1974 |
|
| 1975 return ovlobj; |
|
| 1976 } |
|
| 1977 |
|
| 1978 // ============================================================================= |
|
| 1979 // |
|
| 1980 // Read in overlays from the current file and update overlay info accordingly. |
|
| 1981 // |
|
| 1982 void GLRenderer::initOverlaysFromObjects() |
|
| 1983 { |
|
| 1984 for (EFixedCamera cam : g_Cameras) |
|
| 1985 { |
|
| 1986 if (cam == EFreeCamera) |
|
| 1987 continue; |
|
| 1988 |
|
| 1989 LDGLOverlay& meta = currentDocumentData().overlays[cam]; |
|
| 1990 LDOverlay* ovlobj = findOverlayObject (cam); |
|
| 1991 |
|
| 1992 if (!ovlobj && meta.img) |
|
| 1993 { |
|
| 1994 delete meta.img; |
|
| 1995 meta.img = null; |
|
| 1996 } |
|
| 1997 elif (ovlobj && (meta.img == null || meta.fname != ovlobj->fileName()) && meta.invalid == false) |
|
| 1998 setupOverlay (cam, ovlobj->fileName(), ovlobj->x(), |
|
| 1999 ovlobj->y(), ovlobj->width(), ovlobj->height()); |
|
| 2000 } |
|
| 2001 } |
|
| 2002 |
|
| 2003 // ============================================================================= |
|
| 2004 // |
|
| 2005 void GLRenderer::updateOverlayObjects() |
|
| 2006 { |
|
| 2007 for (EFixedCamera cam : g_Cameras) |
|
| 2008 { |
|
| 2009 if (cam == EFreeCamera) |
|
| 2010 continue; |
|
| 2011 |
|
| 2012 LDGLOverlay& meta = currentDocumentData().overlays[cam]; |
|
| 2013 LDOverlay* ovlobj = findOverlayObject (cam); |
|
| 2014 |
|
| 2015 if (meta.img == null && ovlobj != null) |
|
| 2016 { |
|
| 2017 // If this is the last overlay image, we need to remove the empty space after it as well. |
|
| 2018 LDObject* nextobj = ovlobj->next(); |
|
| 2019 |
|
| 2020 if (nextobj && nextobj->type() == LDObject::EEmpty) |
|
| 2021 nextobj->destroy(); |
|
| 2022 |
|
| 2023 // If the overlay object was there and the overlay itself is |
|
| 2024 // not, remove the object. |
|
| 2025 ovlobj->destroy(); |
|
| 2026 } |
|
| 2027 elif (meta.img != null && ovlobj == null) |
|
| 2028 { |
|
| 2029 // Inverse case: image is there but the overlay object is |
|
| 2030 // not, thus create the object. |
|
| 2031 ovlobj = new LDOverlay; |
|
| 2032 |
|
| 2033 // Find a suitable position to place this object. We want to place |
|
| 2034 // this into the header, which is everything up to the first scemantic |
|
| 2035 // object. If we find another overlay object, place this object after |
|
| 2036 // the last one found. Otherwise, place it before the first schemantic |
|
| 2037 // object and put an empty object after it (though don't do this if |
|
| 2038 // there was no schemantic elements at all) |
|
| 2039 int i, lastOverlay = -1; |
|
| 2040 bool found = false; |
|
| 2041 |
|
| 2042 for (i = 0; i < document()->getObjectCount(); ++i) |
|
| 2043 { |
|
| 2044 LDObject* obj = document()->getObject (i); |
|
| 2045 |
|
| 2046 if (obj->isScemantic()) |
|
| 2047 { |
|
| 2048 found = true; |
|
| 2049 break; |
|
| 2050 } |
|
| 2051 |
|
| 2052 if (obj->type() == LDObject::EOverlay) |
|
| 2053 lastOverlay = i; |
|
| 2054 } |
|
| 2055 |
|
| 2056 if (lastOverlay != -1) |
|
| 2057 document()->insertObj (lastOverlay + 1, ovlobj); |
|
| 2058 else |
|
| 2059 { |
|
| 2060 document()->insertObj (i, ovlobj); |
|
| 2061 |
|
| 2062 if (found) |
|
| 2063 document()->insertObj (i + 1, new LDEmpty); |
|
| 2064 } |
|
| 2065 } |
|
| 2066 |
|
| 2067 if (meta.img && ovlobj) |
|
| 2068 { |
|
| 2069 ovlobj->setCamera (cam); |
|
| 2070 ovlobj->setFileName (meta.fname); |
|
| 2071 ovlobj->setX (meta.ox); |
|
| 2072 ovlobj->setY (meta.oy); |
|
| 2073 ovlobj->setWidth (meta.lw); |
|
| 2074 ovlobj->setHeight (meta.lh); |
|
| 2075 } |
|
| 2076 } |
|
| 2077 |
|
| 2078 if (g_win->R() == this) |
|
| 2079 g_win->refresh(); |
|
| 2080 } |
|