Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
++
++ #include "abstract3drenderer_p.h"
++ #include "texturehelper_p.h"
++ #include "q3dcamera_p.h"
++ #include "q3dtheme_p.h"
++ #include "qvalue3daxisformatter_p.h"
++ #include "shaderhelper_p.h"
++ #include "qcustom3ditem_p.h"
++ #include "qcustom3dlabel_p.h"
++ #include "qcustom3dvolume_p.h"
++ #include "scatter3drenderer_p.h"
++
++ #include <QtCore/qmath.h>
++ #include <QtGui/QOffscreenSurface>
++ #include <QtCore/QThread>
++
++ QT_BEGIN_NAMESPACE
++
++ // Defined in shaderhelper.cpp
++ extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg);
++
++ const qreal doublePi(M_PI * 2.0);
++ const int polarGridRoundness(64);
++ const qreal polarGridAngle(doublePi / qreal(polarGridRoundness));
++ const float polarGridAngleDegrees(float(360.0 / qreal(polarGridRoundness)));
++ const qreal polarGridHalfAngle(polarGridAngle / 2.0);
++
++ Abstract3DRenderer::Abstract3DRenderer(Abstract3DController *controller)
++ : QObject(0),
++ m_hasNegativeValues(false),
++ m_cachedTheme(new Q3DTheme()),
++ m_drawer(new Drawer(m_cachedTheme)),
++ m_cachedShadowQuality(QAbstract3DGraph::ShadowQualityMedium),
++ m_autoScaleAdjustment(1.0f),
++ m_cachedSelectionMode(QAbstract3DGraph::SelectionNone),
++ m_cachedOptimizationHint(QAbstract3DGraph::OptimizationDefault),
++ m_textureHelper(0),
++ m_depthTexture(0),
++ m_cachedScene(new Q3DScene()),
++ m_selectionDirty(true),
++ m_selectionState(SelectNone),
++ m_devicePixelRatio(1.0f),
++ m_selectionLabelDirty(true),
++ m_clickResolved(false),
++ m_graphPositionQueryPending(false),
++ m_graphPositionQueryResolved(false),
++ m_clickedSeries(0),
++ m_clickedType(QAbstract3DGraph::ElementNone),
++ m_selectedLabelIndex(-1),
++ m_selectedCustomItemIndex(-1),
++ m_selectionLabelItem(0),
++ m_visibleSeriesCount(0),
++ m_customItemShader(0),
++ m_volumeTextureShader(0),
++ m_volumeTextureLowDefShader(0),
++ m_volumeTextureSliceShader(0),
++ m_volumeSliceFrameShader(0),
++ m_labelShader(0),
++ m_cursorPositionShader(0),
++ m_cursorPositionFrameBuffer(0),
++ m_cursorPositionTexture(0),
++ m_useOrthoProjection(false),
++ m_xFlipped(false),
++ m_yFlipped(false),
++ m_zFlipped(false),
++ m_yFlippedForGrid(false),
++ m_backgroundObj(0),
++ m_gridLineObj(0),
++ m_labelObj(0),
++ m_positionMapperObj(0),
++ m_graphAspectRatio(2.0f),
++ m_graphHorizontalAspectRatio(0.0f),
++ m_polarGraph(false),
++ m_radialLabelOffset(1.0f),
++ m_polarRadius(2.0f),
++ m_xRightAngleRotation(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f)),
++ m_yRightAngleRotation(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f)),
++ m_zRightAngleRotation(QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f)),
++ m_xRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f)),
++ m_yRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f)),
++ m_zRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -90.0f)),
++ m_xFlipRotation(QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -180.0f)),
++ m_zFlipRotation(QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -180.0f)),
++ m_requestedMargin(-1.0f),
++ m_vBackgroundMargin(0.1f),
++ m_hBackgroundMargin(0.1f),
++ m_scaleXWithBackground(0.0f),
++ m_scaleYWithBackground(0.0f),
++ m_scaleZWithBackground(0.0f),
++ m_oldCameraTarget(QVector3D(2000.0f, 2000.0f, 2000.0f)), // Just random invalid target
++ m_reflectionEnabled(false),
++ m_reflectivity(0.5),
++ #if !QT_CONFIG(opengles2)
++ m_funcs_2_1(0),
++ #endif
++ m_context(0),
++ m_isOpenGLES(true)
++
++ {
++ initializeOpenGLFunctions();
++ m_isOpenGLES = Utils::isOpenGLES();
++ #if !QT_CONFIG(opengles2)
++ if (!m_isOpenGLES) {
++ // Discard warnings about deprecated functions
++ QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs);
++
++ m_funcs_2_1 = new QOpenGLFunctions_2_1;
++ if (m_funcs_2_1)
++ m_funcs_2_1->initializeOpenGLFunctions();
++
++ // Restore original message handler
++ qInstallMessageHandler(handler);
++
++ if (!m_funcs_2_1)
++ qFatal("OpenGL version is too low, at least 2.1 is required");
++ }
++ #endif
++ QObject::connect(m_drawer, &Drawer::drawerChanged, this, &Abstract3DRenderer::updateTextures);
++ QObject::connect(this, &Abstract3DRenderer::needRender, controller,
++ &Abstract3DController::needRender, Qt::QueuedConnection);
++ QObject::connect(this, &Abstract3DRenderer::requestShadowQuality, controller,
++ &Abstract3DController::handleRequestShadowQuality, Qt::QueuedConnection);
++ }
++
++ Abstract3DRenderer::~Abstract3DRenderer()
++ {
++ contextCleanup();
++ delete m_drawer;
++ delete m_cachedScene;
++ delete m_cachedTheme;
++ delete m_selectionLabelItem;
++ delete m_customItemShader;
++ delete m_volumeTextureShader;
++ delete m_volumeTextureLowDefShader;
++ delete m_volumeSliceFrameShader;
++ delete m_volumeTextureSliceShader;
++ delete m_labelShader;
++ delete m_cursorPositionShader;
++
++ for (auto cache: m_renderCacheList) {
++ cache->cleanup(m_textureHelper);
++ delete cache;
++ }
++ m_renderCacheList.clear();
++
++ for (auto item: m_customRenderCache) {
++ GLuint texture = item->texture();
++ m_textureHelper->deleteTexture(&texture);
++ delete item;
++ }
++ m_customRenderCache.clear();
++
++ ObjectHelper::releaseObjectHelper(this, m_backgroundObj);
++ ObjectHelper::releaseObjectHelper(this, m_gridLineObj);
++ ObjectHelper::releaseObjectHelper(this, m_labelObj);
++ ObjectHelper::releaseObjectHelper(this, m_positionMapperObj);
++
++ if (m_textureHelper) {
++ m_textureHelper->deleteTexture(&m_depthTexture);
++ m_textureHelper->deleteTexture(&m_cursorPositionTexture);
++ delete m_textureHelper;
++ }
++
++ m_axisCacheX.clearLabels();
++ m_axisCacheY.clearLabels();
++ m_axisCacheZ.clearLabels();
++
++ #if !QT_CONFIG(opengles2)
++ delete m_funcs_2_1;
++ #endif
++ }
++
++ void Abstract3DRenderer::contextCleanup()
++ {
++ if (QOpenGLContext::currentContext())
++ m_textureHelper->glDeleteFramebuffers(1, &m_cursorPositionFrameBuffer);
++ }
++
++ void Abstract3DRenderer::initializeOpenGL()
++ {
++ m_context = QOpenGLContext::currentContext();
++
++ // Set OpenGL features
++ glEnable(GL_DEPTH_TEST);
++ glDepthFunc(GL_LESS);
++ glEnable(GL_CULL_FACE);
++ glCullFace(GL_BACK);
++
++ #if !QT_CONFIG(opengles2)
++ if (!m_isOpenGLES) {
++ glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
++ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
++ glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
++ }
++ #endif
++
++ m_textureHelper = new TextureHelper();
++ m_drawer->initializeOpenGL();
++
++ axisCacheForOrientation(QAbstract3DAxis::AxisOrientationX).setDrawer(m_drawer);
++ axisCacheForOrientation(QAbstract3DAxis::AxisOrientationY).setDrawer(m_drawer);
++ axisCacheForOrientation(QAbstract3DAxis::AxisOrientationZ).setDrawer(m_drawer);
++
++ initLabelShaders(QStringLiteral(":/shaders/vertexLabel"),
++ QStringLiteral(":/shaders/fragmentLabel"));
++
++ initCursorPositionShaders(QStringLiteral(":/shaders/vertexPosition"),
++ QStringLiteral(":/shaders/fragmentPositionMap"));
++
++ loadLabelMesh();
++ loadPositionMapperMesh();
++
++ QObject::connect(m_context.data(), &QOpenGLContext::aboutToBeDestroyed,
++ this, &Abstract3DRenderer::contextCleanup);
++ }
++
++ void Abstract3DRenderer::render(const GLuint defaultFboHandle)
++ {
++ if (defaultFboHandle) {
++ glDepthMask(true);
++ glEnable(GL_DEPTH_TEST);
++ glDepthFunc(GL_LESS);
++ glEnable(GL_CULL_FACE);
++ glCullFace(GL_BACK);
++ glDisable(GL_BLEND); // For QtQuick2 blending is enabled by default, but we don't want it to be
++ }
++
++ // Clear the graph background to the theme color
++ glViewport(m_viewport.x(),
++ m_viewport.y(),
++ m_viewport.width(),
++ m_viewport.height());
++ glScissor(m_viewport.x(),
++ m_viewport.y(),
++ m_viewport.width(),
++ m_viewport.height());
++ glEnable(GL_SCISSOR_TEST);
++ QVector4D clearColor = Utils::vectorFromColor(m_cachedTheme->windowColor());
++ glClearColor(clearColor.x(), clearColor.y(), clearColor.z(), 1.0f);
++ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
++ glDisable(GL_SCISSOR_TEST);
++ }
++
++ void Abstract3DRenderer::updateSelectionState(SelectionState state)
++ {
++ m_selectionState = state;
++ }
++
++ void Abstract3DRenderer::initGradientShaders(const QString &vertexShader,
++ const QString &fragmentShader)
++ {
++ // Do nothing by default
++ Q_UNUSED(vertexShader);
++ Q_UNUSED(fragmentShader);
++ }
++
++ void Abstract3DRenderer::initStaticSelectedItemShaders(const QString &vertexShader,
++ const QString &fragmentShader,
++ const QString &gradientVertexShader,
++ const QString &gradientFragmentShader)
++ {
++ // Do nothing by default
++ Q_UNUSED(vertexShader);
++ Q_UNUSED(fragmentShader);
++ Q_UNUSED(gradientVertexShader);
++ Q_UNUSED(gradientFragmentShader);
++ }
++
++ void Abstract3DRenderer::initCustomItemShaders(const QString &vertexShader,
++ const QString &fragmentShader)
++ {
++ delete m_customItemShader;
++ m_customItemShader = new ShaderHelper(this, vertexShader, fragmentShader);
++ m_customItemShader->initialize();
++ }
++
++ void Abstract3DRenderer::initVolumeTextureShaders(const QString &vertexShader,
++ const QString &fragmentShader,
++ const QString &fragmentLowDefShader,
++ const QString &sliceShader,
++ const QString &sliceFrameVertexShader,
++ const QString &sliceFrameShader)
++ {
++
++ delete m_volumeTextureShader;
++ m_volumeTextureShader = new ShaderHelper(this, vertexShader, fragmentShader);
++ m_volumeTextureShader->initialize();
++
++ delete m_volumeTextureLowDefShader;
++ m_volumeTextureLowDefShader = new ShaderHelper(this, vertexShader, fragmentLowDefShader);
++ m_volumeTextureLowDefShader->initialize();
++
++ delete m_volumeTextureSliceShader;
++ m_volumeTextureSliceShader = new ShaderHelper(this, vertexShader, sliceShader);
++ m_volumeTextureSliceShader->initialize();
++
++ delete m_volumeSliceFrameShader;
++ m_volumeSliceFrameShader = new ShaderHelper(this, sliceFrameVertexShader, sliceFrameShader);
++ m_volumeSliceFrameShader->initialize();
++ }
++
++ void Abstract3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader)
++ {
++ delete m_labelShader;
++ m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader);
++ m_labelShader->initialize();
++ }
++
++ void Abstract3DRenderer::initCursorPositionShaders(const QString &vertexShader,
++ const QString &fragmentShader)
++ {
++ // Init the shader
++ delete m_cursorPositionShader;
++ m_cursorPositionShader = new ShaderHelper(this, vertexShader, fragmentShader);
++ m_cursorPositionShader->initialize();
++ }
++
++ void Abstract3DRenderer::initCursorPositionBuffer()
++ {
++ m_textureHelper->deleteTexture(&m_cursorPositionTexture);
++ m_textureHelper->glDeleteFramebuffers(1, &m_cursorPositionFrameBuffer);
++ m_cursorPositionFrameBuffer = 0;
++
++ if (m_primarySubViewport.size().isEmpty())
++ return;
++
++ m_cursorPositionTexture =
++ m_textureHelper->createCursorPositionTexture(m_primarySubViewport.size(),
++ m_cursorPositionFrameBuffer);
++ }
++
++ void Abstract3DRenderer::updateTheme(Q3DTheme *theme)
++ {
++ // Synchronize the controller theme with renderer
++ bool updateDrawer = theme->d_ptr->sync(*m_cachedTheme->d_ptr);
++
++ if (updateDrawer)
++ m_drawer->setTheme(m_cachedTheme);
++ }
++
++ void Abstract3DRenderer::updateScene(Q3DScene *scene)
++ {
++ m_viewport = scene->d_ptr->glViewport();
++ m_secondarySubViewport = scene->d_ptr->glSecondarySubViewport();
++
++ if (m_primarySubViewport != scene->d_ptr->glPrimarySubViewport()) {
++ // Resize of primary subviewport means resizing shadow and selection buffers
++ m_primarySubViewport = scene->d_ptr->glPrimarySubViewport();
++ handleResize();
++ }
++
++ if (m_devicePixelRatio != scene->devicePixelRatio()) {
++ m_devicePixelRatio = scene->devicePixelRatio();
++ handleResize();
++ }
++
++ QPoint logicalPixelPosition = scene->selectionQueryPosition();
++ m_inputPosition = QPoint(logicalPixelPosition.x() * m_devicePixelRatio,
++ logicalPixelPosition.y() * m_devicePixelRatio);
++
++ QPoint logicalGraphPosition = scene->graphPositionQuery();
++ m_graphPositionQuery = QPoint(logicalGraphPosition.x() * m_devicePixelRatio,
++ logicalGraphPosition.y() * m_devicePixelRatio);
++
++ // Synchronize the renderer scene to controller scene
++ scene->d_ptr->sync(*m_cachedScene->d_ptr);
++
++ updateCameraViewport();
++
++ if (Q3DScene::invalidSelectionPoint() == logicalPixelPosition) {
++ updateSelectionState(SelectNone);
++ } else {
++ if (scene->isSlicingActive()) {
++ if (scene->isPointInPrimarySubView(logicalPixelPosition))
++ updateSelectionState(SelectOnOverview);
++ else if (scene->isPointInSecondarySubView(logicalPixelPosition))
++ updateSelectionState(SelectOnSlice);
++ else
++ updateSelectionState(SelectNone);
++ } else {
++ updateSelectionState(SelectOnScene);
++ }
++ }
++
++ if (Q3DScene::invalidSelectionPoint() != logicalGraphPosition)
++ m_graphPositionQueryPending = true;
++
++ // Queue up another render when we have a query that needs resolving.
++ // This is needed because QtQuick scene graph can sometimes do a sync without following it up
++ // with a render.
++ if (m_graphPositionQueryPending || m_selectionState != SelectNone)
++ emit needRender();
++ }
++
++ void Abstract3DRenderer::updateTextures()
++ {
++ m_axisCacheX.updateTextures();
++ m_axisCacheY.updateTextures();
++ m_axisCacheZ.updateTextures();
++ }
++
++ void Abstract3DRenderer::reInitShaders()
++ {
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)
++ && qobject_cast<Scatter3DRenderer *>(this)) {
++ initGradientShaders(QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadow"));
++ initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadowNoTex"),
++ QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY"));
++ initShaders(QStringLiteral(":/shaders/vertexShadowNoMatrices"),
++ QStringLiteral(":/shaders/fragmentShadowNoTex"));
++ } else {
++ initGradientShaders(QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadowNoTexColorOnY"));
++ initShaders(QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadowNoTex"));
++ }
++ initBackgroundShaders(QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadowNoTex"));
++ initCustomItemShaders(QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentShadow"));
++ } else {
++ if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)
++ && qobject_cast<Scatter3DRenderer *>(this)) {
++ initGradientShaders(QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTexture"));
++ initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragment"),
++ QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentColorOnY"));
++ initShaders(QStringLiteral(":/shaders/vertexNoMatrices"),
++ QStringLiteral(":/shaders/fragment"));
++ } else {
++ initGradientShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentColorOnY"));
++ initShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragment"));
++ }
++ initBackgroundShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragment"));
++ initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTexture"));
++ }
++ initVolumeTextureShaders(QStringLiteral(":/shaders/vertexTexture3D"),
++ QStringLiteral(":/shaders/fragmentTexture3D"),
++ QStringLiteral(":/shaders/fragmentTexture3DLowDef"),
++ QStringLiteral(":/shaders/fragmentTexture3DSlice"),
++ QStringLiteral(":/shaders/vertexPosition"),
++ QStringLiteral(":/shaders/fragment3DSliceFrames"));
++ } else {
++ if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)
++ && qobject_cast<Scatter3DRenderer *>(this)) {
++ initGradientShaders(QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTextureES2"));
++ initStaticSelectedItemShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentES2"),
++ QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentColorOnYES2"));
++ initShaders(QStringLiteral(":/shaders/vertexNoMatrices"),
++ QStringLiteral(":/shaders/fragmentES2"));
++ } else {
++ initGradientShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentColorOnYES2"));
++ initShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentES2"));
++ }
++ initBackgroundShaders(QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentES2"));
++ initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTextureES2"));
++ }
++ }
++
++ void Abstract3DRenderer::handleShadowQualityChange()
++ {
++ reInitShaders();
++
++ if (m_cachedScene->activeLight()->isAutoPosition()
++ || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ m_cachedScene->d_ptr->setLightPositionRelativeToCamera(defaultLightPos);
++ emit needRender();
++ }
++ if (m_isOpenGLES && m_cachedShadowQuality != QAbstract3DGraph::ShadowQualityNone) {
++ emit requestShadowQuality(QAbstract3DGraph::ShadowQualityNone);
++ qWarning("Shadows are not yet supported for OpenGL ES2");
++ m_cachedShadowQuality = QAbstract3DGraph::ShadowQualityNone;
++ }
++ }
++
++ void Abstract3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode)
++ {
++ m_cachedSelectionMode = mode;
++ m_selectionDirty = true;
++ }
++
++ void Abstract3DRenderer::updateAspectRatio(float ratio)
++ {
++ m_graphAspectRatio = ratio;
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::updateHorizontalAspectRatio(float ratio)
++ {
++ m_graphHorizontalAspectRatio = ratio;
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::updatePolar(bool enable)
++ {
++ m_polarGraph = enable;
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::updateRadialLabelOffset(float offset)
++ {
++ m_radialLabelOffset = offset;
++ }
++
++ void Abstract3DRenderer::updateMargin(float margin)
++ {
++ m_requestedMargin = margin;
++ }
++
++ void Abstract3DRenderer::updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint)
++ {
++ m_cachedOptimizationHint = hint;
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::handleResize()
++ {
++ if (m_primarySubViewport.width() == 0 || m_primarySubViewport.height() == 0)
++ return;
++
++ // Recalculate zoom
++ calculateZoomLevel();
++
++ // Re-init selection buffer
++ initSelectionBuffer();
++
++ // Re-init depth buffer
++ updateDepthBuffer();
++
++ initCursorPositionBuffer();
++ }
++
++ void Abstract3DRenderer::calculateZoomLevel()
++ {
++ // Calculate zoom level based on aspect ratio
++ GLfloat div;
++ GLfloat zoomAdjustment;
++ div = qMin(m_primarySubViewport.width(), m_primarySubViewport.height());
++ zoomAdjustment = defaultRatio
++ * ((m_primarySubViewport.width() / div)
++ / (m_primarySubViewport.height() / div));
++ m_autoScaleAdjustment = qMin(zoomAdjustment, 1.0f); // clamp to 1.0f
++ }
++
++ void Abstract3DRenderer::updateAxisType(QAbstract3DAxis::AxisOrientation orientation,
++ QAbstract3DAxis::AxisType type)
++ {
++ axisCacheForOrientation(orientation).setType(type);
++ }
++
++ void Abstract3DRenderer::updateAxisTitle(QAbstract3DAxis::AxisOrientation orientation,
++ const QString &title)
++ {
++ axisCacheForOrientation(orientation).setTitle(title);
++ }
++
++ void Abstract3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation,
++ const QStringList &labels)
++ {
++ axisCacheForOrientation(orientation).setLabels(labels);
++ }
++
++ void Abstract3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orientation,
++ float min, float max)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ cache.setMin(min);
++ cache.setMax(max);
++
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::updateAxisSegmentCount(QAbstract3DAxis::AxisOrientation orientation,
++ int count)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ cache.setSegmentCount(count);
++ }
++
++ void Abstract3DRenderer::updateAxisSubSegmentCount(QAbstract3DAxis::AxisOrientation orientation,
++ int count)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ cache.setSubSegmentCount(count);
++ }
++
++ void Abstract3DRenderer::updateAxisLabelFormat(QAbstract3DAxis::AxisOrientation orientation,
++ const QString &format)
++ {
++ axisCacheForOrientation(orientation).setLabelFormat(format);
++ }
++
++ void Abstract3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation orientation,
++ bool enable)
++ {
++ axisCacheForOrientation(orientation).setReversed(enable);
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::updateAxisFormatter(QAbstract3DAxis::AxisOrientation orientation,
++ QValue3DAxisFormatter *formatter)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ if (cache.ctrlFormatter() != formatter) {
++ delete cache.formatter();
++ cache.setFormatter(formatter->createNewInstance());
++ cache.setCtrlFormatter(formatter);
++ }
++ formatter->d_ptr->populateCopy(*(cache.formatter()));
++ cache.markPositionsDirty();
++
++ for (auto cache: m_renderCacheList)
++ cache->setDataDirty(true);
++ }
++
++ void Abstract3DRenderer::updateAxisLabelAutoRotation(QAbstract3DAxis::AxisOrientation orientation,
++ float angle)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ if (cache.labelAutoRotation() != angle)
++ cache.setLabelAutoRotation(angle);
++ }
++
++ void Abstract3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation,
++ bool visible)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ if (cache.isTitleVisible() != visible)
++ cache.setTitleVisible(visible);
++ }
++
++ void Abstract3DRenderer::updateAxisTitleFixed(QAbstract3DAxis::AxisOrientation orientation,
++ bool fixed)
++ {
++ AxisRenderCache &cache = axisCacheForOrientation(orientation);
++ if (cache.isTitleFixed() != fixed)
++ cache.setTitleFixed(fixed);
++ }
++
++ void Abstract3DRenderer::modifiedSeriesList(const QList<QAbstract3DSeries *> &seriesList)
++ {
++ for (auto series: seriesList) {
++ SeriesRenderCache *cache = m_renderCacheList.value(series, 0);
++ if (cache)
++ cache->setDataDirty(true);
++ }
++ }
++
++ void Abstract3DRenderer::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh)
++ {
++ // Default implementation does nothing.
++ Q_UNUSED(fileName);
++ Q_UNUSED(mesh);
++ }
++
++ void Abstract3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList)
++ {
++ for (auto cache: m_renderCacheList)
++ cache->setValid(false);
++
++ m_visibleSeriesCount = 0;
++ int seriesCount = seriesList.size();
++ for (int i = 0; i < seriesCount; i++) {
++ QAbstract3DSeries *series = seriesList.at(i);
++ SeriesRenderCache *cache = m_renderCacheList.value(series);
++ bool newSeries = false;
++ if (!cache) {
++ cache = createNewCache(series);
++ m_renderCacheList[series] = cache;
++ newSeries = true;
++ }
++ cache->setValid(true);
++ cache->populate(newSeries);
++ if (cache->isVisible())
++ m_visibleSeriesCount++;
++ }
++
++ // Remove non-valid objects from the cache list
++ for (auto cache: m_renderCacheList) {
++ if (!cache->isValid())
++ cleanCache(cache);
++ }
++ }
++
++ void Abstract3DRenderer::updateCustomData(const QList<QCustom3DItem *> &customItems)
++ {
++ if (customItems.isEmpty() && m_customRenderCache.isEmpty())
++ return;
++
++ for (auto item: m_customRenderCache)
++ item->setValid(false);
++
++ int itemCount = customItems.size();
++ // Check custom item list for items that are not yet in render item cache
++ for (int i = 0; i < itemCount; i++) {
++ QCustom3DItem *item = customItems.at(i);
++ CustomRenderItem *renderItem = m_customRenderCache.value(item);
++ if (!renderItem)
++ renderItem = addCustomItem(item);
++ if (renderItem) {
++ renderItem->setValid(true);
++ renderItem->setIndex(i); // always update index, as it must match the custom item index
++ }
++ }
++
++ // Check render item cache and remove items that are not in customItems list anymore
++ for (auto renderItem: m_customRenderCache) {
++ if (!renderItem->isValid()) {
++ m_customRenderCache.remove(renderItem->itemPointer());
++ GLuint texture = renderItem->texture();
++ m_textureHelper->deleteTexture(&texture);
++ delete renderItem;
++ }
++ }
++
++ m_customItemDrawOrder.clear();
++ m_customItemDrawOrder = QList<QCustom3DItem *>(customItems);
++ }
++
++ void Abstract3DRenderer::updateCustomItems()
++ {
++ // Check all items
++ for (auto item: m_customRenderCache)
++ updateCustomItem(item);
++ }
++
++ SeriesRenderCache *Abstract3DRenderer::createNewCache(QAbstract3DSeries *series)
++ {
++ return new SeriesRenderCache(series, this);
++ }
++
++ void Abstract3DRenderer::cleanCache(SeriesRenderCache *cache)
++ {
++ m_renderCacheList.remove(cache->series());
++ cache->cleanup(m_textureHelper);
++ delete cache;
++ }
++
++ AxisRenderCache &Abstract3DRenderer::axisCacheForOrientation(
++ QAbstract3DAxis::AxisOrientation orientation)
++ {
++ switch (orientation) {
++ case QAbstract3DAxis::AxisOrientationX:
++ return m_axisCacheX;
++ case QAbstract3DAxis::AxisOrientationY:
++ return m_axisCacheY;
++ case QAbstract3DAxis::AxisOrientationZ:
++ return m_axisCacheZ;
++ default:
++ qFatal("Abstract3DRenderer::axisCacheForOrientation");
++ return m_axisCacheX;
++ }
++ }
++
++ void Abstract3DRenderer::lowerShadowQuality()
++ {
++ QAbstract3DGraph::ShadowQuality newQuality = QAbstract3DGraph::ShadowQualityNone;
++
++ switch (m_cachedShadowQuality) {
++ case QAbstract3DGraph::ShadowQualityHigh:
++ qWarning("Creating high quality shadows failed. Changing to medium quality.");
++ newQuality = QAbstract3DGraph::ShadowQualityMedium;
++ break;
++ case QAbstract3DGraph::ShadowQualityMedium:
++ qWarning("Creating medium quality shadows failed. Changing to low quality.");
++ newQuality = QAbstract3DGraph::ShadowQualityLow;
++ break;
++ case QAbstract3DGraph::ShadowQualityLow:
++ qWarning("Creating low quality shadows failed. Switching shadows off.");
++ newQuality = QAbstract3DGraph::ShadowQualityNone;
++ break;
++ case QAbstract3DGraph::ShadowQualitySoftHigh:
++ qWarning("Creating soft high quality shadows failed. Changing to soft medium quality.");
++ newQuality = QAbstract3DGraph::ShadowQualitySoftMedium;
++ break;
++ case QAbstract3DGraph::ShadowQualitySoftMedium:
++ qWarning("Creating soft medium quality shadows failed. Changing to soft low quality.");
++ newQuality = QAbstract3DGraph::ShadowQualitySoftLow;
++ break;
++ case QAbstract3DGraph::ShadowQualitySoftLow:
++ qWarning("Creating soft low quality shadows failed. Switching shadows off.");
++ newQuality = QAbstract3DGraph::ShadowQualityNone;
++ break;
++ default:
++ // You'll never get here
++ break;
++ }
++
++ emit requestShadowQuality(newQuality);
++ updateShadowQuality(newQuality);
++ }
++
++ void Abstract3DRenderer::drawAxisTitleY(const QVector3D &sideLabelRotation,
++ const QVector3D &backLabelRotation,
++ const QVector3D &sideLabelTrans,
++ const QVector3D &backLabelTrans,
++ const QQuaternion &totalSideRotation,
++ const QQuaternion &totalBackRotation,
++ AbstractRenderItem &dummyItem,
++ const Q3DCamera *activeCamera,
++ float labelsMaxWidth,
++ const QMatrix4x4 &viewMatrix,
++ const QMatrix4x4 &projectionMatrix,
++ ShaderHelper *shader)
++ {
++ float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheY.titleItem().size().height();
++ float titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor));
++ float yRotation;
++ QVector3D titleTrans;
++ QQuaternion totalRotation;
++ if (m_xFlipped == m_zFlipped) {
++ yRotation = backLabelRotation.y();
++ titleTrans = backLabelTrans;
++ totalRotation = totalBackRotation;
++ } else {
++ yRotation = sideLabelRotation.y();
++ titleTrans = sideLabelTrans;
++ totalRotation = totalSideRotation;
++ }
++
++ QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation);
++ QVector3D titleOffsetVector =
++ offsetRotator.rotatedVector(QVector3D(-titleOffset, 0.0f, 0.0f));
++
++ QQuaternion titleRotation;
++ if (m_axisCacheY.isTitleFixed()) {
++ titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation)
++ * m_zRightAngleRotation;
++ } else {
++ titleRotation = totalRotation * m_zRightAngleRotation;
++ }
++ dummyItem.setTranslation(titleTrans + titleOffsetVector);
++
++ m_drawer->drawLabel(dummyItem, m_axisCacheY.titleItem(), viewMatrix,
++ projectionMatrix, zeroVector, titleRotation, 0,
++ m_cachedSelectionMode, shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, Qt::AlignBottom);
++ }
++
++ void Abstract3DRenderer::drawAxisTitleX(const QVector3D &labelRotation,
++ const QVector3D &labelTrans,
++ const QQuaternion &totalRotation,
++ AbstractRenderItem &dummyItem,
++ const Q3DCamera *activeCamera,
++ float labelsMaxWidth,
++ const QMatrix4x4 &viewMatrix,
++ const QMatrix4x4 &projectionMatrix,
++ ShaderHelper *shader,
++ bool radial)
++ {
++ float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheX.titleItem().size().height();
++ float titleOffset;
++ if (radial)
++ titleOffset = -2.0f * (labelMargin + m_drawer->scaledFontSize());
++ else
++ titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor));
++ float zRotation = 0.0f;
++ float yRotation = 0.0f;
++ float xRotation = -90.0f + labelRotation.z();
++ float offsetRotation = labelRotation.z();
++ float extraRotation = -90.0f;
++ Qt::AlignmentFlag alignment = Qt::AlignTop;
++ if (m_yFlippedForGrid) {
++ alignment = Qt::AlignBottom;
++ zRotation = 180.0f;
++ if (m_zFlipped) {
++ titleOffset = -titleOffset;
++ if (m_xFlipped) {
++ offsetRotation = -offsetRotation;
++ extraRotation = -extraRotation;
++ } else {
++ xRotation = -90.0f - labelRotation.z();
++ }
++ } else {
++ yRotation = 180.0f;
++ if (m_xFlipped) {
++ offsetRotation = -offsetRotation;
++ xRotation = -90.0f - labelRotation.z();
++ } else {
++ extraRotation = -extraRotation;
++ }
++ }
++ } else {
++ if (m_zFlipped) {
++ titleOffset = -titleOffset;
++ if (m_xFlipped) {
++ yRotation = 180.0f;
++ offsetRotation = -offsetRotation;
++ } else {
++ yRotation = 180.0f;
++ xRotation = -90.0f - labelRotation.z();
++ extraRotation = -extraRotation;
++ }
++ if (m_yFlipped) {
++ alignment = Qt::AlignBottom;
++ extraRotation = -extraRotation;
++ if (m_xFlipped)
++ xRotation = 90.0f + labelRotation.z();
++ else
++ xRotation = 90.0f - labelRotation.z();
++ }
++ } else {
++ if (m_xFlipped) {
++ offsetRotation = -offsetRotation;
++ xRotation = -90.0f - labelRotation.z();
++ extraRotation = -extraRotation;
++ }
++ if (m_yFlipped) {
++ xRotation = 90.0f + labelRotation.z();
++ alignment = Qt::AlignBottom;
++ extraRotation = -extraRotation;
++ if (m_xFlipped)
++ xRotation = 90.0f - labelRotation.z();
++ }
++ }
++ }
++
++ if (radial) {
++ if (m_zFlipped) {
++ titleOffset = -titleOffset;
++ } else {
++ if (m_yFlippedForGrid)
++ alignment = Qt::AlignTop;
++ else
++ alignment = Qt::AlignBottom;
++ }
++ }
++
++ if (offsetRotation == 180.0f || offsetRotation == -180.0f)
++ offsetRotation = 0.0f;
++ QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, offsetRotation);
++ QVector3D titleOffsetVector =
++ offsetRotator.rotatedVector(QVector3D(0.0f, 0.0f, titleOffset));
++
++ QQuaternion titleRotation;
++ if (m_axisCacheX.isTitleFixed()) {
++ titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, zRotation)
++ * QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation)
++ * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xRotation);
++ } else {
++ titleRotation = totalRotation
++ * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, extraRotation);
++ }
++ dummyItem.setTranslation(labelTrans + titleOffsetVector);
++
++ m_drawer->drawLabel(dummyItem, m_axisCacheX.titleItem(), viewMatrix,
++ projectionMatrix, zeroVector, titleRotation, 0,
++ m_cachedSelectionMode, shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, alignment);
++ }
++
++ void Abstract3DRenderer::drawAxisTitleZ(const QVector3D &labelRotation,
++ const QVector3D &labelTrans,
++ const QQuaternion &totalRotation,
++ AbstractRenderItem &dummyItem,
++ const Q3DCamera *activeCamera,
++ float labelsMaxWidth,
++ const QMatrix4x4 &viewMatrix,
++ const QMatrix4x4 &projectionMatrix,
++ ShaderHelper *shader)
++ {
++ float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheZ.titleItem().size().height();
++ float titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor));
++ float zRotation = labelRotation.z();
++ float yRotation = -90.0f;
++ float xRotation = -90.0f;
++ float extraRotation = 90.0f;
++ Qt::AlignmentFlag alignment = Qt::AlignTop;
++ if (m_yFlippedForGrid) {
++ alignment = Qt::AlignBottom;
++ xRotation = -xRotation;
++ if (m_zFlipped) {
++ if (m_xFlipped) {
++ titleOffset = -titleOffset;
++ zRotation = -zRotation;
++ extraRotation = -extraRotation;
++ } else {
++ zRotation = -zRotation;
++ yRotation = -yRotation;
++ }
++ } else {
++ if (m_xFlipped) {
++ titleOffset = -titleOffset;
++ } else {
++ extraRotation = -extraRotation;
++ yRotation = -yRotation;
++ }
++ }
++ } else {
++ if (m_zFlipped) {
++ zRotation = -zRotation;
++ if (m_xFlipped) {
++ titleOffset = -titleOffset;
++ } else {
++ extraRotation = -extraRotation;
++ yRotation = -yRotation;
++ }
++ } else {
++ if (m_xFlipped) {
++ titleOffset = -titleOffset;
++ extraRotation = -extraRotation;
++ } else {
++ yRotation = -yRotation;
++ }
++ }
++ if (m_yFlipped) {
++ xRotation = -xRotation;
++ alignment = Qt::AlignBottom;
++ extraRotation = -extraRotation;
++ }
++ }
++
++ float offsetRotation = zRotation;
++ if (offsetRotation == 180.0f || offsetRotation == -180.0f)
++ offsetRotation = 0.0f;
++ QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, offsetRotation);
++ QVector3D titleOffsetVector =
++ offsetRotator.rotatedVector(QVector3D(titleOffset, 0.0f, 0.0f));
++
++ QQuaternion titleRotation;
++ if (m_axisCacheZ.isTitleFixed()) {
++ titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, zRotation)
++ * QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation)
++ * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, xRotation);
++ } else {
++ titleRotation = totalRotation
++ * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, extraRotation);
++ }
++ dummyItem.setTranslation(labelTrans + titleOffsetVector);
++
++ m_drawer->drawLabel(dummyItem, m_axisCacheZ.titleItem(), viewMatrix,
++ projectionMatrix, zeroVector, titleRotation, 0,
++ m_cachedSelectionMode, shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, alignment);
++ }
++
++ void Abstract3DRenderer::loadGridLineMesh()
++ {
++ ObjectHelper::resetObjectHelper(this, m_gridLineObj,
++ QStringLiteral(":/defaultMeshes/plane"));
++ }
++
++ void Abstract3DRenderer::loadLabelMesh()
++ {
++ ObjectHelper::resetObjectHelper(this, m_labelObj,
++ QStringLiteral(":/defaultMeshes/plane"));
++ }
++
++ void Abstract3DRenderer::loadPositionMapperMesh()
++ {
++ ObjectHelper::resetObjectHelper(this, m_positionMapperObj,
++ QStringLiteral(":/defaultMeshes/barFull"));
++ }
++
++ void Abstract3DRenderer::generateBaseColorTexture(const QColor &color, GLuint *texture)
++ {
++ m_textureHelper->deleteTexture(texture);
++ *texture = m_textureHelper->createUniformTexture(color);
++ }
++
++ void Abstract3DRenderer::fixGradientAndGenerateTexture(QLinearGradient *gradient,
++ GLuint *gradientTexture)
++ {
++ // Readjust start/stop to match gradient texture size
++ gradient->setStart(qreal(gradientTextureWidth), qreal(gradientTextureHeight));
++ gradient->setFinalStop(0.0, 0.0);
++
++ m_textureHelper->deleteTexture(gradientTexture);
++
++ *gradientTexture = m_textureHelper->createGradientTexture(*gradient);
++ }
++
++ LabelItem &Abstract3DRenderer::selectionLabelItem()
++ {
++ if (!m_selectionLabelItem)
++ m_selectionLabelItem = new LabelItem;
++ return *m_selectionLabelItem;
++ }
++
++ void Abstract3DRenderer::setSelectionLabel(const QString &label)
++ {
++ if (m_selectionLabelItem)
++ m_selectionLabelItem->clear();
++ m_selectionLabel = label;
++ }
++
++ QString &Abstract3DRenderer::selectionLabel()
++ {
++ return m_selectionLabel;
++ }
++
++ QVector4D Abstract3DRenderer::indexToSelectionColor(GLint index)
++ {
++ GLubyte idxRed = index & 0xff;
++ GLubyte idxGreen = (index & 0xff00) >> 8;
++ GLubyte idxBlue = (index & 0xff0000) >> 16;
++
++ return QVector4D(idxRed, idxGreen, idxBlue, 0);
++ }
++
++ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item)
++ {
++ CustomRenderItem *newItem = new CustomRenderItem();
++ newItem->setRenderer(this);
++ newItem->setItemPointer(item); // Store pointer for render item updates
++ if (!newItem->setMesh(item->meshFile())) {
++ delete newItem;
++ return nullptr;
++ }
++ newItem->setOrigPosition(item->position());
++ newItem->setOrigScaling(item->scaling());
++ newItem->setScalingAbsolute(item->isScalingAbsolute());
++ newItem->setPositionAbsolute(item->isPositionAbsolute());
++ QImage textureImage = item->d_ptr->textureImage();
++ bool facingCamera = false;
++ GLuint texture = 0;
++ if (item->d_ptr->m_isLabelItem) {
++ QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
++ newItem->setLabelItem(true);
++ float pointSize = labelItem->font().pointSizeF();
++ // Check do we have custom visuals or need to use theme
++ if (!labelItem->dptr()->m_customVisuals) {
++ // Recreate texture using theme
++ labelItem->dptr()->createTextureImage(m_cachedTheme->labelBackgroundColor(),
++ m_cachedTheme->labelTextColor(),
++ m_cachedTheme->isLabelBackgroundEnabled(),
++ m_cachedTheme->isLabelBorderEnabled());
++ pointSize = m_cachedTheme->font().pointSizeF();
++ textureImage = item->d_ptr->textureImage();
++ }
++ // Calculate scaling based on text (texture size), font size and asked scaling
++ float scaledFontSize = (0.05f + pointSize / 500.0f) / float(textureImage.height());
++ QVector3D scaling = newItem->origScaling();
++ scaling.setX(scaling.x() * textureImage.width() * scaledFontSize);
++ scaling.setY(scaling.y() * textureImage.height() * scaledFontSize);
++ newItem->setOrigScaling(scaling);
++ // Check if facing camera
++ facingCamera = labelItem->isFacingCamera();
++ } else if (item->d_ptr->m_isVolumeItem && !m_isOpenGLES) {
++ QCustom3DVolume *volumeItem = static_cast<QCustom3DVolume *>(item);
++ newItem->setTextureWidth(volumeItem->textureWidth());
++ newItem->setTextureHeight(volumeItem->textureHeight());
++ newItem->setTextureDepth(volumeItem->textureDepth());
++ if (volumeItem->textureFormat() == QImage::Format_Indexed8)
++ newItem->setColorTable(volumeItem->colorTable());
++ newItem->setTextureFormat(volumeItem->textureFormat());
++ newItem->setVolume(true);
++ newItem->setBlendNeeded(true);
++ texture = m_textureHelper->create3DTexture(volumeItem->textureData(),
++ volumeItem->textureWidth(),
++ volumeItem->textureHeight(),
++ volumeItem->textureDepth(),
++ volumeItem->textureFormat());
++ newItem->setSliceIndexX(volumeItem->sliceIndexX());
++ newItem->setSliceIndexY(volumeItem->sliceIndexY());
++ newItem->setSliceIndexZ(volumeItem->sliceIndexZ());
++ newItem->setAlphaMultiplier(volumeItem->alphaMultiplier());
++ newItem->setPreserveOpacity(volumeItem->preserveOpacity());
++ newItem->setUseHighDefShader(volumeItem->useHighDefShader());
++
++ newItem->setDrawSlices(volumeItem->drawSlices());
++ newItem->setDrawSliceFrames(volumeItem->drawSliceFrames());
++ newItem->setSliceFrameColor(volumeItem->sliceFrameColor());
++ newItem->setSliceFrameWidths(volumeItem->sliceFrameWidths());
++ newItem->setSliceFrameGaps(volumeItem->sliceFrameGaps());
++ newItem->setSliceFrameThicknesses(volumeItem->sliceFrameThicknesses());
++ }
++ recalculateCustomItemScalingAndPos(newItem);
++ newItem->setRotation(item->rotation());
++
++ // In OpenGL ES we simply draw volumes as regular custom item placeholders.
++ if (!item->d_ptr->m_isVolumeItem || m_isOpenGLES)
++ {
++ newItem->setBlendNeeded(textureImage.hasAlphaChannel());
++ texture = m_textureHelper->create2DTexture(textureImage, true, true, true);
++ }
++ newItem->setTexture(texture);
++ item->d_ptr->clearTextureImage();
++ newItem->setVisible(item->isVisible());
++ newItem->setShadowCasting(item->isShadowCasting());
++ newItem->setFacingCamera(facingCamera);
++ m_customRenderCache.insert(item, newItem);
++ return newItem;
++ }
++
++ void Abstract3DRenderer::recalculateCustomItemScalingAndPos(CustomRenderItem *item)
++ {
++ if (!m_polarGraph && !item->isLabel() && !item->isScalingAbsolute()
++ && !item->isPositionAbsolute()) {
++ QVector3D scale = item->origScaling() / 2.0f;
++ QVector3D pos = item->origPosition();
++ QVector3D minBounds(pos.x() - scale.x(),
++ pos.y() - scale.y(),
++ pos.z() + scale.z());
++ QVector3D maxBounds(pos.x() + scale.x(),
++ pos.y() + scale.y(),
++ pos.z() - scale.z());
++ QVector3D minCorner = convertPositionToTranslation(minBounds, false);
++ QVector3D maxCorner = convertPositionToTranslation(maxBounds, false);
++ scale = QVector3D(qAbs(maxCorner.x() - minCorner.x()),
++ qAbs(maxCorner.y() - minCorner.y()),
++ qAbs(maxCorner.z() - minCorner.z())) / 2.0f;
++ if (item->isVolume()) {
++ // Only volume items need to scale and reposition according to bounds
++ QVector3D minBoundsNormal = minCorner;
++ QVector3D maxBoundsNormal = maxCorner;
++ // getVisibleItemBounds returns bounds normalized for fragment shader [-1,1]
++ // Y and Z are also flipped.
++ getVisibleItemBounds(minBoundsNormal, maxBoundsNormal);
++ item->setMinBounds(minBoundsNormal);
++ item->setMaxBounds(maxBoundsNormal);
++ // For scaling calculations, we want [0,1] normalized values
++ minBoundsNormal = item->minBoundsNormal();
++ maxBoundsNormal = item->maxBoundsNormal();
++
++ // Rescale and reposition the item so that it doesn't go over the edges
++ QVector3D adjScaling =
++ QVector3D(scale.x() * (maxBoundsNormal.x() - minBoundsNormal.x()),
++ scale.y() * (maxBoundsNormal.y() - minBoundsNormal.y()),
++ scale.z() * (maxBoundsNormal.z() - minBoundsNormal.z()));
++
++ item->setScaling(adjScaling);
++
++ QVector3D adjPos = item->origPosition();
++ QVector3D dataExtents = QVector3D(maxBounds.x() - minBounds.x(),
++ maxBounds.y() - minBounds.y(),
++ maxBounds.z() - minBounds.z()) / 2.0f;
++ adjPos.setX(adjPos.x() + (dataExtents.x() * minBoundsNormal.x())
++ - (dataExtents.x() * (1.0f - maxBoundsNormal.x())));
++ adjPos.setY(adjPos.y() + (dataExtents.y() * minBoundsNormal.y())
++ - (dataExtents.y() * (1.0f - maxBoundsNormal.y())));
++ adjPos.setZ(adjPos.z() + (dataExtents.z() * minBoundsNormal.z())
++ - (dataExtents.z() * (1.0f - maxBoundsNormal.z())));
++ item->setPosition(adjPos);
++ } else {
++ // Only scale for non-volume items, and do not readjust position
++ item->setScaling(scale);
++ item->setPosition(item->origPosition());
++ }
++ } else {
++ item->setScaling(item->origScaling());
++ item->setPosition(item->origPosition());
++ if (item->isVolume()) {
++ // Y and Z need to be flipped as shader flips those axes
++ item->setMinBounds(QVector3D(-1.0f, 1.0f, 1.0f));
++ item->setMaxBounds(QVector3D(1.0f, -1.0f, -1.0f));
++ }
++ }
++ QVector3D translation = convertPositionToTranslation(item->position(),
++ item->isPositionAbsolute());
++ item->setTranslation(translation);
++ }
++
++ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem)
++ {
++ QCustom3DItem *item = renderItem->itemPointer();
++ if (item->d_ptr->m_dirtyBits.meshDirty) {
++ renderItem->setMesh(item->meshFile());
++ item->d_ptr->m_dirtyBits.meshDirty = false;
++ }
++ if (item->d_ptr->m_dirtyBits.positionDirty) {
++ renderItem->setOrigPosition(item->position());
++ renderItem->setPositionAbsolute(item->isPositionAbsolute());
++ if (!item->d_ptr->m_dirtyBits.scalingDirty)
++ recalculateCustomItemScalingAndPos(renderItem);
++ item->d_ptr->m_dirtyBits.positionDirty = false;
++ }
++ if (item->d_ptr->m_dirtyBits.scalingDirty) {
++ QVector3D scaling = item->scaling();
++ renderItem->setOrigScaling(scaling);
++ renderItem->setScalingAbsolute(item->isScalingAbsolute());
++ // In case we have label item, we need to recreate texture for scaling adjustment
++ if (item->d_ptr->m_isLabelItem) {
++ QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
++ float pointSize = labelItem->font().pointSizeF();
++ // Check do we have custom visuals or need to use theme
++ if (labelItem->dptr()->m_customVisuals) {
++ // Recreate texture
++ labelItem->dptr()->createTextureImage();
++ } else {
++ // Recreate texture using theme
++ labelItem->dptr()->createTextureImage(m_cachedTheme->labelBackgroundColor(),
++ m_cachedTheme->labelTextColor(),
++ m_cachedTheme->isLabelBackgroundEnabled(),
++ m_cachedTheme->isLabelBorderEnabled());
++ pointSize = m_cachedTheme->font().pointSizeF();
++ }
++ QImage textureImage = item->d_ptr->textureImage();
++ // Calculate scaling based on text (texture size), font size and asked scaling
++ float scaledFontSize = (0.05f + pointSize / 500.0f) / float(textureImage.height());
++ scaling.setX(scaling.x() * textureImage.width() * scaledFontSize);
++ scaling.setY(scaling.y() * textureImage.height() * scaledFontSize);
++ item->d_ptr->clearTextureImage();
++ renderItem->setOrigScaling(scaling);
++ }
++ recalculateCustomItemScalingAndPos(renderItem);
++ item->d_ptr->m_dirtyBits.scalingDirty = false;
++ }
++ if (item->d_ptr->m_dirtyBits.rotationDirty) {
++ renderItem->setRotation(item->rotation());
++ item->d_ptr->m_dirtyBits.rotationDirty = false;
++ }
++ if (item->d_ptr->m_dirtyBits.textureDirty) {
++ QImage textureImage = item->d_ptr->textureImage();
++ if (item->d_ptr->m_isLabelItem) {
++ QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
++ // Check do we have custom visuals or need to use theme
++ if (!labelItem->dptr()->m_customVisuals) {
++ // Recreate texture using theme
++ labelItem->dptr()->createTextureImage(m_cachedTheme->labelBackgroundColor(),
++ m_cachedTheme->labelTextColor(),
++ m_cachedTheme->isLabelBackgroundEnabled(),
++ m_cachedTheme->isLabelBorderEnabled());
++ textureImage = item->d_ptr->textureImage();
++ }
++ } else if (!item->d_ptr->m_isVolumeItem || m_isOpenGLES) {
++ renderItem->setBlendNeeded(textureImage.hasAlphaChannel());
++ GLuint oldTexture = renderItem->texture();
++ m_textureHelper->deleteTexture(&oldTexture);
++ GLuint texture = m_textureHelper->create2DTexture(textureImage, true, true, true);
++ renderItem->setTexture(texture);
++ }
++ item->d_ptr->clearTextureImage();
++ item->d_ptr->m_dirtyBits.textureDirty = false;
++ }
++ if (item->d_ptr->m_dirtyBits.visibleDirty) {
++ renderItem->setVisible(item->isVisible());
++ item->d_ptr->m_dirtyBits.visibleDirty = false;
++ }
++ if (item->d_ptr->m_dirtyBits.shadowCastingDirty) {
++ renderItem->setShadowCasting(item->isShadowCasting());
++ item->d_ptr->m_dirtyBits.shadowCastingDirty = false;
++ }
++ if (item->d_ptr->m_isLabelItem) {
++ QCustom3DLabel *labelItem = static_cast<QCustom3DLabel *>(item);
++ if (labelItem->dptr()->m_facingCameraDirty) {
++ renderItem->setFacingCamera(labelItem->isFacingCamera());
++ labelItem->dptr()->m_facingCameraDirty = false;
++ }
++ } else if (item->d_ptr->m_isVolumeItem && !m_isOpenGLES) {
++ QCustom3DVolume *volumeItem = static_cast<QCustom3DVolume *>(item);
++ if (volumeItem->dptr()->m_dirtyBitsVolume.colorTableDirty) {
++ renderItem->setColorTable(volumeItem->colorTable());
++ volumeItem->dptr()->m_dirtyBitsVolume.colorTableDirty = false;
++ }
++ if (volumeItem->dptr()->m_dirtyBitsVolume.textureDimensionsDirty
++ || volumeItem->dptr()->m_dirtyBitsVolume.textureDataDirty
++ || volumeItem->dptr()->m_dirtyBitsVolume.textureFormatDirty) {
++ GLuint oldTexture = renderItem->texture();
++ m_textureHelper->deleteTexture(&oldTexture);
++ GLuint texture = m_textureHelper->create3DTexture(volumeItem->textureData(),
++ volumeItem->textureWidth(),
++ volumeItem->textureHeight(),
++ volumeItem->textureDepth(),
++ volumeItem->textureFormat());
++ renderItem->setTexture(texture);
++ renderItem->setTextureWidth(volumeItem->textureWidth());
++ renderItem->setTextureHeight(volumeItem->textureHeight());
++ renderItem->setTextureDepth(volumeItem->textureDepth());
++ renderItem->setTextureFormat(volumeItem->textureFormat());
++ volumeItem->dptr()->m_dirtyBitsVolume.textureDimensionsDirty = false;
++ volumeItem->dptr()->m_dirtyBitsVolume.textureDataDirty = false;
++ volumeItem->dptr()->m_dirtyBitsVolume.textureFormatDirty = false;
++ }
++ if (volumeItem->dptr()->m_dirtyBitsVolume.slicesDirty) {
++ renderItem->setDrawSlices(volumeItem->drawSlices());
++ renderItem->setDrawSliceFrames(volumeItem->drawSliceFrames());
++ renderItem->setSliceFrameColor(volumeItem->sliceFrameColor());
++ renderItem->setSliceFrameWidths(volumeItem->sliceFrameWidths());
++ renderItem->setSliceFrameGaps(volumeItem->sliceFrameGaps());
++ renderItem->setSliceFrameThicknesses(volumeItem->sliceFrameThicknesses());
++ renderItem->setSliceIndexX(volumeItem->sliceIndexX());
++ renderItem->setSliceIndexY(volumeItem->sliceIndexY());
++ renderItem->setSliceIndexZ(volumeItem->sliceIndexZ());
++ volumeItem->dptr()->m_dirtyBitsVolume.slicesDirty = false;
++ }
++ if (volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty) {
++ renderItem->setAlphaMultiplier(volumeItem->alphaMultiplier());
++ renderItem->setPreserveOpacity(volumeItem->preserveOpacity());
++ volumeItem->dptr()->m_dirtyBitsVolume.alphaDirty = false;
++ }
++ if (volumeItem->dptr()->m_dirtyBitsVolume.shaderDirty) {
++ renderItem->setUseHighDefShader(volumeItem->useHighDefShader());
++ volumeItem->dptr()->m_dirtyBitsVolume.shaderDirty = false;
++ }
++ }
++ }
++
++ void Abstract3DRenderer::updateCustomItemPositions()
++ {
++ for (auto renderItem: m_customRenderCache)
++ recalculateCustomItemScalingAndPos(renderItem);
++ }
++
++ void Abstract3DRenderer::drawCustomItems(RenderingState state,
++ ShaderHelper *regularShader,
++ const QMatrix4x4 &viewMatrix,
++ const QMatrix4x4 &projectionViewMatrix,
++ const QMatrix4x4 &depthProjectionViewMatrix,
++ GLuint depthTexture,
++ GLfloat shadowQuality,
++ GLfloat reflection)
++ {
++ if (m_customRenderCache.isEmpty())
++ return;
++
++ ShaderHelper *shader = regularShader;
++ shader->bind();
++
++ if (RenderingNormal == state) {
++ shader->setUniformValue(shader->lightP(), m_cachedScene->activeLight()->position());
++ shader->setUniformValue(shader->ambientS(), m_cachedTheme->ambientLightStrength());
++ shader->setUniformValue(shader->lightColor(),
++ Utils::vectorFromColor(m_cachedTheme->lightColor()));
++ shader->setUniformValue(shader->view(), viewMatrix);
++ }
++
++ // Draw custom items - first regular and then volumes
++ bool volumeDetected = false;
++ int loopCount = 0;
++ while (loopCount < 2) {
++ for (QCustom3DItem *customItem : std::as_const(m_customItemDrawOrder)) {
++ CustomRenderItem *item = m_customRenderCache.value(customItem);
++ // Check that the render item is visible, and skip drawing if not
++ // Also check if reflected item is on the "wrong" side, and skip drawing if it is
++ if (!item->isVisible() || ((m_reflectionEnabled && reflection < 0.0f)
++ && (m_yFlipped == (item->translation().y() >= 0.0)))) {
++ continue;
++ }
++ if (loopCount == 0) {
++ if (item->isVolume()) {
++ volumeDetected = true;
++ continue;
++ }
++ } else {
++ if (!item->isVolume())
++ continue;
++ }
++
++ // If the render item is in data coordinates and not within axis ranges, skip it
++ if (!item->isPositionAbsolute()
++ && (item->position().x() < m_axisCacheX.min()
++ || item->position().x() > m_axisCacheX.max()
++ || item->position().z() < m_axisCacheZ.min()
++ || item->position().z() > m_axisCacheZ.max()
++ || item->position().y() < m_axisCacheY.min()
++ || item->position().y() > m_axisCacheY.max())) {
++ continue;
++ }
++
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 itModelMatrix;
++ QMatrix4x4 MVPMatrix;
++
++ QQuaternion rotation = item->rotation();
++ // Check if the (label) item should be facing camera, and adjust rotation accordingly
++ if (item->isFacingCamera()) {
++ float camRotationX = m_cachedScene->activeCamera()->xRotation();
++ float camRotationY = m_cachedScene->activeCamera()->yRotation();
++ rotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -camRotationX)
++ * QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -camRotationY);
++ }
++
++ if (m_reflectionEnabled) {
++ if (reflection < 0.0f) {
++ if (item->itemPointer()->d_ptr->m_isLabelItem)
++ continue;
++ else
++ glCullFace(GL_FRONT);
++ } else {
++ glCullFace(GL_BACK);
++ }
++ QVector3D trans = item->translation();
++ trans.setY(reflection * trans.y());
++ modelMatrix.translate(trans);
++ if (reflection < 0.0f) {
++ QQuaternion mirror = QQuaternion(rotation.scalar(),
++ -rotation.x(), rotation.y(), -rotation.z());
++ modelMatrix.rotate(mirror);
++ itModelMatrix.rotate(mirror);
++ } else {
++ modelMatrix.rotate(rotation);
++ itModelMatrix.rotate(rotation);
++ }
++ QVector3D scale = item->scaling();
++ scale.setY(reflection * scale.y());
++ modelMatrix.scale(scale);
++ } else {
++ modelMatrix.translate(item->translation());
++ modelMatrix.rotate(rotation);
++ modelMatrix.scale(item->scaling());
++ itModelMatrix.rotate(rotation);
++ }
++ if (!item->isFacingCamera())
++ itModelMatrix.scale(item->scaling());
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ if (RenderingNormal == state) {
++ // Normal render
++ ShaderHelper *prevShader = shader;
++ if (item->isVolume() && !m_isOpenGLES) {
++ if (item->drawSlices() &&
++ (item->sliceIndexX() >= 0
++ || item->sliceIndexY() >= 0
++ || item->sliceIndexZ() >= 0)) {
++ shader = m_volumeTextureSliceShader;
++ } else if (item->useHighDefShader()) {
++ shader = m_volumeTextureShader;
++ } else {
++ shader = m_volumeTextureLowDefShader;
++ }
++ } else if (item->isLabel()) {
++ shader = m_labelShader;
++ } else {
++ shader = regularShader;
++ }
++ if (shader != prevShader)
++ shader->bind();
++ shader->setUniformValue(shader->model(), modelMatrix);
++ shader->setUniformValue(shader->MVP(), MVPMatrix);
++ shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed());
++
++ if (item->isBlendNeeded()) {
++ glEnable(GL_BLEND);
++ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
++ if (!item->isVolume() && !m_isOpenGLES)
++ glDisable(GL_CULL_FACE);
++ } else {
++ glDisable(GL_BLEND);
++ glEnable(GL_CULL_FACE);
++ }
++
++ if (!m_isOpenGLES && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone
++ && !item->isVolume()) {
++ // Set shadow shader bindings
++ shader->setUniformValue(shader->shadowQ(), shadowQuality);
++ shader->setUniformValue(shader->depth(), depthProjectionViewMatrix * modelMatrix);
++ shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength() / 10.0f);
++ m_drawer->drawObject(shader, item->mesh(), item->texture(), depthTexture);
++ } else {
++ // Set shadowless shader bindings
++ if (item->isVolume() && !m_isOpenGLES) {
++ QVector3D cameraPos = m_cachedScene->activeCamera()->position();
++ cameraPos = MVPMatrix.inverted().map(cameraPos);
++ // Adjust camera position according to min/max bounds
++ cameraPos = -(cameraPos
++ + ((oneVector - cameraPos) * item->minBoundsNormal())
++ - ((oneVector + cameraPos) * (oneVector - item->maxBoundsNormal())));
++ shader->setUniformValue(shader->cameraPositionRelativeToModel(), cameraPos);
++ GLint color8Bit = (item->textureFormat() == QImage::Format_Indexed8) ? 1 : 0;
++ if (color8Bit) {
++ shader->setUniformValueArray(shader->colorIndex(),
++ item->colorTable().constData(), 256);
++ }
++ shader->setUniformValue(shader->color8Bit(), color8Bit);
++ shader->setUniformValue(shader->alphaMultiplier(), item->alphaMultiplier());
++ shader->setUniformValue(shader->preserveOpacity(),
++ item->preserveOpacity() ? 1 : 0);
++
++ shader->setUniformValue(shader->minBounds(), item->minBounds());
++ shader->setUniformValue(shader->maxBounds(), item->maxBounds());
++
++ if (shader == m_volumeTextureSliceShader) {
++ shader->setUniformValue(shader->volumeSliceIndices(),
++ item->sliceFractions());
++ } else {
++ // Precalculate texture dimensions so we can optimize
++ // ray stepping to hit every texture layer.
++ QVector3D textureDimensions(1.0f / float(item->textureWidth()),
++ 1.0f / float(item->textureHeight()),
++ 1.0f / float(item->textureDepth()));
++
++ // Worst case scenario sample count
++ int sampleCount;
++ if (shader == m_volumeTextureLowDefShader) {
++ sampleCount = qMax(item->textureWidth(),
++ qMax(item->textureDepth(), item->textureHeight()));
++ // Further improve speed with big textures by simply dropping every
++ // other sample:
++ if (sampleCount > 256)
++ sampleCount /= 2;
++ } else {
++ sampleCount = item->textureWidth() + item->textureHeight()
++ + item->textureDepth();
++ }
++ shader->setUniformValue(shader->textureDimensions(), textureDimensions);
++ shader->setUniformValue(shader->sampleCount(), sampleCount);
++ }
++ if (item->drawSliceFrames()) {
++ // Set up the slice frame shader
++ glDisable(GL_CULL_FACE);
++ m_volumeSliceFrameShader->bind();
++ m_volumeSliceFrameShader->setUniformValue(
++ m_volumeSliceFrameShader->color(), item->sliceFrameColor());
++
++ // Draw individual slice frames.
++ if (item->sliceIndexX() >= 0)
++ drawVolumeSliceFrame(item, Qt::XAxis, projectionViewMatrix);
++ if (item->sliceIndexY() >= 0)
++ drawVolumeSliceFrame(item, Qt::YAxis, projectionViewMatrix);
++ if (item->sliceIndexZ() >= 0)
++ drawVolumeSliceFrame(item, Qt::ZAxis, projectionViewMatrix);
++
++ glEnable(GL_CULL_FACE);
++ shader->bind();
++ }
++ m_drawer->drawObject(shader, item->mesh(), 0, 0, item->texture());
++ } else {
++ shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength());
++ m_drawer->drawObject(shader, item->mesh(), item->texture());
++ }
++ }
++ } else if (RenderingSelection == state && !volumeDetected) {
++ // Selection render
++ shader->setUniformValue(shader->MVP(), MVPMatrix);
++ QVector4D itemColor = indexToSelectionColor(item->index());
++ itemColor.setW(customItemAlpha);
++ itemColor /= 255.0f;
++ shader->setUniformValue(shader->color(), itemColor);
++ m_drawer->drawObject(shader, item->mesh());
++ } else if (item->isShadowCasting()) {
++ // Depth render
++ shader->setUniformValue(shader->MVP(), depthProjectionViewMatrix * modelMatrix);
++ m_drawer->drawObject(shader, item->mesh());
++ }
++ }
++ loopCount++;
++ if (!volumeDetected)
++ loopCount++; // Skip second run if no volumes detected
++ }
++
++ if (RenderingNormal == state) {
++ glDisable(GL_BLEND);
++ glEnable(GL_CULL_FACE);
++ }
++ }
++
++ void Abstract3DRenderer::drawVolumeSliceFrame(const CustomRenderItem *item, Qt::Axis axis,
++ const QMatrix4x4 &projectionViewMatrix)
++ {
++ QVector2D frameWidth;
++ QVector3D frameScaling;
++ QVector3D translation = item->translation();
++ QQuaternion rotation = item->rotation();
++ float fracTrans;
++ bool needRotate = !rotation.isIdentity();
++ QMatrix4x4 rotationMatrix;
++ if (needRotate)
++ rotationMatrix.rotate(rotation);
++
++ if (axis == Qt::XAxis) {
++ fracTrans = item->sliceFractions().x();
++ float range = item->maxBoundsNormal().x() - item->minBoundsNormal().x();
++ float minMult = item->minBoundsNormal().x() / range;
++ float maxMult = (1.0f - item->maxBoundsNormal().x()) / range;
++ fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult);
++ if (needRotate)
++ translation += rotationMatrix.map(QVector3D(fracTrans * item->scaling().x(), 0.0f, 0.0f));
++ else
++ translation.setX(translation.x() + fracTrans * item->scaling().x());
++ frameScaling = QVector3D(item->scaling().z()
++ + (item->scaling().z() * item->sliceFrameGaps().z())
++ + (item->scaling().z() * item->sliceFrameWidths().z()),
++ item->scaling().y()
++ + (item->scaling().y() * item->sliceFrameGaps().y())
++ + (item->scaling().y() * item->sliceFrameWidths().y()),
++ item->scaling().x() * item->sliceFrameThicknesses().x());
++ frameWidth = QVector2D(item->scaling().z() * item->sliceFrameWidths().z(),
++ item->scaling().y() * item->sliceFrameWidths().y());
++ rotation *= m_yRightAngleRotation;
++ } else if (axis == Qt::YAxis) {
++ fracTrans = item->sliceFractions().y();
++ float range = item->maxBoundsNormal().y() - item->minBoundsNormal().y();
++ // Y axis is logically flipped, so we need to swam min and max bounds
++ float minMult = (1.0f - item->maxBoundsNormal().y()) / range;
++ float maxMult = item->minBoundsNormal().y() / range;
++ fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult);
++ if (needRotate)
++ translation -= rotationMatrix.map(QVector3D(0.0f, fracTrans * item->scaling().y(), 0.0f));
++ else
++ translation.setY(translation.y() - fracTrans * item->scaling().y());
++ frameScaling = QVector3D(item->scaling().x()
++ + (item->scaling().x() * item->sliceFrameGaps().x())
++ + (item->scaling().x() * item->sliceFrameWidths().x()),
++ item->scaling().z()
++ + (item->scaling().z() * item->sliceFrameGaps().z())
++ + (item->scaling().z() * item->sliceFrameWidths().z()),
++ item->scaling().y() * item->sliceFrameThicknesses().y());
++ frameWidth = QVector2D(item->scaling().x() * item->sliceFrameWidths().x(),
++ item->scaling().z() * item->sliceFrameWidths().z());
++ rotation *= m_xRightAngleRotation;
++ } else { // Z axis
++ fracTrans = item->sliceFractions().z();
++ float range = item->maxBoundsNormal().z() - item->minBoundsNormal().z();
++ // Z axis is logically flipped, so we need to swam min and max bounds
++ float minMult = (1.0f - item->maxBoundsNormal().z()) / range;
++ float maxMult = item->minBoundsNormal().z() / range;
++ fracTrans = fracTrans - ((1.0f - fracTrans) * minMult) + ((1.0f + fracTrans) * maxMult);
++ if (needRotate)
++ translation -= rotationMatrix.map(QVector3D(0.0f, 0.0f, fracTrans * item->scaling().z()));
++ else
++ translation.setZ(translation.z() - fracTrans * item->scaling().z());
++ frameScaling = QVector3D(item->scaling().x()
++ + (item->scaling().x() * item->sliceFrameGaps().x())
++ + (item->scaling().x() * item->sliceFrameWidths().x()),
++ item->scaling().y()
++ + (item->scaling().y() * item->sliceFrameGaps().y())
++ + (item->scaling().y() * item->sliceFrameWidths().y()),
++ item->scaling().z() * item->sliceFrameThicknesses().z());
++ frameWidth = QVector2D(item->scaling().x() * item->sliceFrameWidths().x(),
++ item->scaling().y() * item->sliceFrameWidths().y());
++ }
++
++ // If the slice is outside the shown area, don't show the frame
++ if (fracTrans < -1.0 || fracTrans > 1.0)
++ return;
++
++ // Shader needs the width of clear space in the middle.
++ frameWidth.setX(1.0f - (frameWidth.x() / frameScaling.x()));
++ frameWidth.setY(1.0f - (frameWidth.y() / frameScaling.y()));
++
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 mvpMatrix;
++
++ modelMatrix.translate(translation);
++ modelMatrix.rotate(rotation);
++ modelMatrix.scale(frameScaling);
++ mvpMatrix = projectionViewMatrix * modelMatrix;
++ m_volumeSliceFrameShader->setUniformValue(m_volumeSliceFrameShader->MVP(), mvpMatrix);
++ m_volumeSliceFrameShader->setUniformValue(m_volumeSliceFrameShader->sliceFrameWidth(),
++ frameWidth);
++
++ m_drawer->drawObject(m_volumeSliceFrameShader, item->mesh());
++
++ }
++
++ void Abstract3DRenderer::queriedGraphPosition(const QMatrix4x4 &projectionViewMatrix,
++ const QVector3D &scaling,
++ GLuint defaultFboHandle)
++ {
++ m_cursorPositionShader->bind();
++
++ // Set up mapper framebuffer
++ glBindFramebuffer(GL_FRAMEBUFFER, m_cursorPositionFrameBuffer);
++ glViewport(0, 0,
++ m_primarySubViewport.width(),
++ m_primarySubViewport.height());
++ glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
++ glClear(GL_COLOR_BUFFER_BIT);
++ glDisable(GL_DITHER); // Dither may affect colors if enabled
++ glEnable(GL_CULL_FACE);
++ glCullFace(GL_FRONT);
++
++ // Draw a cube scaled to the graph dimensions
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++
++ modelMatrix.scale(scaling);
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++ m_cursorPositionShader->setUniformValue(m_cursorPositionShader->MVP(), MVPMatrix);
++ m_drawer->drawObject(m_cursorPositionShader, m_positionMapperObj);
++
++ QVector4D dataColor = Utils::getSelection(m_graphPositionQuery,
++ m_primarySubViewport.height());
++ if (dataColor.w() > 0.0f) {
++ // If position is outside the graph, set the position well outside the graph boundaries
++ dataColor = QVector4D(-10000.0f, -10000.0f, -10000.0f, 0.0f);
++ } else {
++ // Normalize to range [0.0, 1.0]
++ dataColor /= 255.0f;
++ }
++
++ // Restore state
++ glEnable(GL_DITHER);
++ glCullFace(GL_BACK);
++
++ // Note: Zeroing the frame buffer before resetting it is a workaround for flickering that occurs
++ // during zoom in some environments.
++ glBindFramebuffer(GL_FRAMEBUFFER, 0);
++
++ glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle);
++ glViewport(m_primarySubViewport.x(),
++ m_primarySubViewport.y(),
++ m_primarySubViewport.width(),
++ m_primarySubViewport.height());
++
++ QVector3D normalizedValues = dataColor.toVector3D() * 2.0f;
++ normalizedValues -= oneVector;
++ m_queriedGraphPosition = QVector3D(normalizedValues.x(),
++ normalizedValues.y(),
++ normalizedValues.z());
++ m_graphPositionQueryResolved = true;
++ m_graphPositionQueryPending = false;
++ }
++
++ void Abstract3DRenderer::calculatePolarXZ(const QVector3D &dataPos, float &x, float &z) const
++ {
++ // x is angular, z is radial
++ qreal angle = m_axisCacheX.formatter()->positionAt(dataPos.x()) * doublePi;
++ qreal radius = m_axisCacheZ.formatter()->positionAt(dataPos.z());
++
++ // Convert angle & radius to X and Z coords
++ x = float(radius * qSin(angle)) * m_polarRadius;
++ z = -float(radius * qCos(angle)) * m_polarRadius;
++ }
++
++ void Abstract3DRenderer::drawRadialGrid(ShaderHelper *shader, float yFloorLinePos,
++ const QMatrix4x4 &projectionViewMatrix,
++ const QMatrix4x4 &depthMatrix)
++ {
++ static QList<QQuaternion> lineRotations;
++ if (!lineRotations.size()) {
++ lineRotations.resize(polarGridRoundness);
++ for (int j = 0; j < polarGridRoundness; j++) {
++ lineRotations[j] = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f,
++ polarGridAngleDegrees * float(j));
++ }
++ }
++ int gridLineCount = m_axisCacheZ.gridLineCount();
++ const QList<float> &gridPositions = m_axisCacheZ.formatter()->gridPositions();
++ const QList<float> &subGridPositions = m_axisCacheZ.formatter()->subGridPositions();
++ int mainSize = gridPositions.size();
++ QVector3D translateVector(0.0f, yFloorLinePos, 0.0f);
++ QQuaternion finalRotation = m_xRightAngleRotationNeg;
++ if (m_yFlippedForGrid)
++ finalRotation *= m_xFlipRotation;
++
++ for (int i = 0; i < gridLineCount; i++) {
++ float gridPosition = (i >= mainSize)
++ ? subGridPositions.at(i - mainSize) : gridPositions.at(i);
++ float radiusFraction = m_polarRadius * gridPosition;
++ QVector3D gridLineScaler(radiusFraction * float(qSin(polarGridHalfAngle)),
++ gridLineWidth, gridLineWidth);
++ translateVector.setZ(gridPosition * m_polarRadius);
++ for (int j = 0; j < polarGridRoundness; j++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 itModelMatrix;
++ modelMatrix.rotate(lineRotations.at(j));
++ itModelMatrix.rotate(lineRotations.at(j));
++ modelMatrix.translate(translateVector);
++ modelMatrix.scale(gridLineScaler);
++ itModelMatrix.scale(gridLineScaler);
++ modelMatrix.rotate(finalRotation);
++ itModelMatrix.rotate(finalRotation);
++ QMatrix4x4 MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ shader->setUniformValue(shader->model(), modelMatrix);
++ shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed());
++ shader->setUniformValue(shader->MVP(), MVPMatrix);
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthMatrix * modelMatrix;
++ shader->setUniformValue(shader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(shader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(shader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(shader);
++ }
++ }
++ }
++ }
++
++ void Abstract3DRenderer::drawAngularGrid(ShaderHelper *shader, float yFloorLinePos,
++ const QMatrix4x4 &projectionViewMatrix,
++ const QMatrix4x4 &depthMatrix)
++ {
++ float halfRatio((m_polarRadius + (labelMargin / 2.0f)) / 2.0f);
++ QVector3D gridLineScaler(gridLineWidth, gridLineWidth, halfRatio);
++ int gridLineCount = m_axisCacheX.gridLineCount();
++ const QList<float> &gridPositions = m_axisCacheX.formatter()->gridPositions();
++ const QList<float> &subGridPositions = m_axisCacheX.formatter()->subGridPositions();
++ int mainSize = gridPositions.size();
++ QVector3D translateVector(0.0f, yFloorLinePos, -halfRatio);
++ QQuaternion finalRotation;
++ if (m_isOpenGLES)
++ finalRotation = m_yRightAngleRotationNeg;
++ else
++ finalRotation = m_xRightAngleRotationNeg;
++ if (m_yFlippedForGrid)
++ finalRotation *= m_xFlipRotation;
++ for (int i = 0; i < gridLineCount; i++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 itModelMatrix;
++ float gridPosition = (i >= mainSize)
++ ? subGridPositions.at(i - mainSize) : gridPositions.at(i);
++ QQuaternion lineRotation = QQuaternion::fromAxisAndAngle(upVector, gridPosition * 360.0f);
++ modelMatrix.rotate(lineRotation);
++ itModelMatrix.rotate(lineRotation);
++ modelMatrix.translate(translateVector);
++ modelMatrix.scale(gridLineScaler);
++ itModelMatrix.scale(gridLineScaler);
++ modelMatrix.rotate(finalRotation);
++ itModelMatrix.rotate(finalRotation);
++ QMatrix4x4 MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ shader->setUniformValue(shader->model(), modelMatrix);
++ shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed());
++ shader->setUniformValue(shader->MVP(), MVPMatrix);
++ if (m_isOpenGLES) {
++ m_drawer->drawLine(shader);
++ } else {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthMatrix * modelMatrix;
++ shader->setUniformValue(shader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(shader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(shader, m_gridLineObj);
++ }
++ }
++ }
++ }
++
++ float Abstract3DRenderer::calculatePolarBackgroundMargin()
++ {
++ // Check each extents of each angular label
++ // Calculate angular position
++ QList<float> &labelPositions = m_axisCacheX.formatter()->labelPositions();
++ float actualLabelHeight = m_drawer->scaledFontSize() * 2.0f; // All labels are same height
++ float maxNeededMargin = 0.0f;
++
++ // Axis title needs to be accounted for
++ if (m_axisCacheX.isTitleVisible())
++ maxNeededMargin = 2.0f * actualLabelHeight + 3.0f * labelMargin;
++
++ for (int label = 0; label < labelPositions.size(); label++) {
++ QSize labelSize = m_axisCacheX.labelItems().at(label)->size();
++ float actualLabelWidth = actualLabelHeight / labelSize.height() * labelSize.width();
++ float labelPosition = labelPositions.at(label);
++ qreal angle = labelPosition * M_PI * 2.0;
++ float x = qAbs((m_polarRadius + labelMargin) * float(qSin(angle)))
++ + actualLabelWidth - m_polarRadius + labelMargin;
++ float z = qAbs(-(m_polarRadius + labelMargin) * float(qCos(angle)))
++ + actualLabelHeight - m_polarRadius + labelMargin;
++ float neededMargin = qMax(x, z);
++ maxNeededMargin = qMax(maxNeededMargin, neededMargin);
++ }
++
++ return maxNeededMargin;
++ }
++
++ void Abstract3DRenderer::updateCameraViewport()
++ {
++ QVector3D adjustedTarget = m_cachedScene->activeCamera()->target();
++ fixCameraTarget(adjustedTarget);
++ if (m_oldCameraTarget != adjustedTarget) {
++ QVector3D cameraBase = cameraDistanceVector + adjustedTarget;
++
++ m_cachedScene->activeCamera()->d_ptr->setBaseOrientation(cameraBase,
++ adjustedTarget,
++ upVector);
++ m_oldCameraTarget = adjustedTarget;
++ }
++ m_cachedScene->activeCamera()->d_ptr->updateViewMatrix(m_autoScaleAdjustment);
++ // Set light position (i.e rotate light with activeCamera, a bit above it).
++ // Check if we want to use automatic light positioning even without shadows
++ if (m_cachedScene->activeLight()->isAutoPosition()
++ || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ m_cachedScene->d_ptr->setLightPositionRelativeToCamera(defaultLightPos);
++ }
++ }
++
++ QT_END_NAMESPACE