blob: be7ccf6631b1653627e4b7f67e03a4ac1931e5c1 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Data Visualization module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "surface3drenderer_p.h"
#include "q3dcamera_p.h"
#include "shaderhelper_p.h"
#include "texturehelper_p.h"
#include "utils_p.h"
#include <QtCore/qmath.h>
static const int ID_TO_RGBA_MASK = 0xff;
QT_BEGIN_NAMESPACE_DATAVISUALIZATION
//#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();
foreach (SeriesRenderCache *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;
foreach (QAbstract3DSeries *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) {
foreach (SeriesRenderCache *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(QVector<QSurface3DSeries *> seriesList)
{
foreach (QSurface3DSeries *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 QVector<Surface3DController::ChangeRow> &rows)
{
foreach (Surface3DController::ChangeRow 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 QVector<Surface3DController::ChangeItem> &points)
{
foreach (Surface3DController::ChangeItem 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)
{
foreach (SeriesRenderCache *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());
foreach (SeriesRenderCache *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 (!::qFuzzyCompare(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 (!::qFuzzyCompare(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;
foreach (SeriesRenderCache *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()));
foreach (SeriesRenderCache *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);
foreach (SeriesRenderCache *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);
foreach (SeriesRenderCache *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;
foreach (SeriesRenderCache *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()));
foreach (SeriesRenderCache *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;
QVector<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;
foreach (SeriesRenderCache *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)
{
foreach (SeriesRenderCache *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());
foreach (SeriesRenderCache *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;
foreach (SeriesRenderCache *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;
foreach (SeriesRenderCache *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_DATAVISUALIZATION