|
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 } |