1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Data Visualization module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include "abstract3drenderer_p.h"
31#include "texturehelper_p.h"
32#include "q3dcamera_p.h"
33#include "q3dtheme_p.h"
34#include "qvalue3daxisformatter_p.h"
35#include "shaderhelper_p.h"
36#include "qcustom3ditem_p.h"
37#include "qcustom3dlabel_p.h"
38#include "qcustom3dvolume_p.h"
39#include "scatter3drenderer_p.h"
40
41#include <QtCore/qmath.h>
42#include <QtGui/QOffscreenSurface>
43#include <QtCore/QThread>
44
45QT_BEGIN_NAMESPACE_DATAVISUALIZATION
46
47// Defined in shaderhelper.cpp
48extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg);
49
50const qreal doublePi(M_PI * 2.0);
51const int polarGridRoundness(64);
52const qreal polarGridAngle(doublePi / qreal(polarGridRoundness));
53const float polarGridAngleDegrees(float(360.0 / qreal(polarGridRoundness)));
54const qreal polarGridHalfAngle(polarGridAngle / 2.0);
55
56Abstract3DRenderer::Abstract3DRenderer(Abstract3DController *controller)
57 : QObject(0),
58 m_hasNegativeValues(false),
59 m_cachedTheme(new Q3DTheme()),
60 m_drawer(new Drawer(m_cachedTheme)),
61 m_cachedShadowQuality(QAbstract3DGraph::ShadowQualityMedium),
62 m_autoScaleAdjustment(1.0f),
63 m_cachedSelectionMode(QAbstract3DGraph::SelectionNone),
64 m_cachedOptimizationHint(QAbstract3DGraph::OptimizationDefault),
65 m_textureHelper(0),
66 m_depthTexture(0),
67 m_cachedScene(new Q3DScene()),
68 m_selectionDirty(true),
69 m_selectionState(SelectNone),
70 m_devicePixelRatio(1.0f),
71 m_selectionLabelDirty(true),
72 m_clickResolved(false),
73 m_graphPositionQueryPending(false),
74 m_graphPositionQueryResolved(false),
75 m_clickedSeries(0),
76 m_clickedType(QAbstract3DGraph::ElementNone),
77 m_selectedLabelIndex(-1),
78 m_selectedCustomItemIndex(-1),
79 m_selectionLabelItem(0),
80 m_visibleSeriesCount(0),
81 m_customItemShader(0),
82 m_volumeTextureShader(0),
83 m_volumeTextureLowDefShader(0),
84 m_volumeTextureSliceShader(0),
85 m_volumeSliceFrameShader(0),
86 m_labelShader(0),
87 m_cursorPositionShader(0),
88 m_cursorPositionFrameBuffer(0),
89 m_cursorPositionTexture(0),
90 m_useOrthoProjection(false),
91 m_xFlipped(false),
92 m_yFlipped(false),
93 m_zFlipped(false),
94 m_yFlippedForGrid(false),
95 m_backgroundObj(0),
96 m_gridLineObj(0),
97 m_labelObj(0),
98 m_positionMapperObj(0),
99 m_graphAspectRatio(2.0f),
100 m_graphHorizontalAspectRatio(0.0f),
101 m_polarGraph(false),
102 m_radialLabelOffset(1.0f),
103 m_polarRadius(2.0f),
104 m_xRightAngleRotation(QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: 90.0f)),
105 m_yRightAngleRotation(QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: 90.0f)),
106 m_zRightAngleRotation(QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: 90.0f)),
107 m_xRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: -90.0f)),
108 m_yRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: -90.0f)),
109 m_zRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: -90.0f)),
110 m_xFlipRotation(QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: -180.0f)),
111 m_zFlipRotation(QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: -180.0f)),
112 m_requestedMargin(-1.0f),
113 m_vBackgroundMargin(0.1f),
114 m_hBackgroundMargin(0.1f),
115 m_scaleXWithBackground(0.0f),
116 m_scaleYWithBackground(0.0f),
117 m_scaleZWithBackground(0.0f),
118 m_oldCameraTarget(QVector3D(2000.0f, 2000.0f, 2000.0f)), // Just random invalid target
119 m_reflectionEnabled(false),
120 m_reflectivity(0.5),
121#if !defined(QT_OPENGL_ES_2)
122 m_funcs_2_1(0),
123#endif
124 m_context(0),
125 m_isOpenGLES(true)
126
127{
128 initializeOpenGLFunctions();
129 m_isOpenGLES = Utils::isOpenGLES();
130#if !defined(QT_OPENGL_ES_2)
131 if (!m_isOpenGLES) {
132 // Discard warnings about deprecated functions
133 QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs);
134
135 m_funcs_2_1 = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_1>();
136 if (m_funcs_2_1)
137 m_funcs_2_1->initializeOpenGLFunctions();
138
139 // Restore original message handler
140 qInstallMessageHandler(handler);
141
142 if (!m_funcs_2_1)
143 qFatal(msg: "OpenGL version is too low, at least 2.1 is required");
144 }
145#endif
146 QObject::connect(sender: m_drawer, signal: &Drawer::drawerChanged, receiver: this, slot: &Abstract3DRenderer::updateTextures);
147 QObject::connect(sender: this, signal: &Abstract3DRenderer::needRender, receiver: controller,
148 slot: &Abstract3DController::needRender, type: Qt::QueuedConnection);
149 QObject::connect(sender: this, signal: &Abstract3DRenderer::requestShadowQuality, receiver: controller,
150 slot: &Abstract3DController::handleRequestShadowQuality, type: Qt::QueuedConnection);
151}
152
153Abstract3DRenderer::~Abstract3DRenderer()
154{
155 contextCleanup();
156 delete m_drawer;
157 delete m_cachedScene;
158 delete m_cachedTheme;
159 delete m_selectionLabelItem;
160 delete m_customItemShader;
161 delete m_volumeTextureShader;
162 delete m_volumeTextureLowDefShader;
163 delete m_volumeSliceFrameShader;
164 delete m_volumeTextureSliceShader;
165 delete m_labelShader;
166 delete m_cursorPositionShader;
167
168 foreach (SeriesRenderCache *cache, m_renderCacheList) {
169 cache->cleanup(texHelper: m_textureHelper);
170 delete cache;
171 }
172 m_renderCacheList.clear();
173
174 foreach (CustomRenderItem *item, m_customRenderCache) {
175 GLuint texture = item->texture();
176 m_textureHelper->deleteTexture(texture: &texture);
177 delete item;
178 }
179 m_customRenderCache.clear();
180
181 ObjectHelper::releaseObjectHelper(cacheId: this, obj&: m_backgroundObj);
182 ObjectHelper::releaseObjectHelper(cacheId: this, obj&: m_gridLineObj);
183 ObjectHelper::releaseObjectHelper(cacheId: this, obj&: m_labelObj);
184 ObjectHelper::releaseObjectHelper(cacheId: this, obj&: m_positionMapperObj);
185
186 if (m_textureHelper) {
187 m_textureHelper->deleteTexture(texture: &m_depthTexture);
188 m_textureHelper->deleteTexture(texture: &m_cursorPositionTexture);
189 delete m_textureHelper;
190 }
191
192 m_axisCacheX.clearLabels();
193 m_axisCacheY.clearLabels();
194 m_axisCacheZ.clearLabels();
195}
196
197void Abstract3DRenderer::contextCleanup()
198{
199 if (QOpenGLContext::currentContext())
200 m_textureHelper->glDeleteFramebuffers(n: 1, framebuffers: &m_cursorPositionFrameBuffer);
201}
202
203void Abstract3DRenderer::initializeOpenGL()
204{
205 m_context = QOpenGLContext::currentContext();
206
207 // Set OpenGL features
208 glEnable(GL_DEPTH_TEST);
209 glDepthFunc(GL_LESS);
210 glEnable(GL_CULL_FACE);
211 glCullFace(GL_BACK);
212
213#if !defined(QT_OPENGL_ES_2)
214 if (!m_isOpenGLES) {
215 glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
216 glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
217 glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
218 }
219#endif
220
221 m_textureHelper = new TextureHelper();
222 m_drawer->initializeOpenGL();
223
224 axisCacheForOrientation(orientation: QAbstract3DAxis::AxisOrientationX).setDrawer(m_drawer);
225 axisCacheForOrientation(orientation: QAbstract3DAxis::AxisOrientationY).setDrawer(m_drawer);
226 axisCacheForOrientation(orientation: QAbstract3DAxis::AxisOrientationZ).setDrawer(m_drawer);
227
228 initLabelShaders(QStringLiteral(":/shaders/vertexLabel"),
229 QStringLiteral(":/shaders/fragmentLabel"));
230
231 initCursorPositionShaders(QStringLiteral(":/shaders/vertexPosition"),
232 QStringLiteral(":/shaders/fragmentPositionMap"));
233
234 loadLabelMesh();
235 loadPositionMapperMesh();
236
237 QObject::connect(sender: m_context.data(), signal: &QOpenGLContext::aboutToBeDestroyed,
238 receiver: this, slot: &Abstract3DRenderer::contextCleanup);
239}
240
241void Abstract3DRenderer::render(const GLuint defaultFboHandle)
242{
243 if (defaultFboHandle) {
244 glDepthMask(flag: true);
245 glEnable(GL_DEPTH_TEST);
246 glDepthFunc(GL_LESS);
247 glEnable(GL_CULL_FACE);
248 glCullFace(GL_BACK);
249 glDisable(GL_BLEND); // For QtQuick2 blending is enabled by default, but we don't want it to be
250 }
251
252 // Clear the graph background to the theme color
253 glViewport(x: m_viewport.x(),
254 y: m_viewport.y(),
255 width: m_viewport.width(),
256 height: m_viewport.height());
257 glScissor(x: m_viewport.x(),
258 y: m_viewport.y(),
259 width: m_viewport.width(),
260 height: m_viewport.height());
261 glEnable(GL_SCISSOR_TEST);
262 QVector4D clearColor = Utils::vectorFromColor(color: m_cachedTheme->windowColor());
263 glClearColor(red: clearColor.x(), green: clearColor.y(), blue: clearColor.z(), alpha: 1.0f);
264 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
265 glDisable(GL_SCISSOR_TEST);
266}
267
268void Abstract3DRenderer::updateSelectionState(SelectionState state)
269{
270 m_selectionState = state;
271}
272
273void Abstract3DRenderer::initGradientShaders(const QString &vertexShader,
274 const QString &fragmentShader)
275{
276 // Do nothing by default
277 Q_UNUSED(vertexShader)
278 Q_UNUSED(fragmentShader)
279}
280
281void Abstract3DRenderer::initStaticSelectedItemShaders(const QString &vertexShader,
282 const QString &fragmentShader,
283 const QString &gradientVertexShader,
284 const QString &gradientFragmentShader)
285{
286 // Do nothing by default
287 Q_UNUSED(vertexShader)
288 Q_UNUSED(fragmentShader)
289 Q_UNUSED(gradientVertexShader)
290 Q_UNUSED(gradientFragmentShader)
291}
292
293void Abstract3DRenderer::initCustomItemShaders(const QString &vertexShader,
294 const QString &fragmentShader)
295{
296 delete m_customItemShader;
297 m_customItemShader = new ShaderHelper(this, vertexShader, fragmentShader);
298 m_customItemShader->initialize();
299}
300
301void Abstract3DRenderer::initVolumeTextureShaders(const QString &vertexShader,
302 const QString &fragmentShader,
303 const QString &fragmentLowDefShader,
304 const QString &sliceShader,
305 const QString &sliceFrameVertexShader,
306 const QString &sliceFrameShader)
307{
308
309 delete m_volumeTextureShader;
310 m_volumeTextureShader = new ShaderHelper(this, vertexShader, fragmentShader);
311 m_volumeTextureShader->initialize();
312
313 delete m_volumeTextureLowDefShader;
314 m_volumeTextureLowDefShader = new ShaderHelper(this, vertexShader, fragmentLowDefShader);
315 m_volumeTextureLowDefShader->initialize();
316
317 delete m_volumeTextureSliceShader;
318 m_volumeTextureSliceShader = new ShaderHelper(this, vertexShader, sliceShader);
319 m_volumeTextureSliceShader->initialize();
320
321 delete m_volumeSliceFrameShader;
322 m_volumeSliceFrameShader = new ShaderHelper(this, sliceFrameVertexShader, sliceFrameShader);
323 m_volumeSliceFrameShader->initialize();
324}
325
326void Abstract3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader)
327{
328 delete m_labelShader;
329 m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader);
330 m_labelShader->initialize();
331}
332
333void Abstract3DRenderer::initCursorPositionShaders(const QString &vertexShader,
334 const QString &fragmentShader)
335{
336 // Init the shader
337 delete m_cursorPositionShader;
338 m_cursorPositionShader = new ShaderHelper(this, vertexShader, fragmentShader);
339 m_cursorPositionShader->initialize();
340}
341
342void Abstract3DRenderer::initCursorPositionBuffer()
343{
344 m_textureHelper->deleteTexture(texture: &m_cursorPositionTexture);
345 m_textureHelper->glDeleteFramebuffers(n: 1, framebuffers: &m_cursorPositionFrameBuffer);
346 m_cursorPositionFrameBuffer = 0;
347
348 if (m_primarySubViewport.size().isEmpty())
349 return;
350
351 m_cursorPositionTexture =
352 m_textureHelper->createCursorPositionTexture(size: m_primarySubViewport.size(),
353 frameBuffer&: m_cursorPositionFrameBuffer);
354}
355
356void Abstract3DRenderer::updateTheme(Q3DTheme *theme)
357{
358 // Synchronize the controller theme with renderer
359 bool updateDrawer = theme->d_ptr->sync(other&: *m_cachedTheme->d_ptr);
360
361 if (updateDrawer)
362 m_drawer->setTheme(m_cachedTheme);
363}
364
365void Abstract3DRenderer::updateScene(Q3DScene *scene)
366{
367 m_viewport = scene->d_ptr->glViewport();
368 m_secondarySubViewport = scene->d_ptr->glSecondarySubViewport();
369
370 if (m_primarySubViewport != scene->d_ptr->glPrimarySubViewport()) {
371 // Resize of primary subviewport means resizing shadow and selection buffers
372 m_primarySubViewport = scene->d_ptr->glPrimarySubViewport();
373 handleResize();
374 }
375
376 if (m_devicePixelRatio != scene->devicePixelRatio()) {
377 m_devicePixelRatio = scene->devicePixelRatio();
378 handleResize();
379 }
380
381 QPoint logicalPixelPosition = scene->selectionQueryPosition();
382 m_inputPosition = QPoint(logicalPixelPosition.x() * m_devicePixelRatio,
383 logicalPixelPosition.y() * m_devicePixelRatio);
384
385 QPoint logicalGraphPosition = scene->graphPositionQuery();
386 m_graphPositionQuery = QPoint(logicalGraphPosition.x() * m_devicePixelRatio,
387 logicalGraphPosition.y() * m_devicePixelRatio);
388
389 // Synchronize the renderer scene to controller scene
390 scene->d_ptr->sync(other&: *m_cachedScene->d_ptr);
391
392 updateCameraViewport();
393
394 if (Q3DScene::invalidSelectionPoint() == logicalPixelPosition) {
395 updateSelectionState(state: SelectNone);
396 } else {
397 if (scene->isSlicingActive()) {
398 if (scene->isPointInPrimarySubView(point: logicalPixelPosition))
399 updateSelectionState(state: SelectOnOverview);
400 else if (scene->isPointInSecondarySubView(point: logicalPixelPosition))
401 updateSelectionState(state: SelectOnSlice);
402 else
403 updateSelectionState(state: SelectNone);
404 } else {
405 updateSelectionState(state: SelectOnScene);
406 }
407 }
408
409 if (Q3DScene::invalidSelectionPoint() != logicalGraphPosition)
410 m_graphPositionQueryPending = true;
411
412 // Queue up another render when we have a query that needs resolving.
413 // This is needed because QtQuick scene graph can sometimes do a sync without following it up
414 // with a render.
415 if (m_graphPositionQueryPending || m_selectionState != SelectNone)
416 emit needRender();
417}
418
419void Abstract3DRenderer::updateTextures()
420{
421 m_axisCacheX.updateTextures();
422 m_axisCacheY.updateTextures();
423 m_axisCacheZ.updateTextures();
424}
425
426void Abstract3DRenderer::reInitShaders()
427{
428 if (!m_isOpenGLES) {
429 if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
430 if (m_cachedOptimizationHint.testFlag(flag: QAbstract3DGraph::OptimizationStatic)
431 && qobject_cast<Scatter3DRenderer *>(object: this)) {
432 initGradientShaders(QStringLiteral(":/shaders/vertexShadow"),
433 QStringLiteral(":/shaders/fragmentShadow"));
434 initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertexShadow"),
435 QStringLiteral(":/shaders/fragmentShadowNoTex"),
436 QStringLiteral(":/shaders/vertexShadow"),
437 QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY"));
438 initShaders(QStringLiteral(":/shaders/vertexShadowNoMatrices"),
439 QStringLiteral(":/shaders/fragmentShadowNoTex"));
440 } else {
441 initGradientShaders(QStringLiteral(":/shaders/vertexShadow"),
442 QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY"));
443 initShaders(QStringLiteral(":/shaders/vertexShadow"),
444 QStringLiteral(":/shaders/fragmentShadowNoTex"));
445 }
446 initBackgroundShaders(QStringLiteral(":/shaders/vertexShadow"),
447 QStringLiteral(":/shaders/fragmentShadowNoTex"));
448 initCustomItemShaders(QStringLiteral(":/shaders/vertexShadow"),
449 QStringLiteral(":/shaders/fragmentShadow"));
450 } else {
451 if (m_cachedOptimizationHint.testFlag(flag: QAbstract3DGraph::OptimizationStatic)
452 && qobject_cast<Scatter3DRenderer *>(object: this)) {
453 initGradientShaders(QStringLiteral(":/shaders/vertexTexture"),
454 QStringLiteral(":/shaders/fragmentTexture"));
455 initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertex"),
456 QStringLiteral(":/shaders/fragment"),
457 QStringLiteral(":/shaders/vertex"),
458 QStringLiteral(":/shaders/fragmentColorOnY"));
459 initShaders(QStringLiteral(":/shaders/vertexNoMatrices"),
460 QStringLiteral(":/shaders/fragment"));
461 } else {
462 initGradientShaders(QStringLiteral(":/shaders/vertex"),
463 QStringLiteral(":/shaders/fragmentColorOnY"));
464 initShaders(QStringLiteral(":/shaders/vertex"),
465 QStringLiteral(":/shaders/fragment"));
466 }
467 initBackgroundShaders(QStringLiteral(":/shaders/vertex"),
468 QStringLiteral(":/shaders/fragment"));
469 initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"),
470 QStringLiteral(":/shaders/fragmentTexture"));
471 }
472 initVolumeTextureShaders(QStringLiteral(":/shaders/vertexTexture3D"),
473 QStringLiteral(":/shaders/fragmentTexture3D"),
474 QStringLiteral(":/shaders/fragmentTexture3DLowDef"),
475 QStringLiteral(":/shaders/fragmentTexture3DSlice"),
476 QStringLiteral(":/shaders/vertexPosition"),
477 QStringLiteral(":/shaders/fragment3DSliceFrames"));
478 } else {
479 if (m_cachedOptimizationHint.testFlag(flag: QAbstract3DGraph::OptimizationStatic)
480 && qobject_cast<Scatter3DRenderer *>(object: this)) {
481 initGradientShaders(QStringLiteral(":/shaders/vertexTexture"),
482 QStringLiteral(":/shaders/fragmentTextureES2"));
483 initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertex"),
484 QStringLiteral(":/shaders/fragmentES2"),
485 QStringLiteral(":/shaders/vertex"),
486 QStringLiteral(":/shaders/fragmentColorOnYES2"));
487 initShaders(QStringLiteral(":/shaders/vertexNoMatrices"),
488 QStringLiteral(":/shaders/fragmentES2"));
489 } else {
490 initGradientShaders(QStringLiteral(":/shaders/vertex"),
491 QStringLiteral(":/shaders/fragmentColorOnYES2"));
492 initShaders(QStringLiteral(":/shaders/vertex"),
493 QStringLiteral(":/shaders/fragmentES2"));
494 }
495 initBackgroundShaders(QStringLiteral(":/shaders/vertex"),
496 QStringLiteral(":/shaders/fragmentES2"));
497 initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"),
498 QStringLiteral(":/shaders/fragmentTextureES2"));
499 }
500}
501
502void Abstract3DRenderer::handleShadowQualityChange()
503{
504 reInitShaders();
505
506 if (m_cachedScene->activeLight()->isAutoPosition()
507 || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
508 m_cachedScene->d_ptr->setLightPositionRelativeToCamera(relativePosition: defaultLightPos);
509 emit needRender();
510 }
511 if (m_isOpenGLES && m_cachedShadowQuality != QAbstract3DGraph::ShadowQualityNone) {
512 emit requestShadowQuality(quality: QAbstract3DGraph::ShadowQualityNone);
513 qWarning(msg: "Shadows are not yet supported for OpenGL ES2");
514 m_cachedShadowQuality = QAbstract3DGraph::ShadowQualityNone;
515 }
516}
517
518void Abstract3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode)
519{
520 m_cachedSelectionMode = mode;
521 m_selectionDirty = true;
522}
523
524void Abstract3DRenderer::updateAspectRatio(float ratio)
525{
526 m_graphAspectRatio = ratio;
527 foreach (SeriesRenderCache *cache, m_renderCacheList)
528 cache->setDataDirty(true);
529}
530
531void Abstract3DRenderer::updateHorizontalAspectRatio(float ratio)
532{
533 m_graphHorizontalAspectRatio = ratio;
534 foreach (SeriesRenderCache *cache, m_renderCacheList)
535 cache->setDataDirty(true);
536}
537
538void Abstract3DRenderer::updatePolar(bool enable)
539{
540 m_polarGraph = enable;
541 foreach (SeriesRenderCache *cache, m_renderCacheList)
542 cache->setDataDirty(true);
543}
544
545void Abstract3DRenderer::updateRadialLabelOffset(float offset)
546{
547 m_radialLabelOffset = offset;
548}
549
550void Abstract3DRenderer::updateMargin(float margin)
551{
552 m_requestedMargin = margin;
553}
554
555void Abstract3DRenderer::updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint)
556{
557 m_cachedOptimizationHint = hint;
558 foreach (SeriesRenderCache *cache, m_renderCacheList)
559 cache->setDataDirty(true);
560}
561
562void Abstract3DRenderer::handleResize()
563{
564 if (m_primarySubViewport.width() == 0 || m_primarySubViewport.height() == 0)
565 return;
566
567 // Recalculate zoom
568 calculateZoomLevel();
569
570 // Re-init selection buffer
571 initSelectionBuffer();
572
573 // Re-init depth buffer
574 updateDepthBuffer();
575
576 initCursorPositionBuffer();
577}
578
579void Abstract3DRenderer::calculateZoomLevel()
580{
581 // Calculate zoom level based on aspect ratio
582 GLfloat div;
583 GLfloat zoomAdjustment;
584 div = qMin(a: m_primarySubViewport.width(), b: m_primarySubViewport.height());
585 zoomAdjustment = defaultRatio
586 * ((m_primarySubViewport.width() / div)
587 / (m_primarySubViewport.height() / div));
588 m_autoScaleAdjustment = qMin(a: zoomAdjustment, b: 1.0f); // clamp to 1.0f
589}
590
591void Abstract3DRenderer::updateAxisType(QAbstract3DAxis::AxisOrientation orientation,
592 QAbstract3DAxis::AxisType type)
593{
594 axisCacheForOrientation(orientation).setType(type);
595}
596
597void Abstract3DRenderer::updateAxisTitle(QAbstract3DAxis::AxisOrientation orientation,
598 const QString &title)
599{
600 axisCacheForOrientation(orientation).setTitle(title);
601}
602
603void Abstract3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation,
604 const QStringList &labels)
605{
606 axisCacheForOrientation(orientation).setLabels(labels);
607}
608
609void Abstract3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orientation,
610 float min, float max)
611{
612 AxisRenderCache &cache = axisCacheForOrientation(orientation);
613 cache.setMin(min);
614 cache.setMax(max);
615
616 foreach (SeriesRenderCache *cache, m_renderCacheList)
617 cache->setDataDirty(true);
618}
619
620void Abstract3DRenderer::updateAxisSegmentCount(QAbstract3DAxis::AxisOrientation orientation,
621 int count)
622{
623 AxisRenderCache &cache = axisCacheForOrientation(orientation);
624 cache.setSegmentCount(count);
625}
626
627void Abstract3DRenderer::updateAxisSubSegmentCount(QAbstract3DAxis::AxisOrientation orientation,
628 int count)
629{
630 AxisRenderCache &cache = axisCacheForOrientation(orientation);
631 cache.setSubSegmentCount(count);
632}
633
634void Abstract3DRenderer::updateAxisLabelFormat(QAbstract3DAxis::AxisOrientation orientation,
635 const QString &format)
636{
637 axisCacheForOrientation(orientation).setLabelFormat(format);
638}
639
640void Abstract3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation orientation,
641 bool enable)
642{
643 axisCacheForOrientation(orientation).setReversed(enable);
644 foreach (SeriesRenderCache *cache, m_renderCacheList)
645 cache->setDataDirty(true);
646}
647
648void Abstract3DRenderer::updateAxisFormatter(QAbstract3DAxis::AxisOrientation orientation,
649 QValue3DAxisFormatter *formatter)
650{
651 AxisRenderCache &cache = axisCacheForOrientation(orientation);
652 if (cache.ctrlFormatter() != formatter) {
653 delete cache.formatter();
654 cache.setFormatter(formatter->createNewInstance());
655 cache.setCtrlFormatter(formatter);
656 }
657 formatter->d_ptr->populateCopy(copy&: *(cache.formatter()));
658 cache.markPositionsDirty();
659
660 foreach (SeriesRenderCache *cache, m_renderCacheList)
661 cache->setDataDirty(true);
662}
663
664void Abstract3DRenderer::updateAxisLabelAutoRotation(QAbstract3DAxis::AxisOrientation orientation,
665 float angle)
666{
667 AxisRenderCache &cache = axisCacheForOrientation(orientation);
668 if (cache.labelAutoRotation() != angle)
669 cache.setLabelAutoRotation(angle);
670}
671
672void Abstract3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation,
673 bool visible)
674{
675 AxisRenderCache &cache = axisCacheForOrientation(orientation);
676 if (cache.isTitleVisible() != visible)
677 cache.setTitleVisible(visible);
678}
679
680void Abstract3DRenderer::updateAxisTitleFixed(QAbstract3DAxis::AxisOrientation orientation,
681 bool fixed)
682{
683 AxisRenderCache &cache = axisCacheForOrientation(orientation);
684 if (cache.isTitleFixed() != fixed)
685 cache.setTitleFixed(fixed);
686}
687
688void Abstract3DRenderer::modifiedSeriesList(const QVector<QAbstract3DSeries *> &seriesList)
689{
690 foreach (QAbstract3DSeries *series, seriesList) {
691 SeriesRenderCache *cache = m_renderCacheList.value(akey: series, adefaultValue: 0);
692 if (cache)
693 cache->setDataDirty(true);
694 }
695}
696
697void Abstract3DRenderer::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh)
698{
699 // Default implementation does nothing.
700 Q_UNUSED(fileName)
701 Q_UNUSED(mesh)
702}
703
704void Abstract3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList)
705{
706 foreach (SeriesRenderCache *cache, m_renderCacheList)
707 cache->setValid(false);
708
709 m_visibleSeriesCount = 0;
710 int seriesCount = seriesList.size();
711 for (int i = 0; i < seriesCount; i++) {
712 QAbstract3DSeries *series = seriesList.at(i);
713 SeriesRenderCache *cache = m_renderCacheList.value(akey: series);
714 bool newSeries = false;
715 if (!cache) {
716 cache = createNewCache(series);
717 m_renderCacheList[series] = cache;
718 newSeries = true;
719 }
720 cache->setValid(true);
721 cache->populate(newSeries);
722 if (cache->isVisible())
723 m_visibleSeriesCount++;
724 }
725
726 // Remove non-valid objects from the cache list
727 foreach (SeriesRenderCache *cache, m_renderCacheList) {
728 if (!cache->isValid())
729 cleanCache(cache);
730 }
731}
732
733void Abstract3DRenderer::updateCustomData(const QList<QCustom3DItem *> &customItems)
734{
735 if (customItems.isEmpty() && m_customRenderCache.isEmpty())
736 return;
737
738 foreach (CustomRenderItem *item, m_customRenderCache)
739 item->setValid(false);
740
741 int itemCount = customItems.size();
742 // Check custom item list for items that are not yet in render item cache
743 for (int i = 0; i < itemCount; i++) {
744 QCustom3DItem *item = customItems.at(i);
745 CustomRenderItem *renderItem = m_customRenderCache.value(akey: item);
746 if (!renderItem)
747 renderItem = addCustomItem(item);
748 renderItem->setValid(true);
749 renderItem->setIndex(i); // always update index, as it must match the custom item index
750 }
751
752 // Check render item cache and remove items that are not in customItems list anymore
753 foreach (CustomRenderItem *renderItem, m_customRenderCache) {
754 if (!renderItem->isValid()) {
755 m_customRenderCache.remove(akey: renderItem->itemPointer());
756 GLuint texture = renderItem->texture();
757 m_textureHelper->deleteTexture(texture: &texture);
758 delete renderItem;
759 }
760 }
761
762 m_customItemDrawOrder.clear();
763 m_customItemDrawOrder = QList<QCustom3DItem *>(customItems);
764}
765
766void Abstract3DRenderer::updateCustomItems()
767{
768 // Check all items
769 foreach (CustomRenderItem *item, m_customRenderCache)
770 updateCustomItem(renderItem: item);
771}
772
773SeriesRenderCache *Abstract3DRenderer::createNewCache(QAbstract3DSeries *series)
774{
775 return new SeriesRenderCache(series, this);
776}
777
778void Abstract3DRenderer::cleanCache(SeriesRenderCache *cache)
779{
780 m_renderCacheList.remove(akey: cache->series());
781 cache->cleanup(texHelper: m_textureHelper);
782 delete cache;
783}
784
785AxisRenderCache &Abstract3DRenderer::axisCacheForOrientation(
786 QAbstract3DAxis::AxisOrientation orientation)
787{
788 switch (orientation) {
789 case QAbstract3DAxis::AxisOrientationX:
790 return m_axisCacheX;
791 case QAbstract3DAxis::AxisOrientationY:
792 return m_axisCacheY;
793 case QAbstract3DAxis::AxisOrientationZ:
794 return m_axisCacheZ;
795 default:
796 qFatal(msg: "Abstract3DRenderer::axisCacheForOrientation");
797 return m_axisCacheX;
798 }
799}
800
801void Abstract3DRenderer::lowerShadowQuality()
802{
803 QAbstract3DGraph::ShadowQuality newQuality = QAbstract3DGraph::ShadowQualityNone;
804
805 switch (m_cachedShadowQuality) {
806 case QAbstract3DGraph::ShadowQualityHigh:
807 qWarning(msg: "Creating high quality shadows failed. Changing to medium quality.");
808 newQuality = QAbstract3DGraph::ShadowQualityMedium;
809 break;
810 case QAbstract3DGraph::ShadowQualityMedium:
811 qWarning(msg: "Creating medium quality shadows failed. Changing to low quality.");
812 newQuality = QAbstract3DGraph::ShadowQualityLow;
813 break;
814 case QAbstract3DGraph::ShadowQualityLow:
815 qWarning(msg: "Creating low quality shadows failed. Switching shadows off.");
816 newQuality = QAbstract3DGraph::ShadowQualityNone;
817 break;
818 case QAbstract3DGraph::ShadowQualitySoftHigh:
819 qWarning(msg: "Creating soft high quality shadows failed. Changing to soft medium quality.");
820 newQuality = QAbstract3DGraph::ShadowQualitySoftMedium;
821 break;
822 case QAbstract3DGraph::ShadowQualitySoftMedium:
823 qWarning(msg: "Creating soft medium quality shadows failed. Changing to soft low quality.");
824 newQuality = QAbstract3DGraph::ShadowQualitySoftLow;
825 break;
826 case QAbstract3DGraph::ShadowQualitySoftLow:
827 qWarning(msg: "Creating soft low quality shadows failed. Switching shadows off.");
828 newQuality = QAbstract3DGraph::ShadowQualityNone;
829 break;
830 default:
831 // You'll never get here
832 break;
833 }
834
835 emit requestShadowQuality(quality: newQuality);
836 updateShadowQuality(quality: newQuality);
837}
838
839void Abstract3DRenderer::drawAxisTitleY(const QVector3D &sideLabelRotation,
840 const QVector3D &backLabelRotation,
841 const QVector3D &sideLabelTrans,
842 const QVector3D &backLabelTrans,
843 const QQuaternion &totalSideRotation,
844 const QQuaternion &totalBackRotation,
845 AbstractRenderItem &dummyItem,
846 const Q3DCamera *activeCamera,
847 float labelsMaxWidth,
848 const QMatrix4x4 &viewMatrix,
849 const QMatrix4x4 &projectionMatrix,
850 ShaderHelper *shader)
851{
852 float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheY.titleItem().size().height();
853 float titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor));
854 float yRotation;
855 QVector3D titleTrans;
856 QQuaternion totalRotation;
857 if (m_xFlipped == m_zFlipped) {
858 yRotation = backLabelRotation.y();
859 titleTrans = backLabelTrans;
860 totalRotation = totalBackRotation;
861 } else {
862 yRotation = sideLabelRotation.y();
863 titleTrans = sideLabelTrans;
864 totalRotation = totalSideRotation;
865 }
866
867 QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation);
868 QVector3D titleOffsetVector =
869 offsetRotator.rotatedVector(vector: QVector3D(-titleOffset, 0.0f, 0.0f));
870
871 QQuaternion titleRotation;
872 if (m_axisCacheY.isTitleFixed()) {
873 titleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation)
874 * m_zRightAngleRotation;
875 } else {
876 titleRotation = totalRotation * m_zRightAngleRotation;
877 }
878 dummyItem.setTranslation(titleTrans + titleOffsetVector);
879
880 m_drawer->drawLabel(item: dummyItem, labelItem: m_axisCacheY.titleItem(), viewmatrix: viewMatrix,
881 projectionmatrix: projectionMatrix, positionComp: zeroVector, rotation: titleRotation, itemHeight: 0,
882 mode: m_cachedSelectionMode, shader, object: m_labelObj, camera: activeCamera,
883 useDepth: true, rotateAlong: true, position: Drawer::LabelMid, alignment: Qt::AlignBottom);
884}
885
886void Abstract3DRenderer::drawAxisTitleX(const QVector3D &labelRotation,
887 const QVector3D &labelTrans,
888 const QQuaternion &totalRotation,
889 AbstractRenderItem &dummyItem,
890 const Q3DCamera *activeCamera,
891 float labelsMaxWidth,
892 const QMatrix4x4 &viewMatrix,
893 const QMatrix4x4 &projectionMatrix,
894 ShaderHelper *shader,
895 bool radial)
896{
897 float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheX.titleItem().size().height();
898 float titleOffset;
899 if (radial)
900 titleOffset = -2.0f * (labelMargin + m_drawer->scaledFontSize());
901 else
902 titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor));
903 float zRotation = 0.0f;
904 float yRotation = 0.0f;
905 float xRotation = -90.0f + labelRotation.z();
906 float offsetRotation = labelRotation.z();
907 float extraRotation = -90.0f;
908 Qt::AlignmentFlag alignment = Qt::AlignTop;
909 if (m_yFlippedForGrid) {
910 alignment = Qt::AlignBottom;
911 zRotation = 180.0f;
912 if (m_zFlipped) {
913 titleOffset = -titleOffset;
914 if (m_xFlipped) {
915 offsetRotation = -offsetRotation;
916 extraRotation = -extraRotation;
917 } else {
918 xRotation = -90.0f - labelRotation.z();
919 }
920 } else {
921 yRotation = 180.0f;
922 if (m_xFlipped) {
923 offsetRotation = -offsetRotation;
924 xRotation = -90.0f - labelRotation.z();
925 } else {
926 extraRotation = -extraRotation;
927 }
928 }
929 } else {
930 if (m_zFlipped) {
931 titleOffset = -titleOffset;
932 if (m_xFlipped) {
933 yRotation = 180.0f;
934 offsetRotation = -offsetRotation;
935 } else {
936 yRotation = 180.0f;
937 xRotation = -90.0f - labelRotation.z();
938 extraRotation = -extraRotation;
939 }
940 } else {
941 if (m_xFlipped) {
942 offsetRotation = -offsetRotation;
943 xRotation = -90.0f - labelRotation.z();
944 extraRotation = -extraRotation;
945 }
946 }
947 }
948
949 if (radial) {
950 if (m_zFlipped) {
951 titleOffset = -titleOffset;
952 } else {
953 if (m_yFlippedForGrid)
954 alignment = Qt::AlignTop;
955 else
956 alignment = Qt::AlignBottom;
957 }
958 }
959
960 if (offsetRotation == 180.0f || offsetRotation == -180.0f)
961 offsetRotation = 0.0f;
962 QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: offsetRotation);
963 QVector3D titleOffsetVector =
964 offsetRotator.rotatedVector(vector: QVector3D(0.0f, 0.0f, titleOffset));
965
966 QQuaternion titleRotation;
967 if (m_axisCacheX.isTitleFixed()) {
968 titleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: zRotation)
969 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation)
970 * QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: xRotation);
971 } else {
972 titleRotation = totalRotation
973 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: extraRotation);
974 }
975 dummyItem.setTranslation(labelTrans + titleOffsetVector);
976
977 m_drawer->drawLabel(item: dummyItem, labelItem: m_axisCacheX.titleItem(), viewmatrix: viewMatrix,
978 projectionmatrix: projectionMatrix, positionComp: zeroVector, rotation: titleRotation, itemHeight: 0,
979 mode: m_cachedSelectionMode, shader, object: m_labelObj, camera: activeCamera,
980 useDepth: true, rotateAlong: true, position: Drawer::LabelMid, alignment);
981}
982
983void Abstract3DRenderer::drawAxisTitleZ(const QVector3D &labelRotation,
984 const QVector3D &labelTrans,
985 const QQuaternion &totalRotation,
986 AbstractRenderItem &dummyItem,
987 const Q3DCamera *activeCamera,
988 float labelsMaxWidth,
989 const QMatrix4x4 &viewMatrix,
990 const QMatrix4x4 &projectionMatrix,
991 ShaderHelper *shader)
992{
993 float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheZ.titleItem().size().height();
994 float titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor));
995 float zRotation = labelRotation.z();
996 float yRotation = -90.0f;
997 float xRotation = -90.0f;
998 float extraRotation = 90.0f;
999 Qt::AlignmentFlag alignment = Qt::AlignTop;
1000 if (m_yFlippedForGrid) {
1001 alignment = Qt::AlignBottom;
1002 xRotation = -xRotation;
1003 if (m_zFlipped) {
1004 if (m_xFlipped) {
1005 titleOffset = -titleOffset;
1006 zRotation = -zRotation;
1007 extraRotation = -extraRotation;
1008 } else {
1009 zRotation = -zRotation;
1010 yRotation = -yRotation;
1011 }
1012 } else {
1013 if (m_xFlipped) {
1014 titleOffset = -titleOffset;
1015 } else {
1016 extraRotation = -extraRotation;
1017 yRotation = -yRotation;
1018 }
1019 }
1020 } else {
1021 if (m_zFlipped) {
1022 zRotation = -zRotation;
1023 if (m_xFlipped) {
1024 titleOffset = -titleOffset;
1025 } else {
1026 extraRotation = -extraRotation;
1027 yRotation = -yRotation;
1028 }
1029 } else {
1030 if (m_xFlipped) {
1031 titleOffset = -titleOffset;
1032 extraRotation = -extraRotation;
1033 } else {
1034 yRotation = -yRotation;
1035 }
1036 }
1037 }
1038
1039 float offsetRotation = zRotation;
1040 if (offsetRotation == 180.0f || offsetRotation == -180.0f)
1041 offsetRotation = 0.0f;
1042 QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: offsetRotation);
1043 QVector3D titleOffsetVector =
1044 offsetRotator.rotatedVector(vector: QVector3D(titleOffset, 0.0f, 0.0f));
1045
1046 QQuaternion titleRotation;
1047 if (m_axisCacheZ.isTitleFixed()) {
1048 titleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: zRotation)
1049 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation)
1050 * QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: xRotation);
1051 } else {
1052 titleRotation = totalRotation
1053 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: extraRotation);
1054 }
1055 dummyItem.setTranslation(labelTrans + titleOffsetVector);
1056
1057 m_drawer->drawLabel(item: dummyItem, labelItem: m_axisCacheZ.titleItem(), viewmatrix: viewMatrix,
1058 projectionmatrix: projectionMatrix, positionComp: zeroVector, rotation: titleRotation, itemHeight: 0,
1059 mode: m_cachedSelectionMode, shader, object: m_labelObj, camera: activeCamera,
1060 useDepth: true, rotateAlong: true, position: Drawer::LabelMid, alignment);
1061}
1062
1063void Abstract3DRenderer::loadGridLineMesh()
1064{
1065 ObjectHelper::resetObjectHelper(cacheId: this, obj&: m_gridLineObj,
1066 QStringLiteral(":/defaultMeshes/plane"));
1067}
1068
1069void Abstract3DRenderer::loadLabelMesh()
1070{
1071 ObjectHelper::resetObjectHelper(cacheId: this, obj&: m_labelObj,
1072 QStringLiteral(":/defaultMeshes/plane"));
1073}
1074
1075void Abstract3DRenderer::loadPositionMapperMesh()
1076{
1077 ObjectHelper::resetObjectHelper(cacheId: this, obj&: m_positionMapperObj,
1078 QStringLiteral(":/defaultMeshes/barFull"));
1079}
1080
1081void Abstract3DRenderer::generateBaseColorTexture(const QColor &color, GLuint *texture)
1082{
1083 m_textureHelper->deleteTexture(texture);
1084 *texture = m_textureHelper->createUniformTexture(color);
1085}
1086
1087void Abstract3DRenderer::fixGradientAndGenerateTexture(QLinearGradient *gradient,
1088 GLuint *gradientTexture)
1089{
1090 // Readjust start/stop to match gradient texture size
1091 gradient->setStart(x: qreal(gradientTextureWidth), y: qreal(gradientTextureHeight));
1092 gradient->setFinalStop(x: 0.0, y: 0.0);
1093
1094 m_textureHelper->deleteTexture(texture: gradientTexture);
1095
1096 *gradientTexture = m_textureHelper->createGradientTexture(gradient: *gradient);
1097}
1098
1099LabelItem &Abstract3DRenderer::selectionLabelItem()
1100{
1101 if (!m_selectionLabelItem)
1102 m_selectionLabelItem = new LabelItem;
1103 return *m_selectionLabelItem;
1104}
1105
1106void Abstract3DRenderer::setSelectionLabel(const QString &label)
1107{
1108 if (m_selectionLabelItem)
1109 m_selectionLabelItem->clear();
1110 m_selectionLabel = label;
1111}
1112
1113QString &Abstract3DRenderer::selectionLabel()
1114{
1115 return m_selectionLabel;
1116}
1117
1118QVector4D Abstract3DRenderer::indexToSelectionColor(GLint index)
1119{
1120 GLubyte idxRed = index & 0xff;
1121 GLubyte idxGreen = (index & 0xff00) >> 8;
1122 GLubyte idxBlue = (index & 0xff0000) >> 16;
1123
1124 return QVector4D(idxRed, idxGreen, idxBlue, 0);
1125}
1126
1127CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item)
1128{
1129 CustomRenderItem *newItem = new CustomRenderItem();
1130 newItem->setRenderer(this);
1131 newItem->setItemPointer(item); // Store pointer for render item updates
1132 newItem->setMesh(item->meshFile());
1133 newItem->setOrigPosition(item->position());
1134 newItem->setOrigScaling(item->scaling());
1135 newItem->setScalingAbsolute(item->isScalingAbsolute());
1136 newItem->setPositionAbsolute(item->isPositionAbsolute());
1137 QImage textureImage = item->d_ptr->textureImage();
1138 bool facingCamera = false;
1139 GLuint texture = 0;
1140 if (item->d_ptr->m_isLabelItem) {
1141 QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
1142 newItem->setLabelItem(true);
1143 float pointSize = labelItem->font().pointSizeF();
1144 // Check do we have custom visuals or need to use theme
1145 if (!labelItem->dptr()->m_customVisuals) {
1146 // Recreate texture using theme
1147 labelItem->dptr()->createTextureImage(bgrColor: m_cachedTheme->labelBackgroundColor(),
1148 txtColor: m_cachedTheme->labelTextColor(),
1149 background: m_cachedTheme->isLabelBackgroundEnabled(),
1150 borders: m_cachedTheme->isLabelBorderEnabled());
1151 pointSize = m_cachedTheme->font().pointSizeF();
1152 textureImage = item->d_ptr->textureImage();
1153 }
1154 // Calculate scaling based on text (texture size), font size and asked scaling
1155 float scaledFontSize = (0.05f + pointSize / 500.0f) / float(textureImage.height());
1156 QVector3D scaling = newItem->origScaling();
1157 scaling.setX(scaling.x() * textureImage.width() * scaledFontSize);
1158 scaling.setY(scaling.y() * textureImage.height() * scaledFontSize);
1159 newItem->setOrigScaling(scaling);
1160 // Check if facing camera
1161 facingCamera = labelItem->isFacingCamera();
1162 } else if (item->d_ptr->m_isVolumeItem && !m_isOpenGLES) {
1163 QCustom3DVolume *volumeItem = static_cast<QCustom3DVolume *>(item);
1164 newItem->setTextureWidth(volumeItem->textureWidth());
1165 newItem->setTextureHeight(volumeItem->textureHeight());
1166 newItem->setTextureDepth(volumeItem->textureDepth());
1167 if (volumeItem->textureFormat() == QImage::Format_Indexed8)
1168 newItem->setColorTable(volumeItem->colorTable());
1169 newItem->setTextureFormat(volumeItem->textureFormat());
1170 newItem->setVolume(true);
1171 newItem->setBlendNeeded(true);
1172 texture = m_textureHelper->create3DTexture(data: volumeItem->textureData(),
1173 width: volumeItem->textureWidth(),
1174 height: volumeItem->textureHeight(),
1175 depth: volumeItem->textureDepth(),
1176 dataFormat: volumeItem->textureFormat());
1177 newItem->setSliceIndexX(volumeItem->sliceIndexX());
1178 newItem->setSliceIndexY(volumeItem->sliceIndexY());
1179 newItem->setSliceIndexZ(volumeItem->sliceIndexZ());
1180 newItem->setAlphaMultiplier(volumeItem->alphaMultiplier());
1181 newItem->setPreserveOpacity(volumeItem->preserveOpacity());
1182 newItem->setUseHighDefShader(volumeItem->useHighDefShader());
1183
1184 newItem->setDrawSlices(volumeItem->drawSlices());
1185 newItem->setDrawSliceFrames(volumeItem->drawSliceFrames());
1186 newItem->setSliceFrameColor(volumeItem->sliceFrameColor());
1187 newItem->setSliceFrameWidths(volumeItem->sliceFrameWidths());
1188 newItem->setSliceFrameGaps(volumeItem->sliceFrameGaps());
1189 newItem->setSliceFrameThicknesses(volumeItem->sliceFrameThicknesses());
1190 }
1191 recalculateCustomItemScalingAndPos(item: newItem);
1192 newItem->setRotation(item->rotation());
1193
1194 // In OpenGL ES we simply draw volumes as regular custom item placeholders.
1195 if (!item->d_ptr->m_isVolumeItem || m_isOpenGLES)
1196 {
1197 newItem->setBlendNeeded(textureImage.hasAlphaChannel());
1198 texture = m_textureHelper->create2DTexture(image: textureImage, useTrilinearFiltering: true, convert: true, smoothScale: true);
1199 }
1200 newItem->setTexture(texture);
1201 item->d_ptr->clearTextureImage();
1202 newItem->setVisible(item->isVisible());
1203 newItem->setShadowCasting(item->isShadowCasting());
1204 newItem->setFacingCamera(facingCamera);
1205 m_customRenderCache.insert(akey: item, avalue: newItem);
1206 return newItem;
1207}
1208
1209void Abstract3DRenderer::recalculateCustomItemScalingAndPos(CustomRenderItem *item)
1210{
1211 if (!m_polarGraph && !item->isLabel() && !item->isScalingAbsolute()
1212 && !item->isPositionAbsolute()) {
1213 QVector3D scale = item->origScaling() / 2.0f;
1214 QVector3D pos = item->origPosition();
1215 QVector3D minBounds(pos.x() - scale.x(),
1216 pos.y() - scale.y(),
1217 pos.z() + scale.z());
1218 QVector3D maxBounds(pos.x() + scale.x(),
1219 pos.y() + scale.y(),
1220 pos.z() - scale.z());
1221 QVector3D minCorner = convertPositionToTranslation(position: minBounds, isAbsolute: false);
1222 QVector3D maxCorner = convertPositionToTranslation(position: maxBounds, isAbsolute: false);
1223 scale = QVector3D(qAbs(t: maxCorner.x() - minCorner.x()),
1224 qAbs(t: maxCorner.y() - minCorner.y()),
1225 qAbs(t: maxCorner.z() - minCorner.z())) / 2.0f;
1226 if (item->isVolume()) {
1227 // Only volume items need to scale and reposition according to bounds
1228 QVector3D minBoundsNormal = minCorner;
1229 QVector3D maxBoundsNormal = maxCorner;
1230 // getVisibleItemBounds returns bounds normalized for fragment shader [-1,1]
1231 // Y and Z are also flipped.
1232 getVisibleItemBounds(minBounds&: minBoundsNormal, maxBounds&: maxBoundsNormal);
1233 item->setMinBounds(minBoundsNormal);
1234 item->setMaxBounds(maxBoundsNormal);
1235 // For scaling calculations, we want [0,1] normalized values
1236 minBoundsNormal = item->minBoundsNormal();
1237 maxBoundsNormal = item->maxBoundsNormal();
1238
1239 // Rescale and reposition the item so that it doesn't go over the edges
1240 QVector3D adjScaling =
1241 QVector3D(scale.x() * (maxBoundsNormal.x() - minBoundsNormal.x()),
1242 scale.y() * (maxBoundsNormal.y() - minBoundsNormal.y()),
1243 scale.z() * (maxBoundsNormal.z() - minBoundsNormal.z()));
1244
1245 item->setScaling(adjScaling);
1246
1247 QVector3D adjPos = item->origPosition();
1248 QVector3D dataExtents = QVector3D(maxBounds.x() - minBounds.x(),
1249 maxBounds.y() - minBounds.y(),
1250 maxBounds.z() - minBounds.z()) / 2.0f;
1251 adjPos.setX(adjPos.x() + (dataExtents.x() * minBoundsNormal.x())
1252 - (dataExtents.x() * (1.0f - maxBoundsNormal.x())));
1253 adjPos.setY(adjPos.y() + (dataExtents.y() * minBoundsNormal.y())
1254 - (dataExtents.y() * (1.0f - maxBoundsNormal.y())));
1255 adjPos.setZ(adjPos.z() + (dataExtents.z() * minBoundsNormal.z())
1256 - (dataExtents.z() * (1.0f - maxBoundsNormal.z())));
1257 item->setPosition(adjPos);
1258 } else {
1259 // Only scale for non-volume items, and do not readjust position
1260 item->setScaling(scale);
1261 item->setPosition(item->origPosition());
1262 }
1263 } else {
1264 item->setScaling(item->origScaling());
1265 item->setPosition(item->origPosition());
1266 if (item->isVolume()) {
1267 // Y and Z need to be flipped as shader flips those axes
1268 item->setMinBounds(QVector3D(-1.0f, 1.0f, 1.0f));
1269 item->setMaxBounds(QVector3D(1.0f, -1.0f, -1.0f));
1270 }
1271 }
1272 QVector3D translation = convertPositionToTranslation(position: item->position(),
1273 isAbsolute: item->isPositionAbsolute());
1274 item->setTranslation(translation);
1275}
1276
1277void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem)
1278{
1279 QCustom3DItem *item = renderItem->itemPointer();
1280 if (item->d_ptr->m_dirtyBits.meshDirty) {
1281 renderItem->setMesh(item->meshFile());
1282 item->d_ptr->m_dirtyBits.meshDirty = false;
1283 }
1284 if (item->d_ptr->m_dirtyBits.positionDirty) {
1285 renderItem->setOrigPosition(item->position());
1286 renderItem->setPositionAbsolute(item->isPositionAbsolute());
1287 if (!item->d_ptr->m_dirtyBits.scalingDirty)
1288 recalculateCustomItemScalingAndPos(item: renderItem);
1289 item->d_ptr->m_dirtyBits.positionDirty = false;
1290 }
1291 if (item->d_ptr->m_dirtyBits.scalingDirty) {
1292 QVector3D scaling = item->scaling();
1293 renderItem->setOrigScaling(scaling);
1294 renderItem->setScalingAbsolute(item->isScalingAbsolute());
1295 // In case we have label item, we need to recreate texture for scaling adjustment
1296 if (item->d_ptr->m_isLabelItem) {
1297 QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
1298 float pointSize = labelItem->font().pointSizeF();
1299 // Check do we have custom visuals or need to use theme
1300 if (labelItem->dptr()->m_customVisuals) {
1301 // Recreate texture
1302 labelItem->dptr()->createTextureImage();
1303 } else {
1304 // Recreate texture using theme
1305 labelItem->dptr()->createTextureImage(bgrColor: m_cachedTheme->labelBackgroundColor(),
1306 txtColor: m_cachedTheme->labelTextColor(),
1307 background: m_cachedTheme->isLabelBackgroundEnabled(),
1308 borders: m_cachedTheme->isLabelBorderEnabled());
1309 pointSize = m_cachedTheme->font().pointSizeF();
1310 }
1311 QImage textureImage = item->d_ptr->textureImage();
1312 // Calculate scaling based on text (texture size), font size and asked scaling
1313 float scaledFontSize = (0.05f + pointSize / 500.0f) / float(textureImage.height());
1314 scaling.setX(scaling.x() * textureImage.width() * scaledFontSize);
1315 scaling.setY(scaling.y() * textureImage.height() * scaledFontSize);
1316 item->d_ptr->clearTextureImage();
1317 renderItem->setOrigScaling(scaling);
1318 }
1319 recalculateCustomItemScalingAndPos(item: renderItem);
1320 item->d_ptr->m_dirtyBits.scalingDirty = false;
1321 }
1322 if (item->d_ptr->m_dirtyBits.rotationDirty) {
1323 renderItem->setRotation(item->rotation());
1324 item->d_ptr->m_dirtyBits.rotationDirty = false;
1325 }
1326 if (item->d_ptr->m_dirtyBits.textureDirty) {
1327 QImage textureImage = item->d_ptr->textureImage();
1328 if (item->d_ptr->m_isLabelItem) {
1329 QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
1330 // Check do we have custom visuals or need to use theme
1331 if (!labelItem->dptr()->m_customVisuals) {
1332 // Recreate texture using theme
1333 labelItem->dptr()->createTextureImage(bgrColor: m_cachedTheme->labelBackgroundColor(),
1334 txtColor: m_cachedTheme->labelTextColor(),
1335 background: m_cachedTheme->isLabelBackgroundEnabled(),
1336 borders: m_cachedTheme->isLabelBorderEnabled());
1337 textureImage = item->d_ptr->textureImage();
1338 }
1339 } else if (!item->d_ptr->m_isVolumeItem || m_isOpenGLES) {
1340 renderItem->setBlendNeeded(textureImage.hasAlphaChannel());
1341 GLuint oldTexture = renderItem->texture();
1342 m_textureHelper->deleteTexture(texture: &oldTexture);
1343 GLuint texture = m_textureHelper->create2DTexture(image: textureImage, useTrilinearFiltering: true, convert: true, smoothScale: true);
1344 renderItem->setTexture(texture);
1345 }
1346 item->d_ptr->clearTextureImage();
1347 item->d_ptr->m_dirtyBits.textureDirty = false;
1348 }
1349 if (item->d_ptr->m_dirtyBits.visibleDirty) {
1350 renderItem->setVisible(item->isVisible());
1351 item->d_ptr->m_dirtyBits.visibleDirty = false;
1352 }
1353 if (item->d_ptr->m_dirtyBits.shadowCastingDirty) {
1354 renderItem->setShadowCasting(item->isShadowCasting());
1355 item->d_ptr->m_dirtyBits.shadowCastingDirty = false;
1356 }
1357 if (item->d_ptr->m_isLabelItem) {
1358 QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
1359 if (labelItem->dptr()->m_facingCameraDirty) {
1360 renderItem->setFacingCamera(labelItem->isFacingCamera());
1361 labelItem->dptr()->m_facingCameraDirty = false;
1362 }
1363 } else if (item->d_ptr->m_isVolumeItem && !m_isOpenGLES) {
1364 QCustom3DVolume *volumeItem = static_cast<QCustom3DVolume *>(item);
1365 if (volumeItem->dptr()->m_dirtyBitsVolume.colorTableDirty) {
1366 renderItem->setColorTable(volumeItem->colorTable());
1367 volumeItem->dptr()->m_dirtyBitsVolume.colorTableDirty = false;
1368 }
1369 if (volumeItem->dptr()->m_dirtyBitsVolume.textureDimensionsDirty
1370 || volumeItem->dptr()->m_dirtyBitsVolume.textureDataDirty
1371 || volumeItem->dptr()->m_dirtyBitsVolume.textureFormatDirty) {
1372 GLuint oldTexture = renderItem->texture();
1373 m_textureHelper->deleteTexture(texture: &oldTexture);
1374 GLuint texture = m_textureHelper->create3DTexture(data: volumeItem->textureData(),
1375 width: volumeItem->textureWidth(),
1376 height: volumeItem->textureHeight(),
1377 depth: volumeItem->textureDepth(),
1378 dataFormat: volumeItem->textureFormat());
1379 renderItem->setTexture(texture);
1380 renderItem->setTextureWidth(volumeItem->textureWidth());
1381 renderItem->setTextureHeight(volumeItem->textureHeight());
1382 renderItem->setTextureDepth(volumeItem->textureDepth());
1383 renderItem->setTextureFormat(volumeItem->textureFormat());
1384 volumeItem->dptr()->m_dirtyBitsVolume.textureDimensionsDirty = false;
1385 volumeItem->dptr()->m_dirtyBitsVolume.textureDataDirty = false;
1386 volumeItem->dptr()->m_dirtyBitsVolume.textureFormatDirty = false;
1387 }
1388 if (volumeItem->dptr()->m_dirtyBitsVolume.slicesDirty) {
1389 renderItem->setDrawSlices(volumeItem->drawSlices());
1390 renderItem->setDrawSliceFrames(volumeItem->drawSliceFrames());
1391 renderItem->setSliceFrameColor(volumeItem->sliceFrameColor());
1392 renderItem->setSliceFrameWidths(volumeItem->sliceFrameWidths());
1393 renderItem->setSliceFrameGaps(volumeItem->sliceFrameGaps());
1394 renderItem->setSliceFrameThicknesses(volumeItem->sliceFrameThicknesses());
1395 renderItem->setSliceIndexX(volumeItem->sliceIndexX());
1396 renderItem->setSliceIndexY(volumeItem->sliceIndexY());
1397 renderItem->setSliceIndexZ(volumeItem->sliceIndexZ());
1398 volumeItem->dptr()->m_dirtyBitsVolume.slicesDirty = false;
1399 }
1400 if (volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty) {
1401 renderItem->setAlphaMultiplier(volumeItem->alphaMultiplier());
1402 renderItem->setPreserveOpacity(volumeItem->preserveOpacity());
1403 volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty = false;
1404 }
1405 if (volumeItem->dptr()->m_dirtyBitsVolume.shaderDirty) {
1406 renderItem->setUseHighDefShader(volumeItem->useHighDefShader());
1407 volumeItem->dptr()->m_dirtyBitsVolume.shaderDirty = false;
1408 }
1409 }
1410}
1411
1412void Abstract3DRenderer::updateCustomItemPositions()
1413{
1414 foreach (CustomRenderItem *renderItem, m_customRenderCache)
1415 recalculateCustomItemScalingAndPos(item: renderItem);
1416}
1417
1418void Abstract3DRenderer::drawCustomItems(RenderingState state,
1419 ShaderHelper *regularShader,
1420 const QMatrix4x4 &viewMatrix,
1421 const QMatrix4x4 &projectionViewMatrix,
1422 const QMatrix4x4 &depthProjectionViewMatrix,
1423 GLuint depthTexture,
1424 GLfloat shadowQuality,
1425 GLfloat reflection)
1426{
1427 if (m_customRenderCache.isEmpty())
1428 return;
1429
1430 ShaderHelper *shader = regularShader;
1431 shader->bind();
1432
1433 if (RenderingNormal == state) {
1434 shader->setUniformValue(uniform: shader->lightP(), value: m_cachedScene->activeLight()->position());
1435 shader->setUniformValue(uniform: shader->ambientS(), value: m_cachedTheme->ambientLightStrength());
1436 shader->setUniformValue(uniform: shader->lightColor(),
1437 value: Utils::vectorFromColor(color: m_cachedTheme->lightColor()));
1438 shader->setUniformValue(uniform: shader->view(), value: viewMatrix);
1439 }
1440
1441 // Draw custom items - first regular and then volumes
1442 bool volumeDetected = false;
1443 int loopCount = 0;
1444 while (loopCount < 2) {
1445 for (QCustom3DItem *customItem : qAsConst(t&: m_customItemDrawOrder)) {
1446 CustomRenderItem *item = m_customRenderCache.value(akey: customItem);
1447 // Check that the render item is visible, and skip drawing if not
1448 // Also check if reflected item is on the "wrong" side, and skip drawing if it is
1449 if (!item->isVisible() || ((m_reflectionEnabled && reflection < 0.0f)
1450 && (m_yFlipped == (item->translation().y() >= 0.0)))) {
1451 continue;
1452 }
1453 if (loopCount == 0) {
1454 if (item->isVolume()) {
1455 volumeDetected = true;
1456 continue;
1457 }
1458 } else {
1459 if (!item->isVolume())
1460 continue;
1461 }
1462
1463 // If the render item is in data coordinates and not within axis ranges, skip it
1464 if (!item->isPositionAbsolute()
1465 && (item->position().x() < m_axisCacheX.min()
1466 || item->position().x() > m_axisCacheX.max()
1467 || item->position().z() < m_axisCacheZ.min()
1468 || item->position().z() > m_axisCacheZ.max()
1469 || item->position().y() < m_axisCacheY.min()
1470 || item->position().y() > m_axisCacheY.max())) {
1471 continue;
1472 }
1473
1474 QMatrix4x4 modelMatrix;
1475 QMatrix4x4 itModelMatrix;
1476 QMatrix4x4 MVPMatrix;
1477
1478 QQuaternion rotation = item->rotation();
1479 // Check if the (label) item should be facing camera, and adjust rotation accordingly
1480 if (item->isFacingCamera()) {
1481 float camRotationX = m_cachedScene->activeCamera()->xRotation();
1482 float camRotationY = m_cachedScene->activeCamera()->yRotation();
1483 rotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: -camRotationX)
1484 * QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: -camRotationY);
1485 }
1486
1487 if (m_reflectionEnabled) {
1488 if (reflection < 0.0f) {
1489 if (item->itemPointer()->d_ptr->m_isLabelItem)
1490 continue;
1491 else
1492 glCullFace(GL_FRONT);
1493 } else {
1494 glCullFace(GL_BACK);
1495 }
1496 QVector3D trans = item->translation();
1497 trans.setY(reflection * trans.y());
1498 modelMatrix.translate(vector: trans);
1499 if (reflection < 0.0f) {
1500 QQuaternion mirror = QQuaternion(rotation.scalar(),
1501 -rotation.x(), rotation.y(), -rotation.z());
1502 modelMatrix.rotate(quaternion: mirror);
1503 itModelMatrix.rotate(quaternion: mirror);
1504 } else {
1505 modelMatrix.rotate(quaternion: rotation);
1506 itModelMatrix.rotate(quaternion: rotation);
1507 }
1508 QVector3D scale = item->scaling();
1509 scale.setY(reflection * scale.y());
1510 modelMatrix.scale(vector: scale);
1511 } else {
1512 modelMatrix.translate(vector: item->translation());
1513 modelMatrix.rotate(quaternion: rotation);
1514 modelMatrix.scale(vector: item->scaling());
1515 itModelMatrix.rotate(quaternion: rotation);
1516 }
1517 if (!item->isFacingCamera())
1518 itModelMatrix.scale(vector: item->scaling());
1519 MVPMatrix = projectionViewMatrix * modelMatrix;
1520
1521 if (RenderingNormal == state) {
1522 // Normal render
1523 ShaderHelper *prevShader = shader;
1524 if (item->isVolume() && !m_isOpenGLES) {
1525 if (item->drawSlices() &&
1526 (item->sliceIndexX() >= 0
1527 || item->sliceIndexY() >= 0
1528 || item->sliceIndexZ() >= 0)) {
1529 shader = m_volumeTextureSliceShader;
1530 } else if (item->useHighDefShader()) {
1531 shader = m_volumeTextureShader;
1532 } else {
1533 shader = m_volumeTextureLowDefShader;
1534 }
1535 } else if (item->isLabel()) {
1536 shader = m_labelShader;
1537 } else {
1538 shader = regularShader;
1539 }
1540 if (shader != prevShader)
1541 shader->bind();
1542 shader->setUniformValue(uniform: shader->model(), value: modelMatrix);
1543 shader->setUniformValue(uniform: shader->MVP(), value: MVPMatrix);
1544 shader->setUniformValue(uniform: shader->nModel(), value: itModelMatrix.inverted().transposed());
1545
1546 if (item->isBlendNeeded()) {
1547 glEnable(GL_BLEND);
1548 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1549 if (!item->isVolume() && !m_isOpenGLES)
1550 glDisable(GL_CULL_FACE);
1551 } else {
1552 glDisable(GL_BLEND);
1553 glEnable(GL_CULL_FACE);
1554 }
1555
1556 if (!m_isOpenGLES && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone
1557 && !item->isVolume()) {
1558 // Set shadow shader bindings
1559 shader->setUniformValue(uniform: shader->shadowQ(), value: shadowQuality);
1560 shader->setUniformValue(uniform: shader->depth(), value: depthProjectionViewMatrix * modelMatrix);
1561 shader->setUniformValue(uniform: shader->lightS(), value: m_cachedTheme->lightStrength() / 10.0f);
1562 m_drawer->drawObject(shader, object: item->mesh(), textureId: item->texture(), depthTextureId: depthTexture);
1563 } else {
1564 // Set shadowless shader bindings
1565 if (item->isVolume() && !m_isOpenGLES) {
1566 QVector3D cameraPos = m_cachedScene->activeCamera()->position();
1567 cameraPos = MVPMatrix.inverted().map(point: cameraPos);
1568 // Adjust camera position according to min/max bounds
1569 cameraPos = -(cameraPos
1570 + ((oneVector - cameraPos) * item->minBoundsNormal())
1571 - ((oneVector + cameraPos) * (oneVector - item->maxBoundsNormal())));
1572 shader->setUniformValue(uniform: shader->cameraPositionRelativeToModel(), value: cameraPos);
1573 GLint color8Bit = (item->textureFormat() == QImage::Format_Indexed8) ? 1 : 0;
1574 if (color8Bit) {
1575 shader->setUniformValueArray(uniform: shader->colorIndex(),
1576 values: item->colorTable().constData(), count: 256);
1577 }
1578 shader->setUniformValue(uniform: shader->color8Bit(), value: color8Bit);
1579 shader->setUniformValue(uniform: shader->alphaMultiplier(), value: item->alphaMultiplier());
1580 shader->setUniformValue(uniform: shader->preserveOpacity(),
1581 value: item->preserveOpacity() ? 1 : 0);
1582
1583 shader->setUniformValue(uniform: shader->minBounds(), value: item->minBounds());
1584 shader->setUniformValue(uniform: shader->maxBounds(), value: item->maxBounds());
1585
1586 if (shader == m_volumeTextureSliceShader) {
1587 shader->setUniformValue(uniform: shader->volumeSliceIndices(),
1588 value: item->sliceFractions());
1589 } else {
1590 // Precalculate texture dimensions so we can optimize
1591 // ray stepping to hit every texture layer.
1592 QVector3D textureDimensions(1.0f / float(item->textureWidth()),
1593 1.0f / float(item->textureHeight()),
1594 1.0f / float(item->textureDepth()));
1595
1596 // Worst case scenario sample count
1597 int sampleCount;
1598 if (shader == m_volumeTextureLowDefShader) {
1599 sampleCount = qMax(a: item->textureWidth(),
1600 b: qMax(a: item->textureDepth(), b: item->textureHeight()));
1601 // Further improve speed with big textures by simply dropping every
1602 // other sample:
1603 if (sampleCount > 256)
1604 sampleCount /= 2;
1605 } else {
1606 sampleCount = item->textureWidth() + item->textureHeight()
1607 + item->textureDepth();
1608 }
1609 shader->setUniformValue(uniform: shader->textureDimensions(), value: textureDimensions);
1610 shader->setUniformValue(uniform: shader->sampleCount(), value: sampleCount);
1611 }
1612 if (item->drawSliceFrames()) {
1613 // Set up the slice frame shader
1614 glDisable(GL_CULL_FACE);
1615 m_volumeSliceFrameShader->bind();
1616 m_volumeSliceFrameShader->setUniformValue(
1617 uniform: m_volumeSliceFrameShader->color(), value: item->sliceFrameColor());
1618
1619 // Draw individual slice frames.
1620 if (item->sliceIndexX() >= 0)
1621 drawVolumeSliceFrame(item, axis: Qt::XAxis, projectionViewMatrix);
1622 if (item->sliceIndexY() >= 0)
1623 drawVolumeSliceFrame(item, axis: Qt::YAxis, projectionViewMatrix);
1624 if (item->sliceIndexZ() >= 0)
1625 drawVolumeSliceFrame(item, axis: Qt::ZAxis, projectionViewMatrix);
1626
1627 glEnable(GL_CULL_FACE);
1628 shader->bind();
1629 }
1630 m_drawer->drawObject(shader, object: item->mesh(), textureId: 0, depthTextureId: 0, textureId3D: item->texture());
1631 } else {
1632 shader->setUniformValue(uniform: shader->lightS(), value: m_cachedTheme->lightStrength());
1633 m_drawer->drawObject(shader, object: item->mesh(), textureId: item->texture());
1634 }
1635 }
1636 } else if (RenderingSelection == state) {
1637 // Selection render
1638 shader->setUniformValue(uniform: shader->MVP(), value: MVPMatrix);
1639 QVector4D itemColor = indexToSelectionColor(index: item->index());
1640 itemColor.setW(customItemAlpha);
1641 itemColor /= 255.0f;
1642 shader->setUniformValue(uniform: shader->color(), value: itemColor);
1643 m_drawer->drawObject(shader, object: item->mesh());
1644 } else if (item->isShadowCasting()) {
1645 // Depth render
1646 shader->setUniformValue(uniform: shader->MVP(), value: depthProjectionViewMatrix * modelMatrix);
1647 m_drawer->drawObject(shader, object: item->mesh());
1648 }
1649 }
1650 loopCount++;
1651 if (!volumeDetected)
1652 loopCount++; // Skip second run if no volumes detected
1653 }
1654
1655 if (RenderingNormal == state) {
1656 glDisable(GL_BLEND);
1657 glEnable(GL_CULL_FACE);
1658 }
1659}
1660
1661void Abstract3DRenderer::drawVolumeSliceFrame(const CustomRenderItem *item, Qt::Axis axis,
1662 const QMatrix4x4 &projectionViewMatrix)
1663{
1664 QVector2D frameWidth;
1665 QVector3D frameScaling;
1666 QVector3D translation = item->translation();
1667 QQuaternion rotation = item->rotation();
1668 float fracTrans;
1669 bool needRotate = !rotation.isIdentity();
1670 QMatrix4x4 rotationMatrix;
1671 if (needRotate)
1672 rotationMatrix.rotate(quaternion: rotation);
1673
1674 if (axis == Qt::XAxis) {
1675 fracTrans = item->sliceFractions().x();
1676 float range = item->maxBoundsNormal().x() - item->minBoundsNormal().x();
1677 float minMult = item->minBoundsNormal().x() / range;
1678 float maxMult = (1.0f - item->maxBoundsNormal().x()) / range;
1679 fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult);
1680 if (needRotate)
1681 translation += rotationMatrix.map(point: QVector3D(fracTrans * item->scaling().x(), 0.0f, 0.0f));
1682 else
1683 translation.setX(translation.x() + fracTrans * item->scaling().x());
1684 frameScaling = QVector3D(item->scaling().z()
1685 + (item->scaling().z() * item->sliceFrameGaps().z())
1686 + (item->scaling().z() * item->sliceFrameWidths().z()),
1687 item->scaling().y()
1688 + (item->scaling().y() * item->sliceFrameGaps().y())
1689 + (item->scaling().y() * item->sliceFrameWidths().y()),
1690 item->scaling().x() * item->sliceFrameThicknesses().x());
1691 frameWidth = QVector2D(item->scaling().z() * item->sliceFrameWidths().z(),
1692 item->scaling().y() * item->sliceFrameWidths().y());
1693 rotation *= m_yRightAngleRotation;
1694 } else if (axis == Qt::YAxis) {
1695 fracTrans = item->sliceFractions().y();
1696 float range = item->maxBoundsNormal().y() - item->minBoundsNormal().y();
1697 // Y axis is logically flipped, so we need to swam min and max bounds
1698 float minMult = (1.0f - item->maxBoundsNormal().y()) / range;
1699 float maxMult = item->minBoundsNormal().y() / range;
1700 fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult);
1701 if (needRotate)
1702 translation -= rotationMatrix.map(point: QVector3D(0.0f, fracTrans * item->scaling().y(), 0.0f));
1703 else
1704 translation.setY(translation.y() - fracTrans * item->scaling().y());
1705 frameScaling = QVector3D(item->scaling().x()
1706 + (item->scaling().x() * item->sliceFrameGaps().x())
1707 + (item->scaling().x() * item->sliceFrameWidths().x()),
1708 item->scaling().z()
1709 + (item->scaling().z() * item->sliceFrameGaps().z())
1710 + (item->scaling().z() * item->sliceFrameWidths().z()),
1711 item->scaling().y() * item->sliceFrameThicknesses().y());
1712 frameWidth = QVector2D(item->scaling().x() * item->sliceFrameWidths().x(),
1713 item->scaling().z() * item->sliceFrameWidths().z());
1714 rotation *= m_xRightAngleRotation;
1715 } else { // Z axis
1716 fracTrans = item->sliceFractions().z();
1717 float range = item->maxBoundsNormal().z() - item->minBoundsNormal().z();
1718 // Z axis is logically flipped, so we need to swam min and max bounds
1719 float minMult = (1.0f - item->maxBoundsNormal().z()) / range;
1720 float maxMult = item->minBoundsNormal().z() / range;
1721 fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult);
1722 if (needRotate)
1723 translation -= rotationMatrix.map(point: QVector3D(0.0f, 0.0f, fracTrans * item->scaling().z()));
1724 else
1725 translation.setZ(translation.z() - fracTrans * item->scaling().z());
1726 frameScaling = QVector3D(item->scaling().x()
1727 + (item->scaling().x() * item->sliceFrameGaps().x())
1728 + (item->scaling().x() * item->sliceFrameWidths().x()),
1729 item->scaling().y()
1730 + (item->scaling().y() * item->sliceFrameGaps().y())
1731 + (item->scaling().y() * item->sliceFrameWidths().y()),
1732 item->scaling().z() * item->sliceFrameThicknesses().z());
1733 frameWidth = QVector2D(item->scaling().x() * item->sliceFrameWidths().x(),
1734 item->scaling().y() * item->sliceFrameWidths().y());
1735 }
1736
1737 // If the slice is outside the shown area, don't show the frame
1738 if (fracTrans < -1.0 || fracTrans > 1.0)
1739 return;
1740
1741 // Shader needs the width of clear space in the middle.
1742 frameWidth.setX(1.0f - (frameWidth.x() / frameScaling.x()));
1743 frameWidth.setY(1.0f - (frameWidth.y() / frameScaling.y()));
1744
1745 QMatrix4x4 modelMatrix;
1746 QMatrix4x4 mvpMatrix;
1747
1748 modelMatrix.translate(vector: translation);
1749 modelMatrix.rotate(quaternion: rotation);
1750 modelMatrix.scale(vector: frameScaling);
1751 mvpMatrix = projectionViewMatrix * modelMatrix;
1752 m_volumeSliceFrameShader->setUniformValue(uniform: m_volumeSliceFrameShader->MVP(), value: mvpMatrix);
1753 m_volumeSliceFrameShader->setUniformValue(uniform: m_volumeSliceFrameShader->sliceFrameWidth(),
1754 value: frameWidth);
1755
1756 m_drawer->drawObject(shader: m_volumeSliceFrameShader, object: item->mesh());
1757
1758}
1759
1760void Abstract3DRenderer::queriedGraphPosition(const QMatrix4x4 &projectionViewMatrix,
1761 const QVector3D &scaling,
1762 GLuint defaultFboHandle)
1763{
1764 m_cursorPositionShader->bind();
1765
1766 // Set up mapper framebuffer
1767 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: m_cursorPositionFrameBuffer);
1768 glViewport(x: 0, y: 0,
1769 width: m_primarySubViewport.width(),
1770 height: m_primarySubViewport.height());
1771 glClearColor(red: 1.0f, green: 1.0f, blue: 1.0f, alpha: 1.0f);
1772 glClear(GL_COLOR_BUFFER_BIT);
1773 glDisable(GL_DITHER); // Dither may affect colors if enabled
1774 glEnable(GL_CULL_FACE);
1775 glCullFace(GL_FRONT);
1776
1777 // Draw a cube scaled to the graph dimensions
1778 QMatrix4x4 modelMatrix;
1779 QMatrix4x4 MVPMatrix;
1780
1781 modelMatrix.scale(vector: scaling);
1782
1783 MVPMatrix = projectionViewMatrix * modelMatrix;
1784 m_cursorPositionShader->setUniformValue(uniform: m_cursorPositionShader->MVP(), value: MVPMatrix);
1785 m_drawer->drawObject(shader: m_cursorPositionShader, object: m_positionMapperObj);
1786
1787 QVector4D dataColor = Utils::getSelection(mousepos: m_graphPositionQuery,
1788 height: m_primarySubViewport.height());
1789 if (dataColor.w() > 0.0f) {
1790 // If position is outside the graph, set the position well outside the graph boundaries
1791 dataColor = QVector4D(-10000.0f, -10000.0f, -10000.0f, 0.0f);
1792 } else {
1793 // Normalize to range [0.0, 1.0]
1794 dataColor /= 255.0f;
1795 }
1796
1797 // Restore state
1798 glEnable(GL_DITHER);
1799 glCullFace(GL_BACK);
1800
1801 // Note: Zeroing the frame buffer before resetting it is a workaround for flickering that occurs
1802 // during zoom in some environments.
1803 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: 0);
1804
1805 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: defaultFboHandle);
1806 glViewport(x: m_primarySubViewport.x(),
1807 y: m_primarySubViewport.y(),
1808 width: m_primarySubViewport.width(),
1809 height: m_primarySubViewport.height());
1810
1811 QVector3D normalizedValues = dataColor.toVector3D() * 2.0f;
1812 normalizedValues -= oneVector;
1813 m_queriedGraphPosition = QVector3D(normalizedValues.x(),
1814 normalizedValues.y(),
1815 normalizedValues.z());
1816 m_graphPositionQueryResolved = true;
1817 m_graphPositionQueryPending = false;
1818}
1819
1820void Abstract3DRenderer::calculatePolarXZ(const QVector3D &dataPos, float &x, float &z) const
1821{
1822 // x is angular, z is radial
1823 qreal angle = m_axisCacheX.formatter()->positionAt(value: dataPos.x()) * doublePi;
1824 qreal radius = m_axisCacheZ.formatter()->positionAt(value: dataPos.z());
1825
1826 // Convert angle & radius to X and Z coords
1827 x = float(radius * qSin(v: angle)) * m_polarRadius;
1828 z = -float(radius * qCos(v: angle)) * m_polarRadius;
1829}
1830
1831void Abstract3DRenderer::drawRadialGrid(ShaderHelper *shader, float yFloorLinePos,
1832 const QMatrix4x4 &projectionViewMatrix,
1833 const QMatrix4x4 &depthMatrix)
1834{
1835 static QVector<QQuaternion> lineRotations;
1836 if (!lineRotations.size()) {
1837 lineRotations.resize(asize: polarGridRoundness);
1838 for (int j = 0; j < polarGridRoundness; j++) {
1839 lineRotations[j] = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f,
1840 angle: polarGridAngleDegrees * float(j));
1841 }
1842 }
1843 int gridLineCount = m_axisCacheZ.gridLineCount();
1844 const QVector<float> &gridPositions = m_axisCacheZ.formatter()->gridPositions();
1845 const QVector<float> &subGridPositions = m_axisCacheZ.formatter()->subGridPositions();
1846 int mainSize = gridPositions.size();
1847 QVector3D translateVector(0.0f, yFloorLinePos, 0.0f);
1848 QQuaternion finalRotation = m_xRightAngleRotationNeg;
1849 if (m_yFlippedForGrid)
1850 finalRotation *= m_xFlipRotation;
1851
1852 for (int i = 0; i < gridLineCount; i++) {
1853 float gridPosition = (i >= mainSize)
1854 ? subGridPositions.at(i: i - mainSize) : gridPositions.at(i);
1855 float radiusFraction = m_polarRadius * gridPosition;
1856 QVector3D gridLineScaler(radiusFraction * float(qSin(v: polarGridHalfAngle)),
1857 gridLineWidth, gridLineWidth);
1858 translateVector.setZ(gridPosition * m_polarRadius);
1859 for (int j = 0; j < polarGridRoundness; j++) {
1860 QMatrix4x4 modelMatrix;
1861 QMatrix4x4 itModelMatrix;
1862 modelMatrix.rotate(quaternion: lineRotations.at(i: j));
1863 itModelMatrix.rotate(quaternion: lineRotations.at(i: j));
1864 modelMatrix.translate(vector: translateVector);
1865 modelMatrix.scale(vector: gridLineScaler);
1866 itModelMatrix.scale(vector: gridLineScaler);
1867 modelMatrix.rotate(quaternion: finalRotation);
1868 itModelMatrix.rotate(quaternion: finalRotation);
1869 QMatrix4x4 MVPMatrix = projectionViewMatrix * modelMatrix;
1870
1871 shader->setUniformValue(uniform: shader->model(), value: modelMatrix);
1872 shader->setUniformValue(uniform: shader->nModel(), value: itModelMatrix.inverted().transposed());
1873 shader->setUniformValue(uniform: shader->MVP(), value: MVPMatrix);
1874 if (!m_isOpenGLES) {
1875 if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
1876 // Set shadow shader bindings
1877 QMatrix4x4 depthMVPMatrix = depthMatrix * modelMatrix;
1878 shader->setUniformValue(uniform: shader->depth(), value: depthMVPMatrix);
1879 // Draw the object
1880 m_drawer->drawObject(shader, object: m_gridLineObj, textureId: 0, depthTextureId: m_depthTexture);
1881 } else {
1882 // Draw the object
1883 m_drawer->drawObject(shader, object: m_gridLineObj);
1884 }
1885 } else {
1886 m_drawer->drawLine(shader);
1887 }
1888 }
1889 }
1890}
1891
1892void Abstract3DRenderer::drawAngularGrid(ShaderHelper *shader, float yFloorLinePos,
1893 const QMatrix4x4 &projectionViewMatrix,
1894 const QMatrix4x4 &depthMatrix)
1895{
1896 float halfRatio((m_polarRadius + (labelMargin / 2.0f)) / 2.0f);
1897 QVector3D gridLineScaler(gridLineWidth, gridLineWidth, halfRatio);
1898 int gridLineCount = m_axisCacheX.gridLineCount();
1899 const QVector<float> &gridPositions = m_axisCacheX.formatter()->gridPositions();
1900 const QVector<float> &subGridPositions = m_axisCacheX.formatter()->subGridPositions();
1901 int mainSize = gridPositions.size();
1902 QVector3D translateVector(0.0f, yFloorLinePos, -halfRatio);
1903 QQuaternion finalRotation;
1904 if (m_isOpenGLES)
1905 finalRotation = m_yRightAngleRotationNeg;
1906 else
1907 finalRotation = m_xRightAngleRotationNeg;
1908 if (m_yFlippedForGrid)
1909 finalRotation *= m_xFlipRotation;
1910 for (int i = 0; i < gridLineCount; i++) {
1911 QMatrix4x4 modelMatrix;
1912 QMatrix4x4 itModelMatrix;
1913 float gridPosition = (i >= mainSize)
1914 ? subGridPositions.at(i: i - mainSize) : gridPositions.at(i);
1915 QQuaternion lineRotation = QQuaternion::fromAxisAndAngle(axis: upVector, angle: gridPosition * 360.0f);
1916 modelMatrix.rotate(quaternion: lineRotation);
1917 itModelMatrix.rotate(quaternion: lineRotation);
1918 modelMatrix.translate(vector: translateVector);
1919 modelMatrix.scale(vector: gridLineScaler);
1920 itModelMatrix.scale(vector: gridLineScaler);
1921 modelMatrix.rotate(quaternion: finalRotation);
1922 itModelMatrix.rotate(quaternion: finalRotation);
1923 QMatrix4x4 MVPMatrix = projectionViewMatrix * modelMatrix;
1924
1925 shader->setUniformValue(uniform: shader->model(), value: modelMatrix);
1926 shader->setUniformValue(uniform: shader->nModel(), value: itModelMatrix.inverted().transposed());
1927 shader->setUniformValue(uniform: shader->MVP(), value: MVPMatrix);
1928 if (m_isOpenGLES) {
1929 m_drawer->drawLine(shader);
1930 } else {
1931 if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
1932 // Set shadow shader bindings
1933 QMatrix4x4 depthMVPMatrix = depthMatrix * modelMatrix;
1934 shader->setUniformValue(uniform: shader->depth(), value: depthMVPMatrix);
1935 // Draw the object
1936 m_drawer->drawObject(shader, object: m_gridLineObj, textureId: 0, depthTextureId: m_depthTexture);
1937 } else {
1938 // Draw the object
1939 m_drawer->drawObject(shader, object: m_gridLineObj);
1940 }
1941 }
1942 }
1943}
1944
1945float Abstract3DRenderer::calculatePolarBackgroundMargin()
1946{
1947 // Check each extents of each angular label
1948 // Calculate angular position
1949 QVector<float> &labelPositions = m_axisCacheX.formatter()->labelPositions();
1950 float actualLabelHeight = m_drawer->scaledFontSize() * 2.0f; // All labels are same height
1951 float maxNeededMargin = 0.0f;
1952
1953 // Axis title needs to be accounted for
1954 if (m_axisCacheX.isTitleVisible())
1955 maxNeededMargin = 2.0f * actualLabelHeight + 3.0f * labelMargin;
1956
1957 for (int label = 0; label < labelPositions.size(); label++) {
1958 QSize labelSize = m_axisCacheX.labelItems().at(i: label)->size();
1959 float actualLabelWidth = actualLabelHeight / labelSize.height() * labelSize.width();
1960 float labelPosition = labelPositions.at(i: label);
1961 qreal angle = labelPosition * M_PI * 2.0;
1962 float x = qAbs(t: (m_polarRadius + labelMargin) * float(qSin(v: angle)))
1963 + actualLabelWidth - m_polarRadius + labelMargin;
1964 float z = qAbs(t: -(m_polarRadius + labelMargin) * float(qCos(v: angle)))
1965 + actualLabelHeight - m_polarRadius + labelMargin;
1966 float neededMargin = qMax(a: x, b: z);
1967 maxNeededMargin = qMax(a: maxNeededMargin, b: neededMargin);
1968 }
1969
1970 return maxNeededMargin;
1971}
1972
1973void Abstract3DRenderer::updateCameraViewport()
1974{
1975 QVector3D adjustedTarget = m_cachedScene->activeCamera()->target();
1976 fixCameraTarget(target&: adjustedTarget);
1977 if (m_oldCameraTarget != adjustedTarget) {
1978 QVector3D cameraBase = cameraDistanceVector + adjustedTarget;
1979
1980 m_cachedScene->activeCamera()->d_ptr->setBaseOrientation(defaultPosition: cameraBase,
1981 defaultTarget: adjustedTarget,
1982 defaultUp: upVector);
1983 m_oldCameraTarget = adjustedTarget;
1984 }
1985 m_cachedScene->activeCamera()->d_ptr->updateViewMatrix(zoomAdjustment: m_autoScaleAdjustment);
1986 // Set light position (i.e rotate light with activeCamera, a bit above it).
1987 // Check if we want to use automatic light positioning even without shadows
1988 if (m_cachedScene->activeLight()->isAutoPosition()
1989 || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
1990 m_cachedScene->d_ptr->setLightPositionRelativeToCamera(relativePosition: defaultLightPos);
1991 }
1992}
1993
1994QT_END_NAMESPACE_DATAVISUALIZATION
1995

source code of qtdatavis3d/src/datavisualization/engine/abstract3drenderer.cpp