// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses

#include "bars3drenderer_p.h"
#include "q3dcamera_p.h"
#include "shaderhelper_p.h"
#include "texturehelper_p.h"
#include "utils_p.h"
#include "barseriesrendercache_p.h"

#include <QtGui/qquaternion.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 bool sliceGridLabels = true;

Bars3DRenderer::Bars3DRenderer(Bars3DController *controller)
    : Abstract3DRenderer(controller),
      m_cachedIsSlicingActivated(false),
      m_cachedRowCount(0),
      m_cachedColumnCount(0),
      m_cachedBarSeriesMargin(0.0f, 0.0f),
      m_selectedBar(0),
      m_sliceCache(0),
      m_sliceTitleItem(0),
      m_updateLabels(false),
      m_barShader(0),
      m_barGradientShader(0),
      m_depthShader(0),
      m_selectionShader(0),
      m_backgroundShader(0),
      m_bgrTexture(0),
      m_selectionTexture(0),
      m_depthFrameBuffer(0),
      m_selectionFrameBuffer(0),
      m_selectionDepthBuffer(0),
      m_shadowQualityToShader(100.0f),
      m_shadowQualityMultiplier(3),
      m_heightNormalizer(1.0f),
      m_backgroundAdjustment(0.0f),
      m_rowWidth(0),
      m_columnDepth(0),
      m_maxDimension(0),
      m_scaleX(0),
      m_scaleZ(0),
      m_scaleFactor(0),
      m_maxSceneSize(40.0f),
      m_visualSelectedBarPos(Bars3DController::invalidSelectionPosition()),
      m_selectedBarPos(Bars3DController::invalidSelectionPosition()),
      m_selectedSeriesCache(0),
      m_noZeroInRange(false),
      m_seriesScaleX(0.0f),
      m_seriesScaleZ(0.0f),
      m_seriesStep(0.0f),
      m_seriesStart(0.0f),
      m_clickedPosition(Bars3DController::invalidSelectionPosition()),
      m_keepSeriesUniform(false),
      m_haveUniformColorSeries(false),
      m_haveGradientSeries(false),
      m_zeroPosition(0.0f),
      m_xScaleFactor(1.0f),
      m_zScaleFactor(1.0f),
      m_floorLevel(0.0f),
      m_actualFloorLevel(0.0f)
{
    m_axisCacheY.setScale(2.0f);
    m_axisCacheY.setTranslate(-1.0f);

    initializeOpenGL();
}

Bars3DRenderer::~Bars3DRenderer()
{
    contextCleanup();
    delete m_barShader;
    delete m_barGradientShader;
    delete m_depthShader;
    delete m_selectionShader;
    delete m_backgroundShader;
}

void Bars3DRenderer::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 Bars3DRenderer::initializeOpenGL()
{
    Abstract3DRenderer::initializeOpenGL();

    // Initialize shaders

    // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api.
    initDepthShader();

    // Init selection shader
    initSelectionShader();

    // Load grid line mesh
    loadGridLineMesh();

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

void Bars3DRenderer::fixCameraTarget(QVector3D &target)
{
    target.setX(target.x() * m_xScaleFactor);
    target.setY(0.0f);
    target.setZ(target.z() * -m_zScaleFactor);
}

void Bars3DRenderer::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_xScaleFactor)
        minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_xScaleFactor) / itemRangeX));
    else
        minBounds.setX(-1.0f);

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

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

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

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

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

void Bars3DRenderer::updateData()
{
    int minRow = m_axisCacheZ.min();
    int maxRow = m_axisCacheZ.max();
    int minCol = m_axisCacheX.min();
    int maxCol = m_axisCacheX.max();
    int newRows = maxRow - minRow + 1;
    int newColumns = maxCol - minCol + 1;
    int dataRowCount = 0;
    int maxDataRowCount = 0;

    m_seriesScaleX = 1.0f / float(m_visibleSeriesCount);
    m_seriesStep = 1.0f / float(m_visibleSeriesCount);
    m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) / 2.0f)
            * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));

    if (m_keepSeriesUniform)
        m_seriesScaleZ = m_seriesScaleX;
    else
        m_seriesScaleZ = 1.0f;

    if (m_cachedRowCount != newRows || m_cachedColumnCount != newColumns) {
        // Force update for selection related items
        m_sliceCache = 0;
        m_sliceTitleItem = 0;

        m_cachedColumnCount = newColumns;
        m_cachedRowCount = newRows;
        // Calculate max scene size
        GLfloat sceneRatio = qMin(GLfloat(newColumns) / GLfloat(newRows),
                                  GLfloat(newRows) / GLfloat(newColumns));
        m_maxSceneSize = 2.0f * qSqrt(sceneRatio * newColumns * newRows);
    }

    calculateSceneScalingFactors();

    m_zeroPosition = m_axisCacheY.formatter()->positionAt(m_actualFloorLevel);

    for (auto baseCache: m_renderCacheList) {
        BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
        if (cache->isVisible()) {
            const QBar3DSeries *currentSeries = cache->series();
            BarRenderItemArray &renderArray = cache->renderArray();
            bool dimensionsChanged = false;
            if (newRows != renderArray.size()
                    || newColumns != renderArray.at(0).size()) {
                // Destroy old render items and reallocate new array
                dimensionsChanged = true;
                renderArray.resize(newRows);
                for (int i = 0; i < newRows; i++)
                    renderArray[i].resize(newColumns);
                cache->sliceArray().clear();
            }

            if (cache->dataDirty() || dimensionsChanged) {
                QBarDataProxy *dataProxy = currentSeries->dataProxy();
                dataRowCount = dataProxy->rowCount();
                if (maxDataRowCount < dataRowCount)
                    maxDataRowCount = qMin(dataRowCount, newRows);
                int dataRowIndex = minRow;
                for (int i = 0; i < newRows; i++) {
                    BarRenderItemRow &renderRow = renderArray[i];
                    const QBarDataRow *dataRow = 0;
                    if (dataRowIndex < dataRowCount)
                        dataRow = dataProxy->rowAt(dataRowIndex);
                    updateRenderRow(dataRow, renderRow);
                    dataRowIndex++;
                }
                cache->setDataDirty(false);
            }
        }
    }

    // Reset selected bar to update selection
    updateSelectedBar(m_selectedBarPos,
                      m_selectedSeriesCache ? m_selectedSeriesCache->series() : 0);
}

void Bars3DRenderer::updateRenderRow(const QBarDataRow *dataRow, BarRenderItemRow &renderRow)
{
    int j = 0;
    int renderRowSize = renderRow.size();
    int startIndex = m_axisCacheX.min();

    if (dataRow) {
        int updateSize = qMin((dataRow->size() - startIndex), renderRowSize);
        int dataColIndex = startIndex;
        for (; j < updateSize ; j++) {
            updateRenderItem(dataRow->at(dataColIndex), renderRow[j]);
            dataColIndex++;
        }
    }
    for (; j < renderRowSize; j++) {
        renderRow[j].setValue(0.0f);
        renderRow[j].setHeight(0.0f);
        renderRow[j].setRotation(identityQuaternion);
    }
}

void Bars3DRenderer::updateRenderItem(const QBarDataItem &dataItem, BarRenderItem &renderItem)
{
    float value = dataItem.value();
    float heightValue = m_axisCacheY.formatter()->positionAt(value);
    if (m_noZeroInRange) {
        if (m_hasNegativeValues) {
            heightValue = -1.0f + heightValue;
            if (heightValue > 0.0f)
                heightValue = 0.0f;
        } else {
            if (heightValue < 0.0f)
                heightValue = 0.0f;
        }
    } else {
        heightValue -= m_zeroPosition;
    }
    if (m_axisCacheY.reversed())
        heightValue = -heightValue;

    renderItem.setValue(value);
    renderItem.setHeight(heightValue);

    float angle = dataItem.rotation();
    if (angle) {
        renderItem.setRotation(
                    QQuaternion::fromAxisAndAngle(
                        upVector, angle));
    } else {
        renderItem.setRotation(identityQuaternion);
    }
}

