/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Data Visualization module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "scatter3drenderer_p.h"
#include "q3dcamera_p.h"
#include "shaderhelper_p.h"
#include "texturehelper_p.h"
#include "utils_p.h"
#include "scatterseriesrendercache_p.h"
#include "scatterobjectbufferhelper_p.h"
#include "scatterpointbufferhelper_p.h"

#include <QtCore/qmath.h>

// You can verify that depth buffer drawing works correctly by uncommenting this.
// You should see the scene from  where the light is
//#define SHOW_DEPTH_TEXTURE_SCENE

QT_BEGIN_NAMESPACE

const GLfloat defaultMinSize = 0.01f;
const GLfloat defaultMaxSize = 0.1f;
const GLfloat itemScaler = 3.0f;

Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller)
    : Abstract3DRenderer(controller),
      m_selectedItem(0),
      m_updateLabels(false),
      m_dotShader(0),
      m_dotGradientShader(0),
      m_staticSelectedItemGradientShader(0),
      m_staticSelectedItemShader(0),
      m_pointShader(0),
      m_depthShader(0),
      m_selectionShader(0),
      m_backgroundShader(0),
      m_staticGradientPointShader(0),
      m_bgrTexture(0),
      m_selectionTexture(0),
      m_depthFrameBuffer(0),
      m_selectionFrameBuffer(0),
      m_selectionDepthBuffer(0),
      m_shadowQualityToShader(100.0f),
      m_shadowQualityMultiplier(3),
      m_scaleX(0.0f),
      m_scaleY(0.0f),
      m_scaleZ(0.0f),
      m_selectedItemIndex(Scatter3DController::invalidSelectionIndex()),
      m_selectedSeriesCache(0),
      m_oldSelectedSeriesCache(0),
      m_dotSizeScale(1.0f),
      m_maxItemSize(0.0f),
      m_clickedIndex(Scatter3DController::invalidSelectionIndex()),
      m_havePointSeries(false),
      m_haveMeshSeries(false),
      m_haveUniformColorMeshSeries(false),
      m_haveGradientMeshSeries(false)
{
    initializeOpenGL();
}

Scatter3DRenderer::~Scatter3DRenderer()
{
    contextCleanup();
    delete m_dotShader;
    delete m_staticSelectedItemGradientShader;
    delete m_staticSelectedItemShader;
    delete m_dotGradientShader;
    delete m_depthShader;
    delete m_selectionShader;
    delete m_backgroundShader;
    delete m_staticGradientPointShader;
}

void Scatter3DRenderer::contextCleanup()
{
    if (QOpenGLContext::currentContext()) {
        m_textureHelper->glDeleteFramebuffers(1, &m_selectionFrameBuffer);
        m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer);
        m_textureHelper->deleteTexture(&m_selectionTexture);
        m_textureHelper->glDeleteFramebuffers(1, &m_depthFrameBuffer);
        m_textureHelper->deleteTexture(&m_bgrTexture);
    }
}

void Scatter3DRenderer::initializeOpenGL()
{
    Abstract3DRenderer::initializeOpenGL();

    // Initialize shaders

    if (!m_isOpenGLES) {
        initDepthShader(); // For shadows
        loadGridLineMesh();
    } else {
        initPointShader();
    }

    // Init selection shader
    initSelectionShader();

    // Set view port
    glViewport(m_primarySubViewport.x(),
               m_primarySubViewport.y(),
               m_primarySubViewport.width(),
               m_primarySubViewport.height());

    // Load background mesh (we need to be initialized first)
    loadBackgroundMesh();
}

void Scatter3DRenderer::fixCameraTarget(QVector3D &target)
{
    target.setX(target.x() * m_scaleX);
    target.setY(target.y() * m_scaleY);
    target.setZ(target.z() * -m_scaleZ);
}

void Scatter3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds)
{
    // The inputs are the item bounds in OpenGL coordinates.
    // The outputs limit these bounds to visible ranges, normalized to range [-1, 1]
    // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those
    float itemRangeX = (maxBounds.x() - minBounds.x());
    float itemRangeY = (maxBounds.y() - minBounds.y());
    float itemRangeZ = (maxBounds.z() - minBounds.z());

    if (minBounds.x() < -m_scaleX)
        minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_scaleX) / itemRangeX));
    else
        minBounds.setX(-1.0f);

    if (minBounds.y() < -m_scaleY)
        minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + m_scaleY) / itemRangeY)));
    else
        minBounds.setY(1.0f);

    if (minBounds.z() < -m_scaleZ)
        minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_scaleZ) / itemRangeZ)));
    else
        minBounds.setZ(1.0f);

    if (maxBounds.x() > m_scaleX)
        maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_scaleX) / itemRangeX));
    else
        maxBounds.setX(1.0f);

    if (maxBounds.y() > m_scaleY)
        maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - m_scaleY) / itemRangeY)));
    else
        maxBounds.setY(-1.0f);

    if (maxBounds.z() > m_scaleZ)
        maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_scaleZ) / itemRangeZ)));
    else
        maxBounds.setZ(-1.0f);
}

void Scatter3DRenderer::updateData()
{
    calculateSceneScalingFactors();
    int totalDataSize = 0;

    foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
        ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache);
        if (cache->isVisible()) {
            const QScatter3DSeries *currentSeries = cache->series();
            ScatterRenderItemArray &renderArray = cache->renderArray();
            QScatterDataProxy *dataProxy = currentSeries->dataProxy();
            const QScatterDataArray &dataArray = *dataProxy->array();
            int dataSize = dataArray.size();
            totalDataSize += dataSize;
            if (cache->dataDirty()) {
                if (dataSize != renderArray.size())
                    renderArray.resize(dataSize);

                for (int i = 0; i < dataSize; i++)
                    updateRenderItem(dataArray.at(i), renderArray[i]);

                if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic))
                    cache->setStaticBufferDirty(true);

                cache->setDataDirty(false);
            }
        }
    }

    if (totalDataSize) {
        m_dotSizeScale = GLfloat(qBound(defaultMinSize,
                                        2.0f / float(qSqrt(qreal(totalDataSize))),
                                        defaultMaxSize));
    }

    if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)) {
        foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
            ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache);
            if (cache->isVisible()) {
                ScatterRenderItemArray &renderArray = cache->renderArray();
                const int renderArraySize = renderArray.size();

                if (cache->mesh() == QAbstract3DSeries::MeshPoint) {
                    ScatterPointBufferHelper *points = cache->bufferPoints();
                    if (!points) {
                        points = new ScatterPointBufferHelper();
                        cache->setBufferPoints(points);
                    }
                    points->setScaleY(m_scaleY);
                    points->load(cache);
                } else {
                    ScatterObjectBufferHelper *object = cache->bufferObject();
                    if (!object) {
                        object = new ScatterObjectBufferHelper();
                        cache->setBufferObject(object);
                    }
                    if (renderArraySize != cache->oldArraySize()
                            || cache->object()->objectFile() != cache->oldMeshFileName()
                            || cache->staticBufferDirty()) {
                        object->setScaleY(m_scaleY);
                        object->fullLoad(cache, m_dotSizeScale);
                        cache->setOldArraySize(renderArraySize);
                        cache->setOldMeshFileName(cache->object()->objectFile());
                    } else {
                        object->update(cache, m_dotSizeScale);
                    }
                }

                cache->setStaticBufferDirty(false);
            }
        }
    }

    updateSelectedItem(m_selectedItemIndex,
                       m_selectedSeriesCache ? m_selectedSeriesCache->series() : 0);
}

void Scatter3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList)
{
    int seriesCount = seriesList.size();

    // Check OptimizationStatic specific issues before populate marks changeTracker done
    if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)) {
        for (int i = 0; i < seriesCount; i++) {
            QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(seriesList[i]);
            if (scatterSeries->isVisible()) {
                QAbstract3DSeriesChangeBitField &changeTracker = scatterSeries->d_ptr->m_changeTracker;
                ScatterSeriesRenderCache *cache =
                        static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(scatterSeries));
                if (cache) {
                    if (changeTracker.baseGradientChanged || changeTracker.colorStyleChanged)
                        cache->setStaticObjectUVDirty(true);
                    if (cache->itemSize() != scatterSeries->itemSize())
                        cache->setStaticBufferDirty(true);
                }
            }
        }
    }

    Abstract3DRenderer::updateSeries(seriesList);

    float maxItemSize = 0.0f;
    float itemSize = 0.0f;
    bool noSelection = true;

    m_havePointSeries = false;
    m_haveMeshSeries = false;
    m_haveUniformColorMeshSeries = false;
    m_haveGradientMeshSeries = false;

    for (int i = 0; i < seriesCount; i++) {
        QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(seriesList[i]);
        if (scatterSeries->isVisible()) {
            ScatterSeriesRenderCache *cache =
                    static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(scatterSeries));
            itemSize = scatterSeries->itemSize();
            if (maxItemSize < itemSize)
                maxItemSize = itemSize;
            if (cache->itemSize() != itemSize)
                cache->setItemSize(itemSize);
            if (noSelection
                    && scatterSeries->selectedItem() != QScatter3DSeries::invalidSelectionIndex()) {
                if (m_selectionLabel != cache->itemLabel())
                    m_selectionLabelDirty = true;
                noSelection = false;
            }

            if (cache->mesh() == QAbstract3DSeries::MeshPoint) {
                m_havePointSeries = true;
            } else {
                m_haveMeshSeries = true;
                if (cache->colorStyle() == Q3DTheme::ColorStyleUniform)
                    m_haveUniformColorMeshSeries = true;
                else
                    m_haveGradientMeshSeries = true;
            }

            if (cache->staticBufferDirty()) {
                if (cache->mesh() != QAbstract3DSeries::MeshPoint) {
                    ScatterObjectBufferHelper *object = cache->bufferObject();
                    object->update(cache, m_dotSizeScale);
                }
                cache->setStaticBufferDirty(false);
            }
            if (cache->staticObjectUVDirty()) {
                if (cache->mesh() == QAbstract3DSeries::MeshPoint) {
                    ScatterPointBufferHelper *object = cache->bufferPoints();
                    object->updateUVs(cache);
                } else {
                    ScatterObjectBufferHelper *object = cache->bufferObject();
                    object->updateUVs(cache);
                }
                cache->setStaticObjectUVDirty(false);
            }
        }
    }
    m_maxItemSize = maxItemSize;
    calculateSceneScalingFactors();

    if (noSelection) {
        if (!selectionLabel().isEmpty())
            m_selectionLabelDirty = true;
        m_selectedSeriesCache = 0;
    }
}

SeriesRenderCache *Scatter3DRenderer::createNewCache(QAbstract3DSeries *series)
{
    return new ScatterSeriesRenderCache(series, this);
}

