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