void Bars3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList)
{
    Abstract3DRenderer::updateSeries(seriesList);

    bool noSelection = true;
    int seriesCount = seriesList.size();
    int visualIndex = 0;
    m_haveUniformColorSeries = false;
    m_haveGradientSeries = false;
    for (int i = 0; i < seriesCount; i++) {
        QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(seriesList[i]);
        BarSeriesRenderCache *cache =
                static_cast<BarSeriesRenderCache *>(m_renderCacheList.value(barSeries));
        if (barSeries->isVisible()) {
            if (noSelection
                    && barSeries->selectedBar() != QBar3DSeries::invalidSelectionPosition()) {
                if (selectionLabel() != cache->itemLabel())
                    m_selectionLabelDirty = true;
                noSelection = false;
            }
            cache->setVisualIndex(visualIndex++);
            if (cache->colorStyle() == Q3DTheme::ColorStyleUniform)
                m_haveUniformColorSeries = true;
            else
                m_haveGradientSeries = true;
        } else {
            cache->setVisualIndex(-1);
        }

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

SeriesRenderCache *Bars3DRenderer::createNewCache(QAbstract3DSeries *series)
{
    return new BarSeriesRenderCache(series, this);
}

void Bars3DRenderer::updateRows(const QList<Bars3DController::ChangeRow> &rows)
{
    int minRow = m_axisCacheZ.min();
    int maxRow = m_axisCacheZ.max();
    BarSeriesRenderCache *cache = 0;
    const QBar3DSeries *prevSeries = 0;
    const QBarDataArray *dataArray = 0;

    for (auto item: rows) {
        const int row = item.row;
        if (row < minRow || row > maxRow)
            continue;
        QBar3DSeries *currentSeries = item.series;
        if (currentSeries != prevSeries) {
            cache = static_cast<BarSeriesRenderCache *>(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()) {
            updateRenderRow(dataArray->at(row), cache->renderArray()[row - minRow]);
            if (m_cachedIsSlicingActivated
                    && cache == m_selectedSeriesCache
                    && m_selectedBarPos.x() == row) {
                m_selectionDirty = true; // Need to update slice view
            }
        }
    }
}

void Bars3DRenderer::updateItems(const QList<Bars3DController::ChangeItem> &items)
{
    int minRow = m_axisCacheZ.min();
    int maxRow = m_axisCacheZ.max();
    int minCol = m_axisCacheX.min();
    int maxCol = m_axisCacheX.max();
    BarSeriesRenderCache *cache = 0;
    const QBar3DSeries *prevSeries = 0;
    const QBarDataArray *dataArray = 0;

    for (auto item: items) {
        const int row = item.point.x();
        const int col = item.point.y();
        if (row < minRow || row > maxRow || col < minCol || col > maxCol)
            continue;
        QBar3DSeries *currentSeries = item.series;
        if (currentSeries != prevSeries) {
            cache = static_cast<BarSeriesRenderCache *>(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()) {
            updateRenderItem(dataArray->at(row)->at(col),
                             cache->renderArray()[row - minRow][col - minCol]);
            if (m_cachedIsSlicingActivated
                    && cache == m_selectedSeriesCache
                    && m_selectedBarPos == QPoint(row, col)) {
                m_selectionDirty = true; // Need to update slice view
            }
        }
    }
}

void Bars3DRenderer::updateScene(Q3DScene *scene)
{
    if (!m_noZeroInRange) {
        scene->activeCamera()->d_ptr->setMinYRotation(-90.0);
        scene->activeCamera()->d_ptr->setMaxYRotation(90.0);
    } else {
        if ((m_hasNegativeValues && !m_axisCacheY.reversed())
                || (!m_hasNegativeValues && m_axisCacheY.reversed())) {
            scene->activeCamera()->d_ptr->setMinYRotation(-90.0f);
            scene->activeCamera()->d_ptr->setMaxYRotation(0.0);
        } else {
            scene->activeCamera()->d_ptr->setMinYRotation(0.0f);
            scene->activeCamera()->d_ptr->setMaxYRotation(90.0);
        }
    }

    Abstract3DRenderer::updateScene(scene);

    updateSlicingActive(scene->isSlicingActive());
}

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

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

    drawScene(defaultFboHandle);
    if (m_cachedIsSlicingActivated)
        drawSlicedScene();
}

void Bars3DRenderer::drawSlicedScene()
{
    if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow)
            == m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) {
        qWarning("Invalid selection mode. Either QAbstract3DGraph::SelectionRow or"
                 " QAbstract3DGraph::SelectionColumn must be set before calling"
                 " setSlicingActive(true).");
        return;
    }

    GLfloat barPosX = 0;
    QVector3D lightPos;
    QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor());

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

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

    // Set view matrix
    QMatrix4x4 viewMatrix;

    // Adjust scaling (zoom rate based on aspect ratio)
    GLfloat camZPosSliced = cameraDistance / m_autoScaleAdjustment;

    viewMatrix.lookAt(QVector3D(0.0f, 0.0f, camZPosSliced), zeroVector, upVector);

    // Set light position
    lightPos = QVector3D(0.0f, 0.0f, camZPosSliced * 2.0f);

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

    // Draw the selected row / column
    QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix;
    bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow);
    bool itemMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionItem);

    GLfloat barPosYAdjustment = -0.8f; // Translate to -1.0 + 0.2 for row/column labels
    GLfloat gridAdjustment = 1.0f + barPosYAdjustment - m_backgroundAdjustment;
    GLfloat scaleFactor = 0.0f;
    if (rowMode)
        scaleFactor = (1.1f * m_rowWidth) / m_scaleFactor;
    else
        scaleFactor = (1.1f * m_columnDepth) / m_scaleFactor;
    GLfloat barLabelYPos = barPosYAdjustment - labelMargin;
    GLfloat zeroPosAdjustment = 0.0f;
    GLfloat directionMultiplier = 2.0f;
    GLfloat directionBase = 0.0f;
    if (m_axisCacheY.reversed()) {
        directionMultiplier = -2.0f;
        directionBase = -2.0f;
    }
    zeroPosAdjustment = directionBase +
            directionMultiplier * m_axisCacheY.min() / m_heightNormalizer;
    zeroPosAdjustment = qBound(-2.0f, zeroPosAdjustment, 0.0f);

    // Draw grid lines
    if (m_cachedTheme->isGridEnabled()) {
        glDisable(GL_DEPTH_TEST);
        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()
                                    + m_cachedTheme->lightStrength() / 7.0f);
        lineShader->setUniformValue(lineShader->lightS(), 0.0f);
        lineShader->setUniformValue(lineShader->lightColor(), lightColor);

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

            QVector3D gridLineScale(scaleFactor, gridLineWidth, gridLineWidth);
            bool noZero = true;
            QMatrix4x4 MVPMatrix;
            QMatrix4x4 itModelMatrix;

            for (int line = 0; line < gridLineCount; line++) {
                QMatrix4x4 modelMatrix;
                GLfloat gridPos = m_axisCacheY.gridLinePosition(line) + gridAdjustment;
                modelMatrix.translate(0.0f, gridPos, 0.0f);
                modelMatrix.scale(gridLineScale);
                itModelMatrix = modelMatrix;
                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);

                // Draw the object
                if (m_isOpenGLES)
                    m_drawer->drawLine(lineShader);
                else
                    m_drawer->drawObject(lineShader, m_gridLineObj);

                // Check if we have a line at zero position already
                if (gridPos == (barPosYAdjustment + zeroPosAdjustment))
                    noZero = false;
            }

            // Draw a line at zero, if none exists
            if (!m_noZeroInRange && noZero) {
                QMatrix4x4 modelMatrix;
                modelMatrix.translate(0.0f, barPosYAdjustment - zeroPosAdjustment, 0.0f);
                modelMatrix.scale(gridLineScale);
                itModelMatrix = modelMatrix;
                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);
                lineShader->setUniformValue(lineShader->color(),
                                            Utils::vectorFromColor(
                                                m_cachedTheme->labelTextColor()));

                // Draw the object
                if (m_isOpenGLES)
                    m_drawer->drawLine(lineShader);
                else
                    m_drawer->drawObject(lineShader, m_gridLineObj);
            }
        }

        if (sliceGridLabels) {
            // Bind label shader
            m_labelShader->bind();
            glCullFace(GL_BACK);
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

            // Draw grid labels
            int labelNbr = 0;
            int labelCount = m_axisCacheY.labelCount();
            QVector3D labelTrans = QVector3D(scaleFactor + labelMargin, 0.0f, 0.0f);

            for (int i = 0; i < labelCount; i++) {
                if (m_axisCacheY.labelItems().size() > labelNbr) {
                    const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(labelNbr);
                    GLfloat gridPos = m_axisCacheY.labelPosition(i) + gridAdjustment;
                    labelTrans.setY(gridPos);
                    m_dummyBarRenderItem.setTranslation(labelTrans);
                    m_drawer->drawLabel(m_dummyBarRenderItem, axisLabelItem, viewMatrix,
                                        projectionMatrix, zeroVector, identityQuaternion, 0,
                                        m_cachedSelectionMode, m_labelShader, m_labelObj,
                                        activeCamera, true, true, Drawer::LabelMid, Qt::AlignLeft);
                }
                labelNbr++;
            }
            glDisable(GL_BLEND);
            glEnable(GL_DEPTH_TEST);
        }
    }

    // Draw bars
    QVector3D modelMatrixScaler(m_scaleX * m_seriesScaleX, 0.0f, m_scaleZ * m_seriesScaleZ);
    if (!rowMode) {
        modelMatrixScaler.setX(m_scaleZ * m_seriesScaleZ);
        modelMatrixScaler.setZ(m_scaleX * m_seriesScaleX);
    }

    // Set common bar shader bindings
    m_barShader->bind();
    m_barShader->setUniformValue(m_barShader->lightP(), lightPos);
    m_barShader->setUniformValue(m_barShader->view(), viewMatrix);
    m_barShader->setUniformValue(m_barShader->lightS(), 0.15f);
    m_barShader->setUniformValue(m_barShader->ambientS(),
                                 m_cachedTheme->ambientLightStrength()
                                 + m_cachedTheme->lightStrength() / 7.0f);
    m_barShader->setUniformValue(m_barShader->lightColor(), lightColor);
    m_barGradientShader->bind();
    m_barGradientShader->setUniformValue(m_barGradientShader->lightP(), lightPos);
    m_barGradientShader->setUniformValue(m_barGradientShader->view(), viewMatrix);
    m_barGradientShader->setUniformValue(m_barGradientShader->lightS(), 0.15f);
    m_barGradientShader->setUniformValue(m_barGradientShader->ambientS(),
                                         m_cachedTheme->ambientLightStrength()
                                         + m_cachedTheme->lightStrength() / 7.0f);
    m_barGradientShader->setUniformValue(m_barGradientShader->gradientMin(), 0.0f);
    m_barGradientShader->setUniformValue(m_barGradientShader->lightColor(), lightColor);

    // Default to uniform shader
    ShaderHelper *barShader = m_barShader;
    barShader->bind();

    Q3DTheme::ColorStyle previousColorStyle = Q3DTheme::ColorStyleUniform;
    Q3DTheme::ColorStyle colorStyle = Q3DTheme::ColorStyleUniform;
    ObjectHelper *barObj = 0;
    QVector4D highlightColor;
    QVector4D baseColor;
    GLuint highlightGradientTexture = 0;
    GLuint baseGradientTexture = 0;
    bool colorStyleIsUniform = true;
    int firstVisualIndex = m_renderCacheList.size();
    QList<BarRenderSliceItem> *firstVisualSliceArray = 0;
    BarRenderSliceItem *selectedItem = 0;

    QQuaternion seriesRotation;
    for (auto baseCache: m_renderCacheList) {
        if (baseCache->isVisible()
                && (baseCache == m_selectedSeriesCache
                    || m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionMultiSeries))) {
            BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
            QList<BarRenderSliceItem> &sliceArray = cache->sliceArray();
            int sliceCount = sliceArray.size();
            if (firstVisualIndex > cache->visualIndex()) {
                firstVisualIndex = cache->visualIndex();
                firstVisualSliceArray = &sliceArray;
            }

            barObj = cache->object();
            colorStyle = cache->colorStyle();
            colorStyleIsUniform = (colorStyle == Q3DTheme::ColorStyleUniform);
            if (colorStyleIsUniform) {
                highlightColor = cache->singleHighlightColor();
                baseColor = cache->baseColor();
            } else {
                highlightGradientTexture = cache->singleHighlightGradientTexture();
                baseGradientTexture = cache->baseGradientTexture();
            }

            // Rebind shader if it has changed
            if (colorStyleIsUniform != (previousColorStyle == Q3DTheme::ColorStyleUniform)) {
                if (colorStyleIsUniform)
                    barShader = m_barShader;
                else
                    barShader = m_barGradientShader;
                barShader->bind();
            }

            if (!colorStyleIsUniform && (previousColorStyle != colorStyle)
                    && (colorStyle == Q3DTheme::ColorStyleObjectGradient)) {
                m_barGradientShader->setUniformValue(m_barGradientShader->gradientHeight(), 0.5f);
            }

            previousColorStyle = colorStyle;
            seriesRotation = cache->meshRotation();
            bool selectedSeries = (cache == m_selectedSeriesCache);

            for (int bar = 0; bar < sliceCount; bar++) {
                BarRenderSliceItem &item = cache->sliceArray()[bar];
                if (selectedSeries && itemMode && sliceGridLabels
                        && m_visualSelectedBarPos.x() == item.position().x()
                        && m_visualSelectedBarPos.y() == item.position().y()) {
                    selectedItem = &item;
                }
                if (!item.value())
                    continue;

                if (item.height() < 0)
                    glCullFace(GL_FRONT);
                else
                    glCullFace(GL_BACK);

                QMatrix4x4 MVPMatrix;
                QMatrix4x4 modelMatrix;
                QMatrix4x4 itModelMatrix;
                QQuaternion barRotation = item.rotation();
                GLfloat barPosY = item.translation().y() + barPosYAdjustment - zeroPosAdjustment;

                if (rowMode) {
                    barPosX = item.translation().x();
                } else {
                    barPosX = -(item.translation().z()); // flip z; frontmost bar to the left
                    barRotation *= m_yRightAngleRotation;
                }

                modelMatrix.translate(barPosX, barPosY, 0.0f);
                modelMatrixScaler.setY(item.height());

                if (!seriesRotation.isIdentity())
                    barRotation *= seriesRotation;

                if (!barRotation.isIdentity()) {
                    modelMatrix.rotate(barRotation);
                    itModelMatrix.rotate(barRotation);
                }

                modelMatrix.scale(modelMatrixScaler);
                itModelMatrix.scale(modelMatrixScaler);

                MVPMatrix = projectionViewMatrix * modelMatrix;

                QVector4D barColor;
                GLuint gradientTexture = 0;

                if (itemMode && m_visualSelectedBarPos.x() == item.position().x()
                        && m_visualSelectedBarPos.y() == item.position().y()) {
                    if (colorStyleIsUniform)
                        barColor = highlightColor;
                    else
                        gradientTexture = highlightGradientTexture;
                } else {
                    if (colorStyleIsUniform)
                        barColor = baseColor;
                    else
                        gradientTexture = baseGradientTexture;
                }

                if (item.height() != 0) {
                    // Set shader bindings
                    barShader->setUniformValue(barShader->model(), modelMatrix);
                    barShader->setUniformValue(barShader->nModel(),
                                               itModelMatrix.inverted().transposed());
                    barShader->setUniformValue(barShader->MVP(), MVPMatrix);
                    if (colorStyleIsUniform) {
                        barShader->setUniformValue(barShader->color(), barColor);
                    } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) {
                        barShader->setUniformValue(barShader->gradientHeight(),
                                                   (qAbs(item.height()) / m_gradientFraction));
                    }

                    // Draw the object
                    m_drawer->drawObject(barShader,
                                         barObj,
                                         gradientTexture);
                }
            }
        }
    }

    // Draw labels
    m_labelShader->bind();
    glDisable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    const LabelItem &sliceSelectionLabel = *m_sliceTitleItem;
    QVector3D positionComp(0.0f, m_autoScaleAdjustment, 0.0f);

    // Draw labels for bars
    QVector3D sliceValueRotation(0.0f, 0.0f, 90.0f);
    QVector3D sliceLabelRotation(0.0f, 0.0f, -45.0f);
    QQuaternion totalSliceValueRotation = Utils::calculateRotation(sliceValueRotation);
    QQuaternion totalSliceLabelRotation = Utils::calculateRotation(sliceLabelRotation);

    int labelCount = m_sliceCache->labelItems().size();

    for (int labelNo = 0; labelNo < labelCount; labelNo++) {
        // Check for invalid usage (no selection when setting slicing active)
        if (!firstVisualSliceArray) {
            qWarning("No slice data found. Make sure there is a valid selection.");
            continue;
        }

        // Get labels from first series only
        const BarRenderSliceItem &item = firstVisualSliceArray->at(labelNo);
        m_dummyBarRenderItem.setTranslation(QVector3D(item.translation().x(),
                                                      barLabelYPos,
                                                      item.translation().z()));

        // Draw labels
        m_drawer->drawLabel(m_dummyBarRenderItem, *m_sliceCache->labelItems().at(labelNo),
                            viewMatrix, projectionMatrix, positionComp, totalSliceLabelRotation,
                            0, m_cachedSelectionMode, m_labelShader,
                            m_labelObj, activeCamera, false, false, Drawer::LabelMid,
                            Qt::AlignLeft | Qt::AlignTop, true);
    }

    if (!sliceGridLabels) {
        for (auto baseCache: m_renderCacheList) {
            if (baseCache->isVisible()) {
                BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
                QList<BarRenderSliceItem> &sliceArray = cache->sliceArray();
                int sliceCount = sliceArray.size();
                for (int col = 0; col < sliceCount; col++) {
                    BarRenderSliceItem &item = sliceArray[col];

                    // Draw values
                    if (item.height() != 0.0f || (!m_noZeroInRange && item.value() == 0.0f)) {
                        // Create label texture if we need it
                        if (item.sliceLabel().isNull() || m_updateLabels) {
                            QString valueLabelText = m_axisCacheY.formatter()->stringForValue(
                                        qreal(item.value()), m_axisCacheY.labelFormat());
                            item.setSliceLabel(valueLabelText);
                            m_drawer->generateLabelItem(item.sliceLabelItem(), item.sliceLabel());
                            m_updateLabels = false;
                        }
                        Qt::AlignmentFlag alignment =
                                (item.height() > 0) ? Qt::AlignLeft : Qt::AlignRight;
                        Drawer::LabelPosition labelPos =
                                (item.height() < 0) ? Drawer::LabelBelow : Drawer::LabelOver;
                        m_dummyBarRenderItem.setTranslation(QVector3D(item.translation().x(),
                                                                      barPosYAdjustment
                                                                      - zeroPosAdjustment
                                                                      + item.height(),
                                                                      item.translation().z()));

                        m_drawer->drawLabel(m_dummyBarRenderItem, item.sliceLabelItem(), viewMatrix,
                                            projectionMatrix, zeroVector, totalSliceValueRotation,
                                            item.height(), m_cachedSelectionMode, m_labelShader,
                                            m_labelObj, activeCamera, false, false, labelPos,
                                            alignment, true);
                    }
                }
            }
        }
    } else if (selectedItem) {
        // Only draw value for selected item when grid labels are on
        // Create label texture if we need it
        if (selectedItem->sliceLabel().isNull() || m_updateLabels) {
            QString valueLabelText = m_axisCacheY.formatter()->stringForValue(
                        qreal(selectedItem->value()), m_axisCacheY.labelFormat());
            selectedItem->setSliceLabel(valueLabelText);
            m_drawer->generateLabelItem(selectedItem->sliceLabelItem(), selectedItem->sliceLabel());
            m_updateLabels = false;
        }
        Qt::AlignmentFlag alignment = (selectedItem->height() > 0) ? Qt::AlignLeft : Qt::AlignRight;
        Drawer::LabelPosition labelPos =
                (selectedItem->height() < 0) ? Drawer::LabelBelow : Drawer::LabelOver;
        m_dummyBarRenderItem.setTranslation(QVector3D(selectedItem->translation().x(),
                                                      barPosYAdjustment - zeroPosAdjustment
                                                      + selectedItem->height(),
                                                      selectedItem->translation().z()));

        m_drawer->drawLabel(m_dummyBarRenderItem, selectedItem->sliceLabelItem(), viewMatrix,
                            projectionMatrix, zeroVector, totalSliceValueRotation,
                            selectedItem->height(), m_cachedSelectionMode, m_labelShader,
                            m_labelObj, activeCamera, false, false, labelPos,
                            alignment, true);
    }

    // Draw labels for axes
    if (rowMode) {
        if (m_sliceTitleItem) {
            m_drawer->drawLabel(m_dummyBarRenderItem, sliceSelectionLabel, viewMatrix, projectionMatrix,
                                positionComp, identityQuaternion, 0, m_cachedSelectionMode,
                                m_labelShader, m_labelObj, activeCamera, false, false,
                                Drawer::LabelTop, Qt::AlignCenter, true);
        }
        m_drawer->drawLabel(m_dummyBarRenderItem, m_axisCacheX.titleItem(), viewMatrix, projectionMatrix,
                            positionComp, identityQuaternion, 0, m_cachedSelectionMode,
                            m_labelShader, m_labelObj, activeCamera, false, false,
                            Drawer::LabelBottom, Qt::AlignCenter, true);
    } else {
        m_drawer->drawLabel(m_dummyBarRenderItem, m_axisCacheZ.titleItem(), viewMatrix, projectionMatrix,
                            positionComp, identityQuaternion, 0, m_cachedSelectionMode,
                            m_labelShader,
                            m_labelObj, activeCamera, false, false, Drawer::LabelBottom,
                            Qt::AlignCenter, true);
        if (m_sliceTitleItem) {
            m_drawer->drawLabel(m_dummyBarRenderItem, sliceSelectionLabel, viewMatrix, projectionMatrix,
                                positionComp, identityQuaternion, 0, m_cachedSelectionMode,
                                m_labelShader,
                                m_labelObj, activeCamera, false, false, Drawer::LabelTop,
                                Qt::AlignCenter, true);
        }
    }
    // Y-axis label
    QVector3D labelTrans = QVector3D(-scaleFactor - labelMargin, 0.2f, 0.0f); // y = 0.2 for row/column labels (see barPosYAdjustment)
    m_dummyBarRenderItem.setTranslation(labelTrans);
    m_drawer->drawLabel(m_dummyBarRenderItem, m_axisCacheY.titleItem(), viewMatrix,
                        projectionMatrix, zeroVector, totalSliceValueRotation, 0,
                        m_cachedSelectionMode, m_labelShader, m_labelObj, activeCamera,
                        false, false, Drawer::LabelMid, Qt::AlignBottom);

    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);

    // Release shader
    glUseProgram(0);
}