void Scatter3DRenderer::updateItems(const QList<Scatter3DController::ChangeItem> &items)
{
    ScatterSeriesRenderCache *cache = 0;
    const QScatter3DSeries *prevSeries = 0;
    const QScatterDataArray *dataArray = 0;
    const bool optimizationStatic = m_cachedOptimizationHint.testFlag(
                QAbstract3DGraph::OptimizationStatic);

    foreach (Scatter3DController::ChangeItem item, items) {
        QScatter3DSeries *currentSeries = item.series;
        if (currentSeries != prevSeries) {
            cache = static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(currentSeries));
            prevSeries = currentSeries;
            dataArray = item.series->dataProxy()->array();
            // Invisible series render caches are not updated, but instead just marked dirty, so that
            // they can be completely recalculated when they are turned visible.
            if (!cache->isVisible() && !cache->dataDirty())
                cache->setDataDirty(true);
        }
        if (cache->isVisible()) {
            const int index = item.index;
            if (index >= cache->renderArray().size())
                continue; // Items removed from array for same render
            bool oldVisibility;
            ScatterRenderItem &item = cache->renderArray()[index];
            if (optimizationStatic)
                oldVisibility = item.isVisible();
            updateRenderItem(dataArray->at(index), item);
            if (optimizationStatic) {
                if (!cache->visibilityChanged() && oldVisibility != item.isVisible())
                    cache->setVisibilityChanged(true);
                cache->updateIndices().append(index);
            }
        }
    }
    if (optimizationStatic) {
        foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
            ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache);
            if (cache->isVisible() && cache->updateIndices().size()) {
                if (cache->mesh() == QAbstract3DSeries::MeshPoint) {
                    cache->bufferPoints()->update(cache);
                    if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient)
                        cache->bufferPoints()->updateUVs(cache);
                } else {
                    if (cache->visibilityChanged()) {
                        // If any change changes item visibility, full load is needed to
                        // resize the buffers.
                        cache->updateIndices().clear();
                        cache->bufferObject()->fullLoad(cache, m_dotSizeScale);
                    } else {
                        cache->bufferObject()->update(cache, m_dotSizeScale);
                        if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient)
                            cache->bufferObject()->updateUVs(cache);
                    }
                }
                cache->updateIndices().clear();
            }
            cache->setVisibilityChanged(false);
        }
    }
}

void Scatter3DRenderer::updateScene(Q3DScene *scene)
{
    scene->activeCamera()->d_ptr->setMinYRotation(-90.0f);

    Abstract3DRenderer::updateScene(scene);
}

void Scatter3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation,
                                         const QStringList &labels)
{
    Abstract3DRenderer::updateAxisLabels(orientation, labels);

    // Angular axis label dimensions affect the chart dimensions
    if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX)
        calculateSceneScalingFactors();
}

void Scatter3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation,
                                                  bool visible)
{
    Abstract3DRenderer::updateAxisTitleVisibility(orientation, visible);

    // Angular axis title existence affects the chart dimensions
    if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX)
        calculateSceneScalingFactors();
}

void Scatter3DRenderer::updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint)
{
    Abstract3DRenderer::updateOptimizationHint(hint);

    Abstract3DRenderer::reInitShaders();

    if (m_isOpenGLES && hint.testFlag(QAbstract3DGraph::OptimizationStatic)
            && !m_staticGradientPointShader) {
        initStaticPointShaders(QStringLiteral(":/shaders/vertexPointES2_UV"),
                               QStringLiteral(":/shaders/fragmentLabel"));
    }
}

void Scatter3DRenderer::updateMargin(float margin)
{
    Abstract3DRenderer::updateMargin(margin);
    calculateSceneScalingFactors();
}

void Scatter3DRenderer::resetClickedStatus()
{
    m_clickedIndex = Scatter3DController::invalidSelectionIndex();
    m_clickedSeries = 0;
}

void Scatter3DRenderer::render(GLuint defaultFboHandle)
{
    // Handle GL state setup for FBO buffers and clearing of the render surface
    Abstract3DRenderer::render(defaultFboHandle);

    if (m_axisCacheX.positionsDirty())
        m_axisCacheX.updateAllPositions();
    if (m_axisCacheY.positionsDirty())
        m_axisCacheY.updateAllPositions();
    if (m_axisCacheZ.positionsDirty())
        m_axisCacheZ.updateAllPositions();

    // Draw dots scene
    drawScene(defaultFboHandle);
}

void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle)
{
    GLfloat backgroundRotation = 0;
    GLfloat selectedItemSize = 0.0f;

    // Get the optimization flag
    const bool optimizationDefault =
            !m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic);

    const Q3DCamera *activeCamera = m_cachedScene->activeCamera();

    QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor());

    // Specify viewport
    glViewport(m_primarySubViewport.x(),
               m_primarySubViewport.y(),
               m_primarySubViewport.width(),
               m_primarySubViewport.height());

    // Set up projection matrix
    QMatrix4x4 projectionMatrix;
    GLfloat viewPortRatio = (GLfloat)m_primarySubViewport.width()
            / (GLfloat)m_primarySubViewport.height();
    if (m_useOrthoProjection) {
        GLfloat orthoRatio = 2.0f;
        projectionMatrix.ortho(-viewPortRatio * orthoRatio, viewPortRatio * orthoRatio,
                               -orthoRatio, orthoRatio,
                               0.0f, 100.0f);
    } else {
        projectionMatrix.perspective(45.0f, viewPortRatio, 0.1f, 100.0f);
    }

    // Calculate view matrix
    QMatrix4x4 viewMatrix = activeCamera->d_ptr->viewMatrix();
    QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix;

    // Calculate label flipping
    if (viewMatrix.row(0).x() > 0)
        m_zFlipped = false;
    else
        m_zFlipped = true;
    if (viewMatrix.row(0).z() <= 0)
        m_xFlipped = false;
    else
        m_xFlipped = true;

    // Check if we're viewing the scene from below
    if (viewMatrix.row(2).y() < 0)
        m_yFlipped = true;
    else
        m_yFlipped = false;
    m_yFlippedForGrid = m_yFlipped; // Polar axis grid drawing in abstract needs this

    // Calculate background rotation
    if (!m_zFlipped && !m_xFlipped)
        backgroundRotation = 270.0f;
    else if (!m_zFlipped && m_xFlipped)
        backgroundRotation = 180.0f;
    else if (m_zFlipped && m_xFlipped)
        backgroundRotation = 90.0f;
    else if (m_zFlipped && !m_xFlipped)
        backgroundRotation = 0.0f;

    // Get light position from the scene
    QVector3D lightPos = m_cachedScene->activeLight()->position();

    // Introduce regardless of shadow quality to simplify logic
    QMatrix4x4 depthProjectionViewMatrix;

    ShaderHelper *pointSelectionShader;
    if (!m_isOpenGLES) {
#if !QT_CONFIG(opengles2)
        if (m_havePointSeries) {
            glEnable(GL_POINT_SMOOTH);
            glEnable(GL_PROGRAM_POINT_SIZE);
        }

        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
            // Render scene into a depth texture for using with shadow mapping
            // Bind depth shader
            m_depthShader->bind();

            // Set viewport for depth map rendering. Must match texture size. Larger values give smoother shadows.
            glViewport(0, 0,
                       m_primarySubViewport.width() * m_shadowQualityMultiplier,
                       m_primarySubViewport.height() * m_shadowQualityMultiplier);

            // Enable drawing to framebuffer
            glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer);
            glClear(GL_DEPTH_BUFFER_BIT);

            // Set front face culling to reduce self-shadowing issues
            glCullFace(GL_FRONT);

            QMatrix4x4 depthViewMatrix;
            QMatrix4x4 depthProjectionMatrix;

            // Get the depth view matrix
            // It may be possible to hack lightPos here if we want to make some tweaks to shadow
            QVector3D depthLightPos = activeCamera->d_ptr->calculatePositionRelativeToCamera(
                        zeroVector, 0.0f, 2.5f / m_autoScaleAdjustment);
            depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector);
            // Set the depth projection matrix
            depthProjectionMatrix.perspective(15.0f, viewPortRatio, 3.0f, 100.0f);
            depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix;

            // Draw dots to depth buffer
            foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
                if (baseCache->isVisible()) {
                    ScatterSeriesRenderCache *cache =
                            static_cast<ScatterSeriesRenderCache *>(baseCache);
                    ObjectHelper *dotObj = cache->object();
                    QQuaternion seriesRotation(cache->meshRotation());
                    const ScatterRenderItemArray &renderArray = cache->renderArray();
                    const int renderArraySize = renderArray.size();
                    bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint);
                    float itemSize = cache->itemSize() / itemScaler;
                    if (itemSize == 0.0f)
                        itemSize = m_dotSizeScale;
                    if (drawingPoints) {
                        // Scale points based on shadow quality for shadows, not by zoom level
                        m_funcs_2_1->glPointSize(itemSize * 100.0f * m_shadowQualityMultiplier);
                    }
                    QVector3D modelScaler(itemSize, itemSize, itemSize);

                    if (!optimizationDefault
                            && ((drawingPoints && cache->bufferPoints()->indexCount() == 0)
                                || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) {
                        continue;
                    }

                    int loopCount = 1;
                    if (optimizationDefault)
                        loopCount = renderArraySize;
                    for (int dot = 0; dot < loopCount; dot++) {
                        const ScatterRenderItem &item = renderArray.at(dot);
                        if (!item.isVisible() && optimizationDefault)
                            continue;

                        QMatrix4x4 modelMatrix;
                        QMatrix4x4 MVPMatrix;

                        if (optimizationDefault) {
                            modelMatrix.translate(item.translation());
                            if (!drawingPoints) {
                                if (!seriesRotation.isIdentity() || !item.rotation().isIdentity())
                                    modelMatrix.rotate(seriesRotation * item.rotation());
                                modelMatrix.scale(modelScaler);
                            }
                        }

                        MVPMatrix = depthProjectionViewMatrix * modelMatrix;

                        m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix);

                        if (drawingPoints) {
                            if (optimizationDefault)
                                m_drawer->drawPoint(m_depthShader);
                            else
                                m_drawer->drawPoints(m_depthShader, cache->bufferPoints(), 0);
                        } else {
                            if (optimizationDefault) {
                                // 1st attribute buffer : vertices
                                glEnableVertexAttribArray(m_depthShader->posAtt());
                                glBindBuffer(GL_ARRAY_BUFFER, dotObj->vertexBuf());
                                glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0,
                                                      (void *)0);

                                // Index buffer
                                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dotObj->elementBuf());

                                // Draw the triangles
                                glDrawElements(GL_TRIANGLES, dotObj->indexCount(),
                                               GL_UNSIGNED_INT, (void *)0);

                                // Free buffers
                                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
                                glBindBuffer(GL_ARRAY_BUFFER, 0);

                                glDisableVertexAttribArray(m_depthShader->posAtt());
                            } else {
                                ScatterObjectBufferHelper *object = cache->bufferObject();
                                // 1st attribute buffer : vertices
                                glEnableVertexAttribArray(m_depthShader->posAtt());
                                glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf());
                                glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0,
                                                      (void *)0);

                                // Index buffer
                                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf());

                                // Draw the triangles
                                glDrawElements(GL_TRIANGLES, object->indexCount(),
                                               GL_UNSIGNED_INT, (void *)0);

                                // Free buffers
                                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
                                glBindBuffer(GL_ARRAY_BUFFER, 0);

                                glDisableVertexAttribArray(m_depthShader->posAtt());
                            }
                        }
                    }
                }
            }

            Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix,
                                                projectionViewMatrix,
                                                depthProjectionViewMatrix, m_depthTexture,
                                                m_shadowQualityToShader);

            // Disable drawing to framebuffer (= enable drawing to screen)
            glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle);

            // Reset culling to normal
            glCullFace(GL_BACK);

            // Revert to original viewport
            glViewport(m_primarySubViewport.x(),
                       m_primarySubViewport.y(),
                       m_primarySubViewport.width(),
                       m_primarySubViewport.height());
        }
