src/gldraw.cc

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

mercurial