void Bars3DRenderer::drawScene(GLuint defaultFboHandle)
{
    GLint startBar = 0;
    GLint stopBar = 0;
    GLint stepBar = 0;

    GLint startRow = 0;
    GLint stopRow = 0;
    GLint stepRow = 0;

    GLfloat backgroundRotation = 0;

    GLfloat colPos = 0;
    GLfloat rowPos = 0;

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

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

    // Get the view matrix
    QMatrix4x4 viewMatrix = activeCamera->d_ptr->viewMatrix();

    // Calculate drawing order
    // Draw order is reversed to optimize amount of drawing (ie. draw front objects first,
    // depth test handles not needing to draw objects behind them)
    if (viewMatrix.row(0).x() > 0) {
        startRow = 0;
        stopRow = m_cachedRowCount;
        stepRow = 1;
        m_zFlipped = false;
    } else {
        startRow = m_cachedRowCount - 1;
        stopRow = -1;
        stepRow = -1;
        m_zFlipped = true;
    }
    if (viewMatrix.row(0).z() <= 0) {
        startBar = 0;
        stopBar = m_cachedColumnCount;
        stepBar = 1;
        m_xFlipped = false;
    } else {
        startBar = m_cachedColumnCount - 1;
        stopBar = -1;
        stepBar = -1;
        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;

    // calculate background rotation based on view matrix rotation
    if (viewMatrix.row(0).x() > 0 && viewMatrix.row(0).z() <= 0)
        backgroundRotation = 270.0f;
    else if (viewMatrix.row(0).x() > 0 && viewMatrix.row(0).z() > 0)
        backgroundRotation = 180.0f;
    else if (viewMatrix.row(0).x() <= 0 && viewMatrix.row(0).z() > 0)
        backgroundRotation = 90.0f;
    else if (viewMatrix.row(0).x() <= 0 && viewMatrix.row(0).z() <= 0)
        backgroundRotation = 0.0f;

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

    // Skip depth rendering if we're in slice mode
    // Introduce regardless of shadow quality to simplify logic
    QMatrix4x4 depthViewMatrix;
    QMatrix4x4 depthProjectionMatrix;
    QMatrix4x4 depthProjectionViewMatrix;

    QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix;

    BarRenderItem *selectedBar(0);

    if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
        // Render scene into a depth texture for using with shadow mapping
        // Enable drawing to depth framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer);
        glClear(GL_DEPTH_BUFFER_BIT);

        // Bind depth shader
        m_depthShader->bind();

        // Set viewport for depth map rendering. Must match texture size. Larger values give smoother shadows.
        // Depth viewport must always start from 0, 0, as it is rendered into a texture, not screen
        glViewport(0, 0,
                   m_primarySubViewport.width() * m_shadowQualityMultiplier,
                   m_primarySubViewport.height() * m_shadowQualityMultiplier);

        // 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, 3.5f / m_autoScaleAdjustment);
        depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector);

        // Set the depth projection matrix
        depthProjectionMatrix.perspective(10.0f, viewPortRatio, 3.0f, 100.0f);
        depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix;

        // Draw bars to depth buffer
        QVector3D shadowScaler(m_scaleX * m_seriesScaleX * 0.9f, 0.0f,
                               m_scaleZ * m_seriesScaleZ * 0.9f);
        for (auto baseCache: m_renderCacheList) {
            if (baseCache->isVisible()) {
                BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
                float seriesPos = m_seriesStart + m_seriesStep
                        * (cache->visualIndex() - (cache->visualIndex()
                                                   * m_cachedBarSeriesMargin.width())) + 0.5f;
                ObjectHelper *barObj = cache->object();
                QQuaternion seriesRotation(cache->meshRotation());
                const BarRenderItemArray &renderArray = cache->renderArray();
                for (int row = startRow; row != stopRow; row += stepRow) {
                    const BarRenderItemRow &renderRow = renderArray.at(row);
                    for (int bar = startBar; bar != stopBar; bar += stepBar) {
                        const BarRenderItem &item = renderRow.at(bar);
                        if (!item.value())
                            continue;
                        GLfloat shadowOffset = 0.0f;
                        // Set front face culling for negative valued bars and back face culling
                        // for positive valued bars to remove peter-panning issues
                        if (item.height() > 0) {
                            glCullFace(GL_BACK);
                            if (m_yFlipped)
                                shadowOffset = 0.015f;
                        } else {
                            glCullFace(GL_FRONT);
                            if (!m_yFlipped)
                                shadowOffset = -0.015f;
                        }

                        if (m_cachedTheme->isBackgroundEnabled() && m_reflectionEnabled
                                && ((m_yFlipped && item.height() > 0.0)
                                    || (!m_yFlipped && item.height() < 0.0))) {
                            continue;
                        }

                        QMatrix4x4 modelMatrix;
                        QMatrix4x4 MVPMatrix;

                        colPos = (bar + seriesPos) * (m_cachedBarSpacing.width());
                        rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());

                        // Draw shadows for bars "on the other side" a bit off ground to avoid
                        // seeing shadows through the ground
                        modelMatrix.translate((colPos - m_rowWidth) / m_scaleFactor,
                                              item.height() + shadowOffset,
                                              (m_columnDepth - rowPos) / m_scaleFactor);
                        // Scale the bars down in X and Z to reduce self-shadowing issues
                        shadowScaler.setY(item.height());
                        if (!seriesRotation.isIdentity() || !item.rotation().isIdentity())
                            modelMatrix.rotate(seriesRotation * item.rotation());
                        modelMatrix.scale(shadowScaler);

                        MVPMatrix = depthProjectionViewMatrix * modelMatrix;

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

                        // 1st attribute buffer : vertices
                        glEnableVertexAttribArray(m_depthShader->posAtt());
                        glBindBuffer(GL_ARRAY_BUFFER, barObj->vertexBuf());
                        glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0,
                                              (void *)0);

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

                        // Draw the triangles
                        glDrawElements(GL_TRIANGLES, barObj->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 depth 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());
    }

    // Do position mapping when necessary
    if (m_graphPositionQueryPending) {
        QVector3D graphDimensions(m_xScaleFactor, 0.0f, m_zScaleFactor);
        queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle);

        // Y is always at floor level
        m_queriedGraphPosition.setY(0.0f);
        emit needRender();
    }

    // Skip selection mode drawing if we're slicing or have no selection mode
    if (!m_cachedIsSlicingActivated && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone
            && m_selectionState == SelectOnScene
            && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty())
            && m_selectionTexture) {
        // Bind selection shader
        m_selectionShader->bind();

        // Draw bars 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 (= selectionSkipColor)
        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
        for (auto baseCache: m_renderCacheList) {
            if (baseCache->isVisible()) {
                BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
                float seriesPos = m_seriesStart + m_seriesStep
                        * (cache->visualIndex() - (cache->visualIndex()
                                                   * m_cachedBarSeriesMargin.width())) + 0.5f;
                ObjectHelper *barObj = cache->object();
                QQuaternion seriesRotation(cache->meshRotation());
                const BarRenderItemArray &renderArray = cache->renderArray();
                for (int row = startRow; row != stopRow; row += stepRow) {
                    const BarRenderItemRow &renderRow = renderArray.at(row);
                    for (int bar = startBar; bar != stopBar; bar += stepBar) {
                        const BarRenderItem &item = renderRow.at(bar);
                        if (!item.value())
                            continue;

                        if (item.height() < 0)
                            glCullFace(GL_FRONT);
                        else
                            glCullFace(GL_BACK);

                        QMatrix4x4 modelMatrix;
                        QMatrix4x4 MVPMatrix;

                        colPos = (bar + seriesPos) * (m_cachedBarSpacing.width());
                        rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());

                        modelMatrix.translate((colPos - m_rowWidth) / m_scaleFactor,
                                              item.height(),
                                              (m_columnDepth - rowPos) / m_scaleFactor);
                        if (!seriesRotation.isIdentity() || !item.rotation().isIdentity())
                            modelMatrix.rotate(seriesRotation * item.rotation());
                        modelMatrix.scale(QVector3D(m_scaleX * m_seriesScaleX,
                                                    item.height(),
                                                    m_scaleZ * m_seriesScaleZ));

                        MVPMatrix = projectionViewMatrix * modelMatrix;

                        QVector4D barColor = QVector4D(GLfloat(row) / 255.0f,
                                                       GLfloat(bar) / 255.0f,
                                                       GLfloat(cache->visualIndex()) / 255.0f,
                                                       itemAlpha);

                        m_selectionShader->setUniformValue(m_selectionShader->MVP(), MVPMatrix);
                        m_selectionShader->setUniformValue(m_selectionShader->color(), barColor);

                        m_drawer->drawSelectionObject(m_selectionShader, barObj);
                    }
                }
            }
        }
        glCullFace(GL_BACK);
        Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader,
                                            viewMatrix,
                                            projectionViewMatrix, depthProjectionViewMatrix,
                                            m_depthTexture, m_shadowQualityToShader);
        drawLabels(true, activeCamera, viewMatrix, projectionMatrix);
        drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix,
                       viewMatrix, false, true);
        glEnable(GL_DITHER);

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

        emit needRender();

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

    if (m_reflectionEnabled) {
        //
        // Draw reflections
        //
        glDisable(GL_DEPTH_TEST);
        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
        glEnable(GL_STENCIL_TEST);
        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
        glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

        // Draw background stencil
        drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix,
                       viewMatrix);

        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        glEnable(GL_DEPTH_TEST);

        glStencilFunc(GL_EQUAL, 1, 0xffffffff);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

        // Set light
        QVector3D reflectionLightPos = lightPos;
        reflectionLightPos.setY(-(lightPos.y()));
        m_cachedScene->activeLight()->setPosition(reflectionLightPos);

        // Draw bar reflections
        (void)drawBars(&selectedBar, depthProjectionViewMatrix,
                       projectionViewMatrix, viewMatrix,
                       startRow, stopRow, stepRow,
                       startBar, stopBar, stepBar, -1.0f);

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

        // Reset light
        m_cachedScene->activeLight()->setPosition(lightPos);

        glDisable(GL_STENCIL_TEST);

        glCullFace(GL_BACK);
    }

    //
    // Draw the real scene
    //
    // Draw background
    if (m_reflectionEnabled) {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix,
                       viewMatrix, true);
        glDisable(GL_BLEND);
    } else {
        drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix,
                       viewMatrix);
    }

    // Draw bars
    bool barSelectionFound = drawBars(&selectedBar, depthProjectionViewMatrix,
                                      projectionViewMatrix, viewMatrix,
                                      startRow, stopRow, stepRow,
                                      startBar, stopBar, stepBar);

    // Draw grid lines
    drawGridLines(depthProjectionViewMatrix, projectionViewMatrix, viewMatrix);

    // Draw custom items
    Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix,
                                        projectionViewMatrix, depthProjectionViewMatrix,
                                        m_depthTexture, m_shadowQualityToShader);

    // Draw labels
    drawLabels(false, activeCamera, viewMatrix, projectionMatrix);

    // Handle selected bar label generation
    if (barSelectionFound) {
        // Print value of selected bar
        glDisable(GL_DEPTH_TEST);
        // Draw the selection label
        LabelItem &labelItem = selectionLabelItem();
        if (m_selectedBar != selectedBar || 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_selectedBar = selectedBar;
        }

        Drawer::LabelPosition position =
                m_selectedBar->height() >= 0 ? Drawer::LabelOver : Drawer::LabelBelow;

        m_drawer->drawLabel(*selectedBar, labelItem, viewMatrix, projectionMatrix,
                            zeroVector, identityQuaternion, selectedBar->height(),
                            m_cachedSelectionMode, m_labelShader,
                            m_labelObj, activeCamera, true, false, position);

        // Reset label update flag; they should have been updated when we get here
        m_updateLabels = false;

        glEnable(GL_DEPTH_TEST);
    } else {
        m_selectedBar = 0;
    }

    glDisable(GL_BLEND);

    // Release shader
    glUseProgram(0);
    m_selectionDirty = false;
}