#endif
        pointSelectionShader = m_selectionShader;
    } else {
        pointSelectionShader = m_pointShader;
    }

    ShaderHelper *selectionShader = m_selectionShader;

    // Do position mapping when necessary
    if (m_graphPositionQueryPending) {
        QVector3D graphDimensions(m_scaleX, m_scaleY, m_scaleZ);
        queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle);
        emit needRender();
    }

    // Skip selection mode drawing if we have no selection mode
    if (m_cachedSelectionMode > QAbstract3DGraph::SelectionNone
            && SelectOnScene == m_selectionState
            && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty())
            && m_selectionTexture) {
        // Draw dots to selection buffer
        glBindFramebuffer(GL_FRAMEBUFFER, m_selectionFrameBuffer);
        glViewport(0, 0,
                   m_primarySubViewport.width(),
                   m_primarySubViewport.height());

        glEnable(GL_DEPTH_TEST); // Needed, otherwise the depth render buffer is not used
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // Set clear color to white (= skipColor)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Needed for clearing the frame buffer
        glDisable(GL_DITHER); // disable dithering, it may affect colors if enabled

        bool previousDrawingPoints = false;
        int totalIndex = 0;
        foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
            if (baseCache->isVisible()) {
                ScatterSeriesRenderCache *cache =
                        static_cast<ScatterSeriesRenderCache *>(baseCache);
                ObjectHelper *dotObj = cache->object();
                QQuaternion seriesRotation(cache->meshRotation());
                const ScatterRenderItemArray &renderArray = cache->renderArray();
                const int renderArraySize = renderArray.size();
                bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint);
                float itemSize = cache->itemSize() / itemScaler;
                if (itemSize == 0.0f)
                    itemSize = m_dotSizeScale;
#if !QT_CONFIG(opengles2)
                if (drawingPoints && !m_isOpenGLES)
                    m_funcs_2_1->glPointSize(itemSize * activeCamera->zoomLevel());
#endif
                QVector3D modelScaler(itemSize, itemSize, itemSize);

                // Rebind selection shader if it has changed
                if (!totalIndex || drawingPoints != previousDrawingPoints) {
                    previousDrawingPoints = drawingPoints;
                    if (drawingPoints)
                        selectionShader = pointSelectionShader;
                    else
                        selectionShader = m_selectionShader;

                    selectionShader->bind();
                }
                cache->setSelectionIndexOffset(totalIndex);
                for (int dot = 0; dot < renderArraySize; dot++) {
                    const ScatterRenderItem &item = renderArray.at(dot);
                    if (!item.isVisible()) {
                        totalIndex++;
                        continue;
                    }

                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 MVPMatrix;

                    modelMatrix.translate(item.translation());
                    if (!drawingPoints) {
                        if (!seriesRotation.isIdentity() || !item.rotation().isIdentity())
                            modelMatrix.rotate(seriesRotation * item.rotation());
                        modelMatrix.scale(modelScaler);
                    }

                    MVPMatrix = projectionViewMatrix * modelMatrix;

                    QVector4D dotColor = indexToSelectionColor(totalIndex++);
                    dotColor /= 255.0f;

                    selectionShader->setUniformValue(selectionShader->MVP(), MVPMatrix);
                    selectionShader->setUniformValue(selectionShader->color(), dotColor);

                    if (drawingPoints)
                        m_drawer->drawPoint(selectionShader);
                    else
                        m_drawer->drawSelectionObject(selectionShader, dotObj);
                }
            }
        }

        Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader,
                                            viewMatrix, projectionViewMatrix,
                                            depthProjectionViewMatrix, m_depthTexture,
                                            m_shadowQualityToShader);

        drawLabels(true, activeCamera, viewMatrix, projectionMatrix);

        glEnable(GL_DITHER);

        // Read color under cursor
        QVector4D clickedColor = Utils::getSelection(m_inputPosition,
                                                     m_viewport.height());
        selectionColorToSeriesAndIndex(clickedColor, m_clickedIndex, m_clickedSeries);
        m_clickResolved = true;

        emit needRender();

        // Revert to original fbo and viewport
        glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle);
        glViewport(m_primarySubViewport.x(),
                   m_primarySubViewport.y(),
                   m_primarySubViewport.width(),
                   m_primarySubViewport.height());
    }

    // Draw dots
    ShaderHelper *dotShader = 0;
    GLuint gradientTexture = 0;
    bool dotSelectionFound = false;
    ScatterRenderItem *selectedItem(0);
    QVector4D baseColor;
    QVector4D dotColor;

    bool previousDrawingPoints = false;
    Q3DTheme::ColorStyle previousMeshColorStyle = Q3DTheme::ColorStyleUniform;
    if (m_haveMeshSeries) {
        // Set unchanging shader bindings
        if (m_haveGradientMeshSeries) {
            m_dotGradientShader->bind();
            m_dotGradientShader->setUniformValue(m_dotGradientShader->lightP(), lightPos);
            m_dotGradientShader->setUniformValue(m_dotGradientShader->view(), viewMatrix);
            m_dotGradientShader->setUniformValue(m_dotGradientShader->ambientS(),
                                                 m_cachedTheme->ambientLightStrength());
            m_dotGradientShader->setUniformValue(m_dotGradientShader->lightColor(), lightColor);
        }
        if (m_haveUniformColorMeshSeries) {
            m_dotShader->bind();
            m_dotShader->setUniformValue(m_dotShader->lightP(), lightPos);
            m_dotShader->setUniformValue(m_dotShader->view(), viewMatrix);
            m_dotShader->setUniformValue(m_dotShader->ambientS(),
                                         m_cachedTheme->ambientLightStrength());
            m_dotShader->setUniformValue(m_dotShader->lightColor(), lightColor);
            dotShader = m_dotShader;
        } else {
            dotShader = m_dotGradientShader;
            previousMeshColorStyle = Q3DTheme::ColorStyleRangeGradient;
            m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), 0.0f);
        }
    } else {
        dotShader = pointSelectionShader;
    }

    float rangeGradientYScaler = 0.5f / m_scaleY;

    foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
        if (baseCache->isVisible()) {
            ScatterSeriesRenderCache *cache =
                    static_cast<ScatterSeriesRenderCache *>(baseCache);
            ObjectHelper *dotObj = cache->object();
            QQuaternion seriesRotation(cache->meshRotation());
            ScatterRenderItemArray &renderArray = cache->renderArray();
            const int renderArraySize = renderArray.size();
            bool selectedSeries = m_cachedSelectionMode > QAbstract3DGraph::SelectionNone
                    && (m_selectedSeriesCache == cache);
            bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint);
            Q3DTheme::ColorStyle colorStyle = cache->colorStyle();
            bool colorStyleIsUniform = (colorStyle == Q3DTheme::ColorStyleUniform);
            bool useColor = colorStyleIsUniform || drawingPoints;
            bool rangeGradientPoints = drawingPoints
                    && (colorStyle == Q3DTheme::ColorStyleRangeGradient);
            float itemSize = cache->itemSize() / itemScaler;
            if (itemSize == 0.0f)
                itemSize = m_dotSizeScale;
#if !QT_CONFIG(opengles2)
            if (drawingPoints && !m_isOpenGLES)
                m_funcs_2_1->glPointSize(itemSize * activeCamera->zoomLevel());
