src/gldraw.cpp

changeset 183
f1b8cb53d2a2
child 185
6fea53f1ffc2
equal deleted inserted replaced
182:9374fea8f77f 183:f1b8cb53d2a2
1 /*
2 * LDForge: LDraw parts authoring CAD
3 * Copyright (C) 2013 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 <QtGui>
20 #include <QGLWidget>
21 #include <GL/glu.h>
22 #include "common.h"
23 #include "config.h"
24 #include "file.h"
25 #include "gldraw.h"
26 #include "bbox.h"
27 #include "colors.h"
28 #include "gui.h"
29 #include "misc.h"
30 #include "history.h"
31
32 static double g_objOffset[3];
33
34 static short g_pulseTick = 0;
35 static const short g_numPulseTicks = 8;
36 static const short g_pulseInterval = 65;
37
38 static const struct staticCameraMeta {
39 const char glrotate[3];
40 const Axis axisX, axisY;
41 const bool negX, negY;
42 } g_staticCameras[6] = {
43 { { 1, 0, 0 }, X, Z, false, false },
44 { { 0, 0, 0 }, X, Y, false, true },
45 { { 0, 1, 0 }, Z, Y, true, true },
46 { { -1, 0, 0 }, X, Z, false, true },
47 { { 0, 0, 0 }, X, Y, true, true },
48 { { 0, -1, 0 }, Z, Y, false, true },
49 };
50
51 cfg (str, gl_bgcolor, "#CCCCD9");
52 cfg (str, gl_maincolor, "#707078");
53 cfg (float, gl_maincolor_alpha, 1.0);
54 cfg (int, gl_linethickness, 2);
55 cfg (bool, gl_colorbfc, true);
56 cfg (bool, gl_selflash, false);
57 cfg (int, gl_camera, GLRenderer::Free);
58 cfg (bool, gl_blackedges, true);
59 cfg (bool, gl_axes, false);
60
61 // CameraIcon::img is a heap-allocated QPixmap because otherwise it gets
62 // initialized before program gets to main() and constructs a QApplication
63 // and Qt doesn't like that.
64 struct CameraIcon {
65 QPixmap* img;
66 QRect srcRect, destRect, selRect;
67 GLRenderer::Camera cam;
68 } g_CameraIcons[7];
69
70 const char* g_CameraNames[7] = { "Top", "Front", "Left", "Bottom", "Back", "Right", "Free" };
71
72 const GLRenderer::Camera g_Cameras[7] = {
73 GLRenderer::Top,
74 GLRenderer::Front,
75 GLRenderer::Left,
76 GLRenderer::Bottom,
77 GLRenderer::Back,
78 GLRenderer::Right,
79 GLRenderer::Free
80 };
81
82 const struct GLAxis {
83 const QColor col;
84 const vertex vert;
85 } g_GLAxes[3] = {
86 { QColor (255, 0, 0), vertex (10000, 0, 0) },
87 { QColor (128, 192, 0), vertex (0, 10000, 0) },
88 { QColor (0, 160, 192), vertex (0, 0, 10000) },
89 };
90
91 // =============================================================================
92 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
93 // =============================================================================
94 GLRenderer::GLRenderer (QWidget* parent) : QGLWidget (parent) {
95 resetAngles ();
96 m_picking = m_rangepick = false;
97 m_camera = (GLRenderer::Camera) gl_camera.value;
98 m_drawToolTip = false;
99 m_planeDraw = false;
100
101 m_pulseTimer = new QTimer (this);
102 connect (m_pulseTimer, SIGNAL (timeout ()), this, SLOT (slot_timerUpdate ()));
103
104 m_toolTipTimer = new QTimer (this);
105 m_toolTipTimer->setSingleShot (true);
106 connect (m_toolTipTimer, SIGNAL (timeout ()), this, SLOT (slot_toolTipTimer ()));
107
108 m_thickBorderPen = QPen (QColor (0, 0, 0, 208), 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
109 m_thinBorderPen = m_thickBorderPen;
110 m_thinBorderPen.setWidth (1);
111
112 // Init camera icons
113 for (const GLRenderer::Camera cam : g_Cameras) {
114 str iconname;
115 iconname.format ("camera-%s", str (g_CameraNames[cam]).tolower ().chars ());
116
117 CameraIcon* info = &g_CameraIcons[cam];
118 info->img = new QPixmap (getIcon (iconname));
119 info->cam = cam;
120 }
121
122 calcCameraIconRects ();
123 }
124
125 // =============================================================================
126 GLRenderer::~GLRenderer() {
127 for (CameraIcon& info : g_CameraIcons)
128 delete info.img;
129 }
130
131 // =============================================================================
132 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
133 // =============================================================================
134 void GLRenderer::calcCameraIconRects () {
135 ushort i = 0;
136
137 for (CameraIcon& info : g_CameraIcons) {
138 const long x1 = (m_width - (info.cam != Free ? 48 : 16)) + ((i % 3) * 16) - 1,
139 y1 = ((i / 3) * 16) + 1;
140
141 info.srcRect = QRect (0, 0, 16, 16);
142 info.destRect = QRect (x1, y1, 16, 16);
143 info.selRect = QRect (info.destRect.x (), info.destRect.y (),
144 info.destRect.width () + 1, info.destRect.height () + 1);
145 ++i;
146 }
147 }
148
149 // =============================================================================
150 void GLRenderer::resetAngles () {
151 m_rotX = 30.0f;
152 m_rotY = 325.f;
153 m_panX = m_panY = m_rotZ = 0.0f;
154
155 // Set the default zoom based on the bounding box
156 m_zoom = g_BBox.size () * 6;
157 }
158
159 // =============================================================================
160 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
161 // =============================================================================
162 void GLRenderer::initializeGL () {
163 setBackground ();
164
165 glEnable (GL_POLYGON_OFFSET_FILL);
166 glPolygonOffset (1.0f, 1.0f);
167
168 glEnable (GL_DEPTH_TEST);
169 glShadeModel (GL_SMOOTH);
170 glEnable (GL_MULTISAMPLE);
171
172 glEnable (GL_BLEND);
173 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
174
175 glEnable (GL_LINE_SMOOTH);
176 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
177
178 glLineWidth (gl_linethickness);
179
180 setAutoFillBackground (false);
181 setMouseTracking (true);
182 setFocusPolicy (Qt::WheelFocus);
183 compileObjects ();
184 }
185
186 // =============================================================================
187 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
188 // =============================================================================
189 QColor GLRenderer::getMainColor () {
190 QColor col (gl_maincolor.value.chars());
191
192 if (!col.isValid ())
193 return QColor (0, 0, 0);
194
195 col.setAlpha (gl_maincolor_alpha * 255.f);
196 return col;
197 }
198
199 // -----------------------------------------------------------------------------
200 void GLRenderer::setBackground () {
201 QColor col (gl_bgcolor.value.chars());
202
203 if (!col.isValid ())
204 return;
205
206 m_darkbg = luma (col) < 80;
207
208 col.setAlpha (255);
209 qglClearColor (col);
210 }
211
212 // =============================================================================
213 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
214 // =============================================================================
215 static vector<short> g_daWarnedColors;
216 void GLRenderer::setObjectColor (LDObject* obj) {
217 QColor qcol;
218
219 if (!obj->isColored())
220 return;
221
222 if (m_picking) {
223 // Make the color by the object's index color if we're picking, so we can
224 // make the index from the color we get from the picking results.
225 long i = obj->getIndex (g_curfile);
226
227 // If we couldn't find the index, this object must not be from this file,
228 // therefore it must be an object inlined from a subfile reference or
229 // decomposed from a radial. Find the top level parent object and use
230 // its index.
231 if (i == -1)
232 i = obj->topLevelParent ()->getIndex (g_curfile);
233
234 // We should have the index now.
235 assert (i != -1);
236
237 // Calculate a color based from this index. This method caters for
238 // 16777216 objects. I don't think that'll be exceeded anytime soon. :)
239 // ATM biggest is 53588.dat with 12600 lines.
240 double r = (i / (256 * 256)) % 256,
241 g = (i / 256) % 256,
242 b = i % 256;
243
244 qglColor (QColor (r, g, b, 255));
245 return;
246 }
247
248 #if 0
249 if (gl_colorbfc &&
250 obj->getType () != LDObject::Line &&
251 obj->getType () != LDObject::CondLine)
252 {
253 if (bBackSide)
254 glColor4f (0.9f, 0.0f, 0.0f, 1.0f);
255 else
256 glColor4f (0.0f, 0.8f, 0.0f, 1.0f);
257 return;
258 }
259 #endif
260
261 if (obj->dColor == maincolor)
262 qcol = getMainColor ();
263 else {
264 color* col = getColor (obj->dColor);
265 qcol = col->qColor;
266 }
267
268 if (obj->dColor == edgecolor) {
269 qcol = Qt::black;
270 color* col;
271
272 if (!gl_blackedges && obj->parent != null && (col = getColor (obj->parent->dColor)) != null)
273 qcol = col->qEdge;
274 }
275
276 if (qcol.isValid () == false) {
277 // The color was unknown. Use main color to make the object at least
278 // not appear pitch-black.
279 if (obj->dColor != edgecolor)
280 qcol = getMainColor ();
281
282 // Warn about the unknown colors, but only once.
283 for (short i : g_daWarnedColors)
284 if (obj->dColor == i)
285 return;
286
287 printf ("%s: Unknown color %d!\n", __func__, obj->dColor);
288 g_daWarnedColors.push_back (obj->dColor);
289 return;
290 }
291
292 long r = qcol.red (),
293 g = qcol.green (),
294 b = qcol.blue (),
295 a = qcol.alpha ();
296
297 // If it's selected, brighten it up, also pulse flash it if desired.
298 if (g_win->isSelected (obj)) {
299 short tick, numTicks;
300
301 if (gl_selflash) {
302 tick = (g_pulseTick < (g_numPulseTicks / 2)) ? g_pulseTick : (g_numPulseTicks - g_pulseTick);
303 numTicks = g_numPulseTicks;
304 } else {
305 tick = 2;
306 numTicks = 5;
307 }
308
309 const long add = ((tick * 128) / numTicks);
310 r = min (r + add, 255l);
311 g = min (g + add, 255l);
312 b = min (b + add, 255l);
313
314 // a = 255;
315 }
316
317 glColor4f (
318 ((double) r) / 255.0f,
319 ((double) g) / 255.0f,
320 ((double) b) / 255.0f,
321 ((double) a) / 255.0f);
322 }
323
324 // =============================================================================
325 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
326 // =============================================================================
327 void GLRenderer::refresh () {
328 update ();
329 swapBuffers ();
330 }
331
332 // =============================================================================
333 void GLRenderer::hardRefresh () {
334 compileObjects ();
335 refresh ();
336
337 glLineWidth (gl_linethickness);
338 }
339
340 // =============================================================================
341 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
342 // =============================================================================
343 void GLRenderer::resizeGL (int w, int h) {
344 m_width = w;
345 m_height = h;
346
347 calcCameraIconRects ();
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 void GLRenderer::drawGLScene () const {
357 if (g_curfile == null)
358 return;
359
360 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
361 glEnable (GL_DEPTH_TEST);
362
363 if (m_camera != GLRenderer::Free) {
364 glMatrixMode (GL_PROJECTION);
365 glPushMatrix ();
366
367 glLoadIdentity ();
368 glOrtho (-m_virtWidth, m_virtWidth, -m_virtHeight, m_virtHeight, -100.0f, 100.0f);
369 glTranslatef (m_panX, m_panY, 0.0f);
370 glRotatef (90.0f, g_staticCameras[m_camera].glrotate[0],
371 g_staticCameras[m_camera].glrotate[1],
372 g_staticCameras[m_camera].glrotate[2]);
373
374 // Back camera needs to be handled differently
375 if (m_camera == GLRenderer::Back) {
376 glRotatef (180.0f, 1.0f, 0.0f, 0.0f);
377 glRotatef (180.0f, 0.0f, 0.0f, 1.0f);
378 }
379 } else {
380 glMatrixMode (GL_MODELVIEW);
381 glPushMatrix ();
382 glLoadIdentity ();
383
384 glTranslatef (0.0f, 0.0f, -2.0f);
385 glTranslatef (m_panX, m_panY, -m_zoom);
386 glRotatef (m_rotX, 1.0f, 0.0f, 0.0f);
387 glRotatef (m_rotY, 0.0f, 1.0f, 0.0f);
388 glRotatef (m_rotZ, 0.0f, 0.0f, 1.0f);
389 }
390
391 for (LDObject* obj : g_curfile->m_objs) {
392 if (obj->hidden ())
393 continue; // Don't draw hidden objects
394
395 glCallList (m_picking == false ? obj->uGLList : obj->uGLPickList);
396 }
397
398 if (gl_axes && !m_picking)
399 glCallList (m_axeslist);
400
401 glPopMatrix ();
402 glMatrixMode (GL_MODELVIEW);
403 }
404
405 // =============================================================================
406 vertex GLRenderer::coord_2to3 (const QPoint& pos2d, const bool snap) const {
407 vertex pos3d;
408 const staticCameraMeta* cam = &g_staticCameras[m_camera];
409 const Axis axisX = cam->axisX;
410 const Axis axisY = cam->axisY;
411 const short negXFac = cam->negX ? -1 : 1,
412 negYFac = cam->negY ? -1 : 1;
413
414 // Calculate cx and cy - these are the LDraw unit coords the cursor is at.
415 double cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]);
416 double cy = (m_virtHeight - ((2 * pos2d.y () * m_virtHeight) / m_height) - m_panY) - (negYFac * g_objOffset[axisY]);
417
418 if (snap) {
419 cx = Grid::snap (cx, (Grid::Config) axisX);
420 cy = Grid::snap (cy, (Grid::Config) axisY);
421 }
422
423 cx *= negXFac;
424 cy *= negYFac;
425
426 pos3d = g_origin;
427 pos3d[axisX] = cx;
428 pos3d[axisY] = cy;
429 return pos3d;
430 }
431
432 // =============================================================================
433 QPoint GLRenderer::coord_3to2 (const vertex& pos3d) const {
434 /*
435 cx = (-m_virtWidth + ((2 * pos2d.x () * m_virtWidth) / m_width) - m_panX) - (negXFac * g_objOffset[axisX]);
436
437 cx = (-vw + ((2 * x * vw) / w) - panx) - (neg * ofs)
438 cx + (neg * ofs) = (-vw + ((2 * x * vw) / w) - panx)
439 cx + (neg * ofs) = ((2 * x * vw) / w) - vw - panx
440 (cx + (neg * ofs)) + vw + panx = (2 * x * vw) / w
441 ((cx + (neg * ofs)) + vw + panx) * w = 2 * vw * x
442
443 x = (((cx + (neg * ofs)) + vw + panx) * w) / (2 * vw)
444 */
445
446 QPoint pos2d;
447 const staticCameraMeta* cam = &g_staticCameras[m_camera];
448 const Axis axisX = cam->axisX;
449 const Axis axisY = cam->axisY;
450 const short negXFac = cam->negX ? -1 : 1,
451 negYFac = cam->negY ? -1 : 1;
452
453 short x1 = (((pos3d[axisX] + (negXFac * g_objOffset[axisX])) +
454 m_virtWidth + m_panX) * m_width) / (2 * m_virtWidth);
455 short y1 = -(((pos3d[axisY] + (negYFac * g_objOffset[axisY])) -
456 m_virtHeight + m_panY) * m_height) / (2 * m_virtHeight);
457
458 x1 *= negXFac;
459 y1 *= negYFac;
460
461 pos2d = QPoint (x1, y1);
462 return pos2d;
463 }
464
465 // =============================================================================
466 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
467 // =============================================================================
468 void GLRenderer::paintEvent (QPaintEvent* ev) {
469 Q_UNUSED (ev)
470 m_virtWidth = m_zoom;
471 m_virtHeight = (m_height * m_virtWidth) / m_width;
472 drawGLScene ();
473
474 QPainter paint (this);
475 QFontMetrics metrics = QFontMetrics (QFont ());
476 paint.setRenderHint (QPainter::Antialiasing);
477
478 m_hoverpos = g_origin;
479
480 if (m_camera != Free) {
481 // Calculate 3d position of the cursor
482 m_hoverpos = coord_2to3 (m_pos, true);
483
484 // Paint the coordinates onto the screen.
485 str text;
486 text.format ("X: %s, Y: %s, Z: %s", ftoa (m_hoverpos[X]).chars (),
487 ftoa (m_hoverpos[Y]).chars (), ftoa (m_hoverpos[Z]).chars ());
488
489 QFontMetrics metrics = QFontMetrics (font ());
490 QRect textSize = metrics.boundingRect (0, 0, m_width, m_height, Qt::AlignCenter, text);
491
492 paint.drawText (m_width - textSize.width (), m_height - 16, textSize.width (),
493 textSize.height (), Qt::AlignCenter, text);
494
495 // If we're plane drawing, draw the vertices onto the screen.
496 if (m_planeDraw) {
497 ushort numverts = m_planeDrawVerts.size () + 1;
498 const short blipsize = 8;
499
500 if (numverts > 0) {
501 QPoint* poly = new QPoint[numverts];
502
503 uchar i = 0;
504 for (vertex& vert : m_planeDrawVerts) {
505 poly[i] = coord_3to2 (vert);
506 ++i;
507 }
508
509 // Draw the cursor vertex as the last one in the list.
510 if (numverts < 5)
511 poly[i] = coord_3to2 (m_hoverpos);
512 else
513 numverts = 4;
514
515 paint.setPen (m_thinBorderPen);
516 paint.setBrush (QColor (128, 192, 0));
517
518 // Draw vertex blips
519 for (ushort i = 0; i < numverts; ++i) {
520 QPoint& blip = poly[i];
521 paint.drawEllipse (blip.x () - blipsize / 2, blip.y () - blipsize / 2,
522 blipsize, blipsize);
523 }
524
525 paint.setPen (m_thickBorderPen);
526 paint.setBrush (QColor (128, 192, 0, 128));
527 paint.drawPolygon (poly, numverts);
528
529 delete[] poly;
530 }
531 }
532 }
533
534 // Camera icons
535 if (!m_picking && m_planeDraw == false) {
536 // Draw a background for the selected camera
537 paint.setPen (m_thinBorderPen);
538 paint.setBrush (QBrush (QColor (0, 128, 160, 128)));
539 paint.drawRect (g_CameraIcons[camera ()].selRect);
540
541 // Draw the actual icons
542 for (CameraIcon& info : g_CameraIcons)
543 paint.drawPixmap (info.destRect, *info.img, info.srcRect);
544
545 // Draw a label for the current camera in the top left corner
546 {
547 const ushort margin = 4;
548
549 str label;
550 label.format ("%s Camera", g_CameraNames[camera ()]);
551 paint.setBrush (Qt::black);
552 paint.drawText (QPoint (margin, margin + metrics.ascent ()), label);
553 }
554
555 // Tool tips
556 if (m_drawToolTip) {
557 if (g_CameraIcons[m_toolTipCamera].destRect.contains (m_pos) == false)
558 m_drawToolTip = false;
559 else {
560 QPen bord = m_thinBorderPen;
561 bord.setBrush (Qt::black);
562
563 const ushort margin = 2;
564 ushort x0 = m_pos.x (),
565 y0 = m_pos.y ();
566
567 str label;
568 label.format ("%s Camera", g_CameraNames[m_toolTipCamera]);
569
570 const ushort textWidth = metrics.width (label),
571 textHeight = metrics.height (),
572 fullWidth = textWidth + (2 * margin),
573 fullHeight = textHeight + (2 * margin);
574
575 QRect area (m_pos.x (), m_pos.y (), fullWidth, fullHeight);
576
577 if (x0 + fullWidth > m_width)
578 x0 -= fullWidth;
579
580 if (y0 + fullHeight > m_height)
581 y0 -= fullHeight;
582
583 paint.setBrush (QColor (0, 128, 255, 208));
584 paint.setPen (bord);
585 paint.drawRect (x0, y0, fullWidth, fullHeight);
586
587 paint.setBrush (Qt::black);
588 paint.drawText (QPoint (x0 + margin, y0 + margin + metrics.ascent ()), label);
589 }
590 }
591 }
592
593 // If we're range-picking, draw a rectangle encompassing the selection area.
594 if (m_rangepick && !m_picking) {
595 const short x0 = m_rangeStart.x (),
596 y0 = m_rangeStart.y (),
597 x1 = m_pos.x (),
598 y1 = m_pos.y ();
599
600 QRect rect (x0, y0, x1 - x0, y1 - y0);
601 QColor fillColor = (m_addpick ? "#80FF00" : "#00CCFF");
602 fillColor.setAlphaF (0.2f);
603
604 paint.setPen (m_thickBorderPen);
605 paint.setBrush (QBrush (fillColor));
606 paint.drawRect (rect);
607 }
608 }
609
610 // =============================================================================
611 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
612 // =============================================================================
613 void GLRenderer::compileObjects () {
614 if (g_BBox.empty () == false) {
615 g_objOffset[X] = -(g_BBox.v0 ()[X] + g_BBox.v1 ()[X]) / 2;
616 g_objOffset[Y] = -(g_BBox.v0 ()[Y] + g_BBox.v1 ()[Y]) / 2;
617 g_objOffset[Z] = -(g_BBox.v0 ()[Z] + g_BBox.v1 ()[Z]) / 2;
618 } else {
619 // use a default bbox if we need
620 g_objOffset[X] = g_objOffset[Y] = g_objOffset[Z] = 0;
621 }
622
623 if (!g_curfile) {
624 printf ("renderer: no files loaded, cannot compile anything\n");
625 return;
626 }
627
628 for (LDObject* obj : g_curfile->m_objs) {
629 GLuint* upaLists[2] = {
630 &obj->uGLList,
631 &obj->uGLPickList
632 };
633
634 for (GLuint* upMemberList : upaLists) {
635 GLuint uList = glGenLists (1);
636 glNewList (uList, GL_COMPILE);
637
638 m_picking = (upMemberList == &obj->uGLPickList);
639 compileOneObject (obj);
640 m_picking = false;
641
642 glEndList ();
643 *upMemberList = uList;
644 }
645 }
646
647 // Compile axes
648 m_axeslist = glGenLists (1);
649 glNewList (m_axeslist, GL_COMPILE);
650 glBegin (GL_LINES);
651 for (const GLAxis& ax : g_GLAxes) {
652 qglColor (ax.col);
653 compileVertex (ax.vert);
654 compileVertex (-ax.vert);
655 }
656 glEnd ();
657 glEndList ();
658 }
659
660 // =============================================================================
661 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
662 // =============================================================================
663 void GLRenderer::compileSubObject (LDObject* obj, const GLenum gltype) {
664 glBegin (gltype);
665
666 const short numverts = (obj->getType () != LDObject::CondLine) ? obj->vertices () : 2;
667
668 for (short i = 0; i < numverts; ++i)
669 compileVertex (obj->vaCoords[i]);
670
671 glEnd ();
672 }
673
674 // =============================================================================
675 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
676 // =============================================================================
677 void GLRenderer::compileOneObject (LDObject* obj) {
678 setObjectColor (obj);
679
680 switch (obj->getType ()) {
681 case LDObject::Line:
682 compileSubObject (obj, GL_LINES);
683 break;
684
685 case LDObject::CondLine:
686 glLineStipple (1, 0x6666);
687 glEnable (GL_LINE_STIPPLE);
688
689 compileSubObject (obj, GL_LINES);
690
691 glDisable (GL_LINE_STIPPLE);
692 break;
693
694 case LDObject::Triangle:
695 compileSubObject (obj, GL_TRIANGLES);
696 break;
697
698 case LDObject::Quad:
699 compileSubObject (obj, GL_QUADS);
700 break;
701
702 case LDObject::Subfile:
703 {
704 LDSubfile* ref = static_cast<LDSubfile*> (obj);
705 vector<LDObject*> objs = ref->inlineContents (true, true);
706
707 for (LDObject* obj : objs) {
708 compileOneObject (obj);
709 delete obj;
710 }
711 }
712 break;
713
714 case LDObject::Radial:
715 {
716 LDRadial* pRadial = static_cast<LDRadial*> (obj);
717 std::vector<LDObject*> objs = pRadial->decompose (true);
718
719 for (LDObject* obj : objs) {
720 compileOneObject (obj);
721 delete obj;
722 }
723 }
724 break;
725
726 #if 0
727 TODO: find a proper way to draw vertices without having them be affected by zoom.
728 case LDObject::Vertex:
729 {
730 LDVertex* pVert = static_cast<LDVertex*> (obj);
731 LDTriangle* pPoly;
732 vertex* vPos = &(pVert->vPosition);
733 const double fPolyScale = max (fZoom, 1.0);
734
735 #define BIPYRAMID_COORD(N) ((((i + N) % 4) >= 2 ? 1 : -1) * 0.3f * fPolyScale)
736
737 for (int i = 0; i < 8; ++i) {
738 pPoly = new LDTriangle;
739 pPoly->vaCoords[0] = {vPos->x, vPos->y + ((i >= 4 ? 1 : -1) * 0.4f * fPolyScale), vPos->z};
740 pPoly->vaCoords[1] = {
741 vPos->x + BIPYRAMID_COORD (0),
742 vPos->y,
743 vPos->z + BIPYRAMID_COORD (1)
744 };
745
746 pPoly->vaCoords[2] = {
747 vPos->x + BIPYRAMID_COORD (1),
748 vPos->y,
749 vPos->z + BIPYRAMID_COORD (2)
750 };
751
752 pPoly->dColor = pVert->dColor;
753 compileOneObject (pPoly);
754 delete pPoly;
755 }
756 }
757 break;
758 #endif // 0
759
760 default:
761 break;
762 }
763 }
764
765 // =============================================================================
766 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
767 // =============================================================================
768 void GLRenderer::compileVertex (const vertex& vrt) {
769 glVertex3d (
770 (vrt[X] + g_objOffset[0]),
771 -(vrt[Y] + g_objOffset[1]),
772 -(vrt[Z] + g_objOffset[2]));
773 }
774
775 // =============================================================================
776 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
777 // =============================================================================
778 void GLRenderer::clampAngle (double& angle) {
779 while (angle < 0)
780 angle += 360.0;
781 while (angle > 360.0)
782 angle -= 360.0;
783 }
784
785 // =============================================================================
786 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
787 // =============================================================================
788 void GLRenderer::mouseReleaseEvent (QMouseEvent* ev) {
789 const bool wasLeft = (m_lastButtons & Qt::LeftButton) && !(ev->buttons() & Qt::LeftButton);
790 const bool wasRight = (m_lastButtons & Qt::RightButton) && !(ev->buttons() & Qt::RightButton);
791
792 if (wasLeft) {
793 if (m_planeDraw) {
794 // If we picked an already-existing vertex, stop drawing
795 for (vertex& vert : m_planeDrawVerts) {
796 if (vert == m_hoverpos) {
797 endPlaneDraw (true);
798 return;
799 }
800 }
801
802 // Also, if have 4 verts, also stop drawing.
803 if (m_planeDrawVerts.size () >= 4)
804 endPlaneDraw (true);
805
806 m_planeDrawVerts.push_back (m_hoverpos);
807
808 update ();
809 return;
810 } else {
811 if (!m_rangepick)
812 m_addpick = (m_keymods & Qt::ControlModifier);
813
814 if (m_totalmove < 10 || m_rangepick)
815 pick (ev->x (), ev->y ());
816 }
817
818 m_rangepick = false;
819 m_totalmove = 0;
820 return;
821 }
822
823 if (wasRight && m_planeDraw) {
824 if (m_planeDrawVerts.size () > 0) {
825 // Remove the last vertex
826 m_planeDrawVerts.erase (m_planeDrawVerts.end () - 1);
827 } else {
828 endPlaneDraw (false);
829 return;
830 }
831
832 update ();
833 }
834 }
835
836 // =============================================================================
837 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
838 // =============================================================================
839 void GLRenderer::mousePressEvent (QMouseEvent* ev) {
840 if (ev->buttons () & Qt::LeftButton)
841 m_totalmove = 0;
842
843 if (ev->modifiers () & Qt::ShiftModifier) {
844 m_rangepick = true;
845 m_rangeStart.setX (ev->x ());
846 m_rangeStart.setY (ev->y ());
847
848 m_addpick = (m_keymods & Qt::ControlModifier);
849 }
850
851 m_lastButtons = ev->buttons ();
852 }
853
854 // =============================================================================
855 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
856 // =============================================================================
857 void GLRenderer::mouseMoveEvent (QMouseEvent* ev) {
858 int dx = ev->x () - m_pos.x ();
859 int dy = ev->y () - m_pos.y ();
860 m_totalmove += abs (dx) + abs (dy);
861
862 if (ev->buttons () & Qt::LeftButton && !m_rangepick) {
863 m_rotX = m_rotX + (dy);
864 m_rotY = m_rotY + (dx);
865
866 clampAngle (m_rotX);
867 clampAngle (m_rotY);
868 }
869
870 if (ev->buttons () & Qt::MidButton) {
871 m_panX += 0.03f * dx * (m_zoom / 7.5f);
872 m_panY -= 0.03f * dy * (m_zoom / 7.5f);
873 }
874
875 // Start the tool tip timer
876 if (!m_drawToolTip)
877 m_toolTipTimer->start (1000);
878
879 m_pos = ev->pos ();
880 update ();
881 }
882
883 // =============================================================================
884 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
885 // =============================================================================
886 void GLRenderer::keyPressEvent (QKeyEvent* ev) {
887 m_keymods = ev->modifiers ();
888 }
889
890 void GLRenderer::keyReleaseEvent (QKeyEvent* ev) {
891 m_keymods = ev->modifiers ();
892 }
893
894 // =============================================================================
895 void GLRenderer::wheelEvent (QWheelEvent* ev) {
896 m_zoom *= (ev->delta () < 0) ? 1.2f : (1.0f / 1.2f);
897 m_zoom = clamp (m_zoom, 0.01, 10000.0);
898
899 update ();
900 ev->accept ();
901 }
902
903 // =============================================================================
904 void GLRenderer::leaveEvent (QEvent* ev) {
905 Q_UNUSED (ev);
906 m_drawToolTip = false;
907 m_toolTipTimer->stop ();
908 update ();
909 }
910
911 // =============================================================================
912 void GLRenderer::contextMenuEvent (QContextMenuEvent* ev) {
913 g_win->spawnContextMenu (ev->globalPos ());
914 }
915
916 // =============================================================================
917 void GLRenderer::setCamera (const GLRenderer::Camera cam) {
918 m_camera = cam;
919 gl_camera = (int) cam;
920 }
921
922 // =============================================================================
923 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
924 // =============================================================================
925 void GLRenderer::updateSelFlash () {
926 if (gl_selflash && g_win->sel ().size() > 0) {
927 m_pulseTimer->start (g_pulseInterval);
928 g_pulseTick = 0;
929 } else
930 m_pulseTimer->stop ();
931 }
932
933 // =============================================================================
934 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
935 // =============================================================================
936 void GLRenderer::pick (uint mouseX, uint mouseY) {
937 // Check if we selected a camera icon
938 if (!m_rangepick) {
939 QPoint pos (mouseX, mouseY);
940
941 for (CameraIcon& info : g_CameraIcons) {
942 if (info.destRect.contains (pos)) {
943 setCamera (info.cam);
944 update ();
945 return;
946 }
947 }
948 }
949
950 GLint viewport[4];
951 LDObject* removedObject = null;
952
953 // Clear the selection if we do not wish to add to it.
954 if (!m_addpick) {
955 std::vector<LDObject*> oldsel = g_win->sel ();
956 g_win->sel ().clear ();
957
958 // Recompile the prior selection to remove the highlight color
959 for (LDObject* obj : oldsel)
960 recompileObject (obj);
961 }
962
963 m_picking = true;
964
965 // Paint the picking scene
966 glDisable (GL_DITHER);
967 glClearColor (1.0f, 1.0f, 1.0f, 1.0f);
968 drawGLScene ();
969
970 glGetIntegerv (GL_VIEWPORT, viewport);
971
972 short x0 = mouseX,
973 y0 = mouseY;
974 short x1, y1;
975
976 // Determine how big an area to read - with range picking, we pick by
977 // the area given, with single pixel picking, we use an 1 x 1 area.
978 if (m_rangepick) {
979 x1 = m_rangeStart.x ();
980 y1 = m_rangeStart.y ();
981 } else {
982 x1 = x0 + 1;
983 y1 = y0 + 1;
984 }
985
986 // x0 and y0 must be less than x1 and y1, respectively.
987 if (x0 > x1)
988 dataswap (x0, x1);
989
990 if (y0 > y1)
991 dataswap (y0, y1);
992
993 // Clamp the values to ensure they're within bounds
994 x0 = max<short> (0, x0);
995 y0 = max<short> (0, y0);
996 x1 = min<short> (x1, m_width);
997 y1 = min<short> (y1, m_height);
998
999 const short areawidth = (x1 - x0);
1000 const short areaheight = (y1 - y0);
1001 const long numpixels = areawidth * areaheight;
1002
1003 // Allocate space for the pixel data.
1004 uchar* const pixeldata = new uchar[4 * numpixels];
1005 uchar* pixelptr = &pixeldata[0];
1006
1007 assert (viewport[3] == m_height);
1008
1009 // Read pixels from the color buffer.
1010 glReadPixels (x0, viewport[3] - y1, areawidth, areaheight, GL_RGBA, GL_UNSIGNED_BYTE, pixeldata);
1011
1012 // Go through each pixel read and add them to the selection.
1013 for (long i = 0; i < numpixels; ++i) {
1014 uint32 idx =
1015 (*(pixelptr) * 0x10000) +
1016 (*(pixelptr + 1) * 0x00100) +
1017 (*(pixelptr + 2) * 0x00001);
1018 pixelptr += 4;
1019
1020 if (idx == 0xFFFFFF)
1021 continue; // White is background; skip
1022
1023 LDObject* obj = g_curfile->object (idx);
1024
1025 // If this is an additive single pick and the object is currently selected,
1026 // we remove it from selection instead.
1027 if (!m_rangepick && m_addpick) {
1028 bool removed = false;
1029
1030 for (ulong i = 0; i < g_win->sel ().size(); ++i) {
1031 if (g_win->sel ()[i] == obj) {
1032 g_win->sel ().erase (g_win->sel ().begin () + i);
1033 removedObject = obj;
1034 removed = true;
1035 }
1036 }
1037
1038 if (removed)
1039 break;
1040 }
1041
1042 g_win->sel ().push_back (obj);
1043 }
1044
1045 delete[] pixeldata;
1046
1047 // Remove duplicate entries. For this to be effective, the vector must be
1048 // sorted first.
1049 std::vector<LDObject*>& sel = g_win->sel ();
1050 std::sort (sel.begin(), sel.end ());
1051 std::vector<LDObject*>::iterator pos = std::unique (sel.begin (), sel.end ());
1052 sel.resize (std::distance (sel.begin (), pos));
1053
1054 // Update everything now.
1055 g_win->buildObjList ();
1056
1057 m_picking = false;
1058 m_rangepick = false;
1059 glEnable (GL_DITHER);
1060
1061 setBackground ();
1062 updateSelFlash ();
1063
1064 for (LDObject* obj : g_win->sel ())
1065 recompileObject (obj);
1066
1067 if (removedObject != null)
1068 recompileObject (removedObject);
1069
1070 drawGLScene ();
1071 swapBuffers ();
1072 update ();
1073 }
1074
1075 // =============================================================================
1076 void GLRenderer::beginPlaneDraw () {
1077 if (m_camera == Free)
1078 return; // Cannot draw with the free camera
1079
1080 m_planeDraw = true;
1081
1082 // Disable the context menu - we need the right mouse button
1083 // for removing vertices.
1084 setContextMenuPolicy (Qt::NoContextMenu);
1085
1086 // Use the crosshair cursor when plane drawing.
1087 setCursor (Qt::CrossCursor);
1088
1089 // Clear the selection when beginning to draw onto a plane.
1090 // FIXME: make the selection clearing stuff in ::pick a method and use it
1091 // here! This code doesn't update the GL lists.
1092 g_win->sel ().clear ();
1093 g_win->updateSelection ();
1094 update ();
1095 }
1096
1097 // =============================================================================
1098 void GLRenderer::endPlaneDraw (bool accept) {
1099 // If we accepted, clean the selection and create the object
1100 if (accept) {
1101 vector<vertex>& verts = m_planeDrawVerts;
1102 LDObject* obj = null;
1103
1104 switch (verts.size ()) {
1105 case 1:
1106 {
1107 // 1 vertex - add a vertex object
1108 obj = new LDVertex;
1109 static_cast<LDVertex*> (obj)->vPosition = verts[0];
1110 obj->dColor = maincolor;
1111 }
1112 break;
1113
1114 case 2:
1115 {
1116 // 2 verts - make a line
1117 obj = new LDLine;
1118 obj->dColor = edgecolor;
1119 for (ushort i = 0; i < 2; ++i)
1120 obj->vaCoords[i] = verts[i];
1121 }
1122 break;
1123
1124 case 3:
1125 case 4:
1126 {
1127 obj = (verts.size () == 3) ?
1128 static_cast<LDObject*> (new LDTriangle) :
1129 static_cast<LDObject*> (new LDQuad);
1130
1131 obj->dColor = maincolor;
1132 for (ushort i = 0; i < obj->vertices (); ++i)
1133 obj->vaCoords[i] = verts[i];
1134 }
1135 break;
1136 }
1137
1138 if (obj) {
1139 g_curfile->addObject (obj);
1140 recompileObject (obj);
1141 g_win->refresh ();
1142
1143 History::addEntry (new AddHistory ({(ulong) obj->getIndex (g_curfile)}, {obj->clone ()}));
1144 }
1145 }
1146
1147 m_planeDraw = false;
1148 m_planeDrawVerts.clear ();
1149
1150 unsetCursor ();
1151
1152 // Restore the context menu
1153 setContextMenuPolicy (Qt::DefaultContextMenu);
1154
1155 update ();
1156 }
1157
1158 // =============================================================================
1159 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1160 // =============================================================================
1161 void GLRenderer::recompileObject (LDObject* obj) {
1162 // Replace the old list with the new one.
1163 GLuint uList = glGenLists (1);
1164 glNewList (uList, GL_COMPILE);
1165
1166 compileOneObject (obj);
1167
1168 glEndList ();
1169 obj->uGLList = uList;
1170 }
1171
1172 // =============================================================================
1173 uchar* GLRenderer::screencap (ushort& w, ushort& h) {
1174 w = m_width;
1175 h = m_height;
1176 uchar* cap = new uchar[4 * w * h];
1177
1178 m_screencap = true;
1179 update ();
1180 m_screencap = false;
1181
1182 // Capture the pixels
1183 glReadPixels (0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, cap);
1184
1185 // Restore the background
1186 setBackground ();
1187
1188 return cap;
1189 }
1190
1191 // =============================================================================
1192 void GLRenderer::slot_timerUpdate () {
1193 ++g_pulseTick %= g_numPulseTicks;
1194
1195 for (LDObject* obj : g_win->sel ())
1196 recompileObject (obj);
1197
1198 update ();
1199 }
1200
1201 // =============================================================================
1202 void GLRenderer::slot_toolTipTimer () {
1203 // We come here if the cursor has stayed in one place for longer than a
1204 // a second. Check if we're holding it over a camera icon - if so, draw
1205 // a tooltip.
1206 for (CameraIcon& icon : g_CameraIcons) {
1207 if (icon.destRect.contains (m_pos)) {
1208 m_toolTipCamera = icon.cam;
1209 m_drawToolTip = true;
1210 update ();
1211 break;
1212 }
1213 }
1214 }

mercurial