bool Bars3DRenderer::drawBars(BarRenderItem **selectedBar,
                              const QMatrix4x4 &depthProjectionViewMatrix,
                              const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix,
                              GLint startRow, GLint stopRow, GLint stepRow,
                              GLint startBar, GLint stopBar, GLint stepBar, GLfloat reflection)
{
    QVector3D lightPos =  m_cachedScene->activeLight()->position();
    QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor());

    bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow);

    ShaderHelper *barShader = 0;
    GLuint gradientTexture = 0;
    Q3DTheme::ColorStyle previousColorStyle = Q3DTheme::ColorStyleUniform;

    // Set unchanging shader bindings
    if (m_haveGradientSeries) {
        m_barGradientShader->bind();
        m_barGradientShader->setUniformValue(m_barGradientShader->lightP(), lightPos);
        m_barGradientShader->setUniformValue(m_barGradientShader->view(), viewMatrix);
        m_barGradientShader->setUniformValue(m_barGradientShader->ambientS(),
                                             m_cachedTheme->ambientLightStrength());
        m_barGradientShader->setUniformValue(m_barGradientShader->gradientMin(), 0.0f);
        m_barGradientShader->setUniformValue(m_barGradientShader->lightColor(), lightColor);
    }

    if (m_haveUniformColorSeries) {
        m_barShader->bind();
        m_barShader->setUniformValue(m_barShader->lightP(), lightPos);
        m_barShader->setUniformValue(m_barShader->view(), viewMatrix);
        m_barShader->setUniformValue(m_barShader->ambientS(),
                                     m_cachedTheme->ambientLightStrength());
        m_barShader->setUniformValue(m_barShader->lightColor(), lightColor);
        barShader = m_barShader;
    } else {
        barShader = m_barGradientShader;
        previousColorStyle = Q3DTheme::ColorStyleRangeGradient;
    }

    int sliceReserveAmount = 0;
    if (m_selectionDirty && m_cachedIsSlicingActivated) {
        // Slice doesn't own its items, no need to delete them - just clear
        if (rowMode)
            sliceReserveAmount = m_cachedColumnCount;
        else
            sliceReserveAmount = m_cachedRowCount;

        // Set slice cache, i.e. axis cache from where slice labels are taken
        if (rowMode)
            m_sliceCache = &m_axisCacheX;
        else
            m_sliceCache = &m_axisCacheZ;
        m_sliceTitleItem = 0;
    }

    glEnable(GL_POLYGON_OFFSET_FILL);
    glPolygonOffset(0.5f, 1.0f);

    GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f;
    GLfloat adjustedHighlightStrength = m_cachedTheme->highlightLightStrength() / 10.0f;

    bool barSelectionFound = false;

    QVector4D baseColor;
    QVector4D barColor;
    QVector3D modelScaler(m_scaleX * m_seriesScaleX, 0.0f, m_scaleZ * m_seriesScaleZ);
    bool somethingSelected =
            (m_visualSelectedBarPos != Bars3DController::invalidSelectionPosition());
    for (auto baseCache: m_renderCacheList) {
        if (baseCache->isVisible()) {
            BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
            float seriesPos = m_seriesStart + m_seriesStep
                    * (cache->visualIndex() - (cache->visualIndex()
                                               * m_cachedBarSeriesMargin.width())) + 0.5f;
            ObjectHelper *barObj = cache->object();
            QQuaternion seriesRotation(cache->meshRotation());
            Q3DTheme::ColorStyle colorStyle = cache->colorStyle();
            BarRenderItemArray &renderArray = cache->renderArray();
            bool colorStyleIsUniform = (colorStyle == Q3DTheme::ColorStyleUniform);
            if (sliceReserveAmount)
                cache->sliceArray().resize(sliceReserveAmount);

            // Rebind shader if it has changed
            if (colorStyleIsUniform != (previousColorStyle == Q3DTheme::ColorStyleUniform)) {
                if (colorStyleIsUniform)
                    barShader = m_barShader;
                else
                    barShader = m_barGradientShader;
                barShader->bind();
            }

            if (colorStyleIsUniform) {
                baseColor = cache->baseColor();
            } else if ((previousColorStyle != colorStyle)
                       && (colorStyle == Q3DTheme::ColorStyleObjectGradient)) {
                m_barGradientShader->setUniformValue(m_barGradientShader->gradientHeight(), 0.5f);
            }

            // Always use base color when no selection mode
            if (m_cachedSelectionMode == QAbstract3DGraph::SelectionNone) {
                if (colorStyleIsUniform)
                    barColor = baseColor;
                else
                    gradientTexture = cache->baseGradientTexture();
            }

            previousColorStyle = colorStyle;
            for (int row = startRow; row != stopRow; row += stepRow) {
                BarRenderItemRow &renderRow = renderArray[row];
                for (int bar = startBar; bar != stopBar; bar += stepBar) {
                    BarRenderItem &item = renderRow[bar];
                    float adjustedHeight = reflection * item.height();
                    if (adjustedHeight < 0)
                        glCullFace(GL_FRONT);
                    else
                        glCullFace(GL_BACK);

                    QMatrix4x4 modelMatrix;
                    QMatrix4x4 itModelMatrix;
                    QMatrix4x4 MVPMatrix;

                    GLfloat colPos = (bar + seriesPos) * (m_cachedBarSpacing.width());
                    GLfloat rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());

                    modelMatrix.translate((colPos - m_rowWidth) / m_scaleFactor,
                                          adjustedHeight,
                                          (m_columnDepth - rowPos) / m_scaleFactor);
                    modelScaler.setY(adjustedHeight);
                    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
                    GLfloat lightStrength = m_cachedTheme->lightStrength();
                    GLfloat shadowLightStrength = adjustedLightStrength;

                    if (m_cachedSelectionMode > QAbstract3DGraph::SelectionNone) {
                        Bars3DController::SelectionType selectionType =
                                Bars3DController::SelectionNone;
                        if (somethingSelected)
                            selectionType = isSelected(row, bar, cache);

                        switch (selectionType) {
                        case Bars3DController::SelectionItem: {
                            if (colorStyleIsUniform)
                                barColor = cache->singleHighlightColor();
                            else
                                gradientTexture = cache->singleHighlightGradientTexture();

                            lightStrength = m_cachedTheme->highlightLightStrength();
                            shadowLightStrength = adjustedHighlightStrength;
                            // Insert position data into render item
                            // We have no ownership, don't delete the previous one
                            if (!m_cachedIsSlicingActivated
                                    && m_selectedSeriesCache == cache) {
                                *selectedBar = &item;
                                (*selectedBar)->setPosition(QPoint(row, bar));
                                item.setTranslation(modelMatrix.column(3).toVector3D());
                                barSelectionFound = true;
                            }
                            if (m_selectionDirty && m_cachedIsSlicingActivated) {
                                QVector3D translation = modelMatrix.column(3).toVector3D();
                                if (m_cachedSelectionMode & QAbstract3DGraph::SelectionColumn
                                        && m_visibleSeriesCount > 1) {
                                    translation.setZ((m_columnDepth
                                                      - ((row + seriesPos)
                                                         * (m_cachedBarSpacing.height())))
                                                     / m_scaleFactor);
                                }
                                item.setTranslation(translation);
                                item.setPosition(QPoint(row, bar));
                                if (rowMode)
                                    cache->sliceArray()[bar].setItem(item);
                                else
                                    cache->sliceArray()[row].setItem(item);
                            }
                            break;
                        }
                        case Bars3DController::SelectionRow: {
                            // Current bar is on the same row as the selected bar
                            if (colorStyleIsUniform)
                                barColor = cache->multiHighlightColor();
                            else
                                gradientTexture = cache->multiHighlightGradientTexture();

                            lightStrength = m_cachedTheme->highlightLightStrength();
                            shadowLightStrength = adjustedHighlightStrength;
                            if (m_cachedIsSlicingActivated) {
                                item.setTranslation(modelMatrix.column(3).toVector3D());
                                item.setPosition(QPoint(row, bar));
                                if (m_selectionDirty) {
                                    if (!m_sliceTitleItem && m_axisCacheZ.labelItems().size() > row)
                                        m_sliceTitleItem = m_axisCacheZ.labelItems().at(row);
                                    cache->sliceArray()[bar].setItem(item);
                                }
                            }
                            break;
                        }
                        case Bars3DController::SelectionColumn: {
                            // Current bar is on the same column as the selected bar
                            if (colorStyleIsUniform)
                                barColor = cache->multiHighlightColor();
                            else
                                gradientTexture = cache->multiHighlightGradientTexture();

                            lightStrength = m_cachedTheme->highlightLightStrength();
                            shadowLightStrength = adjustedHighlightStrength;
                            if (m_cachedIsSlicingActivated) {
                                QVector3D translation = modelMatrix.column(3).toVector3D();
                                if (m_visibleSeriesCount > 1) {
                                    translation.setZ((m_columnDepth
                                                      - ((row + seriesPos)
                                                         * (m_cachedBarSpacing.height())))
                                                     / m_scaleFactor);
                                }
                                item.setTranslation(translation);
                                item.setPosition(QPoint(row, bar));
                                if (m_selectionDirty) {
                                    if (!m_sliceTitleItem && m_axisCacheX.labelItems().size() > bar)
                                        m_sliceTitleItem = m_axisCacheX.labelItems().at(bar);
                                    cache->sliceArray()[row].setItem(item);
                                }
                            }
                            break;
                        }
                        case Bars3DController::SelectionNone: {
                            // Current bar is not selected, nor on a row or column
                            if (colorStyleIsUniform) {
                                QList<QColor> rowColors = cache->series()->rowColors();
                                if (rowColors.size() == 0) {
                                    barColor = baseColor;
                                } else {
                                    int rowColorIndex = row % rowColors.size();
                                    barColor =  Utils::vectorFromColor(rowColors[rowColorIndex]);
                                }
                            } else {
                                gradientTexture = cache->baseGradientTexture();
                            }
                            break;
                        }
                        }
                    }

                    if (item.height() == 0) {
                        continue;
                    } else if ((m_reflectionEnabled
                                && (reflection == 1.0f
                                    || (reflection != 1.0f
                                        && ((m_yFlipped && item.height() < 0.0)
                                            || (!m_yFlipped && item.height() > 0.0)))))
                               || !m_reflectionEnabled) {
                        // Skip drawing of 0-height bars and reflections of bars on the "wrong side"
                        // Set shader bindings
                        barShader->setUniformValue(barShader->model(), modelMatrix);
                        barShader->setUniformValue(barShader->nModel(),
                                                   itModelMatrix.transposed().inverted());
                        barShader->setUniformValue(barShader->MVP(), MVPMatrix);
                        if (colorStyleIsUniform) {
                            barShader->setUniformValue(barShader->color(), barColor);
                        } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) {
                            barShader->setUniformValue(barShader->gradientHeight(),
                                                       qAbs(item.height()) / m_gradientFraction);
                        }

                        if (((m_reflectionEnabled && reflection == 1.0f
                             && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone)
                                || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone)
                             && !m_isOpenGLES) {
                            // Set shadow shader bindings
                            QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                            barShader->setUniformValue(barShader->shadowQ(),
                                                       m_shadowQualityToShader);
                            barShader->setUniformValue(barShader->depth(), depthMVPMatrix);
                            barShader->setUniformValue(barShader->lightS(), shadowLightStrength);
                            barShader->setUniformValue(barShader->lightColor(), lightColor);

                            // Draw the object
                            m_drawer->drawObject(barShader, barObj, gradientTexture,
                                                 m_depthTexture);
                        } else {
                            // Set shadowless shader bindings
                            if (m_reflectionEnabled && reflection != 1.0f
                                    && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
                                barShader->setUniformValue(barShader->lightS(),
                                                           adjustedLightStrength);
                            } else {
                                barShader->setUniformValue(barShader->lightS(), lightStrength);
                            }

                            // Draw the object
                            m_drawer->drawObject(barShader, barObj, gradientTexture);
                        }
                    }
                }
            }
        }
    }
    glDisable(GL_POLYGON_OFFSET_FILL);

    // Reset culling
    glCullFace(GL_BACK);

    return barSelectionFound;
}