#endif
            QVector3D modelScaler(itemSize, itemSize, itemSize);
            int gradientImageHeight = cache->gradientImage().height();
            int maxGradientPositition = gradientImageHeight - 1;

            if (!optimizationDefault
                    && ((drawingPoints && cache->bufferPoints()->indexCount() == 0)
                        || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) {
                continue;
            }

            // Rebind shader if it has changed
            if (drawingPoints != previousDrawingPoints
                    || (!drawingPoints &&
                        (colorStyleIsUniform != (previousMeshColorStyle
                                                 == Q3DTheme::ColorStyleUniform)))
                    || (!optimizationDefault && drawingPoints)) {
                previousDrawingPoints = drawingPoints;
                if (drawingPoints) {
                    if (!optimizationDefault && rangeGradientPoints) {
                        if (m_isOpenGLES)
                            dotShader = m_staticGradientPointShader;
                        else
                            dotShader = m_labelShader;
                    } else {
                        dotShader = pointSelectionShader;
                    }
                } else {
                    if (colorStyleIsUniform)
                        dotShader = m_dotShader;
                    else
                        dotShader = m_dotGradientShader;
                }
                dotShader->bind();
            }

            if (!drawingPoints && !colorStyleIsUniform && previousMeshColorStyle != colorStyle) {
                if (colorStyle == Q3DTheme::ColorStyleObjectGradient) {
                    dotShader->setUniformValue(dotShader->gradientMin(), 0.0f);
                    dotShader->setUniformValue(dotShader->gradientHeight(),
                                               0.5f);
                } else {
                    // Each dot is of uniform color according to its Y-coordinate
                    dotShader->setUniformValue(dotShader->gradientHeight(),
                                               0.0f);
                }
            }

            if (!drawingPoints)
                previousMeshColorStyle = colorStyle;

            if (useColor) {
                baseColor = cache->baseColor();
                dotColor = baseColor;
            }
            int loopCount = 1;
            if (optimizationDefault)
                loopCount = renderArraySize;

            for (int i = 0; i < loopCount; i++) {
                ScatterRenderItem &item = renderArray[i];
                if (!item.isVisible() && optimizationDefault)
                    continue;

                QMatrix4x4 modelMatrix;
                QMatrix4x4 MVPMatrix;
                QMatrix4x4 itModelMatrix;

                if (optimizationDefault) {
                    modelMatrix.translate(item.translation());
                    if (!drawingPoints) {
                        if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) {
                            QQuaternion totalRotation = seriesRotation * item.rotation();
                            modelMatrix.rotate(totalRotation);
                            itModelMatrix.rotate(totalRotation);
                        }
                        modelMatrix.scale(modelScaler);
                        itModelMatrix.scale(modelScaler);
                    }
                }
#ifdef SHOW_DEPTH_TEXTURE_SCENE
                MVPMatrix = depthProjectionViewMatrix * modelMatrix;
#else
                MVPMatrix = projectionViewMatrix * modelMatrix;
#endif

                if (useColor) {
                    if (rangeGradientPoints) {
                        // Drawing points with range gradient
                        // Get color from gradient based on items y position converted to percent
                        int position = ((item.translation().y() + m_scaleY) * rangeGradientYScaler) * gradientImageHeight;
                        position = qMin(maxGradientPositition, position); // clamp to edge
                        dotColor = Utils::vectorFromColor(
                                    cache->gradientImage().pixel(0, position));
                    } else {
                        dotColor = baseColor;
                    }
                } else {
                    gradientTexture = cache->baseGradientTexture();
                }

                if (!optimizationDefault && rangeGradientPoints)
                    gradientTexture = cache->baseGradientTexture();

                GLfloat lightStrength = m_cachedTheme->lightStrength();
                if (optimizationDefault && selectedSeries && (m_selectedItemIndex == i)) {
                    if (useColor)
                        dotColor = cache->singleHighlightColor();
                    else
                        gradientTexture = cache->singleHighlightGradientTexture();
                    lightStrength = m_cachedTheme->highlightLightStrength();
                    // Save the reference to the item to be used in label drawing
                    selectedItem = &item;
                    dotSelectionFound = true;
                    // Save selected item size (adjusted with font size) for selection label
                    // positioning
                    selectedItemSize = itemSize + m_drawer->scaledFontSize() - 0.05f;
                }

                if (!drawingPoints) {
                    // Set shader bindings
                    dotShader->setUniformValue(dotShader->model(), modelMatrix);
                    dotShader->setUniformValue(dotShader->nModel(),
                                               itModelMatrix.inverted().transposed());
                }

                dotShader->setUniformValue(dotShader->MVP(), MVPMatrix);
                if (useColor) {
                    dotShader->setUniformValue(dotShader->color(), dotColor);
                } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) {
                    dotShader->setUniformValue(dotShader->gradientMin(),
                                               (item.translation().y() + m_scaleY)
                                               * rangeGradientYScaler);
                }
                if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
                    if (!drawingPoints) {
                        // Set shadow shader bindings
                        QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                        dotShader->setUniformValue(dotShader->shadowQ(), m_shadowQualityToShader);
                        dotShader->setUniformValue(dotShader->depth(), depthMVPMatrix);
                        dotShader->setUniformValue(dotShader->lightS(), lightStrength / 10.0f);

                        // Draw the object
                        if (optimizationDefault) {
                            m_drawer->drawObject(dotShader, dotObj, gradientTexture,
                                                 m_depthTexture);
                        } else {
                            m_drawer->drawObject(dotShader, cache->bufferObject(), gradientTexture,
                                                 m_depthTexture);
                        }
                    } else {
                        // Draw the object
                        if (optimizationDefault)
                            m_drawer->drawPoint(dotShader);
                        else
                            m_drawer->drawPoints(dotShader, cache->bufferPoints(), gradientTexture);
                    }
                } else {
                    if (!drawingPoints) {
                        // Set shadowless shader bindings
                        dotShader->setUniformValue(dotShader->lightS(), lightStrength);
                        // Draw the object
                        if (optimizationDefault)
                            m_drawer->drawObject(dotShader, dotObj, gradientTexture);
                        else
                            m_drawer->drawObject(dotShader, cache->bufferObject(), gradientTexture);
                    } else {
                        // Draw the object
                        if (optimizationDefault)
                            m_drawer->drawPoint(dotShader);
                        else
                            m_drawer->drawPoints(dotShader, cache->bufferPoints(), gradientTexture);
                    }
                }
            }


            // Draw the selected item on static optimization
            if (!optimizationDefault && selectedSeries
                    && m_selectedItemIndex != Scatter3DController::invalidSelectionIndex()) {
                ScatterRenderItem &item = renderArray[m_selectedItemIndex];
                if (item.isVisible()) {
                    ShaderHelper *selectionShader;
                    if (drawingPoints) {
                        selectionShader = pointSelectionShader;
                    } else {
                        if (colorStyleIsUniform)
                            selectionShader = m_staticSelectedItemShader;
                        else
                            selectionShader = m_staticSelectedItemGradientShader;
                    }
                    selectionShader->bind();

                    ObjectHelper *dotObj = cache->object();

                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 itModelMatrix;

                    modelMatrix.translate(item.translation());
                    if (!drawingPoints) {
                        if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) {
                            QQuaternion totalRotation = seriesRotation * item.rotation();
                            modelMatrix.rotate(totalRotation);
                            itModelMatrix.rotate(totalRotation);
                        }
                        modelMatrix.scale(modelScaler);
                        itModelMatrix.scale(modelScaler);

                        selectionShader->setUniformValue(selectionShader->lightP(),
                                                         lightPos);
                        selectionShader->setUniformValue(selectionShader->view(),
                                                         viewMatrix);
                        selectionShader->setUniformValue(selectionShader->ambientS(),
                                                         m_cachedTheme->ambientLightStrength());
                        selectionShader->setUniformValue(selectionShader->lightColor(),
                                                         lightColor);
                    }

                    QMatrix4x4 MVPMatrix;
#ifdef SHOW_DEPTH_TEXTURE_SCENE
                    MVPMatrix = depthProjectionViewMatrix * modelMatrix;
#else
                    MVPMatrix = projectionViewMatrix * modelMatrix;
#endif

                    if (useColor)
                        dotColor = cache->singleHighlightColor();
                    else
                        gradientTexture = cache->singleHighlightGradientTexture();
                    GLfloat lightStrength = m_cachedTheme->highlightLightStrength();
                    // Save the reference to the item to be used in label drawing
                    selectedItem = &item;
                    dotSelectionFound = true;
                    // Save selected item size (adjusted with font size) for selection label
                    // positioning
                    selectedItemSize = itemSize + m_drawer->scaledFontSize() - 0.05f;

                    if (!drawingPoints) {
                        // Set shader bindings
                        selectionShader->setUniformValue(selectionShader->model(), modelMatrix);
                        selectionShader->setUniformValue(selectionShader->nModel(),
                                                         itModelMatrix.inverted().transposed());
                        if (!colorStyleIsUniform) {
                            if (colorStyle == Q3DTheme::ColorStyleObjectGradient) {
                                selectionShader->setUniformValue(selectionShader->gradientMin(),
                                                                 0.0f);
                                selectionShader->setUniformValue(selectionShader->gradientHeight(),
                                                                 0.5f);
                            } else {
                                // Each dot is of uniform color according to its Y-coordinate
                                selectionShader->setUniformValue(selectionShader->gradientHeight(),
                                                                 0.0f);
                                selectionShader->setUniformValue(selectionShader->gradientMin(),
                                                                 (item.translation().y() + m_scaleY)
                                                                 * rangeGradientYScaler);
                            }
                        }
                    }

                    selectionShader->setUniformValue(selectionShader->MVP(), MVPMatrix);
                    if (useColor)
                        selectionShader->setUniformValue(selectionShader->color(), dotColor);

                    if (!drawingPoints) {
                        glEnable(GL_POLYGON_OFFSET_FILL);
                        glPolygonOffset(-1.0f, 1.0f);
                    }

                    if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone
                            && !m_isOpenGLES) {
                        if (!drawingPoints) {
                            // Set shadow shader bindings
                            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                            selectionShader->setUniformValue(selectionShader->shadowQ(),
                                                             m_shadowQualityToShader);
                            selectionShader->setUniformValue(selectionShader->depth(),
                                                             depthMVPMatrix);
                            selectionShader->setUniformValue(selectionShader->lightS(),
                                                             lightStrength / 10.0f);

                            // Draw the object
                            m_drawer->drawObject(selectionShader, dotObj, gradientTexture,
                                                 m_depthTexture);
                        } else {
                            // Draw the object
                            m_drawer->drawPoint(selectionShader);
                        }
                    } else {
                        if (!drawingPoints) {
                            // Set shadowless shader bindings
                            selectionShader->setUniformValue(selectionShader->lightS(),
                                                             lightStrength);
                            // Draw the object
                            m_drawer->drawObject(selectionShader, dotObj, gradientTexture);
                        } else {
                            // Draw the object
                            m_drawer->drawPoint(selectionShader);
                        }
                    }

                    if (!drawingPoints)
                        glDisable(GL_POLYGON_OFFSET_FILL);
                }
                dotShader->bind();
            }
        }
    }

#if !QT_CONFIG(opengles2)
    if (m_havePointSeries) {
        glDisable(GL_POINT_SMOOTH);
        glDisable(GL_PROGRAM_POINT_SIZE);
    }
#endif

    // Bind background shader
    m_backgroundShader->bind();

    glCullFace(GL_BACK);

    // Draw background
    if (m_cachedTheme->isBackgroundEnabled() && m_backgroundObj) {
        QMatrix4x4 modelMatrix;
        QMatrix4x4 MVPMatrix;
        QMatrix4x4 itModelMatrix;

        QVector3D bgScale(m_scaleXWithBackground, m_scaleYWithBackground,
                          m_scaleZWithBackground);
        modelMatrix.scale(bgScale);
        // If we're viewing from below, background object must be flipped
        if (m_yFlipped) {
            modelMatrix.rotate(m_xFlipRotation);
            modelMatrix.rotate(270.0f - backgroundRotation, 0.0f, 1.0f, 0.0f);
        } else {
            modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f);
        }
        itModelMatrix = modelMatrix; // Only scaling and rotations, can be used directly

#ifdef SHOW_DEPTH_TEXTURE_SCENE
        MVPMatrix = depthProjectionViewMatrix * modelMatrix;
#else
        MVPMatrix = projectionViewMatrix * modelMatrix;
