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

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