void Bars3DRenderer::drawBackground(GLfloat backgroundRotation,
                                    const QMatrix4x4 &depthProjectionViewMatrix,
                                    const QMatrix4x4 &projectionViewMatrix,
                                    const QMatrix4x4 &viewMatrix, bool reflectingDraw,
                                    bool drawingSelectionBuffer)
{
    // Draw background
    if (m_cachedTheme->isBackgroundEnabled() && m_backgroundObj) {
        QVector3D lightPos =  m_cachedScene->activeLight()->position();
        QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor());
        GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f;
        ShaderHelper *shader = 0;

        // Bind background shader
        if (drawingSelectionBuffer)
            shader = m_selectionShader; // Use single color shader when drawing to selection buffer
        else
            shader = m_backgroundShader;
        shader->bind();

        QMatrix4x4 modelMatrix;
        QMatrix4x4 MVPMatrix;
        QMatrix4x4 itModelMatrix;

        QVector3D backgroundScaler(m_scaleXWithBackground, m_scaleYWithBackground,
                                   m_scaleZWithBackground);
        QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor());
        if (m_reflectionEnabled)
            backgroundColor.setW(backgroundColor.w() * m_reflectivity);

        // Set shader bindings
        shader->setUniformValue(shader->lightP(), lightPos);
        shader->setUniformValue(shader->view(), viewMatrix);
        if (drawingSelectionBuffer) {
            // Use selectionSkipColor for background when drawing to selection buffer
            shader->setUniformValue(shader->color(), selectionSkipColor);
        } else {
            shader->setUniformValue(shader->color(), backgroundColor);
        }
        shader->setUniformValue(shader->ambientS(),
                                m_cachedTheme->ambientLightStrength() * 2.0f);
        shader->setUniformValue(shader->lightColor(), lightColor);

        // Draw floor
        modelMatrix.scale(backgroundScaler);

        if (m_yFlipped)
            modelMatrix.rotate(m_xRightAngleRotation);
        else
            modelMatrix.rotate(m_xRightAngleRotationNeg);

        itModelMatrix = modelMatrix;