#endif
        QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor());

        // Set shader bindings
        m_backgroundShader->setUniformValue(m_backgroundShader->lightP(), lightPos);
        m_backgroundShader->setUniformValue(m_backgroundShader->view(), viewMatrix);
        m_backgroundShader->setUniformValue(m_backgroundShader->model(), modelMatrix);
        m_backgroundShader->setUniformValue(m_backgroundShader->nModel(),
                                            itModelMatrix.inverted().transposed());
        m_backgroundShader->setUniformValue(m_backgroundShader->MVP(), MVPMatrix);
        m_backgroundShader->setUniformValue(m_backgroundShader->color(), backgroundColor);
        m_backgroundShader->setUniformValue(m_backgroundShader->ambientS(),
                                            m_cachedTheme->ambientLightStrength() * 2.0f);
        m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor);

        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
            // Set shadow shader bindings
            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
            m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(),
                                                m_shadowQualityToShader);
            m_backgroundShader->setUniformValue(m_backgroundShader->depth(), depthMVPMatrix);
            m_backgroundShader->setUniformValue(m_backgroundShader->lightS(),
                                                m_cachedTheme->lightStrength() / 10.0f);

            // Draw the object
            m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture);
        } else {
            // Set shadowless shader bindings
            m_backgroundShader->setUniformValue(m_backgroundShader->lightS(),
                                                m_cachedTheme->lightStrength());

            // Draw the object
            m_drawer->drawObject(m_backgroundShader, m_backgroundObj);
        }
    }

    // Draw grid lines
    QVector3D gridLineScaleX(m_scaleXWithBackground, gridLineWidth, gridLineWidth);
    QVector3D gridLineScaleZ(gridLineWidth, gridLineWidth, m_scaleZWithBackground);
    QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth);

    if (m_cachedTheme->isGridEnabled()) {
        ShaderHelper *lineShader;
        if (m_isOpenGLES)
            lineShader = m_selectionShader; // Plain color shader for GL_LINES
        else
            lineShader = m_backgroundShader;

        // Bind line shader
        lineShader->bind();

        // Set unchanging shader bindings
        QVector4D lineColor = Utils::vectorFromColor(m_cachedTheme->gridLineColor());
        lineShader->setUniformValue(lineShader->lightP(), lightPos);
        lineShader->setUniformValue(lineShader->view(), viewMatrix);
        lineShader->setUniformValue(lineShader->color(), lineColor);
        lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength());
        lineShader->setUniformValue(lineShader->lightColor(), lightColor);
        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
            // Set shadowed shader bindings
            lineShader->setUniformValue(lineShader->shadowQ(), m_shadowQualityToShader);
            lineShader->setUniformValue(lineShader->lightS(),
                                        m_cachedTheme->lightStrength() / 20.0f);
        } else {
            // Set shadowless shader bindings
            lineShader->setUniformValue(lineShader->lightS(),
                                        m_cachedTheme->lightStrength() / 2.5f);
        }

        QQuaternion lineYRotation;
        QQuaternion lineXRotation;

        if (m_xFlipped)
            lineYRotation = m_yRightAngleRotationNeg;
        else
            lineYRotation = m_yRightAngleRotation;

        if (m_yFlippedForGrid)
            lineXRotation = m_xRightAngleRotation;
        else
            lineXRotation = m_xRightAngleRotationNeg;

        GLfloat yFloorLinePosition = -m_scaleYWithBackground + gridLineOffset;
        if (m_yFlippedForGrid)
            yFloorLinePosition = -yFloorLinePosition;

        // Rows (= Z)
        if (m_axisCacheZ.segmentCount() > 0) {
            // Floor lines
            int gridLineCount = m_axisCacheZ.gridLineCount();
            if (m_polarGraph) {
                drawRadialGrid(lineShader, yFloorLinePosition, projectionViewMatrix,
                               depthProjectionViewMatrix);
            } else {
                for (int line = 0; line < gridLineCount; line++) {
                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 MVPMatrix;
                    QMatrix4x4 itModelMatrix;

                    modelMatrix.translate(0.0f, yFloorLinePosition,
                                          m_axisCacheZ.gridLinePosition(line));

                    modelMatrix.scale(gridLineScaleX);
                    itModelMatrix.scale(gridLineScaleX);

                    modelMatrix.rotate(lineXRotation);
                    itModelMatrix.rotate(lineXRotation);

                    MVPMatrix = projectionViewMatrix * modelMatrix;

                    // Set the rest of the shader bindings
                    lineShader->setUniformValue(lineShader->model(), modelMatrix);
                    lineShader->setUniformValue(lineShader->nModel(),
                                                itModelMatrix.inverted().transposed());
                    lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);

                    if (m_isOpenGLES) {
                        m_drawer->drawLine(lineShader);
                    } else {
                        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                            // Set shadow shader bindings
                            lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
                        } else {
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj);
                        }
                    }
                }

                // Side wall lines
                GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset;

                if (!m_xFlipped)
                    lineXTrans = -lineXTrans;

                for (int line = 0; line < gridLineCount; line++) {
                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 MVPMatrix;
                    QMatrix4x4 itModelMatrix;

                    modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line));

                    modelMatrix.scale(gridLineScaleY);
                    itModelMatrix.scale(gridLineScaleY);

                    if (m_isOpenGLES) {
                        modelMatrix.rotate(m_zRightAngleRotation);
                        itModelMatrix.rotate(m_zRightAngleRotation);
                    } else {
                        modelMatrix.rotate(lineYRotation);
                        itModelMatrix.rotate(lineYRotation);
                    }

                    MVPMatrix = projectionViewMatrix * modelMatrix;

                    // Set the rest of the shader bindings
                    lineShader->setUniformValue(lineShader->model(), modelMatrix);
                    lineShader->setUniformValue(lineShader->nModel(),
                                                itModelMatrix.inverted().transposed());
                    lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);

                    if (!m_isOpenGLES) {
                        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                            // Set shadow shader bindings
                            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                            lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
                        } else {
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj);
                        }
                    } else {
                        m_drawer->drawLine(lineShader);
                    }
                }
            }
        }

        // Columns (= X)
        if (m_axisCacheX.segmentCount() > 0) {
            if (m_isOpenGLES)
                lineXRotation = m_yRightAngleRotation;
            // Floor lines
            int gridLineCount = m_axisCacheX.gridLineCount();

            if (m_polarGraph) {
                drawAngularGrid(lineShader, yFloorLinePosition, projectionViewMatrix,
                                depthProjectionViewMatrix);
            } else {
                for (int line = 0; line < gridLineCount; line++) {
                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 MVPMatrix;
                    QMatrix4x4 itModelMatrix;

                    modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition,
                                          0.0f);

                    modelMatrix.scale(gridLineScaleZ);
                    itModelMatrix.scale(gridLineScaleZ);

                    modelMatrix.rotate(lineXRotation);
                    itModelMatrix.rotate(lineXRotation);

                    MVPMatrix = projectionViewMatrix * modelMatrix;

                    // Set the rest of the shader bindings
                    lineShader->setUniformValue(lineShader->model(), modelMatrix);
                    lineShader->setUniformValue(lineShader->nModel(),
                                                itModelMatrix.inverted().transposed());
                    lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);

                    if (!m_isOpenGLES) {
                        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                            // Set shadow shader bindings
                            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                            lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
                        } else {
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj);
                        }
                    } else {
                        m_drawer->drawLine(lineShader);
                    }
                }

                // Back wall lines
                GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset;

                if (!m_zFlipped)
                    lineZTrans = -lineZTrans;

                for (int line = 0; line < gridLineCount; line++) {
                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 MVPMatrix;
                    QMatrix4x4 itModelMatrix;

                    modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans);

                    modelMatrix.scale(gridLineScaleY);
                    itModelMatrix.scale(gridLineScaleY);

                    if (m_isOpenGLES) {
                        modelMatrix.rotate(m_zRightAngleRotation);
                        itModelMatrix.rotate(m_zRightAngleRotation);
                    } else {
                        if (m_zFlipped) {
                            modelMatrix.rotate(m_xFlipRotation);
                            itModelMatrix.rotate(m_xFlipRotation);
                        }
                    }

                    MVPMatrix = projectionViewMatrix * modelMatrix;

                    // Set the rest of the shader bindings
                    lineShader->setUniformValue(lineShader->model(), modelMatrix);
                    lineShader->setUniformValue(lineShader->nModel(),
                                                itModelMatrix.inverted().transposed());
                    lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);

                    if (!m_isOpenGLES) {
                        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                            // Set shadow shader bindings
                            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                            lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
                        } else {
                            // Draw the object
                            m_drawer->drawObject(lineShader, m_gridLineObj);
                        }
                    } else {
                        m_drawer->drawLine(lineShader);
                    }
                }
            }
        }

        // Horizontal wall lines
        if (m_axisCacheY.segmentCount() > 0) {
            // Back wall
            int gridLineCount = m_axisCacheY.gridLineCount();

            GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset;

            if (!m_zFlipped)
                lineZTrans = -lineZTrans;

            for (int line = 0; line < gridLineCount; line++) {
                QMatrix4x4 modelMatrix;
                QMatrix4x4 MVPMatrix;
                QMatrix4x4 itModelMatrix;

                modelMatrix.translate(0.0f, m_axisCacheY.gridLinePosition(line), lineZTrans);

                modelMatrix.scale(gridLineScaleX);
                itModelMatrix.scale(gridLineScaleX);

                if (m_zFlipped) {
                    modelMatrix.rotate(m_xFlipRotation);
                    itModelMatrix.rotate(m_xFlipRotation);
                }

                MVPMatrix = projectionViewMatrix * modelMatrix;

                // Set the rest of the shader bindings
                lineShader->setUniformValue(lineShader->model(), modelMatrix);
                lineShader->setUniformValue(lineShader->nModel(),
                                            itModelMatrix.inverted().transposed());
                lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);

                if (!m_isOpenGLES) {
                    if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                        // Set shadow shader bindings
                        QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                        lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
                        // Draw the object
                        m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
                    } else {
                        // Draw the object
                        m_drawer->drawObject(lineShader, m_gridLineObj);
                    }
                } else {
                    m_drawer->drawLine(lineShader);
                }
            }

            // Side wall
            GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset;

            if (!m_xFlipped)
                lineXTrans = -lineXTrans;

            for (int line = 0; line < gridLineCount; line++) {
                QMatrix4x4 modelMatrix;
                QMatrix4x4 MVPMatrix;
                QMatrix4x4 itModelMatrix;

                modelMatrix.translate(lineXTrans, m_axisCacheY.gridLinePosition(line), 0.0f);

                modelMatrix.scale(gridLineScaleZ);
                itModelMatrix.scale(gridLineScaleZ);

                modelMatrix.rotate(lineYRotation);
                itModelMatrix.rotate(lineYRotation);

                MVPMatrix = projectionViewMatrix * modelMatrix;

                // Set the rest of the shader bindings
                lineShader->setUniformValue(lineShader->model(), modelMatrix);
                lineShader->setUniformValue(lineShader->nModel(),
                                            itModelMatrix.inverted().transposed());
                lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);

                if (!m_isOpenGLES) {
                    if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                        // Set shadow shader bindings
                        QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                        lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
                        // Draw the object
                        m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
                    } else {
                        // Draw the object
                        m_drawer->drawObject(lineShader, m_gridLineObj);
                    }
                } else {
                    m_drawer->drawLine(lineShader);
                }
            }
        }
    }

    Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix,
                                        projectionViewMatrix, depthProjectionViewMatrix,
                                        m_depthTexture, m_shadowQualityToShader);

    drawLabels(false, activeCamera, viewMatrix, projectionMatrix);

    // Handle selection clearing and selection label drawing
    if (!dotSelectionFound) {
        // We have no ownership, don't delete. Just NULL the pointer.
        m_selectedItem = NULL;
    } else {
        glDisable(GL_DEPTH_TEST);
        // Draw the selection label
        LabelItem &labelItem = selectionLabelItem();
        if (m_selectedItem != selectedItem || m_updateLabels
                || !labelItem.textureId() || m_selectionLabelDirty) {
            QString labelText = selectionLabel();
            if (labelText.isNull() || m_selectionLabelDirty) {
                labelText = m_selectedSeriesCache->itemLabel();
                setSelectionLabel(labelText);
                m_selectionLabelDirty = false;
            }
            m_drawer->generateLabelItem(labelItem, labelText);
            m_selectedItem = selectedItem;
        }

        m_drawer->drawLabel(*selectedItem, labelItem, viewMatrix, projectionMatrix,
                            zeroVector, identityQuaternion, selectedItemSize, m_cachedSelectionMode,
                            m_labelShader, m_labelObj, activeCamera, true, false,
                            Drawer::LabelOver);

        // Reset label update flag; they should have been updated when we get here
        m_updateLabels = false;
        glEnable(GL_DEPTH_TEST);
    }

    glDisable(GL_BLEND);

    // Release shader
    glUseProgram(0);

    m_selectionDirty = false;
}

