Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
++
++ #include "surface3drenderer_p.h"
++ #include "q3dcamera_p.h"
++ #include "shaderhelper_p.h"
++ #include "texturehelper_p.h"
++ #include "utils_p.h"
++
++ #include <QtGui/qquaternion.h>
++
++ #include <QtCore/qmath.h>
++
++ static const int ID_TO_RGBA_MASK = 0xff;
++
++ QT_BEGIN_NAMESPACE
++
++ //#define SHOW_DEPTH_TEXTURE_SCENE
++
++ const GLfloat sliceZScale = 0.1f;
++ const GLfloat sliceUnits = 2.5f;
++ const uint greenMultiplier = 256;
++ const uint blueMultiplier = 65536;
++ const uint alphaMultiplier = 16777216;
++
++ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller)
++ : Abstract3DRenderer(controller),
++ m_cachedIsSlicingActivated(false),
++ m_depthShader(0),
++ m_backgroundShader(0),
++ m_surfaceFlatShader(0),
++ m_surfaceSmoothShader(0),
++ m_surfaceTexturedSmoothShader(0),
++ m_surfaceTexturedFlatShader(0),
++ m_surfaceGridShader(0),
++ m_surfaceSliceFlatShader(0),
++ m_surfaceSliceSmoothShader(0),
++ m_selectionShader(0),
++ m_heightNormalizer(0.0f),
++ m_scaleX(0.0f),
++ m_scaleY(0.0f),
++ m_scaleZ(0.0f),
++ m_depthFrameBuffer(0),
++ m_selectionFrameBuffer(0),
++ m_selectionDepthBuffer(0),
++ m_selectionResultTexture(0),
++ m_shadowQualityToShader(33.3f),
++ m_flatSupported(true),
++ m_selectionActive(false),
++ m_shadowQualityMultiplier(3),
++ m_selectedPoint(Surface3DController::invalidSelectionPosition()),
++ m_selectedSeries(0),
++ m_clickedPosition(Surface3DController::invalidSelectionPosition()),
++ m_selectionTexturesDirty(false),
++ m_noShadowTexture(0)
++ {
++ // Check if flat feature is supported
++ ShaderHelper tester(this, QStringLiteral(":/shaders/vertexSurfaceFlat"),
++ QStringLiteral(":/shaders/fragmentSurfaceFlat"));
++ if (!tester.testCompile()) {
++ m_flatSupported = false;
++ connect(this, &Surface3DRenderer::flatShadingSupportedChanged,
++ controller, &Surface3DController::handleFlatShadingSupportedChange);
++ emit flatShadingSupportedChanged(m_flatSupported);
++ qWarning() << "Warning: Flat qualifier not supported on your platform's GLSL language."
++ " Requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.";
++ }
++
++ initializeOpenGL();
++ }
++
++ Surface3DRenderer::~Surface3DRenderer()
++ {
++ contextCleanup();
++ delete m_depthShader;
++ delete m_backgroundShader;
++ delete m_selectionShader;
++ delete m_surfaceFlatShader;
++ delete m_surfaceSmoothShader;
++ delete m_surfaceTexturedSmoothShader;
++ delete m_surfaceTexturedFlatShader;
++ delete m_surfaceGridShader;
++ delete m_surfaceSliceFlatShader;
++ delete m_surfaceSliceSmoothShader;
++ }
++
++ void Surface3DRenderer::contextCleanup()
++ {
++ if (QOpenGLContext::currentContext()) {
++ m_textureHelper->glDeleteFramebuffers(1, &m_depthFrameBuffer);
++ m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer);
++ m_textureHelper->glDeleteFramebuffers(1, &m_selectionFrameBuffer);
++
++ m_textureHelper->deleteTexture(&m_noShadowTexture);
++ m_textureHelper->deleteTexture(&m_depthTexture);
++ m_textureHelper->deleteTexture(&m_selectionResultTexture);
++ }
++ }
++
++ void Surface3DRenderer::initializeOpenGL()
++ {
++ Abstract3DRenderer::initializeOpenGL();
++
++ // Initialize shaders
++ initSurfaceShaders();
++
++ if (!m_isOpenGLES) {
++ initDepthShader(); // For shadows
++ loadGridLineMesh();
++ }
++
++ // Init selection shader
++ initSelectionShaders();
++
++ // Resize in case we've missed resize events
++ // Resize calls initSelectionBuffer and initDepthBuffer, so they don't need to be called here
++ handleResize();
++
++ // Load background mesh (we need to be initialized first)
++ loadBackgroundMesh();
++
++ // Create texture for no shadows
++ QImage image(2, 2, QImage::Format_RGB32);
++ image.fill(Qt::white);
++ m_noShadowTexture = m_textureHelper->create2DTexture(image, false, true, false, true);
++ }
++
++ void Surface3DRenderer::fixCameraTarget(QVector3D &target)
++ {
++ target.setX(target.x() * m_scaleX);
++ target.setY(target.y() * m_scaleY);
++ target.setZ(target.z() * -m_scaleZ);
++ }
++
++ void Surface3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds)
++ {
++ // The inputs are the item bounds in OpenGL coordinates.
++ // The outputs limit these bounds to visible ranges, normalized to range [-1, 1]
++ // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those
++ float itemRangeX = (maxBounds.x() - minBounds.x());
++ float itemRangeY = (maxBounds.y() - minBounds.y());
++ float itemRangeZ = (maxBounds.z() - minBounds.z());
++
++ if (minBounds.x() < -m_scaleX)
++ minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_scaleX) / itemRangeX));
++ else
++ minBounds.setX(-1.0f);
++
++ if (minBounds.y() < -m_scaleY)
++ minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + m_scaleY) / itemRangeY)));
++ else
++ minBounds.setY(1.0f);
++
++ if (minBounds.z() < -m_scaleZ)
++ minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_scaleZ) / itemRangeZ)));
++ else
++ minBounds.setZ(1.0f);
++
++ if (maxBounds.x() > m_scaleX)
++ maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_scaleX) / itemRangeX));
++ else
++ maxBounds.setX(1.0f);
++
++ if (maxBounds.y() > m_scaleY)
++ maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - m_scaleY) / itemRangeY)));
++ else
++ maxBounds.setY(-1.0f);
++
++ if (maxBounds.z() > m_scaleZ)
++ maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_scaleZ) / itemRangeZ)));
++ else
++ maxBounds.setZ(-1.0f);
++ }
++
++ void Surface3DRenderer::updateData()
++ {
++ calculateSceneScalingFactors();
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->isVisible() && cache->dataDirty()) {
++ const QSurface3DSeries *currentSeries = cache->series();
++ QSurfaceDataProxy *dataProxy = currentSeries->dataProxy();
++ const QSurfaceDataArray &array = *dataProxy->array();
++ QSurfaceDataArray &dataArray = cache->dataArray();
++ QRect sampleSpace;
++
++ // Need minimum of 2x2 array to draw a surface
++ if (array.size() >= 2 && array.at(0)->size() >= 2)
++ sampleSpace = calculateSampleRect(array);
++
++ bool dimensionsChanged = false;
++ if (cache->sampleSpace() != sampleSpace) {
++ if (sampleSpace.width() >= 2)
++ m_selectionTexturesDirty = true;
++
++ dimensionsChanged = true;
++ cache->setSampleSpace(sampleSpace);
++
++ for (int i = 0; i < dataArray.size(); i++)
++ delete dataArray.at(i);
++ dataArray.clear();
++ }
++
++ if (sampleSpace.width() >= 2 && sampleSpace.height() >= 2) {
++ if (dimensionsChanged) {
++ dataArray.reserve(sampleSpace.height());
++ for (int i = 0; i < sampleSpace.height(); i++)
++ dataArray << new QSurfaceDataRow(sampleSpace.width());
++ }
++ for (int i = 0; i < sampleSpace.height(); i++) {
++ for (int j = 0; j < sampleSpace.width(); j++) {
++ (*(dataArray.at(i)))[j] = array.at(i + sampleSpace.y())->at(
++ j + sampleSpace.x());
++ }
++ }
++
++ checkFlatSupport(cache);
++ updateObjects(cache, dimensionsChanged);
++ cache->setFlatStatusDirty(false);
++ } else {
++ cache->surfaceObject()->clear();
++ }
++ cache->setDataDirty(false);
++ }
++ }
++
++ if (m_selectionTexturesDirty && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone)
++ updateSelectionTextures();
++
++ updateSelectedPoint(m_selectedPoint, m_selectedSeries);
++ }
++
++ void Surface3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList)
++ {
++ Abstract3DRenderer::updateSeries(seriesList);
++
++ bool noSelection = true;
++ for (auto series: seriesList) {
++ QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>( m_renderCacheList.value(series));
++ if (noSelection
++ && surfaceSeries->selectedPoint() != QSurface3DSeries::invalidSelectionPosition()) {
++ if (selectionLabel() != cache->itemLabel())
++ m_selectionLabelDirty = true;
++ noSelection = false;
++ }
++
++ if (cache->isFlatStatusDirty() && cache->sampleSpace().width()) {
++ checkFlatSupport(cache);
++ updateObjects(cache, true);
++ cache->setFlatStatusDirty(false);
++ }
++ }
++
++ if (noSelection && !selectionLabel().isEmpty()) {
++ m_selectionLabelDirty = true;
++ updateSelectedPoint(Surface3DController::invalidSelectionPosition(), 0);
++ }
++
++ // Selection pointer issues
++ if (m_selectedSeries) {
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ QVector4D highlightColor =
++ Utils::vectorFromColor(cache->series()->singleHighlightColor());
++ SelectionPointer *slicePointer = cache->sliceSelectionPointer();
++ if (slicePointer) {
++ slicePointer->setHighlightColor(highlightColor);
++ slicePointer->setPointerObject(cache->object());
++ slicePointer->setRotation(cache->meshRotation());
++ }
++ SelectionPointer *mainPointer = cache->mainSelectionPointer();
++ if (mainPointer) {
++ mainPointer->setHighlightColor(highlightColor);
++ mainPointer->setPointerObject(cache->object());
++ mainPointer->setRotation(cache->meshRotation());
++ }
++ }
++ }
++ }
++
++ void Surface3DRenderer::updateSurfaceTextures(QList<QSurface3DSeries *> seriesList)
++ {
++ for (auto series: seriesList) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(m_renderCacheList.value(series));
++ if (cache) {
++ GLuint oldTexture = cache->surfaceTexture();
++ m_textureHelper->deleteTexture(&oldTexture);
++ cache->setSurfaceTexture(0);
++
++ const QSurface3DSeries *currentSeries = cache->series();
++ QSurfaceDataProxy *dataProxy = currentSeries->dataProxy();
++ const QSurfaceDataArray &array = *dataProxy->array();
++
++ if (!series->texture().isNull()) {
++ GLuint texId = m_textureHelper->create2DTexture(series->texture(),
++ true, true, true, true);
++ glBindTexture(GL_TEXTURE_2D, texId);
++ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
++ glBindTexture(GL_TEXTURE_2D, 0);
++ cache->setSurfaceTexture(texId);
++
++ if (cache->isFlatShadingEnabled())
++ cache->surfaceObject()->coarseUVs(array, cache->dataArray());
++ else
++ cache->surfaceObject()->smoothUVs(array, cache->dataArray());
++ }
++ }
++ }
++ }
++
++ SeriesRenderCache *Surface3DRenderer::createNewCache(QAbstract3DSeries *series)
++ {
++ m_selectionTexturesDirty = true;
++ return new SurfaceSeriesRenderCache(series, this);
++ }
++
++ void Surface3DRenderer::cleanCache(SeriesRenderCache *cache)
++ {
++ Abstract3DRenderer::cleanCache(cache);
++ m_selectionTexturesDirty = true;
++ }
++
++ void Surface3DRenderer::updateRows(const QList<Surface3DController::ChangeRow> &rows)
++ {
++ for (auto item: rows) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(m_renderCacheList.value(item.series));
++ QSurfaceDataArray &dstArray = cache->dataArray();
++ const QRect &sampleSpace = cache->sampleSpace();
++
++ const QSurfaceDataArray *srcArray = 0;
++ QSurfaceDataProxy *dataProxy = item.series->dataProxy();
++ if (dataProxy)
++ srcArray = dataProxy->array();
++
++ if (cache && srcArray->size() >= 2 && srcArray->at(0)->size() >= 2 &&
++ sampleSpace.width() >= 2 && sampleSpace.height() >= 2) {
++ bool updateBuffers = false;
++ int sampleSpaceTop = sampleSpace.y() + sampleSpace.height();
++ int row = item.row;
++ if (row >= sampleSpace.y() && row <= sampleSpaceTop) {
++ updateBuffers = true;
++ for (int j = 0; j < sampleSpace.width(); j++) {
++ (*(dstArray.at(row - sampleSpace.y())))[j] =
++ srcArray->at(row)->at(j + sampleSpace.x());
++ }
++
++ if (cache->isFlatShadingEnabled()) {
++ cache->surfaceObject()->updateCoarseRow(dstArray, row - sampleSpace.y(),
++ m_polarGraph);
++ } else {
++ cache->surfaceObject()->updateSmoothRow(dstArray, row - sampleSpace.y(),
++ m_polarGraph);
++ }
++ }
++ if (updateBuffers)
++ cache->surfaceObject()->uploadBuffers();
++ }
++ }
++
++ updateSelectedPoint(m_selectedPoint, m_selectedSeries);
++ }
++
++ void Surface3DRenderer::updateItems(const QList<Surface3DController::ChangeItem> &points)
++ {
++ for (auto item: points) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(m_renderCacheList.value(item.series));
++ QSurfaceDataArray &dstArray = cache->dataArray();
++ const QRect &sampleSpace = cache->sampleSpace();
++
++ const QSurfaceDataArray *srcArray = 0;
++ QSurfaceDataProxy *dataProxy = item.series->dataProxy();
++ if (dataProxy)
++ srcArray = dataProxy->array();
++
++ if (cache && srcArray->size() >= 2 && srcArray->at(0)->size() >= 2 &&
++ sampleSpace.width() >= 2 && sampleSpace.height() >= 2) {
++ int sampleSpaceTop = sampleSpace.y() + sampleSpace.height();
++ int sampleSpaceRight = sampleSpace.x() + sampleSpace.width();
++ bool updateBuffers = false;
++ // Note: Point is (row, column), samplespace is (columns x rows)
++ QPoint point = item.point;
++
++ if (point.x() <= sampleSpaceTop && point.x() >= sampleSpace.y() &&
++ point.y() <= sampleSpaceRight && point.y() >= sampleSpace.x()) {
++ updateBuffers = true;
++ int x = point.y() - sampleSpace.x();
++ int y = point.x() - sampleSpace.y();
++ (*(dstArray.at(y)))[x] = srcArray->at(point.x())->at(point.y());
++
++ if (cache->isFlatShadingEnabled())
++ cache->surfaceObject()->updateCoarseItem(dstArray, y, x, m_polarGraph);
++ else
++ cache->surfaceObject()->updateSmoothItem(dstArray, y, x, m_polarGraph);
++ }
++ if (updateBuffers)
++ cache->surfaceObject()->uploadBuffers();
++ }
++
++ }
++
++ updateSelectedPoint(m_selectedPoint, m_selectedSeries);
++ }
++
++ void Surface3DRenderer::updateSliceDataModel(const QPoint &point)
++ {
++ for (auto baseCache: m_renderCacheList)
++ static_cast<SurfaceSeriesRenderCache *>(baseCache)->sliceSurfaceObject()->clear();
++
++ if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionMultiSeries)) {
++ // Find axis coordinates for the selected point
++ SeriesRenderCache *selectedCache =
++ m_renderCacheList.value(const_cast<QSurface3DSeries *>(m_selectedSeries));
++ QSurfaceDataArray &dataArray =
++ static_cast<SurfaceSeriesRenderCache *>(selectedCache)->dataArray();
++ QSurfaceDataItem item = dataArray.at(point.x())->at(point.y());
++ QPointF coords(item.x(), item.z());
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->series() != m_selectedSeries) {
++ QPoint mappedPoint = mapCoordsToSampleSpace(cache, coords);
++ updateSliceObject(cache, mappedPoint);
++ } else {
++ updateSliceObject(cache, point);
++ }
++ }
++ } else {
++ if (m_selectedSeries) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(
++ m_renderCacheList.value(m_selectedSeries));
++ if (cache)
++ updateSliceObject(static_cast<SurfaceSeriesRenderCache *>(cache), point);
++ }
++ }
++ }
++
++ QPoint Surface3DRenderer::mapCoordsToSampleSpace(SurfaceSeriesRenderCache *cache,
++ const QPointF &coords)
++ {
++ QPoint point(-1, -1);
++
++ QSurfaceDataArray &dataArray = cache->dataArray();
++ int top = dataArray.size() - 1;
++ int right = dataArray.at(top)->size() - 1;
++ QSurfaceDataItem itemBottomLeft = dataArray.at(0)->at(0);
++ QSurfaceDataItem itemTopRight = dataArray.at(top)->at(right);
++
++ if (itemBottomLeft.x() <= coords.x() && itemTopRight.x() >= coords.x()) {
++ float modelX = coords.x() - itemBottomLeft.x();
++ float spanX = itemTopRight.x() - itemBottomLeft.x();
++ float stepX = spanX / float(right);
++ int sampleX = int((modelX + (stepX / 2.0f)) / stepX);
++
++ QSurfaceDataItem item = dataArray.at(0)->at(sampleX);
++ if (!QtPrivate::fuzzyCompare(float(coords.x()), item.x())) {
++ int direction = 1;
++ if (item.x() > coords.x())
++ direction = -1;
++
++ findMatchingColumn(coords.x(), sampleX, direction, dataArray);
++ }
++
++ if (sampleX >= 0 && sampleX <= right)
++ point.setY(sampleX);
++ }
++
++ if (itemBottomLeft.z() <= coords.y() && itemTopRight.z() >= coords.y()) {
++ float modelY = coords.y() - itemBottomLeft.z();
++ float spanY = itemTopRight.z() - itemBottomLeft.z();
++ float stepY = spanY / float(top);
++ int sampleY = int((modelY + (stepY / 2.0f)) / stepY);
++
++ QSurfaceDataItem item = dataArray.at(sampleY)->at(0);
++ if (!QtPrivate::fuzzyCompare(float(coords.y()), item.z())) {
++ int direction = 1;
++ if (item.z() > coords.y())
++ direction = -1;
++
++ findMatchingRow(coords.y(), sampleY, direction, dataArray);
++ }
++
++ if (sampleY >= 0 && sampleY <= top)
++ point.setX(sampleY);
++ }
++
++ return point;
++ }
++
++ void Surface3DRenderer::findMatchingRow(float z, int &sample, int direction,
++ QSurfaceDataArray &dataArray)
++ {
++ int maxZ = dataArray.size() - 1;
++ QSurfaceDataItem item = dataArray.at(sample)->at(0);
++ float distance = qAbs(z - item.z());
++ int newSample = sample + direction;
++ while (newSample >= 0 && newSample <= maxZ) {
++ item = dataArray.at(newSample)->at(0);
++ float newDist = qAbs(z - item.z());
++ if (newDist < distance) {
++ sample = newSample;
++ distance = newDist;
++ } else {
++ break;
++ }
++ newSample = sample + direction;
++ }
++ }
++
++ void Surface3DRenderer::findMatchingColumn(float x, int &sample, int direction,
++ QSurfaceDataArray &dataArray)
++ {
++ int maxX = dataArray.at(0)->size() - 1;
++ QSurfaceDataItem item = dataArray.at(0)->at(sample);
++ float distance = qAbs(x - item.x());
++ int newSample = sample + direction;
++ while (newSample >= 0 && newSample <= maxX) {
++ item = dataArray.at(0)->at(newSample);
++ float newDist = qAbs(x - item.x());
++ if (newDist < distance) {
++ sample = newSample;
++ distance = newDist;
++ } else {
++ break;
++ }
++ newSample = sample + direction;
++ }
++ }
++
++ void Surface3DRenderer::updateSliceObject(SurfaceSeriesRenderCache *cache, const QPoint &point)
++ {
++ int column = point.y();
++ int row = point.x();
++
++ if ((m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow) && row == -1) ||
++ (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn) && column == -1)) {
++ cache->sliceSurfaceObject()->clear();
++ return;
++ }
++
++ QSurfaceDataArray &sliceDataArray = cache->sliceDataArray();
++ for (int i = 0; i < sliceDataArray.size(); i++)
++ delete sliceDataArray.at(i);
++ sliceDataArray.clear();
++ sliceDataArray.reserve(2);
++
++ QSurfaceDataRow *sliceRow;
++ QSurfaceDataArray &dataArray = cache->dataArray();
++ float adjust = (0.025f * m_heightNormalizer) / 2.0f;
++ float doubleAdjust = 2.0f * adjust;
++ bool flipZX = false;
++ float zBack;
++ float zFront;
++ if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow)) {
++ QSurfaceDataRow *src = dataArray.at(row);
++ sliceRow = new QSurfaceDataRow(src->size());
++ zBack = m_axisCacheZ.min();
++ zFront = m_axisCacheZ.max();
++ for (int i = 0; i < sliceRow->size(); i++)
++ (*sliceRow)[i].setPosition(QVector3D(src->at(i).x(), src->at(i).y() + adjust, zFront));
++ } else {
++ flipZX = true;
++ const QRect &sampleSpace = cache->sampleSpace();
++ sliceRow = new QSurfaceDataRow(sampleSpace.height());
++ zBack = m_axisCacheX.min();
++ zFront = m_axisCacheX.max();
++ for (int i = 0; i < sampleSpace.height(); i++) {
++ (*sliceRow)[i].setPosition(QVector3D(dataArray.at(i)->at(column).z(),
++ dataArray.at(i)->at(column).y() + adjust,
++ zFront));
++ }
++ }
++ sliceDataArray << sliceRow;
++
++ // Make a duplicate, so that we get a little bit depth
++ QSurfaceDataRow *duplicateRow = new QSurfaceDataRow(*sliceRow);
++ for (int i = 0; i < sliceRow->size(); i++) {
++ (*sliceRow)[i].setPosition(QVector3D(sliceRow->at(i).x(),
++ sliceRow->at(i).y() - doubleAdjust,
++ zBack));
++ }
++ sliceDataArray << duplicateRow;
++
++ QRect sliceRect(0, 0, sliceRow->size(), 2);
++ if (sliceRow->size() > 0) {
++ if (cache->isFlatShadingEnabled()) {
++ cache->sliceSurfaceObject()->setUpData(sliceDataArray, sliceRect, true, false, flipZX);
++ } else {
++ cache->sliceSurfaceObject()->setUpSmoothData(sliceDataArray, sliceRect, true, false,
++ flipZX);
++ }
++ }
++ }
++
++ inline static float getDataValue(const QSurfaceDataArray &array, bool searchRow, int index)
++ {
++ if (searchRow)
++ return array.at(0)->at(index).x();
++ else
++ return array.at(index)->at(0).z();
++ }
++
++ inline static int binarySearchArray(const QSurfaceDataArray &array, int maxIdx, float limitValue,
++ bool searchRow, bool lowBound, bool ascending)
++ {
++ int min = 0;
++ int max = maxIdx;
++ int mid = 0;
++ int retVal;
++ while (max >= min) {
++ mid = (min + max) / 2;
++ float arrayValue = getDataValue(array, searchRow, mid);
++ if (arrayValue == limitValue)
++ return mid;
++ if (ascending) {
++ if (arrayValue < limitValue)
++ min = mid + 1;
++ else
++ max = mid - 1;
++ } else {
++ if (arrayValue > limitValue)
++ min = mid + 1;
++ else
++ max = mid - 1;
++ }
++ }
++
++ // Exact match not found, return closest depending on bound.
++ // The boundary is between last mid and min/max.
++ if (lowBound == ascending) {
++ if (mid > max)
++ retVal = mid;
++ else
++ retVal = min;
++ } else {
++ if (mid > max)
++ retVal = max;
++ else
++ retVal = mid;
++ }
++ if (retVal < 0 || retVal > maxIdx) {
++ retVal = -1;
++ } else if (lowBound) {
++ if (getDataValue(array, searchRow, retVal) < limitValue)
++ retVal = -1;
++ } else {
++ if (getDataValue(array, searchRow, retVal) > limitValue)
++ retVal = -1;
++ }
++ return retVal;
++ }
++
++ QRect Surface3DRenderer::calculateSampleRect(const QSurfaceDataArray &array)
++ {
++ QRect sampleSpace;
++
++ const int maxRow = array.size() - 1;
++ const int maxColumn = array.at(0)->size() - 1;
++
++ // We assume data is ordered sequentially in rows for X-value and in columns for Z-value.
++ // Determine if data is ascending or descending in each case.
++ const bool ascendingX = array.at(0)->at(0).x() < array.at(0)->at(maxColumn).x();
++ const bool ascendingZ = array.at(0)->at(0).z() < array.at(maxRow)->at(0).z();
++
++ int idx = binarySearchArray(array, maxColumn, m_axisCacheX.min(), true, true, ascendingX);
++ if (idx != -1) {
++ if (ascendingX)
++ sampleSpace.setLeft(idx);
++ else
++ sampleSpace.setRight(idx);
++ } else {
++ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
++ return sampleSpace;
++ }
++
++ idx = binarySearchArray(array, maxColumn, m_axisCacheX.max(), true, false, ascendingX);
++ if (idx != -1) {
++ if (ascendingX)
++ sampleSpace.setRight(idx);
++ else
++ sampleSpace.setLeft(idx);
++ } else {
++ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
++ return sampleSpace;
++ }
++
++ idx = binarySearchArray(array, maxRow, m_axisCacheZ.min(), false, true, ascendingZ);
++ if (idx != -1) {
++ if (ascendingZ)
++ sampleSpace.setTop(idx);
++ else
++ sampleSpace.setBottom(idx);
++ } else {
++ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
++ return sampleSpace;
++ }
++
++ idx = binarySearchArray(array, maxRow, m_axisCacheZ.max(), false, false, ascendingZ);
++ if (idx != -1) {
++ if (ascendingZ)
++ sampleSpace.setBottom(idx);
++ else
++ sampleSpace.setTop(idx);
++ } else {
++ sampleSpace.setWidth(-1); // to indicate nothing needs to be shown
++ return sampleSpace;
++ }
++
++ return sampleSpace;
++ }
++
++ void Surface3DRenderer::updateScene(Q3DScene *scene)
++ {
++ Abstract3DRenderer::updateScene(scene);
++
++ if (m_selectionActive
++ && m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionItem)) {
++ m_selectionDirty = true; // Ball may need repositioning if scene changes
++ }
++
++ updateSlicingActive(scene->isSlicingActive());
++ }
++
++ void Surface3DRenderer::render(GLuint defaultFboHandle)
++ {
++ // Handle GL state setup for FBO buffers and clearing of the render surface
++ Abstract3DRenderer::render(defaultFboHandle);
++
++ if (m_axisCacheX.positionsDirty())
++ m_axisCacheX.updateAllPositions();
++ if (m_axisCacheY.positionsDirty())
++ m_axisCacheY.updateAllPositions();
++ if (m_axisCacheZ.positionsDirty())
++ m_axisCacheZ.updateAllPositions();
++
++ drawScene(defaultFboHandle);
++ if (m_cachedIsSlicingActivated)
++ drawSlicedScene();
++
++ // Render selection label
++ if (m_selectionActive
++ && m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionItem)) {
++ for (SeriesRenderCache *baseCache: m_renderCacheList) {
++ const SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->slicePointerActive() && cache->renderable() &&
++ m_cachedIsSlicingActivated ) {
++ cache->sliceSelectionPointer()->renderSelectionLabel(defaultFboHandle);
++ }
++ if (cache->mainPointerActive() && cache->renderable()) {
++ cache->mainSelectionPointer()->renderSelectionLabel(defaultFboHandle,
++ m_useOrthoProjection);
++ }
++ }
++ }
++ }
++
++ void Surface3DRenderer::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;
++ }
++
++ 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 aspect = (GLfloat)m_secondarySubViewport.width()
++ / (GLfloat)m_secondarySubViewport.height();
++ GLfloat sliceUnitsScaled = sliceUnits / m_autoScaleAdjustment;
++ projectionMatrix.ortho(-sliceUnitsScaled * aspect, sliceUnitsScaled * aspect,
++ -sliceUnitsScaled, sliceUnitsScaled,
++ -1.0f, 4.0f);
++
++ // Set view matrix
++ QMatrix4x4 viewMatrix;
++ viewMatrix.lookAt(QVector3D(0.0f, 0.0f, 1.0f), zeroVector, upVector);
++
++ // Set light position
++ lightPos = QVector3D(0.0f, 0.0f, 2.0f);
++
++ QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix;
++
++ const Q3DCamera *activeCamera = m_cachedScene->activeCamera();
++
++ bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow);
++ AxisRenderCache &sliceCache = rowMode ? m_axisCacheX : m_axisCacheZ;
++
++ GLfloat scaleXBackground = 0.0f;
++ if (rowMode) {
++ // Don't use the regular margin for polar, as the graph is not going to be to scale anyway,
++ // and polar graphs often have quite a bit of margin, resulting in ugly slices.
++ if (m_polarGraph)
++ scaleXBackground = m_scaleX + 0.1f;
++ else
++ scaleXBackground = m_scaleXWithBackground;
++ } else {
++ if (m_polarGraph)
++ scaleXBackground = m_scaleZ + 0.1f;
++ else
++ scaleXBackground = m_scaleZWithBackground;
++ }
++
++ // Disable culling to avoid ugly conditionals with reversed axes and data
++ glDisable(GL_CULL_FACE);
++
++ if (!m_renderCacheList.isEmpty()) {
++ bool drawGrid = false;
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->sliceSurfaceObject()->indexCount() && cache->renderable()) {
++ if (!drawGrid && cache->surfaceGridVisible()) {
++ glEnable(GL_POLYGON_OFFSET_FILL);
++ glPolygonOffset(0.5f, 1.0f);
++ drawGrid = true;
++ }
++
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ QVector3D scaling(1.0f, 1.0f, sliceZScale);
++ modelMatrix.scale(scaling);
++ itModelMatrix.scale(scaling);
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++ cache->setMVPMatrix(MVPMatrix);
++
++ if (cache->surfaceVisible()) {
++ ShaderHelper *surfaceShader = m_surfaceSliceSmoothShader;
++ if (cache->isFlatShadingEnabled())
++ surfaceShader = m_surfaceSliceFlatShader;
++
++ surfaceShader->bind();
++
++ GLuint colorTexture = cache->baseUniformTexture();
++ if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) {
++ colorTexture = cache->baseUniformTexture();
++ surfaceShader->setUniformValue(surfaceShader->gradientMin(), 0.0f);
++ surfaceShader->setUniformValue(surfaceShader->gradientHeight(), 0.0f);
++ } else {
++ colorTexture = cache->baseGradientTexture();
++ if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) {
++ float objMin = cache->surfaceObject()->minYValue();
++ float objMax = cache->surfaceObject()->maxYValue();
++ float objRange = objMax - objMin;
++ surfaceShader->setUniformValue(surfaceShader->gradientMin(),
++ -(objMin / objRange));
++ surfaceShader->setUniformValue(surfaceShader->gradientHeight(),
++ 1.0f / objRange);
++ } else {
++ surfaceShader->setUniformValue(surfaceShader->gradientMin(), 0.5f);
++ surfaceShader->setUniformValue(surfaceShader->gradientHeight(),
++ 1.0f / (m_scaleY * 2.0f));
++ }
++ }
++
++ // Set shader bindings
++ surfaceShader->setUniformValue(surfaceShader->lightP(), lightPos);
++ surfaceShader->setUniformValue(surfaceShader->view(), viewMatrix);
++ surfaceShader->setUniformValue(surfaceShader->model(), modelMatrix);
++ surfaceShader->setUniformValue(surfaceShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ surfaceShader->setUniformValue(surfaceShader->MVP(), MVPMatrix);
++ surfaceShader->setUniformValue(surfaceShader->lightS(), 0.0f);
++ surfaceShader->setUniformValue(surfaceShader->ambientS(),
++ m_cachedTheme->ambientLightStrength()
++ + m_cachedTheme->lightStrength() / 10.0f);
++ surfaceShader->setUniformValue(surfaceShader->lightColor(), lightColor);
++
++ m_drawer->drawObject(surfaceShader, cache->sliceSurfaceObject(), colorTexture);
++ }
++ }
++ }
++
++ // Draw surface grid
++ if (drawGrid) {
++ glDisable(GL_POLYGON_OFFSET_FILL);
++ m_surfaceGridShader->bind();
++ m_surfaceGridShader->setUniformValue(m_surfaceGridShader->color(),
++ Utils::vectorFromColor(m_cachedTheme->gridLineColor()));
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->sliceSurfaceObject()->indexCount() && cache->isVisible() &&
++ cache->surfaceGridVisible()) {
++ m_surfaceGridShader->setUniformValue(m_surfaceGridShader->MVP(),
++ cache->MVPMatrix());
++ m_drawer->drawSurfaceGrid(m_surfaceGridShader, cache->sliceSurfaceObject());
++ }
++ }
++ }
++ }
++
++ glEnable(GL_CULL_FACE);
++ glCullFace(GL_BACK);
++
++ // Grid lines
++ if (m_cachedTheme->isGridEnabled()) {
++ ShaderHelper *lineShader;
++ if (m_isOpenGLES)
++ lineShader = m_selectionShader; // Plain color shader for GL_LINES
++ else
++ lineShader = m_backgroundShader;
++
++ // Bind line shader
++ lineShader->bind();
++
++ // Set unchanging shader bindings
++ QVector4D lineColor = Utils::vectorFromColor(m_cachedTheme->gridLineColor());
++ lineShader->setUniformValue(lineShader->lightP(), lightPos);
++ lineShader->setUniformValue(lineShader->view(), viewMatrix);
++ lineShader->setUniformValue(lineShader->color(), lineColor);
++ lineShader->setUniformValue(lineShader->ambientS(),
++ m_cachedTheme->ambientLightStrength()
++ + m_cachedTheme->lightStrength() / 10.0f);
++ lineShader->setUniformValue(lineShader->lightS(), 0.0f);
++ lineShader->setUniformValue(lineShader->lightColor(), lightColor);
++
++ // Horizontal lines
++ int gridLineCount = m_axisCacheY.gridLineCount();
++ if (m_axisCacheY.segmentCount() > 0) {
++ QVector3D gridLineScaleX(scaleXBackground, gridLineWidth, gridLineWidth);
++
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(0.0f, m_axisCacheY.gridLinePosition(line), -1.0f);
++
++ modelMatrix.scale(gridLineScaleX);
++ itModelMatrix.scale(gridLineScaleX);
++
++ 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);
++ }
++ }
++
++ // Vertical lines
++ QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth);
++
++ gridLineCount = sliceCache.gridLineCount();
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(sliceCache.gridLinePosition(line), 0.0f, -1.0f);
++ modelMatrix.scale(gridLineScaleY);
++ itModelMatrix.scale(gridLineScaleY);
++
++ if (m_isOpenGLES) {
++ modelMatrix.rotate(m_zRightAngleRotation);
++ itModelMatrix.rotate(m_zRightAngleRotation);
++ }
++
++ 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);
++ }
++ }
++
++ // Draw labels
++ m_labelShader->bind();
++ glDisable(GL_DEPTH_TEST);
++ glEnable(GL_BLEND);
++ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
++
++ // Y Labels to back wall
++ int labelNbr = 0;
++
++ QVector3D positionComp(0.0f, 0.0f, 0.0f);
++ QVector3D labelTrans = QVector3D(scaleXBackground + labelMargin, 0.0f, 0.0f);
++ int labelCount = m_axisCacheY.labelCount();
++ for (int label = 0; label < labelCount; label++) {
++ if (m_axisCacheY.labelItems().size() > labelNbr) {
++ labelTrans.setY(m_axisCacheY.labelPosition(label));
++ const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(labelNbr);
++
++ // Draw the label here
++ m_dummyRenderItem.setTranslation(labelTrans);
++ m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
++ positionComp, identityQuaternion, 0, m_cachedSelectionMode,
++ m_labelShader, m_labelObj, activeCamera, true, true,
++ Drawer::LabelMid, Qt::AlignLeft, true);
++ }
++ labelNbr++;
++ }
++
++ // X Labels to ground
++ int countLabelItems = sliceCache.labelItems().size();
++
++ QVector3D rotation(0.0f, 0.0f, -45.0f);
++ QQuaternion totalRotation = Utils::calculateRotation(rotation);
++
++ labelNbr = 0;
++ positionComp.setY(-0.1f);
++ labelTrans.setY(-m_scaleYWithBackground);
++ labelCount = sliceCache.labelCount();
++ for (int label = 0; label < labelCount; label++) {
++ if (countLabelItems > labelNbr) {
++ // Draw the label here
++ if (rowMode)
++ labelTrans.setX(sliceCache.labelPosition(label));
++ else
++ labelTrans.setX(-sliceCache.labelPosition(label));
++
++ m_dummyRenderItem.setTranslation(labelTrans);
++
++ LabelItem *axisLabelItem;
++ axisLabelItem = sliceCache.labelItems().at(labelNbr);
++
++ m_drawer->drawLabel(m_dummyRenderItem, *axisLabelItem, viewMatrix, projectionMatrix,
++ positionComp, totalRotation, 0, QAbstract3DGraph::SelectionRow,
++ m_labelShader, m_labelObj, activeCamera,
++ false, false, Drawer::LabelBelow,
++ Qt::AlignLeft | Qt::AlignTop, true);
++ }
++ labelNbr++;
++ }
++
++ // Draw labels for axes
++ AbstractRenderItem *dummyItem(0);
++ positionComp.setY(m_autoScaleAdjustment);
++ m_drawer->drawLabel(*dummyItem, sliceCache.titleItem(), viewMatrix, projectionMatrix,
++ positionComp, identityQuaternion, 0, m_cachedSelectionMode, m_labelShader,
++ m_labelObj, activeCamera, false, false, Drawer::LabelBottom,
++ Qt::AlignCenter, true);
++
++ // Y-axis label
++ rotation = QVector3D(0.0f, 0.0f, 90.0f);
++ totalRotation = Utils::calculateRotation(rotation);
++ labelTrans = QVector3D(-scaleXBackground - labelMargin, 0.0f, 0.0f);
++ m_dummyRenderItem.setTranslation(labelTrans);
++ m_drawer->drawLabel(m_dummyRenderItem, m_axisCacheY.titleItem(), viewMatrix,
++ projectionMatrix, zeroVector, totalRotation, 0,
++ m_cachedSelectionMode, m_labelShader, m_labelObj, activeCamera,
++ false, false, Drawer::LabelMid, Qt::AlignBottom);
++
++ glEnable(GL_DEPTH_TEST);
++ glDisable(GL_BLEND);
++
++ // Release shader
++ glUseProgram(0);
++ }
++
++ void Surface3DRenderer::drawScene(GLuint defaultFboHandle)
++ {
++ bool noShadows = true;
++
++ GLfloat backgroundRotation = 0;
++ QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor());
++
++ 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);
++ }
++
++ const Q3DCamera *activeCamera = m_cachedScene->activeCamera();
++
++ // Calculate view matrix
++ QMatrix4x4 viewMatrix = activeCamera->d_ptr->viewMatrix();
++
++ QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix;
++
++ // Calculate flipping indicators
++ if (viewMatrix.row(0).x() > 0)
++ m_zFlipped = false;
++ else
++ m_zFlipped = true;
++ if (viewMatrix.row(0).z() <= 0)
++ m_xFlipped = false;
++ else
++ m_xFlipped = true;
++
++ m_yFlippedForGrid = m_yFlipped;
++ if (m_flipHorizontalGrid) {
++ if (!m_useOrthoProjection) {
++ // Need to determine if camera is below graph top
++ float distanceToCenter = activeCamera->position().length()
++ / activeCamera->zoomLevel() / m_autoScaleAdjustment * 100.0f;
++ qreal cameraAngle = qDegreesToRadians(qreal(activeCamera->yRotation()));
++ float cameraYPos = float(qSin(cameraAngle)) * distanceToCenter;
++ m_yFlippedForGrid = cameraYPos < (m_scaleYWithBackground - m_oldCameraTarget.y());
++ } else if (m_useOrthoProjection && activeCamera->yRotation() == 0.0f) {
++ // With ortho we only need to flip at angle zero, to fix label autorotation angles
++ m_yFlippedForGrid = !m_yFlipped;
++ }
++ }
++
++ // 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;
++
++ QVector3D lightPos = m_cachedScene->activeLight()->position();
++
++ QMatrix4x4 depthViewMatrix;
++ QMatrix4x4 depthProjectionMatrix;
++ QMatrix4x4 depthProjectionViewMatrix;
++
++ // Draw depth buffer
++ GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f;
++ if (!m_isOpenGLES && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone &&
++ (!m_renderCacheList.isEmpty() || !m_customRenderCache.isEmpty())) {
++ // Render scene into a depth texture for using with shadow mapping
++ // Enable drawing to depth framebuffer
++ glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer);
++
++ // Attach texture to depth attachment
++ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
++ m_depthTexture, 0);
++ 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.
++ 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, 4.0f / m_autoScaleAdjustment);
++ depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector);
++
++ // Set the depth projection matrix
++ depthProjectionMatrix.perspective(10.0f, (GLfloat)m_primarySubViewport.width()
++ / (GLfloat)m_primarySubViewport.height(), 3.0f, 100.0f);
++ depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix;
++
++ // Surface is not closed, so don't cull anything
++ glDisable(GL_CULL_FACE);
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ SurfaceObject *object = cache->surfaceObject();
++ if (object->indexCount() && cache->surfaceVisible() && cache->isVisible()
++ && cache->sampleSpace().width() >= 2 && cache->sampleSpace().height() >= 2) {
++ // No translation nor scaling for surfaces, therefore no modelMatrix
++ // Use directly projectionViewMatrix
++ m_depthShader->setUniformValue(m_depthShader->MVP(), depthProjectionViewMatrix);
++
++ // 1st attribute buffer : vertices
++ glEnableVertexAttribArray(m_depthShader->posAtt());
++ glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf());
++ glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0,
++ (void *)0);
++
++ // Index buffer
++ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf());
++
++ // Draw the triangles
++ glDrawElements(GL_TRIANGLES, object->indexCount(), GL_UNSIGNED_INT, (void *)0);
++ }
++ }
++
++ // Free buffers
++ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
++ glBindBuffer(GL_ARRAY_BUFFER, 0);
++
++ glDisableVertexAttribArray(m_depthShader->posAtt());
++
++ glEnable(GL_CULL_FACE);
++ glCullFace(GL_FRONT);
++
++ 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);
++
++ // Revert to original viewport
++ glViewport(m_primarySubViewport.x(),
++ m_primarySubViewport.y(),
++ m_primarySubViewport.width(),
++ m_primarySubViewport.height());
++
++ // Reset culling to normal
++ glEnable(GL_CULL_FACE);
++ glCullFace(GL_BACK);
++ }
++
++ // Do position mapping when necessary
++ if (m_graphPositionQueryPending) {
++ QVector3D graphDimensions(m_scaleX, m_scaleY, m_scaleZ);
++ queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle);
++ emit needRender();
++ }
++
++ // Draw selection buffer
++ if (!m_cachedIsSlicingActivated && (!m_renderCacheList.isEmpty()
++ || !m_customRenderCache.isEmpty())
++ && m_selectionState == SelectOnScene
++ && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone
++ && m_selectionResultTexture) {
++ m_selectionShader->bind();
++ 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(0.0f, 0.0f, 0.0f, 0.0f);
++ 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
++
++ glDisable(GL_CULL_FACE);
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->surfaceObject()->indexCount() && cache->renderable()) {
++ m_selectionShader->setUniformValue(m_selectionShader->MVP(), projectionViewMatrix);
++
++ cache->surfaceObject()->activateSurfaceTexture(false);
++
++ m_drawer->drawObject(m_selectionShader, cache->surfaceObject(),
++ cache->selectionTexture());
++ }
++ }
++ m_surfaceGridShader->bind();
++ Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader,
++ viewMatrix,
++ projectionViewMatrix, depthProjectionViewMatrix,
++ m_depthTexture, m_shadowQualityToShader);
++ drawLabels(true, activeCamera, viewMatrix, projectionMatrix);
++
++ glEnable(GL_DITHER);
++
++ QVector4D clickedColor = Utils::getSelection(m_inputPosition, m_viewport.height());
++
++ glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle);
++
++ // Put the RGBA value back to uint
++ uint selectionId = uint(clickedColor.x())
++ + uint(clickedColor.y()) * greenMultiplier
++ + uint(clickedColor.z()) * blueMultiplier
++ + uint(clickedColor.w()) * alphaMultiplier;
++
++ m_clickedPosition = selectionIdToSurfacePoint(selectionId);
++ m_clickResolved = true;
++
++ emit needRender();
++
++ // Revert to original viewport
++ glViewport(m_primarySubViewport.x(),
++ m_primarySubViewport.y(),
++ m_primarySubViewport.width(),
++ m_primarySubViewport.height());
++ }
++
++ // Selection handling
++ if (m_selectionDirty || m_selectionLabelDirty) {
++ QPoint visiblePoint = Surface3DController::invalidSelectionPosition();
++ if (m_selectedSeries) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(
++ m_renderCacheList.value(const_cast<QSurface3DSeries *>(m_selectedSeries)));
++ if (cache && m_selectedPoint != Surface3DController::invalidSelectionPosition()) {
++ const QRect &sampleSpace = cache->sampleSpace();
++ int x = m_selectedPoint.x() - sampleSpace.y();
++ int y = m_selectedPoint.y() - sampleSpace.x();
++ if (x >= 0 && y >= 0 && x < sampleSpace.height() && y < sampleSpace.width()
++ && cache->dataArray().size()) {
++ visiblePoint = QPoint(x, y);
++ }
++ }
++ }
++
++ if (m_cachedSelectionMode == QAbstract3DGraph::SelectionNone
++ || visiblePoint == Surface3DController::invalidSelectionPosition()) {
++ m_selectionActive = false;
++ } else {
++ if (m_cachedIsSlicingActivated)
++ updateSliceDataModel(visiblePoint);
++ if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionItem))
++ surfacePointSelected(visiblePoint);
++ m_selectionActive = true;
++ }
++
++ m_selectionDirty = false;
++ }
++
++ // Draw the surface
++ if (!m_renderCacheList.isEmpty()) {
++ // For surface we can see glimpses from underneath
++ glDisable(GL_CULL_FACE);
++
++ bool drawGrid = false;
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ #ifdef SHOW_DEPTH_TEXTURE_SCENE
++ MVPMatrix = depthProjectionViewMatrix;
++ #else
++ MVPMatrix = projectionViewMatrix;
++ #endif
++ cache->setMVPMatrix(MVPMatrix);
++
++ const QRect &sampleSpace = cache->sampleSpace();
++ if (cache->surfaceObject()->indexCount() && cache->isVisible() &&
++ sampleSpace.width() >= 2 && sampleSpace.height() >= 2) {
++ noShadows = false;
++ if (!drawGrid && cache->surfaceGridVisible()) {
++ glEnable(GL_POLYGON_OFFSET_FILL);
++ glPolygonOffset(0.5f, 1.0f);
++ drawGrid = true;
++ }
++
++ if (cache->surfaceVisible()) {
++ ShaderHelper *shader = m_surfaceFlatShader;
++ if (cache->surfaceTexture())
++ shader = m_surfaceTexturedFlatShader;
++ if (!cache->isFlatShadingEnabled()) {
++ shader = m_surfaceSmoothShader;
++ if (cache->surfaceTexture())
++ shader = m_surfaceTexturedSmoothShader;
++ }
++ shader->bind();
++
++ // Set shader bindings
++ shader->setUniformValue(shader->lightP(), lightPos);
++ shader->setUniformValue(shader->view(), viewMatrix);
++ shader->setUniformValue(shader->model(), modelMatrix);
++ shader->setUniformValue(shader->nModel(),
++ itModelMatrix.inverted().transposed());
++ shader->setUniformValue(shader->MVP(), MVPMatrix);
++ shader->setUniformValue(shader->ambientS(),
++ m_cachedTheme->ambientLightStrength());
++ shader->setUniformValue(shader->lightColor(), lightColor);
++
++ // Set the surface texturing
++ cache->surfaceObject()->activateSurfaceTexture(false);
++ GLuint texture;
++ if (cache->surfaceTexture()) {
++ texture = cache->surfaceTexture();
++ cache->surfaceObject()->activateSurfaceTexture(true);
++ } else {
++ if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) {
++ texture = cache->baseUniformTexture();
++ shader->setUniformValue(shader->gradientMin(), 0.0f);
++ shader->setUniformValue(shader->gradientHeight(), 0.0f);
++ } else {
++ texture = cache->baseGradientTexture();
++ if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) {
++ float objMin = cache->surfaceObject()->minYValue();
++ float objMax = cache->surfaceObject()->maxYValue();
++ float objRange = objMax - objMin;
++ shader->setUniformValue(shader->gradientMin(), -(objMin / objRange));
++ shader->setUniformValue(shader->gradientHeight(), 1.0f / objRange);
++ } else {
++ shader->setUniformValue(shader->gradientMin(), 0.5f);
++ shader->setUniformValue(shader->gradientHeight(),
++ 1.0f / (m_scaleY * 2.0f));
++ }
++ }
++ }
++
++ if (!m_isOpenGLES &&
++ m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // 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 objects
++ m_drawer->drawObject(shader, cache->surfaceObject(), texture,
++ m_depthTexture);
++ } else {
++ // Set shadowless shader bindings
++ shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength());
++ // Draw the objects
++ m_drawer->drawObject(shader, cache->surfaceObject(), texture);
++ }
++ }
++ }
++ }
++ glEnable(GL_CULL_FACE);
++
++ // Draw surface grid
++ if (drawGrid) {
++ glDisable(GL_POLYGON_OFFSET_FILL);
++ m_surfaceGridShader->bind();
++ m_surfaceGridShader->setUniformValue(m_surfaceGridShader->color(),
++ Utils::vectorFromColor(
++ m_cachedTheme->gridLineColor()));
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ m_surfaceGridShader->setUniformValue(m_surfaceGridShader->MVP(),
++ cache->MVPMatrix());
++
++ const QRect &sampleSpace = cache->sampleSpace();
++ if (cache->surfaceObject()->indexCount() && cache->surfaceGridVisible()
++ && cache->isVisible() && sampleSpace.width() >= 2
++ && sampleSpace.height() >= 2) {
++ m_drawer->drawSurfaceGrid(m_surfaceGridShader, cache->surfaceObject());
++ }
++ }
++ }
++ }
++
++ // Render selection ball
++ if (m_selectionActive
++ && m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionItem)) {
++ for (SeriesRenderCache *baseCache: m_renderCacheList) {
++ const SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->slicePointerActive() && cache->renderable() &&
++ m_cachedIsSlicingActivated ) {
++ cache->sliceSelectionPointer()->renderSelectionPointer(defaultFboHandle);
++ }
++ if (cache->mainPointerActive() && cache->renderable()) {
++ cache->mainSelectionPointer()->renderSelectionPointer(defaultFboHandle,
++ m_useOrthoProjection);
++ }
++ }
++ }
++
++ // Bind background shader
++ m_backgroundShader->bind();
++ glCullFace(GL_BACK);
++
++ // Draw background
++ if (m_cachedTheme->isBackgroundEnabled() && m_backgroundObj) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ QVector3D bgScale(m_scaleXWithBackground, m_scaleYWithBackground, m_scaleZWithBackground);
++ modelMatrix.scale(bgScale);
++
++ // If we're viewing from below, background object must be flipped
++ if (m_yFlipped) {
++ modelMatrix.rotate(m_xFlipRotation);
++ modelMatrix.rotate(270.0f - backgroundRotation, 0.0f, 1.0f, 0.0f);
++ } else {
++ modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f);
++ }
++
++ itModelMatrix = modelMatrix; // Only scaling and rotations, can be used directly
++
++ #ifdef SHOW_DEPTH_TEXTURE_SCENE
++ MVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ #else
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++ #endif
++
++ bool blendEnabled = false;
++ QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor());
++ if (backgroundColor.w() < 1.0f) {
++ blendEnabled = true;
++ glEnable(GL_BLEND);
++ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
++ }
++
++ // Set shader bindings
++ m_backgroundShader->setUniformValue(m_backgroundShader->lightP(), lightPos);
++ m_backgroundShader->setUniformValue(m_backgroundShader->view(), viewMatrix);
++ m_backgroundShader->setUniformValue(m_backgroundShader->model(), modelMatrix);
++ m_backgroundShader->setUniformValue(m_backgroundShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ m_backgroundShader->setUniformValue(m_backgroundShader->MVP(), MVPMatrix);
++ m_backgroundShader->setUniformValue(m_backgroundShader->color(), backgroundColor);
++ m_backgroundShader->setUniformValue(m_backgroundShader->ambientS(),
++ m_cachedTheme->ambientLightStrength() * 2.0f);
++ m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor);
++
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(),
++ m_shadowQualityToShader);
++ m_backgroundShader->setUniformValue(m_backgroundShader->depth(), depthMVPMatrix);
++ m_backgroundShader->setUniformValue(m_backgroundShader->lightS(),
++ adjustedLightStrength);
++ // Draw the object
++ if (noShadows && m_customRenderCache.isEmpty())
++ m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_noShadowTexture);
++ else
++ m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture);
++ } else {
++ // Set shadowless shader bindings
++ m_backgroundShader->setUniformValue(m_backgroundShader->lightS(),
++ m_cachedTheme->lightStrength());
++
++ // Draw the object
++ m_drawer->drawObject(m_backgroundShader, m_backgroundObj);
++ }
++
++ if (blendEnabled)
++ glDisable(GL_BLEND);
++ }
++
++ // Draw grid lines
++ QVector3D gridLineScaleX(m_scaleXWithBackground, gridLineWidth, gridLineWidth);
++ QVector3D gridLineScaleZ(gridLineWidth, gridLineWidth, m_scaleZWithBackground);
++ QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth);
++
++ if (m_cachedTheme->isGridEnabled()) {
++ ShaderHelper *lineShader;
++ if (m_isOpenGLES)
++ lineShader = m_surfaceGridShader; // Plain color shader for GL_LINES
++ else
++ lineShader = m_backgroundShader;
++
++ // Bind line shader
++ lineShader->bind();
++
++ // Set unchanging shader bindings
++ QVector4D lineColor = Utils::vectorFromColor(m_cachedTheme->gridLineColor());
++ lineShader->setUniformValue(lineShader->lightP(), lightPos);
++ lineShader->setUniformValue(lineShader->view(), viewMatrix);
++ lineShader->setUniformValue(lineShader->color(), lineColor);
++ lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength());
++ lineShader->setUniformValue(lineShader->lightColor(), lightColor);
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) {
++ // Set shadowed shader bindings
++ lineShader->setUniformValue(lineShader->shadowQ(), m_shadowQualityToShader);
++ lineShader->setUniformValue(lineShader->lightS(),
++ m_cachedTheme->lightStrength() / 20.0f);
++ } else {
++ // Set shadowless shader bindings
++ lineShader->setUniformValue(lineShader->lightS(),
++ m_cachedTheme->lightStrength() / 2.5f);
++ }
++
++ QQuaternion lineYRotation;
++ QQuaternion lineXRotation;
++
++ if (m_xFlipped)
++ lineYRotation = m_yRightAngleRotationNeg;
++ else
++ lineYRotation = m_yRightAngleRotation;
++
++ if (m_yFlippedForGrid)
++ lineXRotation = m_xRightAngleRotation;
++ else
++ lineXRotation = m_xRightAngleRotationNeg;
++
++ float yFloorLinePosition = -m_scaleYWithBackground + gridLineOffset;
++ if (m_yFlipped != m_flipHorizontalGrid)
++ yFloorLinePosition = -yFloorLinePosition;
++
++ // Rows (= Z)
++ if (m_axisCacheZ.segmentCount() > 0) {
++ int gridLineCount = m_axisCacheZ.gridLineCount();
++ // Floor lines
++ if (m_polarGraph) {
++ drawRadialGrid(lineShader, yFloorLinePosition, projectionViewMatrix,
++ depthProjectionViewMatrix);
++ } else {
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(0.0f, yFloorLinePosition,
++ m_axisCacheZ.gridLinePosition(line));
++
++ modelMatrix.scale(gridLineScaleX);
++ itModelMatrix.scale(gridLineScaleX);
++
++ modelMatrix.rotate(lineXRotation);
++ itModelMatrix.rotate(lineXRotation);
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ // Set the rest of the shader bindings
++ lineShader->setUniformValue(lineShader->model(), modelMatrix);
++ lineShader->setUniformValue(lineShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(lineShader);
++ }
++ }
++ // Side wall lines
++ GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset;
++
++ if (!m_xFlipped)
++ lineXTrans = -lineXTrans;
++
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line));
++
++ modelMatrix.scale(gridLineScaleY);
++ itModelMatrix.scale(gridLineScaleY);
++
++ if (m_isOpenGLES) {
++ modelMatrix.rotate(m_zRightAngleRotation);
++ itModelMatrix.rotate(m_zRightAngleRotation);
++ } else {
++ modelMatrix.rotate(lineYRotation);
++ itModelMatrix.rotate(lineYRotation);
++ }
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ // Set the rest of the shader bindings
++ lineShader->setUniformValue(lineShader->model(), modelMatrix);
++ lineShader->setUniformValue(lineShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(lineShader);
++ }
++ }
++ }
++ }
++
++ // Columns (= X)
++ if (m_axisCacheX.segmentCount() > 0) {
++ if (m_isOpenGLES)
++ lineXRotation = m_yRightAngleRotation;
++
++ // Floor lines
++ int gridLineCount = m_axisCacheX.gridLineCount();
++
++ if (m_polarGraph) {
++ drawAngularGrid(lineShader, yFloorLinePosition, projectionViewMatrix,
++ depthProjectionViewMatrix);
++ } else {
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition,
++ 0.0f);
++
++ modelMatrix.scale(gridLineScaleZ);
++ itModelMatrix.scale(gridLineScaleZ);
++
++ modelMatrix.rotate(lineXRotation);
++ itModelMatrix.rotate(lineXRotation);
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ // Set the rest of the shader bindings
++ lineShader->setUniformValue(lineShader->model(), modelMatrix);
++ lineShader->setUniformValue(lineShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(lineShader);
++ }
++ }
++
++ // Back wall lines
++ GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset;
++
++ if (!m_zFlipped)
++ lineZTrans = -lineZTrans;
++
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans);
++
++ modelMatrix.scale(gridLineScaleY);
++ itModelMatrix.scale(gridLineScaleY);
++
++ if (m_isOpenGLES) {
++ modelMatrix.rotate(m_zRightAngleRotation);
++ itModelMatrix.rotate(m_zRightAngleRotation);
++ } else if (m_zFlipped) {
++ modelMatrix.rotate(m_xFlipRotation);
++ itModelMatrix.rotate(m_xFlipRotation);
++ }
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ // Set the rest of the shader bindings
++ lineShader->setUniformValue(lineShader->model(), modelMatrix);
++ lineShader->setUniformValue(lineShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(lineShader);
++ }
++ }
++ }
++ }
++
++ // Horizontal wall lines
++ if (m_axisCacheY.segmentCount() > 0) {
++ // Back wall
++ int gridLineCount = m_axisCacheY.gridLineCount();
++
++ GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset;
++
++ if (!m_zFlipped)
++ lineZTrans = -lineZTrans;
++
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(0.0f, m_axisCacheY.gridLinePosition(line), lineZTrans);
++
++ modelMatrix.scale(gridLineScaleX);
++ itModelMatrix.scale(gridLineScaleX);
++
++ if (m_zFlipped) {
++ modelMatrix.rotate(m_xFlipRotation);
++ itModelMatrix.rotate(m_xFlipRotation);
++ }
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ // Set the rest of the shader bindings
++ lineShader->setUniformValue(lineShader->model(), modelMatrix);
++ lineShader->setUniformValue(lineShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(lineShader);
++ }
++ }
++
++ // Side wall
++ GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset;
++
++ if (!m_xFlipped)
++ lineXTrans = -lineXTrans;
++
++ for (int line = 0; line < gridLineCount; line++) {
++ QMatrix4x4 modelMatrix;
++ QMatrix4x4 MVPMatrix;
++ QMatrix4x4 itModelMatrix;
++
++ modelMatrix.translate(lineXTrans, m_axisCacheY.gridLinePosition(line), 0.0f);
++
++ modelMatrix.scale(gridLineScaleZ);
++ itModelMatrix.scale(gridLineScaleZ);
++
++ modelMatrix.rotate(lineYRotation);
++ itModelMatrix.rotate(lineYRotation);
++
++ MVPMatrix = projectionViewMatrix * modelMatrix;
++
++ // Set the rest of the shader bindings
++ lineShader->setUniformValue(lineShader->model(), modelMatrix);
++ lineShader->setUniformValue(lineShader->nModel(),
++ itModelMatrix.inverted().transposed());
++ lineShader->setUniformValue(lineShader->MVP(), MVPMatrix);
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ // Set shadow shader bindings
++ QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix;
++ lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix);
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture);
++ } else {
++ // Draw the object
++ m_drawer->drawObject(lineShader, m_gridLineObj);
++ }
++ } else {
++ m_drawer->drawLine(lineShader);
++ }
++ }
++ }
++ }
++
++ Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix,
++ projectionViewMatrix, depthProjectionViewMatrix,
++ m_depthTexture, m_shadowQualityToShader);
++
++ drawLabels(false, activeCamera, viewMatrix, projectionMatrix);
++
++ // Release shader
++ glUseProgram(0);
++ }
++
++ void Surface3DRenderer::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_surfaceGridShader;
++ } else {
++ shader = m_labelShader;
++ shader->bind();
++
++ glEnable(GL_BLEND);
++ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
++ }
++
++ glEnable(GL_POLYGON_OFFSET_FILL);
++
++ float labelAutoAngle = m_axisCacheZ.labelAutoRotation();
++ float labelAngleFraction = labelAutoAngle / 90.0f;
++ float fractionCamY = activeCamera->yRotation() * labelAngleFraction;
++ float fractionCamX = activeCamera->xRotation() * labelAngleFraction;
++ float labelsMaxWidth = 0.0f;
++
++ int startIndex;
++ int endIndex;
++ int indexStep;
++
++ // Z Labels
++ QVector3D positionZComp(0.0f, 0.0f, 0.0f);
++ if (m_axisCacheZ.segmentCount() > 0) {
++ int labelCount = m_axisCacheZ.labelCount();
++ float labelXTrans = m_scaleXWithBackground + labelMargin;
++ float labelYTrans = m_flipHorizontalGrid ? m_scaleYWithBackground : -m_scaleYWithBackground;
++ if (m_polarGraph) {
++ labelXTrans *= m_radialLabelOffset;
++ // YTrans up only if over background
++ if (m_radialLabelOffset < 1.0f)
++ labelYTrans += gridLineOffset + gridLineWidth;
++ }
++ Qt::Alignment alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
++ QVector3D labelRotation;
++ if (m_xFlipped)
++ labelXTrans = -labelXTrans;
++ if (m_yFlipped)
++ labelYTrans = -labelYTrans;
++ if (labelAutoAngle == 0.0f) {
++ if (m_zFlipped)
++ labelRotation.setY(180.0f);
++ if (m_yFlippedForGrid) {
++ if (m_zFlipped)
++ labelRotation.setY(180.0f);
++ else
++ labelRotation.setY(0.0f);
++ labelRotation.setX(90.0f);
++ } else {
++ labelRotation.setX(-90.0f);
++ }
++ } else {
++ if (m_zFlipped)
++ labelRotation.setY(180.0f);
++ if (m_yFlippedForGrid) {
++ if (m_zFlipped) {
++ if (m_xFlipped) {
++ labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX)
++ * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle + fractionCamY);
++ } else {
++ labelRotation.setX(90.0f + (labelAutoAngle + fractionCamX)
++ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle - fractionCamY);
++ }
++ } else {
++ if (m_xFlipped) {
++ labelRotation.setX(90.0f + (labelAutoAngle - fractionCamX)
++ * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle - fractionCamY);
++ } else {
++ labelRotation.setX(90.0f - (labelAutoAngle + fractionCamX)
++ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle + fractionCamY);
++ }
++ }
++ } else {
++ if (m_zFlipped) {
++ if (m_xFlipped) {
++ labelRotation.setX(-90.0f + (labelAutoAngle - fractionCamX)
++ * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle + fractionCamY);
++ } else {
++ labelRotation.setX(-90.0f - (labelAutoAngle + fractionCamX)
++ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle - fractionCamY);
++ }
++ } else {
++ if (m_xFlipped) {
++ labelRotation.setX(-90.0f - (labelAutoAngle - fractionCamX)
++ * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle - fractionCamY);
++ } else {
++ labelRotation.setX(-90.0f + (labelAutoAngle + fractionCamX)
++ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle + fractionCamY);
++ }
++ }
++ }
++ }
++
++ QQuaternion totalRotation = Utils::calculateRotation(labelRotation);
++
++ QVector3D labelTrans = QVector3D(labelXTrans,
++ labelYTrans,
++ 0.0f);
++
++ if (m_zFlipped) {
++ startIndex = 0;
++ endIndex = labelCount;
++ indexStep = 1;
++ } else {
++ startIndex = labelCount - 1;
++ endIndex = -1;
++ indexStep = -1;
++ }
++ float offsetValue = 0.0f;
++ for (int label = startIndex; label != endIndex; label = label + indexStep) {
++ glPolygonOffset(offsetValue++ / -10.0f, 1.0f);
++ const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label);
++ // Draw the label here
++ if (m_polarGraph) {
++ float direction = m_zFlipped ? -1.0f : 1.0f;
++ labelTrans.setZ((m_axisCacheZ.formatter()->labelPositions().at(label)
++ * -m_polarRadius
++ + m_drawer->scaledFontSize() + gridLineWidth) * direction);
++ } else {
++ labelTrans.setZ(m_axisCacheZ.labelPosition(label));
++ }
++ if (label == 0 || label == (labelCount - 1)) {
++ // If the margin is small, adjust the position of the edge labels to avoid overlapping
++ // with labels of the other axes.
++ float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height();
++ float labelOverlap = qAbs(labelTrans.z())
++ + (scaleFactor * axisLabelItem.size().height() / 2.0f)
++ - m_scaleZWithBackground + labelMargin;
++ // No need to adjust quite as much on the front edges
++ if (label != startIndex)
++ labelOverlap /= 2.0f;
++ if (labelOverlap > 0.0f) {
++ if (label == 0)
++ labelTrans.setZ(labelTrans.z() - labelOverlap);
++ else
++ labelTrans.setZ(labelTrans.z() + labelOverlap);
++ }
++ }
++ m_dummyRenderItem.setTranslation(labelTrans);
++
++ if (drawSelection) {
++ QVector4D labelColor = QVector4D(label / 255.0f, 0.0f, 0.0f,
++ alphaForRowSelection);
++ shader->setUniformValue(shader->color(), labelColor);
++ }
++
++ m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
++ positionZComp, totalRotation, 0, m_cachedSelectionMode,
++ shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, alignment, false, drawSelection);
++ labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width()));
++ }
++ if (!drawSelection && m_axisCacheZ.isTitleVisible()) {
++ if (m_polarGraph) {
++ float titleZ = -m_polarRadius / 2.0f;
++ if (m_zFlipped)
++ titleZ = -titleZ;
++ labelTrans.setZ(titleZ);
++ } else {
++ labelTrans.setZ(0.0f);
++ }
++ drawAxisTitleZ(labelRotation, labelTrans, totalRotation, m_dummyRenderItem,
++ activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader);
++ }
++ }
++ // X Labels
++ if (m_axisCacheX.segmentCount() > 0) {
++ labelsMaxWidth = 0.0f;
++ labelAutoAngle = m_axisCacheX.labelAutoRotation();
++ labelAngleFraction = labelAutoAngle / 90.0f;
++ fractionCamY = activeCamera->yRotation() * labelAngleFraction;
++ fractionCamX = activeCamera->xRotation() * labelAngleFraction;
++ int labelCount = m_axisCacheX.labelCount();
++
++ float labelZTrans = 0.0f;
++ float labelYTrans = m_flipHorizontalGrid ? m_scaleYWithBackground : -m_scaleYWithBackground;
++ if (m_polarGraph)
++ labelYTrans += gridLineOffset + gridLineWidth;
++ else
++ labelZTrans = m_scaleZWithBackground + labelMargin;
++
++ Qt::Alignment alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
++ QVector3D labelRotation;
++ if (m_zFlipped)
++ labelZTrans = -labelZTrans;
++ if (m_yFlipped)
++ labelYTrans = -labelYTrans;
++ if (labelAutoAngle == 0.0f) {
++ labelRotation = QVector3D(-90.0f, 90.0f, 0.0f);
++ if (m_xFlipped)
++ labelRotation.setY(-90.0f);
++ if (m_yFlippedForGrid) {
++ if (m_xFlipped)
++ labelRotation.setY(-90.0f);
++ else
++ labelRotation.setY(90.0f);
++ labelRotation.setX(90.0f);
++ }
++ } else {
++ if (m_xFlipped)
++ labelRotation.setY(-90.0f);
++ else
++ labelRotation.setY(90.0f);
++ if (m_yFlippedForGrid) {
++ if (m_zFlipped) {
++ if (m_xFlipped) {
++ labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX)
++ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle - fractionCamY);
++ } else {
++ labelRotation.setX(90.0f - (2.0f * labelAutoAngle + fractionCamX)
++ * (labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle + fractionCamY);
++ }
++ } else {
++ if (m_xFlipped) {
++ labelRotation.setX(90.0f + fractionCamX
++ * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle + fractionCamY);
++ } else {
++ labelRotation.setX(90.0f - fractionCamX
++ * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle - fractionCamY);
++ }
++ }
++ } else {
++ if (m_zFlipped) {
++ if (m_xFlipped) {
++ labelRotation.setX(-90.0f + (2.0f * labelAutoAngle - fractionCamX)
++ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle - fractionCamY);
++ } else {
++ labelRotation.setX(-90.0f + (2.0f * labelAutoAngle + fractionCamX)
++ * (labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle + fractionCamY);
++ }
++ } else {
++ if (m_xFlipped) {
++ labelRotation.setX(-90.0f - fractionCamX
++ * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(-labelAutoAngle + fractionCamY);
++ } else {
++ labelRotation.setX(-90.0f + fractionCamX
++ * -(labelAutoAngle - fractionCamY) / labelAutoAngle);
++ labelRotation.setZ(labelAutoAngle - fractionCamY);
++ }
++ }
++ }
++ }
++
++ QQuaternion totalRotation = Utils::calculateRotation(labelRotation);
++ if (m_polarGraph) {
++ if ((!m_yFlippedForGrid && (m_zFlipped != m_xFlipped))
++ || (m_yFlippedForGrid && (m_zFlipped == m_xFlipped))) {
++ totalRotation *= m_zRightAngleRotation;
++ } else {
++ totalRotation *= m_zRightAngleRotationNeg;
++ }
++ }
++
++ QVector3D labelTrans = QVector3D(0.0f,
++ labelYTrans,
++ labelZTrans);
++
++ if (m_xFlipped) {
++ startIndex = labelCount - 1;
++ endIndex = -1;
++ indexStep = -1;
++ } else {
++ startIndex = 0;
++ endIndex = labelCount;
++ indexStep = 1;
++ }
++ float offsetValue = 0.0f;
++ bool showLastLabel = false;
++ QList<float> &labelPositions = m_axisCacheX.formatter()->labelPositions();
++ int lastLabelPosIndex = labelPositions.size() - 1;
++ if (labelPositions.size()
++ && (labelPositions.at(lastLabelPosIndex) != 1.0f || labelPositions.at(0) != 0.0f)) {
++ // Avoid overlapping first and last label if they would get on same position
++ showLastLabel = true;
++ }
++
++ for (int label = startIndex; label != endIndex; label = label + indexStep) {
++ glPolygonOffset(offsetValue++ / -10.0f, 1.0f);
++ // Draw the label here
++ if (m_polarGraph) {
++ // Calculate angular position
++ if (label == lastLabelPosIndex && !showLastLabel)
++ continue;
++ float labelPosition = labelPositions.at(label);
++ qreal angle = labelPosition * M_PI * 2.0;
++ labelTrans.setX((m_polarRadius + labelMargin) * float(qSin(angle)));
++ labelTrans.setZ(-(m_polarRadius + labelMargin) * float(qCos(angle)));
++ // Alignment depends on label angular position, as well as flips
++ Qt::AlignmentFlag vAlignment = Qt::AlignCenter;
++ Qt::AlignmentFlag hAlignment = Qt::AlignCenter;
++ const float centerMargin = 0.005f;
++ if (labelPosition < 0.25f - centerMargin || labelPosition > 0.75f + centerMargin)
++ vAlignment = m_zFlipped ? Qt::AlignTop : Qt::AlignBottom;
++ else if (labelPosition > 0.25f + centerMargin && labelPosition < 0.75f - centerMargin)
++ vAlignment = m_zFlipped ? Qt::AlignBottom : Qt::AlignTop;
++
++ if (labelPosition < 0.50f - centerMargin && labelPosition > centerMargin)
++ hAlignment = m_zFlipped ? Qt::AlignRight : Qt::AlignLeft;
++ else if (labelPosition < 1.0f - centerMargin && labelPosition > 0.5f + centerMargin)
++ hAlignment = m_zFlipped ? Qt::AlignLeft : Qt::AlignRight;
++ if (m_yFlippedForGrid && vAlignment != Qt::AlignCenter)
++ vAlignment = (vAlignment == Qt::AlignTop) ? Qt::AlignBottom : Qt::AlignTop;
++ alignment = vAlignment | hAlignment;
++ } else {
++ labelTrans.setX(m_axisCacheX.labelPosition(label));
++ }
++ const LabelItem &axisLabelItem = *m_axisCacheX.labelItems().at(label);
++ if (label == 0 || label == (labelCount - 1)) {
++ // If the margin is small, adjust the position of the edge labels to avoid overlapping
++ // with labels of the other axes.
++ float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height();
++ float labelOverlap = qAbs(labelTrans.x())
++ + (scaleFactor * axisLabelItem.size().height() / 2.0f)
++ - m_scaleXWithBackground + labelMargin;
++ // No need to adjust quite as much on the front edges
++ if (label != startIndex)
++ labelOverlap /= 2.0f;
++ if (labelOverlap > 0.0f) {
++ if (label == 0)
++ labelTrans.setX(labelTrans.x() + labelOverlap);
++ else
++ labelTrans.setX(labelTrans.x() - labelOverlap);
++ }
++ }
++ m_dummyRenderItem.setTranslation(labelTrans);
++
++ if (drawSelection) {
++ QVector4D labelColor = QVector4D(0.0f, label / 255.0f, 0.0f,
++ alphaForColumnSelection);
++ shader->setUniformValue(shader->color(), labelColor);
++ }
++
++ m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
++ positionZComp, totalRotation, 0, m_cachedSelectionMode,
++ shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, alignment, false, drawSelection);
++ labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width()));
++ }
++ if (!drawSelection && m_axisCacheX.isTitleVisible()) {
++ labelTrans.setX(0.0f);
++ bool radial = false;
++ if (m_polarGraph) {
++ if (m_xFlipped == m_zFlipped)
++ totalRotation *= m_zRightAngleRotation;
++ else
++ totalRotation *= m_zRightAngleRotationNeg;
++ if (m_yFlippedForGrid)
++ totalRotation *= QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, -180.0f);
++ labelTrans.setZ(-m_polarRadius);
++ radial = true;
++ }
++ drawAxisTitleX(labelRotation, labelTrans, totalRotation, m_dummyRenderItem,
++ activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader,
++ radial);
++ }
++ }
++ // Y Labels
++ if (m_axisCacheY.segmentCount() > 0) {
++ labelsMaxWidth = 0.0f;
++ labelAutoAngle = m_axisCacheY.labelAutoRotation();
++ labelAngleFraction = labelAutoAngle / 90.0f;
++ fractionCamY = activeCamera->yRotation() * labelAngleFraction;
++ fractionCamX = activeCamera->xRotation() * labelAngleFraction;
++ int labelCount = m_axisCacheY.labelCount();
++
++ float labelXTrans = m_scaleXWithBackground;
++ float labelZTrans = m_scaleZWithBackground;
++
++ // Back & side wall
++ float labelMarginXTrans = labelMargin;
++ float labelMarginZTrans = labelMargin;
++ QVector3D backLabelRotation(0.0f, -90.0f, 0.0f);
++ QVector3D sideLabelRotation(0.0f, 0.0f, 0.0f);
++ Qt::AlignmentFlag backAlignment =
++ (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
++ Qt::AlignmentFlag sideAlignment =
++ (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight;
++ if (!m_xFlipped) {
++ labelXTrans = -labelXTrans;
++ labelMarginXTrans = -labelMargin;
++ }
++ if (m_zFlipped) {
++ labelZTrans = -labelZTrans;
++ labelMarginZTrans = -labelMargin;
++ }
++ if (labelAutoAngle == 0.0f) {
++ if (!m_xFlipped)
++ backLabelRotation.setY(90.0f);
++ if (m_zFlipped)
++ sideLabelRotation.setY(180.f);
++ } else {
++ // Orient side labels somewhat towards the camera
++ if (m_xFlipped) {
++ if (m_zFlipped)
++ sideLabelRotation.setY(180.0f + (2.0f * labelAutoAngle) - fractionCamX);
++ else
++ sideLabelRotation.setY(-fractionCamX);
++ backLabelRotation.setY(-90.0f + labelAutoAngle - fractionCamX);
++ } else {
++ if (m_zFlipped)
++ sideLabelRotation.setY(180.0f - (2.0f * labelAutoAngle) - fractionCamX);
++ else
++ sideLabelRotation.setY(-fractionCamX);
++ backLabelRotation.setY(90.0f - labelAutoAngle - fractionCamX);
++ }
++ }
++ sideLabelRotation.setX(-fractionCamY);
++ backLabelRotation.setX(-fractionCamY);
++
++ QQuaternion totalSideRotation = Utils::calculateRotation(sideLabelRotation);
++ QQuaternion totalBackRotation = Utils::calculateRotation(backLabelRotation);
++
++ QVector3D labelTransBack = QVector3D(labelXTrans, 0.0f, labelZTrans + labelMarginZTrans);
++ QVector3D labelTransSide(-labelXTrans - labelMarginXTrans, 0.0f, -labelZTrans);
++
++ if (m_yFlipped) {
++ startIndex = labelCount - 1;
++ endIndex = -1;
++ indexStep = -1;
++ } else {
++ startIndex = 0;
++ endIndex = labelCount;
++ indexStep = 1;
++ }
++ float offsetValue = 0.0f;
++ for (int label = startIndex; label != endIndex; label = label + indexStep) {
++ const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(label);
++ float labelYTrans = m_axisCacheY.labelPosition(label);
++
++ glPolygonOffset(offsetValue++ / -10.0f, 1.0f);
++
++ if (drawSelection) {
++ QVector4D labelColor = QVector4D(0.0f, 0.0f, label / 255.0f,
++ alphaForValueSelection);
++ shader->setUniformValue(shader->color(), labelColor);
++ }
++
++ if (label == startIndex) {
++ // If the margin is small, adjust the position of the edge label to avoid
++ // overlapping with labels of the other axes.
++ float scaleFactor = m_drawer->scaledFontSize() / axisLabelItem.size().height();
++ float labelOverlap = qAbs(labelYTrans)
++ + (scaleFactor * axisLabelItem.size().height() / 2.0f)
++ - m_scaleYWithBackground + labelMargin;
++ if (labelOverlap > 0.0f) {
++ if (label == 0)
++ labelYTrans += labelOverlap;
++ else
++ labelYTrans -= labelOverlap;
++ }
++ }
++
++ // Back wall
++ labelTransBack.setY(labelYTrans);
++ m_dummyRenderItem.setTranslation(labelTransBack);
++ m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
++ positionZComp, totalBackRotation, 0, m_cachedSelectionMode,
++ shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, backAlignment, false,
++ drawSelection);
++
++ // Side wall
++ labelTransSide.setY(labelYTrans);
++ m_dummyRenderItem.setTranslation(labelTransSide);
++ m_drawer->drawLabel(m_dummyRenderItem, axisLabelItem, viewMatrix, projectionMatrix,
++ positionZComp, totalSideRotation, 0, m_cachedSelectionMode,
++ shader, m_labelObj, activeCamera,
++ true, true, Drawer::LabelMid, sideAlignment, false,
++ drawSelection);
++ labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width()));
++ }
++ if (!drawSelection && m_axisCacheY.isTitleVisible()) {
++ labelTransSide.setY(0.0f);
++ labelTransBack.setY(0.0f);
++ drawAxisTitleY(sideLabelRotation, backLabelRotation, labelTransSide, labelTransBack,
++ totalSideRotation, totalBackRotation, m_dummyRenderItem, activeCamera,
++ labelsMaxWidth, viewMatrix, projectionMatrix,
++ shader);
++ }
++ }
++ glDisable(GL_POLYGON_OFFSET_FILL);
++
++ if (!drawSelection)
++ glDisable(GL_BLEND);
++ }
++
++ void Surface3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode)
++ {
++ Abstract3DRenderer::updateSelectionMode(mode);
++
++ if (m_cachedSelectionMode > QAbstract3DGraph::SelectionNone)
++ updateSelectionTextures();
++ }
++
++ void Surface3DRenderer::updateSelectionTextures()
++ {
++ uint lastSelectionId = 1;
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ GLuint texture = cache->selectionTexture();
++ m_textureHelper->deleteTexture(&texture);
++ createSelectionTexture(cache, lastSelectionId);
++ }
++ m_selectionTexturesDirty = false;
++ }
++
++ void Surface3DRenderer::createSelectionTexture(SurfaceSeriesRenderCache *cache,
++ uint &lastSelectionId)
++ {
++ // Create the selection ID image. Each grid corner gets 1 pixel area of
++ // ID color so that each vertex (data point) has 2x2 pixel area of ID color,
++ // except the vertices on the edges.
++ const QRect &sampleSpace = cache->sampleSpace();
++ int idImageWidth = (sampleSpace.width() - 1) * 2;
++ int idImageHeight = (sampleSpace.height() - 1) * 2;
++
++ if (idImageHeight <= 0 || idImageWidth <= 0) {
++ cache->setSelectionIdRange(~0U, ~0U);
++ cache->setSelectionTexture(0);
++ return;
++ }
++
++ int stride = idImageWidth * 4 * sizeof(uchar); // 4 = number of color components (rgba)
++
++ uint idStart = lastSelectionId;
++ uchar *bits = new uchar[idImageWidth * idImageHeight * 4 * sizeof(uchar)];
++ for (int i = 0; i < idImageHeight; i += 2) {
++ for (int j = 0; j < idImageWidth; j += 2) {
++ int p = (i * idImageWidth + j) * 4;
++ uchar r, g, b, a;
++ idToRGBA(lastSelectionId, &r, &g, &b, &a);
++ fillIdCorner(&bits[p], r, g, b, a);
++
++ idToRGBA(lastSelectionId + 1, &r, &g, &b, &a);
++ fillIdCorner(&bits[p + 4], r, g, b, a);
++
++ idToRGBA(lastSelectionId + sampleSpace.width(), &r, &g, &b, &a);
++ fillIdCorner(&bits[p + stride], r, g, b, a);
++
++ idToRGBA(lastSelectionId + sampleSpace.width() + 1, &r, &g, &b, &a);
++ fillIdCorner(&bits[p + stride + 4], r, g, b, a);
++
++ lastSelectionId++;
++ }
++ lastSelectionId++;
++ }
++ lastSelectionId += sampleSpace.width();
++ cache->setSelectionIdRange(idStart, lastSelectionId - 1);
++
++ // Move the ID image (bits) to the texture
++ QImage image = QImage(bits, idImageWidth, idImageHeight, QImage::Format_RGB32);
++ GLuint selectionTexture = m_textureHelper->create2DTexture(image, false, false, false);
++ cache->setSelectionTexture(selectionTexture);
++
++ // Release the temp bits allocation
++ delete[] bits;
++ }
++
++ void Surface3DRenderer::initSelectionBuffer()
++ {
++ // Create the result selection texture and buffers
++ m_textureHelper->deleteTexture(&m_selectionResultTexture);
++
++ m_selectionResultTexture = m_textureHelper->createSelectionTexture(m_primarySubViewport.size(),
++ m_selectionFrameBuffer,
++ m_selectionDepthBuffer);
++ }
++
++ void Surface3DRenderer::fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a)
++ {
++ p[0] = r;
++ p[1] = g;
++ p[2] = b;
++ p[3] = a;
++ }
++
++ void Surface3DRenderer::idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a)
++ {
++ *r = id & ID_TO_RGBA_MASK;
++ *g = (id >> 8) & ID_TO_RGBA_MASK;
++ *b = (id >> 16) & ID_TO_RGBA_MASK;
++ *a = (id >> 24) & ID_TO_RGBA_MASK;
++ }
++
++ void Surface3DRenderer::calculateSceneScalingFactors()
++ {
++ // Margin for background (the default 0.10 makes it 10% larger to avoid
++ // selection ball being drawn inside background)
++ if (m_requestedMargin < 0.0f) {
++ m_hBackgroundMargin = 0.1f;
++ m_vBackgroundMargin = 0.1f;
++ } else {
++ m_hBackgroundMargin = m_requestedMargin;
++ m_vBackgroundMargin = m_requestedMargin;
++ }
++ if (m_polarGraph) {
++ float polarMargin = calculatePolarBackgroundMargin();
++ m_hBackgroundMargin = qMax(m_hBackgroundMargin, polarMargin);
++ }
++
++ // Calculate scene scaling and translation factors
++ m_heightNormalizer = GLfloat(m_axisCacheY.max() - m_axisCacheY.min());
++
++ float horizontalAspectRatio;
++ if (m_polarGraph)
++ horizontalAspectRatio = 1.0f;
++ else
++ horizontalAspectRatio = m_graphHorizontalAspectRatio;
++
++ QSizeF areaSize;
++ if (horizontalAspectRatio == 0.0f) {
++ areaSize.setHeight(m_axisCacheZ.max() - m_axisCacheZ.min());
++ areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min());
++ } else {
++ areaSize.setHeight(1.0f);
++ areaSize.setWidth(horizontalAspectRatio);
++ }
++
++ float horizontalMaxDimension;
++ if (m_graphAspectRatio > 2.0f) {
++ horizontalMaxDimension = 2.0f;
++ m_scaleY = 2.0f / m_graphAspectRatio;
++ } else {
++ horizontalMaxDimension = m_graphAspectRatio;
++ m_scaleY = 1.0f;
++ }
++ if (m_polarGraph)
++ m_polarRadius = horizontalMaxDimension;
++
++ float scaleFactor = qMax(areaSize.width(), areaSize.height());
++ m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
++ m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
++
++ m_scaleXWithBackground = m_scaleX + m_hBackgroundMargin;
++ m_scaleYWithBackground = m_scaleY + m_vBackgroundMargin;
++ m_scaleZWithBackground = m_scaleZ + m_hBackgroundMargin;
++
++ m_axisCacheX.setScale(m_scaleX * 2.0f);
++ m_axisCacheY.setScale(m_scaleY * 2.0f);
++ m_axisCacheZ.setScale(-m_scaleZ * 2.0f);
++ m_axisCacheX.setTranslate(-m_scaleX);
++ m_axisCacheY.setTranslate(-m_scaleY);
++ m_axisCacheZ.setTranslate(m_scaleZ);
++
++ updateCameraViewport();
++ updateCustomItemPositions();
++ }
++
++ void Surface3DRenderer::checkFlatSupport(SurfaceSeriesRenderCache *cache)
++ {
++ bool flatEnable = cache->isFlatShadingEnabled();
++ if (flatEnable && !m_flatSupported) {
++ qWarning() << "Warning: Flat qualifier not supported on your platform's GLSL language."
++ " Requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension.";
++ cache->setFlatShadingEnabled(false);
++ cache->setFlatChangeAllowed(false);
++ }
++ }
++
++ void Surface3DRenderer::updateObjects(SurfaceSeriesRenderCache *cache, bool dimensionChanged)
++ {
++ QSurfaceDataArray &dataArray = cache->dataArray();
++ const QRect &sampleSpace = cache->sampleSpace();
++
++ const QSurface3DSeries *currentSeries = cache->series();
++ QSurfaceDataProxy *dataProxy = currentSeries->dataProxy();
++ const QSurfaceDataArray &array = *dataProxy->array();
++
++ if (cache->isFlatShadingEnabled()) {
++ cache->surfaceObject()->setUpData(dataArray, sampleSpace, dimensionChanged, m_polarGraph);
++ if (cache->surfaceTexture())
++ cache->surfaceObject()->coarseUVs(array, dataArray);
++ } else {
++ cache->surfaceObject()->setUpSmoothData(dataArray, sampleSpace, dimensionChanged,
++ m_polarGraph);
++ if (cache->surfaceTexture())
++ cache->surfaceObject()->smoothUVs(array, dataArray);
++ }
++ }
++
++ void Surface3DRenderer::updateSelectedPoint(const QPoint &position, QSurface3DSeries *series)
++ {
++ m_selectedPoint = position;
++ m_selectedSeries = series;
++ m_selectionDirty = true;
++ }
++
++ void Surface3DRenderer::updateFlipHorizontalGrid(bool flip)
++ {
++ m_flipHorizontalGrid = flip;
++ }
++
++ void Surface3DRenderer::resetClickedStatus()
++ {
++ m_clickedPosition = Surface3DController::invalidSelectionPosition();
++ m_clickedSeries = 0;
++ }
++
++ void Surface3DRenderer::loadBackgroundMesh()
++ {
++ ObjectHelper::resetObjectHelper(this, m_backgroundObj,
++ QStringLiteral(":/defaultMeshes/background"));
++ }
++
++ void Surface3DRenderer::surfacePointSelected(const QPoint &point)
++ {
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ cache->setSlicePointerActivity(false);
++ cache->setMainPointerActivity(false);
++ }
++
++ if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionMultiSeries)) {
++ // Find axis coordinates for the selected point
++ SurfaceSeriesRenderCache *selectedCache =
++ static_cast<SurfaceSeriesRenderCache *>(
++ m_renderCacheList.value(const_cast<QSurface3DSeries *>(m_selectedSeries)));
++ QSurfaceDataArray &dataArray = selectedCache->dataArray();
++ QSurfaceDataItem item = dataArray.at(point.x())->at(point.y());
++ QPointF coords(item.x(), item.z());
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->series() != m_selectedSeries) {
++ QPoint mappedPoint = mapCoordsToSampleSpace(cache, coords);
++ updateSelectionPoint(cache, mappedPoint, false);
++ } else {
++ updateSelectionPoint(cache, point, true);
++ }
++ }
++ } else {
++ if (m_selectedSeries) {
++ SurfaceSeriesRenderCache *cache =
++ static_cast<SurfaceSeriesRenderCache *>(
++ m_renderCacheList.value(const_cast<QSurface3DSeries *>(m_selectedSeries)));
++ if (cache)
++ updateSelectionPoint(cache, point, true);
++ }
++ }
++ }
++
++ void Surface3DRenderer::updateSelectionPoint(SurfaceSeriesRenderCache *cache, const QPoint &point,
++ bool label)
++ {
++ int row = point.x();
++ int column = point.y();
++
++ if (column < 0 || row < 0)
++ return;
++
++ SelectionPointer *slicePointer = cache->sliceSelectionPointer();
++ if (!slicePointer && m_cachedIsSlicingActivated) {
++ slicePointer = new SelectionPointer(m_drawer);
++ cache->setSliceSelectionPointer(slicePointer);
++ }
++ SelectionPointer *mainPointer = cache->mainSelectionPointer();
++ if (!mainPointer) {
++ mainPointer = new SelectionPointer(m_drawer);
++ cache->setMainSelectionPointer(mainPointer);
++ }
++
++ QString selectionLabel;
++ if (label) {
++ m_selectionLabelDirty = false;
++ selectionLabel = cache->itemLabel();
++ }
++
++ if (m_cachedIsSlicingActivated) {
++ QVector3D subPosFront;
++ QVector3D subPosBack;
++ if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow)) {
++ subPosFront = cache->sliceSurfaceObject()->vertexAt(column, 0);
++ subPosBack = cache->sliceSurfaceObject()->vertexAt(column, 1);
++ } else if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) {
++ subPosFront = cache->sliceSurfaceObject()->vertexAt(row, 0);
++ subPosBack = cache->sliceSurfaceObject()->vertexAt(row, 1);
++ }
++ slicePointer->updateBoundingRect(m_secondarySubViewport);
++ slicePointer->updateSliceData(true, m_autoScaleAdjustment);
++ slicePointer->setPosition((subPosFront + subPosBack) / 2.0f);
++ slicePointer->setLabel(selectionLabel);
++ slicePointer->setPointerObject(cache->object());
++ slicePointer->setLabelObject(m_labelObj);
++ slicePointer->setHighlightColor(cache->singleHighlightColor());
++ slicePointer->updateScene(m_cachedScene);
++ slicePointer->setRotation(cache->meshRotation());
++ cache->setSlicePointerActivity(true);
++ }
++
++ QVector3D mainPos;
++ mainPos = cache->surfaceObject()->vertexAt(column, row);
++ mainPointer->updateBoundingRect(m_primarySubViewport);
++ mainPointer->updateSliceData(false, m_autoScaleAdjustment);
++ mainPointer->setPosition(mainPos);
++ mainPointer->setLabel(selectionLabel);
++ mainPointer->setPointerObject(cache->object());
++ mainPointer->setLabelObject(m_labelObj);
++ mainPointer->setHighlightColor(cache->singleHighlightColor());
++ mainPointer->updateScene(m_cachedScene);
++ mainPointer->setRotation(cache->meshRotation());
++ cache->setMainPointerActivity(true);
++ }
++
++ // Maps selection Id to surface point in data array
++ QPoint Surface3DRenderer::selectionIdToSurfacePoint(uint id)
++ {
++ m_clickedType = QAbstract3DGraph::ElementNone;
++ m_selectedLabelIndex = -1;
++ m_selectedCustomItemIndex = -1;
++ // Check for label and custom item selection
++ if (id / alphaMultiplier == labelRowAlpha) {
++ m_selectedLabelIndex = id - (alphaMultiplier * uint(labelRowAlpha));
++ m_clickedType = QAbstract3DGraph::ElementAxisZLabel;
++ return Surface3DController::invalidSelectionPosition();
++ } else if (id / alphaMultiplier == labelColumnAlpha) {
++ m_selectedLabelIndex = (id - (alphaMultiplier * uint(labelColumnAlpha))) / greenMultiplier;
++ m_clickedType = QAbstract3DGraph::ElementAxisXLabel;
++ return Surface3DController::invalidSelectionPosition();
++ } else if (id / alphaMultiplier == labelValueAlpha) {
++ m_selectedLabelIndex = (id - (alphaMultiplier * uint(labelValueAlpha))) / blueMultiplier;
++ m_clickedType = QAbstract3DGraph::ElementAxisYLabel;
++ return Surface3DController::invalidSelectionPosition();
++ } else if (id / alphaMultiplier == customItemAlpha) {
++ // Custom item selection
++ m_clickedType = QAbstract3DGraph::ElementCustomItem;
++ m_selectedCustomItemIndex = id - (alphaMultiplier * uint(customItemAlpha));
++ return Surface3DController::invalidSelectionPosition();
++ }
++
++ // Not a label selection
++ SurfaceSeriesRenderCache *selectedCache = 0;
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->isWithinIdRange(id)) {
++ selectedCache = cache;
++ break;
++ }
++ }
++ if (!selectedCache) {
++ m_clickedSeries = 0;
++ return Surface3DController::invalidSelectionPosition();
++ }
++
++ uint idInSeries = id - selectedCache->selectionIdStart() + 1;
++ const QRect &sampleSpace = selectedCache->sampleSpace();
++ int column = ((idInSeries - 1) % sampleSpace.width()) + sampleSpace.x();
++ int row = ((idInSeries - 1) / sampleSpace.width()) + sampleSpace.y();
++
++ m_clickedSeries = selectedCache->series();
++ m_clickedType = QAbstract3DGraph::ElementSeries;
++ return QPoint(row, column);
++ }
++
++ void Surface3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality quality)
++ {
++ m_cachedShadowQuality = quality;
++
++ switch (quality) {
++ case QAbstract3DGraph::ShadowQualityLow:
++ m_shadowQualityToShader = 33.3f;
++ m_shadowQualityMultiplier = 1;
++ break;
++ case QAbstract3DGraph::ShadowQualityMedium:
++ m_shadowQualityToShader = 100.0f;
++ m_shadowQualityMultiplier = 3;
++ break;
++ case QAbstract3DGraph::ShadowQualityHigh:
++ m_shadowQualityToShader = 200.0f;
++ m_shadowQualityMultiplier = 5;
++ break;
++ case QAbstract3DGraph::ShadowQualitySoftLow:
++ m_shadowQualityToShader = 5.0f;
++ m_shadowQualityMultiplier = 1;
++ break;
++ case QAbstract3DGraph::ShadowQualitySoftMedium:
++ m_shadowQualityToShader = 10.0f;
++ m_shadowQualityMultiplier = 3;
++ break;
++ case QAbstract3DGraph::ShadowQualitySoftHigh:
++ m_shadowQualityToShader = 15.0f;
++ m_shadowQualityMultiplier = 4;
++ break;
++ default:
++ m_shadowQualityToShader = 0.0f;
++ m_shadowQualityMultiplier = 1;
++ break;
++ }
++
++ handleShadowQualityChange();
++
++ updateDepthBuffer();
++ }
++
++ void Surface3DRenderer::updateTextures()
++ {
++ Abstract3DRenderer::updateTextures();
++
++ if (m_polarGraph)
++ calculateSceneScalingFactors();
++ }
++
++ void Surface3DRenderer::updateSlicingActive(bool isSlicing)
++ {
++ if (m_cachedIsSlicingActivated == isSlicing)
++ 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;
++
++ for (auto baseCache: m_renderCacheList) {
++ SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache);
++ if (cache->mainSelectionPointer())
++ cache->mainSelectionPointer()->updateBoundingRect(m_primarySubViewport);
++ }
++ }
++
++ void Surface3DRenderer::initShaders(const QString &vertexShader, const QString &fragmentShader)
++ {
++ Q_UNUSED(vertexShader);
++ Q_UNUSED(fragmentShader);
++
++ delete m_surfaceFlatShader;
++ delete m_surfaceSmoothShader;
++ delete m_surfaceTexturedSmoothShader;
++ delete m_surfaceTexturedFlatShader;
++ delete m_surfaceSliceFlatShader;
++ delete m_surfaceSliceSmoothShader;
++
++ if (!m_isOpenGLES) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentSurfaceShadowNoTex"));
++ m_surfaceTexturedSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexShadow"),
++ QStringLiteral(":/shaders/fragmentTexturedSurfaceShadow"));
++ } else {
++ m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentSurface"));
++ m_surfaceTexturedSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTexture"));
++ }
++ m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentSurface"));
++ if (m_flatSupported) {
++ if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) {
++ m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceShadowFlat"),
++ QStringLiteral(":/shaders/fragmentSurfaceShadowFlat"));
++ m_surfaceTexturedFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceShadowFlat"),
++ QStringLiteral(":/shaders/fragmentTexturedSurfaceShadowFlat"));
++ } else {
++ m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"),
++ QStringLiteral(":/shaders/fragmentSurfaceFlat"));
++ m_surfaceTexturedFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"),
++ QStringLiteral(":/shaders/fragmentSurfaceTexturedFlat"));
++ }
++ m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"),
++ QStringLiteral(":/shaders/fragmentSurfaceFlat"));
++ } else {
++ m_surfaceFlatShader = 0;
++ m_surfaceSliceFlatShader = 0;
++ m_surfaceTexturedFlatShader = 0;
++ }
++ } else {
++ m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentSurfaceES2"));
++ m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentSurfaceES2"));
++ m_surfaceTexturedSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTextureES2"));
++ m_surfaceTexturedFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexTexture"),
++ QStringLiteral(":/shaders/fragmentTextureES2"));
++ m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentSurfaceES2"));
++ m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"),
++ QStringLiteral(":/shaders/fragmentSurfaceES2"));
++ }
++
++ m_surfaceSmoothShader->initialize();
++ m_surfaceSliceSmoothShader->initialize();
++ m_surfaceTexturedSmoothShader->initialize();
++ if (m_flatSupported) {
++ m_surfaceFlatShader->initialize();
++ m_surfaceSliceFlatShader->initialize();
++ m_surfaceTexturedFlatShader->initialize();
++ }
++ }
++
++ void Surface3DRenderer::initBackgroundShaders(const QString &vertexShader,
++ const QString &fragmentShader)
++ {
++ if (m_backgroundShader)
++ delete m_backgroundShader;
++ m_backgroundShader = new ShaderHelper(this, vertexShader, fragmentShader);
++ m_backgroundShader->initialize();
++ }
++
++ void Surface3DRenderer::initSelectionShaders()
++ {
++ if (m_selectionShader)
++ delete m_selectionShader;
++ m_selectionShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexLabel"),
++ QStringLiteral(":/shaders/fragmentLabel"));
++ m_selectionShader->initialize();
++ }
++
++ void Surface3DRenderer::initSurfaceShaders()
++ {
++ // Gridline shader
++ if (m_surfaceGridShader)
++ delete m_surfaceGridShader;
++ m_surfaceGridShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPlainColor"),
++ QStringLiteral(":/shaders/fragmentPlainColor"));
++ m_surfaceGridShader->initialize();
++
++ // Triggers surface shader selection by shadow setting
++ handleShadowQualityChange();
++ }
++
++ void Surface3DRenderer::initDepthShader()
++ {
++ if (!m_isOpenGLES) {
++ delete m_depthShader;
++ m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"),
++ QStringLiteral(":/shaders/fragmentDepth"));
++ m_depthShader->initialize();
++ }
++ }
++
++ void Surface3DRenderer::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();
++ }
++ }
++ }
++
++ QVector3D Surface3DRenderer::convertPositionToTranslation(const QVector3D &position,
++ bool isAbsolute)
++ {
++ float xTrans = 0.0f;
++ float yTrans = 0.0f;
++ float zTrans = 0.0f;
++ if (!isAbsolute) {
++ if (m_polarGraph) {
++ calculatePolarXZ(position, xTrans, zTrans);
++ } else {
++ xTrans = m_axisCacheX.positionAt(position.x());
++ zTrans = m_axisCacheZ.positionAt(position.z());
++ }
++ yTrans = m_axisCacheY.positionAt(position.y());
++ } else {
++ xTrans = position.x() * m_scaleX;
++ yTrans = position.y() * m_scaleY;
++ zTrans = position.z() * -m_scaleZ;
++ }
++ return QVector3D(xTrans, yTrans, zTrans);
++ }
++
++ void Surface3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation,
++ const QStringList &labels)
++ {
++ Abstract3DRenderer::updateAxisLabels(orientation, labels);
++
++ // Angular axis label dimensions affect the chart dimensions
++ if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX)
++ calculateSceneScalingFactors();
++ }
++
++ void Surface3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, bool visible)
++ {
++ Abstract3DRenderer::updateAxisTitleVisibility(orientation, visible);
++
++ // Angular axis title existence affects the chart dimensions
++ if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX)
++ calculateSceneScalingFactors();
++ }
++
++ void Surface3DRenderer::updateMargin(float margin)
++ {
++ Abstract3DRenderer::updateMargin(margin);
++ calculateSceneScalingFactors();
++ }
++
++ QT_END_NAMESPACE