#ifdef SHOW_DEPTH_TEXTURE_SCENE
        MVPMatrix = depthProjectionViewMatrix * modelMatrix;
#else
        MVPMatrix = projectionViewMatrix * modelMatrix;
#endif
        // Set changed shader bindings
        shader->setUniformValue(shader->model(), modelMatrix);
        shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed());
        shader->setUniformValue(shader->MVP(), MVPMatrix);

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

        // Draw walls
        modelMatrix = QMatrix4x4();
        itModelMatrix = QMatrix4x4();
        modelMatrix.translate(0.0f, m_backgroundAdjustment, 0.0f);

        modelMatrix.scale(backgroundScaler);
        itModelMatrix.scale(backgroundScaler);
        modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f);
        itModelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f);

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

        // Set changed shader bindings
        shader->setUniformValue(shader->model(), modelMatrix);
        shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed());
        shader->setUniformValue(shader->MVP(), MVPMatrix);
        if (!m_reflectionEnabled || (m_reflectionEnabled && reflectingDraw)) {
            if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
                // Set shadow shader bindings
                QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
                shader->setUniformValue(shader->shadowQ(), m_shadowQualityToShader);
                shader->setUniformValue(shader->depth(), depthMVPMatrix);
                shader->setUniformValue(shader->lightS(), adjustedLightStrength);

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

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

void Bars3DRenderer::drawGridLines(const QMatrix4x4 &depthProjectionViewMatrix,
                                   const QMatrix4x4 &projectionViewMatrix,
                                   const QMatrix4x4 &viewMatrix)
{
    if (m_cachedTheme->isGridEnabled()) {
        ShaderHelper *lineShader;
        if (m_isOpenGLES)
            lineShader = m_selectionShader; // Plain color shader for GL_LINES
        else
            lineShader = m_backgroundShader;

        QQuaternion lineRotation;

        QVector3D lightPos =  m_cachedScene->activeLight()->position();
        QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor());

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

        // Set unchanging shader bindings
        QVector4D barColor = Utils::vectorFromColor(m_cachedTheme->gridLineColor());
        lineShader->setUniformValue(lineShader->lightP(), lightPos);
        lineShader->setUniformValue(lineShader->view(), viewMatrix);
        lineShader->setUniformValue(lineShader->color(), barColor);
        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);
        }

        GLfloat yFloorLinePosition = gridLineOffset;
        if (m_yFlipped)
            yFloorLinePosition = -yFloorLinePosition;

        QVector3D gridLineScaler(m_scaleXWithBackground, gridLineWidth, gridLineWidth);

        if (m_yFlipped)
            lineRotation = m_xRightAngleRotation;
        else
            lineRotation = m_xRightAngleRotationNeg;

        // Floor lines: rows
        for (GLfloat row = 0.0f; row <= m_cachedRowCount; row++) {
            QMatrix4x4 modelMatrix;
            QMatrix4x4 MVPMatrix;
            QMatrix4x4 itModelMatrix;

            GLfloat rowPos = row * m_cachedBarSpacing.height();
            modelMatrix.translate(0.0f, yFloorLinePosition,
                                  (m_columnDepth - rowPos) / m_scaleFactor);
            modelMatrix.scale(gridLineScaler);
            itModelMatrix.scale(gridLineScaler);
            modelMatrix.rotate(lineRotation);
            itModelMatrix.rotate(lineRotation);

            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) {
                    // 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);
                }
            }
        }

        // Floor lines: columns
        if (m_isOpenGLES)
            lineRotation = m_yRightAngleRotation;
        gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, m_scaleZWithBackground);
        for (GLfloat bar = 0.0f; bar <= m_cachedColumnCount; bar++) {
            QMatrix4x4 modelMatrix;
            QMatrix4x4 MVPMatrix;
            QMatrix4x4 itModelMatrix;

            GLfloat colPos = bar * m_cachedBarSpacing.width();
            modelMatrix.translate((m_rowWidth - colPos) / m_scaleFactor,
                                  yFloorLinePosition, 0.0f);
            modelMatrix.scale(gridLineScaler);
            itModelMatrix.scale(gridLineScaler);
            modelMatrix.rotate(lineRotation);
            itModelMatrix.rotate(lineRotation);

            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) {
                    // 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);
                }
            }
        }

        if (m_axisCacheY.segmentCount() > 0) {
            // Wall lines: back wall
            int gridLineCount = m_axisCacheY.gridLineCount();

            GLfloat zWallLinePosition = -m_scaleZWithBackground + gridLineOffset;
            if (m_zFlipped)
                zWallLinePosition = -zWallLinePosition;

            gridLineScaler = QVector3D(m_scaleXWithBackground, gridLineWidth, gridLineWidth);
            for (int line = 0; line < gridLineCount; line++) {
                QMatrix4x4 modelMatrix;
                QMatrix4x4 MVPMatrix;
                QMatrix4x4 itModelMatrix;

                modelMatrix.translate(0.0f,
                                      m_axisCacheY.gridLinePosition(line),
                                      zWallLinePosition);
                modelMatrix.scale(gridLineScaler);
                itModelMatrix.scale(gridLineScaler);
                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) {
                    m_drawer->drawLine(lineShader);
                } else {
                    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);
                    }
                }
            }

            // Wall lines: side wall
            GLfloat xWallLinePosition = -m_scaleXWithBackground + gridLineOffset;
            if (m_xFlipped)
                xWallLinePosition = -xWallLinePosition;

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

            gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, m_scaleZWithBackground);
            for (int line = 0; line < gridLineCount; line++) {
                QMatrix4x4 modelMatrix;
                QMatrix4x4 MVPMatrix;
                QMatrix4x4 itModelMatrix;

                modelMatrix.translate(xWallLinePosition,
                                      m_axisCacheY.gridLinePosition(line),
                                      0.0f);
                modelMatrix.scale(gridLineScaler);
                itModelMatrix.scale(gridLineScaler);
                modelMatrix.rotate(lineRotation);
                itModelMatrix.rotate(lineRotation);

                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) {
                        // 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);
                    }
                }
            }
        }
    }
}