void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamera,
                                   const QMatrix4x4 &viewMatrix,
                                   const QMatrix4x4 &projectionMatrix) {
    ShaderHelper *shader = 0;
    GLfloat alphaForValueSelection = labelValueAlpha / 255.0f;
    GLfloat alphaForRowSelection = labelRowAlpha / 255.0f;
    GLfloat alphaForColumnSelection = labelColumnAlpha / 255.0f;
    if (drawSelection) {
        shader = m_selectionShader;
        // m_selectionShader is already bound
    } else {
        shader = m_labelShader;
        shader->bind();

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    }

    glEnable(GL_POLYGON_OFFSET_FILL);

    float labelAutoAngle = m_axisCacheZ.labelAutoRotation();
    float labelAngleFraction = labelAutoAngle / 90.0f;
    float fractionCamY = activeCamera->yRotation() * labelAngleFraction;
    float fractionCamX = activeCamera->xRotation() * labelAngleFraction;
    float labelsMaxWidth = 0.0f;

    int startIndex;
    int endIndex;
    int indexStep;

    // Z Labels
    if (m_axisCacheZ.segmentCount() > 0) {
        int labelCount = m_axisCacheZ.labelCount();
        float labelXTrans = m_scaleXWithBackground + labelMargin;
        float labelYTrans = -m_scaleYWithBackground;
        if (m_polarGraph) {
            labelXTrans *= m_radialLabelOffset;
            // YTrans up only if over background
            if (m_radialLabelOffset < 1.0f)
                labelYTrans += gridLineOffset + gridLineWidth;
        }
        Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
        QVector3D labelRotation;
        if (m_xFlipped)
            labelXTrans = -labelXTrans;
        if (m_yFlipped)
            labelYTrans = -labelYTrans;
        if (labelAutoAngle == 0.0f) {
            if (m_zFlipped)
                labelRotation.setY(180.0f);
            if (m_yFlippedForGrid) {
                if (m_zFlipped)
                    labelRotation.setY(180.0f);
                else
                    labelRotation.setY(0.0f);
                labelRotation.setX(90.0f);
            } else {
                labelRotation.setX(-90.0f);
            }
        } else {
            if (m_zFlipped)
                labelRotation.setY(180.0f);
            if (m_yFlippedForGrid) {
                if (m_zFlipped) {
                    if (m_xFlipped) {
                        labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX)
                                           * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle + fractionCamY);
                    } else {
                        labelRotation.setX(90.0f + (labelAutoAngle + fractionCamX)
                                           * (labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle - fractionCamY);
                    }
                } else {
                    if (m_xFlipped) {
                        labelRotation.setX(90.0f + (labelAutoAngle - fractionCamX)
                                           * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle - fractionCamY);
                    } else {
                        labelRotation.setX(90.0f - (labelAutoAngle + fractionCamX)
                                           * (labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle + fractionCamY);
                    }
                }
            } else {
                if (m_zFlipped) {
                    if (m_xFlipped) {
                        labelRotation.setX(-90.0f + (labelAutoAngle - fractionCamX)
                                           * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle + fractionCamY);
                    } else {
                        labelRotation.setX(-90.0f - (labelAutoAngle + fractionCamX)
                                           * (labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle - fractionCamY);
                    }
                } else {
                    if (m_xFlipped) {
                        labelRotation.setX(-90.0f - (labelAutoAngle - fractionCamX)
                                           * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle - fractionCamY);
                    } else {
                        labelRotation.setX(-90.0f + (labelAutoAngle + fractionCamX)
                                           * (labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle + fractionCamY);
                    }
                }
            }
        }
        QQuaternion totalRotation = Utils::calculateRotation(labelRotation);
        QVector3D labelTrans = QVector3D(labelXTrans, labelYTrans, 0.0f);
        if (m_zFlipped) {
            startIndex = 0;
            endIndex = labelCount;
            indexStep = 1;
        } else {
            startIndex = labelCount - 1;
            endIndex = -1;
            indexStep = -1;
        }
        float offsetValue = 0.0f;
        for (int label = startIndex; label != endIndex; label = label + indexStep) {
            glPolygonOffset(offsetValue++ / -10.0f, 1.0f);
            const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label);
            // Draw the label here
            if (m_polarGraph) {
                float direction = m_zFlipped ? -1.0f : 1.0f;
                labelTrans.setZ((m_axisCacheZ.formatter()->labelPositions().at(label)
                                 * -m_polarRadius
                                 + m_drawer->scaledFontSize() + gridLineWidth) * direction);
            } else {
                labelTrans.setZ(m_axisCacheZ.labelPosition(label));
            }
            if (label == 0 || label == (labelCount - 1)) {
                // If the margin is small, adjust the position of the edge labels to avoid overlapping
                // with labels of the other axes.
                float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height();
                float labelOverlap = qAbs(labelTrans.z())
                        + (scaleFactor * axisLabelItem.size().height() / 2.0f)
                        - m_scaleZWithBackground + labelMargin;
                // No need to adjust quite as much on the front edges
                if (label != startIndex)
                    labelOverlap /= 2.0f;
                if (labelOverlap > 0.0f) {
                    if (label == 0)
                        labelTrans.setZ(labelTrans.z() - labelOverlap);
                    else
                        labelTrans.setZ(labelTrans.z() + labelOverlap);
                }
            }
            m_dummyRenderItem.setTranslation(labelTrans);

            if (drawSelection) {
                QVector4D labelColor = QVector4D(label / 255.0f, 0.0f, 0.0f,
                                                 alphaForRowSelection);
                shader->setUniformValue(shader->color(), labelColor);
            }

            m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
                                zeroVector, totalRotation, 0, m_cachedSelectionMode,
                                shader, m_labelObj, activeCamera, true, true,
                                Drawer::LabelMid, alignment, false, drawSelection);
            labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width()));
        }
        if (!drawSelection && m_axisCacheZ.isTitleVisible()) {
            if (m_polarGraph) {
                float titleZ = -m_polarRadius / 2.0f;
                if (m_zFlipped)
                    titleZ = -titleZ;
                labelTrans.setZ(titleZ);
            } else {
                labelTrans.setZ(0.0f);
            }
            drawAxisTitleZ(labelRotation, labelTrans, totalRotation, m_dummyRenderItem,
                           activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader);
        }
    }

    // X Labels
    if (m_axisCacheX.segmentCount() > 0) {
        labelsMaxWidth = 0.0f;
        labelAutoAngle = m_axisCacheX.labelAutoRotation();
        labelAngleFraction = labelAutoAngle / 90.0f;
        fractionCamY = activeCamera->yRotation() * labelAngleFraction;
        fractionCamX = activeCamera->xRotation() * labelAngleFraction;
        int labelCount = m_axisCacheX.labelCount();
        float labelZTrans = 0.0f;
        float labelYTrans = -m_scaleYWithBackground;
        if (m_polarGraph)
            labelYTrans += gridLineOffset + gridLineWidth;
        else
            labelZTrans = m_scaleZWithBackground + labelMargin;

        Qt::Alignment alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
        QVector3D labelRotation;
        if (m_zFlipped)
            labelZTrans = -labelZTrans;
        if (m_yFlipped)
            labelYTrans = -labelYTrans;
        if (labelAutoAngle == 0.0f) {
            labelRotation = QVector3D(-90.0f, 90.0f, 0.0f);
            if (m_xFlipped)
                labelRotation.setY(-90.0f);
            if (m_yFlippedForGrid) {
                if (m_xFlipped)
                    labelRotation.setY(-90.0f);
                else
                    labelRotation.setY(90.0f);
                labelRotation.setX(90.0f);
            }
        } else {
            if (m_xFlipped)
                labelRotation.setY(-90.0f);
            else
                labelRotation.setY(90.0f);
            if (m_yFlippedForGrid) {
                if (m_zFlipped) {
                    if (m_xFlipped) {
                        labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX)
                                           * (labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle - fractionCamY);
                    } else {
                        labelRotation.setX(90.0f - (2.0f * labelAutoAngle + fractionCamX)
                                           * (labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle + fractionCamY);
                    }
                } else {
                    if (m_xFlipped) {
                        labelRotation.setX(90.0f + fractionCamX
                                           * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle + fractionCamY);
                    } else {
                        labelRotation.setX(90.0f - fractionCamX
                                           * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle - fractionCamY);
                    }
                }
            } else {
                if (m_zFlipped) {
                    if (m_xFlipped) {
                        labelRotation.setX(-90.0f + (2.0f * labelAutoAngle - fractionCamX)
                                           * (labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle - fractionCamY);
                    } else {
                        labelRotation.setX(-90.0f + (2.0f * labelAutoAngle + fractionCamX)
                                           * (labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle + fractionCamY);
                    }
                } else {
                    if (m_xFlipped) {
                        labelRotation.setX(-90.0f - fractionCamX
                                           * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(-labelAutoAngle + fractionCamY);
                    } else {
                        labelRotation.setX(-90.0f + fractionCamX
                                           * -(labelAutoAngle - fractionCamY) / labelAutoAngle);
                        labelRotation.setZ(labelAutoAngle - fractionCamY);
                    }
                }
            }
        }

        QQuaternion totalRotation = Utils::calculateRotation(labelRotation);
        if (m_polarGraph) {
            if ((!m_yFlippedForGrid && (m_zFlipped != m_xFlipped))
                    || (m_yFlippedForGrid && (m_zFlipped == m_xFlipped))) {
                totalRotation *= m_zRightAngleRotation;
            } else {
                totalRotation *= m_zRightAngleRotationNeg;
            }
        }
        QVector3D labelTrans = QVector3D(0.0f, labelYTrans, labelZTrans);
        if (m_xFlipped) {
            startIndex = labelCount - 1;
            endIndex = -1;
            indexStep = -1;
        } else {
            startIndex = 0;
            endIndex = labelCount;
            indexStep = 1;
        }
        float offsetValue = 0.0f;
        bool showLastLabel = false;
        QList<float> &labelPositions = m_axisCacheX.formatter()->labelPositions();
        int lastLabelPosIndex = labelPositions.size() - 1;
        if (labelPositions.size()
                && (labelPositions.at(lastLabelPosIndex) != 1.0f || labelPositions.at(0) != 0.0f)) {
            // Avoid overlapping first and last label if they would get on same position
            showLastLabel = true;
        }

        for (int label = startIndex; label != endIndex; label = label + indexStep) {
            glPolygonOffset(offsetValue++ / -10.0f, 1.0f);
            // Draw the label here
            if (m_polarGraph) {
                // Calculate angular position
                if (label == lastLabelPosIndex && !showLastLabel)
                    continue;
                float labelPosition = labelPositions.at(label);
                qreal angle = labelPosition * M_PI * 2.0;
                labelTrans.setX((m_polarRadius + labelMargin) * float(qSin(angle)));
                labelTrans.setZ(-(m_polarRadius + labelMargin) * float(qCos(angle)));
                // Alignment depends on label angular position, as well as flips
                Qt::AlignmentFlag vAlignment = Qt::AlignCenter;
                Qt::AlignmentFlag hAlignment = Qt::AlignCenter;
                const float centerMargin = 0.005f;
                if (labelPosition < 0.25f - centerMargin || labelPosition > 0.75f + centerMargin)
                    vAlignment = m_zFlipped ? Qt::AlignTop : Qt::AlignBottom;
                else if (labelPosition > 0.25f + centerMargin && labelPosition < 0.75f - centerMargin)
                    vAlignment = m_zFlipped ? Qt::AlignBottom : Qt::AlignTop;

                if (labelPosition < 0.50f - centerMargin && labelPosition > centerMargin)
                    hAlignment = m_zFlipped ? Qt::AlignRight : Qt::AlignLeft;
                else if (labelPosition < 1.0f - centerMargin && labelPosition > 0.5f + centerMargin)
                    hAlignment = m_zFlipped ? Qt::AlignLeft : Qt::AlignRight;
                if (m_yFlippedForGrid && vAlignment != Qt::AlignCenter)
                    vAlignment = (vAlignment == Qt::AlignTop) ? Qt::AlignBottom : Qt::AlignTop;
                alignment = vAlignment | hAlignment;
            } else {
                labelTrans.setX(m_axisCacheX.labelPosition(label));
            }
            const LabelItem &axisLabelItem = *m_axisCacheX.labelItems().at(label);
            if (label == 0 || label == (labelCount - 1)) {
                // If the margin is small, adjust the position of the edge labels to avoid overlapping
                // with labels of the other axes.
                float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height();
                float labelOverlap = qAbs(labelTrans.x())
                        + (scaleFactor * axisLabelItem.size().height() / 2.0f)
                        - m_scaleXWithBackground + labelMargin;
                // No need to adjust quite as much on the front edges
                if (label != startIndex)
                    labelOverlap /= 2.0f;
                if (labelOverlap > 0.0f) {
                    if (label == 0)
                        labelTrans.setX(labelTrans.x() + labelOverlap);
                    else
                        labelTrans.setX(labelTrans.x() - labelOverlap);
                }
            }
            m_dummyRenderItem.setTranslation(labelTrans);

            if (drawSelection) {
                QVector4D labelColor = QVector4D(0.0f, label / 255.0f, 0.0f,
                                                 alphaForColumnSelection);
                shader->setUniformValue(shader->color(), labelColor);
            }

            m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
                                zeroVector, totalRotation, 0, m_cachedSelectionMode,
                                shader, m_labelObj, activeCamera, true, true,
                                Drawer::LabelMid, alignment, false, drawSelection);
            labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width()));
        }
        if (!drawSelection && m_axisCacheX.isTitleVisible()) {
            labelTrans.setX(0.0f);
            bool radial = false;
            if (m_polarGraph) {
                if (m_xFlipped == m_zFlipped)
                    totalRotation *= m_zRightAngleRotation;
                else
                    totalRotation *= m_zRightAngleRotationNeg;
                if (m_yFlippedForGrid)
                    totalRotation *= QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -180.0f);
                labelTrans.setZ(-m_polarRadius);
                radial = true;
            }
            drawAxisTitleX(labelRotation, labelTrans, totalRotation, m_dummyRenderItem,
                           activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader,
                           radial);
        }
    }

    // Y Labels
    if (m_axisCacheY.segmentCount() > 0) {
        labelsMaxWidth = 0.0f;
        labelAutoAngle = m_axisCacheY.labelAutoRotation();
        labelAngleFraction = labelAutoAngle / 90.0f;
        fractionCamY = activeCamera->yRotation() * labelAngleFraction;
        fractionCamX = activeCamera->xRotation() * labelAngleFraction;
        int labelCount = m_axisCacheY.labelCount();

        float labelXTrans = m_scaleXWithBackground;
        float labelZTrans = m_scaleZWithBackground;

        // Back & side wall
        float labelMarginXTrans = labelMargin;
        float labelMarginZTrans = labelMargin;
        QVector3D backLabelRotation(0.0f, -90.0f, 0.0f);
        QVector3D sideLabelRotation(0.0f, 0.0f, 0.0f);
        Qt::AlignmentFlag backAlignment =
                (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
        Qt::AlignmentFlag sideAlignment =
                (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
        if (!m_xFlipped) {
            labelXTrans = -labelXTrans;
            labelMarginXTrans = -labelMargin;
        }
        if (m_zFlipped) {
            labelZTrans = -labelZTrans;
            labelMarginZTrans = -labelMargin;
        }
        if (labelAutoAngle == 0.0f) {
            if (!m_xFlipped)
                backLabelRotation.setY(90.0f);
            if (m_zFlipped)
                sideLabelRotation.setY(180.f);
        } else {
            // Orient side labels somewhat towards the camera
            if (m_xFlipped) {
                if (m_zFlipped)
                    sideLabelRotation.setY(180.0f + (2.0f * labelAutoAngle) - fractionCamX);
                else
                    sideLabelRotation.setY(-fractionCamX);
                backLabelRotation.setY(-90.0f + labelAutoAngle - fractionCamX);
            } else {
                if (m_zFlipped)
                    sideLabelRotation.setY(180.0f - (2.0f * labelAutoAngle) - fractionCamX);
                else
                    sideLabelRotation.setY(-fractionCamX);
                backLabelRotation.setY(90.0f - labelAutoAngle - fractionCamX);
            }
        }
        sideLabelRotation.setX(-fractionCamY);
        backLabelRotation.setX(-fractionCamY);

        QQuaternion totalSideRotation = Utils::calculateRotation(sideLabelRotation);
        QQuaternion totalBackRotation = Utils::calculateRotation(backLabelRotation);

        QVector3D labelTransBack = QVector3D(labelXTrans, 0.0f, labelZTrans + labelMarginZTrans);
        QVector3D labelTransSide(-labelXTrans - labelMarginXTrans, 0.0f, -labelZTrans);

        if (m_yFlipped) {
            startIndex = labelCount - 1;
            endIndex = -1;
            indexStep = -1;
        } else {
            startIndex = 0;
            endIndex = labelCount;
            indexStep = 1;
        }
        float offsetValue = 0.0f;
        for (int label = startIndex; label != endIndex; label = label + indexStep) {
            const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(label);
            float labelYTrans = m_axisCacheY.labelPosition(label);

            glPolygonOffset(offsetValue++ / -10.0f, 1.0f);

            if (drawSelection) {
                QVector4D labelColor = QVector4D(0.0f, 0.0f, label / 255.0f,
                                                 alphaForValueSelection);
                shader->setUniformValue(shader->color(), labelColor);
            }

            if (label == startIndex) {
                // If the margin is small, adjust the position of the edge label to avoid
                // overlapping with labels of the other axes.
                float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height();
                float labelOverlap = qAbs(labelYTrans)
                        + (scaleFactor * axisLabelItem.size().height() / 2.0f)
                        - m_scaleYWithBackground + labelMargin;
                if (labelOverlap > 0.0f) {
                    if (label == 0)
                        labelYTrans += labelOverlap;
                    else
                        labelYTrans -= labelOverlap;
                }
            }

            // Back wall
            labelTransBack.setY(labelYTrans);
            m_dummyRenderItem.setTranslation(labelTransBack);
            m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
                                zeroVector, totalBackRotation, 0, m_cachedSelectionMode,
                                shader, m_labelObj, activeCamera, true, true,
                                Drawer::LabelMid, backAlignment, false, drawSelection);

            // Side wall
            labelTransSide.setY(labelYTrans);
            m_dummyRenderItem.setTranslation(labelTransSide);
            m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
                                zeroVector, totalSideRotation, 0, m_cachedSelectionMode,
                                shader, m_labelObj, activeCamera, true, true,
                                Drawer::LabelMid, sideAlignment, false, drawSelection);
            labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width()));
        }
        if (!drawSelection && m_axisCacheY.isTitleVisible()) {
            labelTransSide.setY(0.0f);
            labelTransBack.setY(0.0f);
            drawAxisTitleY(sideLabelRotation, backLabelRotation, labelTransSide, labelTransBack,
                           totalSideRotation, totalBackRotation, m_dummyRenderItem, activeCamera,
                           labelsMaxWidth, viewMatrix, projectionMatrix,
                           shader);
        }
    }
    glDisable(GL_POLYGON_OFFSET_FILL);
}

void Scatter3DRenderer::updateSelectedItem(int index, QScatter3DSeries *series)
{
    m_selectionDirty = true;
    m_selectionLabelDirty = true;
    m_selectedSeriesCache =
            static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(series, 0));
    m_selectedItemIndex = Scatter3DController::invalidSelectionIndex();

    if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)
            && m_oldSelectedSeriesCache
            && m_oldSelectedSeriesCache->mesh() == QAbstract3DSeries::MeshPoint) {
        m_oldSelectedSeriesCache->bufferPoints()->popPoint();
        m_oldSelectedSeriesCache = 0;
    }

    if (m_selectedSeriesCache) {
        const ScatterRenderItemArray &renderArray = m_selectedSeriesCache->renderArray();
        if (index < renderArray.size() && index >= 0) {
            m_selectedItemIndex = index;

            if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)
                    && m_selectedSeriesCache->mesh() == QAbstract3DSeries::MeshPoint) {
                m_selectedSeriesCache->bufferPoints()->pushPoint(m_selectedItemIndex);
                m_oldSelectedSeriesCache = m_selectedSeriesCache;
            }
        }
    }
}

void Scatter3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality quality)
{
    m_cachedShadowQuality = quality;
    switch (quality) {
    case QAbstract3DGraph::ShadowQualityLow:
        m_shadowQualityToShader = 33.3f;
        m_shadowQualityMultiplier = 1;
        break;
    case QAbstract3DGraph::ShadowQualityMedium:
        m_shadowQualityToShader = 100.0f;
        m_shadowQualityMultiplier = 3;
        break;
    case QAbstract3DGraph::ShadowQualityHigh:
        m_shadowQualityToShader = 200.0f;
        m_shadowQualityMultiplier = 5;
        break;
    case QAbstract3DGraph::ShadowQualitySoftLow:
        m_shadowQualityToShader = 5.0f;
        m_shadowQualityMultiplier = 1;
        break;
    case QAbstract3DGraph::ShadowQualitySoftMedium:
        m_shadowQualityToShader = 10.0f;
        m_shadowQualityMultiplier = 3;
        break;
    case QAbstract3DGraph::ShadowQualitySoftHigh:
        m_shadowQualityToShader = 15.0f;
        m_shadowQualityMultiplier = 4;
        break;
    default:
        m_shadowQualityToShader = 0.0f;
        m_shadowQualityMultiplier = 1;
        break;
    }

    handleShadowQualityChange();

    // Re-init depth buffer
    updateDepthBuffer();
}