void Bars3DRenderer::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);

    // If camera x rotation is 180, side labels face wrong direction
    float activeCameraXRotation = (activeCamera->xRotation() >= 180.0f) ? -180.0f
                                                                        : activeCamera->xRotation();

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

    int startIndex;
    int endIndex;
    int indexStep;

    // Y Labels
    int labelCount = m_axisCacheY.labelCount();
    GLfloat labelMarginXTrans = labelMargin;
    GLfloat labelMarginZTrans = labelMargin;
    GLfloat labelXTrans = m_scaleXWithBackground;
    GLfloat labelZTrans = m_scaleZWithBackground;
    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 backLabelTrans = QVector3D(labelXTrans, 0.0f,
                                         labelZTrans + labelMarginZTrans);
    QVector3D sideLabelTrans = QVector3D(-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 i = startIndex; i != endIndex; i = i + indexStep) {
        backLabelTrans.setY(m_axisCacheY.labelPosition(i));
        sideLabelTrans.setY(backLabelTrans.y());

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

        const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(i);

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

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

        // Side wall
        m_dummyBarRenderItem.setTranslation(sideLabelTrans);
        m_drawer->drawLabel(m_dummyBarRenderItem, 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()) {
        sideLabelTrans.setY(m_backgroundAdjustment);
        backLabelTrans.setY(m_backgroundAdjustment);
        drawAxisTitleY(sideLabelRotation, backLabelRotation, sideLabelTrans, backLabelTrans,
                       totalSideRotation, totalBackRotation, m_dummyBarRenderItem, activeCamera,
                       labelsMaxWidth, viewMatrix, projectionMatrix, shader);
    }

    // Z labels
    // Calculate the positions for row and column labels and store them
    labelsMaxWidth = 0.0f;
    labelAutoAngle = m_axisCacheZ.labelAutoRotation();
    labelAngleFraction = labelAutoAngle / 90.0f;
    fractionCamY = activeCamera->yRotation() * labelAngleFraction;
    fractionCamX = activeCameraXRotation * labelAngleFraction;
    GLfloat labelYAdjustment = 0.005f;
    GLfloat colPosValue = m_scaleXWithBackground + labelMargin;
    GLfloat rowPosValue = m_scaleZWithBackground + labelMargin;
    GLfloat rowPos = 0.0f;
    GLfloat colPos = 0.0f;
    Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
    QVector3D labelRotation;

    if (labelAutoAngle == 0.0f) {
        if (m_zFlipped)
            labelRotation.setY(180.0f);
        if (m_yFlipped) {
            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_yFlipped) {
            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);
    labelCount = qMin(m_axisCacheZ.labelCount(), m_cachedRowCount);
    if (m_zFlipped) {
        startIndex = 0;
        endIndex = labelCount;
        indexStep = 1;
    } else {
        startIndex = labelCount - 1;
        endIndex = -1;
        indexStep = -1;
    }
    offsetValue = 0.0f;
    for (int row = startIndex; row != endIndex; row = row + indexStep) {
        // Go through all rows and get position of max+1 or min-1 column, depending on x flip
        // We need only positions for them, labels have already been generated
        rowPos = (row + 0.5f) * m_cachedBarSpacing.height();
        if (m_xFlipped)
            colPos = -colPosValue;
        else
            colPos = colPosValue;

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

        QVector3D labelPos = QVector3D(colPos,
                                       labelYAdjustment, // raise a bit over background to avoid depth "glimmering"
                                       (m_columnDepth - rowPos) / m_scaleFactor);

        m_dummyBarRenderItem.setTranslation(labelPos);
        const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(row);

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

        m_drawer->drawLabel(m_dummyBarRenderItem, 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()) {
        QVector3D titleTrans(colPos, 0.0f, 0.0f);
        drawAxisTitleZ(labelRotation, titleTrans, totalRotation, m_dummyBarRenderItem,
                       activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader);
    }

    // X labels
    labelsMaxWidth = 0.0f;
    labelAutoAngle = m_axisCacheX.labelAutoRotation();
    labelAngleFraction = labelAutoAngle / 90.0f;
    fractionCamY = activeCamera->yRotation() * labelAngleFraction;
    fractionCamX = activeCameraXRotation * labelAngleFraction;
    alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
    if (labelAutoAngle == 0.0f) {
        labelRotation = QVector3D(-90.0f, 90.0f, 0.0f);
        if (m_xFlipped)
            labelRotation.setY(-90.0f);
        if (m_yFlipped) {
            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_yFlipped) {
            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);
                }
            }
        }
    }

    totalRotation = Utils::calculateRotation(labelRotation);
    labelCount = qMin(m_axisCacheX.labelCount(), m_cachedColumnCount);
    if (m_xFlipped) {
        startIndex = labelCount - 1;
        endIndex = -1;
        indexStep = -1;
    } else {
        startIndex = 0;
        endIndex = labelCount;
        indexStep = 1;
    }
    offsetValue = 0.0f;
    for (int column = startIndex; column != endIndex; column = column + indexStep) {
        // Go through all columns and get position of max+1 or min-1 row, depending on z flip
        // We need only positions for them, labels have already been generated
        colPos = (column + 0.5f) * m_cachedBarSpacing.width();
        if (m_zFlipped)
            rowPos = -rowPosValue;
        else
            rowPos = rowPosValue;

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

        QVector3D labelPos = QVector3D((colPos - m_rowWidth) / m_scaleFactor,
                                       labelYAdjustment, // raise a bit over background to avoid depth "glimmering"
                                       rowPos);

        m_dummyBarRenderItem.setTranslation(labelPos);
        const LabelItem &axisLabelItem = *m_axisCacheX.labelItems().at(column);

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

        m_drawer->drawLabel(m_dummyBarRenderItem, 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()) {
        QVector3D titleTrans(0.0f, 0.0f, rowPos);
        drawAxisTitleX(labelRotation, titleTrans, totalRotation, m_dummyBarRenderItem,
                       activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader);
    }

#if 0 // Debug label
    static LabelItem debugLabelItem;
    QString debugLabelString(QStringLiteral("Flips: x:%1 y:%2 z:%3 xr:%4 yr:%5"));
    QString finalDebugString = debugLabelString.arg(m_xFlipped).arg(m_yFlipped).arg(m_zFlipped)
            .arg(activeCamera->xRotation()).arg(activeCamera->yRotation());
    m_dummyBarRenderItem.setTranslation(QVector3D(m_xFlipped ? -1.5f : 1.5f,
                                                  m_yFlipped ? 1.5f : -1.5f,
                                                  m_zFlipped ? -1.5f : 1.5f));

    m_drawer->generateLabelItem(debugLabelItem, finalDebugString);
    m_drawer->drawLabel(m_dummyBarRenderItem, debugLabelItem, viewMatrix, projectionMatrix,
                        zeroVector, identityQuaternion, 0, m_cachedSelectionMode,
                        shader, m_labelObj, activeCamera,
                        true, false, Drawer::LabelMid, Qt::AlignHCenter, false, drawSelection);
#endif
    glDisable(GL_POLYGON_OFFSET_FILL);
}

void Bars3DRenderer::updateMultiSeriesScaling(bool uniform)
{
    m_keepSeriesUniform = uniform;

    // Recalculate scale factors
    m_seriesScaleX = 1.0f / float(m_visibleSeriesCount);
    if (m_keepSeriesUniform)
        m_seriesScaleZ = m_seriesScaleX;
    else
        m_seriesScaleZ = 1.0f;
}

void Bars3DRenderer::updateBarSpecs(GLfloat thicknessRatio, const QSizeF &spacing, bool relative)
{
    // Convert ratio to QSizeF, as we need it in that format for autoscaling calculations
    m_cachedBarThickness.setWidth(1.0f);
    m_cachedBarThickness.setHeight(1.0f / thicknessRatio);

    if (relative) {
        m_cachedBarSpacing.setWidth((m_cachedBarThickness.width() * 2)
                                    * (spacing.width() + 1.0f));
        m_cachedBarSpacing.setHeight((m_cachedBarThickness.height() * 2)
                                     * (spacing.height() + 1.0f));
    } else {
        m_cachedBarSpacing = m_cachedBarThickness * 2 + spacing * 2;
    }

    // Slice mode doesn't update correctly without this
    if (m_cachedIsSlicingActivated)
        m_selectionDirty = true;

    // Calculate here and at setting sample space
    calculateSceneScalingFactors();
}

void Bars3DRenderer::updateBarSeriesMargin(const QSizeF &margin)
{
    m_cachedBarSeriesMargin = margin;
    calculateSeriesStartPosition();
    calculateSceneScalingFactors();
}

void Bars3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orientation, float min,
                                     float max)
{
    Abstract3DRenderer::updateAxisRange(orientation, min, max);

    if (orientation == QAbstract3DAxis::AxisOrientationY)
        calculateHeightAdjustment();
}

void Bars3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation orientation, bool enable)
{
    Abstract3DRenderer::updateAxisReversed(orientation, enable);
    if (orientation == QAbstract3DAxis::AxisOrientationY)
        calculateHeightAdjustment();
}


void Bars3DRenderer::updateSelectedBar(const QPoint &position, QBar3DSeries *series)
{
    m_selectedBarPos = position;
    m_selectedSeriesCache = static_cast<BarSeriesRenderCache *>(m_renderCacheList.value(series, 0));
    m_selectionDirty = true;
    m_selectionLabelDirty = true;

    if (!m_selectedSeriesCache
            || !m_selectedSeriesCache->isVisible()
            || m_selectedSeriesCache->renderArray().isEmpty()) {
        m_visualSelectedBarPos = Bars3DController::invalidSelectionPosition();
        return;
    }

    int adjustedZ = m_selectedBarPos.x() - int(m_axisCacheZ.min());
    int adjustedX = m_selectedBarPos.y() - int(m_axisCacheX.min());
    int maxZ = m_selectedSeriesCache->renderArray().size() - 1;
    int maxX = maxZ >= 0 ? m_selectedSeriesCache->renderArray().at(0).size() - 1 : -1;

    if (m_selectedBarPos == Bars3DController::invalidSelectionPosition()
            || adjustedZ < 0 || adjustedZ > maxZ
            || adjustedX < 0 || adjustedX > maxX) {
        m_visualSelectedBarPos = Bars3DController::invalidSelectionPosition();
    } else {
        m_visualSelectedBarPos = QPoint(adjustedZ, adjustedX);
    }
}