void Scatter3DRenderer::loadBackgroundMesh()
{
    ObjectHelper::resetObjectHelper(this, m_backgroundObj,
                                    QStringLiteral(":/defaultMeshes/background"));
}

void Scatter3DRenderer::updateTextures()
{
    Abstract3DRenderer::updateTextures();

    // Drawer has changed; this flag needs to be checked when checking if we need to update labels
    m_updateLabels = true;

    if (m_polarGraph)
        calculateSceneScalingFactors();
}

void Scatter3DRenderer::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh)
{
    // Load full version of meshes that have it available
    if (mesh != QAbstract3DSeries::MeshSphere
            && mesh != QAbstract3DSeries::MeshMinimal
            && mesh != QAbstract3DSeries::MeshPoint
            && mesh != QAbstract3DSeries::MeshArrow) {
        fileName.append(QStringLiteral("Full"));
    }
}

void Scatter3DRenderer::calculateTranslation(ScatterRenderItem &item)
{
    // We need to normalize translations
    const QVector3D &pos = item.position();
    float xTrans;
    float yTrans = m_axisCacheY.positionAt(pos.y());
    float zTrans;
    if (m_polarGraph) {
        calculatePolarXZ(pos, xTrans, zTrans);
    } else {
        xTrans = m_axisCacheX.positionAt(pos.x());
        zTrans = m_axisCacheZ.positionAt(pos.z());
    }
    item.setTranslation(QVector3D(xTrans, yTrans, zTrans));
}

void Scatter3DRenderer::calculateSceneScalingFactors()
{
    if (m_requestedMargin < 0.0f) {
        if (m_maxItemSize > defaultMaxSize)
            m_hBackgroundMargin = m_maxItemSize / itemScaler;
        else
            m_hBackgroundMargin = defaultMaxSize;
        m_vBackgroundMargin = m_hBackgroundMargin;
    } else {
        m_hBackgroundMargin = m_requestedMargin;
        m_vBackgroundMargin = m_requestedMargin;
    }
    if (m_polarGraph) {
        float polarMargin = calculatePolarBackgroundMargin();
        m_hBackgroundMargin = qMax(m_hBackgroundMargin, polarMargin);
    }

    float horizontalAspectRatio;
    if (m_polarGraph)
        horizontalAspectRatio = 1.0f;
    else
        horizontalAspectRatio = m_graphHorizontalAspectRatio;

    QSizeF areaSize;
    if (horizontalAspectRatio == 0.0f) {
        areaSize.setHeight(m_axisCacheZ.max() -  m_axisCacheZ.min());
        areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min());
    } else {
        areaSize.setHeight(1.0f);
        areaSize.setWidth(horizontalAspectRatio);
    }

    float horizontalMaxDimension;
    if (m_graphAspectRatio > 2.0f) {
        horizontalMaxDimension = 2.0f;
        m_scaleY = 2.0f / m_graphAspectRatio;
    } else {
        horizontalMaxDimension = m_graphAspectRatio;
        m_scaleY = 1.0f;
    }
    if (m_polarGraph)
        m_polarRadius = horizontalMaxDimension;

    float scaleFactor = qMax(areaSize.width(), areaSize.height());
    m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
    m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;

    m_scaleXWithBackground = m_scaleX + m_hBackgroundMargin;
    m_scaleYWithBackground = m_scaleY + m_vBackgroundMargin;
    m_scaleZWithBackground = m_scaleZ + m_hBackgroundMargin;

    m_axisCacheX.setScale(m_scaleX * 2.0f);
    m_axisCacheY.setScale(m_scaleY * 2.0f);
    m_axisCacheZ.setScale(-m_scaleZ * 2.0f);
    m_axisCacheX.setTranslate(-m_scaleX);
    m_axisCacheY.setTranslate(-m_scaleY);
    m_axisCacheZ.setTranslate(m_scaleZ);

    updateCameraViewport();
    updateCustomItemPositions();
}

void Scatter3DRenderer::initShaders(const QString &vertexShader, const QString &fragmentShader)
{
    delete m_dotShader;
    m_dotShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_dotShader->initialize();
}

void Scatter3DRenderer::initGradientShaders(const QString &vertexShader,
                                            const QString &fragmentShader)
{
    delete m_dotGradientShader;
    m_dotGradientShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_dotGradientShader->initialize();

}

void Scatter3DRenderer::initStaticSelectedItemShaders(const QString &vertexShader,
                                                      const QString &fragmentShader,
                                                      const QString &gradientVertexShader,
                                                      const QString &gradientFragmentShader)
{
    delete m_staticSelectedItemShader;
    m_staticSelectedItemShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_staticSelectedItemShader->initialize();

    delete m_staticSelectedItemGradientShader;
    m_staticSelectedItemGradientShader = new ShaderHelper(this, gradientVertexShader,
                                                          gradientFragmentShader);
    m_staticSelectedItemGradientShader->initialize();
}

void Scatter3DRenderer::initSelectionShader()
{
    delete m_selectionShader;
    m_selectionShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPlainColor"),
                                         QStringLiteral(":/shaders/fragmentPlainColor"));
    m_selectionShader->initialize();
}

void Scatter3DRenderer::initSelectionBuffer()
{
    m_textureHelper->deleteTexture(&m_selectionTexture);

    if (m_primarySubViewport.size().isEmpty())
        return;

    m_selectionTexture = m_textureHelper->createSelectionTexture(m_primarySubViewport.size(),
                                                                 m_selectionFrameBuffer,
                                                                 m_selectionDepthBuffer);
}

void Scatter3DRenderer::initDepthShader()
{
    if (!m_isOpenGLES) {
        if (m_depthShader)
            delete m_depthShader;
        m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"),
                                         QStringLiteral(":/shaders/fragmentDepth"));
        m_depthShader->initialize();
    }
}

void Scatter3DRenderer::updateDepthBuffer()
{
    if (!m_isOpenGLES) {
        m_textureHelper->deleteTexture(&m_depthTexture);

        if (m_primarySubViewport.size().isEmpty())
            return;

        if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
            m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(),
                                                                            m_depthFrameBuffer,
                                                                            m_shadowQualityMultiplier);
            if (!m_depthTexture)
                lowerShadowQuality();
        }
    }
}

void Scatter3DRenderer::initPointShader()
{
    if (m_isOpenGLES) {
        if (m_pointShader)
            delete m_pointShader;
        m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPointES2"),
                                         QStringLiteral(":/shaders/fragmentPlainColor"));
        m_pointShader->initialize();
    }
}

void Scatter3DRenderer::initBackgroundShaders(const QString &vertexShader,
                                              const QString &fragmentShader)
{
    if (m_backgroundShader)
        delete m_backgroundShader;
    m_backgroundShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_backgroundShader->initialize();
}

void Scatter3DRenderer::initStaticPointShaders(const QString &vertexShader,
                                               const QString &fragmentShader)
{
    if (m_staticGradientPointShader)
        delete m_staticGradientPointShader;
    m_staticGradientPointShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_staticGradientPointShader->initialize();
}

void Scatter3DRenderer::selectionColorToSeriesAndIndex(const QVector4D &color,
                                                       int &index,
                                                       QAbstract3DSeries *&series)
{
    m_clickedType = QAbstract3DGraph::ElementNone;
    m_selectedLabelIndex = -1;
    m_selectedCustomItemIndex = -1;
    if (color != selectionSkipColor) {
        if (color.w() == labelRowAlpha) {
            // Row selection
            index = Scatter3DController::invalidSelectionIndex();
            m_selectedLabelIndex = color.x();
            m_clickedType = QAbstract3DGraph::ElementAxisZLabel;
        } else if (color.w() == labelColumnAlpha) {
            // Column selection
            index = Scatter3DController::invalidSelectionIndex();
            m_selectedLabelIndex = color.y();
            m_clickedType = QAbstract3DGraph::ElementAxisXLabel;
        } else if (color.w() == labelValueAlpha) {
            // Value selection
            index = Scatter3DController::invalidSelectionIndex();
            m_selectedLabelIndex = color.z();
            m_clickedType = QAbstract3DGraph::ElementAxisYLabel;
        } else if (color.w() == customItemAlpha) {
            // Custom item selection
            index = Scatter3DController::invalidSelectionIndex();
            m_selectedCustomItemIndex = int(color.x())
                    + (int(color.y()) << 8)
                    + (int(color.z()) << 16);
            m_clickedType = QAbstract3DGraph::ElementCustomItem;
        } else {
            int totalIndex = int(color.x())
                    + (int(color.y()) << 8)
                    + (int(color.z()) << 16);
            // Find the series and adjust the index accordingly
            foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
                if (baseCache->isVisible()) {
                    ScatterSeriesRenderCache *cache =
                            static_cast<ScatterSeriesRenderCache *>(baseCache);
                    int offset = cache->selectionIndexOffset();
                    if (totalIndex >= offset
                            && totalIndex < (offset + cache->renderArray().size())) {
                        index = totalIndex - offset;
                        series = cache->series();
                        m_clickedType = QAbstract3DGraph::ElementSeries;
                        return;
                    }
                }
            }
        }
    }

    // No valid match found
    index = Scatter3DController::invalidSelectionIndex();
    series = 0;
}

void Scatter3DRenderer::updateRenderItem(const QScatterDataItem &dataItem,
                                         ScatterRenderItem &renderItem)
{
    QVector3D dotPos = dataItem.position();
    if ((dotPos.x() >= m_axisCacheX.min() && dotPos.x() <= m_axisCacheX.max() )
            && (dotPos.y() >= m_axisCacheY.min() && dotPos.y() <= m_axisCacheY.max())
            && (dotPos.z() >= m_axisCacheZ.min() && dotPos.z() <= m_axisCacheZ.max())) {
        renderItem.setPosition(dotPos);
        renderItem.setVisible(true);
        if (!dataItem.rotation().isIdentity())
            renderItem.setRotation(dataItem.rotation().normalized());
        else
            renderItem.setRotation(identityQuaternion);
        calculateTranslation(renderItem);
    } else {
        renderItem.setVisible(false);
    }
}

QVector3D Scatter3DRenderer::convertPositionToTranslation(const QVector3D &position,
                                                          bool isAbsolute)
{
    float xTrans = 0.0f;
    float yTrans = 0.0f;
    float zTrans = 0.0f;
    if (!isAbsolute) {
        if (m_polarGraph) {
            calculatePolarXZ(position, xTrans, zTrans);
        } else {
            xTrans = m_axisCacheX.positionAt(position.x());
            zTrans = m_axisCacheZ.positionAt(position.z());
        }
        yTrans = m_axisCacheY.positionAt(position.y());
    } else {
        xTrans = position.x() * m_scaleX;
        yTrans = position.y() * m_scaleY;
        zTrans = position.z() * -m_scaleZ;
    }
    return QVector3D(xTrans, yTrans, zTrans);
}

QT_END_NAMESPACE