void Bars3DRenderer::resetClickedStatus()
{
    m_clickedPosition = Bars3DController::invalidSelectionPosition();
    m_clickedSeries = 0;
}

void Bars3DRenderer::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 = 7.5f;
        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();

    // Redraw to handle both reflections and shadows on background
    if (m_reflectionEnabled)
        needRender();
}

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

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

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

void Bars3DRenderer::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh)
{
    if (!m_cachedTheme->isBackgroundEnabled()) {
        // Load full version of meshes that have it available
        // Note: Minimal, Point, and Arrow not supported in bar charts
        if (mesh != QAbstract3DSeries::MeshSphere)
            fileName.append(QStringLiteral("Full"));
    }
}

void Bars3DRenderer::calculateSceneScalingFactors()
{
    // Calculate scene scaling and translation factors
    m_rowWidth = (m_cachedColumnCount * m_cachedBarSpacing.width()) / 2.0f;
    m_columnDepth = (m_cachedRowCount * m_cachedBarSpacing.height()) / 2.0f;
    m_maxDimension = qMax(m_rowWidth, m_columnDepth);
    m_scaleFactor = qMin((m_cachedColumnCount * (m_maxDimension / m_maxSceneSize)),
                         (m_cachedRowCount * (m_maxDimension / m_maxSceneSize)));

    // Single bar scaling
    m_scaleX = m_cachedBarThickness.width() / m_scaleFactor;
    m_scaleZ = m_cachedBarThickness.height() / m_scaleFactor;

    // Adjust scaling according to margin
    m_scaleX -= m_scaleX * m_cachedBarSeriesMargin.width();
    m_scaleZ -= m_scaleZ * m_cachedBarSeriesMargin.height();

    // Whole graph scale factors
    m_xScaleFactor = m_rowWidth / m_scaleFactor;
    m_zScaleFactor = m_columnDepth / m_scaleFactor;

    if (m_requestedMargin < 0.0f) {
        m_hBackgroundMargin = 0.0f;
        m_vBackgroundMargin = 0.0f;
    } else {
        m_hBackgroundMargin = m_requestedMargin;
        m_vBackgroundMargin = m_requestedMargin;
    }

    m_scaleXWithBackground = m_xScaleFactor + m_hBackgroundMargin;
    m_scaleYWithBackground = 1.0f + m_vBackgroundMargin;
    m_scaleZWithBackground = m_zScaleFactor + m_hBackgroundMargin;

    updateCameraViewport();
    updateCustomItemPositions();
}

void Bars3DRenderer::calculateHeightAdjustment()
{
    float min = m_axisCacheY.min();
    float max = m_axisCacheY.max();
    GLfloat newAdjustment = 1.0f;
    m_actualFloorLevel = qBound(min, m_floorLevel, max);
    GLfloat maxAbs = qFabs(max - m_actualFloorLevel);

    // Check if we have negative values
    if (min < m_actualFloorLevel)
        m_hasNegativeValues = true;
    else if (min >= m_actualFloorLevel)
        m_hasNegativeValues = false;

    if (max < m_actualFloorLevel) {
        m_heightNormalizer = GLfloat(qFabs(min) - qFabs(max));
        maxAbs = qFabs(max) - qFabs(min);
    } else {
        m_heightNormalizer = GLfloat(max - min);
    }

    // Height fractions are used in gradient calculations and are therefore doubled
    // Note that if max or min is exactly zero, we still consider it outside the range
    if (max <= m_actualFloorLevel || min >= m_actualFloorLevel) {
        m_noZeroInRange = true;
        m_gradientFraction = 2.0f;
    } else {
        m_noZeroInRange = false;
        GLfloat minAbs = qFabs(min - m_actualFloorLevel);
        m_gradientFraction = qMax(minAbs, maxAbs) / m_heightNormalizer * 2.0f;
    }

    // Calculate translation adjustment for background floor
    newAdjustment = (qBound(0.0f, (maxAbs / m_heightNormalizer), 1.0f) - 0.5f) * 2.0f;
    if (m_axisCacheY.reversed())
        newAdjustment = -newAdjustment;

    if (newAdjustment != m_backgroundAdjustment) {
        m_backgroundAdjustment = newAdjustment;
        m_axisCacheY.setTranslate(m_backgroundAdjustment - 1.0f);
    }
}

void Bars3DRenderer::calculateSeriesStartPosition()
{
    m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) / 2.0f)
            * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));
}

Bars3DController::SelectionType Bars3DRenderer::isSelected(int row, int bar,
                                                           const BarSeriesRenderCache *cache)
{
    Bars3DController::SelectionType isSelectedType = Bars3DController::SelectionNone;

    if ((m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionMultiSeries)
         && m_selectedSeriesCache) || cache == m_selectedSeriesCache) {
        if (row == m_visualSelectedBarPos.x() && bar == m_visualSelectedBarPos.y()
                && (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionItem))) {
            isSelectedType = Bars3DController::SelectionItem;
        } else if (row == m_visualSelectedBarPos.x()
                   && (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow))) {
            isSelectedType = Bars3DController::SelectionRow;
        } else if (bar == m_visualSelectedBarPos.y()
                   && (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn))) {
            isSelectedType = Bars3DController::SelectionColumn;
        }
    }

    return isSelectedType;
}

QPoint Bars3DRenderer::selectionColorToArrayPosition(const QVector4D &selectionColor)
{
    QPoint position = Bars3DController::invalidSelectionPosition();
    m_clickedType = QAbstract3DGraph::ElementNone;
    m_selectedLabelIndex = -1;
    m_selectedCustomItemIndex = -1;
    if (selectionColor.w() == itemAlpha) {
        // Normal selection item
        position = QPoint(int(selectionColor.x() + int(m_axisCacheZ.min())),
                          int(selectionColor.y()) + int(m_axisCacheX.min()));
        // Pass item clicked info to input handler
        m_clickedType = QAbstract3DGraph::ElementSeries;
    } else if (selectionColor.w() == labelRowAlpha) {
        // Row selection
        if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow)) {
            // Use column from previous selection in case we have row + column mode
            GLint previousCol = qMax(0, m_selectedBarPos.y()); // Use 0 if previous is invalid
            position = QPoint(int(selectionColor.x() + int(m_axisCacheZ.min())), previousCol);
        }
        m_selectedLabelIndex = selectionColor.x();
        // Pass label clicked info to input handler
        m_clickedType = QAbstract3DGraph::ElementAxisZLabel;
    } else if (selectionColor.w() == labelColumnAlpha) {
        // Column selection
        if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) {
            // Use row from previous selection in case we have row + column mode
            GLint previousRow = qMax(0, m_selectedBarPos.x()); // Use 0 if previous is invalid
            position = QPoint(previousRow, int(selectionColor.y()) + int(m_axisCacheX.min()));
        }
        m_selectedLabelIndex = selectionColor.y();
        // Pass label clicked info to input handler
        m_clickedType = QAbstract3DGraph::ElementAxisXLabel;
    } else if (selectionColor.w() == labelValueAlpha) {
        // Value selection
        position = Bars3DController::invalidSelectionPosition();
        m_selectedLabelIndex = selectionColor.z();
        // Pass label clicked info to input handler
        m_clickedType = QAbstract3DGraph::ElementAxisYLabel;
    } else if (selectionColor.w() == customItemAlpha) {
        // Custom item selection
        position = Bars3DController::invalidSelectionPosition();
        m_selectedCustomItemIndex = int(selectionColor.x())
                + (int(selectionColor.y()) << 8)
                + (int(selectionColor.z()) << 16);
        m_clickedType = QAbstract3DGraph::ElementCustomItem;
    }
    return position;
}

QBar3DSeries *Bars3DRenderer::selectionColorToSeries(const QVector4D &selectionColor)
{
    if (selectionColor == selectionSkipColor) {
        return 0;
    } else {
        int seriesIndexFromColor(selectionColor.z());
        for (auto baseCache: m_renderCacheList) {
            BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache);
            if (cache->visualIndex() == seriesIndexFromColor)
                return cache->series();
        }
    }
    return 0;
}

void Bars3DRenderer::updateSlicingActive(bool isSlicing)
{
    if (isSlicing == m_cachedIsSlicingActivated)
        return;

    m_cachedIsSlicingActivated = isSlicing;

    if (!m_cachedIsSlicingActivated) {
        // We need to re-init selection buffer in case there has been a resize
        initSelectionBuffer();
        initCursorPositionBuffer();
    }

    updateDepthBuffer(); // Re-init depth buffer as well
    m_selectionDirty = true;
}

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

void Bars3DRenderer::initGradientShaders(const QString &vertexShader, const QString &fragmentShader)
{
    if (m_barGradientShader)
        delete m_barGradientShader;
    m_barGradientShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_barGradientShader->initialize();
}

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

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

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

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

void Bars3DRenderer::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 Bars3DRenderer::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 Bars3DRenderer::initBackgroundShaders(const QString &vertexShader,
                                           const QString &fragmentShader)
{
    if (m_backgroundShader)
        delete m_backgroundShader;
    m_backgroundShader = new ShaderHelper(this, vertexShader, fragmentShader);
    m_backgroundShader->initialize();
}

QVector3D Bars3DRenderer::convertPositionToTranslation(const QVector3D &position, bool isAbsolute)
{
    float xTrans = 0.0f;
    float yTrans = 0.0f;
    float zTrans = 0.0f;
    if (!isAbsolute) {
        // Convert row and column to translation on graph
        xTrans = (((position.x() - m_axisCacheX.min() + 0.5f) * m_cachedBarSpacing.width())
                  - m_rowWidth) / m_scaleFactor;
        zTrans = (m_columnDepth - ((position.z() - m_axisCacheZ.min() + 0.5f)
                                   * m_cachedBarSpacing.height())) / m_scaleFactor;
        yTrans = m_axisCacheY.positionAt(position.y());
    } else {
        xTrans = position.x() * m_xScaleFactor;
        yTrans = position.y() + m_backgroundAdjustment;
        zTrans = position.z() * -m_zScaleFactor;
    }
    return QVector3D(xTrans, yTrans, zTrans);
}

void Bars3DRenderer::updateAspectRatio(float ratio)
{
    Q_UNUSED(ratio);
}

void Bars3DRenderer::updateFloorLevel(float level)
{
    for (auto cache: m_renderCacheList)
        cache->setDataDirty(true);
    m_floorLevel = level;
    calculateHeightAdjustment();
}

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

QT_END_NAMESPACE
