diff options
Diffstat (limited to 'src/datavisualization')
128 files changed, 9643 insertions, 2737 deletions
diff --git a/src/datavisualization/axis/axis.pri b/src/datavisualization/axis/axis.pri index 0173b597..4c142c91 100644 --- a/src/datavisualization/axis/axis.pri +++ b/src/datavisualization/axis/axis.pri @@ -16,3 +16,5 @@ SOURCES += \ $$PWD/qcategory3daxis.cpp \ $$PWD/qvalue3daxisformatter.cpp \ $$PWD/qlogvalue3daxisformatter.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/axis/qlogvalue3daxisformatter.cpp b/src/datavisualization/axis/qlogvalue3daxisformatter.cpp index 7367e7c5..85fd5c4f 100644 --- a/src/datavisualization/axis/qlogvalue3daxisformatter.cpp +++ b/src/datavisualization/axis/qlogvalue3daxisformatter.cpp @@ -42,6 +42,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \since QtDataVisualization 1.1 * \ingroup datavisualization_qml * \instantiates QLogValue3DAxisFormatter + * \inherits ValueAxis3DFormatter * \brief LogValueAxis3DFormatter implements logarithmic value axis formatter. * * This type provides formatting rules for a logarithmic ValueAxis3D. diff --git a/src/datavisualization/axis/qvalue3daxis.cpp b/src/datavisualization/axis/qvalue3daxis.cpp index 8207174f..3b9c9e3d 100644 --- a/src/datavisualization/axis/qvalue3daxis.cpp +++ b/src/datavisualization/axis/qvalue3daxis.cpp @@ -18,6 +18,7 @@ #include "qvalue3daxis_p.h" #include "qvalue3daxisformatter_p.h" +#include "abstract3dcontroller_p.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -68,8 +69,16 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty string ValueAxis3D::labelFormat * - * Defines the label format to be used for the labels on this axis. Supported specifiers are: + * Defines the label format to be used for the labels on this axis. How the format is interpreted + * depends on the axis formatter and the locale in use. Using the default formatter and default + * locale (\c{"C"}), the formatting uses QString::sprintf(). Supported specifiers are: * \c {d, i, o, x, X, f, F, e, E, g, G, c}. See QString::sprintf() for additional details. + * For other locales, the default formatter uses reduced set of printf format specifiers: + * \c {d, i, f, F, e, E, g, G}. In these cases, the only supported modifier is the precision + * modifier for the floating point and exponential formats. The decimal point and other locale + * dependent formatting is done according to the graph locale. + * + * \sa AbstractGraph3D::locale */ /*! @@ -164,12 +173,20 @@ int QValue3DAxis::subSegmentCount() const /*! * \property QValue3DAxis::labelFormat * - * Defines the label \a format to be used for the labels on this axis. Supported specifiers are: + * Defines the label format to be used for the labels on this axis. How the format is interpreted + * depends on the axis formatter and the locale in use. Using the default formatter and default + * locale (\c{"C"}), the formatting uses QString::sprintf(). Supported specifiers are: * \c {d, i, o, x, X, f, F, e, E, g, G, c}. See QString::sprintf() for additional details. + * For other locales, the default formatter uses reduced set of printf format specifiers: + * \c {d, i, f, F, e, E, g, G}. In these cases, the only supported modifier is the precision + * modifier for the floating point and exponential formats. The decimal point and other locale + * dependent formatting is done according to the graph locale. * * Usage example: * * \c {axis->setLabelFormat("%.2f mm");} + * + * \sa formatter, QAbstract3DGraph::locale */ void QValue3DAxis::setLabelFormat(const QString &format) { @@ -201,6 +218,9 @@ void QValue3DAxis::setFormatter(QValue3DAxisFormatter *formatter) dptr()->m_formatter = formatter; formatter->setParent(this); formatter->d_ptr->setAxis(this); + Abstract3DController *controller = qobject_cast<Abstract3DController *>(parent()); + if (controller) + formatter->setLocale(controller->locale()); emit formatterChanged(formatter); emit dptr()->formatterDirty(); } diff --git a/src/datavisualization/axis/qvalue3daxisformatter.cpp b/src/datavisualization/axis/qvalue3daxisformatter.cpp index 56ca3b0f..f6b705a9 100644 --- a/src/datavisualization/axis/qvalue3daxisformatter.cpp +++ b/src/datavisualization/axis/qvalue3daxisformatter.cpp @@ -270,6 +270,28 @@ QStringList &QValue3DAxisFormatter::labelStrings() const return d_ptr->m_labelStrings; } +/*! + * Sets the \a locale that this formatter uses. + * The graph automatically sets the formatter's locale to a graph's locale whenever the parent axis + * is set as an active axis of the graph, the axis formatter is set to an axis attached to + * the graph, or the graph's locale changes. + * + * \sa locale(), QAbstract3DGraph::locale + */ +void QValue3DAxisFormatter::setLocale(const QLocale &locale) +{ + d_ptr->m_cLocaleInUse = (locale == QLocale::c()); + d_ptr->m_locale = locale; + markDirty(true); +} +/*! + * \return the current locale this formatter is using. + */ +QLocale QValue3DAxisFormatter::locale() const +{ + return d_ptr->m_locale; +} + // QValue3DAxisFormatterPrivate QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate(QValue3DAxisFormatter *q) : QObject(0), @@ -281,7 +303,10 @@ QValue3DAxisFormatterPrivate::QValue3DAxisFormatterPrivate(QValue3DAxisFormatter m_axis(0), m_preparsedParamType(Utils::ParamTypeUnknown), m_allowNegatives(true), - m_allowZero(true) + m_allowZero(true), + m_formatPrecision(6), // 6 and 'g' are defaults in Qt API for format precision and spec + m_formatSpec('g'), + m_cLocaleInUse(true) { } @@ -363,12 +388,19 @@ QString QValue3DAxisFormatterPrivate::stringForValue(qreal value, const QString { if (m_previousLabelFormat.compare(format)) { // Format string different than the previous one used, reparse it - m_previousLabelFormat = format; - m_preparsedParamType = Utils::findFormatParamType(format); m_labelFormatArray = format.toUtf8(); + m_previousLabelFormat = format; + m_preparsedParamType = Utils::preParseFormat(format, m_formatPreStr, m_formatPostStr, + m_formatPrecision, m_formatSpec); } - return Utils::formatLabel(m_labelFormatArray, m_preparsedParamType, value); + if (m_cLocaleInUse) { + return Utils::formatLabelSprintf(m_labelFormatArray, m_preparsedParamType, value); + } else { + return Utils::formatLabelLocalized(m_preparsedParamType, value, m_locale, m_formatPreStr, + m_formatPostStr, m_formatPrecision, m_formatSpec, + m_labelFormatArray); + } } float QValue3DAxisFormatterPrivate::positionAt(float value) const diff --git a/src/datavisualization/axis/qvalue3daxisformatter.h b/src/datavisualization/axis/qvalue3daxisformatter.h index c7b0bac5..82e49f21 100644 --- a/src/datavisualization/axis/qvalue3daxisformatter.h +++ b/src/datavisualization/axis/qvalue3daxisformatter.h @@ -24,6 +24,7 @@ #include <QtCore/QScopedPointer> #include <QtCore/QVector> #include <QtCore/QStringList> +#include <QtCore/QLocale> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -60,6 +61,9 @@ protected: QVector<float> &labelPositions() const; QStringList &labelStrings() const; + void setLocale(const QLocale &locale); + QLocale locale() const; + QScopedPointer<QValue3DAxisFormatterPrivate> d_ptr; private: diff --git a/src/datavisualization/axis/qvalue3daxisformatter_p.h b/src/datavisualization/axis/qvalue3daxisformatter_p.h index 2d1dc920..9571d001 100644 --- a/src/datavisualization/axis/qvalue3daxisformatter_p.h +++ b/src/datavisualization/axis/qvalue3daxisformatter_p.h @@ -32,6 +32,7 @@ #include "datavisualizationglobal_p.h" #include "qvalue3daxisformatter.h" #include "utils_p.h" +#include <QtCore/QLocale> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -83,6 +84,13 @@ protected: bool m_allowNegatives; bool m_allowZero; + QLocale m_locale; + QString m_formatPreStr; + QString m_formatPostStr; + int m_formatPrecision; + char m_formatSpec; + bool m_cLocaleInUse; + friend class QValue3DAxisFormatter; }; diff --git a/src/datavisualization/common.pri b/src/datavisualization/common.pri deleted file mode 100644 index 9a497c2c..00000000 --- a/src/datavisualization/common.pri +++ /dev/null @@ -1,7 +0,0 @@ -INCLUDEPATH += $$PWD/engine \ - $$PWD/global \ - $$PWD/utils \ - $$PWD/axis \ - $$PWD/data \ - $$PWD/input \ - $$PWD/theme diff --git a/src/datavisualization/data/abstractrenderitem_p.h b/src/datavisualization/data/abstractrenderitem_p.h index 57977a3c..ccee2b00 100644 --- a/src/datavisualization/data/abstractrenderitem_p.h +++ b/src/datavisualization/data/abstractrenderitem_p.h @@ -48,10 +48,12 @@ public: inline void setTranslation(const QVector3D &translation) { m_translation = translation; } inline const QVector3D &translation() const {return m_translation; } - inline QQuaternion rotation() const { return m_rotation; } + inline const QQuaternion &rotation() const { return m_rotation; } inline void setRotation(const QQuaternion &rotation) { - if (m_rotation != rotation) + if (rotation.isNull()) + m_rotation = identityQuaternion; + else m_rotation = rotation; } diff --git a/src/datavisualization/data/customrenderitem.cpp b/src/datavisualization/data/customrenderitem.cpp index a1c70057..f56ca86e 100644 --- a/src/datavisualization/data/customrenderitem.cpp +++ b/src/datavisualization/data/customrenderitem.cpp @@ -23,9 +23,32 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION CustomRenderItem::CustomRenderItem() : AbstractRenderItem(), m_texture(0), + m_positionAbsolute(false), + m_scalingAbsolute(true), m_object(0), + m_needBlend(true), m_visible(true), - m_renderer(0) + m_valid(true), + m_index(0), + m_shadowCasting(false), + m_isFacingCamera(false), + m_item(0), + m_renderer(0), + m_labelItem(false), + m_textureWidth(0), + m_textureHeight(0), + m_textureDepth(0), + m_isVolume(false), + m_textureFormat(QImage::Format_ARGB32), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true), + m_useHighDefShader(true), + m_drawSlices(false), + m_drawSliceFrames(false) + { } @@ -39,4 +62,47 @@ void CustomRenderItem::setMesh(const QString &meshFile) ObjectHelper::resetObjectHelper(m_renderer, m_object, meshFile); } +void CustomRenderItem::setColorTable(const QVector<QRgb> &colors) +{ + m_colorTable.resize(256); + for (int i = 0; i < 256; i++) { + if (i < colors.size()) { + const QRgb &rgb = colors.at(i); + m_colorTable[i] = QVector4D(float(qRed(rgb)) / 255.0f, + float(qGreen(rgb)) / 255.0f, + float(qBlue(rgb)) / 255.0f, + float(qAlpha(rgb)) / 255.0f); + } else { + m_colorTable[i] = QVector4D(0.0f, 0.0f, 0.0f, 0.0f); + } + } +} + +void CustomRenderItem::setMinBounds(const QVector3D &bounds) +{ + m_minBounds = bounds; + m_minBoundsNormal = m_minBounds; + m_minBoundsNormal.setY(-m_minBoundsNormal.y()); + m_minBoundsNormal.setZ(-m_minBoundsNormal.z()); + m_minBoundsNormal = 0.5f * (m_minBoundsNormal + oneVector); +} + +void CustomRenderItem::setMaxBounds(const QVector3D &bounds) +{ + m_maxBounds = bounds; + m_maxBoundsNormal = m_maxBounds; + m_maxBoundsNormal.setY(-m_maxBoundsNormal.y()); + m_maxBoundsNormal.setZ(-m_maxBoundsNormal.z()); + m_maxBoundsNormal = 0.5f * (m_maxBoundsNormal + oneVector); +} + +void CustomRenderItem::setSliceFrameColor(const QColor &color) +{ + const QRgb &rgb = color.rgba(); + m_sliceFrameColor = QVector4D(float(qRed(rgb)) / 255.0f, + float(qGreen(rgb)) / 255.0f, + float(qBlue(rgb)) / 255.0f, + float(1.0f)); // Alpha not supported for frames +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/customrenderitem_p.h b/src/datavisualization/data/customrenderitem_p.h index 4fb94276..ad868fac 100644 --- a/src/datavisualization/data/customrenderitem_p.h +++ b/src/datavisualization/data/customrenderitem_p.h @@ -31,6 +31,9 @@ #include "abstractrenderitem_p.h" #include "objecthelper_p.h" +#include <QtGui/QRgb> +#include <QtGui/QImage> +#include <QtGui/QColor> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -48,11 +51,17 @@ public: void setMesh(const QString &meshFile); inline ObjectHelper *mesh() const { return m_object; } inline void setScaling(const QVector3D &scaling) { m_scaling = scaling; } - inline QVector3D scaling() const { return m_scaling; } + inline const QVector3D &scaling() const { return m_scaling; } + inline void setOrigScaling(const QVector3D &scaling) { m_origScaling = scaling; } + inline const QVector3D &origScaling() const { return m_origScaling; } inline void setPosition(const QVector3D &position) { m_position = position; } - inline QVector3D position() const { return m_position; } - inline void setPositionAbsolute(bool absolute) { m_absolute = absolute; } - inline bool isPositionAbsolute() const { return m_absolute; } + inline const QVector3D &position() const { return m_position; } + inline void setOrigPosition(const QVector3D &position) { m_origPosition = position; } + inline const QVector3D &origPosition() const { return m_origPosition; } + inline void setPositionAbsolute(bool absolute) { m_positionAbsolute = absolute; } + inline bool isPositionAbsolute() const { return m_positionAbsolute; } + inline void setScalingAbsolute(bool absolute) { m_scalingAbsolute = absolute; } + inline bool isScalingAbsolute() const { return m_scalingAbsolute; } inline void setBlendNeeded(bool blend) { m_needBlend = blend; } inline bool isBlendNeeded() const { return m_needBlend; } inline void setVisible(bool visible) { m_visible = visible; } @@ -68,14 +77,78 @@ public: inline void setFacingCamera(bool facing) { m_isFacingCamera = facing; } inline bool isFacingCamera() const { return m_isFacingCamera; } inline void setRenderer(Abstract3DRenderer *renderer) { m_renderer = renderer; } + inline void setLabelItem(bool isLabel) { m_labelItem = isLabel; } + inline bool isLabel() const { return m_labelItem; } + + // Volume specific + inline void setTextureWidth(int width) { m_textureWidth = width; setSliceIndexX(m_sliceIndexX); } + inline int textureWidth() const { return m_textureWidth; } + inline void setTextureHeight(int height) { m_textureHeight = height; setSliceIndexY(m_sliceIndexY); } + inline int textureHeight() const { return m_textureHeight; } + inline void setTextureDepth(int depth) { m_textureDepth = depth; setSliceIndexZ(m_sliceIndexZ); } + inline int textureDepth() const { return m_textureDepth; } + inline int textureSize() const { return m_textureWidth * m_textureHeight * m_textureDepth; } + inline void setColorTable(const QVector<QVector4D> &colors) { m_colorTable = colors; } + void setColorTable(const QVector<QRgb> &colors); + inline const QVector<QVector4D> &colorTable() const { return m_colorTable; } + inline void setVolume(bool volume) { m_isVolume = volume; } + inline bool isVolume() const { return m_isVolume; } + inline void setTextureFormat(QImage::Format format) { m_textureFormat = format; } + inline QImage::Format textureFormat() const { return m_textureFormat; } + inline void setSliceIndexX(int index) + { + m_sliceIndexX = index; + m_sliceFractions.setX((float(index) + 0.5f) / float(m_textureWidth) * 2.0 - 1.0); + } + inline void setSliceIndexY(int index) + { + m_sliceIndexY = index; + m_sliceFractions.setY((float(index) + 0.5f) / float(m_textureHeight) * 2.0 - 1.0); + } + inline void setSliceIndexZ(int index) + { + m_sliceIndexZ = index; + m_sliceFractions.setZ((float(index) + 0.5f) / float(m_textureDepth) * 2.0 - 1.0); + } + inline const QVector3D &sliceFractions() const { return m_sliceFractions; } + inline int sliceIndexX() const { return m_sliceIndexX; } + inline int sliceIndexY() const { return m_sliceIndexY; } + inline int sliceIndexZ() const { return m_sliceIndexZ; } + inline void setAlphaMultiplier(float mult) { m_alphaMultiplier = mult; } + inline float alphaMultiplier() const { return m_alphaMultiplier; } + inline void setPreserveOpacity(bool enable) { m_preserveOpacity = enable; } + inline bool preserveOpacity() const { return m_preserveOpacity; } + inline void setUseHighDefShader(bool enable) { m_useHighDefShader = enable; } + inline bool useHighDefShader() const {return m_useHighDefShader; } + void setMinBounds(const QVector3D &bounds); + inline const QVector3D &minBounds() const { return m_minBounds; } + void setMaxBounds(const QVector3D &bounds); + inline const QVector3D &maxBounds() const { return m_maxBounds; } + inline const QVector3D &minBoundsNormal() const { return m_minBoundsNormal; } + inline const QVector3D &maxBoundsNormal() const { return m_maxBoundsNormal; } + inline void setDrawSlices(bool enable) { m_drawSlices = enable; } + inline bool drawSlices() const {return m_drawSlices; } + inline void setDrawSliceFrames(bool enable) { m_drawSliceFrames = enable; } + inline bool drawSliceFrames() const {return m_drawSliceFrames; } + void setSliceFrameColor(const QColor &color); + inline const QVector4D &sliceFrameColor() const { return m_sliceFrameColor; } + inline void setSliceFrameWidths(const QVector3D &widths) { m_sliceFrameWidths = widths * 2.0f; } + inline const QVector3D &sliceFrameWidths() const { return m_sliceFrameWidths; } + inline void setSliceFrameGaps(const QVector3D &gaps) { m_sliceFrameGaps = gaps * 2.0f; } + inline const QVector3D &sliceFrameGaps() const { return m_sliceFrameGaps; } + inline void setSliceFrameThicknesses(const QVector3D &thicknesses) { m_sliceFrameThicknesses = thicknesses; } + inline const QVector3D &sliceFrameThicknesses() const { return m_sliceFrameThicknesses; } private: Q_DISABLE_COPY(CustomRenderItem) GLuint m_texture; QVector3D m_scaling; + QVector3D m_origScaling; QVector3D m_position; - bool m_absolute; + QVector3D m_origPosition; + bool m_positionAbsolute; + bool m_scalingAbsolute; ObjectHelper *m_object; // shared reference bool m_needBlend; bool m_visible; @@ -85,6 +158,32 @@ private: bool m_isFacingCamera; QCustom3DItem *m_item; Abstract3DRenderer *m_renderer; + bool m_labelItem; + + // Volume specific + int m_textureWidth; + int m_textureHeight; + int m_textureDepth; + QVector<QVector4D> m_colorTable; + bool m_isVolume; + QImage::Format m_textureFormat; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + QVector3D m_sliceFractions; + float m_alphaMultiplier; + bool m_preserveOpacity; + bool m_useHighDefShader; + QVector3D m_minBounds; + QVector3D m_maxBounds; + QVector3D m_minBoundsNormal; + QVector3D m_maxBoundsNormal; + bool m_drawSlices; + bool m_drawSliceFrames; + QVector4D m_sliceFrameColor; + QVector3D m_sliceFrameWidths; + QVector3D m_sliceFrameGaps; + QVector3D m_sliceFrameThicknesses; }; typedef QHash<QCustom3DItem *, CustomRenderItem *> CustomRenderItemArray; diff --git a/src/datavisualization/data/data.pri b/src/datavisualization/data/data.pri index 73f398bf..e2bbd6eb 100644 --- a/src/datavisualization/data/data.pri +++ b/src/datavisualization/data/data.pri @@ -41,7 +41,9 @@ HEADERS += \ $$PWD/qcustom3ditem.h \ $$PWD/qcustom3ditem_p.h \ $$PWD/qcustom3dlabel.h \ - $$PWD/qcustom3dlabel_p.h + $$PWD/qcustom3dlabel_p.h \ + $$PWD/qcustom3dvolume.h \ + $$PWD/qcustom3dvolume_p.h SOURCES += \ $$PWD/labelitem.cpp \ @@ -69,4 +71,7 @@ SOURCES += \ $$PWD/qsurface3dseries.cpp \ $$PWD/customrenderitem.cpp \ $$PWD/qcustom3ditem.cpp \ - $$PWD/qcustom3dlabel.cpp + $$PWD/qcustom3dlabel.cpp \ + $$PWD/qcustom3dvolume.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/data/labelitem.cpp b/src/datavisualization/data/labelitem.cpp index 859b0550..ec8ba3fd 100644 --- a/src/datavisualization/data/labelitem.cpp +++ b/src/datavisualization/data/labelitem.cpp @@ -28,7 +28,7 @@ LabelItem::LabelItem() LabelItem::~LabelItem() { - glDeleteTextures(1, &m_textureId); + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_textureId); } void LabelItem::setSize(const QSize &size) @@ -43,7 +43,7 @@ QSize LabelItem::size() const void LabelItem::setTextureId(GLuint textureId) { - glDeleteTextures(1, &m_textureId); + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_textureId); m_textureId = textureId; } @@ -55,7 +55,7 @@ GLuint LabelItem::textureId() const void LabelItem::clear() { if (m_textureId) { - glDeleteTextures(1, &m_textureId); + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &m_textureId); m_textureId = 0; } m_size = QSize(0, 0); diff --git a/src/datavisualization/data/qabstract3dseries.cpp b/src/datavisualization/data/qabstract3dseries.cpp index f8fe6b4f..caa95062 100644 --- a/src/datavisualization/data/qabstract3dseries.cpp +++ b/src/datavisualization/data/qabstract3dseries.cpp @@ -657,6 +657,9 @@ QAbstract3DSeriesPrivate::QAbstract3DSeriesPrivate(QAbstract3DSeries *q, m_mesh(QAbstract3DSeries::MeshCube), m_meshSmooth(false), m_colorStyle(Q3DTheme::ColorStyleUniform), + m_baseColor(Qt::black), + m_singleHighlightColor(Qt::black), + m_multiHighlightColor(Qt::black), m_itemLabelDirty(true), m_itemLabelVisible(true) { diff --git a/src/datavisualization/data/qabstract3dseries.h b/src/datavisualization/data/qabstract3dseries.h index 87c4f3c1..c26c40d1 100644 --- a/src/datavisualization/data/qabstract3dseries.h +++ b/src/datavisualization/data/qabstract3dseries.h @@ -153,6 +153,7 @@ private: friend class Bars3DController; friend class Surface3DController; friend class Surface3DRenderer; + friend class Scatter3DRenderer; friend class Scatter3DController; friend class QBar3DSeries; friend class SeriesRenderCache; diff --git a/src/datavisualization/data/qbar3dseries.cpp b/src/datavisualization/data/qbar3dseries.cpp index 19c3bf79..0aa60b66 100644 --- a/src/datavisualization/data/qbar3dseries.cpp +++ b/src/datavisualization/data/qbar3dseries.cpp @@ -43,26 +43,27 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \row * \li @valueTitle \li Title from value axis * \row - * \li @rowIdx \li Visible row index + * \li @rowIdx \li Visible row index. Localized using graph locale. * \row - * \li @colIdx \li Visible Column index + * \li @colIdx \li Visible Column index. Localized using graph locale. * \row * \li @rowLabel \li Label from row axis * \row * \li @colLabel \li Label from column axis * \row - * \li @valueLabel \li Item value formatted using the same format the value axis attached to the graph uses, - * see \l{QValue3DAxis::setLabelFormat()} for more information. + * \li @valueLabel \li Item value formatted using the same format the value axis attached to + * the graph uses. See \l{QValue3DAxis::labelFormat} for more information. * \row * \li @seriesName \li Name of the series * \row - * \li %<format spec> \li Item value in specified format. + * \li %<format spec> \li Item value in specified format. Formatted using the same rules as + * \l{QValue3DAxis::labelFormat}. * \endtable * * For example: * \snippet doc_src_qtdatavisualization.cpp 1 * - * \sa {Qt Data Visualization Data Handling} + * \sa {Qt Data Visualization Data Handling}, QAbstract3DGraph::locale */ /*! @@ -334,6 +335,10 @@ void QBar3DSeriesPrivate::createItemLabel() return; } + QLocale locale(QLocale::c()); + if (m_controller) + locale = m_controller->locale(); + QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_controller->axisZ()); QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_controller->axisX()); QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_controller->axisY()); @@ -344,13 +349,13 @@ void QBar3DSeriesPrivate::createItemLabel() int selBarPosRow = m_selectedBar.x(); int selBarPosCol = m_selectedBar.y(); - m_itemLabel.replace(rowIndexTag, QString::number(selBarPosRow)); + m_itemLabel.replace(rowIndexTag, locale.toString(selBarPosRow)); if (categoryAxisZ->labels().size() > selBarPosRow) m_itemLabel.replace(rowLabelTag, categoryAxisZ->labels().at(selBarPosRow)); else m_itemLabel.replace(rowLabelTag, QString()); m_itemLabel.replace(rowTitleTag, categoryAxisZ->title()); - m_itemLabel.replace(colIndexTag, QString::number(selBarPosCol)); + m_itemLabel.replace(colIndexTag, locale.toString(selBarPosCol)); if (categoryAxisX->labels().size() > selBarPosCol) m_itemLabel.replace(colLabelTag, categoryAxisX->labels().at(selBarPosCol)); else diff --git a/src/datavisualization/data/qcustom3ditem.cpp b/src/datavisualization/data/qcustom3ditem.cpp index efee3b15..b9825732 100644 --- a/src/datavisualization/data/qcustom3ditem.cpp +++ b/src/datavisualization/data/qcustom3ditem.cpp @@ -62,13 +62,15 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * * Holds the item \a position as a vector3d. Defaults to \c {vector3d(0.0, 0.0, 0.0)}. * - * Item position is either in data coordinates or in absolute coordinates, depending on + * Item position is either in data coordinates or in absolute coordinates, depending on the * positionAbsolute property. When using absolute coordinates, values between \c{-1.0...1.0} are * within axis ranges. * - * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}. + * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}, + * unless the item is a Custom3DVolume that would be partially visible and scalingAbsolute is also + * \c{false}. In that case, the visible portion of the volume will be rendered. * - * \sa positionAbsolute + * \sa positionAbsolute, scalingAbsolute */ /*! \qmlproperty bool Custom3DItem::positionAbsolute @@ -83,8 +85,33 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! \qmlproperty vector3d Custom3DItem::scaling * * Holds the item \a scaling as a vector3d. Defaults to \c {vector3d(0.1, 0.1, 0.1)}. - * The default value sets the item to 10% of the height of the graph, provided the item size is - * normalized. + * + * Item scaling is either in data values or in absolute values, depending on the + * scalingAbsolute property. The default vector interpreted as absolute values sets the item to + * 10% of the height of the graph, provided the item mesh is normalized and the graph aspect ratios + * haven't been changed from the defaults. + * + * \sa scalingAbsolute + */ + +/*! \qmlproperty bool Custom3DItem::scalingAbsolute + * \since QtDataVisualization 1.2 + * + * This property dictates if item scaling is to be handled in data values or in absolute + * values. Defaults to \c{true}. Items with absolute scaling will be rendered at the same + * size, regardless of axis ranges. Items with data scaling will change their apparent size + * according to the axis ranges. If positionAbsolute value is \c{true}, this property is ignored + * and scaling is interpreted as an absolute value. If the item has rotation, the data scaling + * is calculated on the unrotated item. Similarly, for Custom3DVolume items, the range clipping + * is calculated on the unrotated item. + * + * \note: Only absolute scaling is supported for Custom3DLabel items or for custom items used in + * \l{AbstractGraph3D::polar}{polar} graphs. + * + * \note: The custom item's mesh must be normalized to range \c{[-1 ,1]}, or the data + * scaling will not be accurate. + * + * \sa scaling, positionAbsolute */ /*! \qmlproperty quaternion Custom3DItem::rotation @@ -180,7 +207,9 @@ QString QCustom3DItem::meshFile() const * positionAbsolute property. When using absolute coordinates, values between \c{-1.0...1.0} are * within axis ranges. * - * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}. + * \note Items positioned outside any axis range are not rendered if positionAbsolute is \c{false}, + * unless the item is a QCustom3DVolume that would be partially visible and scalingAbsolute is also + * \c{false}. In that case, the visible portion of the volume will be rendered. * * \sa positionAbsolute */ @@ -211,7 +240,7 @@ void QCustom3DItem::setPositionAbsolute(bool positionAbsolute) { if (d_ptr->m_positionAbsolute != positionAbsolute) { d_ptr->m_positionAbsolute = positionAbsolute; - d_ptr->m_dirtyBits.positionAbsoluteDirty = true; + d_ptr->m_dirtyBits.positionDirty = true; emit positionAbsoluteChanged(positionAbsolute); emit d_ptr->needUpdate(); } @@ -225,8 +254,13 @@ bool QCustom3DItem::isPositionAbsolute() const /*! \property QCustom3DItem::scaling * * Holds the item \a scaling as a QVector3D. Defaults to \c {QVector3D(0.1, 0.1, 0.1)}. - * The default value sets the item to 10% of the height of the graph, provided the item size is - * normalized. + * + * Item scaling is either in data values or in absolute values, depending on the + * scalingAbsolute property. The default vector interpreted as absolute values sets the item to + * 10% of the height of the graph, provided the item mesh is normalized and the graph aspect ratios + * haven't been changed from the defaults. + * + * \sa scalingAbsolute */ void QCustom3DItem::setScaling(const QVector3D &scaling) { @@ -243,6 +277,42 @@ QVector3D QCustom3DItem::scaling() const return d_ptr->m_scaling; } +/*! \property QCustom3DItem::scalingAbsolute + * \since QtDataVisualization 1.2 + * + * This property dictates if item scaling is to be handled in data values or in absolute + * values. Defaults to \c{true}. Items with absolute scaling will be rendered at the same + * size, regardless of axis ranges. Items with data scaling will change their apparent size + * according to the axis ranges. If positionAbsolute value is \c{true}, this property is ignored + * and scaling is interpreted as an absolute value. If the item has rotation, the data scaling + * is calculated on the unrotated item. Similarly, for QCustom3DVolume items, the range clipping + * is calculated on the unrotated item. + * + * \note: Only absolute scaling is supported for QCustom3DLabel items or for custom items used in + * \l{QAbstract3DGraph::polar}{polar} graphs. + * + * \note: The custom item's mesh must be normalized to range \c{[-1 ,1]}, or the data + * scaling will not be accurate. + * + * \sa scaling, positionAbsolute + */ +void QCustom3DItem::setScalingAbsolute(bool scalingAbsolute) +{ + if (d_ptr->m_isLabelItem && !scalingAbsolute) { + qWarning() << __FUNCTION__ << "Data bounds are not supported for label items."; + } else if (d_ptr->m_scalingAbsolute != scalingAbsolute) { + d_ptr->m_scalingAbsolute = scalingAbsolute; + d_ptr->m_dirtyBits.scalingDirty = true; + emit scalingAbsoluteChanged(scalingAbsolute); + emit d_ptr->needUpdate(); + } +} + +bool QCustom3DItem::isScalingAbsolute() const +{ + return d_ptr->m_scalingAbsolute; +} + /*! \property QCustom3DItem::rotation * * Holds the item \a rotation as a QQuaternion. Defaults to \c {QQuaternion(0.0, 0.0, 0.0, 0.0)}. @@ -367,13 +437,16 @@ QString QCustom3DItem::textureFile() const QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q) : q_ptr(q), + m_textureImage(QImage(1, 1, QImage::Format_ARGB32)), m_position(QVector3D(0.0f, 0.0f, 0.0f)), m_positionAbsolute(false), m_scaling(QVector3D(0.1f, 0.1f, 0.1f)), - m_rotation(QQuaternion(0.0f, 0.0f, 0.0f, 0.0f)), + m_scalingAbsolute(true), + m_rotation(identityQuaternion), m_visible(true), m_shadowCasting(true), - m_isLabelItem(false) + m_isLabelItem(false), + m_isVolumeItem(false) { } @@ -381,14 +454,17 @@ QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q, const QString &mesh const QVector3D &position, const QVector3D &scaling, const QQuaternion &rotation) : q_ptr(q), + m_textureImage(QImage(1, 1, QImage::Format_ARGB32)), m_meshFile(meshFile), m_position(position), m_positionAbsolute(false), m_scaling(scaling), + m_scalingAbsolute(true), m_rotation(rotation), m_visible(true), m_shadowCasting(true), - m_isLabelItem(false) + m_isLabelItem(false), + m_isVolumeItem(false) { } @@ -412,7 +488,6 @@ void QCustom3DItemPrivate::resetDirtyBits() m_dirtyBits.textureDirty = false; m_dirtyBits.meshDirty = false; m_dirtyBits.positionDirty = false; - m_dirtyBits.positionAbsoluteDirty = false; m_dirtyBits.scalingDirty = false; m_dirtyBits.rotationDirty = false; m_dirtyBits.visibleDirty = false; diff --git a/src/datavisualization/data/qcustom3ditem.h b/src/datavisualization/data/qcustom3ditem.h index 2f7f37cf..5c880213 100644 --- a/src/datavisualization/data/qcustom3ditem.h +++ b/src/datavisualization/data/qcustom3ditem.h @@ -39,6 +39,7 @@ class QT_DATAVISUALIZATION_EXPORT QCustom3DItem : public QObject Q_PROPERTY(QQuaternion rotation READ rotation WRITE setRotation NOTIFY rotationChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) Q_PROPERTY(bool shadowCasting READ isShadowCasting WRITE setShadowCasting NOTIFY shadowCastingChanged) + Q_PROPERTY(bool scalingAbsolute READ isScalingAbsolute WRITE setScalingAbsolute NOTIFY scalingAbsoluteChanged REVISION 1) public: explicit QCustom3DItem(QObject *parent = 0); @@ -62,6 +63,9 @@ public: void setScaling(const QVector3D &scaling); QVector3D scaling() const; + void setScalingAbsolute(bool scalingAbsolute); + bool isScalingAbsolute() const; + void setRotation(const QQuaternion &rotation); QQuaternion rotation(); @@ -84,6 +88,7 @@ signals: void rotationChanged(const QQuaternion &rotation); void visibleChanged(bool visible); void shadowCastingChanged(bool shadowCasting); + Q_REVISION(1) void scalingAbsoluteChanged(bool scalingAbsolute); protected: QCustom3DItem(QCustom3DItemPrivate *d, QObject *parent = 0); diff --git a/src/datavisualization/data/qcustom3ditem_p.h b/src/datavisualization/data/qcustom3ditem_p.h index c1ce5996..627bf53f 100644 --- a/src/datavisualization/data/qcustom3ditem_p.h +++ b/src/datavisualization/data/qcustom3ditem_p.h @@ -29,6 +29,7 @@ #ifndef QCUSTOM3DITEM_P_H #define QCUSTOM3DITEM_P_H +#include "datavisualizationglobal_p.h" #include "qcustom3ditem.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -37,7 +38,6 @@ struct QCustomItemDirtyBitField { bool textureDirty : 1; bool meshDirty : 1; bool positionDirty : 1; - bool positionAbsoluteDirty : 1; bool scalingDirty : 1; bool rotationDirty : 1; bool visibleDirty : 1; @@ -47,7 +47,6 @@ struct QCustomItemDirtyBitField { : textureDirty(false), meshDirty(false), positionDirty(false), - positionAbsoluteDirty(false), scalingDirty(false), rotationDirty(false), visibleDirty(false), @@ -77,11 +76,13 @@ public: QVector3D m_position; bool m_positionAbsolute; QVector3D m_scaling; + bool m_scalingAbsolute; QQuaternion m_rotation; bool m_visible; bool m_shadowCasting; bool m_isLabelItem; + bool m_isVolumeItem; QCustomItemDirtyBitField m_dirtyBits; diff --git a/src/datavisualization/data/qcustom3dlabel.cpp b/src/datavisualization/data/qcustom3dlabel.cpp index 85a37e2d..bb5d71d0 100644 --- a/src/datavisualization/data/qcustom3dlabel.cpp +++ b/src/datavisualization/data/qcustom3dlabel.cpp @@ -44,6 +44,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \since QtDataVisualization 1.1 * \ingroup datavisualization_qml * \instantiates QCustom3DLabel + * \inherits Custom3DItem * \brief The Custom3DLabel type is for creating custom labels to be added to a graph. * * This type is for creating custom labels to be added to a graph. You can set text, font, diff --git a/src/datavisualization/data/qcustom3dvolume.cpp b/src/datavisualization/data/qcustom3dvolume.cpp new file mode 100644 index 00000000..f8287a81 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume.cpp @@ -0,0 +1,1278 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +#include "qcustom3dvolume_p.h" +#include "utils_p.h" + +QT_BEGIN_NAMESPACE_DATAVISUALIZATION + +/*! + * \class QCustom3DVolume + * \inmodule QtDataVisualization + * \brief The QCustom3DVolume class is for creating volume rendered objects to be added to a graph. + * \since QtDataVisualization 1.2 + * + * This class is for creating volume rendered objects to be added to a graph. A volume rendered + * object is a box with a 3D texture. Three slice planes are supported for the volume, one along + * each main axis of the volume. + * + * Rendering volume objects is very performance intensive, especially when the volume is largely + * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly + * with the amount of pixels that the volume occupies on the screen, so showing the volume in a + * smaller view or limiting the zoom level of the graph are easy ways to improve performance. + * Similarly, the volume texture dimensions have a large impact on performance. + * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider + * turning the high definition shader off by setting useHighDefShader property to \c{false}. + * + * \note Volumetric objects are only supported with orthographic projection. + * + * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments. + * + * \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection, useHighDefShader + */ + +/*! + * \qmltype Custom3DVolume + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates QCustom3DVolume + * \inherits Custom3DItem + * \brief The Custom3DVolume type is for creating volume rendered objects to be added to a graph. + * + * This class is for creating volume rendered objects to be added to a graph. A volume rendered + * object is a box with a 3D texture. Three slice planes are supported for the volume, one along + * each main axis of the volume. + * + * Rendering volume objects is very performance intensive, especially when the volume is largely + * transparent, as the contents of the volume are ray-traced. The performance scales nearly linearly + * with the amount of pixels that the volume occupies on the screen, so showing the volume in a + * smaller view or limiting the zoom level of the graph are easy ways to improve performance. + * Similarly, the volume texture dimensions have a large impact on performance. + * If the frame rate is more important than pixel-perfect rendering of the volume contents, consider + * turning the high definition shader off by setting useHighDefShader property to \c{false}. + * + * \note: Filling in the volume data would not typically be efficient or practical from pure QML, + * so properties directly related to that are not fully supported from QML. + * Make a hybrid QML/C++ application if you want to use volume objects with a QML UI. + * + * \note Volumetric objects are only supported with orthographic projection. + * + * \note Volumetric objects utilize 3D textures, which are not supported in OpenGL ES2 environments. + * + * \sa AbstractGraph3D::orthoProjection, useHighDefShader + */ + +/*! \qmlproperty int Custom3DVolume::textureWidth + * + * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note: Changing this property from QML is not supported, as the texture data cannot be resized + * to match. + */ + +/*! \qmlproperty int Custom3DVolume::textureHeight + * + * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note: Changing this property from QML is not supported, as the texture data cannot be resized + * to match. + */ + +/*! \qmlproperty int Custom3DVolume::textureDepth + * + * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note: Changing this property from QML is not supported, as the texture data cannot be resized + * to match. + */ + +/*! \qmlproperty int Custom3DVolume::sliceIndexX + * + * The X dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames + */ + +/*! \qmlproperty int Custom3DVolume::sliceIndexY + * + * The Y dimension index into the texture data indicating which horizontal slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames + */ + +/*! \qmlproperty int Custom3DVolume::sliceIndexZ + * + * The Z dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData, drawSlices, drawSliceFrames + */ + +/*! + * \qmlproperty real Custom3DVolume::alphaMultiplier + * + * The alpha value of every texel of the volume texture is multiplied with this value at + * the render time. This can be used to introduce uniform transparency to the volume. + * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are + * affected, and fully opaque texels are not affected. + * The value must not be negative. + * Defaults to \c{1.0}. + * + * \sa preserveOpacity + */ + +/*! + * \qmlproperty bool Custom3DVolume::preserveOpacity + * + * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have + * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all + * texels. + * Defaults to \c{true}. + * + * \sa alphaMultiplier + */ + +/*! + * \qmlproperty bool Custom3DVolume::useHighDefShader + * + * If this property value is \c{true}, a high definition shader is used to render the volume. + * If it is \c{false}, a low definition shader is used. + * + * The high definition shader guarantees that every visible texel of the volume texture is sampled + * when the volume is rendered. + * The low definition shader renders only a rough approximation of the volume contents, + * but at much higher frame rate. The low definition shader doesn't guarantee every texel of the + * volume texture is sampled, so there may be flickering if the volume contains distinct thin + * features. + * + * \note This value doesn't affect the level of detail when rendering the slices of the volume. + * + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool Custom3DVolume::drawSlices + * + * If this property value is \c{true}, the slices indicated by slice index properties + * will be drawn instead of the full volume. + * If it is \c{false}, the full volume will always be drawn. + * Defaults to \c{false}. + * + * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are + * rotated as well. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ + */ + +/*! + * \qmlproperty bool Custom3DVolume::drawSliceFrames + * + * If this property value is \c{true}, the frames of slices indicated by slice index properties + * will be drawn around the volume. + * If it is \c{false}, no slice frames will be drawn. + * Drawing slice frames is independent of drawing slices, so you can show the full volume and + * still draw the slice frames around it. + * Defaults to \c{false}. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices + */ + +/*! + * \qmlproperty color Custom3DVolume::sliceFrameColor + * + * Indicates the color of the slice frame. Transparent slice frame color is not supported. + * + * Defaults to black. + * + * \sa drawSliceFrames + */ + +/*! + * \qmlproperty vector3d Custom3DVolume::sliceFrameWidths + * + * Indicates the widths of the slice frame. The width can be different on different dimensions, + * so you can for example omit drawing the frames on certain sides of the volume by setting the + * value for that dimension to zero. The values are fractions of the volume thickness in the same + * dimension. The values cannot be negative. + * + * Defaults to \c{vector3d(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ + +/*! + * \qmlproperty vector3d Custom3DVolume::sliceFrameGaps + * + * Indicates the amount of air gap left between the volume itself and the frame in each dimension. + * The gap can be different on different dimensions. The values are fractions of the volume + * thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{vector3d(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ + +/*! + * \qmlproperty vector3d Custom3DVolume::sliceFrameThicknesses + * + * Indicates the thickness of the slice frames for each dimension. The values are fractions of + * the volume thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{vector3d(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ + +/*! + * Constructs QCustom3DVolume with given \a parent. + */ +QCustom3DVolume::QCustom3DVolume(QObject *parent) : + QCustom3DItem(new QCustom3DVolumePrivate(this), parent) +{ +} + +/*! + * Constructs QCustom3DVolume with given \a position, \a scaling, \a rotation, + * \a textureWidth, \a textureHeight, \a textureDepth, \a textureData, \a textureFormat, + * \a colorTable, and optional \a parent. + * + * \sa textureData, setTextureFormat(), colorTable + */ +QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, + QVector<uchar> *textureData, QImage::Format textureFormat, + const QVector<QRgb> &colorTable, QObject *parent) : + QCustom3DItem(new QCustom3DVolumePrivate(this, position, scaling, rotation, textureWidth, + textureHeight, textureDepth, + textureData, textureFormat, colorTable), parent) +{ +} + + +/*! + * Destroys QCustom3DVolume. + */ +QCustom3DVolume::~QCustom3DVolume() +{ +} + +/*! \property QCustom3DVolume::textureWidth + * + * The width of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note The textureData may need to be resized or recreated if this value is changed. + * Defaults to \c{0}. + * + * \sa textureData, textureHeight, textureDepth, setTextureFormat(), textureDataWidth() + */ +void QCustom3DVolume::setTextureWidth(int value) +{ + if (value >= 0) { + if (dptr()->m_textureWidth != value) { + dptr()->m_textureWidth = value; + dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true; + emit textureWidthChanged(value); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Cannot set negative value."; + } +} + +int QCustom3DVolume::textureWidth() const +{ + return dptrc()->m_textureWidth; +} + +/*! \property QCustom3DVolume::textureHeight + * + * The height of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note The textureData may need to be resized or recreated if this value is changed. + * Defaults to \c{0}. + * + * \sa textureData, textureWidth, textureDepth, setTextureFormat() + */ +void QCustom3DVolume::setTextureHeight(int value) +{ + if (value >= 0) { + if (dptr()->m_textureHeight != value) { + dptr()->m_textureHeight = value; + dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true; + emit textureHeightChanged(value); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Cannot set negative value."; + } + +} + +int QCustom3DVolume::textureHeight() const +{ + return dptrc()->m_textureHeight; +} + +/*! \property QCustom3DVolume::textureDepth + * + * The depth of the 3D texture defining the volume content in pixels. Defaults to \c{0}. + * + * \note The textureData may need to be resized or recreated if this value is changed. + * Defaults to \c{0}. + * + * \sa textureData, textureWidth, textureHeight, setTextureFormat() + */ +void QCustom3DVolume::setTextureDepth(int value) +{ + if (value >= 0) { + if (dptr()->m_textureDepth != value) { + dptr()->m_textureDepth = value; + dptr()->m_dirtyBitsVolume.textureDimensionsDirty = true; + emit textureDepthChanged(value); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Cannot set negative value."; + } +} + +int QCustom3DVolume::textureDepth() const +{ + return dptrc()->m_textureDepth; +} + +/*! + * A convenience function for setting all three texture dimensions + * (\a width, \a height, and \a depth) at once. + * + * \sa textureData + */ +void QCustom3DVolume::setTextureDimensions(int width, int height, int depth) +{ + setTextureWidth(width); + setTextureHeight(height); + setTextureDepth(depth); +} + +/*! + * \return the actual texture data width. When the texture format is QImage::Format_Indexed8, + * this is textureWidth aligned to 32bit boundary. Otherwise this is four times textureWidth. + */ +int QCustom3DVolume::textureDataWidth() const +{ + int dataWidth = dptrc()->m_textureWidth; + + if (dptrc()->m_textureFormat == QImage::Format_Indexed8) + dataWidth += dataWidth % 4; + else + dataWidth *= 4; + + return dataWidth; +} + +/*! \property QCustom3DVolume::sliceIndexX + * + * The X dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa textureData, drawSlices, drawSliceFrames + */ +void QCustom3DVolume::setSliceIndexX(int value) +{ + if (dptr()->m_sliceIndexX != value) { + dptr()->m_sliceIndexX = value; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceIndexXChanged(value); + emit dptr()->needUpdate(); + } +} + +int QCustom3DVolume::sliceIndexX() const +{ + return dptrc()->m_sliceIndexX; +} + +/*! \property QCustom3DVolume::sliceIndexY + * + * The Y dimension index into the texture data indicating which horizontal slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa textureData, drawSlices, drawSliceFrames + */ +void QCustom3DVolume::setSliceIndexY(int value) +{ + if (dptr()->m_sliceIndexY != value) { + dptr()->m_sliceIndexY = value; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceIndexYChanged(value); + emit dptr()->needUpdate(); + } +} + +int QCustom3DVolume::sliceIndexY() const +{ + return dptrc()->m_sliceIndexY; +} + +/*! \property QCustom3DVolume::sliceIndexZ + * + * The Z dimension index into the texture data indicating which vertical slice to show. + * Setting any dimension to negative indicates no slice or slice frame for that dimension is drawn. + * If all dimensions are negative, no slices or slice frames are drawn and the volume is drawn + * normally. + * Defaults to \c{-1}. + * + * \sa textureData, drawSlices, drawSliceFrames + */ +void QCustom3DVolume::setSliceIndexZ(int value) +{ + if (dptr()->m_sliceIndexZ != value) { + dptr()->m_sliceIndexZ = value; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceIndexZChanged(value); + emit dptr()->needUpdate(); + } +} + +int QCustom3DVolume::sliceIndexZ() const +{ + return dptrc()->m_sliceIndexZ; +} + +/*! + * A convenience function for setting all three slice indices (\a x, \a y, and \a z) at once. + * + * \sa textureData + */ +void QCustom3DVolume::setSliceIndices(int x, int y, int z) +{ + setSliceIndexX(x); + setSliceIndexY(y); + setSliceIndexZ(z); +} + +/*! \property QCustom3DVolume::colorTable + * + * The array containing the colors for indexed texture formats. If the texture format is not + * indexed, this array is not used and can be empty. + * + * Defaults to \c{0}. + * + * \sa textureData, setTextureFormat(), QImage::colorTable() + */ +void QCustom3DVolume::setColorTable(const QVector<QRgb> &colors) +{ + if (dptr()->m_colorTable != colors) { + dptr()->m_colorTable = colors; + dptr()->m_dirtyBitsVolume.colorTableDirty = true; + emit colorTableChanged(); + emit dptr()->needUpdate(); + } +} + +QVector<QRgb> QCustom3DVolume::colorTable() const +{ + return dptrc()->m_colorTable; +} + +/*! \property QCustom3DVolume::textureData + * + * The array containing the texture data in the format specified by textureFormat. + * The size of this array must be at least + * (\c{textureDataWidth * textureHeight * textureDepth * texture format color depth in bytes}). + * + * A 3D texture is defined by a stack of 2D subtextures. Each subtexture must be of identical size + * (\c{textureDataWidth * textureHeight}), and the depth of the stack is defined by textureDepth + * property. Each 2D texture data is identical to a QImage data with the same format, so + * QImage::bits() can be used to supply the data for each subtexture. + * + * Ownership of the new array transfers to QCustom3DVolume instance. + * If another array is set, the previous array is deleted. + * If the same array is set again, it is assumed that the array contents have been changed and the + * graph rendering is triggered. + * + * \note Each X-line of the data needs to be 32bit aligned. If the textureFormat is + * QImage::Format_Indexed8 and textureWidth is not divisible by four, padding bytes need + * to be added to each X-line of the \a data. You can get the padded byte count with + * textureDataWidth() function. The padding bytes should indicate an fully transparent color + * to avoid rendering artifacts. + * + * Defaults to \c{0}. + * + * \sa colorTable, setTextureFormat(), setSubTextureData(), textureDataWidth() + */ +void QCustom3DVolume::setTextureData(QVector<uchar> *data) +{ + if (dptr()->m_textureData != data) + delete dptr()->m_textureData; + + // Even if the pointer is same as previously, consider this property changed, as the values + // can be changed unbeknownst to us via the array pointer. + dptr()->m_textureData = data; + dptr()->m_dirtyBitsVolume.textureDataDirty = true; + emit textureDataChanged(data); + emit dptr()->needUpdate(); +} + +/*! + * This function creates a new texture data array from an array of \a images and sets it as + * textureData for this volume object. The texture dimensions are also set according to image + * and array dimensions. All of the images in the array must be the same size. If the images are not + * all in QImage::Format_Indexed8 format, all texture data will be converted into + * QImage::Format_ARGB32 format. If the images are in QImage::Format_Indexed8 format, the colorTable + * for the entire volume will be taken from the first image. + * + * \return pointer to the newly created array. + * + * \sa textureData, textureWidth, textureHeight, textureDepth, setTextureFormat() + */ +QVector<uchar> *QCustom3DVolume::createTextureData(const QVector<QImage *> &images) +{ + int imageCount = images.size(); + if (imageCount) { + QImage *currentImage = images.at(0); + int imageWidth = currentImage->width(); + int imageHeight = currentImage->height(); + QImage::Format imageFormat = currentImage->format(); + bool convert = false; + if (imageFormat != QImage::Format_Indexed8 && imageFormat != QImage::Format_ARGB32) { + convert = true; + imageFormat = QImage::Format_ARGB32; + } else { + for (int i = 0; i < imageCount; i++) { + currentImage = images.at(i); + if (imageWidth != currentImage->width() || imageHeight != currentImage->height()) { + qWarning() << __FUNCTION__ << "Not all images were of the same size."; + setTextureData(0); + setTextureWidth(0); + setTextureHeight(0); + setTextureDepth(0); + return 0; + + } + if (currentImage->format() != imageFormat) { + convert = true; + imageFormat = QImage::Format_ARGB32; + break; + } + } + } + int colorBytes = (imageFormat == QImage::Format_Indexed8) ? 1 : 4; + int imageByteWidth = (imageFormat == QImage::Format_Indexed8) + ? currentImage->bytesPerLine() : imageWidth; + int frameSize = imageByteWidth * imageHeight * colorBytes; + QVector<uchar> *newTextureData = new QVector<uchar>; + newTextureData->resize(frameSize * imageCount); + uchar *texturePtr = newTextureData->data(); + QImage convertedImage; + + for (int i = 0; i < imageCount; i++) { + currentImage = images.at(i); + if (convert) { + convertedImage = currentImage->convertToFormat(imageFormat); + currentImage = &convertedImage; + } + memcpy(texturePtr, static_cast<void *>(currentImage->bits()), frameSize); + texturePtr += frameSize; + } + + if (imageFormat == QImage::Format_Indexed8) + setColorTable(images.at(0)->colorTable()); + setTextureData(newTextureData); + setTextureFormat(imageFormat); + setTextureWidth(imageWidth); + setTextureHeight(imageHeight); + setTextureDepth(imageCount); + } else { + setTextureData(0); + setTextureWidth(0); + setTextureHeight(0); + setTextureDepth(0); + } + return dptr()->m_textureData; +} + +QVector<uchar> *QCustom3DVolume::textureData() const +{ + return dptrc()->m_textureData; +} + +/*! + * This function allows setting a single 2D subtexture of the 3D texture along the specified + * \a axis of the volume. + * The \a index parameter specifies the subtexture to set. + * The texture \a data must be in the format specified by textureFormat property and have size of + * the cross-section of the volume texture along the specified axis multiplied by + * the texture format color depth in bytes. + * The \a data is expected to be ordered similarly to the data in images produced by renderSlice() + * method along the same axis. + * + * \note Each X-line of the data needs to be 32bit aligned when targeting Y-axis or Z-axis. + * If the textureFormat is QImage::Format_Indexed8 and textureWidth is not divisible by four, + * padding bytes need to be added to each X-line of the \a data in cases it is not already + * properly aligned. The padding bytes should indicate an fully transparent color to avoid + * rendering artifacts. + * + * \sa textureData, renderSlice() + */ +void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const uchar *data) +{ + if (data) { + int lineSize = textureDataWidth(); + int frameSize = lineSize * dptr()->m_textureHeight; + int dataSize = dptr()->m_textureData->size(); + int pixelWidth = (dptr()->m_textureFormat == QImage::Format_Indexed8) ? 1 : 4; + int targetIndex; + uchar *dataPtr = dptr()->m_textureData->data(); + bool invalid = (index < 0); + if (axis == Qt::XAxis) { + targetIndex = index * pixelWidth; + if (index >= dptr()->m_textureWidth + || (frameSize * (dptr()->m_textureDepth - 1) + targetIndex) > dataSize) { + invalid = true; + } + } else if (axis == Qt::YAxis) { + targetIndex = (index * lineSize) + (frameSize * (dptr()->m_textureDepth - 1)); + if (index >= dptr()->m_textureHeight || (targetIndex + lineSize > dataSize)) + invalid = true; + } else { + targetIndex = index * frameSize; + if (index >= dptr()->m_textureDepth || ((targetIndex + frameSize) > dataSize)) + invalid = true; + } + + if (invalid) { + qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture."; + } else { + const uchar *sourcePtr = data; + uchar *targetPtr = dataPtr + targetIndex; + if (axis == Qt::XAxis) { + int targetWidth = dptr()->m_textureDepth; + int targetHeight = dptr()->m_textureHeight; + for (int i = 0; i < targetHeight; i++) { + targetPtr = dataPtr + targetIndex + (lineSize * i); + for (int j = 0; j < targetWidth; j++) { + for (int k = 0; k < pixelWidth; k++) + *targetPtr++ = *sourcePtr++; + targetPtr += (frameSize - pixelWidth); + } + } + } else if (axis == Qt::YAxis) { + int targetHeight = dptr()->m_textureDepth; + for (int i = 0; i < targetHeight; i++){ + for (int j = 0; j < lineSize; j++) + *targetPtr++ = *sourcePtr++; + targetPtr -= (frameSize + lineSize); + } + } else { + void *subTexPtr = dataPtr + targetIndex; + memcpy(subTexPtr, static_cast<const void *>(data), frameSize); + } + dptr()->m_dirtyBitsVolume.textureDataDirty = true; + emit textureDataChanged(dptr()->m_textureData); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Tried to set null data."; + } +} + +/*! + * This function allows setting a single 2D subtexture of the 3D texture along the specified + * \a axis of the volume. + * The \a index parameter specifies the subtexture to set. + * The source \a image must be in the format specified by the textureFormat property if the + * textureFormat is indexed. If the textureFormat is QImage::Format_ARGB32, the image is converted + * to that format. The image must have the size of the cross-section of the volume texture along + * the specified axis. The orientation of the image should correspond to the orientation of + * the slice image produced by renderSlice() method along the same axis. + * + * \note Each X-line of the data needs to be 32bit aligned when targeting Y-axis or Z-axis. + * If the textureFormat is QImage::Format_Indexed8 and textureWidth is not divisible by four, + * padding bytes need to be added to each X-line of the \a image in cases it is not already + * properly aligned. The padding bytes should indicate an fully transparent color to avoid + * rendering artifacts. It is not guaranteed QImage will do this automatically. + * + * \sa textureData, renderSlice() + */ +void QCustom3DVolume::setSubTextureData(Qt::Axis axis, int index, const QImage &image) +{ + int sourceWidth = image.width(); + int sourceHeight = image.height(); + int targetWidth; + int targetHeight; + if (axis == Qt::XAxis) { + targetWidth = dptr()->m_textureDepth; + targetHeight = dptr()->m_textureHeight; + } else if (axis == Qt::YAxis) { + targetWidth = dptr()->m_textureWidth; + targetHeight = dptr()->m_textureDepth; + } else { + targetWidth = dptr()->m_textureWidth; + targetHeight = dptr()->m_textureHeight; + } + + if (sourceWidth == targetWidth + && sourceHeight == targetHeight + && (image.format() == dptr()->m_textureFormat + || dptr()->m_textureFormat == QImage::Format_ARGB32)) { + QImage convertedImage; + if (dptr()->m_textureFormat == QImage::Format_ARGB32 + && image.format() != QImage::Format_ARGB32) { + convertedImage = image.convertToFormat(QImage::Format_ARGB32); + } else { + convertedImage = image; + } + setSubTextureData(axis, index, convertedImage.bits()); + } else { + qWarning() << __FUNCTION__ << "Invalid image size or format."; + } +} + +// Note: textureFormat is not a Q_PROPERTY to work around an issue in meta object system that +// doesn't allow QImage::format to be a property type. Qt 5.2.1 at least has this problem. + +/*! + * Sets the format of the textureData to \a format. Only two formats are supported currently: + * QImage::Format_Indexed8 and QImage::Format_ARGB32. If an indexed format is specified, colorTable + * must also be set. + * Defaults to QImage::Format_ARGB32. + * + * \sa colorTable, textureData + */ +void QCustom3DVolume::setTextureFormat(QImage::Format format) +{ + if (format == QImage::Format_ARGB32 || format == QImage::Format_Indexed8) { + if (dptr()->m_textureFormat != format) { + dptr()->m_textureFormat = format; + dptr()->m_dirtyBitsVolume.textureFormatDirty = true; + emit textureFormatChanged(format); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Attempted to set invalid texture format."; + } +} + +/*! + * \return the format of the textureData. + * + * \sa setTextureFormat() + */ +QImage::Format QCustom3DVolume::textureFormat() const +{ + return dptrc()->m_textureFormat; +} + +/*! + * \fn void QCustom3DVolume::textureFormatChanged(QImage::Format format) + * + * This signal is emitted when the textureData \a format changes. + * + * \sa setTextureFormat() + */ + +/*! + * \property QCustom3DVolume::alphaMultiplier + * + * The alpha value of every texel of the volume texture is multiplied with this value at + * the render time. This can be used to introduce uniform transparency to the volume. + * If preserveOpacity is \c{true}, only texels with at least some transparency to begin with are + * affected, and fully opaque texels are not affected. + * The value must not be negative. + * Defaults to \c{1.0f}. + * + * \sa preserveOpacity, textureData + */ +void QCustom3DVolume::setAlphaMultiplier(float mult) +{ + if (mult >= 0.0f) { + if (dptr()->m_alphaMultiplier != mult) { + dptr()->m_alphaMultiplier = mult; + dptr()->m_dirtyBitsVolume.alphaDirty = true; + emit alphaMultiplierChanged(mult); + emit dptr()->needUpdate(); + } + } else { + qWarning() << __FUNCTION__ << "Attempted to set negative multiplier."; + } +} + +float QCustom3DVolume::alphaMultiplier() const +{ + return dptrc()->m_alphaMultiplier; +} + +/*! + * \property QCustom3DVolume::preserveOpacity + * + * If this property value is \c{true}, alphaMultiplier is only applied to texels that already have + * some transparency. If it is \c{false}, the multiplier is applied to the alpha value of all + * texels. + * Defaults to \c{true}. + * + * \sa alphaMultiplier + */ +void QCustom3DVolume::setPreserveOpacity(bool enable) +{ + if (dptr()->m_preserveOpacity != enable) { + dptr()->m_preserveOpacity = enable; + dptr()->m_dirtyBitsVolume.alphaDirty = true; + emit preserveOpacityChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::preserveOpacity() const +{ + return dptrc()->m_preserveOpacity; +} + +/*! + * \property QCustom3DVolume::useHighDefShader + * + * If this property value is \c{true}, a high definition shader is used to render the volume. + * If it is \c{false}, a low definition shader is used. + * + * The high definition shader guarantees that every visible texel of the volume texture is sampled + * when the volume is rendered. + * The low definition shader renders only a rough approximation of the volume contents, + * but at much higher frame rate. The low definition shader doesn't guarantee every texel of the + * volume texture is sampled, so there may be flickering if the volume contains distinct thin + * features. + * + * \note This value doesn't affect the level of detail when rendering the slices of the volume. + * + * Defaults to \c{true}. + * + * \sa renderSlice() + */ +void QCustom3DVolume::setUseHighDefShader(bool enable) +{ + if (dptr()->m_useHighDefShader != enable) { + dptr()->m_useHighDefShader = enable; + dptr()->m_dirtyBitsVolume.shaderDirty = true; + emit useHighDefShaderChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::useHighDefShader() const +{ + return dptrc()->m_useHighDefShader; +} + +/*! + * \property QCustom3DVolume::drawSlices + * + * If this property value is \c{true}, the slices indicated by slice index properties + * will be drawn instead of the full volume. + * If it is \c{false}, the full volume will always be drawn. + * Defaults to \c{false}. + * + * \note The slices are always drawn along the item axes, so if the item is rotated, the slices are + * rotated as well. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ + */ +void QCustom3DVolume::setDrawSlices(bool enable) +{ + if (dptr()->m_drawSlices != enable) { + dptr()->m_drawSlices = enable; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit drawSlicesChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::drawSlices() const +{ + return dptrc()->m_drawSlices; +} + +/*! + * \property QCustom3DVolume::drawSliceFrames + * + * If this property value is \c{true}, the frames of slices indicated by slice index properties + * will be drawn around the volume. + * If it is \c{false}, no slice frames will be drawn. + * Drawing slice frames is independent of drawing slices, so you can show the full volume and + * still draw the slice frames around it. This is useful when using renderSlice() to display the + * slices outside the graph itself. + * Defaults to \c{false}. + * + * \sa sliceIndexX, sliceIndexY, sliceIndexZ, drawSlices, renderSlice() + */ +void QCustom3DVolume::setDrawSliceFrames(bool enable) +{ + if (dptr()->m_drawSliceFrames != enable) { + dptr()->m_drawSliceFrames = enable; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit drawSliceFramesChanged(enable); + emit dptr()->needUpdate(); + } +} + +bool QCustom3DVolume::drawSliceFrames() const +{ + return dptrc()->m_drawSliceFrames; +} + +/*! + * \property QCustom3DVolume::sliceFrameColor + * + * Indicates the color of the slice frame. Transparent slice frame color is not supported. + * + * Defaults to black. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameColor(const QColor &color) +{ + if (dptr()->m_sliceFrameColor != color) { + dptr()->m_sliceFrameColor = color; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameColorChanged(color); + emit dptr()->needUpdate(); + } +} + +QColor QCustom3DVolume::sliceFrameColor() const +{ + return dptrc()->m_sliceFrameColor; +} + +/*! + * \property QCustom3DVolume::sliceFrameWidths + * + * Indicates the widths of the slice frame. The width can be different on different dimensions, + * so you can for example omit drawing the frames on certain sides of the volume by setting the + * value for that dimension to zero. The values are fractions of the volume thickness in the same + * dimension. The values cannot be negative. + * + * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameWidths(const QVector3D &values) +{ + if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) { + qWarning() << __FUNCTION__ << "Attempted to set negative values."; + } else if (dptr()->m_sliceFrameWidths != values) { + dptr()->m_sliceFrameWidths = values; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameWidthsChanged(values); + emit dptr()->needUpdate(); + } +} + +QVector3D QCustom3DVolume::sliceFrameWidths() const +{ + return dptrc()->m_sliceFrameWidths; +} + +/*! + * \property QCustom3DVolume::sliceFrameGaps + * + * Indicates the amount of air gap left between the volume itself and the frame in each dimension. + * The gap can be different on different dimensions. The values are fractions of the volume + * thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameGaps(const QVector3D &values) +{ + if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) { + qWarning() << __FUNCTION__ << "Attempted to set negative values."; + } else if (dptr()->m_sliceFrameGaps != values) { + dptr()->m_sliceFrameGaps = values; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameGapsChanged(values); + emit dptr()->needUpdate(); + } +} + +QVector3D QCustom3DVolume::sliceFrameGaps() const +{ + return dptrc()->m_sliceFrameGaps; +} + +/*! + * \property QCustom3DVolume::sliceFrameThicknesses + * + * Indicates the thickness of the slice frames for each dimension. The values are fractions of + * the volume thickness in the same dimension. The values cannot be negative. + * + * Defaults to \c{QVector3D(0.01, 0.01, 0.01)}. + * + * \sa drawSliceFrames + */ +void QCustom3DVolume::setSliceFrameThicknesses(const QVector3D &values) +{ + if (values.x() < 0.0f || values.y() < 0.0f || values.z() < 0.0f) { + qWarning() << __FUNCTION__ << "Attempted to set negative values."; + } else if (dptr()->m_sliceFrameThicknesses != values) { + dptr()->m_sliceFrameThicknesses = values; + dptr()->m_dirtyBitsVolume.slicesDirty = true; + emit sliceFrameThicknessesChanged(values); + emit dptr()->needUpdate(); + } +} + +QVector3D QCustom3DVolume::sliceFrameThicknesses() const +{ + return dptrc()->m_sliceFrameThicknesses; +} + +/*! + * Renders the slice specified by \a index along \a axis into an image. + * The texture format of this object is used. + * + * \return the rendered image of the slice, or a null image if invalid index is specified. + * + * \sa setTextureFormat() + */ +QImage QCustom3DVolume::renderSlice(Qt::Axis axis, int index) +{ + return dptr()->renderSlice(axis, index); +} + +/*! + * \internal + */ +QCustom3DVolumePrivate *QCustom3DVolume::dptr() +{ + return static_cast<QCustom3DVolumePrivate *>(d_ptr.data()); +} + +/*! + * \internal + */ +const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const +{ + return static_cast<const QCustom3DVolumePrivate *>(d_ptr.data()); +} + +QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) : + QCustom3DItemPrivate(q), + m_textureWidth(0), + m_textureHeight(0), + m_textureDepth(0), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_textureFormat(QImage::Format_ARGB32), + m_textureData(0), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true), + m_useHighDefShader(true), + m_drawSlices(false), + m_drawSliceFrames(false), + m_sliceFrameColor(Qt::black), + m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f)) +{ + m_isVolumeItem = true; + m_meshFile = QStringLiteral(":/defaultMeshes/barFull"); +} + +QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector3D &position, + const QVector3D &scaling, + const QQuaternion &rotation, + int textureWidth, int textureHeight, + int textureDepth, QVector<uchar> *textureData, + QImage::Format textureFormat, + const QVector<QRgb> &colorTable) : + QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling, rotation), + m_textureWidth(textureWidth), + m_textureHeight(textureHeight), + m_textureDepth(textureDepth), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_textureFormat(textureFormat), + m_colorTable(colorTable), + m_textureData(textureData), + m_alphaMultiplier(1.0f), + m_preserveOpacity(true), + m_useHighDefShader(true), + m_drawSlices(false), + m_drawSliceFrames(false), + m_sliceFrameColor(Qt::black), + m_sliceFrameWidths(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameGaps(QVector3D(0.01f, 0.01f, 0.01f)), + m_sliceFrameThicknesses(QVector3D(0.01f, 0.01f, 0.01f)) +{ + m_isVolumeItem = true; + m_shadowCasting = false; + + if (m_textureWidth < 0) + m_textureWidth = 0; + if (m_textureHeight < 0) + m_textureHeight = 0; + if (m_textureDepth < 0) + m_textureDepth = 0; + + if (m_textureFormat != QImage::Format_Indexed8) + m_textureFormat = QImage::Format_ARGB32; + +} + +QCustom3DVolumePrivate::~QCustom3DVolumePrivate() +{ + delete m_textureData; +} + +void QCustom3DVolumePrivate::resetDirtyBits() +{ + QCustom3DItemPrivate::resetDirtyBits(); + + m_dirtyBitsVolume.textureDimensionsDirty = false; + m_dirtyBitsVolume.slicesDirty = false; + m_dirtyBitsVolume.colorTableDirty = false; + m_dirtyBitsVolume.textureDataDirty = false; + m_dirtyBitsVolume.textureFormatDirty = false; + m_dirtyBitsVolume.alphaDirty = false; + m_dirtyBitsVolume.shaderDirty = false; +} + +QImage QCustom3DVolumePrivate::renderSlice(Qt::Axis axis, int index) +{ + if (index < 0) + return QImage(); + + int x; + int y; + if (axis == Qt::XAxis) { + if (index >= m_textureWidth) + return QImage(); + x = m_textureDepth; + y = m_textureHeight; + } else if (axis == Qt::YAxis) { + if (index >= m_textureHeight) + return QImage(); + x = m_textureWidth; + y = m_textureDepth; + } else { + if (index >= m_textureDepth) + return QImage(); + x = m_textureWidth; + y = m_textureHeight; + } + + int padding = 0; + int pixelWidth = 4; + int dataWidth = qptr()->textureDataWidth(); + if (m_textureFormat == QImage::Format_Indexed8) { + padding = x % 4; + pixelWidth = 1; + } + QVector<uchar> data((x + padding) * y * pixelWidth); + int frameSize = qptr()->textureDataWidth() * m_textureHeight; + + int dataIndex = 0; + if (axis == Qt::XAxis) { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + + (index * pixelWidth) + (dataWidth * i); + for (int j = 0; j < x; j++) { + for (int k = 0; k < pixelWidth; k++) + data[dataIndex++] = *(p + k); + p += frameSize; + } + } + } else if (axis == Qt::YAxis) { + for (int i = y - 1; i >= 0; i--) { + const uchar *p = m_textureData->constData() + (index * dataWidth) + + (frameSize * i); + for (int j = 0; j < (x * pixelWidth); j++) { + data[dataIndex++] = *p; + p++; + } + } + } else { + for (int i = 0; i < y; i++) { + const uchar *p = m_textureData->constData() + (index * frameSize) + (dataWidth * i); + for (int j = 0; j < (x * pixelWidth); j++) { + data[dataIndex++] = *p; + p++; + } + } + } + + if (m_textureFormat != QImage::Format_Indexed8 && m_alphaMultiplier != 1.0f) { + for (int i = pixelWidth - 1; i < data.size(); i += pixelWidth) + data[i] = static_cast<uchar>(multipliedAlphaValue(data.at(i))); + } + + QImage image(data.constData(), x, y, x * pixelWidth, m_textureFormat); + image.bits(); // Call bits() to detach the new image from local data + if (m_textureFormat == QImage::Format_Indexed8) { + QVector<QRgb> colorTable = m_colorTable; + if (m_alphaMultiplier != 1.0f) { + for (int i = 0; i < colorTable.size(); i++) { + QRgb curCol = colorTable.at(i); + int alpha = multipliedAlphaValue(qAlpha(curCol)); + if (alpha != qAlpha(curCol)) + colorTable[i] = qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), alpha); + } + } + image.setColorTable(colorTable); + } + + return image; +} + +int QCustom3DVolumePrivate::multipliedAlphaValue(int alpha) +{ + int modifiedAlpha = alpha; + if (!m_preserveOpacity || alpha != 255) { + modifiedAlpha = int(m_alphaMultiplier * float(alpha)); + modifiedAlpha = qMin(modifiedAlpha, 255); + } + return modifiedAlpha; +} + +QCustom3DVolume *QCustom3DVolumePrivate::qptr() +{ + return static_cast<QCustom3DVolume *>(q_ptr); +} + +QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/qcustom3dvolume.h b/src/datavisualization/data/qcustom3dvolume.h new file mode 100644 index 00000000..a9dfda86 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +#ifndef QCUSTOM3DVOLUME_H +#define QCUSTOM3DVOLUME_H + +#include <QtDataVisualization/qdatavisualizationglobal.h> +#include <QtDataVisualization/QCustom3DItem> +#include <QtGui/QColor> +#include <QtGui/QImage> + +QT_BEGIN_NAMESPACE_DATAVISUALIZATION + +class QCustom3DVolumePrivate; + +class QT_DATAVISUALIZATION_EXPORT QCustom3DVolume : public QCustom3DItem +{ + Q_OBJECT + Q_PROPERTY(int textureWidth READ textureWidth WRITE setTextureWidth NOTIFY textureWidthChanged) + Q_PROPERTY(int textureHeight READ textureHeight WRITE setTextureHeight NOTIFY textureHeightChanged) + Q_PROPERTY(int textureDepth READ textureDepth WRITE setTextureDepth NOTIFY textureDepthChanged) + Q_PROPERTY(int sliceIndexX READ sliceIndexX WRITE setSliceIndexX NOTIFY sliceIndexXChanged) + Q_PROPERTY(int sliceIndexY READ sliceIndexY WRITE setSliceIndexY NOTIFY sliceIndexYChanged) + Q_PROPERTY(int sliceIndexZ READ sliceIndexZ WRITE setSliceIndexZ NOTIFY sliceIndexZChanged) + Q_PROPERTY(QVector<QRgb> colorTable READ colorTable WRITE setColorTable NOTIFY colorTableChanged) + Q_PROPERTY(QVector<uchar> *textureData READ textureData WRITE setTextureData NOTIFY textureDataChanged) + Q_PROPERTY(float alphaMultiplier READ alphaMultiplier WRITE setAlphaMultiplier NOTIFY alphaMultiplierChanged) + Q_PROPERTY(bool preserveOpacity READ preserveOpacity WRITE setPreserveOpacity NOTIFY preserveOpacityChanged) + Q_PROPERTY(bool useHighDefShader READ useHighDefShader WRITE setUseHighDefShader NOTIFY useHighDefShaderChanged) + Q_PROPERTY(bool drawSlices READ drawSlices WRITE setDrawSlices NOTIFY drawSlicesChanged) + Q_PROPERTY(bool drawSliceFrames READ drawSliceFrames WRITE setDrawSliceFrames NOTIFY drawSliceFramesChanged) + Q_PROPERTY(QColor sliceFrameColor READ sliceFrameColor WRITE setSliceFrameColor NOTIFY sliceFrameColorChanged) + Q_PROPERTY(QVector3D sliceFrameWidths READ sliceFrameWidths WRITE setSliceFrameWidths NOTIFY sliceFrameWidthsChanged) + Q_PROPERTY(QVector3D sliceFrameGaps READ sliceFrameGaps WRITE setSliceFrameGaps NOTIFY sliceFrameGapsChanged) + Q_PROPERTY(QVector3D sliceFrameThicknesses READ sliceFrameThicknesses WRITE setSliceFrameThicknesses NOTIFY sliceFrameThicknessesChanged) + +public: + + explicit QCustom3DVolume(QObject *parent = 0); + explicit QCustom3DVolume(const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, + QVector<uchar> *textureData, QImage::Format textureFormat, + const QVector<QRgb> &colorTable, QObject *parent = 0); + virtual ~QCustom3DVolume(); + + void setTextureWidth(int value); + int textureWidth() const; + void setTextureHeight(int value); + int textureHeight() const; + void setTextureDepth(int value); + int textureDepth() const; + void setTextureDimensions(int width, int height, int depth); + int textureDataWidth() const; + + void setSliceIndexX(int value); + int sliceIndexX() const; + void setSliceIndexY(int value); + int sliceIndexY() const; + void setSliceIndexZ(int value); + int sliceIndexZ() const; + void setSliceIndices(int x, int y, int z); + + void setColorTable(const QVector<QRgb> &colors); + QVector<QRgb> colorTable() const; + + void setTextureData(QVector<uchar> *data); + QVector<uchar> *createTextureData(const QVector<QImage *> &images); + QVector<uchar> *textureData() const; + void setSubTextureData(Qt::Axis axis, int index, const uchar *data); + void setSubTextureData(Qt::Axis axis, int index, const QImage &image); + + void setTextureFormat(QImage::Format format); + QImage::Format textureFormat() const; + + void setAlphaMultiplier(float mult); + float alphaMultiplier() const; + void setPreserveOpacity(bool enable); + bool preserveOpacity() const; + + void setUseHighDefShader(bool enable); + bool useHighDefShader() const; + + void setDrawSlices(bool enable); + bool drawSlices() const; + void setDrawSliceFrames(bool enable); + bool drawSliceFrames() const; + + void setSliceFrameColor(const QColor &color); + QColor sliceFrameColor() const; + void setSliceFrameWidths(const QVector3D &values); + QVector3D sliceFrameWidths() const; + void setSliceFrameGaps(const QVector3D &values); + QVector3D sliceFrameGaps() const; + void setSliceFrameThicknesses(const QVector3D &values); + QVector3D sliceFrameThicknesses() const; + + QImage renderSlice(Qt::Axis axis, int index); + +signals: + void textureWidthChanged(int value); + void textureHeightChanged(int value); + void textureDepthChanged(int value); + void sliceIndexXChanged(int value); + void sliceIndexYChanged(int value); + void sliceIndexZChanged(int value); + void colorTableChanged(); + void textureDataChanged(QVector<uchar> *data); + void textureFormatChanged(QImage::Format format); + void alphaMultiplierChanged(float mult); + void preserveOpacityChanged(bool enabled); + void useHighDefShaderChanged(bool enabled); + void drawSlicesChanged(bool enabled); + void drawSliceFramesChanged(bool enabled); + void sliceFrameColorChanged(const QColor &color); + void sliceFrameWidthsChanged(const QVector3D &values); + void sliceFrameGapsChanged(const QVector3D &values); + void sliceFrameThicknessesChanged(const QVector3D &values); + +protected: + QCustom3DVolumePrivate *dptr(); + const QCustom3DVolumePrivate *dptrc() const; + +private: + Q_DISABLE_COPY(QCustom3DVolume) + + friend class Abstract3DRenderer; +}; + +QT_END_NAMESPACE_DATAVISUALIZATION + +#endif diff --git a/src/datavisualization/data/qcustom3dvolume_p.h b/src/datavisualization/data/qcustom3dvolume_p.h new file mode 100644 index 00000000..642d2af5 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc +** All rights reserved. +** For any questions to Digia, please use contact form at http://qt.digia.com +** +** This file is part of the QtDataVisualization module. +** +** Licensees holding valid Qt Enterprise licenses may use this file in +** accordance with the Qt Enterprise License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. +** +** If you have questions regarding the use of this file, please use +** contact form at http://qt.digia.com +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QtDataVisualization API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCUSTOM3DVOLUME_P_H +#define QCUSTOM3DVOLUME_P_H + +#include "qcustom3dvolume.h" +#include "qcustom3ditem_p.h" + +QT_BEGIN_NAMESPACE_DATAVISUALIZATION + +struct QCustomVolumeDirtyBitField { + bool textureDimensionsDirty : 1; + bool slicesDirty : 1; + bool colorTableDirty : 1; + bool textureDataDirty : 1; + bool textureFormatDirty : 1; + bool alphaDirty : 1; + bool shaderDirty : 1; + + QCustomVolumeDirtyBitField() + : textureDimensionsDirty(false), + slicesDirty(false), + colorTableDirty(false), + textureDataDirty(false), + textureFormatDirty(false), + alphaDirty(false), + shaderDirty(false) + { + } +}; + +class QCustom3DVolumePrivate : public QCustom3DItemPrivate +{ + Q_OBJECT + +public: + QCustom3DVolumePrivate(QCustom3DVolume *q); + QCustom3DVolumePrivate(QCustom3DVolume *q, const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, QVector<uchar> *textureData, + QImage::Format textureFormat, const QVector<QRgb> &colorTable); + virtual ~QCustom3DVolumePrivate(); + + void resetDirtyBits(); + QImage renderSlice(Qt::Axis axis, int index); + + QCustom3DVolume *qptr(); + +public: + int m_textureWidth; + int m_textureHeight; + int m_textureDepth; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + + QImage::Format m_textureFormat; + QVector<QRgb> m_colorTable; + QVector<uchar> *m_textureData; + + float m_alphaMultiplier; + bool m_preserveOpacity; + bool m_useHighDefShader; + + bool m_drawSlices; + bool m_drawSliceFrames; + QColor m_sliceFrameColor; + QVector3D m_sliceFrameWidths; + QVector3D m_sliceFrameGaps; + QVector3D m_sliceFrameThicknesses; + + QCustomVolumeDirtyBitField m_dirtyBitsVolume; + +private: + int multipliedAlphaValue(int alpha); + + friend class QCustom3DVolume; +}; + +QT_END_NAMESPACE_DATAVISUALIZATION + +#endif diff --git a/src/datavisualization/data/qheightmapsurfacedataproxy.cpp b/src/datavisualization/data/qheightmapsurfacedataproxy.cpp index d64046be..85aed432 100644 --- a/src/datavisualization/data/qheightmapsurfacedataproxy.cpp +++ b/src/datavisualization/data/qheightmapsurfacedataproxy.cpp @@ -84,7 +84,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::minXValue * - * The minimum X value for the generated surface points. + * The minimum X value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -92,7 +92,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxXValue * - * The maximum X value for the generated surface points. + * The maximum X value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -100,7 +100,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::minZValue * - * The minimum Z value for the generated surface points. + * The minimum Z value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -108,7 +108,7 @@ const float defaultMaxValue = 10.0f; /*! * \qmlproperty real HeightMapSurfaceDataProxy::maxZValue * - * The maximum Z value for the generated surface points. + * The maximum Z value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -228,7 +228,7 @@ void QHeightMapSurfaceDataProxy::setValueRanges(float minX, float maxX, float mi /*! * \property QHeightMapSurfaceDataProxy::minXValue * - * The minimum X value for the generated surface points. + * The minimum X value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -245,7 +245,7 @@ float QHeightMapSurfaceDataProxy::minXValue() const /*! * \property QHeightMapSurfaceDataProxy::maxXValue * - * The maximum X value for the generated surface points. + * The maximum X value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -262,7 +262,7 @@ float QHeightMapSurfaceDataProxy::maxXValue() const /*! * \property QHeightMapSurfaceDataProxy::minZValue * - * The minimum Z value for the generated surface points. + * The minimum Z value for the generated surface points. Defaults to \c{0.0}. * When setting this property the corresponding maximum value is adjusted if necessary, * to ensure that the range remains valid. */ @@ -279,7 +279,7 @@ float QHeightMapSurfaceDataProxy::minZValue() const /*! * \property QHeightMapSurfaceDataProxy::maxZValue * - * The maximum Z value for the generated surface points. + * The maximum Z value for the generated surface points. Defaults to \c{10.0}. * When setting this property the corresponding minimum value is adjusted if necessary, * to ensure that the range remains valid. */ diff --git a/src/datavisualization/data/qsurface3dseries.cpp b/src/datavisualization/data/qsurface3dseries.cpp index 1107d721..7d4dacfe 100644 --- a/src/datavisualization/data/qsurface3dseries.cpp +++ b/src/datavisualization/data/qsurface3dseries.cpp @@ -141,6 +141,14 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty string Surface3DSeries::textureFile + * + * Holds the texture file name for the surface texture. To clear the texture, set empty + * file name. + */ + + +/*! * \enum QSurface3DSeries::DrawFlag * * Drawing mode of the surface. Values of this enumeration can be combined with OR operator. @@ -300,6 +308,57 @@ QSurface3DSeries::DrawFlags QSurface3DSeries::drawMode() const } /*! + * \property QSurface3DSeries::texture + * + * Set the \a texture as a QImage for the surface. To clear the texture, set empty + * QImage as texture. + */ +void QSurface3DSeries::setTexture(const QImage &texture) +{ + if (dptr()->m_texture != texture) { + dptr()->setTexture(texture); + + emit textureChanged(texture); + dptr()->m_textureFile.clear(); + } +} + +QImage QSurface3DSeries::texture() const +{ + return dptrc()->m_texture; +} + +/*! + * \property QSurface3DSeries::textureFile + * + * Holds the texture file name for the surface texture. To clear the texture, set empty + * file name. + */ +void QSurface3DSeries::setTextureFile(const QString &filename) +{ + if (dptr()->m_textureFile != filename) { + if (filename.isEmpty()) { + setTexture(QImage()); + } else { + QImage image(filename); + if (image.isNull()) { + qWarning() << "Warning: Tried to set invalid image file as surface texture."; + return; + } + setTexture(image); + } + + dptr()->m_textureFile = filename; + emit textureFileChanged(filename); + } +} + +QString QSurface3DSeries::textureFile() const +{ + return dptrc()->m_textureFile; +} + +/*! * \internal */ QSurface3DSeriesPrivate *QSurface3DSeries::dptr() @@ -445,4 +504,11 @@ void QSurface3DSeriesPrivate::setDrawMode(QSurface3DSeries::DrawFlags mode) } } +void QSurface3DSeriesPrivate::setTexture(const QImage &texture) +{ + m_texture = texture; + if (static_cast<Surface3DController *>(m_controller)) + static_cast<Surface3DController *>(m_controller)->updateSurfaceTexture(qptr()); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/qsurface3dseries.h b/src/datavisualization/data/qsurface3dseries.h index 64df7202..7051e583 100644 --- a/src/datavisualization/data/qsurface3dseries.h +++ b/src/datavisualization/data/qsurface3dseries.h @@ -35,6 +35,8 @@ class QT_DATAVISUALIZATION_EXPORT QSurface3DSeries : public QAbstract3DSeries Q_PROPERTY(bool flatShadingEnabled READ isFlatShadingEnabled WRITE setFlatShadingEnabled NOTIFY flatShadingEnabledChanged) Q_PROPERTY(bool flatShadingSupported READ isFlatShadingSupported NOTIFY flatShadingSupportedChanged) Q_PROPERTY(DrawFlags drawMode READ drawMode WRITE setDrawMode NOTIFY drawModeChanged) + Q_PROPERTY(QImage texture READ texture WRITE setTexture NOTIFY textureChanged) + Q_PROPERTY(QString textureFile READ textureFile WRITE setTextureFile NOTIFY textureFileChanged) public: enum DrawFlag { @@ -63,12 +65,19 @@ public: bool isFlatShadingSupported() const; + void setTexture(const QImage &texture); + QImage texture() const; + void setTextureFile(const QString &filename); + QString textureFile() const; + signals: void dataProxyChanged(QSurfaceDataProxy *proxy); void selectedPointChanged(const QPoint &position); void flatShadingEnabledChanged(bool enable); void flatShadingSupportedChanged(bool enable); void drawModeChanged(QSurface3DSeries::DrawFlags mode); + void textureChanged(const QImage &image); + void textureFileChanged(const QString &filename); protected: explicit QSurface3DSeries(QSurface3DSeriesPrivate *d, QObject *parent = 0); diff --git a/src/datavisualization/data/qsurface3dseries_p.h b/src/datavisualization/data/qsurface3dseries_p.h index d4cc2820..270a3ad1 100644 --- a/src/datavisualization/data/qsurface3dseries_p.h +++ b/src/datavisualization/data/qsurface3dseries_p.h @@ -48,6 +48,7 @@ public: void setSelectedPoint(const QPoint &position); void setFlatShadingEnabled(bool enabled); void setDrawMode(QSurface3DSeries::DrawFlags mode); + void setTexture(const QImage &texture); private: QSurface3DSeries *qptr(); @@ -55,6 +56,8 @@ private: QPoint m_selectedPoint; bool m_flatShadingEnabled; QSurface3DSeries::DrawFlags m_drawMode; + QImage m_texture; + QString m_textureFile; private: friend class QSurface3DSeries; diff --git a/src/datavisualization/data/qsurfacedataproxy.cpp b/src/datavisualization/data/qsurfacedataproxy.cpp index dbe7cc49..12b24465 100644 --- a/src/datavisualization/data/qsurfacedataproxy.cpp +++ b/src/datavisualization/data/qsurfacedataproxy.cpp @@ -53,6 +53,10 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * \note Surfaces with less than two rows or columns are not considered valid surfaces and will * not be rendered. * + * \note On some environments, surfaces with a lot of visible vertices may not render, because + * they exceed the per-draw vertex count supported by the graphics driver. + * This is mostly an issue on 32bit and/or OpenGL ES2 platforms. + * * \sa {Qt Data Visualization Data Handling} */ diff --git a/src/datavisualization/datavisualization.pro b/src/datavisualization/datavisualization.pro index 44f0f60c..645a1733 100644 --- a/src/datavisualization/datavisualization.pro +++ b/src/datavisualization/datavisualization.pro @@ -1,5 +1,5 @@ # Target can't start with 'Qt' as it gets major version number inserted into it in that case, -# which we don't want. Exception is mac bundles, where the target name must match the module name +# which we don't want. Exception is OS X bundles, where the target name must match the module name mac:CONFIG(shared, static|shared):contains(QT_CONFIG, qt_framework) { TARGET = QtDataVisualization } else { @@ -7,7 +7,8 @@ mac:CONFIG(shared, static|shared):contains(QT_CONFIG, qt_framework) { } message($$QT_CONFIG) -QT = core gui +QT += core gui +osx: QT += gui-private DEFINES += QT_DATAVISUALIZATION_LIBRARY # Fix exports in static builds for applications linking datavisualization module @@ -19,9 +20,8 @@ QMAKE_DOCS = $$PWD/doc/qtdatavisualization.qdocconf load(qt_module) -include($$PWD/common.pri) -include($$PWD/engine/engine.pri) include($$PWD/global/global.pri) +include($$PWD/engine/engine.pri) include($$PWD/utils/utils.pri) include($$PWD/theme/theme.pri) include($$PWD/axis/axis.pri) diff --git a/src/datavisualization/doc/qtdatavisualization.qdocconf b/src/datavisualization/doc/qtdatavisualization.qdocconf index 6e9f09b4..4d63b526 100644 --- a/src/datavisualization/doc/qtdatavisualization.qdocconf +++ b/src/datavisualization/doc/qtdatavisualization.qdocconf @@ -6,7 +6,7 @@ include($QT_INSTALL_DOCS/global/qt-html-templates-offline.qdocconf) project = QtDataVisualization description = Qt Data Visualization Reference Documentation -version = 1.1.0 +version = 1.2.0 exampledirs += ../../../examples/datavisualization \ snippets @@ -27,14 +27,14 @@ indexes += $QT_INSTALL_DOCS/qtcore/qtcore.index \ qhp.projects = QtDataVisualization qhp.QtDataVisualization.file = qtdatavisualization.qhp -qhp.QtDataVisualization.namespace = com.digia.qtdatavisualization.110 +qhp.QtDataVisualization.namespace = com.digia.qtdatavisualization.120 qhp.QtDataVisualization.virtualFolder = qtdatavisualization qhp.QtDataVisualization.indexTitle = Qt Data Visualization qhp.QtDataVisualization.indexRoot = -qhp.QtDataVisualization.filterAttributes = qtdatavisualization 1.1.0 qtrefdoc -qhp.QtDataVisualization.customFilters.Qt.name = QtDataVisualization 1.1.0 -qhp.QtDataVisualization.customFilters.Qt.filterAttributes = qtdatavisualization 1.1.0 +qhp.QtDataVisualization.filterAttributes = qtdatavisualization 1.2.0 qtrefdoc +qhp.QtDataVisualization.customFilters.Qt.name = QtDataVisualization 1.2.0 +qhp.QtDataVisualization.customFilters.Qt.filterAttributes = qtdatavisualization 1.2.0 qhp.QtDataVisualization.subprojects = gettingstarted examples classes types qhp.QtDataVisualization.subprojects.gettingstarted.title = Getting Started qhp.QtDataVisualization.subprojects.gettingstarted.indexTitle = Qt Data Visualization Getting Started @@ -50,7 +50,7 @@ qhp.QtDataVisualization.subprojects.classes.selectors = class qhp.QtDataVisualization.subprojects.classes.sortPages = true qhp.QtDataVisualization.subprojects.types.title = QML Types qhp.QtDataVisualization.subprojects.types.indexTitle = Qt Data Visualization QML Types -qhp.QtDataVisualization.subprojects.types.selectors = fake:qmlclass +qhp.QtDataVisualization.subprojects.types.selectors = qmlclass qhp.QtDataVisualization.subprojects.types.sortPages = true navigation.landingpage = Qt Data Visualization diff --git a/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp b/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp index 56bfc5ee..1ca3f597 100644 --- a/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp +++ b/src/datavisualization/doc/snippets/doc_src_qmldatavisualization.cpp @@ -17,12 +17,12 @@ ****************************************************************************/ //! [0] -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 //! [0] //! [1] import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Item { width: 640 @@ -61,7 +61,7 @@ Item { //! [2] import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Item { width: 640 @@ -94,7 +94,7 @@ Item { //! [3] import QtQuick 2.0 -import QtDataVisualization 1.1 +import QtDataVisualization 1.2 Item { width: 640 diff --git a/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc b/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc index 2cc3eece..b5a678e5 100644 --- a/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization-qml-abstractdeclarative.qdoc @@ -153,6 +153,32 @@ */ /*! + * \qmlproperty bool AbstractGraph3D::polar + * \since QtDataVisualization 1.2 + * + * If \c {true}, the horizontal axes are changed into polar axes. The X axis becomes the + * angular axis and the Z axis becomes the radial axis. + * Polar mode is not available for bar graphs. + * + * Defaults to \c{false}. + * + * \sa orthoProjection, radialLabelOffset + */ + +/*! + * \qmlproperty real AbstractGraph3D::radialLabelOffset + * \since QtDataVisualization 1.2 + * + * This property specifies the normalized horizontal offset for the axis labels of the radial + * polar axis. The value 0.0 indicates the labels should be drawn next to the 0-angle angular + * axis grid line. The value 1.0 indicates the labels are drawn on their normal place at the edge + * of the graph background. + * This property is ignored if polar property value is \c{false}. Defaults to 1.0. + * + * \sa polar + */ + +/*! * \qmlmethod void AbstractGraph3D::clearSelection() * Clears selection from all attached series. */ @@ -289,22 +315,123 @@ * \qmlproperty real AbstractGraph3D::aspectRatio * \since QtDataVisualization 1.1 * - * Aspect ratio of the graph data. This is the ratio of data scaling between horizontal and - * vertical axes. Defaults to \c{2.0}. + * The aspect ratio is the ratio of the graph scaling between the longest axis on the horizontal + * plane and the Y-axis. Defaults to \c{2.0}. * * \note Has no effect on Bars3D. + * + * \sa horizontalAspectRatio + */ + +/*! + * \qmlproperty real AbstractGraph3D::horizontalAspectRatio + * \since QtDataVisualization 1.2 + * + * The horizontal aspect ratio is the ratio of the graph scaling between the X and Z axes. + * Value of 0.0 indicates automatic scaling according to axis ranges. + * Defaults to \c{0.0}. + * + * \note Has no effect on Bars3D, which handles scaling on the horizontal plane via + * \l{Bars3D::barThickness}{barThickness} and \l{Bars3D::barSpacing}{barSpacing} properties. + * Polar graphs also ignore this property. + * + * \sa aspectRatio, polar, Bars3D::barThickness, Bars3D::barSpacing */ /*! * \qmlproperty AbstractGraph3D.OptimizationHints AbstractGraph3D::optimizationHints - * \since Qt Data Visualization 1.1 - * - * Defines if the rendering optimization is default or static. Default mode provides the full feature set at - * reasonable performance. Static is a beta level feature and currently supports only a subset of the - * features on the Scatter graph. Missing features are object gradient for mesh objects, both gradients - * for points, and diffuse and specular color on rotations. At this point static is intended just for - * introducing a new feature. It optimizes graph rendering and is ideal for large non-changing data - * sets. It is slower with dynamic data changes and item rotations. Selection is not optimized, so using it - * with massive data sets is not advisable. - * Defaults to \c{OptimizationDefault} + * \since QtDataVisualization 1.1 + * + * Defines if the rendering optimization is default or static. Default mode provides the full + * feature set at reasonable performance. Static mode optimizes graph rendering and is ideal for + * large non-changing data sets. It is slower with dynamic data changes and item rotations. + * Selection is not optimized, so using it with massive data sets is not advisable. + * Static works only on the Scatter graph. + * Defaults to \c{OptimizationDefault}. + * + * \note On some environments, large graphs using static optimization may not render, because + * all of the items are rendered using a single draw call, and different graphics drivers have + * different maximum vertice counts per call that they support. + * This is mostly an issue on 32bit and/or OpenGL ES2 platforms. + * To work around this issue, choose an item mesh with low vertex count or use the point mesh. + * + * \sa Abstract3DSeries::mesh + */ + +/*! + * \qmlproperty bool AbstractGraph3D::reflection + * \since QtDataVisualization 1.2 + * + * Sets floor reflections on or off. Defaults to \c{false}. + * + * \note Affects only Bars3D. + * + * \note In Bars3D graphs holding both positive and negative values, reflections are not supported + * for custom items that intersect the floor plane. In that case, reflections should be turned off + * to avoid incorrect rendering. + * + * \sa reflectivity + */ + +/*! + * \qmlproperty real AbstractGraph3D::reflectivity + * \since QtDataVisualization 1.2 + * + * Adjusts floor reflectivity, larger number being more reflective. Valid range is \c{[0...1]}. + * Defaults to \c{0.5}. + * + * \note Affects only Bars3D. + * + * \sa reflection + */ + +/*! + * \qmlproperty locale AbstractGraph3D::locale + * \since QtDataVisualization 1.2 + * + * Sets the locale used for formatting various numeric labels. + * Defaults to \c{"C"} locale. + * + * \sa ValueAxis3D::labelFormat + */ + +/*! + * \qmlproperty vector3d AbstractGraph3D::queriedGraphPosition + * \since QtDataVisualization 1.2 + * + * This read-only property contains the latest graph position values along each axis queried using + * Scene3D::graphPositionQuery. The values are normalized to range \c{[-1, 1]}. + * If the queried position was outside the graph bounds, the values + * will not reflect the real position, but will instead be some undefined position outside + * the range \c{[-1, 1]}. The value will be undefined before any queries are made. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs only allow querying graph position at the graph floor level, + * so the Y-value is always zero for bar graphs and the valid queries can be only made at + * screen positions that contain the floor of the graph. + * + * \sa Scene3D::graphPositionQuery + */ + +/*! + * \qmlproperty real AbstractGraph3D::margin + * \since QtDataVisualization 1.2 + * + * This property contains the absolute value used for graph margin. The graph margin is the space + * left between the edge of the plottable graph area and the edge of the graph background. + * If the margin value is negative, the margins are determined automatically and can vary according + * to size of the items in the series and the type of the graph. + * The value is interpreted as a fraction of Y-axis range, provided the graph aspect ratios have + * not beed changed from the defaults. + * Defaults to \c{-1.0}. + * + * \note Having smaller than the automatically determined margin on scatter graph can cause + * the scatter items at the edges of the graph to overlap with the graph background. + * + * \note On scatter and surface graphs, if the margin is comparatively small to the axis label + * size, the positions of the edge labels of the axes are adjusted to avoid overlap with + * the edge labels of the neighboring axes. */ diff --git a/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc b/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc index 6ee51742..0348652a 100644 --- a/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization-qml-bars3d.qdoc @@ -115,6 +115,14 @@ */ /*! + * \qmlproperty real Bars3D::floorLevel + * + * The desired floor level for the bar graph in Y-axis data coordinates. + * The actual floor level cannot go below Y-axis minimum or above Y-axis maximum. + * Defaults to zero. + */ + +/*! * \qmlmethod void Bars3D::addSeries(Bar3DSeries series) * Adds the \a series to the graph. A graph can contain multiple series, but only one set of axes, * so the rows and columns of all series must match for the visualized data to be meaningful. diff --git a/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc b/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc index 23a9a004..2b83b807 100644 --- a/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization-qml-surface3d.qdoc @@ -73,11 +73,6 @@ */ /*! - \qmlproperty ColorGradient Surface3D::gradient - The current surface gradient. Setting this property replaces the previous gradient. - */ - -/*! * \qmlproperty list<Surface3DSeries> Surface3D::seriesList * \default * This property holds the series of the graph. @@ -86,6 +81,22 @@ */ /*! + * \qmlproperty bool Surface3D::flipHorizontalGrid + * \since QtDataVisualization 1.2 + * + * In some use cases the horizontal axis grid is mostly covered by the surface, so it can be more + * useful to display the horizontal axis grid on top of the graph rather than on the bottom. + * A typical use case for this is showing 2D spectrograms using orthoGraphic projection with + * a top-down viewpoint. + * + * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal background + * of the graph. + * If \c{true}, the horizontal axis grid and labels are drawn on the opposite side of the graph + * from the horizontal background. + * Defaults to \c{false}. + */ + +/*! * \qmlmethod void Surface3D::addSeries(Surface3DSeries series) * Adds the \a series to the graph. */ diff --git a/src/datavisualization/doc/src/qtdatavisualization.qdoc b/src/datavisualization/doc/src/qtdatavisualization.qdoc index af419814..b07074b1 100644 --- a/src/datavisualization/doc/src/qtdatavisualization.qdoc +++ b/src/datavisualization/doc/src/qtdatavisualization.qdoc @@ -37,7 +37,7 @@ */ /*! - \qmlmodule QtDataVisualization 1.1 + \qmlmodule QtDataVisualization 1.2 \title Qt Data Visualization QML Types \ingroup qmlmodules @@ -98,7 +98,7 @@ \row \li Windows (MSVC) \li nmake \row - \li OSX \li make + \li OS X \li make \endtable The above generates the default makefiles for your configuration, which is typically @@ -127,7 +127,7 @@ make release \endcode - For both builds (Windows/Mac only): + For both builds (Windows/OS X only): \code qmake CONFIG+="debug_and_release build_all" make @@ -323,20 +323,21 @@ \title Qt Data Visualization Known Issues \list - \li Android doesn't support both widgets and OpenGL simultaneously, so only - the Qt Quick 2 API is usable in practice in Android. + \li Some platforms like Android and WinRT cannot handle multiple native windows properly, + so only the Qt Quick 2 graphs are available in practice for those platforms. \li Shadows are not supported with OpenGL ES2 (including Angle builds in Windows). \li Anti-aliasing doesn't work with OpenGL ES2 (including Angle builds in Windows). + \li QCustom3DVolume items are not supported with OpenGL ES2 (including Angle builds in + Windows). \li Surfaces with non-straight rows and columns do not always render properly. \li Q3DLight class (and Light3D QML item) are currently not usable for anything. - \li Changing any of Q3DScene properties affecting subviewports currently has no effect. - \li The color style Q3DTheme::ColorStyleObjectGradient doesn't work for surface graphs. + \li Changing most of Q3DScene properties affecting subviewports currently has no effect. \li Widget based examples layout incorrectly in iOS. \li Reparenting a graph to an item in another QQuickWindow is not supported. - \li There is a low-impact binary break between 1.0 and 1.1. The break is due to a QML type - registration conflict with QAbstractItemModel between QtDataVisualization and - QtCommercial.Charts. Introducing the binary break makes it possible to use both - Charts and Data Visualization in the same QML application. + \li Android builds of QML applications importing QtDataVisualization also require + "QT += datavisualization" in the pro file. This is because Qt Data Visualization + QML plugin has a dependency to Qt Data Visualization C++ library, which Qt Creator + doesn't automatically add to the deployment package. \endlist */ diff --git a/src/datavisualization/engine/abstract3dcontroller.cpp b/src/datavisualization/engine/abstract3dcontroller.cpp index 30434dca..275d0fe2 100644 --- a/src/datavisualization/engine/abstract3dcontroller.cpp +++ b/src/datavisualization/engine/abstract3dcontroller.cpp @@ -25,6 +25,7 @@ #include "thememanager_p.h" #include "q3dtheme_p.h" #include "qcustom3ditem_p.h" +#include "utils_p.h" #include <QtCore/QThread> #include <QtGui/QOpenGLFramebufferObject> @@ -37,8 +38,12 @@ Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scen m_selectionMode(QAbstract3DGraph::SelectionItem), m_shadowQuality(QAbstract3DGraph::ShadowQualityMedium), m_useOrthoProjection(false), - m_aspectRatio(2.0f), + m_aspectRatio(2.0), + m_horizontalAspectRatio(0.0), m_optimizationHints(QAbstract3DGraph::OptimizationDefault), + m_reflectionEnabled(false), + m_reflectivity(0.5), + m_locale(QLocale::c()), m_scene(scene), m_activeInputHandler(0), m_axisX(0), @@ -50,12 +55,19 @@ Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scen m_isCustomItemDirty(true), m_isSeriesVisualsDirty(true), m_renderPending(false), + m_isPolar(false), + m_radialLabelOffset(1.0f), m_measureFps(false), m_numFrames(0), - m_currentFps(0.0) + m_currentFps(0.0), + m_clickedType(QAbstract3DGraph::ElementNone), + m_selectedLabelIndex(-1), + m_selectedCustomItemIndex(-1), + m_margin(-1.0) { if (!m_scene) m_scene = new Q3DScene; + m_scene->setParent(this); // Set initial theme Q3DTheme *defaultTheme = new Q3DTheme(Q3DTheme::ThemeQt); @@ -72,10 +84,6 @@ Abstract3DController::Abstract3DController(QRect initialViewport, Q3DScene *scen inputHandler = new QTouch3DInputHandler(); inputHandler->d_ptr->m_isDefaultHandler = true; setActiveInputHandler(inputHandler); - connect(inputHandler, &QAbstract3DInputHandler::inputViewChanged, this, - &Abstract3DController::handleInputViewChanged); - connect(inputHandler, &QAbstract3DInputHandler::positionChanged, this, - &Abstract3DController::handleInputPositionChanged); connect(m_scene->d_ptr.data(), &Q3DScenePrivate::needRender, this, &Abstract3DController::emitNeedRender); } @@ -93,7 +101,7 @@ Abstract3DController::~Abstract3DController() void Abstract3DController::destroyRenderer() { // Renderer can be in another thread, don't delete it directly in that case - if (m_renderer && m_renderer->thread() != QThread::currentThread()) + if (m_renderer && m_renderer->thread() && m_renderer->thread() != this->thread()) m_renderer->deleteLater(); else delete m_renderer; @@ -107,6 +115,13 @@ void Abstract3DController::destroyRenderer() void Abstract3DController::setRenderer(Abstract3DRenderer *renderer) { m_renderer = renderer; + + // If renderer is created in different thread than controller, make sure renderer gets + // destroyed before the render thread finishes. + if (renderer->thread() != this->thread()) { + QObject::connect(renderer->thread(), &QThread::finished, this, + &Abstract3DController::destroyRenderer, Qt::DirectConnection); + } } void Abstract3DController::addSeries(QAbstract3DSeries *series) @@ -163,11 +178,14 @@ void Abstract3DController::synchDataToRenderer() { // Subclass implementations check for renderer validity already, so no need to check here. - // If there is a pending click from renderer, handle that first. - if (m_renderer->isClickPending()) { + m_renderPending = false; + + // If there are pending queries, handle those first + if (m_renderer->isGraphPositionQueryResolved()) + handlePendingGraphPositionQuery(); + + if (m_renderer->isClickQueryResolved()) handlePendingClick(); - m_renderer->clearClickPending(); - } startRecordingRemovesAndInserts(); @@ -176,6 +194,16 @@ void Abstract3DController::synchDataToRenderer() m_renderer->updateTheme(m_themeManager->activeTheme()); + if (m_changeTracker.polarChanged) { + m_renderer->updatePolar(m_isPolar); + m_changeTracker.polarChanged = false; + } + + if (m_changeTracker.radialLabelOffsetChanged) { + m_renderer->updateRadialLabelOffset(m_radialLabelOffset); + m_changeTracker.radialLabelOffsetChanged = false; + } + if (m_changeTracker.shadowQualityChanged) { m_renderer->updateShadowQuality(m_shadowQuality); m_changeTracker.shadowQualityChanged = false; @@ -192,15 +220,31 @@ void Abstract3DController::synchDataToRenderer() } if (m_changeTracker.aspectRatioChanged) { - m_renderer->updateAspectRatio(m_aspectRatio); + m_renderer->updateAspectRatio(float(m_aspectRatio)); m_changeTracker.aspectRatioChanged = false; } + if (m_changeTracker.horizontalAspectRatioChanged) { + m_renderer->updateHorizontalAspectRatio(float(m_horizontalAspectRatio)); + m_changeTracker.horizontalAspectRatioChanged = false; + } + if (m_changeTracker.optimizationHintChanged) { m_renderer->updateOptimizationHint(m_optimizationHints); m_changeTracker.optimizationHintChanged = false; } + if (m_changeTracker.reflectionChanged) { + m_renderer->m_reflectionEnabled = m_reflectionEnabled; + m_changeTracker.reflectionChanged = false; + } + + if (m_changeTracker.reflectivityChanged) { + // Invert value to match functionality to the property description + m_renderer->m_reflectivity = -(m_reflectivity - 1.0); + m_changeTracker.reflectivityChanged = false; + } + if (m_changeTracker.axisXFormatterChanged) { m_changeTracker.axisXFormatterChanged = false; if (m_axisX->type() & QAbstract3DAxis::AxisTypeValue) { @@ -445,6 +489,11 @@ void Abstract3DController::synchDataToRenderer() m_changeTracker.axisZTitleFixedChanged = false; } + if (m_changeTracker.marginChanged) { + m_renderer->updateMargin(float(m_margin)); + m_changeTracker.marginChanged = false; + } + if (m_changedSeriesList.size()) { m_renderer->modifiedSeriesList(m_changedSeriesList); m_changedSeriesList.clear(); @@ -475,8 +524,6 @@ void Abstract3DController::synchDataToRenderer() void Abstract3DController::render(const GLuint defaultFboHandle) { - m_renderPending = false; - // If not initialized, do nothing. if (!m_renderer) return; @@ -765,8 +812,9 @@ void Abstract3DController::setActiveInputHandler(QAbstract3DInputHandler *inputH m_inputHandlers.removeAll(m_activeInputHandler); delete m_activeInputHandler; } else { - // Disconnect the old input handler from the scene + // Disconnect the old input handler m_activeInputHandler->setScene(0); + QObject::disconnect(m_activeInputHandler, 0, this, 0); } } @@ -775,9 +823,16 @@ void Abstract3DController::setActiveInputHandler(QAbstract3DInputHandler *inputH addInputHandler(inputHandler); m_activeInputHandler = inputHandler; - if (m_activeInputHandler) + if (m_activeInputHandler) { m_activeInputHandler->setScene(m_scene); + // Connect the input handler + QObject::connect(m_activeInputHandler, &QAbstract3DInputHandler::inputViewChanged, this, + &Abstract3DController::handleInputViewChanged); + QObject::connect(m_activeInputHandler, &QAbstract3DInputHandler::positionChanged, this, + &Abstract3DController::handleInputPositionChanged); + } + // Notify change of input handler emit activeInputHandlerChanged(m_activeInputHandler); } @@ -886,11 +941,7 @@ QAbstract3DGraph::OptimizationHints Abstract3DController::optimizationHints() co bool Abstract3DController::shadowsSupported() const { -#if defined(QT_OPENGL_ES_2) - return false; -#else - return true; -#endif + return !isOpenGLES(); } bool Abstract3DController::isSlicingActive() const @@ -989,6 +1040,11 @@ void Abstract3DController::releaseCustomItem(QCustom3DItem *item) } } +QList<QCustom3DItem *> Abstract3DController::customItems() const +{ + return m_customItems; +} + void Abstract3DController::updateCustomItem() { m_isCustomItemDirty = true; @@ -1303,6 +1359,11 @@ void Abstract3DController::markSeriesItemLabelsDirty() m_seriesList.at(i)->d_ptr->markItemLabelDirty(); } +bool Abstract3DController::isOpenGLES() const +{ + return Utils::isOpenGLES(); +} + void Abstract3DController::setAxisHelper(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis *axis, QAbstract3DAxis **axisPtr) { @@ -1381,6 +1442,8 @@ void Abstract3DController::setAxisHelper(QAbstract3DAxis::AxisOrientation orient handleAxisLabelFormatChangedBySender(valueAxis); handleAxisReversedChangedBySender(valueAxis); handleAxisFormatterDirtyBySender(valueAxis->dptr()); + + valueAxis->formatter()->setLocale(m_locale); } } @@ -1427,13 +1490,37 @@ void Abstract3DController::emitNeedRender() void Abstract3DController::handlePendingClick() { - QAbstract3DGraph::ElementType type = m_renderer->clickedType(); - emit elementSelected(type); + m_clickedType = m_renderer->clickedType(); + m_selectedLabelIndex = m_renderer->m_selectedLabelIndex; + m_selectedCustomItemIndex = m_renderer->m_selectedCustomItemIndex; + + // Invalidate query position to indicate the query has been handled, unless another + // point has been queried. + if (m_renderer->cachedClickQuery() == m_scene->selectionQueryPosition()) + m_scene->setSelectionQueryPosition(Q3DScene::invalidSelectionPoint()); + + m_renderer->clearClickQueryResolved(); + + emit elementSelected(m_clickedType); +} + +void Abstract3DController::handlePendingGraphPositionQuery() +{ + m_queriedGraphPosition = m_renderer->queriedGraphPosition(); + + // Invalidate query position to indicate the query has been handled, unless another + // point has been queried. + if (m_renderer->cachedGraphPositionQuery() == m_scene->graphPositionQuery()) + m_scene->setGraphPositionQuery(Q3DScene::invalidSelectionPoint()); + + m_renderer->clearGraphPositionQueryResolved(); + + emit queriedGraphPositionChanged(m_queriedGraphPosition); } int Abstract3DController::selectedLabelIndex() const { - int index = m_renderer->m_selectedLabelIndex; + int index = m_selectedLabelIndex; QAbstract3DAxis *axis = selectedAxis(); if (axis && axis->labels().count() <= index) index = -1; @@ -1443,7 +1530,7 @@ int Abstract3DController::selectedLabelIndex() const QAbstract3DAxis *Abstract3DController::selectedAxis() const { QAbstract3DAxis *axis = 0; - QAbstract3DGraph::ElementType type = m_renderer->clickedType(); + QAbstract3DGraph::ElementType type = m_clickedType; switch (type) { case QAbstract3DGraph::ElementAxisXLabel: axis = axisX(); @@ -1464,7 +1551,7 @@ QAbstract3DAxis *Abstract3DController::selectedAxis() const int Abstract3DController::selectedCustomItemIndex() const { - int index = m_renderer->m_selectedCustomItemIndex; + int index = m_selectedCustomItemIndex; if (m_customItems.count() <= index) index = -1; return index; @@ -1481,10 +1568,7 @@ QCustom3DItem *Abstract3DController::selectedCustomItem() const QAbstract3DGraph::ElementType Abstract3DController::selectedElement() const { - if (m_renderer) - return m_renderer->clickedType(); - else - return QAbstract3DGraph::ElementNone; + return m_clickedType; } void Abstract3DController::setOrthoProjection(bool enable) @@ -1505,7 +1589,7 @@ bool Abstract3DController::isOrthoProjection() const return m_useOrthoProjection; } -void Abstract3DController::setAspectRatio(float ratio) +void Abstract3DController::setAspectRatio(qreal ratio) { if (m_aspectRatio != ratio) { m_aspectRatio = ratio; @@ -1516,9 +1600,131 @@ void Abstract3DController::setAspectRatio(float ratio) } } -float Abstract3DController::aspectRatio() +qreal Abstract3DController::aspectRatio() { return m_aspectRatio; } +void Abstract3DController::setHorizontalAspectRatio(qreal ratio) +{ + if (m_horizontalAspectRatio != ratio) { + m_horizontalAspectRatio = ratio; + m_changeTracker.horizontalAspectRatioChanged = true; + emit horizontalAspectRatioChanged(m_horizontalAspectRatio); + m_isDataDirty = true; + emitNeedRender(); + } +} + +qreal Abstract3DController::horizontalAspectRatio() const +{ + return m_horizontalAspectRatio; +} + +void Abstract3DController::setReflection(bool enable) +{ + if (m_reflectionEnabled != enable) { + m_reflectionEnabled = enable; + m_changeTracker.reflectionChanged = true; + emit reflectionChanged(m_reflectionEnabled); + emitNeedRender(); + } +} + +bool Abstract3DController::reflection() const +{ + return m_reflectionEnabled; +} + +void Abstract3DController::setReflectivity(qreal reflectivity) +{ + if (m_reflectivity != reflectivity) { + m_reflectivity = reflectivity; + m_changeTracker.reflectivityChanged = true; + emit reflectivityChanged(m_reflectivity); + emitNeedRender(); + } +} + +qreal Abstract3DController::reflectivity() const +{ + return m_reflectivity; +} + +void Abstract3DController::setPolar(bool enable) +{ + if (enable != m_isPolar) { + m_isPolar = enable; + m_changeTracker.polarChanged = true; + m_isDataDirty = true; + emit polarChanged(m_isPolar); + emitNeedRender(); + } +} + +bool Abstract3DController::isPolar() const +{ + return m_isPolar; +} + +void Abstract3DController::setRadialLabelOffset(float offset) +{ + if (m_radialLabelOffset != offset) { + m_radialLabelOffset = offset; + m_changeTracker.radialLabelOffsetChanged = true; + emit radialLabelOffsetChanged(m_radialLabelOffset); + emitNeedRender(); + } +} + +float Abstract3DController::radialLabelOffset() const +{ + return m_radialLabelOffset; +} + +void Abstract3DController::setLocale(const QLocale &locale) +{ + if (m_locale != locale) { + m_locale = locale; + + // Value axis formatters need to be updated + QValue3DAxis *axis = qobject_cast<QValue3DAxis *>(m_axisX); + if (axis) + axis->formatter()->setLocale(m_locale); + axis = qobject_cast<QValue3DAxis *>(m_axisY); + if (axis) + axis->formatter()->setLocale(m_locale); + axis = qobject_cast<QValue3DAxis *>(m_axisZ); + if (axis) + axis->formatter()->setLocale(m_locale); + emit localeChanged(m_locale); + } +} + +QLocale Abstract3DController::locale() const +{ + return m_locale; +} + +QVector3D Abstract3DController::queriedGraphPosition() const +{ + return m_queriedGraphPosition; +} + +void Abstract3DController::setMargin(qreal margin) +{ + if (m_margin != margin) { + m_margin = margin; + m_changeTracker.marginChanged = true; + emit marginChanged(margin); + emitNeedRender(); + } +} + +qreal Abstract3DController::margin() const +{ + return m_margin; +} + + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/abstract3dcontroller_p.h b/src/datavisualization/engine/abstract3dcontroller_p.h index 0e4d1add..d5a1ac8f 100644 --- a/src/datavisualization/engine/abstract3dcontroller_p.h +++ b/src/datavisualization/engine/abstract3dcontroller_p.h @@ -38,6 +38,7 @@ #include "qcustom3ditem.h" #include <QtGui/QLinearGradient> #include <QtCore/QTime> +#include <QtCore/QLocale> class QOpenGLFramebufferObject; @@ -84,12 +85,18 @@ struct Abstract3DChangeBitField { bool axisYLabelAutoRotationChanged : 1; bool axisZLabelAutoRotationChanged : 1; bool aspectRatioChanged : 1; + bool horizontalAspectRatioChanged : 1; bool axisXTitleVisibilityChanged : 1; bool axisYTitleVisibilityChanged : 1; bool axisZTitleVisibilityChanged : 1; bool axisXTitleFixedChanged : 1; bool axisYTitleFixedChanged : 1; bool axisZTitleFixedChanged : 1; + bool polarChanged : 1; + bool radialLabelOffsetChanged : 1; + bool reflectionChanged : 1; + bool reflectivityChanged : 1; + bool marginChanged : 1; Abstract3DChangeBitField() : themeChanged(true), @@ -128,12 +135,18 @@ struct Abstract3DChangeBitField { axisYLabelAutoRotationChanged(true), axisZLabelAutoRotationChanged(true), aspectRatioChanged(true), + horizontalAspectRatioChanged(true), axisXTitleVisibilityChanged(true), axisYTitleVisibilityChanged(true), axisZTitleVisibilityChanged(true), axisXTitleFixedChanged(true), axisYTitleFixedChanged(true), - axisZTitleFixedChanged(true) + axisZTitleFixedChanged(true), + polarChanged(true), + radialLabelOffsetChanged(true), + reflectionChanged(true), + reflectivityChanged(true), + marginChanged(true) { } }; @@ -156,8 +169,13 @@ private: QAbstract3DGraph::SelectionFlags m_selectionMode; QAbstract3DGraph::ShadowQuality m_shadowQuality; bool m_useOrthoProjection; - float m_aspectRatio; + qreal m_aspectRatio; + qreal m_horizontalAspectRatio; QAbstract3DGraph::OptimizationHints m_optimizationHints; + bool m_reflectionEnabled; + qreal m_reflectivity; + QLocale m_locale; + QVector3D m_queriedGraphPosition; protected: Q3DScene *m_scene; @@ -175,6 +193,8 @@ protected: bool m_isCustomItemDirty; bool m_isSeriesVisualsDirty; bool m_renderPending; + bool m_isPolar; + float m_radialLabelOffset; QList<QAbstract3DSeries *> m_seriesList; @@ -187,13 +207,17 @@ protected: QList<QCustom3DItem *> m_customItems; + QAbstract3DGraph::ElementType m_clickedType; + int m_selectedLabelIndex; + int m_selectedCustomItemIndex; + qreal m_margin; + explicit Abstract3DController(QRect initialViewport, Q3DScene *scene, QObject *parent = 0); public: virtual ~Abstract3DController(); inline bool isInitialized() { return (m_renderer != 0); } - virtual void destroyRenderer(); virtual void synchDataToRenderer(); virtual void render(const GLuint defaultFboHandle = 0); virtual void initializeOpenGL() = 0; @@ -252,6 +276,7 @@ public: void deleteCustomItem(QCustom3DItem *item); void deleteCustomItem(const QVector3D &position); void releaseCustomItem(QCustom3DItem *item); + QList<QCustom3DItem *> customItems() const; int selectedLabelIndex() const; QAbstract3DAxis *selectedAxis() const; @@ -261,6 +286,35 @@ public: void setOrthoProjection(bool enable); bool isOrthoProjection() const; + void setMeasureFps(bool enable); + inline bool measureFps() const { return m_measureFps; } + inline qreal currentFps() const { return m_currentFps; } + + QAbstract3DGraph::ElementType selectedElement() const; + + void setAspectRatio(qreal ratio); + qreal aspectRatio(); + void setHorizontalAspectRatio(qreal ratio); + qreal horizontalAspectRatio() const; + + void setReflection(bool enable); + bool reflection() const; + void setReflectivity(qreal reflectivity); + qreal reflectivity() const; + + void setPolar(bool enable); + bool isPolar() const; + void setRadialLabelOffset(float offset); + float radialLabelOffset() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + QVector3D queriedGraphPosition() const; + + void setMargin(qreal margin); + qreal margin() const; + void emitNeedRender(); virtual void clearSelection() = 0; @@ -286,12 +340,16 @@ public: virtual void handleAxisTitleVisibilityChangedBySender(QObject *sender); virtual void handleAxisTitleFixedChangedBySender(QObject *sender); virtual void handleSeriesVisibilityChangedBySender(QObject *sender); - virtual void handlePendingClick() = 0; + virtual void handlePendingClick(); + virtual void handlePendingGraphPositionQuery(); virtual void adjustAxisRanges() = 0; void markSeriesItemLabelsDirty(); + bool isOpenGLES() const; public slots: + void destroyRenderer(); + void handleAxisTitleChanged(const QString &title); void handleAxisLabelsChanged(); void handleAxisRangeChanged(float min, float max); @@ -320,17 +378,8 @@ public slots: // Renderer callback handlers void handleRequestShadowQuality(QAbstract3DGraph::ShadowQuality quality); - void setMeasureFps(bool enable); - inline bool measureFps() const { return m_measureFps; } - inline qreal currentFps() const { return m_currentFps; } - - QAbstract3DGraph::ElementType selectedElement() const; - void updateCustomItem(); - void setAspectRatio(float ratio); - float aspectRatio(); - signals: void shadowQualityChanged(QAbstract3DGraph::ShadowQuality quality); void activeInputHandlerChanged(QAbstract3DInputHandler *inputHandler); @@ -344,8 +393,16 @@ signals: void measureFpsChanged(bool enabled); void currentFpsChanged(qreal fps); void orthoProjectionChanged(bool enabled); - void aspectRatioChanged(float ratio); + void aspectRatioChanged(qreal ratio); + void horizontalAspectRatioChanged(qreal ratio); void optimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints); + void polarChanged(bool enabled); + void radialLabelOffsetChanged(float offset); + void reflectionChanged(bool enabled); + void reflectivityChanged(qreal reflectivity); + void localeChanged(const QLocale &locale); + void queriedGraphPositionChanged(const QVector3D &data); + void marginChanged(qreal margin); protected: virtual QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation); diff --git a/src/datavisualization/engine/abstract3drenderer.cpp b/src/datavisualization/engine/abstract3drenderer.cpp index 04ede782..cfc691af 100644 --- a/src/datavisualization/engine/abstract3drenderer.cpp +++ b/src/datavisualization/engine/abstract3drenderer.cpp @@ -24,9 +24,24 @@ #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/QWindow> +#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), @@ -37,26 +52,83 @@ Abstract3DRenderer::Abstract3DRenderer(Abstract3DController *controller) 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_clickPending(false), + 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_graphAspectRatio(2.0f) + 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_dummySurfaceAtDelete(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>(); + m_funcs_2_1->initializeOpenGLFunctions(); + + // Restore original message handler + qInstallMessageHandler(handler); + } +#endif QObject::connect(m_drawer, &Drawer::drawerChanged, this, &Abstract3DRenderer::updateTextures); QObject::connect(this, &Abstract3DRenderer::needRender, controller, &Abstract3DController::needRender, Qt::QueuedConnection); @@ -71,6 +143,12 @@ Abstract3DRenderer::~Abstract3DRenderer() 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); @@ -88,12 +166,29 @@ Abstract3DRenderer::~Abstract3DRenderer() 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); + + if (QOpenGLContext::currentContext()) + m_textureHelper->glDeleteFramebuffers(1, &m_cursorPositionFrameBuffer); + + delete m_textureHelper; + } + + m_axisCacheX.clearLabels(); + m_axisCacheY.clearLabels(); + m_axisCacheZ.clearLabels(); - delete m_textureHelper; + restoreContextAfterDelete(); } void Abstract3DRenderer::initializeOpenGL() { + m_context = QOpenGLContext::currentContext(); + // Set OpenGL features glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); @@ -101,9 +196,11 @@ void Abstract3DRenderer::initializeOpenGL() glCullFace(GL_BACK); #if !defined(QT_OPENGL_ES_2) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); + 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(); @@ -112,6 +209,15 @@ void Abstract3DRenderer::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(); } void Abstract3DRenderer::render(const GLuint defaultFboHandle) @@ -137,7 +243,7 @@ void Abstract3DRenderer::render(const GLuint defaultFboHandle) 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); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); } @@ -146,28 +252,87 @@ void Abstract3DRenderer::updateSelectionState(SelectionState state) m_selectionState = state; } -void Abstract3DRenderer::updateInputPosition(const QPoint &position) +void Abstract3DRenderer::initGradientShaders(const QString &vertexShader, + const QString &fragmentShader) { - m_inputPosition = position; + // Do nothing by default + Q_UNUSED(vertexShader) + Q_UNUSED(fragmentShader) } -void Abstract3DRenderer::initGradientShaders(const QString &vertexShader, - const QString &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) { - if (m_customItemShader) - delete m_customItemShader; + 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); + + 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 @@ -193,24 +358,22 @@ void Abstract3DRenderer::updateScene(Q3DScene *scene) handleResize(); } - scene->activeCamera()->d_ptr->updateViewMatrix(m_autoScaleAdjustment); - // Set light position (rotate light with activeCamera, a bit above it (as set in defaultLightPos)) - scene->d_ptr->setLightPositionRelativeToCamera(defaultLightPos); - QPoint logicalPixelPosition = scene->selectionQueryPosition(); - updateInputPosition(QPoint(logicalPixelPosition.x() * m_devicePixelRatio, - logicalPixelPosition.y() * m_devicePixelRatio)); + 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 { - // Selections are one-shot, reset selection active to false before processing - scene->setSelectionQueryPosition(Q3DScene::invalidSelectionPoint()); - m_clickPending = true; - if (scene->isSlicingActive()) { if (scene->isPointInPrimarySubView(logicalPixelPosition)) updateSelectionState(SelectOnOverview); @@ -222,53 +385,109 @@ void Abstract3DRenderer::updateScene(Q3DScene *scene) 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 !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - 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 { - initGradientShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentColorOnY")); - initShaders(QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragment")); + 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")); + initBackgroundShaders(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/fragment")); + QStringLiteral(":/shaders/fragmentES2")); initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"), - QStringLiteral(":/shaders/fragmentTexture")); + QStringLiteral(":/shaders/fragmentTextureES2")); } -#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")); -#endif } void Abstract3DRenderer::handleShadowQualityChange() { reInitShaders(); -#if defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality != QAbstract3DGraph::ShadowQualityNone) { + if (m_isOpenGLES && m_cachedShadowQuality != QAbstract3DGraph::ShadowQualityNone) { emit requestShadowQuality(QAbstract3DGraph::ShadowQualityNone); qWarning("Shadows are not yet supported for OpenGL ES2"); m_cachedShadowQuality = QAbstract3DGraph::ShadowQualityNone; } -#endif } void Abstract3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode) @@ -280,16 +499,39 @@ void Abstract3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mo void Abstract3DRenderer::updateAspectRatio(float ratio) { m_graphAspectRatio = ratio; - calculateZoomLevel(); - m_cachedScene->activeCamera()->d_ptr->updateViewMatrix(m_autoScaleAdjustment); foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - updateCustomItemPositions(); +} + +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() @@ -303,10 +545,10 @@ void Abstract3DRenderer::handleResize() // Re-init selection buffer initSelectionBuffer(); -#if !defined(QT_OPENGL_ES_2) // Re-init depth buffer updateDepthBuffer(); -#endif + + initCursorPositionBuffer(); } void Abstract3DRenderer::calculateZoomLevel() @@ -315,9 +557,9 @@ void Abstract3DRenderer::calculateZoomLevel() GLfloat div; GLfloat zoomAdjustment; div = qMin(m_primarySubViewport.width(), m_primarySubViewport.height()); - zoomAdjustment = 2.0f * defaultRatio + zoomAdjustment = defaultRatio * ((m_primarySubViewport.width() / div) - / (m_primarySubViewport.height() / div)) / m_graphAspectRatio; + / (m_primarySubViewport.height() / div)); m_autoScaleAdjustment = qMin(zoomAdjustment, 1.0f); // clamp to 1.0f } @@ -348,8 +590,6 @@ void Abstract3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orient foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - - updateCustomItemPositions(); } void Abstract3DRenderer::updateAxisSegmentCount(QAbstract3DAxis::AxisOrientation orientation, @@ -378,8 +618,6 @@ void Abstract3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation ori axisCacheForOrientation(orientation).setReversed(enable); foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - - updateCustomItemPositions(); } void Abstract3DRenderer::updateAxisFormatter(QAbstract3DAxis::AxisOrientation orientation, @@ -396,8 +634,6 @@ void Abstract3DRenderer::updateAxisFormatter(QAbstract3DAxis::AxisOrientation or foreach (SeriesRenderCache *cache, m_renderCacheList) cache->setDataDirty(true); - - updateCustomItemPositions(); } void Abstract3DRenderer::updateAxisLabelAutoRotation(QAbstract3DAxis::AxisOrientation orientation, @@ -607,10 +843,9 @@ void Abstract3DRenderer::drawAxisTitleY(const QVector3D &sideLabelRotation, QQuaternion titleRotation; if (m_axisCacheY.isTitleFixed()) { titleRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, yRotation) - * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f); + * m_zRightAngleRotation; } else { - titleRotation = totalRotation - * QQuaternion::fromAxisAndAngle(0.0f, 0.0f, 1.0f, 90.0f); + titleRotation = totalRotation * m_zRightAngleRotation; } dummyItem.setTranslation(titleTrans + titleOffsetVector); @@ -628,17 +863,22 @@ void Abstract3DRenderer::drawAxisTitleX(const QVector3D &labelRotation, float labelsMaxWidth, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - ShaderHelper *shader) + ShaderHelper *shader, + bool radial) { float scaleFactor = m_drawer->scaledFontSize() / m_axisCacheX.titleItem().size().height(); - float titleOffset = 2.0f * (labelMargin + (labelsMaxWidth * scaleFactor)); + 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_yFlipped) { + if (m_yFlippedForGrid) { alignment = Qt::AlignBottom; zRotation = 180.0f; if (m_zFlipped) { @@ -678,6 +918,17 @@ void Abstract3DRenderer::drawAxisTitleX(const QVector3D &labelRotation, } } + 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); @@ -718,7 +969,7 @@ void Abstract3DRenderer::drawAxisTitleZ(const QVector3D &labelRotation, float xRotation = -90.0f; float extraRotation = 90.0f; Qt::AlignmentFlag alignment = Qt::AlignTop; - if (m_yFlipped) { + if (m_yFlippedForGrid) { alignment = Qt::AlignBottom; xRotation = -xRotation; if (m_zFlipped) { @@ -793,6 +1044,11 @@ void Abstract3DRenderer::loadLabelMesh() QStringLiteral(":/defaultMeshes/plane")); } +void Abstract3DRenderer::loadPositionMapperMesh() +{ + ObjectHelper::resetObjectHelper(this, m_positionMapperObj, + QStringLiteral(":/defaultMeshes/barFull")); +} void Abstract3DRenderer::generateBaseColorTexture(const QColor &color, GLuint *texture) { @@ -846,11 +1102,16 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) newItem->setRenderer(this); newItem->setItemPointer(item); // Store pointer for render item updates newItem->setMesh(item->meshFile()); - QVector3D scaling = item->scaling(); + 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) { @@ -864,22 +1125,52 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) } // 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()); } - newItem->setScaling(scaling); + recalculateCustomItemScalingAndPos(newItem); newItem->setRotation(item->rotation()); - newItem->setPosition(item->position()); - newItem->setPositionAbsolute(item->isPositionAbsolute()); - newItem->setBlendNeeded(textureImage.hasAlphaChannel()); - GLuint texture = m_textureHelper->create2DTexture(textureImage, true, true, true); + + // 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(); - QVector3D translation = convertPositionToTranslation(item->position(), - item->isPositionAbsolute()); - newItem->setTranslation(translation); newItem->setVisible(item->isVisible()); newItem->setShadowCasting(item->isShadowCasting()); newItem->setFacingCamera(facingCamera); @@ -887,6 +1178,74 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) 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(); @@ -894,8 +1253,17 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) 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); @@ -918,8 +1286,9 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) scaling.setX(scaling.x() * textureImage.width() * scaledFontSize); scaling.setY(scaling.y() * textureImage.height() * scaledFontSize); item->d_ptr->clearTextureImage(); + renderItem->setOrigScaling(scaling); } - renderItem->setScaling(scaling); + recalculateCustomItemScalingAndPos(renderItem); item->d_ptr->m_dirtyBits.scalingDirty = false; } if (item->d_ptr->m_dirtyBits.rotationDirty) { @@ -939,24 +1308,16 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) 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); } - 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.positionDirty || item->d_ptr->m_dirtyBits.positionAbsoluteDirty) { - renderItem->setPosition(item->position()); - renderItem->setPositionAbsolute(item->isPositionAbsolute()); - QVector3D translation = convertPositionToTranslation(item->position(), - item->isPositionAbsolute()); - renderItem->setTranslation(translation); - item->d_ptr->m_dirtyBits.positionDirty = false; - item->d_ptr->m_dirtyBits.positionAbsoluteDirty = false; - } if (item->d_ptr->m_dirtyBits.visibleDirty) { renderItem->setVisible(item->isVisible()); item->d_ptr->m_dirtyBits.visibleDirty = false; @@ -971,130 +1332,649 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) 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) { - QVector3D translation = convertPositionToTranslation(renderItem->position(), - renderItem->isPositionAbsolute()); - renderItem->setTranslation(translation); - } + foreach (CustomRenderItem *renderItem, m_customRenderCache) + recalculateCustomItemScalingAndPos(renderItem); } void Abstract3DRenderer::drawCustomItems(RenderingState state, - ShaderHelper *shader, + ShaderHelper *regularShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, GLuint depthTexture, - GLfloat shadowQuality) + GLfloat shadowQuality, + GLfloat reflection) { if (m_customRenderCache.isEmpty()) return; + ShaderHelper *shader = regularShader; + shader->bind(); + if (RenderingNormal == state) { - shader->bind(); 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); - - glEnable(GL_TEXTURE_2D); } - // Draw custom items - foreach (CustomRenderItem *item, m_customRenderCache) { - // Check that the render item is visible, and skip drawing if not - if (!item->isVisible()) - continue; - - // Check if the render item is in data coordinates and not within axis ranges, and skip drawing if it is - 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; - } + // Draw custom items - first regular and then volumes + bool volumeDetected = false; + int loopCount = 0; + while (loopCount < 2) { + foreach (CustomRenderItem *item, m_customRenderCache) { + // 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; + } - 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 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())); - modelMatrix.translate(item->translation()); - modelMatrix.rotate(rotation); - modelMatrix.scale(item->scaling()); - itModelMatrix.rotate(rotation); - if (!item->isFacingCamera()) - itModelMatrix.scale(item->scaling()); - MVPMatrix = projectionViewMatrix * modelMatrix; + 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); + 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::fixContextBeforeDelete() +{ + // Only need to fix context if the current context is null. + // Otherwise we expect it to be our shared context, so we can use it for cleanup. + if (!QOpenGLContext::currentContext() && !m_context.isNull() + && QThread::currentThread() == this->thread()) { + m_dummySurfaceAtDelete = new QWindow(); + m_dummySurfaceAtDelete->setSurfaceType(QWindow::OpenGLSurface); + m_dummySurfaceAtDelete->setFormat(m_context->format()); + m_dummySurfaceAtDelete->create(); + + m_context->makeCurrent(m_dummySurfaceAtDelete); + } +} + +void Abstract3DRenderer::restoreContextAfterDelete() +{ + if (m_dummySurfaceAtDelete) + m_context->doneCurrent(); + + delete m_dummySurfaceAtDelete; + m_dummySurfaceAtDelete = 0; +} + +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; - if (RenderingNormal == state) { - // Normal render 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); - glDisable(GL_CULL_FACE); + 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 { - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); + m_drawer->drawLine(shader); } + } + } +} -#if !defined(QT_OPENGL_ES_2) +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 - 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 -#else - Q_UNUSED(depthTexture) - Q_UNUSED(shadowQuality) -#endif - { - // Set shadowless shader bindings - shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); - m_drawer->drawObject(shader, item->mesh(), item->texture()); + 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 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()); } } +} - if (RenderingNormal == state) { - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - glEnable(GL_CULL_FACE); +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) + m_cachedScene->d_ptr->setLightPositionRelativeToCamera(defaultLightPos); } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/abstract3drenderer_p.h b/src/datavisualization/engine/abstract3drenderer_p.h index 0dfc7367..1e38023d 100644 --- a/src/datavisualization/engine/abstract3drenderer_p.h +++ b/src/datavisualization/engine/abstract3drenderer_p.h @@ -30,13 +30,17 @@ #define ABSTRACT3DRENDERER_P_H #include <QtGui/QOpenGLFunctions> - +#if !defined(QT_OPENGL_ES_2) +# include <QtGui/QOpenGLFunctions_2_1> +#endif #include "datavisualizationglobal_p.h" #include "abstract3dcontroller_p.h" #include "axisrendercache_p.h" #include "seriesrendercache_p.h" #include "customrenderitem_p.h" +class QSurface; + QT_BEGIN_NAMESPACE_DATAVISUALIZATION class TextureHelper; @@ -77,21 +81,33 @@ public: virtual void updateSelectionMode(QAbstract3DGraph::SelectionFlags newMode); virtual void updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint); virtual void updateScene(Q3DScene *scene); - virtual void updateTextures() = 0; + virtual void updateTextures(); virtual void initSelectionBuffer() = 0; virtual void updateSelectionState(SelectionState state); - virtual void updateInputPosition(const QPoint &position); -#if !defined(QT_OPENGL_ES_2) virtual void updateDepthBuffer() = 0; -#endif virtual void updateShadowQuality(QAbstract3DGraph::ShadowQuality quality) = 0; virtual void initShaders(const QString &vertexShader, const QString &fragmentShader) = 0; virtual void initGradientShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader); virtual void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader) = 0; virtual void initCustomItemShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initVolumeTextureShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &fragmentLowDefShader, + const QString &sliceShader, + const QString &sliceFrameVertexShader, + const QString &sliceFrameShader); + virtual void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initCursorPositionShaders(const QString &vertexShader, + const QString &fragmentShader); + virtual void initCursorPositionBuffer(); + virtual void updateAxisType(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis::AxisType type); virtual void updateAxisTitle(QAbstract3DAxis::AxisOrientation orientation, @@ -112,7 +128,7 @@ public: virtual void updateAxisLabelAutoRotation(QAbstract3DAxis::AxisOrientation orientation, float angle); virtual void updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, - bool visible); + bool visible); virtual void updateAxisTitleFixed(QAbstract3DAxis::AxisOrientation orientation, bool fixed); virtual void modifiedSeriesList(const QVector<QAbstract3DSeries *> &seriesList); @@ -123,6 +139,10 @@ public: virtual void updateCustomItem(CustomRenderItem *renderItem); virtual void updateAspectRatio(float ratio); + virtual void updateHorizontalAspectRatio(float ratio); + virtual void updatePolar(bool enable); + virtual void updateRadialLabelOffset(float offset); + virtual void updateMargin(float margin); virtual QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute) = 0; @@ -130,21 +150,28 @@ public: void generateBaseColorTexture(const QColor &color, GLuint *texture); void fixGradientAndGenerateTexture(QLinearGradient *gradient, GLuint *gradientTexture); - inline bool isClickPending() { return m_clickPending; } - inline void clearClickPending() { m_clickPending = false; } + inline bool isClickQueryResolved() const { return m_clickResolved; } + inline void clearClickQueryResolved() { m_clickResolved = false; } + inline QPoint cachedClickQuery() const { return m_cachedScene->selectionQueryPosition(); } inline QAbstract3DSeries *clickedSeries() const { return m_clickedSeries; } inline QAbstract3DGraph::ElementType clickedType() { return m_clickedType; } + inline bool isGraphPositionQueryResolved() const { return m_graphPositionQueryResolved; } + inline void clearGraphPositionQueryResolved() { m_graphPositionQueryResolved = false; } + inline QVector3D queriedGraphPosition() const { return m_queriedGraphPosition; } + inline QPoint cachedGraphPositionQuery() const { return m_cachedScene->graphPositionQuery(); } LabelItem &selectionLabelItem(); void setSelectionLabel(const QString &label); QString &selectionLabel(); - void drawCustomItems(RenderingState state, ShaderHelper *shader, + void drawCustomItems(RenderingState state, ShaderHelper *regularShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, - GLuint depthTexture, GLfloat shadowQuality); + GLuint depthTexture, GLfloat shadowQuality, GLfloat reflection = 1.0f); + QVector4D indexToSelectionColor(GLint index); + void calculatePolarXZ(const QVector3D &dataPos, float &x, float &z) const; signals: void needRender(); // Emit this if something in renderer causes need for another render pass. @@ -177,7 +204,7 @@ protected: const QQuaternion &totalRotation, AbstractRenderItem &dummyItem, const Q3DCamera *activeCamera, float labelsMaxWidth, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - ShaderHelper *shader); + ShaderHelper *shader, bool radial = false); void drawAxisTitleZ(const QVector3D &labelRotation, const QVector3D &labelTrans, const QQuaternion &totalRotation, AbstractRenderItem &dummyItem, const Q3DCamera *activeCamera, float labelsMaxWidth, @@ -186,6 +213,26 @@ protected: void loadGridLineMesh(); void loadLabelMesh(); + void loadPositionMapperMesh(); + + void drawRadialGrid(ShaderHelper *shader, float yFloorLinePos, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthMatrix); + void drawAngularGrid(ShaderHelper *shader, float yFloorLinePos, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthMatrix); + + float calculatePolarBackgroundMargin(); + virtual void fixCameraTarget(QVector3D &target) = 0; + void updateCameraViewport(); + + void recalculateCustomItemScalingAndPos(CustomRenderItem *item); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) = 0; + void drawVolumeSliceFrame(const CustomRenderItem *item, Qt::Axis axis, + const QMatrix4x4 &projectionViewMatrix); + void queriedGraphPosition(const QMatrix4x4 &projectionViewMatrix, const QVector3D &scaling, + GLuint defaultFboHandle); + + void fixContextBeforeDelete(); + void restoreContextAfterDelete(); bool m_hasNegativeValues; Q3DTheme *m_cachedTheme; @@ -201,6 +248,7 @@ protected: AxisRenderCache m_axisCacheY; AxisRenderCache m_axisCacheZ; TextureHelper *m_textureHelper; + GLuint m_depthTexture; Q3DScene *m_cachedScene; bool m_selectionDirty; @@ -212,28 +260,75 @@ protected: QRect m_secondarySubViewport; float m_devicePixelRatio; bool m_selectionLabelDirty; - bool m_clickPending; + bool m_clickResolved; + bool m_graphPositionQueryPending; + bool m_graphPositionQueryResolved; QAbstract3DSeries *m_clickedSeries; QAbstract3DGraph::ElementType m_clickedType; int m_selectedLabelIndex; int m_selectedCustomItemIndex; + QVector3D m_queriedGraphPosition; + QPoint m_graphPositionQuery; QString m_selectionLabel; LabelItem *m_selectionLabelItem; int m_visibleSeriesCount; ShaderHelper *m_customItemShader; + ShaderHelper *m_volumeTextureShader; + ShaderHelper *m_volumeTextureLowDefShader; + ShaderHelper *m_volumeTextureSliceShader; + ShaderHelper *m_volumeSliceFrameShader; + ShaderHelper *m_labelShader; + ShaderHelper *m_cursorPositionShader; + GLuint m_cursorPositionFrameBuffer; + GLuint m_cursorPositionTexture; bool m_useOrthoProjection; bool m_xFlipped; bool m_yFlipped; bool m_zFlipped; + bool m_yFlippedForGrid; ObjectHelper *m_backgroundObj; // Shared reference ObjectHelper *m_gridLineObj; // Shared reference ObjectHelper *m_labelObj; // Shared reference + ObjectHelper *m_positionMapperObj; // Shared reference float m_graphAspectRatio; + float m_graphHorizontalAspectRatio; + bool m_polarGraph; + float m_radialLabelOffset; + float m_polarRadius; + + QQuaternion m_xRightAngleRotation; + QQuaternion m_yRightAngleRotation; + QQuaternion m_zRightAngleRotation; + QQuaternion m_xRightAngleRotationNeg; + QQuaternion m_yRightAngleRotationNeg; + QQuaternion m_zRightAngleRotationNeg; + QQuaternion m_xFlipRotation; + QQuaternion m_zFlipRotation; + + float m_requestedMargin; + float m_vBackgroundMargin; + float m_hBackgroundMargin; + float m_scaleXWithBackground; + float m_scaleYWithBackground; + float m_scaleZWithBackground; + + QVector3D m_oldCameraTarget; + + bool m_reflectionEnabled; + qreal m_reflectivity; + + QLocale m_locale; +#if !defined(QT_OPENGL_ES_2) + QOpenGLFunctions_2_1 *m_funcs_2_1; // Not owned +#endif + QPointer<QOpenGLContext> m_context; // Not owned + QWindow *m_dummySurfaceAtDelete; + bool m_isOpenGLES; private: friend class Abstract3DController; diff --git a/src/datavisualization/engine/axisrendercache.cpp b/src/datavisualization/engine/axisrendercache.cpp index 0b7a5c7a..02e3b7f6 100644 --- a/src/datavisualization/engine/axisrendercache.cpp +++ b/src/datavisualization/engine/axisrendercache.cpp @@ -46,6 +46,7 @@ AxisRenderCache::~AxisRenderCache() { foreach (LabelItem *label, m_labelItems) delete label; + m_titleItem.clear(); delete m_formatter; } @@ -54,10 +55,8 @@ void AxisRenderCache::setDrawer(Drawer *drawer) { m_drawer = drawer; m_font = m_drawer->font(); - if (m_drawer) { - QObject::connect(m_drawer, &Drawer::drawerChanged, this, &AxisRenderCache::updateTextures); + if (m_drawer) updateTextures(); - } } void AxisRenderCache::setType(QAbstract3DAxis::AxisType type) @@ -106,10 +105,12 @@ void AxisRenderCache::setLabels(const QStringList &labels) if (i >= oldSize) m_labelItems.append(new LabelItem); if (m_drawer) { - if (labels.at(i).isEmpty()) + if (labels.at(i).isEmpty()) { m_labelItems[i]->clear(); - else if (i >= oldSize || labels.at(i) != m_labels.at(i)) + } else if (i >= oldSize || labels.at(i) != m_labels.at(i) + || m_labelItems[i]->size().width() != widest) { m_drawer->generateLabelItem(*m_labelItems[i], labels.at(i), widest); + } } } m_labels = labels; @@ -175,6 +176,13 @@ void AxisRenderCache::updateTextures() } } +void AxisRenderCache::clearLabels() +{ + m_titleItem.clear(); + for (int i = 0; i < m_labels.size(); i++) + m_labelItems[i]->clear(); +} + int AxisRenderCache::maxLabelWidth(const QStringList &labels) const { int labelWidth = 0; diff --git a/src/datavisualization/engine/axisrendercache_p.h b/src/datavisualization/engine/axisrendercache_p.h index 90321740..e32c590e 100644 --- a/src/datavisualization/engine/axisrendercache_p.h +++ b/src/datavisualization/engine/axisrendercache_p.h @@ -107,8 +107,8 @@ public: inline bool isTitleFixed() const { return m_titleFixed; } inline void setTitleFixed(bool fixed) { m_titleFixed = fixed; } -public slots: void updateTextures(); + void clearLabels(); private: int maxLabelWidth(const QStringList &labels) const; diff --git a/src/datavisualization/engine/bars3dcontroller.cpp b/src/datavisualization/engine/bars3dcontroller.cpp index 3a240c24..2092dd60 100644 --- a/src/datavisualization/engine/bars3dcontroller.cpp +++ b/src/datavisualization/engine/bars3dcontroller.cpp @@ -36,6 +36,7 @@ Bars3DController::Bars3DController(QRect boundRect, Q3DScene *scene) m_isBarSpecRelative(true), m_barThicknessRatio(1.0f), m_barSpacing(QSizeF(1.0, 1.0)), + m_floorLevel(0.0f), m_renderer(0) { // Setting a null axis creates a new default axis according to orientation and graph type. @@ -83,6 +84,12 @@ void Bars3DController::synchDataToRenderer() needSceneUpdate = true; } + // Floor level update requires data update, so do before abstract sync + if (m_changeTracker.floorLevelChanged) { + m_renderer->updateFloorLevel(m_floorLevel); + m_changeTracker.floorLevelChanged = false; + } + Abstract3DController::synchDataToRenderer(); // Notify changes to renderer @@ -114,12 +121,10 @@ void Bars3DController::synchDataToRenderer() m_changeTracker.selectedBarChanged = false; } - if (needSceneUpdate) { - // Since scene is updated before axis updates are handled, - // do another render pass for scene update - m_scene->d_ptr->m_sceneDirty = true; - emitNeedRender(); - } + // Since scene is updated before axis updates are handled, do another render pass to + // properly update controller side camera limits. + if (needSceneUpdate) + m_scene->d_ptr->markDirty(); } void Bars3DController::handleArrayReset() @@ -487,6 +492,19 @@ bool Bars3DController::isBarSpecRelative() return m_isBarSpecRelative; } +void Bars3DController::setFloorLevel(float level) +{ + m_floorLevel = level; + m_isDataDirty = true; + m_changeTracker.floorLevelChanged = true; + emitNeedRender(); +} + +float Bars3DController::floorLevel() const +{ + return m_floorLevel; +} + void Bars3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode) { if (mode.testFlag(QAbstract3DGraph::SelectionSlice) diff --git a/src/datavisualization/engine/bars3dcontroller_p.h b/src/datavisualization/engine/bars3dcontroller_p.h index 4f0e1f20..ac62f37d 100644 --- a/src/datavisualization/engine/bars3dcontroller_p.h +++ b/src/datavisualization/engine/bars3dcontroller_p.h @@ -43,13 +43,15 @@ struct Bars3DChangeBitField { bool selectedBarChanged : 1; bool rowsChanged : 1; bool itemChanged : 1; + bool floorLevelChanged : 1; Bars3DChangeBitField() : multiSeriesScalingChanged(true), barSpecsChanged(true), selectedBarChanged(true), rowsChanged(false), - itemChanged(false) + itemChanged(false), + floorLevelChanged(false) { } }; @@ -84,6 +86,7 @@ private: bool m_isBarSpecRelative; GLfloat m_barThicknessRatio; QSizeF m_barSpacing; + float m_floorLevel; // Rendering Bars3DRenderer *m_renderer; @@ -107,6 +110,8 @@ public: GLfloat barThickness(); QSizeF barSpacing(); bool isBarSpecRelative(); + void setFloorLevel(float level); + float floorLevel() const; inline QBar3DSeries *selectedSeries() const { return m_selectedBarSeries; } diff --git a/src/datavisualization/engine/bars3drenderer.cpp b/src/datavisualization/engine/bars3drenderer.cpp index 689f3f5d..b6a191a9 100644 --- a/src/datavisualization/engine/bars3drenderer.cpp +++ b/src/datavisualization/engine/bars3drenderer.cpp @@ -31,7 +31,6 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -const GLfloat gridLineWidth = 0.005f; const bool sliceGridLabels = true; Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) @@ -48,9 +47,7 @@ Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) m_depthShader(0), m_selectionShader(0), m_backgroundShader(0), - m_labelShader(0), m_bgrTexture(0), - m_depthTexture(0), m_selectionTexture(0), m_depthFrameBuffer(0), m_selectionFrameBuffer(0), @@ -67,7 +64,6 @@ Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) m_scaleFactor(0), m_maxSceneSize(40.0f), m_visualSelectedBarPos(Bars3DController::invalidSelectionPosition()), - m_resetCameraBaseOrientation(true), m_selectedBarPos(Bars3DController::invalidSelectionPosition()), m_selectedSeriesCache(0), m_noZeroInRange(false), @@ -79,17 +75,22 @@ Bars3DRenderer::Bars3DRenderer(Bars3DController *controller) m_keepSeriesUniform(false), m_haveUniformColorSeries(false), m_haveGradientSeries(false), - m_zeroPosition(0.0f) + m_zeroPosition(0.0f), + m_xScaleFactor(1.0f), + m_zScaleFactor(1.0f), + m_floorLevel(0.0f), + m_actualFloorLevel(0.0f) { m_axisCacheY.setScale(2.0f); m_axisCacheY.setTranslate(-1.0f); - initializeOpenGLFunctions(); initializeOpenGL(); } Bars3DRenderer::~Bars3DRenderer() { + fixContextBeforeDelete(); + if (QOpenGLContext::currentContext()) { m_textureHelper->glDeleteFramebuffers(1, &m_selectionFrameBuffer); m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer); @@ -103,7 +104,6 @@ Bars3DRenderer::~Bars3DRenderer() delete m_depthShader; delete m_selectionShader; delete m_backgroundShader; - delete m_labelShader; } void Bars3DRenderer::initializeOpenGL() @@ -111,13 +111,9 @@ void Bars3DRenderer::initializeOpenGL() Abstract3DRenderer::initializeOpenGL(); // Initialize shaders - initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), - QStringLiteral(":/shaders/fragmentLabel")); -#if !defined(QT_OPENGL_ES_2) // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api. initDepthShader(); -#endif // Init selection shader initSelectionShader(); @@ -125,13 +121,57 @@ void Bars3DRenderer::initializeOpenGL() // Load grid line mesh loadGridLineMesh(); - // Load label mesh - loadLabelMesh(); - // Load background mesh (we need to be initialized first) loadBackgroundMesh(); } +void Bars3DRenderer::fixCameraTarget(QVector3D &target) +{ + target.setX(target.x() * m_xScaleFactor); + target.setY(0.0f); + target.setZ(target.z() * -m_zScaleFactor); +} + +void Bars3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) +{ + // The inputs are the item bounds in OpenGL coordinates. + // The outputs limit these bounds to visible ranges, normalized to range [-1, 1] + // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those + float itemRangeX = (maxBounds.x() - minBounds.x()); + float itemRangeY = (maxBounds.y() - minBounds.y()); + float itemRangeZ = (maxBounds.z() - minBounds.z()); + + if (minBounds.x() < -m_xScaleFactor) + minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_xScaleFactor) / itemRangeX)); + else + minBounds.setX(-1.0f); + + if (minBounds.y() < -1.0f + m_backgroundAdjustment) + minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + 1.0f - m_backgroundAdjustment) / itemRangeY))); + else + minBounds.setY(1.0f); + + if (minBounds.z() < -m_zScaleFactor) + minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_zScaleFactor) / itemRangeZ))); + else + minBounds.setZ(1.0f); + + if (maxBounds.x() > m_xScaleFactor) + maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_xScaleFactor) / itemRangeX)); + else + maxBounds.setX(1.0f); + + if (maxBounds.y() > 1.0f + m_backgroundAdjustment) + maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - 1.0f - m_backgroundAdjustment) / itemRangeY))); + else + maxBounds.setY(-1.0f); + + if (maxBounds.z() > m_zScaleFactor) + maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_zScaleFactor) / itemRangeZ))); + else + maxBounds.setZ(-1.0f); +} + void Bars3DRenderer::updateData() { int minRow = m_axisCacheZ.min(); @@ -163,11 +203,11 @@ void Bars3DRenderer::updateData() GLfloat sceneRatio = qMin(GLfloat(newColumns) / GLfloat(newRows), GLfloat(newRows) / GLfloat(newColumns)); m_maxSceneSize = 2.0f * qSqrt(sceneRatio * newColumns * newRows); - // Calculate here and at setting bar specs - calculateSceneScalingFactors(); } - m_zeroPosition = m_axisCacheY.formatter()->positionAt(0.0f); + calculateSceneScalingFactors(); + + m_zeroPosition = m_axisCacheY.formatter()->positionAt(m_actualFloorLevel); foreach (SeriesRenderCache *baseCache, m_renderCacheList) { BarSeriesRenderCache *cache = static_cast<BarSeriesRenderCache *>(baseCache); @@ -390,14 +430,6 @@ void Bars3DRenderer::updateScene(Q3DScene *scene) } } - if (m_resetCameraBaseOrientation) { - // Set initial camera position. Also update if height adjustment has changed. - scene->activeCamera()->d_ptr->setBaseOrientation(cameraDistanceVector, - zeroVector, - upVector); - m_resetCameraBaseOrientation = false; - } - Abstract3DRenderer::updateScene(scene); updateSlicingActive(scene->isSlicingActive()); @@ -418,10 +450,17 @@ void Bars3DRenderer::render(GLuint defaultFboHandle) void Bars3DRenderer::drawSlicedScene() { + if (m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow) + == m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionColumn)) { + qWarning("Invalid selection mode. Either QAbstract3DGraph::SelectionRow or" + " QAbstract3DGraph::SelectionColumn must be set before calling" + " setSlicingActive(true)."); + return; + } + GLfloat barPosX = 0; QVector3D lightPos; QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); - static QQuaternion ninetyDegreeRotation = QQuaternion::fromAxisAndAngle(upVector, 90.0f); // Specify viewport glViewport(m_secondarySubViewport.x(), @@ -482,11 +521,12 @@ void Bars3DRenderer::drawSlicedScene() // Draw grid lines if (m_cachedTheme->isGridEnabled()) { glDisable(GL_DEPTH_TEST); -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; + // Bind line shader lineShader->bind(); @@ -496,7 +536,8 @@ void Bars3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->view(), viewMatrix); lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 7.0f); lineShader->setUniformValue(lineShader->lightS(), 0.0f); lineShader->setUniformValue(lineShader->lightColor(), lightColor); @@ -524,11 +565,10 @@ void Bars3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); // Check if we have a line at zero position already if (gridPos == (barPosYAdjustment + zeroPosAdjustment)) @@ -553,18 +593,16 @@ void Bars3DRenderer::drawSlicedScene() m_cachedTheme->labelTextColor())); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); } } if (sliceGridLabels) { // Bind label shader m_labelShader->bind(); - glEnable(GL_TEXTURE_2D); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -587,7 +625,6 @@ void Bars3DRenderer::drawSlicedScene() } labelNbr++; } - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } @@ -606,14 +643,16 @@ void Bars3DRenderer::drawSlicedScene() m_barShader->setUniformValue(m_barShader->view(), viewMatrix); m_barShader->setUniformValue(m_barShader->lightS(), 0.15f); m_barShader->setUniformValue(m_barShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 7.0f); m_barShader->setUniformValue(m_barShader->lightColor(), lightColor); m_barGradientShader->bind(); m_barGradientShader->setUniformValue(m_barGradientShader->lightP(), lightPos); m_barGradientShader->setUniformValue(m_barGradientShader->view(), viewMatrix); m_barGradientShader->setUniformValue(m_barGradientShader->lightS(), 0.15f); m_barGradientShader->setUniformValue(m_barGradientShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 7.0f); m_barGradientShader->setUniformValue(m_barGradientShader->gradientMin(), 0.0f); m_barGradientShader->setUniformValue(m_barGradientShader->lightColor(), lightColor); @@ -700,7 +739,7 @@ void Bars3DRenderer::drawSlicedScene() barPosX = item.translation().x(); } else { barPosX = -(item.translation().z()); // flip z; frontmost bar to the left - barRotation *= ninetyDegreeRotation; + barRotation *= m_yRightAngleRotation; } modelMatrix.translate(barPosX, barPosY, 0.0f); @@ -760,7 +799,6 @@ void Bars3DRenderer::drawSlicedScene() // Draw labels m_labelShader->bind(); glDisable(GL_DEPTH_TEST); - glEnable(GL_TEXTURE_2D); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -778,6 +816,12 @@ void Bars3DRenderer::drawSlicedScene() int labelCount = m_sliceCache->labelItems().size(); for (int labelNo = 0; labelNo < labelCount; labelNo++) { + // Check for invalid usage (no selection when setting slicing active) + if (!firstVisualSliceArray) { + qWarning("No slice data found. Make sure there is a valid selection."); + continue; + } + // Get labels from first series only const BarRenderSliceItem &item = firstVisualSliceArray->at(labelNo); m_dummyBarRenderItem.setTranslation(QVector3D(item.translation().x(), @@ -889,7 +933,6 @@ void Bars3DRenderer::drawSlicedScene() m_cachedSelectionMode, m_labelShader, m_labelObj, activeCamera, false, false, Drawer::LabelMid, Qt::AlignBottom); - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); @@ -912,8 +955,6 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) GLfloat colPos = 0; GLfloat rowPos = 0; - QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); - const Q3DCamera *activeCamera = m_cachedScene->activeCamera(); glViewport(m_primarySubViewport.x(), @@ -990,15 +1031,9 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 projectionViewMatrix = projectionMatrix * viewMatrix; - bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow); - - GLfloat rowScaleFactor = m_rowWidth / m_scaleFactor; - GLfloat columnScaleFactor = m_columnDepth / m_scaleFactor; - BarRenderItem *selectedBar(0); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Render scene into a depth texture for using with shadow mapping // Enable drawing to depth framebuffer glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer); @@ -1052,6 +1087,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) shadowOffset = -0.015f; } + if (m_cachedTheme->isBackgroundEnabled() && m_reflectionEnabled + && ((m_yFlipped && item.height() > 0.0) + || (!m_yFlipped && item.height() < 0.0))) { + continue; + } + QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; @@ -1097,8 +1138,9 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to depth framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -1112,12 +1154,22 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) m_primarySubViewport.width(), m_primarySubViewport.height()); } -#endif + + // Do position mapping when necessary + if (m_graphPositionQueryPending) { + QVector3D graphDimensions(m_xScaleFactor, 0.0f, m_zScaleFactor); + queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle); + + // Y is always at floor level + m_queriedGraphPosition.setY(0.0f); + emit needRender(); + } // Skip selection mode drawing if we're slicing or have no selection mode if (!m_cachedIsSlicingActivated && m_cachedSelectionMode > QAbstract3DGraph::SelectionNone && m_selectionState == SelectOnScene - && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty())) { + && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty()) + && m_selectionTexture) { // Bind selection shader m_selectionShader->bind(); @@ -1181,18 +1233,20 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } glCullFace(GL_BACK); - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, + viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); - drawLabels(true, activeCamera, viewMatrix, projectionMatrix, rowScaleFactor, - columnScaleFactor); + drawLabels(true, activeCamera, viewMatrix, projectionMatrix); + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix, false, true); glEnable(GL_DITHER); // Read color under cursor - QVector4D clickedColor = Utils::getSelection(m_inputPosition, - m_viewport.height()); + QVector4D clickedColor = Utils::getSelection(m_inputPosition, m_viewport.height()); m_clickedPosition = selectionColorToArrayPosition(clickedColor); m_clickedSeries = selectionColorToSeries(clickedColor); + m_clickResolved = true; emit needRender(); @@ -1204,8 +1258,133 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) m_primarySubViewport.height()); } - // Enable texturing - glEnable(GL_TEXTURE_2D); + if (m_reflectionEnabled) { + // + // Draw reflections + // + glDisable(GL_DEPTH_TEST); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glEnable(GL_STENCIL_TEST); + glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); + glStencilFunc(GL_ALWAYS, 1, 0xffffffff); + + // Draw background stencil + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix); + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glEnable(GL_DEPTH_TEST); + + glStencilFunc(GL_EQUAL, 1, 0xffffffff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + // Set light + QVector3D reflectionLightPos = lightPos; + reflectionLightPos.setY(-(lightPos.y())); + m_cachedScene->activeLight()->setPosition(reflectionLightPos); + + // Draw bar reflections + (void)drawBars(&selectedBar, depthProjectionViewMatrix, + projectionViewMatrix, viewMatrix, + startRow, stopRow, stepRow, + startBar, stopBar, stepBar, -1.0f); + + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, + viewMatrix, projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader, -1.0f); + + // Reset light + m_cachedScene->activeLight()->setPosition(lightPos); + + glDisable(GL_STENCIL_TEST); + + glCullFace(GL_BACK); + } + + // + // Draw the real scene + // + // Draw background + if (m_reflectionEnabled) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix, true); + glDisable(GL_BLEND); + } else { + drawBackground(backgroundRotation, depthProjectionViewMatrix, projectionViewMatrix, + viewMatrix); + } + + // Draw bars + bool barSelectionFound = drawBars(&selectedBar, depthProjectionViewMatrix, + projectionViewMatrix, viewMatrix, + startRow, stopRow, stepRow, + startBar, stopBar, stepBar); + + // Draw grid lines + drawGridLines(depthProjectionViewMatrix, projectionViewMatrix, viewMatrix); + + // Draw custom items + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, + projectionViewMatrix, depthProjectionViewMatrix, + m_depthTexture, m_shadowQualityToShader); + + // Draw labels + drawLabels(false, activeCamera, viewMatrix, projectionMatrix); + + // Handle selected bar label generation + if (barSelectionFound) { + // Print value of selected bar + glDisable(GL_DEPTH_TEST); + // Draw the selection label + LabelItem &labelItem = selectionLabelItem(); + if (m_selectedBar != selectedBar || m_updateLabels || !labelItem.textureId() + || m_selectionLabelDirty) { + QString labelText = selectionLabel(); + if (labelText.isNull() || m_selectionLabelDirty) { + labelText = m_selectedSeriesCache->itemLabel(); + setSelectionLabel(labelText); + m_selectionLabelDirty = false; + } + m_drawer->generateLabelItem(labelItem, labelText); + m_selectedBar = selectedBar; + } + + Drawer::LabelPosition position = + m_selectedBar->height() >= 0 ? Drawer::LabelOver : Drawer::LabelBelow; + + m_drawer->drawLabel(*selectedBar, labelItem, viewMatrix, projectionMatrix, + zeroVector, identityQuaternion, selectedBar->height(), + m_cachedSelectionMode, m_labelShader, + m_labelObj, activeCamera, true, false, position); + + // Reset label update flag; they should have been updated when we get here + m_updateLabels = false; + + glEnable(GL_DEPTH_TEST); + } else { + m_selectedBar = 0; + } + + glDisable(GL_BLEND); + + // Release shader + glUseProgram(0); + m_selectionDirty = false; +} + +bool Bars3DRenderer::drawBars(BarRenderItem **selectedBar, + const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix, + GLint startRow, GLint stopRow, GLint stepRow, + GLint startBar, GLint stopBar, GLint stepBar, GLfloat reflection) +{ + QVector3D lightPos = m_cachedScene->activeLight()->position(); + QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); + + bool rowMode = m_cachedSelectionMode.testFlag(QAbstract3DGraph::SelectionRow); ShaderHelper *barShader = 0; GLuint gradientTexture = 0; @@ -1251,7 +1430,6 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) m_sliceTitleItem = 0; } - // Draw bars glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(0.5f, 1.0f); @@ -1302,12 +1480,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } previousColorStyle = colorStyle; - for (int row = startRow; row != stopRow; row += stepRow) { BarRenderItemRow &renderRow = renderArray[row]; for (int bar = startBar; bar != stopBar; bar += stepBar) { BarRenderItem &item = renderRow[bar]; - if (item.height() < 0) + float adjustedHeight = reflection * item.height(); + if (adjustedHeight < 0) glCullFace(GL_FRONT); else glCullFace(GL_BACK); @@ -1316,13 +1494,13 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 itModelMatrix; QMatrix4x4 MVPMatrix; - colPos = (bar + seriesPos) * (m_cachedBarSpacing.width()); - rowPos = (row + 0.5f) * (m_cachedBarSpacing.height()); + GLfloat colPos = (bar + seriesPos) * (m_cachedBarSpacing.width()); + GLfloat rowPos = (row + 0.5f) * (m_cachedBarSpacing.height()); modelMatrix.translate((colPos - m_rowWidth) / m_scaleFactor, - item.height(), + adjustedHeight, (m_columnDepth - rowPos) / m_scaleFactor); - modelScaler.setY(item.height()); + modelScaler.setY(adjustedHeight); if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) { QQuaternion totalRotation = seriesRotation * item.rotation(); modelMatrix.rotate(totalRotation); @@ -1357,8 +1535,8 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) // We have no ownership, don't delete the previous one if (!m_cachedIsSlicingActivated && m_selectedSeriesCache == cache) { - selectedBar = &item; - selectedBar->setPosition(QPoint(row, bar)); + *selectedBar = &item; + (*selectedBar)->setPosition(QPoint(row, bar)); item.setTranslation(modelMatrix.column(3).toVector3D()); barSelectionFound = true; } @@ -1438,8 +1616,15 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } - // Skip drawing of 0-height bars - if (item.height() != 0) { + if (item.height() == 0) { + continue; + } else if ((m_reflectionEnabled + && (reflection == 1.0f + || (reflection != 1.0f + && ((m_yFlipped && item.height() < 0.0) + || (!m_yFlipped && item.height() > 0.0))))) + || !m_reflectionEnabled) { + // Skip drawing of 0-height bars and reflections of bars on the "wrong side" // Set shader bindings barShader->setUniformValue(barShader->model(), modelMatrix); barShader->setUniformValue(barShader->nModel(), @@ -1452,8 +1637,10 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) qAbs(item.height()) / m_gradientFraction); } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (((m_reflectionEnabled && reflection == 1.0f + && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) + || m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) + && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; barShader->setUniformValue(barShader->shadowQ(), @@ -1465,13 +1652,15 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) // Draw the object m_drawer->drawObject(barShader, barObj, gradientTexture, m_depthTexture); - } else -#else - Q_UNUSED(shadowLightStrength); -#endif - { + } else { // Set shadowless shader bindings - barShader->setUniformValue(barShader->lightS(), lightStrength); + if (m_reflectionEnabled && reflection != 1.0f + && m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + barShader->setUniformValue(barShader->lightS(), + adjustedLightStrength); + } else { + barShader->setUniformValue(barShader->lightS(), lightStrength); + } // Draw the object m_drawer->drawObject(barShader, barObj, gradientTexture); @@ -1481,122 +1670,145 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } } - glDisable(GL_POLYGON_OFFSET_FILL); // Reset culling glCullFace(GL_BACK); - // Bind background shader - m_backgroundShader->bind(); + return barSelectionFound; +} +void Bars3DRenderer::drawBackground(GLfloat backgroundRotation, + const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &viewMatrix, bool reflectingDraw, + bool drawingSelectionBuffer) +{ // Draw background if (m_cachedTheme->isBackgroundEnabled() && m_backgroundObj) { + QVector3D lightPos = m_cachedScene->activeLight()->position(); + QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); + GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f; + ShaderHelper *shader = 0; + + // Bind background shader + if (drawingSelectionBuffer) + shader = m_selectionShader; // Use single color shader when drawing to selection buffer + else + shader = m_backgroundShader; + shader->bind(); + QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - QVector3D backgroundScaler(rowScaleFactor, 1.0f, columnScaleFactor); - modelMatrix.translate(0.0f, m_backgroundAdjustment, 0.0f); + QVector3D backgroundScaler(m_scaleXWithBackground, m_scaleYWithBackground, + m_scaleZWithBackground); + QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor()); + if (m_reflectionEnabled) + backgroundColor.setW(backgroundColor.w() * m_reflectivity); + // Set shader bindings + shader->setUniformValue(shader->lightP(), lightPos); + shader->setUniformValue(shader->view(), viewMatrix); + if (drawingSelectionBuffer) { + // Use selectionSkipColor for background when drawing to selection buffer + shader->setUniformValue(shader->color(), selectionSkipColor); + } else { + shader->setUniformValue(shader->color(), backgroundColor); + } + shader->setUniformValue(shader->ambientS(), + m_cachedTheme->ambientLightStrength() * 2.0f); + shader->setUniformValue(shader->lightColor(), lightColor); + + // Draw floor modelMatrix.scale(backgroundScaler); - itModelMatrix.scale(backgroundScaler); - modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); - itModelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); + + if (m_yFlipped) + modelMatrix.rotate(m_xRightAngleRotation); + else + modelMatrix.rotate(m_xRightAngleRotationNeg); + + itModelMatrix = modelMatrix; #ifdef SHOW_DEPTH_TEXTURE_SCENE MVPMatrix = depthProjectionViewMatrix * modelMatrix; #else MVPMatrix = projectionViewMatrix * modelMatrix; #endif - QVector4D backgroundColor = Utils::vectorFromColor(m_cachedTheme->backgroundColor()); - - // Set shader bindings - m_backgroundShader->setUniformValue(m_backgroundShader->lightP(), lightPos); - m_backgroundShader->setUniformValue(m_backgroundShader->view(), viewMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->model(), modelMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->nModel(), - itModelMatrix.inverted().transposed()); - m_backgroundShader->setUniformValue(m_backgroundShader->MVP(), MVPMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->color(), backgroundColor); - m_backgroundShader->setUniformValue(m_backgroundShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.0f); - m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor); + // Set changed shader bindings + shader->setUniformValue(shader->model(), modelMatrix); + shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); + shader->setUniformValue(shader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + 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); - + shader->setUniformValue(shader->depth(), depthMVPMatrix); // Draw the object - m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture); - } else -#endif - { - // Set shadowless shader bindings - m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), - m_cachedTheme->lightStrength()); - + m_drawer->drawObject(shader, m_gridLineObj, 0, m_depthTexture); + } else { // Draw the object - m_drawer->drawObject(m_backgroundShader, m_backgroundObj); + m_drawer->drawObject(shader, m_gridLineObj); } - // Draw floor + // Draw walls modelMatrix = QMatrix4x4(); itModelMatrix = QMatrix4x4(); + modelMatrix.translate(0.0f, m_backgroundAdjustment, 0.0f); modelMatrix.scale(backgroundScaler); - - if (m_yFlipped) - modelMatrix.rotate(90.0f, 1.0f, 0.0f, 0.0f); - else - modelMatrix.rotate(-90.0f, 1.0f, 0.0f, 0.0f); - - itModelMatrix = modelMatrix; + itModelMatrix.scale(backgroundScaler); + modelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); + itModelMatrix.rotate(backgroundRotation, 0.0f, 1.0f, 0.0f); #ifdef SHOW_DEPTH_TEXTURE_SCENE MVPMatrix = depthProjectionViewMatrix * modelMatrix; #else MVPMatrix = projectionViewMatrix * modelMatrix; #endif + // Set changed shader bindings - m_backgroundShader->setUniformValue(m_backgroundShader->model(), modelMatrix); - m_backgroundShader->setUniformValue(m_backgroundShader->nModel(), - itModelMatrix.inverted().transposed()); - m_backgroundShader->setUniformValue(m_backgroundShader->MVP(), MVPMatrix); + shader->setUniformValue(shader->model(), modelMatrix); + shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); + shader->setUniformValue(shader->MVP(), MVPMatrix); + if (!m_reflectionEnabled || (m_reflectionEnabled && reflectingDraw)) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + shader->setUniformValue(shader->shadowQ(), m_shadowQualityToShader); + shader->setUniformValue(shader->depth(), depthMVPMatrix); + shader->setUniformValue(shader->lightS(), adjustedLightStrength); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - m_backgroundShader->setUniformValue(m_backgroundShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(m_backgroundShader, m_gridLineObj, 0, m_depthTexture); - } else -#endif - { - // Draw the object - m_drawer->drawObject(m_backgroundShader, m_gridLineObj); + // Draw the object + m_drawer->drawObject(shader, m_backgroundObj, 0, m_depthTexture); + } else { + // Set shadowless shader bindings + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); + + // Draw the object + m_drawer->drawObject(shader, m_backgroundObj); + } } } +} - // Disable textures - glDisable(GL_TEXTURE_2D); - - // Draw grid lines +void Bars3DRenderer::drawGridLines(const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &viewMatrix) +{ if (m_cachedTheme->isGridEnabled()) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; + QQuaternion lineRotation; + QVector3D lightPos = m_cachedScene->activeLight()->position(); + QVector4D lightColor = Utils::vectorFromColor(m_cachedTheme->lightColor()); + // Bind bar shader lineShader->bind(); @@ -1607,15 +1819,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) lineShader->setUniformValue(lineShader->color(), barColor); lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength()); lineShader->setUniformValue(lineShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + 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 -#endif - { + } else { // Set shadowless shader bindings lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 2.5f); @@ -1625,12 +1834,12 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) if (m_yFlipped) yFloorLinePosition = -yFloorLinePosition; - QVector3D gridLineScaler(rowScaleFactor, gridLineWidth, gridLineWidth); + QVector3D gridLineScaler(m_scaleXWithBackground, gridLineWidth, gridLineWidth); if (m_yFlipped) - lineRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f); + lineRotation = m_xRightAngleRotation; else - lineRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f); + lineRotation = m_xRightAngleRotationNeg; // Floor lines: rows for (GLfloat row = 0.0f; row <= m_cachedRowCount; row++) { @@ -1638,7 +1847,7 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - rowPos = row * m_cachedBarSpacing.height(); + GLfloat rowPos = row * m_cachedBarSpacing.height(); modelMatrix.translate(0.0f, yFloorLinePosition, (m_columnDepth - rowPos) / m_scaleFactor); modelMatrix.scale(gridLineScaler); @@ -1654,33 +1863,32 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + 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); -#endif } // Floor lines: columns -#if defined(QT_OPENGL_ES_2) - lineRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); -#endif - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, columnScaleFactor); + if (m_isOpenGLES) + lineRotation = m_yRightAngleRotation; + gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, m_scaleZWithBackground); for (GLfloat bar = 0.0f; bar <= m_cachedColumnCount; bar++) { QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - colPos = bar * m_cachedBarSpacing.width(); + GLfloat colPos = bar * m_cachedBarSpacing.width(); modelMatrix.translate((m_rowWidth - colPos) / m_scaleFactor, yFloorLinePosition, 0.0f); modelMatrix.scale(gridLineScaler); @@ -1696,31 +1904,31 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + 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); -#endif } if (m_axisCacheY.segmentCount() > 0) { // Wall lines: back wall int gridLineCount = m_axisCacheY.gridLineCount(); - GLfloat zWallLinePosition = -columnScaleFactor + gridLineOffset; + GLfloat zWallLinePosition = -m_scaleZWithBackground + gridLineOffset; if (m_zFlipped) zWallLinePosition = -zWallLinePosition; - gridLineScaler = QVector3D(rowScaleFactor, gridLineWidth, gridLineWidth); + gridLineScaler = QVector3D(m_scaleXWithBackground, gridLineWidth, gridLineWidth); for (int line = 0; line < gridLineCount; line++) { QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; @@ -1732,8 +1940,8 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) modelMatrix.scale(gridLineScaler); itModelMatrix.scale(gridLineScaler); if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -1744,33 +1952,33 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + 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); -#endif } // Wall lines: side wall - GLfloat xWallLinePosition = -rowScaleFactor + gridLineOffset; + GLfloat xWallLinePosition = -m_scaleXWithBackground + gridLineOffset; if (m_xFlipped) xWallLinePosition = -xWallLinePosition; if (m_xFlipped) - lineRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f); + lineRotation = m_yRightAngleRotationNeg; else - lineRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); + lineRotation = m_yRightAngleRotation; - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, columnScaleFactor); + gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, m_scaleZWithBackground); for (int line = 0; line < gridLineCount; line++) { QMatrix4x4 modelMatrix; QMatrix4x4 MVPMatrix; @@ -1792,76 +2000,27 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + 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); -#endif } } } - - Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); - - drawLabels(false, activeCamera, viewMatrix, projectionMatrix, rowScaleFactor, - columnScaleFactor); - - // Handle selected bar label generation - if (barSelectionFound) { - // Print value of selected bar - glDisable(GL_DEPTH_TEST); - // Draw the selection label - LabelItem &labelItem = selectionLabelItem(); - if (m_selectedBar != selectedBar || m_updateLabels || !labelItem.textureId() - || m_selectionLabelDirty) { - QString labelText = selectionLabel(); - if (labelText.isNull() || m_selectionLabelDirty) { - labelText = m_selectedSeriesCache->itemLabel(); - setSelectionLabel(labelText); - m_selectionLabelDirty = false; - } - m_drawer->generateLabelItem(labelItem, labelText); - m_selectedBar = selectedBar; - } - - Drawer::LabelPosition position = - m_selectedBar->height() >= 0 ? Drawer::LabelOver : Drawer::LabelBelow; - - m_drawer->drawLabel(*selectedBar, labelItem, viewMatrix, projectionMatrix, - zeroVector, identityQuaternion, selectedBar->height(), - m_cachedSelectionMode, m_labelShader, - m_labelObj, activeCamera, true, false, position); - - // Reset label update flag; they should have been updated when we get here - m_updateLabels = false; - - glEnable(GL_DEPTH_TEST); - } else { - m_selectedBar = 0; - } - - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - - // Release shader - glUseProgram(0); - m_selectionDirty = false; } void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamera, - const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - GLfloat rowScaleFactor, GLfloat columnScaleFactor) { + const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix) { ShaderHelper *shader = 0; GLfloat alphaForValueSelection = labelValueAlpha / 255.0f; GLfloat alphaForRowSelection = labelRowAlpha / 255.0f; @@ -1873,7 +2032,6 @@ void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamer shader = m_labelShader; shader->bind(); - glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -1894,8 +2052,8 @@ void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamer int labelCount = m_axisCacheY.labelCount(); GLfloat labelMarginXTrans = labelMargin; GLfloat labelMarginZTrans = labelMargin; - GLfloat labelXTrans = rowScaleFactor; - GLfloat labelZTrans = columnScaleFactor; + GLfloat labelXTrans = m_scaleXWithBackground; + GLfloat labelZTrans = m_scaleZWithBackground; QVector3D backLabelRotation(0.0f, -90.0f, 0.0f); QVector3D sideLabelRotation(0.0f, 0.0f, 0.0f); Qt::AlignmentFlag backAlignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; @@ -1999,10 +2157,8 @@ void Bars3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCamer fractionCamY = activeCamera->yRotation() * labelAngleFraction; fractionCamX = activeCamera->xRotation() * labelAngleFraction; GLfloat labelYAdjustment = 0.005f; - GLfloat scaledRowWidth = rowScaleFactor; - GLfloat scaledColumnDepth = columnScaleFactor; - GLfloat colPosValue = scaledRowWidth + labelMargin; - GLfloat rowPosValue = scaledColumnDepth + labelMargin; + GLfloat colPosValue = m_scaleXWithBackground + labelMargin; + GLfloat rowPosValue = m_scaleZWithBackground + labelMargin; GLfloat rowPos = 0.0f; GLfloat colPos = 0.0f; Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; @@ -2296,14 +2452,8 @@ void Bars3DRenderer::updateAxisRange(QAbstract3DAxis::AxisOrientation orientatio { Abstract3DRenderer::updateAxisRange(orientation, min, max); - if (orientation == QAbstract3DAxis::AxisOrientationY) { - // Check if we have negative values - if (min < 0) - m_hasNegativeValues = true; - else if (min >= 0) - m_hasNegativeValues = false; + if (orientation == QAbstract3DAxis::AxisOrientationY) calculateHeightAdjustment(); - } } void Bars3DRenderer::updateAxisReversed(QAbstract3DAxis::AxisOrientation orientation, bool enable) @@ -2384,10 +2534,12 @@ void Bars3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality quality handleShadowQualityChange(); -#if !defined(QT_OPENGL_ES_2) // Re-init depth buffer updateDepthBuffer(); -#endif + + // Redraw to handle both reflections and shadows on background + if (m_reflectionEnabled) + needRender(); } void Bars3DRenderer::loadBackgroundMesh() @@ -2398,6 +2550,8 @@ void Bars3DRenderer::loadBackgroundMesh() void Bars3DRenderer::updateTextures() { + Abstract3DRenderer::updateTextures(); + // Drawer has changed; this flag needs to be checked when checking if we need to update labels m_updateLabels = true; } @@ -2420,30 +2574,60 @@ void Bars3DRenderer::calculateSceneScalingFactors() m_maxDimension = qMax(m_rowWidth, m_columnDepth); m_scaleFactor = qMin((m_cachedColumnCount * (m_maxDimension / m_maxSceneSize)), (m_cachedRowCount * (m_maxDimension / m_maxSceneSize))); + + // Single bar scaling m_scaleX = m_cachedBarThickness.width() / m_scaleFactor; m_scaleZ = m_cachedBarThickness.height() / m_scaleFactor; + + // Whole graph scale factors + m_xScaleFactor = m_rowWidth / m_scaleFactor; + m_zScaleFactor = m_columnDepth / m_scaleFactor; + + if (m_requestedMargin < 0.0f) { + m_hBackgroundMargin = 0.0f; + m_vBackgroundMargin = 0.0f; + } else { + m_hBackgroundMargin = m_requestedMargin; + m_vBackgroundMargin = m_requestedMargin; + } + + m_scaleXWithBackground = m_xScaleFactor + m_hBackgroundMargin; + m_scaleYWithBackground = 1.0f + m_vBackgroundMargin; + m_scaleZWithBackground = m_zScaleFactor + m_hBackgroundMargin; + + updateCameraViewport(); + updateCustomItemPositions(); } void Bars3DRenderer::calculateHeightAdjustment() { + float min = m_axisCacheY.min(); + float max = m_axisCacheY.max(); GLfloat newAdjustment = 1.0f; - GLfloat maxAbs = qFabs(m_axisCacheY.max()); - - if (m_axisCacheY.max() < 0.0f) { - m_heightNormalizer = GLfloat(qFabs(m_axisCacheY.min()) - qFabs(m_axisCacheY.max())); - maxAbs = qFabs(m_axisCacheY.max()) - qFabs(m_axisCacheY.min()); + m_actualFloorLevel = qBound(min, m_floorLevel, max); + GLfloat maxAbs = qFabs(max - m_actualFloorLevel); + + // Check if we have negative values + if (min < m_actualFloorLevel) + m_hasNegativeValues = true; + else if (min >= m_actualFloorLevel) + m_hasNegativeValues = false; + + if (max < m_actualFloorLevel) { + m_heightNormalizer = GLfloat(qFabs(min) - qFabs(max)); + maxAbs = qFabs(max) - qFabs(min); } else { - m_heightNormalizer = GLfloat(m_axisCacheY.max() - m_axisCacheY.min()); + m_heightNormalizer = GLfloat(max - min); } // Height fractions are used in gradient calculations and are therefore doubled // Note that if max or min is exactly zero, we still consider it outside the range - if (m_axisCacheY.max() <= 0.0f || m_axisCacheY.min() >= 0.0f) { + if (max <= m_actualFloorLevel || min >= m_actualFloorLevel) { m_noZeroInRange = true; m_gradientFraction = 2.0f; } else { m_noZeroInRange = false; - GLfloat minAbs = qFabs(m_axisCacheY.min()); + GLfloat minAbs = qFabs(min - m_actualFloorLevel); m_gradientFraction = qMax(minAbs, maxAbs) / m_heightNormalizer * 2.0f; } @@ -2551,13 +2735,13 @@ void Bars3DRenderer::updateSlicingActive(bool isSlicing) m_cachedIsSlicingActivated = isSlicing; - if (!m_cachedIsSlicingActivated) - initSelectionBuffer(); // We need to re-init selection buffer in case there has been a resize + if (!m_cachedIsSlicingActivated) { + // We need to re-init selection buffer in case there has been a resize + initSelectionBuffer(); + initCursorPositionBuffer(); + } -#if !defined(QT_OPENGL_ES_2) updateDepthBuffer(); // Re-init depth buffer as well -#endif - m_selectionDirty = true; } @@ -2598,32 +2782,35 @@ void Bars3DRenderer::initSelectionBuffer() m_selectionDepthBuffer); } -#if !defined(QT_OPENGL_ES_2) void Bars3DRenderer::initDepthShader() { - if (m_depthShader) - delete m_depthShader; - m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), - QStringLiteral(":/shaders/fragmentDepth")); - m_depthShader->initialize(); + if (!m_isOpenGLES) { + if (m_depthShader) + delete m_depthShader; + m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), + QStringLiteral(":/shaders/fragmentDepth")); + m_depthShader->initialize(); + } } void Bars3DRenderer::updateDepthBuffer() { - m_textureHelper->deleteTexture(&m_depthTexture); + if (!m_isOpenGLES) { + m_textureHelper->deleteTexture(&m_depthTexture); - if (m_primarySubViewport.size().isEmpty()) - return; + 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(); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_depthTexture = + m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), + m_depthFrameBuffer, + m_shadowQualityMultiplier); + if (!m_depthTexture) + lowerShadowQuality(); + } } } -#endif void Bars3DRenderer::initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader) @@ -2634,14 +2821,6 @@ void Bars3DRenderer::initBackgroundShaders(const QString &vertexShader, m_backgroundShader->initialize(); } -void Bars3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) -{ - if (m_labelShader) - delete m_labelShader; - m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); - m_labelShader->initialize(); -} - QVector3D Bars3DRenderer::convertPositionToTranslation(const QVector3D &position, bool isAbsolute) { float xTrans = 0.0f; @@ -2649,15 +2828,15 @@ QVector3D Bars3DRenderer::convertPositionToTranslation(const QVector3D &position float zTrans = 0.0f; if (!isAbsolute) { // Convert row and column to translation on graph - xTrans = (((position.x() + 0.5f) * m_cachedBarSpacing.width()) - m_rowWidth) - / m_scaleFactor; - zTrans = (m_columnDepth - ((position.z() + 0.5f) * m_cachedBarSpacing.height())) - / m_scaleFactor; + xTrans = (((position.x() - m_axisCacheX.min() + 0.5f) * m_cachedBarSpacing.width()) + - m_rowWidth) / m_scaleFactor; + zTrans = (m_columnDepth - ((position.z() - m_axisCacheZ.min() + 0.5f) + * m_cachedBarSpacing.height())) / m_scaleFactor; yTrans = m_axisCacheY.positionAt(position.y()); } else { - xTrans = position.x() * m_scaleX; + xTrans = position.x() * m_xScaleFactor; yTrans = position.y() + m_backgroundAdjustment; - zTrans = position.z() * m_scaleZ; + zTrans = position.z() * -m_zScaleFactor; } return QVector3D(xTrans, yTrans, zTrans); } @@ -2667,4 +2846,18 @@ void Bars3DRenderer::updateAspectRatio(float ratio) Q_UNUSED(ratio) } +void Bars3DRenderer::updateFloorLevel(float level) +{ + foreach (SeriesRenderCache *cache, m_renderCacheList) + cache->setDataDirty(true); + m_floorLevel = level; + calculateHeightAdjustment(); +} + +void Bars3DRenderer::updateMargin(float margin) +{ + Abstract3DRenderer::updateMargin(margin); + calculateSceneScalingFactors(); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/bars3drenderer_p.h b/src/datavisualization/engine/bars3drenderer_p.h index 3a0ab3b8..094b8fd9 100644 --- a/src/datavisualization/engine/bars3drenderer_p.h +++ b/src/datavisualization/engine/bars3drenderer_p.h @@ -66,9 +66,7 @@ private: ShaderHelper *m_depthShader; ShaderHelper *m_selectionShader; ShaderHelper *m_backgroundShader; - ShaderHelper *m_labelShader; GLuint m_bgrTexture; - GLuint m_depthTexture; GLuint m_selectionTexture; GLuint m_depthFrameBuffer; GLuint m_selectionFrameBuffer; @@ -86,7 +84,6 @@ private: GLfloat m_scaleFactor; GLfloat m_maxSceneSize; QPoint m_visualSelectedBarPos; - bool m_resetCameraBaseOrientation; QPoint m_selectedBarPos; BarSeriesRenderCache *m_selectedSeriesCache; BarRenderItem m_dummyBarRenderItem; @@ -100,6 +97,10 @@ private: bool m_haveUniformColorSeries; bool m_haveGradientSeries; float m_zeroPosition; + float m_xScaleFactor; + float m_zScaleFactor; + float m_floorLevel; + float m_actualFloorLevel; public: explicit Bars3DRenderer(Bars3DController *controller); @@ -116,9 +117,13 @@ public: QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute); void updateAspectRatio(float ratio); + void updateFloorLevel(float level); + void updateMargin(float margin); protected: virtual void initializeOpenGL(); + virtual void fixCameraTarget(QVector3D &target); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds); public slots: void updateMultiSeriesScaling(bool uniform); @@ -146,18 +151,25 @@ private: void drawSlicedScene(); void drawScene(GLuint defaultFboHandle); void drawLabels(bool drawSelection, const Q3DCamera *activeCamera, - const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix, - GLfloat rowScaleFactor, GLfloat columnScaleFactor); + const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionMatrix); + + bool drawBars(BarRenderItem **selectedBar, const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix, + GLint startRow, GLint stopRow, GLint stepRow, + GLint startBar, GLint stopBar, GLint stepBar, GLfloat reflection = 1.0f); + void drawBackground(GLfloat backgroundRotation, const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &viewMatrix, + bool reflectingDraw = false, bool drawingSelectionBuffer = false); + void drawGridLines(const QMatrix4x4 &depthProjectionViewMatrix, + const QMatrix4x4 &projectionViewMatrix, + const QMatrix4x4 &viewMatrix); void loadBackgroundMesh(); void initSelectionShader(); void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader); - void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); void initSelectionBuffer(); -#if !defined(QT_OPENGL_ES_2) void initDepthShader(); void updateDepthBuffer(); -#endif void calculateSceneScalingFactors(); void calculateHeightAdjustment(); Abstract3DController::SelectionType isSelected(int row, int bar, diff --git a/src/datavisualization/engine/drawer.cpp b/src/datavisualization/engine/drawer.cpp index b8726840..d4352702 100644 --- a/src/datavisualization/engine/drawer.cpp +++ b/src/datavisualization/engine/drawer.cpp @@ -69,10 +69,9 @@ Drawer::~Drawer() void Drawer::initializeOpenGL() { - if (!m_textureHelper) { - initializeOpenGLFunctions(); + initializeOpenGLFunctions(); + if (!m_textureHelper) m_textureHelper = new TextureHelper(); - } } void Drawer::setTheme(Q3DTheme *theme) @@ -93,8 +92,11 @@ QFont Drawer::font() const } void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLuint textureId, - GLuint depthTextureId) + GLuint depthTextureId, GLuint textureId3D) { +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(textureId3D) +#endif if (textureId) { // Activate texture glActiveTexture(GL_TEXTURE0); @@ -108,6 +110,14 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui glBindTexture(GL_TEXTURE_2D, depthTextureId); shader->setUniformValue(shader->shadow(), 1); } +#if !defined(QT_OPENGL_ES_2) + if (textureId3D) { + // Activate texture + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_3D, textureId3D); + shader->setUniformValue(shader->texture(), 2); + } +#endif // 1st attribute buffer : vertices glEnableVertexAttribArray(shader->posAtt()); @@ -115,14 +125,18 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui glVertexAttribPointer(shader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); // 2nd attribute buffer : normals - glEnableVertexAttribArray(shader->normalAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->normalBuf()); - glVertexAttribPointer(shader->normalAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + if (shader->normalAtt() >= 0) { + glEnableVertexAttribArray(shader->normalAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->normalBuf()); + glVertexAttribPointer(shader->normalAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + } // 3rd attribute buffer : UVs - glEnableVertexAttribArray(shader->uvAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->uvBuf()); - glVertexAttribPointer(shader->uvAtt(), 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + if (shader->uvAtt() >= 0) { + glEnableVertexAttribArray(shader->uvAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->uvBuf()); + glVertexAttribPointer(shader->uvAtt(), 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + } // Index buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); @@ -134,11 +148,19 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDisableVertexAttribArray(shader->uvAtt()); - glDisableVertexAttribArray(shader->normalAtt()); + if (shader->uvAtt() >= 0) + glDisableVertexAttribArray(shader->uvAtt()); + if (shader->normalAtt() >= 0) + glDisableVertexAttribArray(shader->normalAtt()); glDisableVertexAttribArray(shader->posAtt()); // Release textures +#if !defined(QT_OPENGL_ES_2) + if (textureId3D) { + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_3D, 0); + } +#endif if (depthTextureId) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); @@ -206,13 +228,27 @@ void Drawer::drawPoint(ShaderHelper *shader) glDisableVertexAttribArray(shader->posAtt()); } -void Drawer::drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object) +void Drawer::drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object, GLuint textureId) { + if (textureId) { + // Activate texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textureId); + shader->setUniformValue(shader->texture(), 0); + } + // 1st attribute buffer : vertices glEnableVertexAttribArray(shader->posAtt()); glBindBuffer(GL_ARRAY_BUFFER, object->pointBuf()); glVertexAttribPointer(shader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, (void*)0); + // 2nd attribute buffer : UVs + if (textureId) { + glEnableVertexAttribArray(shader->uvAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->uvBuf()); + glVertexAttribPointer(shader->uvAtt(), 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + } + // Draw the points glDrawArrays(GL_POINTS, 0, object->indexCount()); @@ -220,6 +256,12 @@ void Drawer::drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object) glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableVertexAttribArray(shader->posAtt()); + + if (textureId) { + glDisableVertexAttribArray(shader->uvAtt()); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + } } void Drawer::drawLine(ShaderHelper *shader) diff --git a/src/datavisualization/engine/drawer_p.h b/src/datavisualization/engine/drawer_p.h index ffcea315..0c96724c 100644 --- a/src/datavisualization/engine/drawer_p.h +++ b/src/datavisualization/engine/drawer_p.h @@ -75,11 +75,11 @@ public: inline GLfloat scaledFontSize() const { return m_scaledFontSize; } void drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLuint textureId = 0, - GLuint depthTextureId = 0); + GLuint depthTextureId = 0, GLuint textureId3D = 0); void drawSelectionObject(ShaderHelper *shader, AbstractObjectHelper *object); void drawSurfaceGrid(ShaderHelper *shader, SurfaceObject *object); void drawPoint(ShaderHelper *shader); - void drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object); + void drawPoints(ShaderHelper *shader, ScatterPointBufferHelper *object, GLuint textureId); void drawLine(ShaderHelper *shader); void drawLabel(const AbstractRenderItem &item, const LabelItem &labelItem, const QMatrix4x4 &viewmatrix, const QMatrix4x4 &projectionmatrix, diff --git a/src/datavisualization/engine/engine.pri b/src/datavisualization/engine/engine.pri index 96fa7fa9..60703e96 100644 --- a/src/datavisualization/engine/engine.pri +++ b/src/datavisualization/engine/engine.pri @@ -57,3 +57,5 @@ SOURCES += $$PWD/qabstract3dgraph.cpp \ RESOURCES += engine/engine.qrc OTHER_FILES += $$PWD/meshes/* $$PWD/shaders/* + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/engine/engine.qrc b/src/datavisualization/engine/engine.qrc index 673b6ee0..0ef169c9 100644 --- a/src/datavisualization/engine/engine.qrc +++ b/src/datavisualization/engine/engine.qrc @@ -57,5 +57,18 @@ <file alias="fragmentTexture">shaders/texture.frag</file> <file alias="fragmentTextureES2">shaders/texture_ES2.frag</file> <file alias="vertexTexture">shaders/texture.vert</file> + <file alias="fragmentTexturedSurfaceShadowFlat">shaders/surfaceTexturedShadowFlat.frag</file> + <file alias="fragmentSurfaceTexturedFlat">shaders/surfaceTexturedFlat.frag</file> + <file alias="fragmentTexture3D">shaders/texture3d.frag</file> + <file alias="vertexTexture3D">shaders/texture3d.vert</file> + <file alias="fragmentTexture3DSlice">shaders/texture3dslice.frag</file> + <file alias="vertexShadowNoMatrices">shaders/shadowNoMatrices.vert</file> + <file alias="vertexNoMatrices">shaders/defaultNoMatrices.vert</file> + <file alias="fragmentTexture3DLowDef">shaders/texture3dlowdef.frag</file> + <file alias="vertexPointES2_UV">shaders/point_ES2_UV.vert</file> + <file alias="fragment3DSliceFrames">shaders/3dsliceframes.frag</file> + <file alias="vertexPosition">shaders/position.vert</file> + <file alias="fragmentPositionMap">shaders/positionmap.frag</file> + <file alias="fragmentTexturedSurfaceShadow">shaders/surfaceTexturedShadow.frag</file> </qresource> </RCC> diff --git a/src/datavisualization/engine/q3dbars.cpp b/src/datavisualization/engine/q3dbars.cpp index bb7aca89..d42c11b7 100644 --- a/src/datavisualization/engine/q3dbars.cpp +++ b/src/datavisualization/engine/q3dbars.cpp @@ -331,6 +331,26 @@ QBar3DSeries *Q3DBars::selectedSeries() const } /*! + * \property Q3DBars::floorLevel + * + * The desired floor level for the bar graph in Y-axis data coordinates. + * The actual floor level cannot go below Y-axis minimum or above Y-axis maximum. + * Defaults to zero. + */ +void Q3DBars::setFloorLevel(float level) +{ + if (level != floorLevel()) { + dptr()->m_shared->setFloorLevel(level); + emit floorLevelChanged(level); + } +} + +float Q3DBars::floorLevel() const +{ + return dptrc()->m_shared->floorLevel(); +} + +/*! * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use, * addAxis is simply used to give the ownership of the \a axis to the graph. * The \a axis must not be null or added to another graph. diff --git a/src/datavisualization/engine/q3dbars.h b/src/datavisualization/engine/q3dbars.h index 7f9c981f..df1c846e 100644 --- a/src/datavisualization/engine/q3dbars.h +++ b/src/datavisualization/engine/q3dbars.h @@ -40,6 +40,7 @@ class QT_DATAVISUALIZATION_EXPORT Q3DBars : public QAbstract3DGraph Q_PROPERTY(QValue3DAxis *valueAxis READ valueAxis WRITE setValueAxis NOTIFY valueAxisChanged) Q_PROPERTY(QBar3DSeries *primarySeries READ primarySeries WRITE setPrimarySeries NOTIFY primarySeriesChanged) Q_PROPERTY(QBar3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged) + Q_PROPERTY(float floorLevel READ floorLevel WRITE setFloorLevel NOTIFY floorLevelChanged) public: explicit Q3DBars(const QSurfaceFormat *format = 0, QWindow *parent = 0); @@ -75,6 +76,8 @@ public: QList<QAbstract3DAxis *> axes() const; QBar3DSeries *selectedSeries() const; + void setFloorLevel(float level); + float floorLevel() const; signals: void multiSeriesUniformChanged(bool uniform); @@ -86,6 +89,7 @@ signals: void valueAxisChanged(QValue3DAxis *axis); void primarySeriesChanged(QBar3DSeries *series); void selectedSeriesChanged(QBar3DSeries *series); + void floorLevelChanged(float level); private: Q3DBarsPrivate *dptr(); diff --git a/src/datavisualization/engine/q3dcamera.cpp b/src/datavisualization/engine/q3dcamera.cpp index 06fd570c..897c3779 100644 --- a/src/datavisualization/engine/q3dcamera.cpp +++ b/src/datavisualization/engine/q3dcamera.cpp @@ -110,8 +110,36 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty float Camera3D::zoomLevel * - * This property contains the the camera zoom level in percentage. 100.0 means there is no zoom - * in or out set in the camera. + * This property contains the the camera zoom level in percentage. The default value of \c{100.0} + * means there is no zoom in or out set in the camera. + * The value is limited within the bounds defined by minZoomLevel and maxZoomLevel properties. + * + * \sa minZoomLevel, maxZoomLevel + */ + +/*! + * \qmlproperty float Camera3D::minZoomLevel + * + * This property contains the the minimum allowed camera zoom level. + * If the new minimum level is more than the existing maximum level, the maximum level is + * adjusted to the new minimum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * The minZoomLevel cannot be set below \c{1.0}. + * Defaults to \c{10.0}. + * + * \sa zoomLevel, maxZoomLevel + */ + +/*! + * \qmlproperty float Camera3D::maxZoomLevel + * + * This property contains the the maximum allowed camera zoom level. + * If the new maximum level is less than the existing minimum level, the minimum level is + * adjusted to the new maximum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * Defaults to \c{500.0f}. + * + * \sa zoomLevel, minZoomLevel */ /*! @@ -137,6 +165,19 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty vector3d Camera3D::target + * \since QtDataVisualization 1.2 + * + * Holds the camera \a target as a vector3d. Defaults to \c {vector3d(0.0, 0.0, 0.0)}. + * + * Valid coordinate values are between \c{-1.0...1.0}, where the edge values indicate + * the edges of the corresponding axis range. Any values outside this range are clamped to the edge. + * + * \note For bar graphs, the Y-coordinate is ignored and camera always targets a point on + * the horizontal background. + */ + +/*! * Constructs a new 3D camera with position set to origin, up direction facing towards the Y-axis * and looking at origin by default. An optional \a parent parameter can be given and is then passed * to QObject constructor. @@ -160,22 +201,11 @@ Q3DCamera::~Q3DCamera() */ void Q3DCamera::copyValuesFrom(const Q3DObject &source) { - Q3DObject::copyValuesFrom(source); + // Note: Do not copy values from parent, as we are handling the position internally const Q3DCamera &sourceCamera = static_cast<const Q3DCamera &>(source); - d_ptr->m_target.setX(sourceCamera.d_ptr->m_target.x()); - d_ptr->m_target.setY(sourceCamera.d_ptr->m_target.y()); - d_ptr->m_target.setZ(sourceCamera.d_ptr->m_target.z()); - - d_ptr->m_up.setX(sourceCamera.d_ptr->m_up.x()); - d_ptr->m_up.setY(sourceCamera.d_ptr->m_up.y()); - d_ptr->m_up.setZ(sourceCamera.d_ptr->m_up.z()); - - float *values = new float[16]; - sourceCamera.d_ptr->m_viewMatrix.copyDataTo(values); - d_ptr->m_viewMatrix = QMatrix4x4(values); - delete[] values; + d_ptr->m_requestedTarget = sourceCamera.d_ptr->m_requestedTarget; d_ptr->m_xRotation = sourceCamera.d_ptr->m_xRotation; d_ptr->m_yRotation = sourceCamera.d_ptr->m_yRotation; @@ -189,6 +219,8 @@ void Q3DCamera::copyValuesFrom(const Q3DObject &source) d_ptr->m_wrapYRotation = sourceCamera.d_ptr->m_wrapYRotation; d_ptr->m_zoomLevel = sourceCamera.d_ptr->m_zoomLevel; + d_ptr->m_minZoomLevel = sourceCamera.d_ptr->m_minZoomLevel; + d_ptr->m_maxZoomLevel = sourceCamera.d_ptr->m_maxZoomLevel; d_ptr->m_activePreset = sourceCamera.d_ptr->m_activePreset; } @@ -389,6 +421,9 @@ void Q3DCamera::setCameraPreset(CameraPreset preset) break; } + // All presets target the center of the graph + setTarget(zeroVector); + if (d_ptr->m_activePreset != preset) { d_ptr->m_activePreset = preset; setDirty(true); @@ -399,8 +434,11 @@ void Q3DCamera::setCameraPreset(CameraPreset preset) /*! * \property Q3DCamera::zoomLevel * - * This property contains the the camera zoom level in percentage. \c 100.0f means there is no zoom - * in or out set in the camera. + * This property contains the the camera zoom level in percentage. The default value of \c{100.0f} + * means there is no zoom in or out set in the camera. + * The value is limited within the bounds defined by minZoomLevel and maxZoomLevel properties. + * + * \sa minZoomLevel, maxZoomLevel */ float Q3DCamera::zoomLevel() const { @@ -409,10 +447,73 @@ float Q3DCamera::zoomLevel() const void Q3DCamera::setZoomLevel(float zoomLevel) { - if (d_ptr->m_zoomLevel != zoomLevel) { - d_ptr->m_zoomLevel = zoomLevel; + float newZoomLevel = qBound(d_ptr->m_minZoomLevel, zoomLevel, d_ptr->m_maxZoomLevel); + + if (d_ptr->m_zoomLevel != newZoomLevel) { + d_ptr->m_zoomLevel = newZoomLevel; + setDirty(true); + emit zoomLevelChanged(newZoomLevel); + } +} + +/*! + * \property Q3DCamera::minZoomLevel + * + * This property contains the the minimum allowed camera zoom level. + * If the new minimum level is more than the existing maximum level, the maximum level is + * adjusted to the new minimum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * The minZoomLevel cannot be set below \c{1.0f}. + * Defaults to \c{10.0f}. + * + * \sa zoomLevel, maxZoomLevel + */ +float Q3DCamera::minZoomLevel() const +{ + return d_ptr->m_minZoomLevel; +} + +void Q3DCamera::setMinZoomLevel(float zoomLevel) +{ + // Don't allow minimum to be below one, as that can cause zoom to break. + float newMinLevel = qMax(zoomLevel, 1.0f); + if (d_ptr->m_minZoomLevel != newMinLevel) { + d_ptr->m_minZoomLevel = newMinLevel; + if (d_ptr->m_maxZoomLevel < newMinLevel) + setMaxZoomLevel(newMinLevel); + setZoomLevel(d_ptr->m_zoomLevel); setDirty(true); - emit zoomLevelChanged(zoomLevel); + emit minZoomLevelChanged(newMinLevel); + } +} + +/*! + * \property Q3DCamera::maxZoomLevel + * + * This property contains the the maximum allowed camera zoom level. + * If the new maximum level is less than the existing minimum level, the minimum level is + * adjusted to the new maximum as well. + * If current zoomLevel is outside the new bounds, it is adjusted as well. + * Defaults to \c{500.0f}. + * + * \sa zoomLevel, minZoomLevel + */ +float Q3DCamera::maxZoomLevel() const +{ + return d_ptr->m_maxZoomLevel; +} + +void Q3DCamera::setMaxZoomLevel(float zoomLevel) +{ + // Don't allow maximum to be below one, as that can cause zoom to break. + float newMaxLevel = qMax(zoomLevel, 1.0f); + if (d_ptr->m_maxZoomLevel != newMaxLevel) { + d_ptr->m_maxZoomLevel = newMaxLevel; + if (d_ptr->m_minZoomLevel > newMaxLevel) + setMinZoomLevel(newMaxLevel); + setZoomLevel(d_ptr->m_zoomLevel); + setDirty(true); + emit maxZoomLevelChanged(newMaxLevel); } } @@ -459,16 +560,61 @@ void Q3DCamera::setWrapYRotation(bool isEnabled) /*! * Utility function that sets the camera rotations and distance.\a horizontal and \a vertical * define the camera rotations to be used. - * Optional \a zoom parameter can be given to set the zoom percentage of the camera in range of - * \c{10.0f - 500.0f}. + * Optional \a zoom parameter can be given to set the zoom percentage of the camera within + * the bounds defined by minZoomLevel and maxZoomLevel properties. */ void Q3DCamera::setCameraPosition(float horizontal, float vertical, float zoom) { - setZoomLevel(qBound(10.0f, zoom, 500.0f)); + setZoomLevel(zoom); setXRotation(horizontal); setYRotation(vertical); } +/*! + * \property Q3DCamera::target + * \since QtDataVisualization 1.2 + * + * Holds the camera \a target as a QVector3D. Defaults to \c {QVector3D(0.0, 0.0, 0.0)}. + * + * Valid coordinate values are between \c{-1.0...1.0}, where the edge values indicate + * the edges of the corresponding axis range. Any values outside this range are clamped to the edge. + * + * \note For bar graphs, the Y-coordinate is ignored and camera always targets a point on + * the horizontal background. + */ +QVector3D Q3DCamera::target() const +{ + return d_ptr->m_requestedTarget; +} + +void Q3DCamera::setTarget(const QVector3D &target) +{ + QVector3D newTarget = target; + + if (newTarget.x() < -1.0f) + newTarget.setX(-1.0f); + else if (newTarget.x() > 1.0f) + newTarget.setX(1.0f); + + if (newTarget.y() < -1.0f) + newTarget.setY(-1.0f); + else if (newTarget.y() > 1.0f) + newTarget.setY(1.0f); + + if (newTarget.z() < -1.0f) + newTarget.setZ(-1.0f); + else if (newTarget.z() > 1.0f) + newTarget.setZ(1.0f); + + if (d_ptr->m_requestedTarget != newTarget) { + if (d_ptr->m_activePreset != CameraPresetNone) + d_ptr->m_activePreset = CameraPresetNone; + d_ptr->m_requestedTarget = newTarget; + setDirty(true); + emit targetChanged(newTarget); + } +} + Q3DCameraPrivate::Q3DCameraPrivate(Q3DCamera *q) : q_ptr(q), m_isViewMatrixUpdateActive(true), @@ -479,6 +625,8 @@ Q3DCameraPrivate::Q3DCameraPrivate(Q3DCamera *q) : m_maxXRotation(180.0f), m_maxYRotation(90.0f), m_zoomLevel(100.0f), + m_minZoomLevel(10.0f), + m_maxZoomLevel(500.0f), m_wrapXRotation(true), m_wrapYRotation(false), m_activePreset(Q3DCamera::CameraPresetNone) @@ -645,9 +793,9 @@ void Q3DCameraPrivate::updateViewMatrix(float zoomAdjustment) QMatrix4x4 viewMatrix; // Apply to view matrix - viewMatrix.lookAt(q_ptr->position(), m_target, m_up); + viewMatrix.lookAt(q_ptr->position(), m_actualTarget, m_up); // Compensate for translation (if d_ptr->m_target is off origin) - viewMatrix.translate(m_target.x(), m_target.y(), m_target.z()); + viewMatrix.translate(m_actualTarget.x(), m_actualTarget.y(), m_actualTarget.z()); // Apply rotations // Handle x and z rotation when y -angle is other than 0 viewMatrix.rotate(m_xRotation, 0, qCos(qDegreesToRadians(m_yRotation)), @@ -657,7 +805,7 @@ void Q3DCameraPrivate::updateViewMatrix(float zoomAdjustment) // handle zoom by scaling viewMatrix.scale(zoom / 100.0f); // Compensate for translation (if d_ptr->m_target is off origin) - viewMatrix.translate(-m_target.x(), -m_target.y(), -m_target.z()); + viewMatrix.translate(-m_actualTarget.x(), -m_actualTarget.y(), -m_actualTarget.z()); setViewMatrix(viewMatrix); } @@ -713,9 +861,9 @@ void Q3DCameraPrivate::setBaseOrientation(const QVector3D &basePosition, const QVector3D &target, const QVector3D &baseUp) { - if (q_ptr->position() != basePosition || m_target != target || m_up != baseUp) { + if (q_ptr->position() != basePosition || m_actualTarget != target || m_up != baseUp) { q_ptr->setPosition(basePosition); - m_target = target; + m_actualTarget = target; m_up = baseUp; q_ptr->setDirty(true); } @@ -737,20 +885,33 @@ QVector3D Q3DCameraPrivate::calculatePositionRelativeToCamera(const QVector3D &r float distanceModifier) const { // Move the position with camera - GLfloat radiusFactor = cameraDistance * (1.5f + distanceModifier); - GLfloat xAngle; - GLfloat yAngle; + const float radiusFactor = cameraDistance * (1.5f + distanceModifier); + float xAngle; + float yAngle; + if (!fixedRotation) { xAngle = qDegreesToRadians(m_xRotation); - yAngle = qDegreesToRadians(m_yRotation); + float yRotation = m_yRotation; + // Light must not be paraller to eye vector, so fudge the y rotation a bit. + // Note: This needs redoing if we ever allow arbitrary light positioning. + const float yMargin = 0.1f; // Smaller margins cause weird shadow artifacts on tops of bars + const float absYRotation = qAbs(yRotation); + if (absYRotation < 90.0f + yMargin && absYRotation > 90.0f - yMargin) { + if (yRotation < 0.0f) + yRotation = -90.0f + yMargin; + else + yRotation = 90.0f - yMargin; + } + yAngle = qDegreesToRadians(yRotation); } else { xAngle = qDegreesToRadians(fixedRotation); yAngle = 0; } - GLfloat radius = (radiusFactor + relativePosition.y()); // set radius to match the highest height of the position - GLfloat zPos = radius * qCos(xAngle) * qCos(yAngle); - GLfloat xPos = radius * qSin(xAngle) * qCos(yAngle); - GLfloat yPos = (radiusFactor + relativePosition.y()) * qSin(yAngle); + // Set radius to match the highest height of the position + const float radius = (radiusFactor + relativePosition.y()); + const float zPos = radius * qCos(xAngle) * qCos(yAngle); + const float xPos = radius * qSin(xAngle) * qCos(yAngle); + const float yPos = radius * qSin(yAngle); // Keep in the set position in relation to camera return QVector3D(-xPos + relativePosition.x(), diff --git a/src/datavisualization/engine/q3dcamera.h b/src/datavisualization/engine/q3dcamera.h index e9ad6dd2..5b539ccf 100644 --- a/src/datavisualization/engine/q3dcamera.h +++ b/src/datavisualization/engine/q3dcamera.h @@ -35,6 +35,9 @@ class QT_DATAVISUALIZATION_EXPORT Q3DCamera : public Q3DObject Q_PROPERTY(CameraPreset cameraPreset READ cameraPreset WRITE setCameraPreset NOTIFY cameraPresetChanged) Q_PROPERTY(bool wrapXRotation READ wrapXRotation WRITE setWrapXRotation NOTIFY wrapXRotationChanged) Q_PROPERTY(bool wrapYRotation READ wrapYRotation WRITE setWrapYRotation NOTIFY wrapYRotationChanged) + Q_PROPERTY(QVector3D target READ target WRITE setTarget NOTIFY targetChanged REVISION 1) + Q_PROPERTY(float minZoomLevel READ minZoomLevel WRITE setMinZoomLevel NOTIFY minZoomLevelChanged REVISION 1) + Q_PROPERTY(float maxZoomLevel READ maxZoomLevel WRITE setMaxZoomLevel NOTIFY maxZoomLevelChanged REVISION 1) public: enum CameraPreset { @@ -86,9 +89,16 @@ public: float zoomLevel() const; void setZoomLevel(float zoomLevel); + float minZoomLevel() const; + void setMinZoomLevel(float zoomLevel); + float maxZoomLevel() const; + void setMaxZoomLevel(float zoomLevel); void setCameraPosition(float horizontal, float vertical, float zoom = 100.0f); + QVector3D target() const; + void setTarget(const QVector3D &target); + signals: void xRotationChanged(float rotation); void yRotationChanged(float rotation); @@ -96,6 +106,9 @@ signals: void cameraPresetChanged(Q3DCamera::CameraPreset preset); void wrapXRotationChanged(bool isEnabled); void wrapYRotationChanged(bool isEnabled); + Q_REVISION(1) void targetChanged(const QVector3D &target); + Q_REVISION(1) void minZoomLevelChanged(float zoomLevel); + Q_REVISION(1) void maxZoomLevelChanged(float zoomLevel); private: QScopedPointer<Q3DCameraPrivate> d_ptr; diff --git a/src/datavisualization/engine/q3dcamera_p.h b/src/datavisualization/engine/q3dcamera_p.h index 01e7a508..b7826a5b 100644 --- a/src/datavisualization/engine/q3dcamera_p.h +++ b/src/datavisualization/engine/q3dcamera_p.h @@ -84,22 +84,25 @@ signals: public: Q3DCamera *q_ptr; - QVector3D m_target; + QVector3D m_actualTarget; QVector3D m_up; QMatrix4x4 m_viewMatrix; bool m_isViewMatrixUpdateActive; - GLfloat m_xRotation; - GLfloat m_yRotation; - GLfloat m_minXRotation; - GLfloat m_minYRotation; - GLfloat m_maxXRotation; - GLfloat m_maxYRotation; - GLfloat m_zoomLevel; + float m_xRotation; + float m_yRotation; + float m_minXRotation; + float m_minYRotation; + float m_maxXRotation; + float m_maxYRotation; + float m_zoomLevel; + float m_minZoomLevel; + float m_maxZoomLevel; bool m_wrapXRotation; bool m_wrapYRotation; Q3DCamera::CameraPreset m_activePreset; + QVector3D m_requestedTarget; friend class Bars3DRenderer; friend class Surface3DRenderer; diff --git a/src/datavisualization/engine/q3dlight.cpp b/src/datavisualization/engine/q3dlight.cpp index 03f094cb..92b7bd07 100644 --- a/src/datavisualization/engine/q3dlight.cpp +++ b/src/datavisualization/engine/q3dlight.cpp @@ -68,13 +68,8 @@ Q3DLightPrivate::~Q3DLightPrivate() void Q3DLightPrivate::sync(Q3DLight &other) { - // Copies changed values from this light to the other light. If the other light had same changes, - // those changes are discarded. - if (q_ptr->isDirty()) { - other.copyValuesFrom(*q_ptr); - q_ptr->setDirty(false); - other.setDirty(false); - } + Q_UNUSED(other); + // Do nothing. Only value light has to sync is the position, which we handle internally. } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/q3dobject.cpp b/src/datavisualization/engine/q3dobject.cpp index 56edb098..6b442ef8 100644 --- a/src/datavisualization/engine/q3dobject.cpp +++ b/src/datavisualization/engine/q3dobject.cpp @@ -53,9 +53,7 @@ Q3DObject::~Q3DObject() */ void Q3DObject::copyValuesFrom(const Q3DObject &source) { - d_ptr->m_position.setX(source.d_ptr->m_position.x()); - d_ptr->m_position.setY(source.d_ptr->m_position.y()); - d_ptr->m_position.setZ(source.d_ptr->m_position.z()); + d_ptr->m_position = source.d_ptr->m_position; setDirty(true); } @@ -74,6 +72,9 @@ Q3DScene *Q3DObject::parentScene() * \property Q3DObject::position * * This property contains the 3D position of the object. + * + * \note Currently setting this property has no effect, as the positions of Q3DObjects in the + * scene are handled internally. */ QVector3D Q3DObject::position() const { @@ -97,7 +98,7 @@ void Q3DObject::setDirty(bool dirty) { d_ptr->m_isDirty = dirty; if (parentScene()) - parentScene()->d_ptr->m_sceneDirty = true; + parentScene()->d_ptr->markDirty(); } /*! diff --git a/src/datavisualization/engine/q3dscene.cpp b/src/datavisualization/engine/q3dscene.cpp index 9464bc8d..e4015c2b 100644 --- a/src/datavisualization/engine/q3dscene.cpp +++ b/src/datavisualization/engine/q3dscene.cpp @@ -96,9 +96,33 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION */ /*! + * \qmlproperty point Scene3D::graphPositionQuery + * + * This property contains the coordinates for the user input that should be processed + * by the scene as a graph position query. If this is set to value other than + * invalidSelectionPoint, the graph tries to match a graph position to the given \a point + * within the primary viewport. + * After the rendering pass this property is returned to its default state of + * invalidSelectionPoint. The queried graph position can be read from + * AbstractGraph3D::queriedGraphPosition property after the next render pass. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs allow graph position queries only at the graph floor level. + * + * \sa AbstractGraph3D::queriedGraphPosition + */ + +/*! * \qmlproperty bool Scene3D::slicingActive * - * This property contains whether 2D slicing view is currently active or not. + * This property contains whether 2D slicing view is currently active or not. If setting it, you + * must make sure AbstractGraph3D::selectionMode has either + * \l{QAbstract3DGraph::SelectionRow}{AbstractGraph3D.SelectionRow} or + * \l{QAbstract3DGraph::SelectionColumn}{AbstractGraph3D.SelectionColumn} flag set, and there is a + * valid selection. * \note Not all visualizations support the 2D slicing view. */ @@ -135,7 +159,6 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION * A constant property providing an invalid point for selection. */ - /*! * Constructs a basic scene with one light and one camera in it. An * optional \a parent parameter can be given and is then passed to QObject constructor. @@ -195,33 +218,35 @@ void Q3DScene::setPrimarySubViewport(const QRect &primarySubViewport) /*! * Returns whether the given \a point resides inside the primary subview or not. * \return \c true if the point is inside the primary subview. + * \note If subviews are superimposed, and the given \a point resides inside both, result is + * \c true only when the primary subview is on top. */ bool Q3DScene::isPointInPrimarySubView(const QPoint &point) { int x = point.x(); int y = point.y(); - int areaMinX = d_ptr->m_primarySubViewport.x(); - int areaMaxX = d_ptr->m_primarySubViewport.x() + d_ptr->m_primarySubViewport.width(); - int areaMinY = d_ptr->m_primarySubViewport.y(); - int areaMaxY = d_ptr->m_primarySubViewport.y() + d_ptr->m_primarySubViewport.height(); - - return ( x > areaMinX && x < areaMaxX && y > areaMinY && y < areaMaxY ); + bool isInSecondary = d_ptr->isInArea(d_ptr->m_secondarySubViewport, x, y); + if (!isInSecondary || (isInSecondary && !d_ptr->m_isSecondarySubviewOnTop)) + return d_ptr->isInArea(d_ptr->m_primarySubViewport, x, y); + else + return false; } /*! * Returns whether the given \a point resides inside the secondary subview or not. * \return \c true if the point is inside the secondary subview. + * \note If subviews are superimposed, and the given \a point resides inside both, result is + * \c true only when the secondary subview is on top. */ bool Q3DScene::isPointInSecondarySubView(const QPoint &point) { int x = point.x(); int y = point.y(); - int areaMinX = d_ptr->m_secondarySubViewport.x(); - int areaMaxX = d_ptr->m_secondarySubViewport.x() + d_ptr->m_secondarySubViewport.width(); - int areaMinY = d_ptr->m_secondarySubViewport.y(); - int areaMaxY = d_ptr->m_secondarySubViewport.y() + d_ptr->m_secondarySubViewport.height(); - - return ( x > areaMinX && x < areaMaxX && y > areaMinY && y < areaMaxY ); + bool isInPrimary = d_ptr->isInArea(d_ptr->m_primarySubViewport, x, y); + if (!isInPrimary || (isInPrimary && d_ptr->m_isSecondarySubviewOnTop)) + return d_ptr->isInArea(d_ptr->m_secondarySubViewport, x, y); + else + return false; } /*! @@ -254,7 +279,7 @@ void Q3DScene::setSecondarySubViewport(const QRect &secondarySubViewport) * \property Q3DScene::selectionQueryPosition * * This property contains the coordinates for the user input that should be processed - * by the scene as selection. If this is set to value other than invalidSelectionPoint() the + * by the scene as a selection. If this is set to value other than invalidSelectionPoint(), the * graph tries to select a data item, axis label, or a custom item at the given \a point within * the primary viewport. * After the rendering pass the property is returned to its default state of @@ -289,9 +314,47 @@ QPoint Q3DScene::invalidSelectionPoint() } /*! + * \property Q3DScene::graphPositionQuery + * + * This property contains the coordinates for the user input that should be processed + * by the scene as a graph position query. If this is set to value other than + * invalidSelectionPoint(), the graph tries to match a graph position to the given \a point + * within the primary viewport. + * After the rendering pass this property is returned to its default state of + * invalidSelectionPoint(). The queried graph position can be read from + * QAbstract3DGraph::queriedGraphPosition property after the next render pass. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs allow graph position queries only at the graph floor level. + * + * \sa QAbstract3DGraph::queriedGraphPosition + */ +void Q3DScene::setGraphPositionQuery(const QPoint &point) +{ + if (point != d_ptr->m_graphPositionQueryPosition) { + d_ptr->m_graphPositionQueryPosition = point; + d_ptr->m_changeTracker.graphPositionQueryPositionChanged = true; + d_ptr->m_sceneDirty = true; + + emit graphPositionQueryChanged(point); + emit d_ptr->needRender(); + } +} + +QPoint Q3DScene::graphPositionQuery() const +{ + return d_ptr->m_graphPositionQueryPosition; +} + +/*! * \property Q3DScene::slicingActive * - * This property contains whether 2D slicing view is currently active or not. + * This property contains whether 2D slicing view is currently active or not. If setting it, you + * must make sure QAbstract3DGraph::selectionMode has either QAbstract3DGraph::SelectionRow or + * QAbstract3DGraph::SelectionColumn flag set, and there is a valid selection. * \note Not all visualizations support the 2D slicing view. */ bool Q3DScene::isSlicingActive() const @@ -306,6 +369,10 @@ void Q3DScene::setSlicingActive(bool isSlicing) d_ptr->m_changeTracker.slicingActivatedChanged = true; d_ptr->m_sceneDirty = true; + // Set secondary subview behind primary to achieve default functionality (= clicking on + // primary disables slice) + setSecondarySubviewOnTop(!isSlicing); + d_ptr->calculateSubViewports(); emit slicingActiveChanged(isSlicing); emit d_ptr->needRender(); @@ -447,7 +514,9 @@ Q3DScenePrivate::Q3DScenePrivate(Q3DScene *q) : m_light(), m_isUnderSideCameraEnabled(false), m_isSlicingActive(false), - m_selectionQueryPosition(Q3DScene::invalidSelectionPoint()) + m_selectionQueryPosition(Q3DScene::invalidSelectionPoint()), + m_graphPositionQueryPosition(Q3DScene::invalidSelectionPoint()), + m_sceneDirty(true) { } @@ -491,6 +560,11 @@ void Q3DScenePrivate::sync(Q3DScenePrivate &other) m_changeTracker.selectionQueryPositionChanged = false; other.m_changeTracker.selectionQueryPositionChanged = false; } + if (m_changeTracker.graphPositionQueryPositionChanged) { + other.q_ptr->setGraphPositionQuery(q_ptr->graphPositionQuery()); + m_changeTracker.graphPositionQueryPositionChanged = false; + other.m_changeTracker.graphPositionQueryPositionChanged = false; + } if (m_changeTracker.cameraChanged) { m_camera->setDirty(true); m_changeTracker.cameraChanged = false; @@ -649,4 +723,19 @@ void Q3DScenePrivate::setLightPositionRelativeToCamera(const QVector3D &relative distanceModifier)); } +void Q3DScenePrivate::markDirty() +{ + m_sceneDirty = true; + emit needRender(); +} + +bool Q3DScenePrivate::isInArea(const QRect &area, int x, int y) const +{ + int areaMinX = area.x(); + int areaMaxX = area.x() + area.width(); + int areaMinY = area.y(); + int areaMaxY = area.y() + area.height(); + return ( x >= areaMinX && x <= areaMaxX && y >= areaMinY && y <= areaMaxY ); +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/q3dscene.h b/src/datavisualization/engine/q3dscene.h index 1699b125..a46b4d7b 100644 --- a/src/datavisualization/engine/q3dscene.h +++ b/src/datavisualization/engine/q3dscene.h @@ -41,6 +41,7 @@ class QT_DATAVISUALIZATION_EXPORT Q3DScene : public QObject Q_PROPERTY(Q3DCamera* activeCamera READ activeCamera WRITE setActiveCamera NOTIFY activeCameraChanged) Q_PROPERTY(Q3DLight* activeLight READ activeLight WRITE setActiveLight NOTIFY activeLightChanged) Q_PROPERTY(float devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) + Q_PROPERTY(QPoint graphPositionQuery READ graphPositionQuery WRITE setGraphPositionQuery NOTIFY graphPositionQueryChanged REVISION 1) public: Q3DScene(QObject *parent = 0); @@ -60,6 +61,9 @@ public: QPoint selectionQueryPosition() const; static QPoint invalidSelectionPoint(); + void setGraphPositionQuery(const QPoint &point); + QPoint graphPositionQuery() const; + void setSlicingActive(bool isSlicing); bool isSlicingActive() const; @@ -85,6 +89,7 @@ signals: void activeLightChanged(Q3DLight *light); void devicePixelRatioChanged(float pixelRatio); void selectionQueryPositionChanged(const QPoint &position); + Q_REVISION(1) void graphPositionQueryChanged(const QPoint &position); private: QScopedPointer<Q3DScenePrivate> d_ptr; diff --git a/src/datavisualization/engine/q3dscene_p.h b/src/datavisualization/engine/q3dscene_p.h index 2c69e5e0..d0f6e735 100644 --- a/src/datavisualization/engine/q3dscene_p.h +++ b/src/datavisualization/engine/q3dscene_p.h @@ -47,6 +47,7 @@ struct Q3DSceneChangeBitField { bool slicingActivatedChanged : 1; bool devicePixelRatioChanged : 1; bool selectionQueryPositionChanged : 1; + bool graphPositionQueryPositionChanged : 1; bool windowSizeChanged : 1; Q3DSceneChangeBitField() @@ -59,6 +60,7 @@ struct Q3DSceneChangeBitField { slicingActivatedChanged(true), devicePixelRatioChanged(true), selectionQueryPositionChanged(false), + graphPositionQueryPositionChanged(false), windowSizeChanged(true) { } @@ -89,6 +91,10 @@ public: float fixedRotation = 0.0f, float distanceModifier = 0.0f); + void markDirty(); + + bool isInArea(const QRect &area, int x, int y) const; + signals: void needRender(); @@ -106,6 +112,7 @@ public: bool m_isUnderSideCameraEnabled; bool m_isSlicingActive; QPoint m_selectionQueryPosition; + QPoint m_graphPositionQueryPosition; QSize m_windowSize; QRect m_glViewport; QRect m_glPrimarySubViewport; diff --git a/src/datavisualization/engine/q3dsurface.cpp b/src/datavisualization/engine/q3dsurface.cpp index c5ce29d7..ce3e7f4c 100644 --- a/src/datavisualization/engine/q3dsurface.cpp +++ b/src/datavisualization/engine/q3dsurface.cpp @@ -98,6 +98,8 @@ Q3DSurface::Q3DSurface(const QSurfaceFormat *format, QWindow *parent) dptr()->m_shared->initializeOpenGL(); QObject::connect(dptr()->m_shared, &Surface3DController::selectedSeriesChanged, this, &Q3DSurface::selectedSeriesChanged); + QObject::connect(dptr()->m_shared, &Surface3DController::flipHorizontalGridChanged, + this, &Q3DSurface::flipHorizontalGridChanged); } /*! @@ -230,6 +232,31 @@ QSurface3DSeries *Q3DSurface::selectedSeries() const } /*! + * \property Q3DSurface::flipHorizontalGrid + * \since QtDataVisualization 1.2 + * + * In some use cases the horizontal axis grid is mostly covered by the surface, so it can be more + * useful to display the horizontal axis grid on top of the graph rather than on the bottom. + * A typical use case for this is showing 2D spectrograms using orthoGraphic projection with + * a top-down viewpoint. + * + * If \c{false}, the horizontal axis grid and labels are drawn on the horizontal background + * of the graph. + * If \c{true}, the horizontal axis grid and labels are drawn on the opposite side of the graph + * from the horizontal background. + * Defaults to \c{false}. + */ +void Q3DSurface::setFlipHorizontalGrid(bool flip) +{ + dptr()->m_shared->setFlipHorizontalGrid(flip); +} + +bool Q3DSurface::flipHorizontalGrid() const +{ + return dptrc()->m_shared->flipHorizontalGrid(); +} + +/*! * Adds \a axis to the graph. The axes added via addAxis are not yet taken to use, * addAxis is simply used to give the ownership of the \a axis to the graph. * The \a axis must not be null or added to another graph. diff --git a/src/datavisualization/engine/q3dsurface.h b/src/datavisualization/engine/q3dsurface.h index 9868c844..86740519 100644 --- a/src/datavisualization/engine/q3dsurface.h +++ b/src/datavisualization/engine/q3dsurface.h @@ -34,6 +34,7 @@ class QT_DATAVISUALIZATION_EXPORT Q3DSurface : public QAbstract3DGraph Q_PROPERTY(QValue3DAxis *axisY READ axisY WRITE setAxisY NOTIFY axisYChanged) Q_PROPERTY(QValue3DAxis *axisZ READ axisZ WRITE setAxisZ NOTIFY axisZChanged) Q_PROPERTY(QSurface3DSeries *selectedSeries READ selectedSeries NOTIFY selectedSeriesChanged) + Q_PROPERTY(bool flipHorizontalGrid READ flipHorizontalGrid WRITE setFlipHorizontalGrid NOTIFY flipHorizontalGridChanged) public: explicit Q3DSurface(const QSurfaceFormat *format = 0, QWindow *parent = 0); @@ -55,12 +56,15 @@ public: QList<QValue3DAxis *> axes() const; QSurface3DSeries *selectedSeries() const; + void setFlipHorizontalGrid(bool flip); + bool flipHorizontalGrid() const; signals: void axisXChanged(QValue3DAxis *axis); void axisYChanged(QValue3DAxis *axis); void axisZChanged(QValue3DAxis *axis); void selectedSeriesChanged(QSurface3DSeries *series); + void flipHorizontalGridChanged(bool flip); private: Q3DSurfacePrivate *dptr(); diff --git a/src/datavisualization/engine/qabstract3dgraph.cpp b/src/datavisualization/engine/qabstract3dgraph.cpp index b8fa92e8..e51d9ce4 100644 --- a/src/datavisualization/engine/qabstract3dgraph.cpp +++ b/src/datavisualization/engine/qabstract3dgraph.cpp @@ -22,6 +22,7 @@ #include "qabstract3dinputhandler_p.h" #include "q3dscene_p.h" #include "qutils.h" +#include "utils_p.h" #include <QtGui/QGuiApplication> #include <QtGui/QOpenGLContext> @@ -29,6 +30,9 @@ #include <QtGui/QPainter> #include <QtGui/QOpenGLFramebufferObject> #include <QtGui/QOffscreenSurface> +#if defined(Q_OS_OSX) +#include <qpa/qplatformnativeinterface.h> +#endif QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -148,7 +152,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION \value OptimizationDefault Provides the full feature set at a reasonable performance. \value OptimizationStatic - Beta level feature. Optimizes the rendering of static data sets at the expense of some features. + Optimizes the rendering of static data sets at the expense of some features. */ /*! @@ -169,11 +173,7 @@ QAbstract3DGraph::QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFor if (format) { surfaceFormat = *format; // Make sure renderable type is correct -#if !defined(QT_OPENGL_ES_2) - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGL); -#else - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGLES); -#endif + surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType); } else { surfaceFormat = qDefaultSurfaceFormat(); } @@ -197,17 +197,24 @@ QAbstract3DGraph::QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFor qDebug() << "GLSL version:" << (const char *)shaderVersion; #endif -#if !defined(QT_OPENGL_ES_2) - // If we have real OpenGL, GLSL version must be 1.2 or over. Quit if not. - QStringList splitversionstr = - QString::fromLatin1((const char *)shaderVersion).split(QChar::fromLatin1(' ')); - if (splitversionstr[0].toFloat() < 1.2) - qFatal("GLSL version must be 1.20 or higher. Try installing latest display drivers."); -#else - Q_UNUSED(shaderVersion) -#endif + if (!Utils::isOpenGLES()) { + // If we have real OpenGL, GLSL version must be 1.2 or over. Quit if not. + QStringList splitversionstr = + QString::fromLatin1((const char *)shaderVersion).split(QChar::fromLatin1(' ')); + if (splitversionstr[0].toFloat() < 1.2) + qFatal("GLSL version must be 1.20 or higher. Try installing latest display drivers."); + } d_ptr->renderLater(); + +#if defined(Q_OS_OSX) + // Enable touch events for Mac touchpads + typedef void * (*EnableTouch)(QWindow*, bool); + EnableTouch enableTouch = + (EnableTouch)QGuiApplication::platformNativeInterface()->nativeResourceFunctionForIntegration("registertouchwindow"); + if (enableTouch) + enableTouch(this, true); +#endif } /*! @@ -400,7 +407,7 @@ void QAbstract3DGraph::clearSelection() * \return index to the added item if add was successful, -1 if trying to add a null item, and * index of the item if trying to add an already added item. * - * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt() + * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt(), customItems() * * \since QtDataVisualization 1.1 */ @@ -455,6 +462,16 @@ void QAbstract3DGraph::releaseCustomItem(QCustom3DItem *item) } /*! + * \return list of all added custom items. + * \since QtDataVisualization 1.2 + * \sa addCustomItem() + */ +QList<QCustom3DItem *> QAbstract3DGraph::customItems() const +{ + return d_ptr->m_visualController->customItems(); +} + +/*! * Can be used to query the index of the selected label after receiving \c selectedElementChanged * signal with any label type. Selection is valid until the next \c selectedElementChanged signal. * @@ -545,6 +562,8 @@ QAbstract3DGraph::ElementType QAbstract3DGraph::selectedElement() const * \since QtDataVisualization 1.1 * * \return rendered image. + * + * \note OpenGL ES2 does not support anitialiasing, so \a msaaSamples is always forced to \c{0}. */ QImage QAbstract3DGraph::renderToImage(int msaaSamples, const QSize &imageSize) { @@ -591,8 +610,15 @@ qreal QAbstract3DGraph::currentFps() const * \property QAbstract3DGraph::orthoProjection * \since QtDataVisualization 1.1 * - * If \c {true}, orthographic projection will be used for displaying the graph. Defaults to \c{false}. + * If \c {true}, orthographic projection will be used for displaying the graph. + * \note Orthographic projection can be used to create 2D graphs by replacing the default input + * handler with one that doesn't allow rotating the graph and setting the camera to view the graph + * directly from the side or from the top. Also, axis labels typically need to be rotated when + * viewing the graph from the sides. + * Defaults to \c{false}. * \note Shadows will be disabled when set to \c{true}. + * + * \sa QAbstract3DAxis::labelAutoRotation, Q3DCamera::cameraPreset */ void QAbstract3DGraph::setOrthoProjection(bool enable) { @@ -608,14 +634,16 @@ bool QAbstract3DGraph::isOrthoProjection() const * \property QAbstract3DGraph::aspectRatio * \since QtDataVisualization 1.1 * - * Aspect ratio of the graph data. This is the ratio of data scaling between horizontal and - * vertical axes. Defaults to \c{2.0}. + * The aspect ratio is the ratio of the graph scaling between the longest axis on the horizontal + * plane and the Y-axis. Defaults to \c{2.0}. * * \note Has no effect on Q3DBars. + * + * \sa horizontalAspectRatio */ void QAbstract3DGraph::setAspectRatio(qreal ratio) { - d_ptr->m_visualController->setAspectRatio(float(ratio)); + d_ptr->m_visualController->setAspectRatio(ratio); } qreal QAbstract3DGraph::aspectRatio() const @@ -626,14 +654,20 @@ qreal QAbstract3DGraph::aspectRatio() const /*! * \property QAbstract3DGraph::optimizationHints * - * Defines if the rendering optimization is default or static. Default mode provides the full feature set at - * reasonable performance. Static is a beta level feature and currently supports only a subset of the - * features on the Scatter graph. Missing features are object gradient for mesh objects, both gradients - * for points, and diffuse and specular color on rotations. At this point static is intended just for - * introducing a new feature. It optimizes graph rendering and is ideal for large non-changing data - * sets. It is slower with dynamic data changes and item rotations. Selection is not optimized, so using it - * with massive data sets is not advisable. + * Defines if the rendering optimization is default or static. Default mode provides the full + * feature set at reasonable performance. Static mode optimizes graph rendering and is ideal for + * large non-changing data sets. It is slower with dynamic data changes and item rotations. + * Selection is not optimized, so using it with massive data sets is not advisable. + * Static works only on the Scatter graph. * Defaults to \c{OptimizationDefault}. + * + * \note On some environments, large graphs using static optimization may not render, because + * all of the items are rendered using a single draw call, and different graphics drivers have + * different maximum vertice counts per call that they support. + * This is mostly an issue on 32bit and/or OpenGL ES2 platforms. + * To work around this issue, choose an item mesh with low vertex count or use the point mesh. + * + * \sa QAbstract3DSeries::mesh */ void QAbstract3DGraph::setOptimizationHints(OptimizationHints hints) { @@ -646,6 +680,195 @@ QAbstract3DGraph::OptimizationHints QAbstract3DGraph::optimizationHints() const } /*! + * \property QAbstract3DGraph::polar + * \since QtDataVisualization 1.2 + * + * If \c {true}, the horizontal axes are changed into polar axes. The X axis becomes the + * angular axis and the Z axis becomes the radial axis. + * Polar mode is not available for bar graphs. + * + * Defaults to \c{false}. + * + * \sa orthoProjection, radialLabelOffset + */ +void QAbstract3DGraph::setPolar(bool enable) +{ + d_ptr->m_visualController->setPolar(enable); +} + +bool QAbstract3DGraph::isPolar() const +{ + return d_ptr->m_visualController->isPolar(); +} + +/*! + * \property QAbstract3DGraph::radialLabelOffset + * \since QtDataVisualization 1.2 + * + * This property specifies the normalized horizontal offset for the axis labels of the radial + * polar axis. The value 0.0 indicates the labels should be drawn next to the 0-angle angular + * axis grid line. The value 1.0 indicates the labels are drawn on their normal place at the edge + * of the graph background. + * This property is ignored if polar property value is \c{false}. Defaults to 1.0. + * + * \sa polar + */ +void QAbstract3DGraph::setRadialLabelOffset(float offset) +{ + d_ptr->m_visualController->setRadialLabelOffset(offset); +} + +float QAbstract3DGraph::radialLabelOffset() const +{ + return d_ptr->m_visualController->radialLabelOffset(); +} + +/*! + * \property QAbstract3DGraph::horizontalAspectRatio + * \since QtDataVisualization 1.2 + * + * The horizontal aspect ratio is the ratio of the graph scaling between the X and Z axes. + * Value of 0.0 indicates automatic scaling according to axis ranges. + * Defaults to \c{0.0}. + * + * \note Has no effect on Q3DBars, which handles scaling on the horizontal plane via + * \l{Q3DBars::barThickness}{barThickness} and \l{Q3DBars::barSpacing}{barSpacing} properties. + * Polar graphs also ignore this property. + * + * \sa aspectRatio, polar, Q3DBars::barThickness, Q3DBars::barSpacing + */ +void QAbstract3DGraph::setHorizontalAspectRatio(qreal ratio) +{ + d_ptr->m_visualController->setHorizontalAspectRatio(ratio); +} + +qreal QAbstract3DGraph::horizontalAspectRatio() const +{ + return d_ptr->m_visualController->horizontalAspectRatio(); +} + +/*! + * \property QAbstract3DGraph::reflection + * \since QtDataVisualization 1.2 + * + * Sets floor reflections on or off. Defaults to \c{false}. + * + * \note Affects only Q3DBars. + * + * \note In Q3DBars graphs holding both positive and negative values, reflections are not supported + * for custom items that intersect the floor plane. In that case, reflections should be turned off + * to avoid incorrect rendering. + * + * \note If using custom surface format, stencil buffer needs to be defined + * (QSurfaceFormat::setStencilBufferSize()) for reflections to work. + * + * \sa reflectivity + */ +void QAbstract3DGraph::setReflection(bool enable) +{ + d_ptr->m_visualController->setReflection(enable); +} + +bool QAbstract3DGraph::isReflection() const +{ + return d_ptr->m_visualController->reflection(); +} + +/*! + * \property QAbstract3DGraph::reflectivity + * \since QtDataVisualization 1.2 + * + * Adjusts floor reflectivity, larger number being more reflective. Valid range is \c{[0...1]}. + * Defaults to \c{0.5}. + * + * \note Affects only Q3DBars. + * + * \sa reflection + */ +void QAbstract3DGraph::setReflectivity(qreal reflectivity) +{ + d_ptr->m_visualController->setReflectivity(reflectivity); +} + +qreal QAbstract3DGraph::reflectivity() const +{ + return d_ptr->m_visualController->reflectivity(); +} + +/*! + * \property QAbstract3DGraph::locale + * \since QtDataVisualization 1.2 + * + * Sets the locale used for formatting various numeric labels. + * Defaults to \c{"C"} locale. + * + * \sa QValue3DAxis::labelFormat + */ +void QAbstract3DGraph::setLocale(const QLocale &locale) +{ + d_ptr->m_visualController->setLocale(locale); +} + +QLocale QAbstract3DGraph::locale() const +{ + return d_ptr->m_visualController->locale(); +} + +/*! + * \property QAbstract3DGraph::queriedGraphPosition + * \since QtDataVisualization 1.2 + * + * This read-only property contains the latest graph position values along each axis queried using + * Q3DScene::graphPositionQuery. The values are normalized to range \c{[-1, 1]}. + * If the queried position was outside the graph bounds, the values + * will not reflect the real position, but will instead be some undefined position outside + * the range \c{[-1, 1]}. The value will be undefined before any queries are made. + * + * There isn't a single correct 3D coordinate to match to each specific screen position, so to be + * consistent, the queries are always done against the inner sides of an invisible box surrounding + * the graph. + * + * \note Bar graphs only allow querying graph position at the graph floor level, + * so the Y-value is always zero for bar graphs and the valid queries can be only made at + * screen positions that contain the floor of the graph. + * + * \sa Q3DScene::graphPositionQuery + */ +QVector3D QAbstract3DGraph::queriedGraphPosition() const +{ + return d_ptr->m_visualController->queriedGraphPosition(); +} + +/*! + * \property QAbstract3DGraph::margin + * \since QtDataVisualization 1.2 + * + * This property contains the absolute value used for graph margin. The graph margin is the space + * left between the edge of the plottable graph area and the edge of the graph background. + * If the margin value is negative, the margins are determined automatically and can vary according + * to size of the items in the series and the type of the graph. + * The value is interpreted as a fraction of Y-axis range, provided the graph aspect ratios have + * not beed changed from the defaults. + * Defaults to \c{-1.0}. + * + * \note Having smaller than the automatically determined margin on scatter graph can cause + * the scatter items at the edges of the graph to overlap with the graph background. + * + * \note On scatter and surface graphs, if the margin is comparatively small to the axis label + * size, the positions of the edge labels of the axes are adjusted to avoid overlap with + * the edge labels of the neighboring axes. + */ +void QAbstract3DGraph::setMargin(qreal margin) +{ + d_ptr->m_visualController->setMargin(margin); +} + +qreal QAbstract3DGraph::margin() const +{ + return d_ptr->m_visualController->margin(); +} + +/*! * \internal */ bool QAbstract3DGraph::event(QEvent *event) @@ -795,6 +1018,23 @@ void QAbstract3DGraphPrivate::setVisualController(Abstract3DController *controll QObject::connect(m_visualController, &Abstract3DController::aspectRatioChanged, q_ptr, &QAbstract3DGraph::aspectRatioChanged); + QObject::connect(m_visualController, &Abstract3DController::polarChanged, q_ptr, + &QAbstract3DGraph::polarChanged); + QObject::connect(m_visualController, &Abstract3DController::radialLabelOffsetChanged, q_ptr, + &QAbstract3DGraph::radialLabelOffsetChanged); + QObject::connect(m_visualController, &Abstract3DController::horizontalAspectRatioChanged, q_ptr, + &QAbstract3DGraph::horizontalAspectRatioChanged); + + QObject::connect(m_visualController, &Abstract3DController::reflectionChanged, q_ptr, + &QAbstract3DGraph::reflectionChanged); + QObject::connect(m_visualController, &Abstract3DController::reflectivityChanged, q_ptr, + &QAbstract3DGraph::reflectivityChanged); + QObject::connect(m_visualController, &Abstract3DController::localeChanged, q_ptr, + &QAbstract3DGraph::localeChanged); + QObject::connect(m_visualController, &Abstract3DController::queriedGraphPositionChanged, q_ptr, + &QAbstract3DGraph::queriedGraphPositionChanged); + QObject::connect(m_visualController, &Abstract3DController::marginChanged, q_ptr, + &QAbstract3DGraph::marginChanged); } void QAbstract3DGraphPrivate::handleDevicePixelRatioChange() @@ -849,8 +1089,10 @@ QImage QAbstract3DGraphPrivate::renderToImage(int msaaSamples, const QSize &imag // Render the wanted frame offscreen m_context->makeCurrent(m_offscreenSurface); fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); - fboFormat.setInternalTextureFormat(GL_RGB); - fboFormat.setSamples(msaaSamples); + if (!Utils::isOpenGLES()) { + fboFormat.setInternalTextureFormat(GL_RGB); + fboFormat.setSamples(msaaSamples); + } fbo = new QOpenGLFramebufferObject(imageSize, fboFormat); if (fbo->isValid()) { QRect originalViewport = m_visualController->m_scene->viewport(); diff --git a/src/datavisualization/engine/qabstract3dgraph.h b/src/datavisualization/engine/qabstract3dgraph.h index 59f61aae..a0c26a42 100644 --- a/src/datavisualization/engine/qabstract3dgraph.h +++ b/src/datavisualization/engine/qabstract3dgraph.h @@ -25,6 +25,7 @@ #include <QtDataVisualization/qabstract3dinputhandler.h> #include <QtGui/QWindow> #include <QtGui/QOpenGLFunctions> +#include <QtCore/QLocale> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -50,6 +51,14 @@ class QT_DATAVISUALIZATION_EXPORT QAbstract3DGraph : public QWindow, protected Q Q_PROPERTY(ElementType selectedElement READ selectedElement NOTIFY selectedElementChanged) Q_PROPERTY(qreal aspectRatio READ aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged) Q_PROPERTY(OptimizationHints optimizationHints READ optimizationHints WRITE setOptimizationHints NOTIFY optimizationHintsChanged) + Q_PROPERTY(bool polar READ isPolar WRITE setPolar NOTIFY polarChanged) + Q_PROPERTY(float radialLabelOffset READ radialLabelOffset WRITE setRadialLabelOffset NOTIFY radialLabelOffsetChanged) + Q_PROPERTY(qreal horizontalAspectRatio READ horizontalAspectRatio WRITE setHorizontalAspectRatio NOTIFY horizontalAspectRatioChanged) + Q_PROPERTY(bool reflection READ isReflection WRITE setReflection NOTIFY reflectionChanged) + Q_PROPERTY(qreal reflectivity READ reflectivity WRITE setReflectivity NOTIFY reflectivityChanged) + Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged) + Q_PROPERTY(QVector3D queriedGraphPosition READ queriedGraphPosition NOTIFY queriedGraphPositionChanged) + Q_PROPERTY(qreal margin READ margin WRITE setMargin NOTIFY marginChanged) protected: explicit QAbstract3DGraph(QAbstract3DGraphPrivate *d, const QSurfaceFormat *format, @@ -126,6 +135,7 @@ public: void removeCustomItem(QCustom3DItem *item); void removeCustomItemAt(const QVector3D &position); void releaseCustomItem(QCustom3DItem *item); + QList<QCustom3DItem *> customItems() const; int selectedLabelIndex() const; QAbstract3DAxis *selectedAxis() const; @@ -150,6 +160,29 @@ public: void setOptimizationHints(OptimizationHints hints); OptimizationHints optimizationHints() const; + void setPolar(bool enable); + bool isPolar() const; + + void setRadialLabelOffset(float offset); + float radialLabelOffset() const; + + void setHorizontalAspectRatio(qreal ratio); + qreal horizontalAspectRatio() const; + + void setReflection(bool enable); + bool isReflection() const; + + void setReflectivity(qreal reflectivity); + qreal reflectivity() const; + + void setLocale(const QLocale &locale); + QLocale locale() const; + + QVector3D queriedGraphPosition() const; + + void setMargin(qreal margin); + qreal margin() const; + protected: bool event(QEvent *event); void resizeEvent(QResizeEvent *event); @@ -173,6 +206,14 @@ signals: void orthoProjectionChanged(bool enabled); void aspectRatioChanged(qreal ratio); void optimizationHintsChanged(QAbstract3DGraph::OptimizationHints hints); + void polarChanged(bool enabled); + void radialLabelOffsetChanged(float offset); + void horizontalAspectRatioChanged(qreal ratio); + void reflectionChanged(bool enabled); + void reflectivityChanged(qreal reflectivity); + void localeChanged(const QLocale &locale); + void queriedGraphPositionChanged(const QVector3D &data); + void marginChanged(qreal margin); private: Q_DISABLE_COPY(QAbstract3DGraph) diff --git a/src/datavisualization/engine/scatter3drenderer.cpp b/src/datavisualization/engine/scatter3drenderer.cpp index dd50188b..f6367153 100644 --- a/src/datavisualization/engine/scatter3drenderer.cpp +++ b/src/datavisualization/engine/scatter3drenderer.cpp @@ -33,12 +33,9 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -//#define USE_UNIFORM_SCALING // Scale x and z uniformly, or based on autoscaled values - const GLfloat defaultMinSize = 0.01f; const GLfloat defaultMaxSize = 0.1f; const GLfloat itemScaler = 3.0f; -const GLfloat gridLineWidth = 0.005f; Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller) : Abstract3DRenderer(controller), @@ -46,30 +43,27 @@ Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller) m_updateLabels(false), m_dotShader(0), m_dotGradientShader(0), - #if defined(QT_OPENGL_ES_2) + m_staticSelectedItemGradientShader(0), + m_staticSelectedItemShader(0), m_pointShader(0), - #endif m_depthShader(0), m_selectionShader(0), m_backgroundShader(0), - m_labelShader(0), + m_staticGradientPointShader(0), m_bgrTexture(0), - m_depthTexture(0), m_selectionTexture(0), m_depthFrameBuffer(0), m_selectionFrameBuffer(0), m_selectionDepthBuffer(0), m_shadowQualityToShader(100.0f), m_shadowQualityMultiplier(3), - m_heightNormalizer(1.0f), - m_scaleFactor(0), + m_scaleX(0.0f), + m_scaleY(0.0f), + m_scaleZ(0.0f), m_selectedItemIndex(Scatter3DController::invalidSelectionIndex()), m_selectedSeriesCache(0), m_oldSelectedSeriesCache(0), - m_areaSize(QSizeF(0.0, 0.0)), m_dotSizeScale(1.0f), - m_hasHeightAdjustmentChanged(true), - m_backgroundMargin(defaultMaxSize), m_maxItemSize(0.0f), m_clickedIndex(Scatter3DController::invalidSelectionIndex()), m_havePointSeries(false), @@ -77,15 +71,13 @@ Scatter3DRenderer::Scatter3DRenderer(Scatter3DController *controller) m_haveUniformColorMeshSeries(false), m_haveGradientMeshSeries(false) { - m_axisCacheY.setScale(2.0f); - m_axisCacheY.setTranslate(-1.0f); - - initializeOpenGLFunctions(); initializeOpenGL(); } Scatter3DRenderer::~Scatter3DRenderer() { + fixContextBeforeDelete(); + if (QOpenGLContext::currentContext()) { m_textureHelper->glDeleteFramebuffers(1, &m_selectionFrameBuffer); m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer); @@ -94,11 +86,13 @@ Scatter3DRenderer::~Scatter3DRenderer() m_textureHelper->deleteTexture(&m_bgrTexture); } delete m_dotShader; + delete m_staticSelectedItemGradientShader; + delete m_staticSelectedItemShader; delete m_dotGradientShader; delete m_depthShader; delete m_selectionShader; delete m_backgroundShader; - delete m_labelShader; + delete m_staticGradientPointShader; } void Scatter3DRenderer::initializeOpenGL() @@ -106,28 +100,17 @@ void Scatter3DRenderer::initializeOpenGL() Abstract3DRenderer::initializeOpenGL(); // Initialize shaders - initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), - QStringLiteral(":/shaders/fragmentLabel")); -#if !defined(QT_OPENGL_ES_2) - // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api. - initDepthShader(); -#else - // Init point shader - initPointShader(); -#endif + if (!m_isOpenGLES) { + initDepthShader(); // For shadows + loadGridLineMesh(); + } else { + initPointShader(); + } // Init selection shader initSelectionShader(); -#if !defined(QT_OPENGL_ES_2) - // Load grid line mesh - loadGridLineMesh(); -#endif - - // Load label mesh - loadLabelMesh(); - // Set view port glViewport(m_primarySubViewport.x(), m_primarySubViewport.y(), @@ -138,6 +121,53 @@ void Scatter3DRenderer::initializeOpenGL() loadBackgroundMesh(); } +void Scatter3DRenderer::fixCameraTarget(QVector3D &target) +{ + target.setX(target.x() * m_scaleX); + target.setY(target.y() * m_scaleY); + target.setZ(target.z() * -m_scaleZ); +} + +void Scatter3DRenderer::getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds) +{ + // The inputs are the item bounds in OpenGL coordinates. + // The outputs limit these bounds to visible ranges, normalized to range [-1, 1] + // Volume shader flips the Y and Z axes, so we need to set negatives of actual values to those + float itemRangeX = (maxBounds.x() - minBounds.x()); + float itemRangeY = (maxBounds.y() - minBounds.y()); + float itemRangeZ = (maxBounds.z() - minBounds.z()); + + if (minBounds.x() < -m_scaleX) + minBounds.setX(-1.0f + (2.0f * qAbs(minBounds.x() + m_scaleX) / itemRangeX)); + else + minBounds.setX(-1.0f); + + if (minBounds.y() < -m_scaleY) + minBounds.setY(-(-1.0f + (2.0f * qAbs(minBounds.y() + m_scaleY) / itemRangeY))); + else + minBounds.setY(1.0f); + + if (minBounds.z() < -m_scaleZ) + minBounds.setZ(-(-1.0f + (2.0f * qAbs(minBounds.z() + m_scaleZ) / itemRangeZ))); + else + minBounds.setZ(1.0f); + + if (maxBounds.x() > m_scaleX) + maxBounds.setX(1.0f - (2.0f * qAbs(maxBounds.x() - m_scaleX) / itemRangeX)); + else + maxBounds.setX(1.0f); + + if (maxBounds.y() > m_scaleY) + maxBounds.setY(-(1.0f - (2.0f * qAbs(maxBounds.y() - m_scaleY) / itemRangeY))); + else + maxBounds.setY(-1.0f); + + if (maxBounds.z() > m_scaleZ) + maxBounds.setZ(-(1.0f - (2.0f * qAbs(maxBounds.z() - m_scaleZ) / itemRangeZ))); + else + maxBounds.setZ(-1.0f); +} + void Scatter3DRenderer::updateData() { calculateSceneScalingFactors(); @@ -145,24 +175,31 @@ void Scatter3DRenderer::updateData() foreach (SeriesRenderCache *baseCache, m_renderCacheList) { ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache); - if (cache->isVisible() && cache->dataDirty()) { + if (cache->isVisible()) { const QScatter3DSeries *currentSeries = cache->series(); ScatterRenderItemArray &renderArray = cache->renderArray(); QScatterDataProxy *dataProxy = currentSeries->dataProxy(); const QScatterDataArray &dataArray = *dataProxy->array(); int dataSize = dataArray.size(); totalDataSize += dataSize; - if (dataSize != renderArray.size()) - renderArray.resize(dataSize); + if (cache->dataDirty()) { + if (dataSize != renderArray.size()) + renderArray.resize(dataSize); + + for (int i = 0; i < dataSize; i++) + updateRenderItem(dataArray.at(i), renderArray[i]); - for (int i = 0; i < dataSize; i++) - updateRenderItem(dataArray.at(i), renderArray[i]); - cache->setDataDirty(false); + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)) + cache->setStaticBufferDirty(true); + + cache->setDataDirty(false); + } } } if (totalDataSize) { - m_dotSizeScale = GLfloat(qBound(defaultMinSize, 2.0f / float(qSqrt(qreal(totalDataSize))), + m_dotSizeScale = GLfloat(qBound(defaultMinSize, + 2.0f / float(qSqrt(qreal(totalDataSize))), defaultMaxSize)); } @@ -179,6 +216,7 @@ void Scatter3DRenderer::updateData() points = new ScatterPointBufferHelper(); cache->setBufferPoints(points); } + points->setScaleY(m_scaleY); points->load(cache); } else { ScatterObjectBufferHelper *object = cache->bufferObject(); @@ -187,7 +225,9 @@ void Scatter3DRenderer::updateData() cache->setBufferObject(object); } if (renderArraySize != cache->oldArraySize() - || cache->object()->objectFile() != cache->oldMeshFileName()) { + || cache->object()->objectFile() != cache->oldMeshFileName() + || cache->staticBufferDirty()) { + object->setScaleY(m_scaleY); object->fullLoad(cache, m_dotSizeScale); cache->setOldArraySize(renderArraySize); cache->setOldMeshFileName(cache->object()->objectFile()); @@ -195,6 +235,8 @@ void Scatter3DRenderer::updateData() object->update(cache, m_dotSizeScale); } } + + cache->setStaticBufferDirty(false); } } } @@ -205,9 +247,28 @@ void Scatter3DRenderer::updateData() void Scatter3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesList) { + int seriesCount = seriesList.size(); + + // Check OptimizationStatic specific issues before populate marks changeTracker done + if (m_cachedOptimizationHint.testFlag(QAbstract3DGraph::OptimizationStatic)) { + for (int i = 0; i < seriesCount; i++) { + QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(seriesList[i]); + if (scatterSeries->isVisible()) { + QAbstract3DSeriesChangeBitField &changeTracker = scatterSeries->d_ptr->m_changeTracker; + ScatterSeriesRenderCache *cache = + static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(scatterSeries)); + if (cache) { + if (changeTracker.baseGradientChanged || changeTracker.colorStyleChanged) + cache->setStaticObjectUVDirty(true); + if (cache->itemSize() != scatterSeries->itemSize()) + cache->setStaticBufferDirty(true); + } + } + } + } + Abstract3DRenderer::updateSeries(seriesList); - int seriesCount = seriesList.size(); float maxItemSize = 0.0f; float itemSize = 0.0f; bool noSelection = true; @@ -243,13 +304,28 @@ void Scatter3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesLis else m_haveGradientMeshSeries = true; } + + if (cache->staticBufferDirty()) { + if (cache->mesh() != QAbstract3DSeries::MeshPoint) { + ScatterObjectBufferHelper *object = cache->bufferObject(); + object->update(cache, m_dotSizeScale); + } + cache->setStaticBufferDirty(false); + } + if (cache->staticObjectUVDirty()) { + if (cache->mesh() == QAbstract3DSeries::MeshPoint) { + ScatterPointBufferHelper *object = cache->bufferPoints(); + object->updateUVs(cache); + } else { + ScatterObjectBufferHelper *object = cache->bufferObject(); + object->updateUVs(cache); + } + cache->setStaticObjectUVDirty(false); + } } } m_maxItemSize = maxItemSize; - if (maxItemSize > defaultMaxSize) - m_backgroundMargin = maxItemSize / itemScaler; - else - m_backgroundMargin = defaultMaxSize; + calculateSceneScalingFactors(); if (noSelection) { if (!selectionLabel().isEmpty()) @@ -268,6 +344,8 @@ void Scatter3DRenderer::updateItems(const QVector<Scatter3DController::ChangeIte ScatterSeriesRenderCache *cache = 0; const QScatter3DSeries *prevSeries = 0; const QScatterDataArray *dataArray = 0; + const bool optimizationStatic = m_cachedOptimizationHint.testFlag( + QAbstract3DGraph::OptimizationStatic); foreach (Scatter3DController::ChangeItem item, items) { QScatter3DSeries *currentSeries = item.series; @@ -282,7 +360,43 @@ void Scatter3DRenderer::updateItems(const QVector<Scatter3DController::ChangeIte } if (cache->isVisible()) { const int index = item.index; - updateRenderItem(dataArray->at(index), cache->renderArray()[index]); + if (index >= cache->renderArray().size()) + continue; // Items removed from array for same render + bool oldVisibility; + ScatterRenderItem &item = cache->renderArray()[index]; + if (optimizationStatic) + oldVisibility = item.isVisible(); + updateRenderItem(dataArray->at(index), item); + if (optimizationStatic) { + if (!cache->visibilityChanged() && oldVisibility != item.isVisible()) + cache->setVisibilityChanged(true); + cache->updateIndices().append(index); + } + } + } + if (optimizationStatic) { + foreach (SeriesRenderCache *baseCache, m_renderCacheList) { + ScatterSeriesRenderCache *cache = static_cast<ScatterSeriesRenderCache *>(baseCache); + if (cache->isVisible() && cache->updateIndices().size()) { + if (cache->mesh() == QAbstract3DSeries::MeshPoint) { + cache->bufferPoints()->update(cache); + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + cache->bufferPoints()->updateUVs(cache); + } else { + if (cache->visibilityChanged()) { + // If any change changes item visibility, full load is needed to + // resize the buffers. + cache->updateIndices().clear(); + cache->bufferObject()->fullLoad(cache, m_dotSizeScale); + } else { + cache->bufferObject()->update(cache, m_dotSizeScale); + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + cache->bufferObject()->updateUVs(cache); + } + } + cache->updateIndices().clear(); + } + cache->setVisibilityChanged(false); } } } @@ -291,14 +405,46 @@ void Scatter3DRenderer::updateScene(Q3DScene *scene) { scene->activeCamera()->d_ptr->setMinYRotation(-90.0f); - if (m_hasHeightAdjustmentChanged) { - // Set initial camera position. Also update if height adjustment has changed. - scene->activeCamera()->d_ptr->setBaseOrientation(cameraDistanceVector, zeroVector, - upVector); - m_hasHeightAdjustmentChanged = false; + Abstract3DRenderer::updateScene(scene); +} + +void Scatter3DRenderer::updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels) +{ + Abstract3DRenderer::updateAxisLabels(orientation, labels); + + // Angular axis label dimensions affect the chart dimensions + if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX) + calculateSceneScalingFactors(); +} + +void Scatter3DRenderer::updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, + bool visible) +{ + Abstract3DRenderer::updateAxisTitleVisibility(orientation, visible); + + // Angular axis title existence affects the chart dimensions + if (m_polarGraph && orientation == QAbstract3DAxis::AxisOrientationX) + calculateSceneScalingFactors(); +} + +void Scatter3DRenderer::updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint) +{ + Abstract3DRenderer::updateOptimizationHint(hint); + + Abstract3DRenderer::reInitShaders(); + + if (m_isOpenGLES && hint.testFlag(QAbstract3DGraph::OptimizationStatic) + && !m_staticGradientPointShader) { + initStaticPointShaders(QStringLiteral(":/shaders/vertexPointES2_UV"), + QStringLiteral(":/shaders/fragmentLabel")); } +} - Abstract3DRenderer::updateScene(scene); +void Scatter3DRenderer::updateMargin(float margin) +{ + Abstract3DRenderer::updateMargin(margin); + calculateSceneScalingFactors(); } void Scatter3DRenderer::resetClickedStatus() @@ -374,6 +520,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) m_yFlipped = true; else m_yFlipped = false; + m_yFlippedForGrid = m_yFlipped; // Polar axis grid drawing in abstract needs this // Calculate background rotation if (!m_zFlipped && !m_xFlipped) @@ -389,169 +536,182 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QVector3D lightPos = m_cachedScene->activeLight()->position(); // Introduce regardless of shadow quality to simplify logic - QMatrix4x4 depthViewMatrix; - QMatrix4x4 depthProjectionMatrix; QMatrix4x4 depthProjectionViewMatrix; + ShaderHelper *pointSelectionShader; + if (!m_isOpenGLES) { #if !defined(QT_OPENGL_ES_2) - if (m_havePointSeries) { - glEnable(GL_POINT_SMOOTH); - glEnable(GL_PROGRAM_POINT_SIZE); - } - - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Render scene into a depth texture for using with shadow mapping - // Bind depth shader - m_depthShader->bind(); - - // Set viewport for depth map rendering. Must match texture size. Larger values give smoother shadows. - glViewport(0, 0, - m_primarySubViewport.width() * m_shadowQualityMultiplier, - m_primarySubViewport.height() * m_shadowQualityMultiplier); - - // Enable drawing to framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer); - glClear(GL_DEPTH_BUFFER_BIT); - - // Set front face culling to reduce self-shadowing issues - glCullFace(GL_FRONT); - - // Get the depth view matrix - // It may be possible to hack lightPos here if we want to make some tweaks to shadow - QVector3D depthLightPos = activeCamera->d_ptr->calculatePositionRelativeToCamera( - zeroVector, 0.0f, 2.5f / m_autoScaleAdjustment); - depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector); - // Set the depth projection matrix - depthProjectionMatrix.perspective(15.0f, viewPortRatio, 3.0f, 100.0f); - depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix; - - // Draw dots to depth buffer - foreach (SeriesRenderCache *baseCache, m_renderCacheList) { - if (baseCache->isVisible()) { - ScatterSeriesRenderCache *cache = - static_cast<ScatterSeriesRenderCache *>(baseCache); - ObjectHelper *dotObj = cache->object(); - QQuaternion seriesRotation(cache->meshRotation()); - const ScatterRenderItemArray &renderArray = cache->renderArray(); - const int renderArraySize = renderArray.size(); - bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint); - float itemSize = cache->itemSize() / itemScaler; - if (itemSize == 0.0f) - itemSize = m_dotSizeScale; - if (drawingPoints) { - // Scale points based on shadow quality for shadows, not by zoom level - glPointSize(itemSize * 100.0f * m_shadowQualityMultiplier); - } - QVector3D modelScaler(itemSize, itemSize, itemSize); + if (m_havePointSeries) { + glEnable(GL_POINT_SMOOTH); + glEnable(GL_PROGRAM_POINT_SIZE); + } - if (!optimizationDefault - && ((drawingPoints && cache->bufferPoints()->indexCount() == 0) - || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { - continue; - } + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + // Render scene into a depth texture for using with shadow mapping + // Bind depth shader + m_depthShader->bind(); + + // Set viewport for depth map rendering. Must match texture size. Larger values give smoother shadows. + glViewport(0, 0, + m_primarySubViewport.width() * m_shadowQualityMultiplier, + m_primarySubViewport.height() * m_shadowQualityMultiplier); + + // Enable drawing to framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, m_depthFrameBuffer); + glClear(GL_DEPTH_BUFFER_BIT); + + // Set front face culling to reduce self-shadowing issues + glCullFace(GL_FRONT); + + QMatrix4x4 depthViewMatrix; + QMatrix4x4 depthProjectionMatrix; + + // Get the depth view matrix + // It may be possible to hack lightPos here if we want to make some tweaks to shadow + QVector3D depthLightPos = activeCamera->d_ptr->calculatePositionRelativeToCamera( + zeroVector, 0.0f, 2.5f / m_autoScaleAdjustment); + depthViewMatrix.lookAt(depthLightPos, zeroVector, upVector); + // Set the depth projection matrix + depthProjectionMatrix.perspective(15.0f, viewPortRatio, 3.0f, 100.0f); + depthProjectionViewMatrix = depthProjectionMatrix * depthViewMatrix; + + // Draw dots to depth buffer + foreach (SeriesRenderCache *baseCache, m_renderCacheList) { + if (baseCache->isVisible()) { + ScatterSeriesRenderCache *cache = + static_cast<ScatterSeriesRenderCache *>(baseCache); + ObjectHelper *dotObj = cache->object(); + QQuaternion seriesRotation(cache->meshRotation()); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const int renderArraySize = renderArray.size(); + bool drawingPoints = (cache->mesh() == QAbstract3DSeries::MeshPoint); + float itemSize = cache->itemSize() / itemScaler; + if (itemSize == 0.0f) + itemSize = m_dotSizeScale; + if (drawingPoints) { + // Scale points based on shadow quality for shadows, not by zoom level + m_funcs_2_1->glPointSize(itemSize * 100.0f * m_shadowQualityMultiplier); + } + QVector3D modelScaler(itemSize, itemSize, itemSize); - int loopCount = 1; - if (optimizationDefault) - loopCount = renderArraySize; - for (int dot = 0; dot < loopCount; dot++) { - const ScatterRenderItem &item = renderArray.at(dot); - if (!item.isVisible() && optimizationDefault) + if (!optimizationDefault + && ((drawingPoints && cache->bufferPoints()->indexCount() == 0) + || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { continue; - - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - - if (optimizationDefault) { - modelMatrix.translate(item.translation()); - if (!drawingPoints) { - if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) - modelMatrix.rotate(seriesRotation * item.rotation()); - modelMatrix.scale(modelScaler); - } } - MVPMatrix = depthProjectionViewMatrix * modelMatrix; + int loopCount = 1; + if (optimizationDefault) + loopCount = renderArraySize; + for (int dot = 0; dot < loopCount; dot++) { + const ScatterRenderItem &item = renderArray.at(dot); + if (!item.isVisible() && optimizationDefault) + continue; - m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix); + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; - if (drawingPoints) { - if (optimizationDefault) - m_drawer->drawPoint(m_depthShader); - else - m_drawer->drawPoints(m_depthShader, cache->bufferPoints()); - } else { if (optimizationDefault) { - // 1st attribute buffer : vertices - glEnableVertexAttribArray(m_depthShader->posAtt()); - glBindBuffer(GL_ARRAY_BUFFER, dotObj->vertexBuf()); - glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, - (void *)0); - - // Index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dotObj->elementBuf()); + modelMatrix.translate(item.translation()); + if (!drawingPoints) { + if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) + modelMatrix.rotate(seriesRotation * item.rotation()); + modelMatrix.scale(modelScaler); + } + } - // Draw the triangles - glDrawElements(GL_TRIANGLES, dotObj->indexCount(), GL_UNSIGNED_SHORT, - (void *)0); + MVPMatrix = depthProjectionViewMatrix * modelMatrix; - // Free buffers - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix); - glDisableVertexAttribArray(m_depthShader->posAtt()); + if (drawingPoints) { + if (optimizationDefault) + m_drawer->drawPoint(m_depthShader); + else + m_drawer->drawPoints(m_depthShader, cache->bufferPoints(), 0); } else { - ScatterObjectBufferHelper *object = cache->bufferObject(); - // 1st attribute buffer : vertices - glEnableVertexAttribArray(m_depthShader->posAtt()); - glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf()); - glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, - (void *)0); - - // Index buffer - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); - - // Draw the triangles - glDrawElements(GL_TRIANGLES, object->indexCount(), - object->indicesType(), (void *)0); - - // Free buffers - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glDisableVertexAttribArray(m_depthShader->posAtt()); + if (optimizationDefault) { + // 1st attribute buffer : vertices + glEnableVertexAttribArray(m_depthShader->posAtt()); + glBindBuffer(GL_ARRAY_BUFFER, dotObj->vertexBuf()); + glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, + (void *)0); + + // Index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dotObj->elementBuf()); + + // Draw the triangles + glDrawElements(GL_TRIANGLES, dotObj->indexCount(), GL_UNSIGNED_SHORT, + (void *)0); + + // Free buffers + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableVertexAttribArray(m_depthShader->posAtt()); + } else { + ScatterObjectBufferHelper *object = cache->bufferObject(); + // 1st attribute buffer : vertices + glEnableVertexAttribArray(m_depthShader->posAtt()); + glBindBuffer(GL_ARRAY_BUFFER, object->vertexBuf()); + glVertexAttribPointer(m_depthShader->posAtt(), 3, GL_FLOAT, GL_FALSE, 0, + (void *)0); + + // Index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object->elementBuf()); + + // Draw the triangles + glDrawElements(GL_TRIANGLES, object->indexCount(), + object->indicesType(), (void *)0); + + // Free buffers + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDisableVertexAttribArray(m_depthShader->posAtt()); + } } } } } - } - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); - // Disable drawing to framebuffer (= enable drawing to screen) - glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); + // Disable drawing to framebuffer (= enable drawing to screen) + glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); - // Reset culling to normal - glCullFace(GL_BACK); + // Reset culling to normal + glCullFace(GL_BACK); - // Revert to original viewport - glViewport(m_primarySubViewport.x(), - m_primarySubViewport.y(), - m_primarySubViewport.width(), - m_primarySubViewport.height()); + // Revert to original viewport + glViewport(m_primarySubViewport.x(), + m_primarySubViewport.y(), + m_primarySubViewport.width(), + m_primarySubViewport.height()); + } +#endif + pointSelectionShader = m_selectionShader; + } else { + pointSelectionShader = m_pointShader; } - ShaderHelper *pointSelectionShader = m_selectionShader; -#else - ShaderHelper *pointSelectionShader = m_pointShader; -#endif ShaderHelper *selectionShader = m_selectionShader; + // Do position mapping when necessary + if (m_graphPositionQueryPending) { + QVector3D graphDimensions(m_scaleX, m_scaleY, m_scaleZ); + queriedGraphPosition(projectionViewMatrix, graphDimensions, defaultFboHandle); + emit needRender(); + } + // Skip selection mode drawing if we have no selection mode if (m_cachedSelectionMode > QAbstract3DGraph::SelectionNone && SelectOnScene == m_selectionState - && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty())) { + && (m_visibleSeriesCount > 0 || !m_customRenderCache.isEmpty()) + && m_selectionTexture) { // Draw dots to selection buffer glBindFramebuffer(GL_FRAMEBUFFER, m_selectionFrameBuffer); glViewport(0, 0, @@ -578,8 +738,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (itemSize == 0.0f) itemSize = m_dotSizeScale; #if !defined(QT_OPENGL_ES_2) - if (drawingPoints) - glPointSize(itemSize * activeCamera->zoomLevel()); // Scale points based on zoom + if (drawingPoints && !m_isOpenGLES) + m_funcs_2_1->glPointSize(itemSize * activeCamera->zoomLevel()); #endif QVector3D modelScaler(itemSize, itemSize, itemSize); @@ -627,9 +787,10 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, + viewMatrix, projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -639,6 +800,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QVector4D clickedColor = Utils::getSelection(m_inputPosition, m_viewport.height()); selectionColorToSeriesAndIndex(clickedColor, m_clickedIndex, m_clickedSeries); + m_clickResolved = true; emit needRender(); @@ -683,13 +845,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) previousMeshColorStyle = Q3DTheme::ColorStyleRangeGradient; m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), 0.0f); } - glEnable(GL_TEXTURE_2D); } else { dotShader = pointSelectionShader; - previousDrawingPoints = true; - dotShader->bind(); } + float rangeGradientYScaler = 0.5f / m_scaleY; + foreach (SeriesRenderCache *baseCache, m_renderCacheList) { if (baseCache->isVisible()) { ScatterSeriesRenderCache *cache = @@ -710,14 +871,16 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (itemSize == 0.0f) itemSize = m_dotSizeScale; #if !defined(QT_OPENGL_ES_2) - if (drawingPoints) - glPointSize(itemSize * activeCamera->zoomLevel()); // Scale points based on zoom + if (drawingPoints && !m_isOpenGLES) + m_funcs_2_1->glPointSize(itemSize * activeCamera->zoomLevel()); #endif QVector3D modelScaler(itemSize, itemSize, itemSize); + int gradientImageHeight = cache->gradientImage().height(); + int maxGradientPositition = gradientImageHeight - 1; if (!optimizationDefault && ((drawingPoints && cache->bufferPoints()->indexCount() == 0) - || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { + || (!drawingPoints && cache->bufferObject()->indexCount() == 0))) { continue; } @@ -725,10 +888,18 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (drawingPoints != previousDrawingPoints || (!drawingPoints && (colorStyleIsUniform != (previousMeshColorStyle - == Q3DTheme::ColorStyleUniform)))) { + == Q3DTheme::ColorStyleUniform))) + || (!optimizationDefault && drawingPoints)) { previousDrawingPoints = drawingPoints; if (drawingPoints) { - dotShader = pointSelectionShader; + if (!optimizationDefault && rangeGradientPoints) { + if (m_isOpenGLES) + dotShader = m_staticGradientPointShader; + else + dotShader = m_labelShader; + } else { + dotShader = pointSelectionShader; + } } else { if (colorStyleIsUniform) dotShader = m_dotShader; @@ -740,13 +911,13 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (!drawingPoints && !colorStyleIsUniform && previousMeshColorStyle != colorStyle) { if (colorStyle == Q3DTheme::ColorStyleObjectGradient) { - m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientMin(), 0.0f); - m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), - 0.5f); + dotShader->setUniformValue(dotShader->gradientMin(), 0.0f); + dotShader->setUniformValue(dotShader->gradientHeight(), + 0.5f); } else { // Each dot is of uniform color according to its Y-coordinate - m_dotGradientShader->setUniformValue(m_dotGradientShader->gradientHeight(), - 0.0f); + dotShader->setUniformValue(dotShader->gradientHeight(), + 0.0f); } } @@ -760,6 +931,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) int loopCount = 1; if (optimizationDefault) loopCount = renderArraySize; + for (int i = 0; i < loopCount; i++) { ScatterRenderItem &item = renderArray[i]; if (!item.isVisible() && optimizationDefault) @@ -791,7 +963,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (rangeGradientPoints) { // Drawing points with range gradient // Get color from gradient based on items y position converted to percent - int position = int(item.translation().y() * 50.0f) + 50; + int position = ((item.translation().y() + m_scaleY) * rangeGradientYScaler) * gradientImageHeight; + position = qMin(maxGradientPositition, position); // clamp to edge dotColor = Utils::vectorFromColor( cache->gradientImage().pixel(0, position)); } else { @@ -801,6 +974,9 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) gradientTexture = cache->baseGradientTexture(); } + if (!optimizationDefault && rangeGradientPoints) + gradientTexture = cache->baseGradientTexture(); + GLfloat lightStrength = m_cachedTheme->lightStrength(); if (optimizationDefault && selectedSeries && (m_selectedItemIndex == i)) { if (useColor) @@ -808,13 +984,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) else gradientTexture = cache->singleHighlightGradientTexture(); lightStrength = m_cachedTheme->highlightLightStrength(); - // Insert data to ScatterRenderItem - // We don't have ownership, so don't delete the previous one + // Save the reference to the item to be used in label drawing selectedItem = &item; dotSelectionFound = true; // Save selected item size (adjusted with font size) for selection label // positioning - selectedItemSize = itemSize + (m_cachedTheme->font().pointSizeF() / 500.0f); + selectedItemSize = itemSize + m_drawer->scaledFontSize() - 0.05f; } if (!drawingPoints) { @@ -829,10 +1004,10 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) dotShader->setUniformValue(dotShader->color(), dotColor); } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) { dotShader->setUniformValue(dotShader->gradientMin(), - (item.translation().y() + 1.0f) / 2.0f); + (item.translation().y() + m_scaleY) + * rangeGradientYScaler); } -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { if (!drawingPoints) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; @@ -853,11 +1028,9 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (optimizationDefault) m_drawer->drawPoint(dotShader); else - m_drawer->drawPoints(dotShader, cache->bufferPoints()); + m_drawer->drawPoints(dotShader, cache->bufferPoints(), gradientTexture); } - } else -#endif - { + } else { if (!drawingPoints) { // Set shadowless shader bindings dotShader->setUniformValue(dotShader->lightS(), lightStrength); @@ -871,100 +1044,139 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) if (optimizationDefault) m_drawer->drawPoint(dotShader); else - m_drawer->drawPoints(dotShader, cache->bufferPoints()); + m_drawer->drawPoints(dotShader, cache->bufferPoints(), gradientTexture); } } } + // Draw the selected item on static optimization if (!optimizationDefault && selectedSeries && m_selectedItemIndex != Scatter3DController::invalidSelectionIndex()) { ScatterRenderItem &item = renderArray[m_selectedItemIndex]; - ObjectHelper *dotObj = cache->object(); + if (item.isVisible()) { + ShaderHelper *selectionShader; + if (drawingPoints) { + selectionShader = pointSelectionShader; + } else { + if (colorStyleIsUniform) + selectionShader = m_staticSelectedItemShader; + else + selectionShader = m_staticSelectedItemGradientShader; + } + selectionShader->bind(); - QMatrix4x4 modelMatrix; - QMatrix4x4 itModelMatrix; + ObjectHelper *dotObj = cache->object(); - modelMatrix.translate(item.translation()); - if (!drawingPoints) { - if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) { - QQuaternion totalRotation = seriesRotation * item.rotation(); - modelMatrix.rotate(totalRotation); - itModelMatrix.rotate(totalRotation); + QMatrix4x4 modelMatrix; + QMatrix4x4 itModelMatrix; + + modelMatrix.translate(item.translation()); + if (!drawingPoints) { + if (!seriesRotation.isIdentity() || !item.rotation().isIdentity()) { + QQuaternion totalRotation = seriesRotation * item.rotation(); + modelMatrix.rotate(totalRotation); + itModelMatrix.rotate(totalRotation); + } + modelMatrix.scale(modelScaler); + itModelMatrix.scale(modelScaler); + + selectionShader->setUniformValue(selectionShader->lightP(), + lightPos); + selectionShader->setUniformValue(selectionShader->view(), + viewMatrix); + selectionShader->setUniformValue(selectionShader->ambientS(), + m_cachedTheme->ambientLightStrength()); + selectionShader->setUniformValue(selectionShader->lightColor(), + lightColor); } - modelMatrix.scale(modelScaler); - itModelMatrix.scale(modelScaler); - } - QMatrix4x4 MVPMatrix; + QMatrix4x4 MVPMatrix; #ifdef SHOW_DEPTH_TEXTURE_SCENE - MVPMatrix = depthProjectionViewMatrix * modelMatrix; + MVPMatrix = depthProjectionViewMatrix * modelMatrix; #else - MVPMatrix = projectionViewMatrix * modelMatrix; + MVPMatrix = projectionViewMatrix * modelMatrix; #endif - if (useColor) - dotColor = cache->singleHighlightColor(); - else - gradientTexture = cache->singleHighlightGradientTexture(); - GLfloat lightStrength = m_cachedTheme->highlightLightStrength(); - // Save the reference to the item to be used on label drawing - selectedItem = &item; - dotSelectionFound = true; - // Save selected item size (adjusted with font size) for selection label - // positioning - selectedItemSize = itemSize + (m_cachedTheme->font().pointSizeF() / 500.0f); - - if (!drawingPoints) { - // Set shader bindings - dotShader->setUniformValue(dotShader->model(), modelMatrix); - dotShader->setUniformValue(dotShader->nModel(), - itModelMatrix.inverted().transposed()); - } + if (useColor) + dotColor = cache->singleHighlightColor(); + else + gradientTexture = cache->singleHighlightGradientTexture(); + GLfloat lightStrength = m_cachedTheme->highlightLightStrength(); + // Save the reference to the item to be used in label drawing + selectedItem = &item; + dotSelectionFound = true; + // Save selected item size (adjusted with font size) for selection label + // positioning + selectedItemSize = itemSize + m_drawer->scaledFontSize() - 0.05f; - dotShader->setUniformValue(dotShader->MVP(), MVPMatrix); - if (useColor) { - dotShader->setUniformValue(dotShader->color(), dotColor); - } else if (colorStyle == Q3DTheme::ColorStyleRangeGradient) { - dotShader->setUniformValue(dotShader->gradientMin(), - (item.translation().y() + 1.0f) / 2.0f); - } + if (!drawingPoints) { + // Set shader bindings + selectionShader->setUniformValue(selectionShader->model(), modelMatrix); + selectionShader->setUniformValue(selectionShader->nModel(), + itModelMatrix.inverted().transposed()); + if (!colorStyleIsUniform) { + if (colorStyle == Q3DTheme::ColorStyleObjectGradient) { + selectionShader->setUniformValue(selectionShader->gradientMin(), + 0.0f); + selectionShader->setUniformValue(selectionShader->gradientHeight(), + 0.5f); + } else { + // Each dot is of uniform color according to its Y-coordinate + selectionShader->setUniformValue(selectionShader->gradientHeight(), + 0.0f); + selectionShader->setUniformValue(selectionShader->gradientMin(), + (item.translation().y() + m_scaleY) + * rangeGradientYScaler); + } + } + } - if (!drawingPoints) { - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, 1.0f); - } + selectionShader->setUniformValue(selectionShader->MVP(), MVPMatrix); + if (useColor) + selectionShader->setUniformValue(selectionShader->color(), dotColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { if (!drawingPoints) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - dotShader->setUniformValue(dotShader->depth(), depthMVPMatrix); - dotShader->setUniformValue(dotShader->lightS(), lightStrength / 10.0f); - - // Draw the object - m_drawer->drawObject(dotShader, dotObj, gradientTexture, m_depthTexture); - } else { - // Draw the object - m_drawer->drawPoint(dotShader); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, 1.0f); } - } else -#endif - { - if (!drawingPoints) { - // Set shadowless shader bindings - dotShader->setUniformValue(dotShader->lightS(), lightStrength); - // Draw the object - m_drawer->drawObject(dotShader, dotObj, gradientTexture); + + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone + && !m_isOpenGLES) { + if (!drawingPoints) { + // Set shadow shader bindings + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + selectionShader->setUniformValue(selectionShader->shadowQ(), + m_shadowQualityToShader); + selectionShader->setUniformValue(selectionShader->depth(), + depthMVPMatrix); + selectionShader->setUniformValue(selectionShader->lightS(), + lightStrength / 10.0f); + + // Draw the object + m_drawer->drawObject(selectionShader, dotObj, gradientTexture, + m_depthTexture); + } else { + // Draw the object + m_drawer->drawPoint(selectionShader); + } } else { - // Draw the object - m_drawer->drawPoint(dotShader); + if (!drawingPoints) { + // Set shadowless shader bindings + selectionShader->setUniformValue(selectionShader->lightS(), + lightStrength); + // Draw the object + m_drawer->drawObject(selectionShader, dotObj, gradientTexture); + } else { + // Draw the object + m_drawer->drawPoint(selectionShader); + } } - } - if (!drawingPoints) - glDisable(GL_POLYGON_OFFSET_FILL); + if (!drawingPoints) + glDisable(GL_POLYGON_OFFSET_FILL); + } + dotShader->bind(); } } } @@ -987,25 +1199,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat xScale = (m_graphAspectRatio * m_areaSize.width()) / m_scaleFactor - + m_backgroundMargin; - GLfloat zScale = (m_graphAspectRatio * m_areaSize.height()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > xScale) - xScale = m_maxItemSize; - if (m_maxItemSize > zScale) - zScale = m_maxItemSize; - QVector3D bgScale(xScale, 1.0f + m_backgroundMargin, zScale); -#else // ..and this if we want uniform scaling based on largest dimension - QVector3D bgScale((m_graphAspectRatio + m_backgroundMargin), - 1.0f + m_backgroundMargin, - (m_graphAspectRatio + m_backgroundMargin)); -#endif + 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(180.0f, 1.0, 0.0, 0.0); + 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); @@ -1031,8 +1230,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) m_cachedTheme->ambientLightStrength() * 2.0f); m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(), @@ -1043,9 +1241,7 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) // Draw the object m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture); - } else -#endif - { + } else { // Set shadowless shader bindings m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), m_cachedTheme->lightStrength()); @@ -1055,16 +1251,17 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - // Disable textures - glDisable(GL_TEXTURE_2D); - // 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()) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + ShaderHelper *lineShader; + if (m_isOpenGLES) + lineShader = m_selectionShader; // Plain color shader for GL_LINES + else + lineShader = m_backgroundShader; // Bind line shader lineShader->bind(); @@ -1076,15 +1273,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength()); lineShader->setUniformValue(lineShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + 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 -#endif - { + } else { // Set shadowless shader bindings lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 2.5f); @@ -1094,242 +1288,213 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) QQuaternion lineXRotation; if (m_xFlipped) - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f); + lineYRotation = m_yRightAngleRotationNeg; else - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); + lineYRotation = m_yRightAngleRotation; - if (m_yFlipped) - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f); + if (m_yFlippedForGrid) + lineXRotation = m_xRightAngleRotation; else - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f); + lineXRotation = m_xRightAngleRotationNeg; - GLfloat yFloorLinePosition = -1.0f - m_backgroundMargin + gridLineOffset; - if (m_yFlipped) + GLfloat yFloorLinePosition = -m_scaleYWithBackground + gridLineOffset; + if (m_yFlippedForGrid) yFloorLinePosition = -yFloorLinePosition; // Rows (= Z) if (m_axisCacheZ.segmentCount() > 0) { // Floor lines int gridLineCount = m_axisCacheZ.gridLineCount(); + if (m_polarGraph) { + drawRadialGrid(lineShader, yFloorLinePosition, projectionViewMatrix, + depthProjectionViewMatrix); + } else { + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat xScale = (m_graphAspectRatio * m_areaSize.width()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > xScale) - xScale = m_maxItemSize; - QVector3D gridLineScaler(xScale, gridLineWidth, gridLineWidth); -#else // ..and this if we want uniform scaling based on largest dimension - QVector3D gridLineScaler((m_graphAspectRatio + m_backgroundMargin), - gridLineWidth, gridLineWidth); -#endif + modelMatrix.translate(0.0f, yFloorLinePosition, + m_axisCacheZ.gridLinePosition(line)); - 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.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.rotate(lineXRotation); + itModelMatrix.rotate(lineXRotation); - modelMatrix.rotate(lineXRotation); - itModelMatrix.rotate(lineXRotation); + MVPMatrix = projectionViewMatrix * modelMatrix; - MVPMatrix = projectionViewMatrix * modelMatrix; + // Set the rest of the shader bindings + lineShader->setUniformValue(lineShader->model(), modelMatrix); + lineShader->setUniformValue(lineShader->nModel(), + itModelMatrix.inverted().transposed()); + lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); - // 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 !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - // Set shadow shader bindings - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_isOpenGLES) { + m_drawer->drawLine(lineShader); + } else { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; + // Set shadow shader bindings + lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); + } else { + // Draw the object + m_drawer->drawObject(lineShader, m_gridLineObj); + } + } } -#else - m_drawer->drawLine(lineShader); -#endif - } - // Side wall lines - gridLineScaler = QVector3D(gridLineWidth, 1.0f + m_backgroundMargin, gridLineWidth); -#ifndef USE_UNIFORM_SCALING - GLfloat lineXTrans = (m_graphAspectRatio * m_areaSize.width()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineXTrans) - lineXTrans = m_maxItemSize - gridLineOffset; -#else - GLfloat lineXTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; -#endif - if (!m_xFlipped) - lineXTrans = -lineXTrans; + // Side wall lines + GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; + if (!m_xFlipped) + lineXTrans = -lineXTrans; - modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); -#if !defined(QT_OPENGL_ES_2) - modelMatrix.rotate(lineYRotation); - itModelMatrix.rotate(lineYRotation); -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); - MVPMatrix = projectionViewMatrix * modelMatrix; + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else { + modelMatrix.rotate(lineYRotation); + itModelMatrix.rotate(lineYRotation); + } - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + MVPMatrix = projectionViewMatrix * modelMatrix; -#if !defined(QT_OPENGL_ES_2) - 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); + // 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); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } // Columns (= X) if (m_axisCacheX.segmentCount() > 0) { -#if defined(QT_OPENGL_ES_2) - lineXRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); -#endif + if (m_isOpenGLES) + lineXRotation = m_yRightAngleRotation; // Floor lines int gridLineCount = m_axisCacheX.gridLineCount(); -#ifndef USE_UNIFORM_SCALING - GLfloat zScale = (m_graphAspectRatio * m_areaSize.height()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > zScale) - zScale = m_maxItemSize; - QVector3D gridLineScaler(gridLineWidth, gridLineWidth, zScale); -#else - QVector3D gridLineScaler(gridLineWidth, gridLineWidth, - m_graphAspectRatio + m_backgroundMargin); -#endif - - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition, - 0.0f); + if (m_polarGraph) { + drawAngularGrid(lineShader, yFloorLinePosition, projectionViewMatrix, + depthProjectionViewMatrix); + } else { + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), yFloorLinePosition, + 0.0f); - modelMatrix.rotate(lineXRotation); - itModelMatrix.rotate(lineXRotation); + modelMatrix.scale(gridLineScaleZ); + itModelMatrix.scale(gridLineScaleZ); - MVPMatrix = projectionViewMatrix * modelMatrix; + modelMatrix.rotate(lineXRotation); + itModelMatrix.rotate(lineXRotation); - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + MVPMatrix = projectionViewMatrix * modelMatrix; -#if !defined(QT_OPENGL_ES_2) - 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); + // 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); + } } -#else - m_drawer->drawLine(lineShader); -#endif - } - - // Back wall lines -#ifndef USE_UNIFORM_SCALING - GLfloat lineZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineZTrans) - lineZTrans = m_maxItemSize - gridLineOffset; -#else - GLfloat lineZTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; -#endif - if (!m_zFlipped) - lineZTrans = -lineZTrans; - gridLineScaler = QVector3D(gridLineWidth, 1.0f + m_backgroundMargin, gridLineWidth); + // Back wall lines + GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; + if (!m_zFlipped) + lineZTrans = -lineZTrans; - modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); -#if !defined(QT_OPENGL_ES_2) - if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - } -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); - MVPMatrix = projectionViewMatrix * modelMatrix; + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else { + if (m_zFlipped) { + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); + } + } - // Set the rest of the shader bindings - lineShader->setUniformValue(lineShader->model(), modelMatrix); - lineShader->setUniformValue(lineShader->nModel(), - itModelMatrix.inverted().transposed()); - lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); + MVPMatrix = projectionViewMatrix * modelMatrix; -#if !defined(QT_OPENGL_ES_2) - 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); + // 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); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } @@ -1338,21 +1503,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) // Back wall int gridLineCount = m_axisCacheY.gridLineCount(); -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat lineZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineZTrans) - lineZTrans = m_maxItemSize - gridLineOffset; - GLfloat xScale = (m_graphAspectRatio * m_areaSize.width()) / m_scaleFactor - + m_backgroundMargin; - if (m_maxItemSize > xScale) - xScale = m_maxItemSize; - QVector3D gridLineScaler(xScale, gridLineWidth, gridLineWidth); -#else // ..and this if we want uniform scaling based on largest dimension - GLfloat lineZTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; - QVector3D gridLineScaler((m_graphAspectRatio + m_backgroundMargin), - gridLineWidth, gridLineWidth); -#endif + GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; + if (!m_zFlipped) lineZTrans = -lineZTrans; @@ -1363,12 +1515,12 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) modelMatrix.translate(0.0f, m_axisCacheY.gridLinePosition(line), lineZTrans); - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.scale(gridLineScaleX); + itModelMatrix.scale(gridLineScaleX); if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -1379,38 +1531,25 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + 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 { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } // Side wall -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat lineXTrans = (m_graphAspectRatio * m_areaSize.width()) - / m_scaleFactor - gridLineOffset + m_backgroundMargin; - if (m_maxItemSize > lineXTrans) - lineXTrans = m_maxItemSize - gridLineOffset; - GLfloat zScale = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor + m_backgroundMargin; - if (m_maxItemSize > zScale) - zScale = m_maxItemSize; - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, zScale); -#else // ..and this if we want uniform scaling based on largest dimension - GLfloat lineXTrans = m_graphAspectRatio + m_backgroundMargin - gridLineOffset; - gridLineScaler = QVector3D(gridLineWidth, gridLineWidth, - m_graphAspectRatio + m_backgroundMargin); -#endif + GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; + if (!m_xFlipped) lineXTrans = -lineXTrans; @@ -1421,8 +1560,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) modelMatrix.translate(lineXTrans, m_axisCacheY.gridLinePosition(line), 0.0f); - modelMatrix.scale(gridLineScaler); - itModelMatrix.scale(gridLineScaler); + modelMatrix.scale(gridLineScaleZ); + itModelMatrix.scale(gridLineScaleZ); modelMatrix.rotate(lineYRotation); itModelMatrix.rotate(lineYRotation); @@ -1435,20 +1574,20 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + 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 { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } } } @@ -1489,7 +1628,6 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) glEnable(GL_DEPTH_TEST); } - glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); // Release shader @@ -1512,7 +1650,6 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa shader = m_labelShader; shader->bind(); - glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -1532,15 +1669,14 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa // Z Labels if (m_axisCacheZ.segmentCount() > 0) { int labelCount = m_axisCacheZ.labelCount(); -#ifndef USE_UNIFORM_SCALING - GLfloat labelXTrans = (m_graphAspectRatio * m_areaSize.width()) - / m_scaleFactor + labelMargin + m_backgroundMargin; - if (m_maxItemSize > labelXTrans) - labelXTrans = m_maxItemSize + labelMargin; -#else - GLfloat labelXTrans = m_graphAspectRatio + m_backgroundMargin + labelMargin; -#endif - GLfloat labelYTrans = -1.0f - m_backgroundMargin; + float labelXTrans = m_scaleXWithBackground + labelMargin; + float labelYTrans = -m_scaleYWithBackground; + if (m_polarGraph) { + labelXTrans *= m_radialLabelOffset; + // YTrans up only if over background + if (m_radialLabelOffset < 1.0f) + labelYTrans += gridLineOffset + gridLineWidth; + } Qt::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_xFlipped) @@ -1550,7 +1686,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa if (labelAutoAngle == 0.0f) { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) labelRotation.setY(180.0f); else @@ -1562,7 +1698,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } else { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX) @@ -1621,13 +1757,35 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { - labelTrans.setZ(m_axisCacheZ.labelPosition(label)); - 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); - const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label); if (drawSelection) { QVector4D labelColor = QVector4D(label / 255.0f, 0.0f, 0.0f, @@ -1642,7 +1800,14 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width())); } if (!drawSelection && m_axisCacheZ.isTitleVisible()) { - labelTrans.setZ(0.0f); + 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); } @@ -1656,15 +1821,13 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamY = activeCamera->yRotation() * labelAngleFraction; fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheX.labelCount(); -#ifndef USE_UNIFORM_SCALING - GLfloat labelZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor + labelMargin + m_backgroundMargin; - if (m_maxItemSize > labelZTrans) - labelZTrans = m_maxItemSize + labelMargin; -#else - GLfloat labelZTrans = m_graphAspectRatio + m_backgroundMargin + labelMargin; -#endif - GLfloat labelYTrans = -1.0f - m_backgroundMargin; + float labelZTrans = 0.0f; + float labelYTrans = -m_scaleYWithBackground; + if (m_polarGraph) + labelYTrans += gridLineOffset + gridLineWidth; + else + labelZTrans = m_scaleZWithBackground + labelMargin; + Qt::AlignmentFlag alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_zFlipped) @@ -1675,7 +1838,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation = QVector3D(-90.0f, 90.0f, 0.0f); if (m_xFlipped) labelRotation.setY(-90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_xFlipped) labelRotation.setY(-90.0f); else @@ -1687,7 +1850,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation.setY(-90.0f); else labelRotation.setY(90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX) @@ -1735,6 +1898,14 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } 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; @@ -1746,14 +1917,64 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa indexStep = 1; } float offsetValue = 0.0f; - for (int label = startIndex; label != endIndex; label = label + indexStep) { - labelTrans.setX(m_axisCacheX.labelPosition(label)); + 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 - m_dummyRenderItem.setTranslation(labelTrans); + 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 = Qt::AlignmentFlag(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, @@ -1769,8 +1990,20 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } 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); + activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader, + radial); } } @@ -1782,22 +2015,13 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamY = activeCamera->yRotation() * labelAngleFraction; fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheY.labelCount(); -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - GLfloat labelXTrans = (m_graphAspectRatio* m_areaSize.width()) - / m_scaleFactor + m_backgroundMargin; - GLfloat labelZTrans = (m_graphAspectRatio * m_areaSize.height()) - / m_scaleFactor + m_backgroundMargin; - if (m_maxItemSize > labelXTrans) - labelXTrans = m_maxItemSize; - if (m_maxItemSize > labelZTrans) - labelZTrans = m_maxItemSize; -#else // ..and this if we want uniform scaling based on largest dimension - GLfloat labelXTrans = m_graphAspectRatio + m_backgroundMargin; - GLfloat labelZTrans = labelXTrans; -#endif + + float labelXTrans = m_scaleXWithBackground; + float labelZTrans = m_scaleZWithBackground; + // Back & side wall - GLfloat labelMarginXTrans = labelMargin; - GLfloat labelMarginZTrans = labelMargin; + 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 = @@ -1854,7 +2078,7 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(label); - const GLfloat labelYTrans = m_axisCacheY.labelPosition(label); + float labelYTrans = m_axisCacheY.labelPosition(label); glPolygonOffset(offsetValue++ / -10.0f, 1.0f); @@ -1864,6 +2088,21 @@ void Scatter3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa 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); @@ -1958,10 +2197,8 @@ void Scatter3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality qual handleShadowQualityChange(); -#if !defined(QT_OPENGL_ES_2) // Re-init depth buffer updateDepthBuffer(); -#endif } void Scatter3DRenderer::loadBackgroundMesh() @@ -1972,8 +2209,13 @@ void Scatter3DRenderer::loadBackgroundMesh() void Scatter3DRenderer::updateTextures() { + Abstract3DRenderer::updateTextures(); + // Drawer has changed; this flag needs to be checked when checking if we need to update labels m_updateLabels = true; + + if (m_polarGraph) + calculateSceneScalingFactors(); } void Scatter3DRenderer::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh) @@ -1991,35 +2233,83 @@ void Scatter3DRenderer::calculateTranslation(ScatterRenderItem &item) { // We need to normalize translations const QVector3D &pos = item.position(); - float xTrans = m_axisCacheX.positionAt(pos.x()); + float xTrans; float yTrans = m_axisCacheY.positionAt(pos.y()); - float zTrans = m_axisCacheZ.positionAt(pos.z()); + float zTrans; + if (m_polarGraph) { + calculatePolarXZ(pos, xTrans, zTrans); + } else { + xTrans = m_axisCacheX.positionAt(pos.x()); + zTrans = m_axisCacheZ.positionAt(pos.z()); + } item.setTranslation(QVector3D(xTrans, yTrans, zTrans)); } void Scatter3DRenderer::calculateSceneScalingFactors() { - m_heightNormalizer = GLfloat(m_axisCacheY.max() - m_axisCacheY.min()) / 2.0f; - m_areaSize.setHeight((m_axisCacheZ.max() - m_axisCacheZ.min()) / 2.0f); - m_areaSize.setWidth((m_axisCacheX.max() - m_axisCacheX.min()) / 2.0f); - m_scaleFactor = qMax(m_areaSize.width(), m_areaSize.height()); - -#ifndef USE_UNIFORM_SCALING // Use this if we want to use autoscaling for x and z - float factorScaler = 2.0f * m_graphAspectRatio / m_scaleFactor; - m_axisCacheX.setScale(factorScaler * m_areaSize.width()); - m_axisCacheZ.setScale(-factorScaler * m_areaSize.height()); -#else // ..and this if we want uniform scaling based on largest dimension - m_axisCacheX.setScale(2.0f * m_graphAspectRatio); - m_axisCacheZ.setScale(-m_axisCacheX.scale()); -#endif - m_axisCacheX.setTranslate(-m_axisCacheX.scale() / 2.0f); - m_axisCacheZ.setTranslate(-m_axisCacheZ.scale() / 2.0f); + if (m_requestedMargin < 0.0f) { + if (m_maxItemSize > defaultMaxSize) + m_hBackgroundMargin = m_maxItemSize / itemScaler; + else + m_hBackgroundMargin = defaultMaxSize; + m_vBackgroundMargin = m_hBackgroundMargin; + } else { + m_hBackgroundMargin = m_requestedMargin; + m_vBackgroundMargin = m_requestedMargin; + } + if (m_polarGraph) { + float polarMargin = calculatePolarBackgroundMargin(); + m_hBackgroundMargin = qMax(m_hBackgroundMargin, polarMargin); + } + + float horizontalAspectRatio; + if (m_polarGraph) + horizontalAspectRatio = 1.0f; + else + horizontalAspectRatio = m_graphHorizontalAspectRatio; + + QSizeF areaSize; + if (horizontalAspectRatio == 0.0f) { + areaSize.setHeight(m_axisCacheZ.max() - m_axisCacheZ.min()); + areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min()); + } else { + areaSize.setHeight(1.0f); + areaSize.setWidth(horizontalAspectRatio); + } + + float horizontalMaxDimension; + if (m_graphAspectRatio > 2.0f) { + horizontalMaxDimension = 2.0f; + m_scaleY = 2.0f / m_graphAspectRatio; + } else { + horizontalMaxDimension = m_graphAspectRatio; + m_scaleY = 1.0f; + } + if (m_polarGraph) + m_polarRadius = horizontalMaxDimension; + + float scaleFactor = qMax(areaSize.width(), areaSize.height()); + m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor; + m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor; + + m_scaleXWithBackground = m_scaleX + m_hBackgroundMargin; + m_scaleYWithBackground = m_scaleY + m_vBackgroundMargin; + m_scaleZWithBackground = m_scaleZ + m_hBackgroundMargin; + + m_axisCacheX.setScale(m_scaleX * 2.0f); + m_axisCacheY.setScale(m_scaleY * 2.0f); + m_axisCacheZ.setScale(-m_scaleZ * 2.0f); + m_axisCacheX.setTranslate(-m_scaleX); + m_axisCacheY.setTranslate(-m_scaleY); + m_axisCacheZ.setTranslate(m_scaleZ); + + updateCameraViewport(); + updateCustomItemPositions(); } void Scatter3DRenderer::initShaders(const QString &vertexShader, const QString &fragmentShader) { - if (m_dotShader) - delete m_dotShader; + delete m_dotShader; m_dotShader = new ShaderHelper(this, vertexShader, fragmentShader); m_dotShader->initialize(); } @@ -2027,16 +2317,30 @@ void Scatter3DRenderer::initShaders(const QString &vertexShader, const QString & void Scatter3DRenderer::initGradientShaders(const QString &vertexShader, const QString &fragmentShader) { - if (m_dotGradientShader) - delete m_dotGradientShader; + delete m_dotGradientShader; m_dotGradientShader = new ShaderHelper(this, vertexShader, fragmentShader); m_dotGradientShader->initialize(); + +} + +void Scatter3DRenderer::initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader) +{ + delete m_staticSelectedItemShader; + m_staticSelectedItemShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_staticSelectedItemShader->initialize(); + + delete m_staticSelectedItemGradientShader; + m_staticSelectedItemGradientShader = new ShaderHelper(this, gradientVertexShader, + gradientFragmentShader); + m_staticSelectedItemGradientShader->initialize(); } void Scatter3DRenderer::initSelectionShader() { - if (m_selectionShader) - delete m_selectionShader; + delete m_selectionShader; m_selectionShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPlainColor"), QStringLiteral(":/shaders/fragmentPlainColor")); m_selectionShader->initialize(); @@ -2054,41 +2358,45 @@ void Scatter3DRenderer::initSelectionBuffer() m_selectionDepthBuffer); } -#if !defined(QT_OPENGL_ES_2) void Scatter3DRenderer::initDepthShader() { - if (m_depthShader) - delete m_depthShader; - m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), - QStringLiteral(":/shaders/fragmentDepth")); - m_depthShader->initialize(); + if (!m_isOpenGLES) { + if (m_depthShader) + delete m_depthShader; + m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), + QStringLiteral(":/shaders/fragmentDepth")); + m_depthShader->initialize(); + } } void Scatter3DRenderer::updateDepthBuffer() { - m_textureHelper->deleteTexture(&m_depthTexture); + if (!m_isOpenGLES) { + m_textureHelper->deleteTexture(&m_depthTexture); - if (m_primarySubViewport.size().isEmpty()) - return; + 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(); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), + m_depthFrameBuffer, + m_shadowQualityMultiplier); + if (!m_depthTexture) + lowerShadowQuality(); + } } } -#else + void Scatter3DRenderer::initPointShader() { - if (m_pointShader) - delete m_pointShader; - m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPointES2"), - QStringLiteral(":/shaders/fragmentPlainColor")); - m_pointShader->initialize(); + if (m_isOpenGLES) { + if (m_pointShader) + delete m_pointShader; + m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexPointES2"), + QStringLiteral(":/shaders/fragmentPlainColor")); + m_pointShader->initialize(); + } } -#endif void Scatter3DRenderer::initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader) @@ -2099,12 +2407,13 @@ void Scatter3DRenderer::initBackgroundShaders(const QString &vertexShader, m_backgroundShader->initialize(); } -void Scatter3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) +void Scatter3DRenderer::initStaticPointShaders(const QString &vertexShader, + const QString &fragmentShader) { - if (m_labelShader) - delete m_labelShader; - m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); - m_labelShader->initialize(); + if (m_staticGradientPointShader) + delete m_staticGradientPointShader; + m_staticGradientPointShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_staticGradientPointShader->initialize(); } void Scatter3DRenderer::selectionColorToSeriesAndIndex(const QVector4D &color, @@ -2190,13 +2499,17 @@ QVector3D Scatter3DRenderer::convertPositionToTranslation(const QVector3D &posit float yTrans = 0.0f; float zTrans = 0.0f; if (!isAbsolute) { - xTrans = m_axisCacheX.positionAt(position.x()); + 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()); - zTrans = m_axisCacheZ.positionAt(position.z()); } else { - xTrans = position.x() * m_axisCacheX.scale() / 2.0f; - yTrans = position.y(); - zTrans = position.z() * m_axisCacheZ.scale() / 2.0f; + xTrans = position.x() * m_scaleX; + yTrans = position.y() * m_scaleY; + zTrans = position.z() * -m_scaleZ; } return QVector3D(xTrans, yTrans, zTrans); } diff --git a/src/datavisualization/engine/scatter3drenderer_p.h b/src/datavisualization/engine/scatter3drenderer_p.h index 7f213179..b45b31a2 100644 --- a/src/datavisualization/engine/scatter3drenderer_p.h +++ b/src/datavisualization/engine/scatter3drenderer_p.h @@ -53,31 +53,28 @@ private: bool m_updateLabels; ShaderHelper *m_dotShader; ShaderHelper *m_dotGradientShader; -#if defined(QT_OPENGL_ES_2) + ShaderHelper *m_staticSelectedItemGradientShader; + ShaderHelper *m_staticSelectedItemShader; ShaderHelper *m_pointShader; -#endif ShaderHelper *m_depthShader; ShaderHelper *m_selectionShader; ShaderHelper *m_backgroundShader; - ShaderHelper *m_labelShader; + ShaderHelper *m_staticGradientPointShader; GLuint m_bgrTexture; - GLuint m_depthTexture; GLuint m_selectionTexture; GLuint m_depthFrameBuffer; GLuint m_selectionFrameBuffer; GLuint m_selectionDepthBuffer; GLfloat m_shadowQualityToShader; GLint m_shadowQualityMultiplier; - GLfloat m_heightNormalizer; - GLfloat m_scaleFactor; + float m_scaleX; + float m_scaleY; + float m_scaleZ; int m_selectedItemIndex; ScatterSeriesRenderCache *m_selectedSeriesCache; ScatterSeriesRenderCache *m_oldSelectedSeriesCache; - QSizeF m_areaSize; GLfloat m_dotSizeScale; - bool m_hasHeightAdjustmentChanged; ScatterRenderItem m_dummyRenderItem; - GLfloat m_backgroundMargin; GLfloat m_maxItemSize; int m_clickedIndex; bool m_havePointSeries; @@ -94,6 +91,12 @@ public: SeriesRenderCache *createNewCache(QAbstract3DSeries *series); void updateItems(const QVector<Scatter3DController::ChangeItem> &items); void updateScene(Q3DScene *scene); + void updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels); + void updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, + bool visible); + void updateOptimizationHint(QAbstract3DGraph::OptimizationHints hint); + void updateMargin(float margin); QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute); @@ -107,10 +110,16 @@ public slots: protected: virtual void initializeOpenGL(); + virtual void fixCameraTarget(QVector3D &target); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds); private: virtual void initShaders(const QString &vertexShader, const QString &fragmentShader); virtual void initGradientShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initStaticSelectedItemShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &gradientVertexShader, + const QString &gradientFragmentShader); virtual void updateShadowQuality(QAbstract3DGraph::ShadowQuality quality); virtual void updateTextures(); virtual void fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh mesh); @@ -122,14 +131,11 @@ private: void loadBackgroundMesh(); void initSelectionShader(); void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader); - void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); + void initStaticPointShaders(const QString &vertexShader, const QString &fragmentShader); void initSelectionBuffer(); -#if !defined(QT_OPENGL_ES_2) void initDepthShader(); void updateDepthBuffer(); -#else void initPointShader(); -#endif void calculateTranslation(ScatterRenderItem &item); void calculateSceneScalingFactors(); diff --git a/src/datavisualization/engine/scatterseriesrendercache.cpp b/src/datavisualization/engine/scatterseriesrendercache.cpp index e8888d19..4930dde1 100644 --- a/src/datavisualization/engine/scatterseriesrendercache.cpp +++ b/src/datavisualization/engine/scatterseriesrendercache.cpp @@ -27,10 +27,12 @@ ScatterSeriesRenderCache::ScatterSeriesRenderCache(QAbstract3DSeries *series, : SeriesRenderCache(series, renderer), m_itemSize(0.0f), m_selectionIndexOffset(0), + m_staticBufferDirty(false), m_oldRenderArraySize(0), m_oldMeshFileName(QString()), m_scatterBufferObj(0), - m_scatterBufferPoints(0) + m_scatterBufferPoints(0), + m_visibilityChanged(false) { } diff --git a/src/datavisualization/engine/scatterseriesrendercache_p.h b/src/datavisualization/engine/scatterseriesrendercache_p.h index 490e21fb..9c6e8e8f 100644 --- a/src/datavisualization/engine/scatterseriesrendercache_p.h +++ b/src/datavisualization/engine/scatterseriesrendercache_p.h @@ -53,6 +53,8 @@ public: inline float itemSize() const { return m_itemSize; } inline void setSelectionIndexOffset(int offset) { m_selectionIndexOffset = offset; } inline int selectionIndexOffset() const { return m_selectionIndexOffset; } + inline void setStaticBufferDirty(bool state) { m_staticBufferDirty = state; } + inline bool staticBufferDirty() const { return m_staticBufferDirty; } inline int oldArraySize() const { return m_oldRenderArraySize; } inline void setOldArraySize(int size) { m_oldRenderArraySize = size; } inline const QString &oldMeshFileName() const { return m_oldMeshFileName; } @@ -61,15 +63,23 @@ public: inline ScatterObjectBufferHelper *bufferObject() const { return m_scatterBufferObj; } inline void setBufferPoints(ScatterPointBufferHelper *object) { m_scatterBufferPoints = object; } inline ScatterPointBufferHelper *bufferPoints() const { return m_scatterBufferPoints; } + inline QVector<int> &updateIndices() { return m_updateIndices; } + inline QVector<int> &bufferIndices() { return m_bufferIndices; } + inline void setVisibilityChanged(bool changed) { m_visibilityChanged = changed; } + inline bool visibilityChanged() const { return m_visibilityChanged; } protected: ScatterRenderItemArray m_renderArray; float m_itemSize; int m_selectionIndexOffset; // Temporarily cached value for selection color calculations + bool m_staticBufferDirty; int m_oldRenderArraySize; // Used to detect if full buffer change needed QString m_oldMeshFileName; // Used to detect if full buffer change needed ScatterObjectBufferHelper *m_scatterBufferObj; ScatterPointBufferHelper *m_scatterBufferPoints; + QVector<int> m_updateIndices; // Used as temporary cache during item updates + QVector<int> m_bufferIndices; // Cache for mapping renderarray to mesh buffer + bool m_visibilityChanged; // Used to detect if full buffer change needed }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/selectionpointer.cpp b/src/datavisualization/engine/selectionpointer.cpp index 183d3f8e..b57ec511 100644 --- a/src/datavisualization/engine/selectionpointer.cpp +++ b/src/datavisualization/engine/selectionpointer.cpp @@ -122,9 +122,6 @@ void SelectionPointer::render(GLuint defaultFboHandle, bool useOrtho) MVPMatrix = projectionMatrix * viewMatrix * modelMatrix; - // Enable texturing - glEnable(GL_TEXTURE_2D); - QVector3D lightPos = m_cachedScene->activeLight()->position(); // @@ -186,9 +183,6 @@ void SelectionPointer::render(GLuint defaultFboHandle, bool useOrtho) // Release shader glUseProgram(0); - // Disable textures - glDisable(GL_TEXTURE_2D); - // Disable transparency glDisable(GL_BLEND); @@ -217,10 +211,12 @@ void SelectionPointer::setRotation(const QQuaternion &rotation) m_rotation = rotation; } -void SelectionPointer::setLabel(const QString &label) +void SelectionPointer::setLabel(const QString &label, bool themeChange) { - m_label = label; - m_drawer->generateLabelItem(m_labelItem, m_label); + if (themeChange || m_label != label) { + m_label = label; + m_drawer->generateLabelItem(m_labelItem, m_label); + } } void SelectionPointer::setPointerObject(ObjectHelper *object) @@ -236,7 +232,7 @@ void SelectionPointer::setLabelObject(ObjectHelper *object) void SelectionPointer::handleDrawerChange() { m_cachedTheme = m_drawer->theme(); - setLabel(m_label); + setLabel(m_label, true); } void SelectionPointer::updateBoundingRect(const QRect &rect) @@ -256,15 +252,16 @@ void SelectionPointer::initShaders() // The shader for the small point ball if (m_pointShader) delete m_pointShader; -#if !defined(QT_OPENGL_ES_2) - m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragment")); -#else - m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentES2")); -#endif - m_pointShader->initialize(); + if (Utils::isOpenGLES()) { + m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragmentES2")); + } else { + m_pointShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), + QStringLiteral(":/shaders/fragment")); + } + + m_pointShader->initialize(); } QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/selectionpointer_p.h b/src/datavisualization/engine/selectionpointer_p.h index 7dc28024..08382b9f 100644 --- a/src/datavisualization/engine/selectionpointer_p.h +++ b/src/datavisualization/engine/selectionpointer_p.h @@ -49,7 +49,7 @@ public: void render(GLuint defaultFboHandle = 0, bool useOrtho = false); void setPosition(const QVector3D &position); - void setLabel(const QString &label); + void setLabel(const QString &label, bool themeChange = false); void setPointerObject(ObjectHelper *object); void setLabelObject(ObjectHelper *object); void handleDrawerChange(); diff --git a/src/datavisualization/engine/seriesrendercache.cpp b/src/datavisualization/engine/seriesrendercache.cpp index dc4b9db3..77af31c7 100644 --- a/src/datavisualization/engine/seriesrendercache.cpp +++ b/src/datavisualization/engine/seriesrendercache.cpp @@ -37,7 +37,8 @@ SeriesRenderCache::SeriesRenderCache(QAbstract3DSeries *series, Abstract3DRender m_valid(false), m_visible(false), m_renderer(renderer), - m_objectDirty(true) + m_objectDirty(true), + m_staticObjectUVDirty(false) { } @@ -91,9 +92,8 @@ void SeriesRenderCache::populate(bool newSeries) meshFileName = QStringLiteral(":/defaultMeshes/arrow"); break; case QAbstract3DSeries::MeshPoint: -#if defined(QT_OPENGL_ES_2) - qWarning("QAbstract3DSeries::MeshPoint is not fully supported on OpenGL ES2"); -#endif + if (Utils::isOpenGLES()) + qWarning("QAbstract3DSeries::MeshPoint is not fully supported on OpenGL ES2"); break; default: // Default to cube diff --git a/src/datavisualization/engine/seriesrendercache_p.h b/src/datavisualization/engine/seriesrendercache_p.h index 96b61b87..5047d671 100644 --- a/src/datavisualization/engine/seriesrendercache_p.h +++ b/src/datavisualization/engine/seriesrendercache_p.h @@ -71,6 +71,8 @@ public: inline bool isVisible() const { return m_visible; } inline void setDataDirty(bool state) { m_objectDirty = state; } inline bool dataDirty() const { return m_objectDirty; } + inline void setStaticObjectUVDirty(bool state) { m_staticObjectUVDirty = state; } + inline bool staticObjectUVDirty() { return m_staticObjectUVDirty; } protected: QAbstract3DSeries *m_series; @@ -94,6 +96,7 @@ protected: bool m_visible; Abstract3DRenderer *m_renderer; bool m_objectDirty; + bool m_staticObjectUVDirty; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/engine/shaders/3dsliceframes.frag b/src/datavisualization/engine/shaders/3dsliceframes.frag new file mode 100644 index 00000000..44c080dc --- /dev/null +++ b/src/datavisualization/engine/shaders/3dsliceframes.frag @@ -0,0 +1,15 @@ +#version 120 + +uniform highp vec4 color_mdl; +uniform highp vec2 sliceFrameWidth; + +varying highp vec3 pos; + +void main() { + highp vec2 absPos = min(vec2(1.0, 1.0), abs(pos.xy)); + if (absPos.x > sliceFrameWidth.x || absPos.y > sliceFrameWidth.y) + gl_FragColor = color_mdl; + else + discard; +} + diff --git a/src/datavisualization/engine/shaders/default.frag b/src/datavisualization/engine/shaders/default.frag index d16055a3..0276ed95 100644 --- a/src/datavisualization/engine/shaders/default.frag +++ b/src/datavisualization/engine/shaders/default.frag @@ -32,6 +32,7 @@ void main() { materialAmbientColor + materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance + materialSpecularColor * lightStrength * pow(cosAlpha, 5) / distance; - gl_FragColor.a = 1.0; + gl_FragColor.a = color_mdl.a; + gl_FragColor = clamp(gl_FragColor, 0.0, 1.0); } diff --git a/src/datavisualization/engine/shaders/defaultNoMatrices.vert b/src/datavisualization/engine/shaders/defaultNoMatrices.vert new file mode 100644 index 00000000..cef10c19 --- /dev/null +++ b/src/datavisualization/engine/shaders/defaultNoMatrices.vert @@ -0,0 +1,26 @@ +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; +attribute highp vec3 vertexNormal_mdl; + +uniform highp mat4 MVP; +uniform highp mat4 V; +uniform highp vec3 lightPosition_wrld; + +varying highp vec3 lightPosition_wrld_frag; +varying highp vec3 position_wrld; +varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec2 coords_mdl; + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + coords_mdl = vertexPosition_mdl.xy; + position_wrld = vertexPosition_mdl; + vec3 vertexPosition_cmr = vec4(V * vec4(vertexPosition_mdl, 1.0)).xyz; + eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr; + vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz; + lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr; + normal_cmr = vec4(V * vec4(vertexNormal_mdl, 0.0)).xyz; + lightPosition_wrld_frag = lightPosition_wrld; +} diff --git a/src/datavisualization/engine/shaders/default_ES2.frag b/src/datavisualization/engine/shaders/default_ES2.frag index 73d66d5b..60fa3c43 100644 --- a/src/datavisualization/engine/shaders/default_ES2.frag +++ b/src/datavisualization/engine/shaders/default_ES2.frag @@ -34,6 +34,6 @@ void main() { materialAmbientColor + materialDiffuseColor * lightStrength * (cosTheta * cosTheta) / distance + materialSpecularColor * lightStrength * (cosAlpha * cosAlpha * cosAlpha * cosAlpha * cosAlpha) / distance; - gl_FragColor.a = 1.0; + gl_FragColor.a = color_mdl.a; } diff --git a/src/datavisualization/engine/shaders/point_ES2_UV.vert b/src/datavisualization/engine/shaders/point_ES2_UV.vert new file mode 100644 index 00000000..f181db4f --- /dev/null +++ b/src/datavisualization/engine/shaders/point_ES2_UV.vert @@ -0,0 +1,12 @@ +uniform highp mat4 MVP; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; + +varying highp vec2 UV; + +void main() { + gl_PointSize = 5.0; + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + UV = vertexUV; +} diff --git a/src/datavisualization/engine/shaders/position.vert b/src/datavisualization/engine/shaders/position.vert new file mode 100644 index 00000000..34849eae --- /dev/null +++ b/src/datavisualization/engine/shaders/position.vert @@ -0,0 +1,10 @@ +uniform highp mat4 MVP; + +attribute highp vec3 vertexPosition_mdl; + +varying highp vec3 pos; + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + pos = vertexPosition_mdl; +} diff --git a/src/datavisualization/engine/shaders/positionmap.frag b/src/datavisualization/engine/shaders/positionmap.frag new file mode 100644 index 00000000..9a277ab8 --- /dev/null +++ b/src/datavisualization/engine/shaders/positionmap.frag @@ -0,0 +1,11 @@ +varying highp vec3 pos; + +void main() { + // This shader encodes the axis position into the vertex color, assuming the object + // is a cube filling the entire data area of the graph + gl_FragColor = vec4((pos.x + 1.0) / 2.0, + (pos.y + 1.0) / 2.0, + (-pos.z + 1.0) / 2.0, + 0.0); +} + diff --git a/src/datavisualization/engine/shaders/shadowNoMatrices.vert b/src/datavisualization/engine/shaders/shadowNoMatrices.vert new file mode 100644 index 00000000..31748503 --- /dev/null +++ b/src/datavisualization/engine/shaders/shadowNoMatrices.vert @@ -0,0 +1,36 @@ +#version 120 + +uniform highp mat4 MVP; +uniform highp mat4 V; +uniform highp mat4 M; +uniform highp mat4 depthMVP; +uniform highp vec3 lightPosition_wrld; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec3 vertexNormal_mdl; +attribute highp vec2 vertexUV; + +varying highp vec2 UV; +varying highp vec3 position_wrld; +varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec4 shadowCoord; +varying highp vec2 coords_mdl; + +const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.0, + 0.5, 0.5, 0.5, 1.0); + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + coords_mdl = vertexPosition_mdl.xy; + shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0); + position_wrld = vertexPosition_mdl; + vec3 vertexPosition_cmr = vec4(V * vec4(vertexPosition_mdl, 1.0)).xyz; + eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr; + lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz; + normal_cmr = vec4(V * vec4(vertexNormal_mdl, 0.0)).xyz; + UV = vertexUV; +} diff --git a/src/datavisualization/engine/shaders/shadowNoTex.frag b/src/datavisualization/engine/shaders/shadowNoTex.frag index b2e7adfc..84e2f209 100644 --- a/src/datavisualization/engine/shaders/shadowNoTex.frag +++ b/src/datavisualization/engine/shaders/shadowNoTex.frag @@ -61,6 +61,6 @@ void main() { (materialAmbientColor + materialDiffuseColor * lightStrength * cosTheta + materialSpecularColor * lightStrength * pow(cosAlpha, 10)); - gl_FragColor.a = 1.0; + gl_FragColor.a = color_mdl.a; gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0); } diff --git a/src/datavisualization/engine/shaders/surface.frag b/src/datavisualization/engine/shaders/surface.frag index f17dd73e..238e5fed 100644 --- a/src/datavisualization/engine/shaders/surface.frag +++ b/src/datavisualization/engine/shaders/surface.frag @@ -11,9 +11,11 @@ uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; diff --git a/src/datavisualization/engine/shaders/surfaceFlat.frag b/src/datavisualization/engine/shaders/surfaceFlat.frag index 748fb3dd..1a0bbdeb 100644 --- a/src/datavisualization/engine/shaders/surfaceFlat.frag +++ b/src/datavisualization/engine/shaders/surfaceFlat.frag @@ -13,9 +13,11 @@ uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; diff --git a/src/datavisualization/engine/shaders/surfaceFlat.vert b/src/datavisualization/engine/shaders/surfaceFlat.vert index 102bea78..0f953f4c 100644 --- a/src/datavisualization/engine/shaders/surfaceFlat.vert +++ b/src/datavisualization/engine/shaders/surfaceFlat.vert @@ -4,6 +4,7 @@ attribute highp vec3 vertexPosition_mdl; attribute highp vec3 vertexNormal_mdl; +attribute highp vec2 vertexUV; uniform highp mat4 MVP; uniform highp mat4 V; @@ -11,6 +12,7 @@ uniform highp mat4 M; uniform highp mat4 itM; uniform highp vec3 lightPosition_wrld; +varying highp vec2 UV; varying highp vec3 position_wrld; flat varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; @@ -26,4 +28,5 @@ void main() { vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz; lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr; normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz; + UV = vertexUV; } diff --git a/src/datavisualization/engine/shaders/surfaceShadowFlat.frag b/src/datavisualization/engine/shaders/surfaceShadowFlat.frag index 0613a40c..7eaba7f5 100644 --- a/src/datavisualization/engine/shaders/surfaceShadowFlat.frag +++ b/src/datavisualization/engine/shaders/surfaceShadowFlat.frag @@ -2,20 +2,22 @@ #extension GL_EXT_gpu_shader4 : require -varying highp vec3 coords_mdl; +varying highp vec2 coords_mdl; varying highp vec3 position_wrld; flat varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; varying highp vec3 lightDirection_cmr; +varying highp vec4 shadowCoord; uniform highp sampler2DShadow shadowMap; uniform sampler2D textureSampler; -varying highp vec4 shadowCoord; uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp float shadowQuality; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725), @@ -35,7 +37,7 @@ highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.14383161, -0.14100790)); void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; @@ -49,7 +51,7 @@ void main() { highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); highp float bias = 0.005 * tan(acos(cosTheta)); - bias = clamp(bias, 0.0, 0.01); + bias = clamp(bias, 0.001, 0.01); vec4 shadCoords = shadowCoord; shadCoords.z -= bias; diff --git a/src/datavisualization/engine/shaders/surfaceShadowFlat.vert b/src/datavisualization/engine/shaders/surfaceShadowFlat.vert index 8da7b196..98fdde3f 100644 --- a/src/datavisualization/engine/shaders/surfaceShadowFlat.vert +++ b/src/datavisualization/engine/shaders/surfaceShadowFlat.vert @@ -2,9 +2,6 @@ #extension GL_EXT_gpu_shader4 : require -attribute highp vec3 vertexPosition_mdl; -attribute highp vec3 vertexNormal_mdl; - uniform highp mat4 MVP; uniform highp mat4 V; uniform highp mat4 M; @@ -12,12 +9,17 @@ uniform highp mat4 itM; uniform highp mat4 depthMVP; uniform highp vec3 lightPosition_wrld; +attribute highp vec3 vertexPosition_mdl; +attribute highp vec3 vertexNormal_mdl; +attribute highp vec2 vertexUV; + +varying highp vec2 UV; varying highp vec3 position_wrld; flat varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; varying highp vec3 lightDirection_cmr; varying highp vec4 shadowCoord; -varying highp vec3 coords_mdl; +varying highp vec2 coords_mdl; const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, @@ -26,12 +28,12 @@ const highp mat4 bias = mat4(0.5, 0.0, 0.0, 0.0, void main() { gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); - coords_mdl = vertexPosition_mdl; + coords_mdl = vertexPosition_mdl.xy; shadowCoord = bias * depthMVP * vec4(vertexPosition_mdl, 1.0); position_wrld = vec4(M * vec4(vertexPosition_mdl, 1.0)).xyz; vec3 vertexPosition_cmr = vec4(V * M * vec4(vertexPosition_mdl, 1.0)).xyz; eyeDirection_cmr = vec3(0.0, 0.0, 0.0) - vertexPosition_cmr; - vec3 lightPosition_cmr = vec4(V * vec4(lightPosition_wrld, 1.0)).xyz; - lightDirection_cmr = lightPosition_cmr + eyeDirection_cmr; + lightDirection_cmr = vec4(V * vec4(lightPosition_wrld, 0.0)).xyz; normal_cmr = vec4(V * itM * vec4(vertexNormal_mdl, 0.0)).xyz; + UV = vertexUV; } diff --git a/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag b/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag index 1acf8f69..985214be 100644 --- a/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag +++ b/src/datavisualization/engine/shaders/surfaceShadowNoTex.frag @@ -5,15 +5,17 @@ varying highp vec3 position_wrld; varying highp vec3 normal_cmr; varying highp vec3 eyeDirection_cmr; varying highp vec3 lightDirection_cmr; +varying highp vec4 shadowCoord; uniform highp sampler2DShadow shadowMap; uniform sampler2D textureSampler; -varying highp vec4 shadowCoord; uniform highp vec3 lightPosition_wrld; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp float shadowQuality; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.94558609, -0.76890725), @@ -33,7 +35,7 @@ highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), vec2(0.14383161, -0.14100790)); void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; @@ -47,7 +49,7 @@ void main() { highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); highp float bias = 0.005 * tan(acos(cosTheta)); - bias = clamp(bias, 0.0, 0.01); + bias = clamp(bias, 0.001, 0.01); vec4 shadCoords = shadowCoord; shadCoords.z -= bias; diff --git a/src/datavisualization/engine/shaders/surfaceTexturedFlat.frag b/src/datavisualization/engine/shaders/surfaceTexturedFlat.frag new file mode 100644 index 00000000..7c654e0c --- /dev/null +++ b/src/datavisualization/engine/shaders/surfaceTexturedFlat.frag @@ -0,0 +1,39 @@ +#version 120 + +#extension GL_EXT_gpu_shader4 : require + +varying highp vec3 coords_mdl; +varying highp vec3 position_wrld; +flat varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec2 UV; + +uniform sampler2D textureSampler; +uniform highp vec3 lightPosition_wrld; +uniform highp float lightStrength; +uniform highp float ambientStrength; +uniform highp vec4 lightColor; + +void main() { + highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).xyz; + highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; + highp vec3 materialSpecularColor = lightColor.rgb; + + highp float distance = length(lightPosition_wrld - position_wrld); + + highp vec3 n = normalize(normal_cmr); + highp vec3 l = normalize(lightDirection_cmr); + highp float cosTheta = clamp(dot(n, l), 0.0, 1.0); + + highp vec3 E = normalize(eyeDirection_cmr); + highp vec3 R = reflect(-l, n); + highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); + + gl_FragColor.rgb = + materialAmbientColor + + materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance + + materialSpecularColor * lightStrength * pow(cosAlpha, 10) / distance; + gl_FragColor.a = 1.0; +} + diff --git a/src/datavisualization/engine/shaders/surfaceTexturedShadow.frag b/src/datavisualization/engine/shaders/surfaceTexturedShadow.frag new file mode 100644 index 00000000..a4259565 --- /dev/null +++ b/src/datavisualization/engine/shaders/surfaceTexturedShadow.frag @@ -0,0 +1,67 @@ +#version 120 + +uniform highp float lightStrength; +uniform highp float ambientStrength; +uniform highp float shadowQuality; +uniform highp sampler2D textureSampler; +uniform highp sampler2DShadow shadowMap; +uniform highp vec4 lightColor; + +varying highp vec4 shadowCoord; +varying highp vec2 UV; +varying highp vec3 position_wrld; +varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; + +highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), + vec2(0.94558609, -0.76890725), + vec2(-0.094184101, -0.92938870), + vec2(0.34495938, 0.29387760), + vec2(-0.91588581, 0.45771432), + vec2(-0.81544232, -0.87912464), + vec2(-0.38277543, 0.27676845), + vec2(0.97484398, 0.75648379), + vec2(0.44323325, -0.97511554), + vec2(0.53742981, -0.47373420), + vec2(-0.26496911, -0.41893023), + vec2(0.79197514, 0.19090188), + vec2(-0.24188840, 0.99706507), + vec2(-0.81409955, 0.91437590), + vec2(0.19984126, 0.78641367), + vec2(0.14383161, -0.14100790)); + +void main() { + highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).rgb; + highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; + highp vec3 materialSpecularColor = lightColor.rgb * 0.2; + + highp vec3 n = normalize(normal_cmr); + highp vec3 l = normalize(lightDirection_cmr); + highp float cosTheta = clamp(dot(n, l), 0.0, 1.0); + + highp vec3 E = normalize(eyeDirection_cmr); + highp vec3 R = reflect(-l, n); + highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); + + highp float bias = 0.005 * tan(acos(cosTheta)); + bias = clamp(bias, 0.001, 0.01); + + vec4 shadCoords = shadowCoord; + shadCoords.z -= bias; + + highp float visibility = 0.6; + for (int i = 0; i < 15; i++) { + vec4 shadCoordsPD = shadCoords; + shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality; + shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality; + visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r; + } + + gl_FragColor.rgb = + (materialAmbientColor + + materialDiffuseColor * lightStrength * cosTheta + + materialSpecularColor * lightStrength * pow(cosAlpha, 10)); + gl_FragColor.a = texture2D(textureSampler, UV).a; + gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0); +} diff --git a/src/datavisualization/engine/shaders/surfaceTexturedShadowFlat.frag b/src/datavisualization/engine/shaders/surfaceTexturedShadowFlat.frag new file mode 100644 index 00000000..496f4777 --- /dev/null +++ b/src/datavisualization/engine/shaders/surfaceTexturedShadowFlat.frag @@ -0,0 +1,72 @@ +#version 120 + +#extension GL_EXT_gpu_shader4 : require + +varying highp vec3 coords_mdl; +varying highp vec3 position_wrld; +flat varying highp vec3 normal_cmr; +varying highp vec3 eyeDirection_cmr; +varying highp vec3 lightDirection_cmr; +varying highp vec2 UV; + +uniform highp sampler2DShadow shadowMap; +uniform sampler2D textureSampler; +varying highp vec4 shadowCoord; +uniform highp vec3 lightPosition_wrld; +uniform highp float lightStrength; +uniform highp float ambientStrength; +uniform highp float shadowQuality; +uniform highp vec4 lightColor; + +highp vec2 poissonDisk[16] = vec2[16](vec2(-0.94201624, -0.39906216), + vec2(0.94558609, -0.76890725), + vec2(-0.094184101, -0.92938870), + vec2(0.34495938, 0.29387760), + vec2(-0.91588581, 0.45771432), + vec2(-0.81544232, -0.87912464), + vec2(-0.38277543, 0.27676845), + vec2(0.97484398, 0.75648379), + vec2(0.44323325, -0.97511554), + vec2(0.53742981, -0.47373420), + vec2(-0.26496911, -0.41893023), + vec2(0.79197514, 0.19090188), + vec2(-0.24188840, 0.99706507), + vec2(-0.81409955, 0.91437590), + vec2(0.19984126, 0.78641367), + vec2(0.14383161, -0.14100790)); + +void main() { + highp vec3 materialDiffuseColor = texture2D(textureSampler, UV).xyz; + highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; + highp vec3 materialSpecularColor = lightColor.rgb; + + highp vec3 n = normalize(normal_cmr); + highp vec3 l = normalize(lightDirection_cmr); + highp float cosTheta = clamp(dot(n, l), 0.0, 1.0); + + highp vec3 E = normalize(eyeDirection_cmr); + highp vec3 R = reflect(-l, n); + highp float cosAlpha = clamp(dot(E, R), 0.0, 1.0); + + highp float bias = 0.005 * tan(acos(cosTheta)); + bias = clamp(bias, 0.001, 0.01); + + vec4 shadCoords = shadowCoord; + shadCoords.z -= bias; + + highp float visibility = 0.6; + for (int i = 0; i < 15; i++) { + vec4 shadCoordsPD = shadCoords; + shadCoordsPD.x += cos(poissonDisk[i].x) / shadowQuality; + shadCoordsPD.y += sin(poissonDisk[i].y) / shadowQuality; + visibility += 0.025 * shadow2DProj(shadowMap, shadCoordsPD).r; + } + + gl_FragColor.rgb = + (materialAmbientColor + + materialDiffuseColor * lightStrength * cosTheta + + materialSpecularColor * lightStrength * pow(cosAlpha, 10)); + gl_FragColor.a = 1.0; + gl_FragColor.rgb = visibility * clamp(gl_FragColor.rgb, 0.0, 1.0); +} + diff --git a/src/datavisualization/engine/shaders/surface_ES2.frag b/src/datavisualization/engine/shaders/surface_ES2.frag index 58d13834..1d1bdc3e 100644 --- a/src/datavisualization/engine/shaders/surface_ES2.frag +++ b/src/datavisualization/engine/shaders/surface_ES2.frag @@ -10,9 +10,11 @@ uniform sampler2D textureSampler; uniform highp float lightStrength; uniform highp float ambientStrength; uniform highp vec4 lightColor; +uniform highp float gradMin; +uniform highp float gradHeight; void main() { - highp vec2 gradientUV = vec2(0.0, (coords_mdl.y + 1.0) / 2.0); + highp vec2 gradientUV = vec2(0.0, gradMin + coords_mdl.y * gradHeight); highp vec3 materialDiffuseColor = texture2D(textureSampler, gradientUV).xyz; highp vec3 materialAmbientColor = lightColor.rgb * ambientStrength * materialDiffuseColor; highp vec3 materialSpecularColor = lightColor.rgb; diff --git a/src/datavisualization/engine/shaders/texture3d.frag b/src/datavisualization/engine/shaders/texture3d.frag new file mode 100644 index 00000000..3f9c42ff --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3d.frag @@ -0,0 +1,155 @@ +#version 120 + +varying highp vec3 pos; +varying highp vec3 rayDir; + +uniform highp sampler3D textureSampler; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; +uniform highp vec3 textureDimensions; +uniform highp int sampleCount; // This is the maximum sample count +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; + +// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha. +// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over +// entire volume, regardless of texture dimensions +const highp float alphaThicknesses = 32.0; + +void main() { + vec3 rayStart = pos; + + highp vec3 startBounds = minBounds; + highp vec3 endBounds = maxBounds; + if (rayDir.x < 0.0) { + startBounds.x = maxBounds.x; + endBounds.x = minBounds.x; + } + if (rayDir.y > 0.0) { + startBounds.y = maxBounds.y; + endBounds.y = minBounds.y; + } + if (rayDir.z > 0.0) { + startBounds.z = maxBounds.z; + endBounds.z = minBounds.z; + } + + // Calculate ray intersection endpoint + highp vec3 rayStop; + highp vec3 invRayDir = 1.0 / rayDir; + highp vec3 t = (endBounds - rayStart) * invRayDir; + highp float endT = min(t.x, min(t.y, t.z)); + rayStop = rayStart + endT * rayDir; + if (endT <= 0.0) + discard; + + // Convert intersections to texture coords + rayStart = 0.5 * (rayStart + 1.0); + rayStop = 0.5 * (rayStop + 1.0); + + highp vec3 ray = rayStop - rayStart; + + highp vec3 absRay = abs(ray); + highp vec3 invAbsRay = 1.0 / absRay; + highp float fullDist = length(ray); + highp vec3 curPos = rayStart; + + highp vec4 curColor = vec4(0, 0, 0, 0); + highp float curAlpha = 0.0; + highp float curLen = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + + highp vec4 destColor = vec4(0, 0, 0, 0); + highp float totalOpacity = 1.0; + + highp float extraAlphaMultiplier = fullDist * alphaThicknesses * alphaMultiplier; + + // nextEdges vector indicates the next edges of the texel boundaries along each axis that + // the ray is about to cross. The first edges are offset by a fraction of a texel to + // avoid artifacts from rounding errors later. + highp vec3 nextEdges = vec3(floor(curPos.x / textureDimensions.x) * textureDimensions.x, + floor(curPos.y / textureDimensions.y) * textureDimensions.y, + floor(curPos.z / textureDimensions.z) * textureDimensions.z); + + highp vec3 textureSteps = textureDimensions; + highp vec3 textureOffset = textureDimensions * 0.001; + if (ray.x > 0) { + nextEdges.x += textureDimensions.x + textureOffset.x; + } else { + nextEdges.x -= textureOffset.x; + textureSteps.x = -textureDimensions.x; + } + if (ray.y > 0) { + nextEdges.y += textureDimensions.y + textureOffset.y; + } else { + nextEdges.y -= textureOffset.y; + textureSteps.y = -textureDimensions.y; + } + if (ray.z > 0) { + nextEdges.z += textureDimensions.z + textureOffset.z; + } else { + nextEdges.z -= textureOffset.z; + textureSteps.z = -textureDimensions.z; + } + + // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1 + for (int i = 0; i < sampleCount; i++) { + curColor = texture3D(textureSampler, curPos); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + // Find which dimension has least to go to figure out the next step distance + highp vec3 delta = abs(nextEdges - curPos); + highp vec3 modDelta = delta * invAbsRay; + highp float minDelta = min(modDelta.x, min(modDelta.y, modDelta.z)); + highp float stepSize; + if (minDelta == modDelta.x) { + nextEdges.x += textureSteps.x; + stepSize = delta.x * invAbsRay.x; + } + if (minDelta == modDelta.y) { + nextEdges.y += textureSteps.y; + stepSize = delta.y * invAbsRay.y; + } + if (minDelta == modDelta.z) { + nextEdges.z += textureSteps.z; + stepSize = delta.z * invAbsRay.z; + } + + curPos += stepSize * ray; + curLen += stepSize; + + if (curColor.a >= 0.0) { + if (curColor.a == 1.0 && (preserveOpacity == 1 || alphaMultiplier >= 1.0)) + curAlpha = 1.0; + else + curAlpha = curColor.a * extraAlphaMultiplier * stepSize; + highp float nextOpacity = totalOpacity - curAlpha; + // If opacity goes beyond full opacity, we need to adjust current alpha according + // to the fraction of the distance the material is visible, so that we don't get + // box artifacts around texels. + if (nextOpacity < 0.0) { + curAlpha *= totalOpacity / curAlpha; + nextOpacity = 0.0; + } + curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity); + totalOpacity = nextOpacity; + destColor.rgb += curRgb; + } + + if (curLen >= 1.0 || totalOpacity <= 0.0) + break; + } + + if (totalOpacity == 1.0) + discard; + + // Brighten up the final color if there is some transparency left + if (totalOpacity >= 0.0 && totalOpacity < 1.0) + destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity); + + destColor.a = (1.0 - totalOpacity); + gl_FragColor = clamp(destColor, 0.0, 1.0); +} diff --git a/src/datavisualization/engine/shaders/texture3d.vert b/src/datavisualization/engine/shaders/texture3d.vert new file mode 100644 index 00000000..ef3f1b25 --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3d.vert @@ -0,0 +1,36 @@ +uniform highp mat4 MVP; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; +uniform highp vec3 cameraPositionRelativeToModel; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; +attribute highp vec3 vertexNormal_mdl; + +varying highp vec3 pos; +varying highp vec3 rayDir; + +void main() { + gl_Position = MVP * vec4(vertexPosition_mdl, 1.0); + + highp vec3 minBoundsNorm = minBounds; + highp vec3 maxBoundsNorm = maxBounds; + + // Y and Z are flipped in bounds to be directly usable in texture calculations, + // so flip them back to normal for position calculations + minBoundsNorm.yz = -minBoundsNorm.yz; + maxBoundsNorm.yz = -maxBoundsNorm.yz; + + minBoundsNorm = 0.5 * (minBoundsNorm + 1.0); + maxBoundsNorm = 0.5 * (maxBoundsNorm + 1.0); + + pos = vertexPosition_mdl + + ((1.0 - vertexPosition_mdl) * minBoundsNorm) + - ((1.0 + vertexPosition_mdl) * (1.0 - maxBoundsNorm)); + + rayDir = -(cameraPositionRelativeToModel - pos); + + // Flip Y and Z so QImage bits work directly for texture and first image is in the front + rayDir.yz = -rayDir.yz; + pos.yz = -pos.yz; +} diff --git a/src/datavisualization/engine/shaders/texture3dlowdef.frag b/src/datavisualization/engine/shaders/texture3dlowdef.frag new file mode 100644 index 00000000..ed0d41ce --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3dlowdef.frag @@ -0,0 +1,109 @@ +#version 120 + +varying highp vec3 pos; +varying highp vec3 rayDir; + +uniform highp sampler3D textureSampler; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; +uniform highp vec3 textureDimensions; +uniform highp int sampleCount; // This is the maximum sample count +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; + +// Ray traveling straight through a single 'alpha thickness' applies 100% of the encountered alpha. +// Rays traveling shorter distances apply a fraction. This is used to normalize the alpha over +// entire volume, regardless of texture dimensions +const highp float alphaThicknesses = 32.0; +const highp float SQRT3 = 1.73205081; + +void main() { + vec3 rayStart = pos; + highp vec3 startBounds = minBounds; + highp vec3 endBounds = maxBounds; + if (rayDir.x < 0.0) { + startBounds.x = maxBounds.x; + endBounds.x = minBounds.x; + } + if (rayDir.y > 0.0) { + startBounds.y = maxBounds.y; + endBounds.y = minBounds.y; + } + if (rayDir.z > 0.0) { + startBounds.z = maxBounds.z; + endBounds.z = minBounds.z; + } + + // Calculate ray intersection endpoint + highp vec3 rayStop; + highp vec3 invRayDir = 1.0 / rayDir; + highp vec3 t = (endBounds - rayStart) * invRayDir; + highp float endT = min(t.x, min(t.y, t.z)); + if (endT <= 0.0) + discard; + rayStop = rayStart + endT * rayDir; + + // Convert intersections to texture coords + rayStart = 0.5 * (rayStart + 1.0); + rayStop = 0.5 * (rayStop + 1.0); + + highp vec3 ray = rayStop - rayStart; + + highp float fullDist = length(ray); + highp float stepSize = SQRT3 / sampleCount; + highp vec3 step = (SQRT3 * normalize(ray)) / sampleCount; + + rayStart += (step * 0.001); + + highp vec3 curPos = rayStart; + highp float curLen = 0.0; + highp vec4 curColor = vec4(0, 0, 0, 0); + highp float curAlpha = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + highp vec4 destColor = vec4(0, 0, 0, 0); + highp float totalOpacity = 1.0; + + highp float extraAlphaMultiplier = stepSize * alphaThicknesses * alphaMultiplier; + + // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1 + for (int i = 0; i < sampleCount; i++) { + curColor = texture3D(textureSampler, curPos); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + if (curColor.a >= 0.0) { + if (curColor.a == 1.0 && (preserveOpacity == 1 || alphaMultiplier >= 1.0)) + curAlpha = 1.0; + else + curAlpha = curColor.a * extraAlphaMultiplier; + highp float nextOpacity = totalOpacity - curAlpha; + // If opacity goes beyond full opacity, we need to adjust current alpha according + // to the fraction of the distance the material is visible, so that we don't get + // box artifacts around texels. + if (nextOpacity < 0.0) { + curAlpha *= totalOpacity / curAlpha; + nextOpacity = 0.0; + } + + curRgb = curColor.rgb * curAlpha * (totalOpacity + nextOpacity); + destColor.rgb += curRgb; + totalOpacity = nextOpacity; + } + curPos += step; + curLen += stepSize; + if (curLen >= fullDist || totalOpacity <= 0.0) + break; + } + + if (totalOpacity == 1.0) + discard; + + // Brighten up the final color if there is some transparency left + if (totalOpacity >= 0.0 && totalOpacity < 1.0) + destColor *= (1.0 - (totalOpacity * 0.5)) / (1.0 - totalOpacity); + + destColor.a = (1.0 - totalOpacity); + gl_FragColor = clamp(destColor, 0.0, 1.0); +} diff --git a/src/datavisualization/engine/shaders/texture3dslice.frag b/src/datavisualization/engine/shaders/texture3dslice.frag new file mode 100644 index 00000000..c555af98 --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3dslice.frag @@ -0,0 +1,155 @@ +#version 120 + +varying highp vec3 pos; +varying highp vec3 rayDir; + +uniform highp sampler3D textureSampler; +uniform highp vec3 volumeSliceIndices; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; +uniform highp float alphaMultiplier; +uniform highp int preserveOpacity; +uniform highp vec3 minBounds; +uniform highp vec3 maxBounds; + +const highp vec3 xPlaneNormal = vec3(1.0, 0, 0); +const highp vec3 yPlaneNormal = vec3(0, 1.0, 0); +const highp vec3 zPlaneNormal = vec3(0, 0, 1.0); + +void main() { + // Find out where ray intersects the slice planes + vec3 normRayDir = normalize(rayDir); + highp vec3 rayStart = pos; + highp float minT = 2.0f; + if (normRayDir.x != 0.0 && normRayDir.y != 0.0 && normRayDir.z != 0.0) { + highp vec3 boxBounds = vec3(1.0, 1.0, 1.0); + highp vec3 invRayDir = 1.0 / normRayDir; + if (normRayDir.x < 0) + boxBounds.x = -1.0; + if (normRayDir.y < 0) + boxBounds.y = -1.0; + if (normRayDir.z < 0) + boxBounds.z = -1.0; + highp vec3 t = (boxBounds - rayStart) * invRayDir; + minT = min(t.x, min(t.y, t.z)); + } + + highp vec3 xPoint = vec3(volumeSliceIndices.x, 0, 0); + highp vec3 yPoint = vec3(0, volumeSliceIndices.y, 0); + highp vec3 zPoint = vec3(0, 0, volumeSliceIndices.z); + highp float firstD = minT + 1.0; + highp float secondD = firstD; + highp float thirdD = firstD; + if (volumeSliceIndices.x >= -1.0) { + highp float dx = dot(xPoint - rayStart, xPlaneNormal) / dot(normRayDir, xPlaneNormal); + if (dx >= 0.0 && dx <= minT) + firstD = min(dx, firstD); + } + if (volumeSliceIndices.y >= -1.0) { + highp float dy = dot(yPoint - rayStart, yPlaneNormal) / dot(normRayDir, yPlaneNormal); + if (dy >= 0.0 && dy <= minT) { + if (dy < firstD) { + secondD = firstD; + firstD = dy; + } else { + secondD = dy; + } + } + } + if (volumeSliceIndices.z >= -1.0) { + highp float dz = dot(zPoint - rayStart, zPlaneNormal) / dot(normRayDir, zPlaneNormal); + if (dz >= 0.0) { + if (dz < firstD && dz <= minT) { + thirdD = secondD; + secondD = firstD; + firstD = dz; + } else if (dz < secondD){ + thirdD = secondD; + secondD = dz; + } else { + thirdD = dz; + } + } + } + + highp vec4 destColor = vec4(0.0, 0.0, 0.0, 0.0); + highp vec4 curColor = vec4(0.0, 0.0, 0.0, 0.0); + highp float totalAlpha = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + highp float curAlpha = 0.0; + + // Convert intersection to texture coords + + if (firstD <= minT) { + highp vec3 texelVec = rayStart + normRayDir * firstD; + if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x + && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y + && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) { + texelVec = 0.5 * (texelVec + 1.0); + curColor = texture3D(textureSampler, texelVec); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + if (curColor.a > 0.0) { + curAlpha = curColor.a; + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + destColor.rgb = curColor.rgb * curAlpha; + totalAlpha = curAlpha; + } + } + if (secondD <= minT && totalAlpha < 1.0) { + texelVec = rayStart + normRayDir * secondD; + if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x + && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y + && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) { + texelVec = 0.5 * (texelVec + 1.0); + curColor = texture3D(textureSampler, texelVec); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + if (curColor.a > 0.0) { + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += curAlpha; + } + } + if (thirdD <= minT && totalAlpha < 1.0) { + texelVec = rayStart + normRayDir * thirdD; + if (clamp(texelVec.x, minBounds.x, maxBounds.x) == texelVec.x + && clamp(texelVec.y, maxBounds.y, minBounds.y) == texelVec.y + && clamp(texelVec.z, maxBounds.z, minBounds.z) == texelVec.z) { + texelVec = 0.5 * (texelVec + 1.0); + curColor = texture3D(textureSampler, texelVec); + if (curColor.a > 0.0) { + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + if (curColor.a == 1.0 && preserveOpacity != 0) + curAlpha = 1.0; + else + curAlpha = clamp(curColor.a * alphaMultiplier, 0.0, 1.0); + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += curAlpha; + } + } + } + } + } + + if (totalAlpha == 0.0) + discard; + + // Brighten up the final color if there is some transparency left + if (totalAlpha > 0.0 && totalAlpha < 1.0) + destColor *= 1.0 / totalAlpha; + + destColor.a = totalAlpha; + gl_FragColor = clamp(destColor, 0.0, 1.0); +} + diff --git a/src/datavisualization/engine/surface3dcontroller.cpp b/src/datavisualization/engine/surface3dcontroller.cpp index c03bafd8..a6c086da 100644 --- a/src/datavisualization/engine/surface3dcontroller.cpp +++ b/src/datavisualization/engine/surface3dcontroller.cpp @@ -29,7 +29,8 @@ Surface3DController::Surface3DController(QRect rect, Q3DScene *scene) m_renderer(0), m_selectedPoint(invalidSelectionPosition()), m_selectedSeries(0), - m_flatShadingSupported(true) + m_flatShadingSupported(true), + m_flipHorizontalGrid(false) { // Setting a null axis creates a new default axis according to orientation and graph type. // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual @@ -79,6 +80,17 @@ void Surface3DController::synchDataToRenderer() m_renderer->updateSelectedPoint(m_selectedPoint, m_selectedSeries); m_changeTracker.selectedPointChanged = false; } + + if (m_changeTracker.flipHorizontalGridChanged) { + m_renderer->updateFlipHorizontalGrid(m_flipHorizontalGrid); + m_changeTracker.flipHorizontalGridChanged = false; + } + + if (m_changeTracker.surfaceTextureChanged) { + m_renderer->updateSurfaceTextures(m_changedTextures); + m_changeTracker.surfaceTextureChanged = false; + m_changedTextures.clear(); + } } void Surface3DController::handleAxisAutoAdjustRangeChangedInOrientation( @@ -140,6 +152,9 @@ void Surface3DController::addSeries(QAbstract3DSeries *series) QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series); if (surfaceSeries->selectedPoint() != invalidSelectionPosition()) setSelectedPoint(surfaceSeries->selectedPoint(), surfaceSeries, false); + + if (!surfaceSeries->texture().isNull()) + updateSurfaceTexture(surfaceSeries); } void Surface3DController::removeSeries(QAbstract3DSeries *series) @@ -168,6 +183,21 @@ QList<QSurface3DSeries *> Surface3DController::surfaceSeriesList() return surfaceSeriesList; } +void Surface3DController::setFlipHorizontalGrid(bool flip) +{ + if (m_flipHorizontalGrid != flip) { + m_flipHorizontalGrid = flip; + m_changeTracker.flipHorizontalGridChanged = true; + emit flipHorizontalGridChanged(flip); + emitNeedRender(); + } +} + +bool Surface3DController::flipHorizontalGrid() const +{ + return m_flipHorizontalGrid; +} + void Surface3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode) { // Currently surface only supports row and column modes when also slicing @@ -429,6 +459,16 @@ void Surface3DController::handleRowsRemoved(int startIndex, int count) emitNeedRender(); } +void Surface3DController::updateSurfaceTexture(QSurface3DSeries *series) +{ + m_changeTracker.surfaceTextureChanged = true; + + if (!m_changedTextures.contains(series)) + m_changedTextures.append(series); + + emitNeedRender(); +} + void Surface3DController::adjustAxisRanges() { QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX); diff --git a/src/datavisualization/engine/surface3dcontroller_p.h b/src/datavisualization/engine/surface3dcontroller_p.h index 2be74f35..8bcdea91 100644 --- a/src/datavisualization/engine/surface3dcontroller_p.h +++ b/src/datavisualization/engine/surface3dcontroller_p.h @@ -38,14 +38,18 @@ class Surface3DRenderer; class QSurface3DSeries; struct Surface3DChangeBitField { - bool selectedPointChanged : 1; - bool rowsChanged : 1; - bool itemChanged : 1; + bool selectedPointChanged : 1; + bool rowsChanged : 1; + bool itemChanged : 1; + bool flipHorizontalGridChanged : 1; + bool surfaceTextureChanged : 1; Surface3DChangeBitField() : selectedPointChanged(true), rowsChanged(false), - itemChanged(false) + itemChanged(false), + flipHorizontalGridChanged(true), + surfaceTextureChanged(true) { } }; @@ -73,6 +77,8 @@ private: bool m_flatShadingSupported; QVector<ChangeItem> m_changedItems; QVector<ChangeRow> m_changedRows; + bool m_flipHorizontalGrid; + QVector<QSurface3DSeries *> m_changedTextures; public: explicit Surface3DController(QRect rect, Q3DScene *scene = 0); @@ -101,6 +107,11 @@ public: virtual void removeSeries(QAbstract3DSeries *series); virtual QList<QSurface3DSeries *> surfaceSeriesList(); + void setFlipHorizontalGrid(bool flip); + bool flipHorizontalGrid() const; + + void updateSurfaceTexture(QSurface3DSeries *series); + public slots: void handleArrayReset(); void handleRowsAdded(int startIndex, int count); @@ -113,6 +124,7 @@ public slots: signals: void selectedSeriesChanged(QSurface3DSeries *series); + void flipHorizontalGridChanged(bool flip); private: Q_DISABLE_COPY(Surface3DController) diff --git a/src/datavisualization/engine/surface3drenderer.cpp b/src/datavisualization/engine/surface3drenderer.cpp index 38c8d8fe..37d6b463 100644 --- a/src/datavisualization/engine/surface3drenderer.cpp +++ b/src/datavisualization/engine/surface3drenderer.cpp @@ -30,10 +30,6 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION //#define SHOW_DEPTH_TEXTURE_SCENE -// Margin for background (1.10 make it 10% larger to avoid -// selection ball being drawn inside background) -const GLfloat backgroundMargin = 1.1f; -const GLfloat gridLineWidth = 0.005f; const GLfloat sliceZScale = 0.1f; const GLfloat sliceUnits = 2.5f; const uint greenMultiplier = 256; @@ -47,19 +43,16 @@ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller) 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_labelShader(0), m_heightNormalizer(0.0f), - m_scaleFactor(0.0f), m_scaleX(0.0f), + m_scaleY(0.0f), m_scaleZ(0.0f), - m_scaleXWithBackground(0.0f), - m_scaleZWithBackground(0.0f), - m_depthTexture(0), - m_depthModelTexture(0), m_depthFrameBuffer(0), m_selectionFrameBuffer(0), m_selectionDepthBuffer(0), @@ -68,16 +61,12 @@ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller) m_flatSupported(true), m_selectionActive(false), m_shadowQualityMultiplier(3), - m_hasHeightAdjustmentChanged(true), m_selectedPoint(Surface3DController::invalidSelectionPosition()), m_selectedSeries(0), m_clickedPosition(Surface3DController::invalidSelectionPosition()), m_selectionTexturesDirty(false), m_noShadowTexture(0) { - m_axisCacheY.setScale(2.0f); - m_axisCacheY.setTranslate(-1.0f); - // Check if flat feature is supported ShaderHelper tester(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), QStringLiteral(":/shaders/fragmentSurfaceFlat")); @@ -90,12 +79,13 @@ Surface3DRenderer::Surface3DRenderer(Surface3DController *controller) " Requires at least GLSL version 1.2 with GL_EXT_gpu_shader4 extension."; } - initializeOpenGLFunctions(); initializeOpenGL(); } Surface3DRenderer::~Surface3DRenderer() { + fixContextBeforeDelete(); + if (QOpenGLContext::currentContext()) { m_textureHelper->glDeleteFramebuffers(1, &m_depthFrameBuffer); m_textureHelper->glDeleteRenderbuffers(1, &m_selectionDepthBuffer); @@ -103,7 +93,6 @@ Surface3DRenderer::~Surface3DRenderer() m_textureHelper->deleteTexture(&m_noShadowTexture); m_textureHelper->deleteTexture(&m_depthTexture); - m_textureHelper->deleteTexture(&m_depthModelTexture); m_textureHelper->deleteTexture(&m_selectionResultTexture); } delete m_depthShader; @@ -111,10 +100,11 @@ Surface3DRenderer::~Surface3DRenderer() delete m_selectionShader; delete m_surfaceFlatShader; delete m_surfaceSmoothShader; + delete m_surfaceTexturedSmoothShader; + delete m_surfaceTexturedFlatShader; delete m_surfaceGridShader; delete m_surfaceSliceFlatShader; delete m_surfaceSliceSmoothShader; - delete m_labelShader; } void Surface3DRenderer::initializeOpenGL() @@ -123,25 +113,15 @@ void Surface3DRenderer::initializeOpenGL() // Initialize shaders initSurfaceShaders(); - initLabelShaders(QStringLiteral(":/shaders/vertexLabel"), - QStringLiteral(":/shaders/fragmentLabel")); -#if !defined(QT_OPENGL_ES_2) - // Init depth shader (for shadows). Init in any case, easier to handle shadow activation if done via api. - initDepthShader(); -#endif + if (!m_isOpenGLES) { + initDepthShader(); // For shadows + loadGridLineMesh(); + } // Init selection shader initSelectionShaders(); -#if !(defined QT_OPENGL_ES_2) - // Load grid line mesh - loadGridLineMesh(); -#endif - - // Load label mesh - loadLabelMesh(); - // Resize in case we've missed resize events // Resize calls initSelectionBuffer and initDepthBuffer, so they don't need to be called here handleResize(); @@ -155,6 +135,53 @@ void Surface3DRenderer::initializeOpenGL() 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(); @@ -264,6 +291,33 @@ void Surface3DRenderer::updateSeries(const QList<QAbstract3DSeries *> &seriesLis } } +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()) { + cache->setSurfaceTexture(m_textureHelper->create2DTexture( + series->texture(), true, true, true)); + + if (cache->isFlatShadingEnabled()) + cache->surfaceObject()->coarseUVs(array, cache->dataArray()); + else + cache->surfaceObject()->smoothUVs(array, cache->dataArray()); + } + } + } +} + SeriesRenderCache *Surface3DRenderer::createNewCache(QAbstract3DSeries *series) { m_selectionTexturesDirty = true; @@ -301,10 +355,13 @@ void Surface3DRenderer::updateRows(const QVector<Surface3DController::ChangeRow> srcArray->at(row)->at(j + sampleSpace.x()); } - if (cache->isFlatShadingEnabled()) - cache->surfaceObject()->updateCoarseRow(dstArray, row - sampleSpace.y()); - else - cache->surfaceObject()->updateSmoothRow(dstArray, row - sampleSpace.y()); + 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(); @@ -343,9 +400,9 @@ void Surface3DRenderer::updateItems(const QVector<Surface3DController::ChangeIte (*(dstArray.at(y)))[x] = srcArray->at(point.x())->at(point.y()); if (cache->isFlatShadingEnabled()) - cache->surfaceObject()->updateCoarseItem(dstArray, y, x); + cache->surfaceObject()->updateCoarseItem(dstArray, y, x, m_polarGraph); else - cache->surfaceObject()->updateSmoothItem(dstArray, y, x); + cache->surfaceObject()->updateSmoothItem(dstArray, y, x, m_polarGraph); } if (updateBuffers) cache->surfaceObject()->uploadBuffers(); @@ -538,10 +595,12 @@ void Surface3DRenderer::updateSliceObject(SurfaceSeriesRenderCache *cache, const QRect sliceRect(0, 0, sliceRow->size(), 2); if (sliceRow->size() > 0) { - if (cache->isFlatShadingEnabled()) - cache->sliceSurfaceObject()->setUpData(sliceDataArray, sliceRect, true, flipZX); - else - cache->sliceSurfaceObject()->setUpSmoothData(sliceDataArray, sliceRect, true, flipZX); + if (cache->isFlatShadingEnabled()) { + cache->sliceSurfaceObject()->setUpData(sliceDataArray, sliceRect, true, false, flipZX); + } else { + cache->sliceSurfaceObject()->setUpSmoothData(sliceDataArray, sliceRect, true, false, + flipZX); + } } } @@ -664,15 +723,6 @@ QRect Surface3DRenderer::calculateSampleRect(const QSurfaceDataArray &array) void Surface3DRenderer::updateScene(Q3DScene *scene) { - // Set initial camera position - // X must be 0 for rotation to work - we can use "setCameraRotation" for setting it later - if (m_hasHeightAdjustmentChanged) { - scene->activeCamera()->d_ptr->setBaseOrientation(cameraDistanceVector, zeroVector, - upVector); - // For now this is used just to make things once. Proper use will come - m_hasHeightAdjustmentChanged = false; - } - Abstract3DRenderer::updateScene(scene); if (m_selectionActive @@ -716,6 +766,14 @@ void Surface3DRenderer::render(GLuint defaultFboHandle) 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()); @@ -751,6 +809,19 @@ void Surface3DRenderer::drawSlicedScene() 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); @@ -767,11 +838,6 @@ void Surface3DRenderer::drawSlicedScene() drawGrid = true; } - if (rowMode) - scaleXBackground = m_scaleXWithBackground; - else - scaleXBackground = m_scaleZWithBackground; - QMatrix4x4 MVPMatrix; QMatrix4x4 modelMatrix; QMatrix4x4 itModelMatrix; @@ -790,9 +856,27 @@ void Surface3DRenderer::drawSlicedScene() surfaceShader->bind(); - GLuint colorTexture = cache->baseUniformTexture();; - if (cache->colorStyle() != Q3DTheme::ColorStyleUniform) + 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); @@ -801,9 +885,10 @@ void Surface3DRenderer::drawSlicedScene() surfaceShader->setUniformValue(surfaceShader->nModel(), itModelMatrix.inverted().transposed()); surfaceShader->setUniformValue(surfaceShader->MVP(), MVPMatrix); - surfaceShader->setUniformValue(surfaceShader->lightS(), 0.15f); + surfaceShader->setUniformValue(surfaceShader->lightS(), 0.0f); surfaceShader->setUniformValue(surfaceShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 10.0f); surfaceShader->setUniformValue(surfaceShader->lightColor(), lightColor); m_drawer->drawObject(surfaceShader, cache->sliceSurfaceObject(), colorTexture); @@ -830,19 +915,16 @@ void Surface3DRenderer::drawSlicedScene() } } - // Disable textures - glDisable(GL_TEXTURE_2D); - glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // Grid lines - if (m_cachedTheme->isGridEnabled() && m_heightNormalizer) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + 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(); @@ -853,7 +935,8 @@ void Surface3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->view(), viewMatrix); lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), - m_cachedTheme->ambientLightStrength() * 2.3f); + m_cachedTheme->ambientLightStrength() + + m_cachedTheme->lightStrength() / 10.0f); lineShader->setUniformValue(lineShader->lightS(), 0.0f); lineShader->setUniformValue(lineShader->lightColor(), lightColor); @@ -881,16 +964,15 @@ void Surface3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); } } // Vertical lines - QVector3D gridLineScaleY(gridLineWidth, backgroundMargin, gridLineWidth); + QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth); gridLineCount = sliceCache.gridLineCount(); for (int line = 0; line < gridLineCount; line++) { @@ -901,10 +983,11 @@ void Surface3DRenderer::drawSlicedScene() modelMatrix.translate(sliceCache.gridLinePosition(line), 0.0f, -1.0f); modelMatrix.scale(gridLineScaleY); itModelMatrix.scale(gridLineScaleY); -#if (defined QT_OPENGL_ES_2) - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + + if (m_isOpenGLES) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -915,17 +998,15 @@ void Surface3DRenderer::drawSlicedScene() lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); // Draw the object -#if !(defined QT_OPENGL_ES_2) - m_drawer->drawObject(lineShader, m_gridLineObj); -#else - m_drawer->drawLine(lineShader); -#endif + if (m_isOpenGLES) + m_drawer->drawLine(lineShader); + else + m_drawer->drawObject(lineShader, m_gridLineObj); } } // Draw labels m_labelShader->bind(); - glEnable(GL_TEXTURE_2D); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -959,12 +1040,15 @@ void Surface3DRenderer::drawSlicedScene() labelNbr = 0; positionComp.setY(-0.1f); - labelTrans.setY(-backgroundMargin); + labelTrans.setY(-m_scaleYWithBackground); labelCount = sliceCache.labelCount(); for (int label = 0; label < labelCount; label++) { if (countLabelItems > labelNbr) { // Draw the label here - labelTrans.setX(sliceCache.labelPosition(label)); + if (rowMode) + labelTrans.setX(sliceCache.labelPosition(label)); + else + labelTrans.setX(-sliceCache.labelPosition(label)); m_dummyRenderItem.setTranslation(labelTrans); @@ -998,7 +1082,6 @@ void Surface3DRenderer::drawSlicedScene() m_cachedSelectionMode, m_labelShader, m_labelObj, activeCamera, false, false, Drawer::LabelMid, Qt::AlignBottom); - glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); @@ -1048,6 +1131,21 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) 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 = qreal(activeCamera->yRotation()) / 180.0 * M_PI; + 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; @@ -1065,9 +1163,8 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 depthProjectionViewMatrix; // Draw depth buffer -#if !defined(QT_OPENGL_ES_2) GLfloat adjustedLightStrength = m_cachedTheme->lightStrength() / 10.0f; - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && + 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 @@ -1097,6 +1194,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) / (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) { @@ -1104,45 +1202,9 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) SurfaceObject *object = cache->surfaceObject(); if (object->indexCount() && cache->surfaceVisible() && cache->isVisible() && cache->sampleSpace().width() >= 2 && cache->sampleSpace().height() >= 2) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - - MVPMatrix = depthProjectionViewMatrix * modelMatrix; - cache->setMVPMatrix(MVPMatrix); - m_depthShader->setUniformValue(m_depthShader->MVP(), MVPMatrix); - - // 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(), - object->indicesType(), (void *)0); - } - } - - glEnable(GL_CULL_FACE); - glCullFace(GL_FRONT); - - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - m_depthModelTexture, 0); - glClear(GL_DEPTH_BUFFER_BIT); - - 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) { - m_depthShader->setUniformValue(m_depthShader->MVP(), cache->MVPMatrix()); + // 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()); @@ -1165,9 +1227,13 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) glDisableVertexAttribArray(m_depthShader->posAtt()); + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to depth framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -1182,15 +1248,20 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) glEnable(GL_CULL_FACE); glCullFace(GL_BACK); } -#endif - // Enable texturing - glEnable(GL_TEXTURE_2D); + + // 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_cachedSelectionMode > QAbstract3DGraph::SelectionNone + && m_selectionResultTexture) { m_selectionShader->bind(); glBindFramebuffer(GL_FRAMEBUFFER, m_selectionFrameBuffer); glViewport(0, @@ -1208,18 +1279,17 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) foreach (SeriesRenderCache *baseCache, m_renderCacheList) { SurfaceSeriesRenderCache *cache = static_cast<SurfaceSeriesRenderCache *>(baseCache); if (cache->surfaceObject()->indexCount() && cache->renderable()) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; + m_selectionShader->setUniformValue(m_selectionShader->MVP(), projectionViewMatrix); - MVPMatrix = projectionViewMatrix * modelMatrix; - m_selectionShader->setUniformValue(m_selectionShader->MVP(), MVPMatrix); + cache->surfaceObject()->activateSurfaceTexture(false); m_drawer->drawObject(m_selectionShader, cache->surfaceObject(), cache->selectionTexture()); } } m_surfaceGridShader->bind(); - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader, + viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -1237,6 +1307,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) + uint(clickedColor.w()) * alphaMultiplier; m_clickedPosition = selectionIdToSurfacePoint(selectionId); + m_clickResolved = true; emit needRender(); @@ -1249,7 +1320,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) // Draw the surface if (!m_renderCacheList.isEmpty()) { - // For surface we can see climpses from underneath + // For surface we can see glimpses from underneath glDisable(GL_CULL_FACE); bool drawGrid = false; @@ -1261,9 +1332,9 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 itModelMatrix; #ifdef SHOW_DEPTH_TEXTURE_SCENE - MVPMatrix = depthProjectionViewMatrix * modelMatrix; + MVPMatrix = depthProjectionViewMatrix; #else - MVPMatrix = projectionViewMatrix * modelMatrix; + MVPMatrix = projectionViewMatrix; #endif cache->setMVPMatrix(MVPMatrix); @@ -1279,8 +1350,13 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) if (cache->surfaceVisible()) { ShaderHelper *shader = m_surfaceFlatShader; - if (!cache->isFlatShadingEnabled()) + if (cache->surfaceTexture()) + shader = m_surfaceTexturedFlatShader; + if (!cache->isFlatShadingEnabled()) { shader = m_surfaceSmoothShader; + if (cache->surfaceTexture()) + shader = m_surfaceTexturedSmoothShader; + } shader->bind(); // Set shader bindings @@ -1294,14 +1370,35 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) m_cachedTheme->ambientLightStrength()); shader->setUniformValue(shader->lightColor(), lightColor); - GLuint gradientTexture; - if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) - gradientTexture = cache->baseUniformTexture(); - else - gradientTexture = cache->baseGradientTexture(); + // 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 !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (!m_isOpenGLES && + m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; shader->setUniformValue(shader->shadowQ(), m_shadowQualityToShader); @@ -1309,17 +1406,13 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) shader->setUniformValue(shader->lightS(), adjustedLightStrength); // Draw the objects - m_drawer->drawObject(shader, cache->surfaceObject(), gradientTexture, - m_depthModelTexture); - } else -#endif - { + m_drawer->drawObject(shader, cache->surfaceObject(), texture, + m_depthTexture); + } else { // Set shadowless shader bindings - shader->setUniformValue(shader->lightS(), - m_cachedTheme->lightStrength()); - + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); // Draw the objects - m_drawer->drawObject(shader, cache->surfaceObject(), gradientTexture); + m_drawer->drawObject(shader, cache->surfaceObject(), texture); } } } @@ -1359,12 +1452,12 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QMatrix4x4 MVPMatrix; QMatrix4x4 itModelMatrix; - QVector3D bgScale(m_scaleXWithBackground, backgroundMargin, m_scaleZWithBackground); + 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(180.0f, 1.0f, 0.0f, 0.0f); + 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); @@ -1392,8 +1485,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) m_cachedTheme->ambientLightStrength() * 2.0f); m_backgroundShader->setUniformValue(m_backgroundShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !m_isOpenGLES) { // Set shadow shader bindings QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; m_backgroundShader->setUniformValue(m_backgroundShader->shadowQ(), @@ -1406,11 +1498,7 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_noShadowTexture); else m_drawer->drawObject(m_backgroundShader, m_backgroundObj, 0, m_depthTexture); - } else -#else - Q_UNUSED(noShadows); -#endif - { + } else { // Set shadowless shader bindings m_backgroundShader->setUniformValue(m_backgroundShader->lightS(), m_cachedTheme->lightStrength()); @@ -1423,14 +1511,14 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) // Draw grid lines QVector3D gridLineScaleX(m_scaleXWithBackground, gridLineWidth, gridLineWidth); QVector3D gridLineScaleZ(gridLineWidth, gridLineWidth, m_scaleZWithBackground); - QVector3D gridLineScaleY(gridLineWidth, backgroundMargin, gridLineWidth); + QVector3D gridLineScaleY(gridLineWidth, m_scaleYWithBackground, gridLineWidth); - if (m_cachedTheme->isGridEnabled() && m_heightNormalizer) { -#if !(defined QT_OPENGL_ES_2) - ShaderHelper *lineShader = m_backgroundShader; -#else - ShaderHelper *lineShader = m_selectionShader; // Plain color shader for GL_LINES -#endif + 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(); @@ -1442,15 +1530,12 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) lineShader->setUniformValue(lineShader->color(), lineColor); lineShader->setUniformValue(lineShader->ambientS(), m_cachedTheme->ambientLightStrength()); lineShader->setUniformValue(lineShader->lightColor(), lightColor); -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + 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 -#endif - { + } else { // Set shadowless shader bindings lineShader->setUniformValue(lineShader->lightS(), m_cachedTheme->lightStrength() / 2.5f); @@ -1460,205 +1545,211 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) QQuaternion lineXRotation; if (m_xFlipped) - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, -90.0f); + lineYRotation = m_yRightAngleRotationNeg; else - lineYRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); + lineYRotation = m_yRightAngleRotation; - if (m_yFlipped) - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 90.0f); + if (m_yFlippedForGrid) + lineXRotation = m_xRightAngleRotation; else - lineXRotation = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, -90.0f); + lineXRotation = m_xRightAngleRotationNeg; - GLfloat yFloorLinePosition = -backgroundMargin + gridLineOffset; - if (m_yFlipped) + float yFloorLinePosition = -m_scaleYWithBackground + gridLineOffset; + if (m_yFlipped != m_flipHorizontalGrid) yFloorLinePosition = -yFloorLinePosition; // Rows (= Z) if (m_axisCacheZ.segmentCount() > 0) { - // Floor lines int gridLineCount = m_axisCacheZ.gridLineCount(); - - 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 !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + // Floor lines + 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); + } } -#else - m_drawer->drawLine(lineShader); -#endif - } + // Side wall lines + GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; - // Side wall lines - GLfloat lineXTrans = m_scaleXWithBackground - gridLineOffset; + if (!m_xFlipped) + lineXTrans = -lineXTrans; - if (!m_xFlipped) - lineXTrans = -lineXTrans; + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; - for (int line = 0; line < gridLineCount; line++) { - QMatrix4x4 modelMatrix; - QMatrix4x4 MVPMatrix; - QMatrix4x4 itModelMatrix; - - modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); + modelMatrix.translate(lineXTrans, 0.0f, m_axisCacheZ.gridLinePosition(line)); - modelMatrix.scale(gridLineScaleY); - itModelMatrix.scale(gridLineScaleY); + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); -#if !defined(QT_OPENGL_ES_2) - modelMatrix.rotate(lineYRotation); - itModelMatrix.rotate(lineYRotation); -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif - - 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) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else { + modelMatrix.rotate(lineYRotation); + itModelMatrix.rotate(lineYRotation); + } -#if !defined(QT_OPENGL_ES_2) - 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); + 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); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } // Columns (= X) if (m_axisCacheX.segmentCount() > 0) { -#if defined(QT_OPENGL_ES_2) - lineXRotation = QQuaternion::fromAxisAndAngle(0.0f, 1.0f, 0.0f, 90.0f); -#endif + if (m_isOpenGLES) + lineXRotation = m_yRightAngleRotation; + // Floor lines int gridLineCount = m_axisCacheX.gridLineCount(); - 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 !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - // Set shadow shader bindings - QMatrix4x4 depthMVPMatrix = depthProjectionViewMatrix * modelMatrix; - lineShader->setUniformValue(lineShader->depth(), depthMVPMatrix); - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj, 0, m_depthTexture); - } else { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + if (m_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); + } } -#else - m_drawer->drawLine(lineShader); -#endif - } - // Back wall lines - GLfloat lineZTrans = m_scaleZWithBackground - gridLineOffset; + // 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); + if (!m_zFlipped) + lineZTrans = -lineZTrans; - modelMatrix.scale(gridLineScaleY); - itModelMatrix.scale(gridLineScaleY); + for (int line = 0; line < gridLineCount; line++) { + QMatrix4x4 modelMatrix; + QMatrix4x4 MVPMatrix; + QMatrix4x4 itModelMatrix; -#if !defined(QT_OPENGL_ES_2) - if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - } -#else - modelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); - itModelMatrix.rotate(90.0f, 0.0f, 0.0f, 1.0f); -#endif + modelMatrix.translate(m_axisCacheX.gridLinePosition(line), 0.0f, lineZTrans); - MVPMatrix = projectionViewMatrix * modelMatrix; + modelMatrix.scale(gridLineScaleY); + itModelMatrix.scale(gridLineScaleY); - // 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) { + modelMatrix.rotate(m_zRightAngleRotation); + itModelMatrix.rotate(m_zRightAngleRotation); + } else if (m_zFlipped) { + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); + } -#if !defined(QT_OPENGL_ES_2) - 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); + 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); + } } -#else - m_drawer->drawLine(lineShader); -#endif } } @@ -1683,8 +1774,8 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.scale(gridLineScaleX); if (m_zFlipped) { - modelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); - itModelMatrix.rotate(180.0f, 1.0f, 0.0f, 0.0f); + modelMatrix.rotate(m_xFlipRotation); + itModelMatrix.rotate(m_xFlipRotation); } MVPMatrix = projectionViewMatrix * modelMatrix; @@ -1695,20 +1786,20 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + 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 { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } // Side wall @@ -1738,20 +1829,20 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) itModelMatrix.inverted().transposed()); lineShader->setUniformValue(lineShader->MVP(), MVPMatrix); -#if !defined(QT_OPENGL_ES_2) - 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); + 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 { - // Draw the object - m_drawer->drawObject(lineShader, m_gridLineObj); + m_drawer->drawLine(lineShader); } -#else - m_drawer->drawLine(lineShader); -#endif } } } @@ -1811,7 +1902,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } else { shader = m_labelShader; shader->bind(); - glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -1832,8 +1923,14 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa QVector3D positionZComp(0.0f, 0.0f, 0.0f); if (m_axisCacheZ.segmentCount() > 0) { int labelCount = m_axisCacheZ.labelCount(); - GLfloat labelXTrans = m_scaleXWithBackground + labelMargin; - GLfloat labelYTrans = -backgroundMargin; + 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::AlignmentFlag alignment = (m_xFlipped == m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_xFlipped) @@ -1843,7 +1940,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa if (labelAutoAngle == 0.0f) { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) labelRotation.setY(180.0f); else @@ -1855,7 +1952,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } else { if (m_zFlipped) labelRotation.setY(180.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (labelAutoAngle - fractionCamX) @@ -1920,10 +2017,34 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa 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 - labelTrans.setZ(m_axisCacheZ.labelPosition(label)); + 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); - const LabelItem &axisLabelItem = *m_axisCacheZ.labelItems().at(label); if (drawSelection) { QVector4D labelColor = QVector4D(label / 255.0f, 0.0f, 0.0f, @@ -1938,7 +2059,14 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelsMaxWidth = qMax(labelsMaxWidth, float(axisLabelItem.size().width())); } if (!drawSelection && m_axisCacheZ.isTitleVisible()) { - labelTrans.setZ(0.0f); + 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); } @@ -1952,8 +2080,13 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheX.labelCount(); - GLfloat labelZTrans = m_scaleZWithBackground + labelMargin; - GLfloat labelYTrans = -backgroundMargin; + float labelZTrans = 0.0f; + float labelYTrans = m_flipHorizontalGrid ? m_scaleYWithBackground : -m_scaleYWithBackground; + if (m_polarGraph) + labelYTrans += gridLineOffset + gridLineWidth; + else + labelZTrans = m_scaleZWithBackground + labelMargin; + Qt::AlignmentFlag alignment = (m_xFlipped != m_zFlipped) ? Qt::AlignLeft : Qt::AlignRight; QVector3D labelRotation; if (m_zFlipped) @@ -1964,7 +2097,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation = QVector3D(-90.0f, 90.0f, 0.0f); if (m_xFlipped) labelRotation.setY(-90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_xFlipped) labelRotation.setY(-90.0f); else @@ -1976,7 +2109,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa labelRotation.setY(-90.0f); else labelRotation.setY(90.0f); - if (m_yFlipped) { + if (m_yFlippedForGrid) { if (m_zFlipped) { if (m_xFlipped) { labelRotation.setX(90.0f - (2.0f * labelAutoAngle - fractionCamX) @@ -2022,7 +2155,16 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } } } + 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, @@ -2038,12 +2180,64 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa 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 - labelTrans.setX(m_axisCacheX.labelPosition(label)); - m_dummyRenderItem.setTranslation(labelTrans); + 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 = Qt::AlignmentFlag(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, @@ -2059,8 +2253,20 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } 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); + activeCamera, labelsMaxWidth, viewMatrix, projectionMatrix, shader, + radial); } } // Y Labels @@ -2072,12 +2278,12 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa fractionCamX = activeCamera->xRotation() * labelAngleFraction; int labelCount = m_axisCacheY.labelCount(); - GLfloat labelXTrans = m_scaleXWithBackground; - GLfloat labelZTrans = m_scaleZWithBackground; + float labelXTrans = m_scaleXWithBackground; + float labelZTrans = m_scaleZWithBackground; // Back & side wall - GLfloat labelMarginXTrans = labelMargin; - GLfloat labelMarginZTrans = labelMargin; + 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 = @@ -2134,7 +2340,7 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa float offsetValue = 0.0f; for (int label = startIndex; label != endIndex; label = label + indexStep) { const LabelItem &axisLabelItem = *m_axisCacheY.labelItems().at(label); - const GLfloat labelYTrans = m_axisCacheY.labelPosition(label); + float labelYTrans = m_axisCacheY.labelPosition(label); glPolygonOffset(offsetValue++ / -10.0f, 1.0f); @@ -2144,6 +2350,21 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa 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); @@ -2174,10 +2395,8 @@ void Surface3DRenderer::drawLabels(bool drawSelection, const Q3DCamera *activeCa } glDisable(GL_POLYGON_OFFSET_FILL); - if (!drawSelection) { - glDisable(GL_TEXTURE_2D); + if (!drawSelection) glDisable(GL_BLEND); - } } void Surface3DRenderer::updateSelectionMode(QAbstract3DGraph::SelectionFlags mode) @@ -2205,11 +2424,12 @@ void Surface3DRenderer::updateSelectionTextures() void Surface3DRenderer::createSelectionTexture(SurfaceSeriesRenderCache *cache, uint &lastSelectionId) { - // Create the selection ID image. Each grid corner gets 2x2 pixel area of - // ID color so that each vertex (data point) has 4x4 pixel area of ID color + // 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) * 4; - int idImageHeight = (sampleSpace.height() - 1) * 4; + int idImageWidth = (sampleSpace.width() - 1) * 2; + int idImageHeight = (sampleSpace.height() - 1) * 2; if (idImageHeight <= 0 || idImageWidth <= 0) { cache->setSelectionIdRange(-1, -1); @@ -2221,21 +2441,21 @@ void Surface3DRenderer::createSelectionTexture(SurfaceSeriesRenderCache *cache, uint idStart = lastSelectionId; uchar *bits = new uchar[idImageWidth * idImageHeight * 4 * sizeof(uchar)]; - for (int i = 0; i < idImageHeight; i += 4) { - for (int j = 0; j < idImageWidth; j += 4) { + 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, stride); + fillIdCorner(&bits[p], r, g, b, a); idToRGBA(lastSelectionId + 1, &r, &g, &b, &a); - fillIdCorner(&bits[p + 8], r, g, b, a, stride); + fillIdCorner(&bits[p + 4], r, g, b, a); idToRGBA(lastSelectionId + sampleSpace.width(), &r, &g, &b, &a); - fillIdCorner(&bits[p + 2 * stride], r, g, b, a, stride); + fillIdCorner(&bits[p + stride], r, g, b, a); idToRGBA(lastSelectionId + sampleSpace.width() + 1, &r, &g, &b, &a); - fillIdCorner(&bits[p + 2 * stride + 8], r, g, b, a, stride); + fillIdCorner(&bits[p + stride + 4], r, g, b, a); lastSelectionId++; } @@ -2263,24 +2483,12 @@ void Surface3DRenderer::initSelectionBuffer() m_selectionDepthBuffer); } -void Surface3DRenderer::fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a, int stride) +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; - p[4] = r; - p[5] = g; - p[6] = b; - p[7] = a; - p[stride + 0] = r; - p[stride + 1] = g; - p[stride + 2] = b; - p[stride + 3] = a; - p[stride + 4] = r; - p[stride + 5] = g; - p[stride + 6] = b; - p[stride + 7] = a; } void Surface3DRenderer::idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a) @@ -2293,21 +2501,66 @@ void Surface3DRenderer::idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a 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()); - m_areaSize.setHeight(m_axisCacheZ.max() - m_axisCacheZ.min()); - m_areaSize.setWidth(m_axisCacheX.max() - m_axisCacheX.min()); - m_scaleFactor = qMax(m_areaSize.width(), m_areaSize.height()); - m_scaleX = m_graphAspectRatio * m_areaSize.width() / m_scaleFactor; - m_scaleZ = m_graphAspectRatio * m_areaSize.height() / m_scaleFactor; - m_scaleXWithBackground = m_scaleX + backgroundMargin - 1.0f; - m_scaleZWithBackground = m_scaleZ + backgroundMargin - 1.0f; - - float factorScaler = 2.0f * m_graphAspectRatio / m_scaleFactor; - m_axisCacheX.setScale(factorScaler * m_areaSize.width()); - m_axisCacheZ.setScale(-factorScaler * m_areaSize.height()); - m_axisCacheX.setTranslate(-m_axisCacheX.scale() / 2.0f); - m_axisCacheZ.setTranslate(-m_axisCacheZ.scale() / 2.0f); + + 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) @@ -2326,10 +2579,20 @@ void Surface3DRenderer::updateObjects(SurfaceSeriesRenderCache *cache, bool dime QSurfaceDataArray &dataArray = cache->dataArray(); const QRect &sampleSpace = cache->sampleSpace(); - if (cache->isFlatShadingEnabled()) - cache->surfaceObject()->setUpData(dataArray, sampleSpace, dimensionChanged); - else - cache->surfaceObject()->setUpSmoothData(dataArray, sampleSpace, dimensionChanged); + 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) @@ -2339,6 +2602,11 @@ void Surface3DRenderer::updateSelectedPoint(const QPoint &position, QSurface3DSe m_selectionDirty = true; } +void Surface3DRenderer::updateFlipHorizontalGrid(bool flip) +{ + m_flipHorizontalGrid = flip; +} + void Surface3DRenderer::resetClickedStatus() { m_clickedPosition = Surface3DController::invalidSelectionPosition(); @@ -2539,14 +2807,15 @@ void Surface3DRenderer::updateShadowQuality(QAbstract3DGraph::ShadowQuality qual handleShadowQualityChange(); -#if !defined(QT_OPENGL_ES_2) updateDepthBuffer(); -#endif } void Surface3DRenderer::updateTextures() { - // Do nothing, but required as it is pure virtual on parent + Abstract3DRenderer::updateTextures(); + + if (m_polarGraph) + calculateSceneScalingFactors(); } void Surface3DRenderer::updateSlicingActive(bool isSlicing) @@ -2556,12 +2825,13 @@ void Surface3DRenderer::updateSlicingActive(bool isSlicing) m_cachedIsSlicingActivated = isSlicing; - if (!m_cachedIsSlicingActivated) - initSelectionBuffer(); // We need to re-init selection buffer in case there has been a resize + if (!m_cachedIsSlicingActivated) { + // We need to re-init selection buffer in case there has been a resize + initSelectionBuffer(); + initCursorPositionBuffer(); + } -#if !defined(QT_OPENGL_ES_2) updateDepthBuffer(); // Re-init depth buffer as well -#endif m_selectionDirty = true; @@ -2577,55 +2847,68 @@ void Surface3DRenderer::initShaders(const QString &vertexShader, const QString & Q_UNUSED(vertexShader); Q_UNUSED(fragmentShader); - // draw the shader for the surface according to smooth status, shadow and uniform color - if (m_surfaceFlatShader) - delete m_surfaceFlatShader; - if (m_surfaceSmoothShader) - delete m_surfaceSmoothShader; - if (m_surfaceSliceFlatShader) - delete m_surfaceSliceFlatShader; - if (m_surfaceSliceSmoothShader) - delete m_surfaceSliceSmoothShader; - -#if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexShadow"), - QStringLiteral(":/shaders/fragmentSurfaceShadowNoTex")); - } else { - m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurface")); - } - m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurface")); - if (m_flatSupported) { + 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_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceShadowFlat"), - QStringLiteral(":/shaders/fragmentSurfaceShadowFlat")); + 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 = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), - QStringLiteral(":/shaders/fragmentSurfaceFlat")); + m_surfaceFlatShader = 0; + m_surfaceSliceFlatShader = 0; + m_surfaceTexturedFlatShader = 0; } - m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexSurfaceFlat"), - QStringLiteral(":/shaders/fragmentSurfaceFlat")); } else { - m_surfaceFlatShader = 0; - m_surfaceSliceFlatShader = 0; + 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")); } -#else - m_surfaceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); - m_surfaceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); - m_surfaceSliceSmoothShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); - m_surfaceSliceFlatShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertex"), - QStringLiteral(":/shaders/fragmentSurfaceES2")); -#endif + m_surfaceSmoothShader->initialize(); m_surfaceSliceSmoothShader->initialize(); + m_surfaceTexturedSmoothShader->initialize(); if (m_flatSupported) { m_surfaceFlatShader->initialize(); m_surfaceSliceFlatShader->initialize(); + m_surfaceTexturedFlatShader->initialize(); } } @@ -2660,45 +2943,33 @@ void Surface3DRenderer::initSurfaceShaders() handleShadowQualityChange(); } -void Surface3DRenderer::initLabelShaders(const QString &vertexShader, const QString &fragmentShader) -{ - if (m_labelShader) - delete m_labelShader; - m_labelShader = new ShaderHelper(this, vertexShader, fragmentShader); - m_labelShader->initialize(); -} - -#if !defined(QT_OPENGL_ES_2) void Surface3DRenderer::initDepthShader() { - if (m_depthShader) + if (!m_isOpenGLES) { delete m_depthShader; - m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), - QStringLiteral(":/shaders/fragmentDepth")); - m_depthShader->initialize(); + m_depthShader = new ShaderHelper(this, QStringLiteral(":/shaders/vertexDepth"), + QStringLiteral(":/shaders/fragmentDepth")); + m_depthShader->initialize(); + } } void Surface3DRenderer::updateDepthBuffer() { - m_textureHelper->deleteTexture(&m_depthTexture); - m_textureHelper->deleteTexture(&m_depthModelTexture); + if (!m_isOpenGLES) { + m_textureHelper->deleteTexture(&m_depthTexture); - if (m_primarySubViewport.size().isEmpty()) - return; + if (m_primarySubViewport.size().isEmpty()) + return; - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { - m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), - m_depthFrameBuffer, - m_shadowQualityMultiplier); - m_textureHelper->fillDepthTexture(m_depthTexture, m_primarySubViewport.size(), - m_shadowQualityMultiplier, 1.0f); - m_depthModelTexture = m_textureHelper->createDepthTexture(m_primarySubViewport.size(), - m_shadowQualityMultiplier); - if (!m_depthTexture || !m_depthModelTexture) - lowerShadowQuality(); + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + m_depthTexture = m_textureHelper->createDepthTextureFrameBuffer(m_primarySubViewport.size(), + m_depthFrameBuffer, + m_shadowQualityMultiplier); + if (!m_depthTexture) + lowerShadowQuality(); + } } } -#endif QVector3D Surface3DRenderer::convertPositionToTranslation(const QVector3D &position, bool isAbsolute) @@ -2707,15 +2978,44 @@ QVector3D Surface3DRenderer::convertPositionToTranslation(const QVector3D &posit float yTrans = 0.0f; float zTrans = 0.0f; if (!isAbsolute) { - xTrans = m_axisCacheX.positionAt(position.x()); + 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()); - zTrans = m_axisCacheZ.positionAt(position.z()); } else { xTrans = position.x() * m_scaleX; - yTrans = position.y(); - zTrans = position.z() * m_scaleZ; + 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 diff --git a/src/datavisualization/engine/surface3drenderer_p.h b/src/datavisualization/engine/surface3drenderer_p.h index efa8ff7e..57b6f9e6 100644 --- a/src/datavisualization/engine/surface3drenderer_p.h +++ b/src/datavisualization/engine/surface3drenderer_p.h @@ -51,19 +51,16 @@ private: ShaderHelper *m_backgroundShader; ShaderHelper *m_surfaceFlatShader; ShaderHelper *m_surfaceSmoothShader; + ShaderHelper *m_surfaceTexturedSmoothShader; + ShaderHelper *m_surfaceTexturedFlatShader; ShaderHelper *m_surfaceGridShader; ShaderHelper *m_surfaceSliceFlatShader; ShaderHelper *m_surfaceSliceSmoothShader; ShaderHelper *m_selectionShader; - ShaderHelper *m_labelShader; - GLfloat m_heightNormalizer; - GLfloat m_scaleFactor; - GLfloat m_scaleX; - GLfloat m_scaleZ; - GLfloat m_scaleXWithBackground; - GLfloat m_scaleZWithBackground; - GLuint m_depthTexture; - GLuint m_depthModelTexture; + float m_heightNormalizer; + float m_scaleX; + float m_scaleY; + float m_scaleZ; GLuint m_depthFrameBuffer; GLuint m_selectionFrameBuffer; GLuint m_selectionDepthBuffer; @@ -73,13 +70,12 @@ private: bool m_selectionActive; AbstractRenderItem m_dummyRenderItem; GLint m_shadowQualityMultiplier; - QSizeF m_areaSize; - bool m_hasHeightAdjustmentChanged; QPoint m_selectedPoint; QSurface3DSeries *m_selectedSeries; QPoint m_clickedPosition; bool m_selectionTexturesDirty; GLuint m_noShadowTexture; + bool m_flipHorizontalGrid; public: explicit Surface3DRenderer(Surface3DController *controller); @@ -87,6 +83,7 @@ public: void updateData(); void updateSeries(const QList<QAbstract3DSeries *> &seriesList); + void updateSurfaceTextures(QVector<QSurface3DSeries *> seriesList); SeriesRenderCache *createNewCache(QAbstract3DSeries *series); void cleanCache(SeriesRenderCache *cache); void updateSelectionMode(QAbstract3DGraph::SelectionFlags mode); @@ -95,14 +92,22 @@ public: void updateScene(Q3DScene *scene); void updateSlicingActive(bool isSlicing); void updateSelectedPoint(const QPoint &position, QSurface3DSeries *series); + void updateFlipHorizontalGrid(bool flip); inline QPoint clickedPosition() const { return m_clickedPosition; } void resetClickedStatus(); QVector3D convertPositionToTranslation(const QVector3D &position, bool isAbsolute); + void updateAxisLabels(QAbstract3DAxis::AxisOrientation orientation, + const QStringList &labels); + void updateAxisTitleVisibility(QAbstract3DAxis::AxisOrientation orientation, + bool visible); + void updateMargin(float margin); void render(GLuint defaultFboHandle = 0); protected: void initializeOpenGL(); + virtual void fixCameraTarget(QVector3D &target); + virtual void getVisibleItemBounds(QVector3D &minBounds, QVector3D &maxBounds); signals: void flatShadingSupportedChanged(bool supported); @@ -128,7 +133,6 @@ private: void calculateSceneScalingFactors(); void initBackgroundShaders(const QString &vertexShader, const QString &fragmentShader); - void initLabelShaders(const QString &vertexShader, const QString &fragmentShader); void initSelectionShaders(); void initSurfaceShaders(); void initSelectionBuffer(); @@ -136,13 +140,11 @@ private: void updateSelectionTextures(); void createSelectionTexture(SurfaceSeriesRenderCache *cache, uint &lastSelectionId); void idToRGBA(uint id, uchar *r, uchar *g, uchar *b, uchar *a); - void fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a, int stride); + void fillIdCorner(uchar *p, uchar r, uchar g, uchar b, uchar a); void surfacePointSelected(const QPoint &point); void updateSelectionPoint(SurfaceSeriesRenderCache *cache, const QPoint &point, bool label); QPoint selectionIdToSurfacePoint(uint id); -#if !defined(QT_OPENGL_ES_2) void updateDepthBuffer(); -#endif void emitSelectedPointChanged(QPoint position); Q_DISABLE_COPY(Surface3DRenderer) diff --git a/src/datavisualization/engine/surfaceseriesrendercache.cpp b/src/datavisualization/engine/surfaceseriesrendercache.cpp index 1cce5288..f1ad752a 100644 --- a/src/datavisualization/engine/surfaceseriesrendercache.cpp +++ b/src/datavisualization/engine/surfaceseriesrendercache.cpp @@ -39,7 +39,8 @@ SurfaceSeriesRenderCache::SurfaceSeriesRenderCache(QAbstract3DSeries *series, m_sliceSelectionPointer(0), m_mainSelectionPointer(0), m_slicePointerActive(false), - m_mainPointerActive(false) + m_mainPointerActive(false), + m_surfaceTexture(0) { } @@ -62,8 +63,10 @@ void SurfaceSeriesRenderCache::populate(bool newSeries) void SurfaceSeriesRenderCache::cleanup(TextureHelper *texHelper) { - if (QOpenGLContext::currentContext()) + if (QOpenGLContext::currentContext()) { texHelper->deleteTexture(&m_selectionTexture); + texHelper->deleteTexture(&m_surfaceTexture); + } delete m_surfaceObj; delete m_sliceSurfaceObj; diff --git a/src/datavisualization/engine/surfaceseriesrendercache_p.h b/src/datavisualization/engine/surfaceseriesrendercache_p.h index b6254a75..4d04149f 100644 --- a/src/datavisualization/engine/surfaceseriesrendercache_p.h +++ b/src/datavisualization/engine/surfaceseriesrendercache_p.h @@ -85,6 +85,8 @@ public: inline bool slicePointerActive() const { return m_slicePointerActive; } inline void setMainPointerActivity(bool activity) { m_mainPointerActive = activity; } inline bool mainPointerActive() const { return m_mainPointerActive; } + inline void setSurfaceTexture(GLuint texture) { m_surfaceTexture = texture; } + inline GLuint surfaceTexture() const { return m_surfaceTexture; } protected: bool m_surfaceVisible; @@ -105,6 +107,7 @@ protected: SelectionPointer *m_mainSelectionPointer; bool m_slicePointerActive; bool m_mainPointerActive; + GLuint m_surfaceTexture; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/global/datavisualizationglobal_p.h b/src/datavisualization/global/datavisualizationglobal_p.h index abdac998..16bf7c6d 100644 --- a/src/datavisualization/global/datavisualizationglobal_p.h +++ b/src/datavisualization/global/datavisualizationglobal_p.h @@ -52,6 +52,7 @@ static const float gridLineOffset = 0.0035f; // Offset for lifting grid lines of // y position is added to the minimum height (or can be thought to be that much above or below the camera) static const QVector3D defaultLightPos = QVector3D(0.0f, 0.5f, 0.0f); static const QVector3D zeroVector = QVector3D(0.0f, 0.0f, 0.0f); +static const QVector3D oneVector = QVector3D(1.0f, 1.0f, 1.0f); static const QVector3D upVector = QVector3D(0.0f, 1.0f, 0.0f); static const QVector3D cameraDistanceVector = QVector3D(0.0f, 0.0f, cameraDistance); static const QQuaternion identityQuaternion; @@ -69,6 +70,7 @@ static const GLfloat gradientTextureWidth = 2.0f; static const GLfloat uniformTextureHeight = 64.0f; static const GLfloat uniformTextureWidth = 2.0f; static const GLfloat labelMargin = 0.05f; +static const GLfloat gridLineWidth = 0.005f; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/global/global.pri b/src/datavisualization/global/global.pri index 00a3eb65..a43ffb20 100644 --- a/src/datavisualization/global/global.pri +++ b/src/datavisualization/global/global.pri @@ -1,3 +1,5 @@ HEADERS += \ $$PWD/qdatavisualizationglobal.h \ $$PWD/datavisualizationglobal_p.h + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/global/qdatavisualizationglobal.h b/src/datavisualization/global/qdatavisualizationglobal.h index c637f408..a7f96361 100644 --- a/src/datavisualization/global/qdatavisualizationglobal.h +++ b/src/datavisualization/global/qdatavisualizationglobal.h @@ -21,11 +21,11 @@ #include <QtCore/qglobal.h> -#define QT_DATAVISUALIZATION_VERSION_STR "1.1.1" +#define QT_DATAVISUALIZATION_VERSION_STR "1.2.0" /* QT_DATAVISUALIZATION_VERSION is (major << 16) + (minor << 8) + patch. */ -#define QT_DATAVISUALIZATION_VERSION 0x010101 +#define QT_DATAVISUALIZATION_VERSION 0x010200 /* can be used like #if (QT_DATAVISUALIZATION_VERSION >= QT_DATAVISUALIZATION_VERSION_CHECK(1, 0, 0)) */ diff --git a/src/datavisualization/input/input.pri b/src/datavisualization/input/input.pri index 5a4c4a76..f6b9fd6a 100644 --- a/src/datavisualization/input/input.pri +++ b/src/datavisualization/input/input.pri @@ -10,3 +10,5 @@ SOURCES += \ $$PWD/qabstract3dinputhandler.cpp \ $$PWD/q3dinputhandler.cpp \ $$PWD/qtouch3dinputhandler.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/input/q3dinputhandler.cpp b/src/datavisualization/input/q3dinputhandler.cpp index f0044096..43cee84e 100644 --- a/src/datavisualization/input/q3dinputhandler.cpp +++ b/src/datavisualization/input/q3dinputhandler.cpp @@ -18,19 +18,20 @@ #include "datavisualizationglobal_p.h" #include "q3dinputhandler_p.h" +#include "abstract3dcontroller_p.h" QT_BEGIN_NAMESPACE_DATAVISUALIZATION -const int minZoomLevel = 10; -const int halfSizeZoomLevel = 50; -const int oneToOneZoomLevel = 100; -const int maxZoomLevel = 500; +static const int halfSizeZoomLevel = 50; +static const int oneToOneZoomLevel = 100; +static const int driftTowardCenterLevel = 175; +static const float wheelZoomDrift = 0.1f; -const int nearZoomRangeDivider = 12; -const int midZoomRangeDivider = 60; -const int farZoomRangeDivider = 120; +static const int nearZoomRangeDivider = 12; +static const int midZoomRangeDivider = 60; +static const int farZoomRangeDivider = 120; -const float rotationSpeed = 100.0f; +static const float rotationSpeed = 100.0f; /*! * \class Q3DInputHandler @@ -55,12 +56,61 @@ const float rotationSpeed = 100.0f; * \l {QAbstract3DGraph::selectionMode}{selection mode}. * \row * \li Mouse wheel - * \li Zoom in/out within default range (10...500%). + * \li Zoom in/out within the allowable zoom range set for Q3DCamera. * \row * \li Left click on the primary view when the secondary view is visible * \li Closes the secondary view. * \note Secondary view is available only for Q3DBars and Q3DSurface graphs. * \endtable + * + * Rotation, zoom, and selection can each be individually disabled using + * corresponding properties of this class. + */ + +/*! + * \qmltype InputHandler3D + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates Q3DInputHandler + * \brief Basic wheel mouse based input handler. + * + * InputHandler3D is the basic input handler for wheel mouse type of input devices. + * + * See Q3DInputHandler documentation for more details. + */ + +/*! + * \qmlproperty bool InputHandler3D::rotationEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph rotation. + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool InputHandler3D::zoomEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph zooming. + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool InputHandler3D::selectionEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows selection from the graph. + * Defaults to \c{true}. + */ + +/*! + * \qmlproperty bool InputHandler3D::zoomAtTargetEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if zooming changes the camera target to the position of the input + * at the time of the zoom. + * Defaults to \c{true}. */ /*! @@ -92,29 +142,35 @@ void Q3DInputHandler::mousePressEvent(QMouseEvent *event, const QPoint &mousePos Q_UNUSED(mousePos); #else if (Qt::LeftButton == event->button()) { - if (scene()->isSlicingActive()) { - if (scene()->isPointInPrimarySubView(mousePos)) + if (isSelectionEnabled()) { + if (scene()->isSlicingActive()) { + if (scene()->isPointInPrimarySubView(mousePos)) + setInputView(InputViewOnPrimary); + else if (scene()->isPointInSecondarySubView(mousePos)) + setInputView(InputViewOnSecondary); + else + setInputView(InputViewNone); + } else { + // update mouse positions to prevent jumping when releasing or repressing a button + setInputPosition(mousePos); + scene()->setSelectionQueryPosition(mousePos); setInputView(InputViewOnPrimary); - else if (scene()->isPointInSecondarySubView(mousePos)) - setInputView(InputViewOnSecondary); - else - setInputView(InputViewNone); - } else { - // update mouse positions to prevent jumping when releasing or repressing a button - setInputPosition(mousePos); - scene()->setSelectionQueryPosition(mousePos); - setInputView(InputViewOnPrimary); - d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + } } } else if (Qt::MiddleButton == event->button()) { - // reset rotations - setInputPosition(QPoint(0, 0)); + if (isRotationEnabled()) { + // reset rotations + setInputPosition(QPoint(0, 0)); + } } else if (Qt::RightButton == event->button()) { - // disable rotating when in slice view - if (!scene()->isSlicingActive()) - d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; - // update mouse positions to prevent jumping when releasing or repressing a button - setInputPosition(mousePos); + if (isRotationEnabled()) { + // disable rotating when in slice view + if (!scene()->isSlicingActive()) + d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; + // update mouse positions to prevent jumping when releasing or repressing a button + setInputPosition(mousePos); + } } #endif } @@ -148,7 +204,8 @@ void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) #if defined(Q_OS_IOS) Q_UNUSED(mousePos); #else - if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState) { + if (QAbstract3DInputHandlerPrivate::InputStateRotating == d_ptr->m_inputState + && isRotationEnabled()) { // Calculate mouse movement since last frame float xRotation = scene()->activeCamera()->xRotation(); float yRotation = scene()->activeCamera()->yRotation(); @@ -174,34 +231,191 @@ void Q3DInputHandler::mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos) */ void Q3DInputHandler::wheelEvent(QWheelEvent *event) { - // disable zooming if in slice view - if (scene()->isSlicingActive()) - return; + if (isZoomEnabled()) { + // disable zooming if in slice view + if (scene()->isSlicingActive()) + return; - // Adjust zoom level based on what zoom range we're in. - int zoomLevel = scene()->activeCamera()->zoomLevel(); - if (zoomLevel > oneToOneZoomLevel) - zoomLevel += event->angleDelta().y() / nearZoomRangeDivider; - else if (zoomLevel > halfSizeZoomLevel) - zoomLevel += event->angleDelta().y() / midZoomRangeDivider; - else - zoomLevel += event->angleDelta().y() / farZoomRangeDivider; - if (zoomLevel > maxZoomLevel) - zoomLevel = maxZoomLevel; - else if (zoomLevel < minZoomLevel) - zoomLevel = minZoomLevel; + // Adjust zoom level based on what zoom range we're in. + Q3DCamera *camera = scene()->activeCamera(); + int zoomLevel = int(camera->zoomLevel()); + const int minZoomLevel = int(camera->minZoomLevel()); + const int maxZoomLevel = int(camera->maxZoomLevel()); + if (zoomLevel > oneToOneZoomLevel) + zoomLevel += event->angleDelta().y() / nearZoomRangeDivider; + else if (zoomLevel > halfSizeZoomLevel) + zoomLevel += event->angleDelta().y() / midZoomRangeDivider; + else + zoomLevel += event->angleDelta().y() / farZoomRangeDivider; + zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel); - scene()->activeCamera()->setZoomLevel(zoomLevel); + if (isZoomAtTargetEnabled()) { + scene()->setGraphPositionQuery(event->pos()); + d_ptr->m_zoomAtTargetPending = true; + // If zoom at target is enabled, we don't want to zoom yet, as that causes + // jitter. Instead, we zoom next frame, when we apply the camera position. + d_ptr->m_requestedZoomLevel = zoomLevel; + d_ptr->m_driftMultiplier = wheelZoomDrift; + } else { + camera->setZoomLevel(zoomLevel); + } + } +} + +/*! + * \property Q3DInputHandler::rotationEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph rotation. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setRotationEnabled(bool enable) +{ + if (d_ptr->m_rotationEnabled != enable) { + d_ptr->m_rotationEnabled = enable; + emit rotationEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isRotationEnabled() const +{ + return d_ptr->m_rotationEnabled; +} + +/*! + * \property Q3DInputHandler::zoomEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows graph zooming. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setZoomEnabled(bool enable) +{ + if (d_ptr->m_zoomEnabled != enable) { + d_ptr->m_zoomEnabled = enable; + emit zoomEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isZoomEnabled() const +{ + return d_ptr->m_zoomEnabled; +} + +/*! + * \property Q3DInputHandler::selectionEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if this input handler allows selection from the graph. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setSelectionEnabled(bool enable) +{ + if (d_ptr->m_selectionEnabled != enable) { + d_ptr->m_selectionEnabled = enable; + emit selectionEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isSelectionEnabled() const +{ + return d_ptr->m_selectionEnabled; +} + +/*! + * \property Q3DInputHandler::zoomAtTargetEnabled + * \since QtDataVisualization 1.2 + * + * This property specifies if zooming should change the camera target so that the zoomed point + * of the graph stays at the same location after the zoom. + * Defaults to \c{true}. + */ +void Q3DInputHandler::setZoomAtTargetEnabled(bool enable) +{ + if (d_ptr->m_zoomAtTargetEnabled != enable) { + d_ptr->m_zoomAtTargetEnabled = enable; + emit zoomAtTargetEnabledChanged(enable); + } +} + +bool Q3DInputHandler::isZoomAtTargetEnabled() const +{ + return d_ptr->m_zoomAtTargetEnabled; } Q3DInputHandlerPrivate::Q3DInputHandlerPrivate(Q3DInputHandler *q) : q_ptr(q), - m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone) + m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone), + m_rotationEnabled(true), + m_zoomEnabled(true), + m_selectionEnabled(true), + m_zoomAtTargetEnabled(true), + m_zoomAtTargetPending(false), + m_controller(0), + m_requestedZoomLevel(0.0f), + m_driftMultiplier(0.0f) { + QObject::connect(q, &QAbstract3DInputHandler::sceneChanged, + this, &Q3DInputHandlerPrivate::handleSceneChange); } Q3DInputHandlerPrivate::~Q3DInputHandlerPrivate() { } +void Q3DInputHandlerPrivate::handleSceneChange(Q3DScene *scene) +{ + if (scene) { + if (m_controller) { + QObject::disconnect(m_controller, &Abstract3DController::queriedGraphPositionChanged, + this, &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange); + } + + m_controller = qobject_cast<Abstract3DController *>(scene->parent()); + + if (m_controller) { + QObject::connect(m_controller, &Abstract3DController::queriedGraphPositionChanged, + this, &Q3DInputHandlerPrivate::handleQueriedGraphPositionChange); + } + } +} + +void Q3DInputHandlerPrivate::handleQueriedGraphPositionChange() +{ + if (m_zoomAtTargetPending) { + // Check if the zoom point is on graph + QVector3D newTarget = m_controller->queriedGraphPosition(); + float currentZoom = m_requestedZoomLevel; + float previousZoom = q_ptr->scene()->activeCamera()->zoomLevel(); + q_ptr->scene()->activeCamera()->setZoomLevel(currentZoom); + float diffAdj = 0.0f; + + // If zooming in/out outside the graph, or zooming out after certain point, + // move towards the center. + if ((qAbs(newTarget.x()) > 1.0f + || qAbs(newTarget.y()) > 1.0f + || qAbs(newTarget.z()) > 1.0f) + || (previousZoom > currentZoom && currentZoom <= driftTowardCenterLevel)) { + newTarget = zeroVector; + // Add some extra correction so that we actually reach the center eventually + diffAdj = m_driftMultiplier; + if (previousZoom > currentZoom) + diffAdj *= 2.0f; // Correct towards center little more when zooming out + } + + float zoomFraction = 1.0f - (previousZoom / currentZoom); + + // Adjust camera towards the zoom point, attempting to keep the cursor at same graph point + QVector3D oldTarget = q_ptr->scene()->activeCamera()->target(); + QVector3D origDiff = newTarget - oldTarget; + QVector3D diff = origDiff * zoomFraction + (origDiff.normalized() * diffAdj); + if (diff.length() > origDiff.length()) + diff = origDiff; + q_ptr->scene()->activeCamera()->setTarget(oldTarget + diff); + + if (q_ptr->scene()->selectionQueryPosition() == Q3DScene::invalidSelectionPoint()) + m_zoomAtTargetPending = false; + } +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/input/q3dinputhandler.h b/src/datavisualization/input/q3dinputhandler.h index 118bd829..5423b4ea 100644 --- a/src/datavisualization/input/q3dinputhandler.h +++ b/src/datavisualization/input/q3dinputhandler.h @@ -28,17 +28,36 @@ class Q3DInputHandlerPrivate; class QT_DATAVISUALIZATION_EXPORT Q3DInputHandler : public QAbstract3DInputHandler { Q_OBJECT + Q_PROPERTY(bool rotationEnabled READ isRotationEnabled WRITE setRotationEnabled NOTIFY rotationEnabledChanged) + Q_PROPERTY(bool zoomEnabled READ isZoomEnabled WRITE setZoomEnabled NOTIFY zoomEnabledChanged) + Q_PROPERTY(bool selectionEnabled READ isSelectionEnabled WRITE setSelectionEnabled NOTIFY selectionEnabledChanged) + Q_PROPERTY(bool zoomAtTargetEnabled READ isZoomAtTargetEnabled WRITE setZoomAtTargetEnabled NOTIFY zoomAtTargetEnabledChanged) public: explicit Q3DInputHandler(QObject *parent = 0); virtual ~Q3DInputHandler(); + void setRotationEnabled(bool enable); + bool isRotationEnabled() const; + void setZoomEnabled(bool enable); + bool isZoomEnabled() const; + void setSelectionEnabled(bool enable); + bool isSelectionEnabled() const; + void setZoomAtTargetEnabled(bool enable); + bool isZoomAtTargetEnabled() const; + // Input event listeners virtual void mousePressEvent(QMouseEvent *event, const QPoint &mousePos); virtual void mouseReleaseEvent(QMouseEvent *event, const QPoint &mousePos); virtual void mouseMoveEvent(QMouseEvent *event, const QPoint &mousePos); virtual void wheelEvent(QWheelEvent *event); +signals: + void rotationEnabledChanged(bool enable); + void zoomEnabledChanged(bool enable); + void selectionEnabledChanged(bool enable); + void zoomAtTargetEnabledChanged(bool enable); + private: Q_DISABLE_COPY(Q3DInputHandler) diff --git a/src/datavisualization/input/q3dinputhandler_p.h b/src/datavisualization/input/q3dinputhandler_p.h index a9b27307..79b1c8dd 100644 --- a/src/datavisualization/input/q3dinputhandler_p.h +++ b/src/datavisualization/input/q3dinputhandler_p.h @@ -34,15 +34,36 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -class Q3DInputHandlerPrivate +class Abstract3DController; + +class Q3DInputHandlerPrivate : public QObject { + Q_OBJECT public: Q3DInputHandlerPrivate(Q3DInputHandler *q); ~Q3DInputHandlerPrivate(); -public: +public slots: + void handleSceneChange(Q3DScene *scene); + void handleQueriedGraphPositionChange(); + +private: Q3DInputHandler *q_ptr; +protected: QAbstract3DInputHandlerPrivate::InputState m_inputState; + + bool m_rotationEnabled; + bool m_zoomEnabled; + bool m_selectionEnabled; + bool m_zoomAtTargetEnabled; + bool m_zoomAtTargetPending; + + Abstract3DController *m_controller; // Not owned + + float m_requestedZoomLevel; + float m_driftMultiplier; + + friend class Q3DInputHandler; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/input/qtouch3dinputhandler.cpp b/src/datavisualization/input/qtouch3dinputhandler.cpp index 30f31d4a..357a6f3e 100644 --- a/src/datavisualization/input/qtouch3dinputhandler.cpp +++ b/src/datavisualization/input/qtouch3dinputhandler.cpp @@ -22,17 +22,16 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION -const float maxTapAndHoldJitter = 20.0f; -const int maxPinchJitter = 10; +static const float maxTapAndHoldJitter = 20.0f; +static const int maxPinchJitter = 10; #if defined (Q_OS_ANDROID) || defined(Q_OS_IOS) -const int maxSelectionJitter = 10; +static const int maxSelectionJitter = 10; #else -const int maxSelectionJitter = 5; +static const int maxSelectionJitter = 5; #endif -const int tapAndHoldTime = 250; -const float rotationSpeed = 200.0f; -const int minZoomLevel = 10; -const int maxZoomLevel = 500; +static const int tapAndHoldTime = 250; +static const float rotationSpeed = 200.0f; +static const float touchZoomDrift = 0.02f; /*! * \class QTouch3DInputHandler @@ -60,12 +59,29 @@ const int maxZoomLevel = 500; * \li Same as tap. * \row * \li Pinch - * \li Zoom in/out within default range (10...500%). + * \li Zoom in/out within the allowable zoom range set for Q3DCamera. * \row * \li Tap on the primary view when the secondary view is visible * \li Closes the secondary view. * \note Secondary view is available only for Q3DBars and Q3DSurface graphs. * \endtable + * + * Rotation, zoom, and selection can each be individually disabled using + * corresponding Q3DInputHandler properties. + */ + +/*! + * \qmltype TouchInputHandler3D + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates QTouch3DInputHandler + * \inherits InputHandler3D + * \brief Basic touch display based input handler. + * + * TouchInputHandler3D is the basic input handler for touch screen devices. + * + * See QTouch3DInputHandler documentation for more details. */ /*! @@ -97,36 +113,46 @@ void QTouch3DInputHandler::touchEvent(QTouchEvent *event) if (!scene()->isSlicingActive() && points.count() == 2) { d_ptr->m_holdTimer->stop(); QPointF distance = points.at(0).pos() - points.at(1).pos(); - d_ptr->handlePinchZoom(distance.manhattanLength()); + QPoint midPoint = ((points.at(0).pos() + points.at(1).pos()) / 2.0).toPoint(); + d_ptr->handlePinchZoom(distance.manhattanLength(), midPoint); } else if (points.count() == 1) { QPointF pointerPos = points.at(0).pos(); if (event->type() == QEvent::TouchBegin) { // Flush input state d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; if (scene()->isSlicingActive()) { - if (scene()->isPointInPrimarySubView(pointerPos.toPoint())) - setInputView(InputViewOnPrimary); - else if (scene()->isPointInSecondarySubView(pointerPos.toPoint())) - setInputView(InputViewOnSecondary); - else - setInputView(InputViewNone); + if (isSelectionEnabled()) { + if (scene()->isPointInPrimarySubView(pointerPos.toPoint())) + setInputView(InputViewOnPrimary); + else if (scene()->isPointInSecondarySubView(pointerPos.toPoint())) + setInputView(InputViewOnSecondary); + else + setInputView(InputViewNone); + } } else { // Handle possible tap-and-hold selection - d_ptr->m_startHoldPos = pointerPos; - d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos; - d_ptr->m_holdTimer->start(); - setInputView(InputViewOnPrimary); + if (isSelectionEnabled()) { + d_ptr->m_startHoldPos = pointerPos; + d_ptr->m_touchHoldPos = d_ptr->m_startHoldPos; + d_ptr->m_holdTimer->start(); + setInputView(InputViewOnPrimary); + } // Start rotating - d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; - setInputPosition(pointerPos.toPoint()); + if (isRotationEnabled()) { + d_ptr->m_inputState = QAbstract3DInputHandlerPrivate::InputStateRotating; + setInputPosition(pointerPos.toPoint()); + setInputView(InputViewOnPrimary); + } } } else if (event->type() == QEvent::TouchEnd) { setInputView(InputViewNone); d_ptr->m_holdTimer->stop(); // Handle possible selection if (!scene()->isSlicingActive() - && QAbstract3DInputHandlerPrivate::InputStatePinching != d_ptr->m_inputState) + && QAbstract3DInputHandlerPrivate::InputStatePinching + != d_ptr->m_inputState) { d_ptr->handleSelection(pointerPos); + } } else if (event->type() == QEvent::TouchUpdate) { if (!scene()->isSlicingActive()) { d_ptr->m_touchHoldPos = pointerPos; @@ -140,7 +166,8 @@ void QTouch3DInputHandler::touchEvent(QTouchEvent *event) } QTouch3DInputHandlerPrivate::QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q) - : q_ptr(q), + : Q3DInputHandlerPrivate(q), + q_ptr(q), m_holdTimer(0), m_inputState(QAbstract3DInputHandlerPrivate::InputStateNone) { @@ -156,54 +183,71 @@ QTouch3DInputHandlerPrivate::~QTouch3DInputHandlerPrivate() delete m_holdTimer; } -void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance) +void QTouch3DInputHandlerPrivate::handlePinchZoom(float distance, const QPoint &pos) { - int newDistance = distance; - int prevDist = q_ptr->prevDistance(); - if (prevDist > 0 && qAbs(prevDist - newDistance) < maxPinchJitter) - return; - m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching; - Q3DCamera *camera = q_ptr->scene()->activeCamera(); - int zoomLevel = camera->zoomLevel(); - float zoomRate = qSqrt(qSqrt(zoomLevel)); - if (newDistance > prevDist) - zoomLevel += zoomRate; - else - zoomLevel -= zoomRate; - if (zoomLevel > maxZoomLevel) - zoomLevel = maxZoomLevel; - else if (zoomLevel < minZoomLevel) - zoomLevel = minZoomLevel; - camera->setZoomLevel(zoomLevel); - q_ptr->setPrevDistance(newDistance); + if (q_ptr->isZoomEnabled()) { + int newDistance = distance; + int prevDist = q_ptr->prevDistance(); + if (prevDist > 0 && qAbs(prevDist - newDistance) < maxPinchJitter) + return; + m_inputState = QAbstract3DInputHandlerPrivate::InputStatePinching; + Q3DCamera *camera = q_ptr->scene()->activeCamera(); + int zoomLevel = int(camera->zoomLevel()); + const int minZoomLevel = int(camera->minZoomLevel()); + const int maxZoomLevel = int(camera->maxZoomLevel()); + float zoomRate = qSqrt(qSqrt(zoomLevel)); + if (newDistance > prevDist) + zoomLevel += zoomRate; + else + zoomLevel -= zoomRate; + zoomLevel = qBound(minZoomLevel, zoomLevel, maxZoomLevel); + + if (q_ptr->isZoomAtTargetEnabled()) { + q_ptr->scene()->setGraphPositionQuery(pos); + m_zoomAtTargetPending = true; + // If zoom at target is enabled, we don't want to zoom yet, as that causes + // jitter. Instead, we zoom next frame, when we apply the camera position. + m_requestedZoomLevel = zoomLevel; + m_driftMultiplier = touchZoomDrift; + } else { + camera->setZoomLevel(zoomLevel); + } + + q_ptr->setPrevDistance(newDistance); + } } void QTouch3DInputHandlerPrivate::handleTapAndHold() { - QPointF distance = m_startHoldPos - m_touchHoldPos; - if (distance.manhattanLength() < maxTapAndHoldJitter) { - q_ptr->setInputPosition(m_touchHoldPos.toPoint()); - q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint()); - m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + if (q_ptr->isSelectionEnabled()) { + QPointF distance = m_startHoldPos - m_touchHoldPos; + if (distance.manhattanLength() < maxTapAndHoldJitter) { + q_ptr->setInputPosition(m_touchHoldPos.toPoint()); + q_ptr->scene()->setSelectionQueryPosition(m_touchHoldPos.toPoint()); + m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + } } } void QTouch3DInputHandlerPrivate::handleSelection(const QPointF &position) { - QPointF distance = m_startHoldPos - position; - if (distance.manhattanLength() < maxSelectionJitter) { - m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; - q_ptr->scene()->setSelectionQueryPosition(position.toPoint()); - } else { - m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; - q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone); + if (q_ptr->isSelectionEnabled()) { + QPointF distance = m_startHoldPos - position; + if (distance.manhattanLength() < maxSelectionJitter) { + m_inputState = QAbstract3DInputHandlerPrivate::InputStateSelecting; + q_ptr->scene()->setSelectionQueryPosition(position.toPoint()); + } else { + m_inputState = QAbstract3DInputHandlerPrivate::InputStateNone; + q_ptr->setInputView(QAbstract3DInputHandler::InputViewNone); + } + q_ptr->setPreviousInputPos(position.toPoint()); } - q_ptr->setPreviousInputPos(position.toPoint()); } void QTouch3DInputHandlerPrivate::handleRotation(const QPointF &position) { - if (QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) { + if (q_ptr->isRotationEnabled() + && QAbstract3DInputHandlerPrivate::InputStateRotating == m_inputState) { Q3DScene *scene = q_ptr->scene(); Q3DCamera *camera = scene->activeCamera(); float xRotation = camera->xRotation(); diff --git a/src/datavisualization/input/qtouch3dinputhandler_p.h b/src/datavisualization/input/qtouch3dinputhandler_p.h index 613b5f28..b01904ca 100644 --- a/src/datavisualization/input/qtouch3dinputhandler_p.h +++ b/src/datavisualization/input/qtouch3dinputhandler_p.h @@ -19,7 +19,7 @@ #ifndef QTOUCH3DINPUTHANDLER_P_H #define QTOUCH3DINPUTHANDLER_P_H -#include "qabstract3dinputhandler_p.h" +#include "q3dinputhandler_p.h" #include "qtouch3dinputhandler.h" class QTimer; @@ -28,7 +28,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION class QAbstract3DInputHandler; -class QTouch3DInputHandlerPrivate : public QObject +class QTouch3DInputHandlerPrivate : public Q3DInputHandlerPrivate { Q_OBJECT @@ -36,13 +36,14 @@ public: QTouch3DInputHandlerPrivate(QTouch3DInputHandler *q); ~QTouch3DInputHandlerPrivate(); - void handlePinchZoom(float distance); + void handlePinchZoom(float distance, const QPoint &pos); void handleTapAndHold(); void handleSelection(const QPointF &position); void handleRotation(const QPointF &position); -public: +private: QTouch3DInputHandler *q_ptr; +public: QTimer *m_holdTimer; QAbstract3DInputHandlerPrivate::InputState m_inputState; QPointF m_startHoldPos; diff --git a/src/datavisualization/theme/q3dtheme.cpp b/src/datavisualization/theme/q3dtheme.cpp index 97ff8f81..a1dfe8b3 100644 --- a/src/datavisualization/theme/q3dtheme.cpp +++ b/src/datavisualization/theme/q3dtheme.cpp @@ -323,7 +323,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty real Theme3D::lightStrength * - * Specular light strength for the whole graph. Value must be between 0.0 and 1.0. + * Specular light strength for the whole graph. Value must be between 0.0 and 10.0. */ /*! @@ -335,7 +335,7 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION /*! * \qmlproperty real Theme3D::highlightLightStrength * - * Specular light strength for highlighted objects. Value must be between 0.0 and 1.0. + * Specular light strength for highlighted objects. Value must be between 0.0 and 10.0. */ /*! @@ -679,7 +679,7 @@ QLinearGradient Q3DTheme::multiHighlightGradient() const /*! * \property Q3DTheme::lightStrength * - * Specular light strength for the whole graph. Value must be 0.0f and 10.0f. + * Specular light strength for the whole graph. Value must be between 0.0f and 10.0f. */ void Q3DTheme::setLightStrength(float strength) { diff --git a/src/datavisualization/theme/theme.pri b/src/datavisualization/theme/theme.pri index 1de3035a..cf7b434c 100644 --- a/src/datavisualization/theme/theme.pri +++ b/src/datavisualization/theme/theme.pri @@ -6,3 +6,5 @@ HEADERS += \ SOURCES += \ $$PWD/q3dtheme.cpp \ $$PWD/thememanager.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/utils/abstractobjecthelper.cpp b/src/datavisualization/utils/abstractobjecthelper.cpp index c350d096..40b3a45e 100644 --- a/src/datavisualization/utils/abstractobjecthelper.cpp +++ b/src/datavisualization/utils/abstractobjecthelper.cpp @@ -28,6 +28,7 @@ AbstractObjectHelper::AbstractObjectHelper() m_indexCount(0), m_meshDataLoaded(false) { + initializeOpenGLFunctions(); } AbstractObjectHelper::~AbstractObjectHelper() diff --git a/src/datavisualization/utils/abstractobjecthelper_p.h b/src/datavisualization/utils/abstractobjecthelper_p.h index c1618909..99f85dab 100644 --- a/src/datavisualization/utils/abstractobjecthelper_p.h +++ b/src/datavisualization/utils/abstractobjecthelper_p.h @@ -38,11 +38,11 @@ class AbstractObjectHelper: protected QOpenGLFunctions protected: AbstractObjectHelper(); public: - ~AbstractObjectHelper(); + virtual ~AbstractObjectHelper(); GLuint vertexBuf(); GLuint normalBuf(); - GLuint uvBuf(); + virtual GLuint uvBuf(); GLuint elementBuf(); GLuint indexCount(); GLuint indicesType(); diff --git a/src/datavisualization/utils/objecthelper.cpp b/src/datavisualization/utils/objecthelper.cpp index a66e0f7e..4240d6f5 100644 --- a/src/datavisualization/utils/objecthelper.cpp +++ b/src/datavisualization/utils/objecthelper.cpp @@ -111,7 +111,6 @@ ObjectHelper *ObjectHelper::getObjectHelper(const Abstract3DRenderer *cacheId, void ObjectHelper::load() { - initializeOpenGLFunctions(); if (m_meshDataLoaded) { // Delete old data glDeleteBuffers(1, &m_vertexbuffer); @@ -122,6 +121,10 @@ void ObjectHelper::load() m_indexedVertices.clear(); m_indexedUVs.clear(); m_indexedNormals.clear(); + m_vertexbuffer = 0; + m_uvbuffer = 0; + m_normalbuffer = 0; + m_elementbuffer = 0; } QVector<QVector3D> vertices; QVector<QVector2D> uvs; diff --git a/src/datavisualization/utils/objecthelper_p.h b/src/datavisualization/utils/objecthelper_p.h index c84f53bd..b00e5d8d 100644 --- a/src/datavisualization/utils/objecthelper_p.h +++ b/src/datavisualization/utils/objecthelper_p.h @@ -41,7 +41,7 @@ class ObjectHelper : public AbstractObjectHelper private: ObjectHelper(const QString &objectFile); public: - ~ObjectHelper(); + virtual ~ObjectHelper(); static void resetObjectHelper(const Abstract3DRenderer *cacheId, ObjectHelper *&obj, const QString &meshFile); diff --git a/src/datavisualization/utils/qutils.h b/src/datavisualization/utils/qutils.h index 43375a9c..d4acfc99 100644 --- a/src/datavisualization/utils/qutils.h +++ b/src/datavisualization/utils/qutils.h @@ -28,17 +28,21 @@ inline static QSurfaceFormat qDefaultSurfaceFormat(bool antialias = true) QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); + surfaceFormat.setStencilBufferSize(8); surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer); -#if !defined(QT_OPENGL_ES_2) - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGL); -#else + surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType); +#if defined(QT_OPENGL_ES_2) // Antialias not supported for ES antialias = false; - surfaceFormat.setRenderableType(QSurfaceFormat::OpenGLES); + surfaceFormat.setRedBufferSize(8); + surfaceFormat.setBlueBufferSize(8); + surfaceFormat.setGreenBufferSize(8); #endif if (antialias) surfaceFormat.setSamples(8); + else + surfaceFormat.setSamples(0); return surfaceFormat; } diff --git a/src/datavisualization/utils/scatterobjectbufferhelper.cpp b/src/datavisualization/utils/scatterobjectbufferhelper.cpp index d68b9df4..44c84ae0 100644 --- a/src/datavisualization/utils/scatterobjectbufferhelper.cpp +++ b/src/datavisualization/utils/scatterobjectbufferhelper.cpp @@ -20,12 +20,14 @@ #include "objecthelper_p.h" #include <QtGui/QVector2D> #include <QtGui/QMatrix4x4> +#include <QtCore/qmath.h> QT_BEGIN_NAMESPACE_DATAVISUALIZATION const GLfloat itemScaler = 3.0f; ScatterObjectBufferHelper::ScatterObjectBufferHelper() + : m_scaleY(0.0f) { m_indicesType = GL_UNSIGNED_INT; } @@ -36,34 +38,40 @@ ScatterObjectBufferHelper::~ScatterObjectBufferHelper() void ScatterObjectBufferHelper::fullLoad(ScatterSeriesRenderCache *cache, qreal dotScale) { - initializeOpenGLFunctions(); - m_meshDataLoaded = false; m_indexCount = 0; ObjectHelper *dotObj = cache->object(); - ScatterRenderItemArray &renderArray = cache->renderArray(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); const uint renderArraySize = renderArray.size(); + if (renderArraySize == 0) return; // No use to go forward - uint itemCount = renderArraySize; + + uint itemCount = 0; QQuaternion seriesRotation(cache->meshRotation()); + if (m_meshDataLoaded) { // Delete old data glDeleteBuffers(1, &m_vertexbuffer); glDeleteBuffers(1, &m_uvbuffer); glDeleteBuffers(1, &m_normalbuffer); glDeleteBuffers(1, &m_elementbuffer); + m_vertexbuffer = 0; + m_uvbuffer = 0; + m_normalbuffer = 0; + m_elementbuffer = 0; } + // Index vertices const QVector<unsigned short> indices = dotObj->indices(); const QVector<QVector3D> indexed_vertices = dotObj->indexedvertices(); const QVector<QVector2D> indexed_uvs = dotObj->indexedUVs(); const QVector<QVector3D> indexed_normals = dotObj->indexedNormals(); - int indicesCount = indices.count(); - int verticeCount = indexed_vertices.count(); - int uvsCount = indexed_uvs.count(); - int normalsCount = indexed_normals.count(); + const int indicesCount = indices.count(); + const int verticeCount = indexed_vertices.count(); + const int uvsCount = indexed_uvs.count(); + const int normalsCount = indexed_normals.count(); float itemSize = cache->itemSize() / itemScaler; if (itemSize == 0.0f) @@ -89,47 +97,58 @@ void ScatterObjectBufferHelper::fullLoad(ScatterSeriesRenderCache *cache, qreal buffered_indices.resize(indicesCount * renderArraySize); buffered_vertices.resize(verticeCount * renderArraySize); - buffered_uvs.resize(uvsCount * renderArraySize); buffered_normals.resize(normalsCount * renderArraySize); - uint pos = 0; + buffered_uvs.resize(uvsCount * renderArraySize); + + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + createRangeGradientUVs(cache, buffered_uvs); + else if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) + createObjectGradientUVs(cache, buffered_uvs, indexed_vertices); + + QVector2D dummyUV(0.0f, 0.0f); + + cache->bufferIndices().resize(renderArraySize); for (uint i = 0; i < renderArraySize; i++) { - ScatterRenderItem &item = renderArray[i]; - if (!item.isVisible()) { - itemCount--; + const ScatterRenderItem &item = renderArray.at(i); + if (!item.isVisible()) continue; - } + else + cache->bufferIndices()[i] = itemCount; - int offset = pos * verticeCount; + int offset = itemCount * verticeCount; if (item.rotation().isIdentity()) { - for (int j = 0; j < verticeCount; j++) + for (int j = 0; j < verticeCount; j++) { buffered_vertices[j + offset] = scaled_vertices[j] + item.translation(); + buffered_normals[j + offset] = indexed_normals[j]; + } } else { QMatrix4x4 matrix; - matrix.rotate(seriesRotation * item.rotation()); - modelMatrix = matrix.transposed(); - modelMatrix.scale(modelScaler); + QQuaternion totalRotation = seriesRotation * item.rotation(); + matrix.rotate(totalRotation); + matrix.scale(modelScaler); + QMatrix4x4 itModelMatrix = matrix.inverted(); + modelMatrix = matrix.transposed(); // Because of row-column major difference - for (int j = 0; j < verticeCount; j++) + for (int j = 0; j < verticeCount; j++) { buffered_vertices[j + offset] = indexed_vertices[j] * modelMatrix + item.translation(); + buffered_normals[j + offset] = indexed_normals[j] * itModelMatrix; + } } - offset = pos * normalsCount; - for (int j = 0; j < normalsCount; j++) - buffered_normals[j + offset] = indexed_normals[j]; - - offset = pos * uvsCount; - for (int j = 0; j < uvsCount; j++) - buffered_uvs[j + offset] = indexed_uvs[j]; + if (cache->colorStyle() == Q3DTheme::ColorStyleUniform) { + offset = itemCount * uvsCount; + for (int j = 0; j < uvsCount; j++) + buffered_uvs[j + offset] = dummyUV; + } - int offsetVertice = i * verticeCount; - offset = pos * indicesCount; - for (int j = 0; j < indicesCount; j++) { + int offsetVertice = itemCount * verticeCount; + offset = itemCount * indicesCount; + for (int j = 0; j < indicesCount; j++) buffered_indices[j + offset] = GLuint(indices[j] + offsetVertice); - } - pos++; + itemCount++; } m_indexCount = indicesCount * itemCount; @@ -164,15 +183,128 @@ void ScatterObjectBufferHelper::fullLoad(ScatterSeriesRenderCache *cache, qreal } } -void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal dotScale) +void ScatterObjectBufferHelper::updateUVs(ScatterSeriesRenderCache *cache) +{ + ObjectHelper *dotObj = cache->object(); + const int uvsCount = dotObj->indexedUVs().count(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); + + if (!updateSize) + return; + + QVector<QVector2D> buffered_uvs; + buffered_uvs.resize(uvsCount * updateSize); + + uint itemCount = 0; + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) { + itemCount = createRangeGradientUVs(cache, buffered_uvs); + } else if (cache->colorStyle() == Q3DTheme::ColorStyleObjectGradient) { + const QVector<QVector3D> indexed_vertices = dotObj->indexedvertices(); + itemCount = createObjectGradientUVs(cache, buffered_uvs, indexed_vertices); + } + + glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); + int itemSize = uvsCount * sizeof(QVector2D); + if (cache->updateIndices().size()) { + int pos = 0; + for (int i = 0; i < updateSize; i++) { + int index = cache->updateIndices().at(i); + if (renderArray.at(index).isVisible()) { + int dataPos = cache->bufferIndices().at(index); + glBufferSubData(GL_ARRAY_BUFFER, itemSize * dataPos, itemSize, + &buffered_uvs.at(uvsCount * pos++)); + } + } + } else { + glBufferData(GL_ARRAY_BUFFER, itemSize * itemCount, &buffered_uvs.at(0), GL_STATIC_DRAW); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +uint ScatterObjectBufferHelper::createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs) { - initializeOpenGLFunctions(); + ObjectHelper *dotObj = cache->object(); + const int uvsCount = dotObj->indexedUVs().count(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); + const float yAdjustment = 0.1f; + const float flippedYAdjustment = 0.9f; + + QVector2D uv; + uv.setX(0.0f); + uint pos = 0; + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); + if (!item.isVisible()) + continue; + + float y = ((item.translation().y() + m_scaleY) * 0.5f) / m_scaleY; + // Avoid values near gradient texel boundary, as this causes artifacts + // with some graphics cards. + const float floorY = float(qFloor(y * gradientTextureHeight)); + const float diff = (y * gradientTextureHeight) - floorY; + if (diff < yAdjustment) + y += yAdjustment / gradientTextureHeight; + else if (diff > flippedYAdjustment) + y -= yAdjustment / gradientTextureHeight; + uv.setY(y); + + int offset = pos * uvsCount; + for (int j = 0; j < uvsCount; j++) + buffered_uvs[j + offset] = uv; + + pos++; + } + + return pos; +} + +uint ScatterObjectBufferHelper::createObjectGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs, + const QVector<QVector3D> &indexed_vertices) +{ ObjectHelper *dotObj = cache->object(); - ScatterRenderItemArray &renderArray = cache->renderArray(); - const int renderArraySize = renderArray.size(); + const int uvsCount = dotObj->indexedUVs().count(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const uint renderArraySize = renderArray.size(); + + QVector2D uv; + uv.setX(0.0f); + uint pos = 0; + for (uint i = 0; i < renderArraySize; i++) { + const ScatterRenderItem &item = renderArray.at(i); + if (!item.isVisible()) + continue; + + int offset = pos * uvsCount; + for (int j = 0; j < uvsCount; j++) { + uv.setY((indexed_vertices.at(j).y() + 1.0f) / 2.0f); + buffered_uvs[j + offset] = uv; + } + + pos++; + } + + return pos; +} + +void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal dotScale) +{ + ObjectHelper *dotObj = cache->object(); + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); QQuaternion seriesRotation(cache->meshRotation()); + if (!updateSize) + return; + // Index vertices const QVector<QVector3D> indexed_vertices = dotObj->indexedvertices(); int verticeCount = indexed_vertices.count(); @@ -195,14 +327,16 @@ void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal do scaled_vertices[i] = indexed_vertices[i] * modelMatrix; QVector<QVector3D> buffered_vertices; + buffered_vertices.resize(verticeCount * updateSize); - buffered_vertices.resize(verticeCount * renderArraySize); - for (int i = 0; i < renderArraySize; i++) { - ScatterRenderItem &item = renderArray[i]; + int itemCount = 0; + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); if (!item.isVisible()) continue; - const int offset = i * verticeCount; + const int offset = itemCount * verticeCount; if (item.rotation().isIdentity()) { for (int j = 0; j < verticeCount; j++) buffered_vertices[j + offset] = scaled_vertices[j] + item.translation(); @@ -216,15 +350,28 @@ void ScatterObjectBufferHelper::update(ScatterSeriesRenderCache *cache, qreal do buffered_vertices[j + offset] = indexed_vertices[j] * modelMatrix + item.translation(); } + itemCount++; } glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer); - glBufferData(GL_ARRAY_BUFFER, buffered_vertices.size() * sizeof(QVector3D), - &buffered_vertices.at(0), - GL_DYNAMIC_DRAW); - + int sizeOfItem = verticeCount * sizeof(QVector3D); + if (updateAll) { + if (itemCount) { + glBufferData(GL_ARRAY_BUFFER, itemCount * sizeOfItem, + &buffered_vertices.at(0), GL_STATIC_DRAW); + } + } else { + itemCount = 0; + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + if (renderArray.at(index).isVisible()) { + glBufferSubData(GL_ARRAY_BUFFER, cache->bufferIndices().at(index) * sizeOfItem, + sizeOfItem, &buffered_vertices.at(itemCount * verticeCount)); + itemCount++; + } + } + } glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); m_meshDataLoaded = true; } diff --git a/src/datavisualization/utils/scatterobjectbufferhelper_p.h b/src/datavisualization/utils/scatterobjectbufferhelper_p.h index 952c3d7d..08a42900 100644 --- a/src/datavisualization/utils/scatterobjectbufferhelper_p.h +++ b/src/datavisualization/utils/scatterobjectbufferhelper_p.h @@ -39,10 +39,21 @@ class ScatterObjectBufferHelper : public AbstractObjectHelper { public: ScatterObjectBufferHelper(); - ~ScatterObjectBufferHelper(); + virtual ~ScatterObjectBufferHelper(); void fullLoad(ScatterSeriesRenderCache *cache, qreal dotScale); void update(ScatterSeriesRenderCache *cache, qreal dotScale); + void updateUVs(ScatterSeriesRenderCache *cache); + void setScaleY(float scale) { m_scaleY = scale; } + +private: + uint createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs); + uint createObjectGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs, + const QVector<QVector3D> &indexed_vertices); + + float m_scaleY; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/scatterpointbufferhelper.cpp b/src/datavisualization/utils/scatterpointbufferhelper.cpp index b14d84ad..22e76f92 100644 --- a/src/datavisualization/utils/scatterpointbufferhelper.cpp +++ b/src/datavisualization/utils/scatterpointbufferhelper.cpp @@ -17,6 +17,7 @@ ****************************************************************************/ #include "scatterpointbufferhelper_p.h" +#include <QtGui/QVector2D> QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -24,8 +25,7 @@ const QVector3D hiddenPos(-1000.0f, -1000.0f, -1000.0f); ScatterPointBufferHelper::ScatterPointBufferHelper() : m_pointbuffer(0), - m_oldRemoveIndex(0), - m_oldRemove(false) + m_oldRemoveIndex(-1) { m_indicesType = GL_UNSIGNED_INT; } @@ -47,7 +47,8 @@ void ScatterPointBufferHelper::pushPoint(uint pointIndex) { glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); - if (m_oldRemove && m_oldRemoveIndex < pointIndex) { + // Pop the previous point if it is still pushed + if (m_oldRemoveIndex >= 0) { glBufferSubData(GL_ARRAY_BUFFER, m_oldRemoveIndex * sizeof(QVector3D), sizeof(QVector3D), &m_bufferedPoints.at(m_oldRemoveIndex)); } @@ -59,26 +60,22 @@ void ScatterPointBufferHelper::pushPoint(uint pointIndex) glBindBuffer(GL_ARRAY_BUFFER, 0); m_oldRemoveIndex = pointIndex; - m_oldRemove = true; } void ScatterPointBufferHelper::popPoint() { - if (m_oldRemove) { + if (m_oldRemoveIndex >= 0) { glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); glBufferSubData(GL_ARRAY_BUFFER, m_oldRemoveIndex * sizeof(QVector3D), sizeof(QVector3D), &m_bufferedPoints.at(m_oldRemoveIndex)); glBindBuffer(GL_ARRAY_BUFFER, 0); } - m_oldRemoveIndex = 0; - m_oldRemove = false; + m_oldRemoveIndex = -1; } void ScatterPointBufferHelper::load(ScatterSeriesRenderCache *cache) { - initializeOpenGLFunctions(); - ScatterRenderItemArray &renderArray = cache->renderArray(); const int renderArraySize = renderArray.size(); m_indexCount = 0; @@ -86,13 +83,16 @@ void ScatterPointBufferHelper::load(ScatterSeriesRenderCache *cache) if (m_meshDataLoaded) { // Delete old data glDeleteBuffers(1, &m_pointbuffer); + glDeleteBuffers(1, &m_uvbuffer); m_bufferedPoints.clear(); + m_pointbuffer = 0; + m_uvbuffer = 0; } bool itemsVisible = false; m_bufferedPoints.resize(renderArraySize); for (int i = 0; i < renderArraySize; i++) { - ScatterRenderItem &item = renderArray[i]; + const ScatterRenderItem &item = renderArray.at(i); if (!item.isVisible()) { m_bufferedPoints[i] = hiddenPos; } else { @@ -101,19 +101,108 @@ void ScatterPointBufferHelper::load(ScatterSeriesRenderCache *cache) } } + QVector<QVector2D> buffered_uvs; if (itemsVisible) m_indexCount = renderArraySize; if (m_indexCount > 0) { + if (cache->colorStyle() == Q3DTheme::ColorStyleRangeGradient) + createRangeGradientUVs(cache, buffered_uvs); + glGenBuffers(1, &m_pointbuffer); glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); glBufferData(GL_ARRAY_BUFFER, m_bufferedPoints.size() * sizeof(QVector3D), &m_bufferedPoints.at(0), GL_DYNAMIC_DRAW); + + if (buffered_uvs.size()) { + glGenBuffers(1, &m_uvbuffer); + glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); + glBufferData(GL_ARRAY_BUFFER, buffered_uvs.size() * sizeof(QVector2D), + &buffered_uvs.at(0), GL_STATIC_DRAW); + } + glBindBuffer(GL_ARRAY_BUFFER, 0); m_meshDataLoaded = true; } } +void ScatterPointBufferHelper::update(ScatterSeriesRenderCache *cache) +{ + // It may be that the buffer hasn't yet been initialized, in case the entire series was + // hidden items. No need to update in that case. + if (m_indexCount > 0) { + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const int updateSize = cache->updateIndices().size(); + + glBindBuffer(GL_ARRAY_BUFFER, m_pointbuffer); + for (int i = 0; i < updateSize; i++) { + int index = cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); + if (!item.isVisible()) + m_bufferedPoints[index] = hiddenPos; + else + m_bufferedPoints[index] = item.translation(); + + if (index != m_oldRemoveIndex) { + glBufferSubData(GL_ARRAY_BUFFER, index * sizeof(QVector3D), + sizeof(QVector3D), &m_bufferedPoints.at(index)); + } + } + glBindBuffer(GL_ARRAY_BUFFER, 0); + } +} + +void ScatterPointBufferHelper::updateUVs(ScatterSeriesRenderCache *cache) +{ + // It may be that the buffer hasn't yet been initialized, in case the entire series was + // hidden items. No need to update in that case. + if (m_indexCount > 0) { + QVector<QVector2D> buffered_uvs; + createRangeGradientUVs(cache, buffered_uvs); + + if (buffered_uvs.size()) { + if (!m_uvbuffer) + glGenBuffers(1, &m_uvbuffer); + + int updateSize = cache->updateIndices().size(); + glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); + if (updateSize) { + for (int i = 0; i < updateSize; i++) { + int index = cache->updateIndices().at(i); + glBufferSubData(GL_ARRAY_BUFFER, index * sizeof(QVector2D), + sizeof(QVector2D), &buffered_uvs.at(i)); + + } + } else { + glBufferData(GL_ARRAY_BUFFER, buffered_uvs.size() * sizeof(QVector2D), + &buffered_uvs.at(0), GL_STATIC_DRAW); + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + } +} + +void ScatterPointBufferHelper::createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs) +{ + const ScatterRenderItemArray &renderArray = cache->renderArray(); + const bool updateAll = (cache->updateIndices().size() == 0); + const int updateSize = updateAll ? renderArray.size() : cache->updateIndices().size(); + buffered_uvs.resize(updateSize); + + QVector2D uv; + uv.setX(0.0f); + for (int i = 0; i < updateSize; i++) { + int index = updateAll ? i : cache->updateIndices().at(i); + const ScatterRenderItem &item = renderArray.at(index); + + float y = ((item.translation().y() + m_scaleY) * 0.5f) / m_scaleY; + uv.setY(y); + buffered_uvs[i] = uv; + } +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/scatterpointbufferhelper_p.h b/src/datavisualization/utils/scatterpointbufferhelper_p.h index b3adcfa8..8b34542d 100644 --- a/src/datavisualization/utils/scatterpointbufferhelper_p.h +++ b/src/datavisualization/utils/scatterpointbufferhelper_p.h @@ -39,21 +39,28 @@ class ScatterPointBufferHelper : public AbstractObjectHelper { public: ScatterPointBufferHelper(); - ~ScatterPointBufferHelper(); + virtual ~ScatterPointBufferHelper(); GLuint pointBuf(); void pushPoint(uint pointIndex); void popPoint(); void load(ScatterSeriesRenderCache *cache); + void update(ScatterSeriesRenderCache *cache); + void setScaleY(float scale) { m_scaleY = scale; } + void updateUVs(ScatterSeriesRenderCache *cache); public: GLuint m_pointbuffer; private: + void createRangeGradientUVs(ScatterSeriesRenderCache *cache, + QVector<QVector2D> &buffered_uvs); + +private: QVector<QVector3D> m_bufferedPoints; - uint m_oldRemoveIndex; - bool m_oldRemove; + int m_oldRemoveIndex; + float m_scaleY; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/shaderhelper.cpp b/src/datavisualization/utils/shaderhelper.cpp index 7fb237c6..bbd7fe0e 100644 --- a/src/datavisualization/utils/shaderhelper.cpp +++ b/src/datavisualization/utils/shaderhelper.cpp @@ -40,7 +40,37 @@ ShaderHelper::ShaderHelper(QObject *parent, m_vertexShaderFile(vertexShader), m_fragmentShaderFile(fragmentShader), m_textureFile(texture), - m_depthTextureFile(depthTexture) + m_depthTextureFile(depthTexture), + m_positionAttr(0), + m_uvAttr(0), + m_normalAttr(0), + m_colorUniform(0), + m_viewMatrixUniform(0), + m_modelMatrixUniform(0), + m_invTransModelMatrixUniform(0), + m_depthMatrixUniform(0), + m_mvpMatrixUniform(0), + m_lightPositionUniform(0), + m_lightStrengthUniform(0), + m_ambientStrengthUniform(0), + m_shadowQualityUniform(0), + m_textureUniform(0), + m_shadowUniform(0), + m_gradientMinUniform(0), + m_gradientHeightUniform(0), + m_lightColorUniform(0), + m_volumeSliceIndicesUniform(0), + m_colorIndexUniform(0), + m_cameraPositionRelativeToModelUniform(0), + m_color8BitUniform(0), + m_textureDimensionsUniform(0), + m_sampleCountUniform(0), + m_alphaMultiplierUniform(0), + m_preserveOpacityUniform(0), + m_minBoundsUniform(0), + m_maxBoundsUniform(0), + m_sliceFrameWidthUniform(0), + m_initialized(false) { } @@ -93,6 +123,17 @@ void ShaderHelper::initialize() m_gradientMinUniform = m_program->uniformLocation("gradMin"); m_gradientHeightUniform = m_program->uniformLocation("gradHeight"); m_lightColorUniform = m_program->uniformLocation("lightColor"); + m_volumeSliceIndicesUniform = m_program->uniformLocation("volumeSliceIndices"); + m_colorIndexUniform = m_program->uniformLocation("colorIndex"); + m_cameraPositionRelativeToModelUniform = m_program->uniformLocation("cameraPositionRelativeToModel"); + m_color8BitUniform = m_program->uniformLocation("color8Bit"); + m_textureDimensionsUniform = m_program->uniformLocation("textureDimensions"); + m_sampleCountUniform = m_program->uniformLocation("sampleCount"); + m_alphaMultiplierUniform = m_program->uniformLocation("alphaMultiplier"); + m_preserveOpacityUniform = m_program->uniformLocation("preserveOpacity"); + m_minBoundsUniform = m_program->uniformLocation("minBounds"); + m_maxBoundsUniform = m_program->uniformLocation("maxBounds"); + m_sliceFrameWidthUniform = m_program->uniformLocation("sliceFrameWidth"); m_initialized = true; } @@ -125,151 +166,239 @@ void ShaderHelper::release() m_program->release(); } -void ShaderHelper::setUniformValue(GLuint uniform, const QVector3D &value) +void ShaderHelper::setUniformValue(GLint uniform, const QVector2D &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, const QVector4D &value) +void ShaderHelper::setUniformValue(GLint uniform, const QVector3D &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, const QMatrix4x4 &value) +void ShaderHelper::setUniformValue(GLint uniform, const QVector4D &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, GLfloat value) +void ShaderHelper::setUniformValue(GLint uniform, const QMatrix4x4 &value) { m_program->setUniformValue(uniform, value); } -void ShaderHelper::setUniformValue(GLuint uniform, GLint value) +void ShaderHelper::setUniformValue(GLint uniform, GLfloat value) { m_program->setUniformValue(uniform, value); } -GLuint ShaderHelper::MVP() +void ShaderHelper::setUniformValue(GLint uniform, GLint value) +{ + m_program->setUniformValue(uniform, value); +} + +void ShaderHelper::setUniformValueArray(GLint uniform, const QVector4D *values, int count) +{ + m_program->setUniformValueArray(uniform, values, count); +} + +GLint ShaderHelper::MVP() { if (!m_initialized) qFatal("Shader not initialized"); return m_mvpMatrixUniform; } -GLuint ShaderHelper::view() +GLint ShaderHelper::view() { if (!m_initialized) qFatal("Shader not initialized"); return m_viewMatrixUniform; } -GLuint ShaderHelper::model() +GLint ShaderHelper::model() { if (!m_initialized) qFatal("Shader not initialized"); return m_modelMatrixUniform; } -GLuint ShaderHelper::nModel() +GLint ShaderHelper::nModel() { if (!m_initialized) qFatal("Shader not initialized"); return m_invTransModelMatrixUniform; } -GLuint ShaderHelper::depth() +GLint ShaderHelper::depth() { if (!m_initialized) qFatal("Shader not initialized"); return m_depthMatrixUniform; } -GLuint ShaderHelper::lightP() +GLint ShaderHelper::lightP() { if (!m_initialized) qFatal("Shader not initialized"); return m_lightPositionUniform; } -GLuint ShaderHelper::lightS() +GLint ShaderHelper::lightS() { if (!m_initialized) qFatal("Shader not initialized"); return m_lightStrengthUniform; } -GLuint ShaderHelper::ambientS() +GLint ShaderHelper::ambientS() { if (!m_initialized) qFatal("Shader not initialized"); return m_ambientStrengthUniform; } -GLuint ShaderHelper::shadowQ() +GLint ShaderHelper::shadowQ() { if (!m_initialized) qFatal("Shader not initialized"); return m_shadowQualityUniform; } -GLuint ShaderHelper::color() +GLint ShaderHelper::color() { if (!m_initialized) qFatal("Shader not initialized"); return m_colorUniform; } -GLuint ShaderHelper::texture() +GLint ShaderHelper::texture() { if (!m_initialized) qFatal("Shader not initialized"); return m_textureUniform; } -GLuint ShaderHelper::shadow() +GLint ShaderHelper::shadow() { if (!m_initialized) qFatal("Shader not initialized"); return m_shadowUniform; } -GLuint ShaderHelper::gradientMin() +GLint ShaderHelper::gradientMin() { if (!m_initialized) qFatal("Shader not initialized"); return m_gradientMinUniform; } -GLuint ShaderHelper::gradientHeight() +GLint ShaderHelper::gradientHeight() { if (!m_initialized) qFatal("Shader not initialized"); return m_gradientHeightUniform; } -GLuint ShaderHelper::lightColor() +GLint ShaderHelper::lightColor() { if (!m_initialized) qFatal("Shader not initialized"); return m_lightColorUniform; } -GLuint ShaderHelper::posAtt() +GLint ShaderHelper::volumeSliceIndices() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_volumeSliceIndicesUniform; +} + +GLint ShaderHelper::colorIndex() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_colorIndexUniform; +} + +GLint ShaderHelper::cameraPositionRelativeToModel() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_cameraPositionRelativeToModelUniform; +} + +GLint ShaderHelper::color8Bit() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_color8BitUniform; +} + +GLint ShaderHelper::textureDimensions() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_textureDimensionsUniform; +} + +GLint ShaderHelper::sampleCount() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_sampleCountUniform; +} + +GLint ShaderHelper::alphaMultiplier() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_alphaMultiplierUniform; +} + +GLint ShaderHelper::preserveOpacity() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_preserveOpacityUniform; +} + +GLint ShaderHelper::maxBounds() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_maxBoundsUniform; +} + +GLint ShaderHelper::minBounds() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_minBoundsUniform; +} + +GLint ShaderHelper::sliceFrameWidth() +{ + + if (!m_initialized) + qFatal("Shader not initialized"); + return m_sliceFrameWidthUniform; +} + +GLint ShaderHelper::posAtt() { if (!m_initialized) qFatal("Shader not initialized"); return m_positionAttr; } -GLuint ShaderHelper::uvAtt() +GLint ShaderHelper::uvAtt() { if (!m_initialized) qFatal("Shader not initialized"); return m_uvAttr; } -GLuint ShaderHelper::normalAtt() +GLint ShaderHelper::normalAtt() { if (!m_initialized) qFatal("Shader not initialized"); diff --git a/src/datavisualization/utils/shaderhelper_p.h b/src/datavisualization/utils/shaderhelper_p.h index fdef0dff..812cba18 100644 --- a/src/datavisualization/utils/shaderhelper_p.h +++ b/src/datavisualization/utils/shaderhelper_p.h @@ -52,31 +52,44 @@ class ShaderHelper bool testCompile(); void bind(); void release(); - void setUniformValue(GLuint uniform, const QVector3D &value); - void setUniformValue(GLuint uniform, const QVector4D &value); - void setUniformValue(GLuint uniform, const QMatrix4x4 &value); - void setUniformValue(GLuint uniform, GLfloat value); - void setUniformValue(GLuint uniform, GLint value); - - GLuint MVP(); - GLuint view(); - GLuint model(); - GLuint nModel(); - GLuint depth(); - GLuint lightP(); - GLuint lightS(); - GLuint ambientS(); - GLuint shadowQ(); - GLuint color(); - GLuint texture(); - GLuint shadow(); - GLuint gradientMin(); - GLuint gradientHeight(); - GLuint lightColor(); - - GLuint posAtt(); - GLuint uvAtt(); - GLuint normalAtt(); + void setUniformValue(GLint uniform, const QVector2D &value); + void setUniformValue(GLint uniform, const QVector3D &value); + void setUniformValue(GLint uniform, const QVector4D &value); + void setUniformValue(GLint uniform, const QMatrix4x4 &value); + void setUniformValue(GLint uniform, GLfloat value); + void setUniformValue(GLint uniform, GLint value); + void setUniformValueArray(GLint uniform, const QVector4D *values, int count); + + GLint MVP(); + GLint view(); + GLint model(); + GLint nModel(); + GLint depth(); + GLint lightP(); + GLint lightS(); + GLint ambientS(); + GLint shadowQ(); + GLint color(); + GLint texture(); + GLint shadow(); + GLint gradientMin(); + GLint gradientHeight(); + GLint lightColor(); + GLint volumeSliceIndices(); + GLint colorIndex(); + GLint cameraPositionRelativeToModel(); + GLint color8Bit(); + GLint textureDimensions(); + GLint sampleCount(); + GLint alphaMultiplier(); + GLint preserveOpacity(); + GLint maxBounds(); + GLint minBounds(); + GLint sliceFrameWidth(); + + GLint posAtt(); + GLint uvAtt(); + GLint normalAtt(); private: QObject *m_caller; @@ -88,25 +101,36 @@ class ShaderHelper QString m_textureFile; QString m_depthTextureFile; - GLuint m_positionAttr; - GLuint m_uvAttr; - GLuint m_normalAttr; - - GLuint m_colorUniform; - GLuint m_viewMatrixUniform; - GLuint m_modelMatrixUniform; - GLuint m_invTransModelMatrixUniform; - GLuint m_depthMatrixUniform; - GLuint m_mvpMatrixUniform; - GLuint m_lightPositionUniform; - GLuint m_lightStrengthUniform; - GLuint m_ambientStrengthUniform; - GLuint m_shadowQualityUniform; - GLuint m_textureUniform; - GLuint m_shadowUniform; - GLuint m_gradientMinUniform; - GLuint m_gradientHeightUniform; - GLuint m_lightColorUniform; + GLint m_positionAttr; + GLint m_uvAttr; + GLint m_normalAttr; + + GLint m_colorUniform; + GLint m_viewMatrixUniform; + GLint m_modelMatrixUniform; + GLint m_invTransModelMatrixUniform; + GLint m_depthMatrixUniform; + GLint m_mvpMatrixUniform; + GLint m_lightPositionUniform; + GLint m_lightStrengthUniform; + GLint m_ambientStrengthUniform; + GLint m_shadowQualityUniform; + GLint m_textureUniform; + GLint m_shadowUniform; + GLint m_gradientMinUniform; + GLint m_gradientHeightUniform; + GLint m_lightColorUniform; + GLint m_volumeSliceIndicesUniform; + GLint m_colorIndexUniform; + GLint m_cameraPositionRelativeToModelUniform; + GLint m_color8BitUniform; + GLint m_textureDimensionsUniform; + GLint m_sampleCountUniform; + GLint m_alphaMultiplierUniform; + GLint m_preserveOpacityUniform; + GLint m_minBoundsUniform; + GLint m_maxBoundsUniform; + GLint m_sliceFrameWidthUniform; GLboolean m_initialized; }; diff --git a/src/datavisualization/utils/surfaceobject.cpp b/src/datavisualization/utils/surfaceobject.cpp index d999ba90..b93030b1 100644 --- a/src/datavisualization/utils/surfaceobject.cpp +++ b/src/datavisualization/utils/surfaceobject.cpp @@ -30,26 +30,31 @@ SurfaceObject::SurfaceObject(Surface3DRenderer *renderer) m_gridIndexCount(0), m_axisCacheX(renderer->m_axisCacheX), m_axisCacheY(renderer->m_axisCacheY), - m_axisCacheZ(renderer->m_axisCacheZ) - + m_axisCacheZ(renderer->m_axisCacheZ), + m_renderer(renderer), + m_returnTextureBuffer(false), + m_dataDimension(0), + m_oldDataDimension(-1) { m_indicesType = GL_UNSIGNED_INT; - initializeOpenGLFunctions(); glGenBuffers(1, &m_vertexbuffer); glGenBuffers(1, &m_normalbuffer); glGenBuffers(1, &m_uvbuffer); glGenBuffers(1, &m_elementbuffer); glGenBuffers(1, &m_gridElementbuffer); + glGenBuffers(1, &m_uvTextureBuffer); } SurfaceObject::~SurfaceObject() { - if (QOpenGLContext::currentContext()) + if (QOpenGLContext::currentContext()) { glDeleteBuffers(1, &m_gridElementbuffer); + glDeleteBuffers(1, &m_uvTextureBuffer); + } } void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ) + bool changeGeometry, bool polar, bool flipXZ) { m_columns = space.width(); m_rows = space.height(); @@ -59,6 +64,12 @@ void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QR m_surfaceType = SurfaceSmooth; + checkDirections(dataArray); + bool indicesDirty = false; + if (m_dataDimension != m_oldDataDimension) + indicesDirty = true; + m_oldDataDimension = m_dataDimension; + // Create/populate vertix table if (changeGeometry) m_vertices.resize(totalSize); @@ -68,17 +79,14 @@ void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QR uvs.resize(totalSize); int totalIndex = 0; - AxisRenderCache &xCache = flipXZ ? m_axisCacheZ : m_axisCacheX; - AxisRenderCache &zCache = flipXZ ? m_axisCacheX : m_axisCacheZ; + // Init min and max to ridiculous values + m_minY = 10000000.0; + m_maxY = -10000000.0f; for (int i = 0; i < m_rows; i++) { const QSurfaceDataRow &p = *dataArray.at(i); for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = p.at(j); - float normalizedX = xCache.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = zCache.positionAt(data.z()); - m_vertices[totalIndex] = QVector3D(normalizedX, normalizedY, normalizedZ); + getNormalizedVertex(p.at(j), m_vertices[totalIndex], polar, flipXZ); if (changeGeometry) uvs[totalIndex] = QVector2D(GLfloat(j) * uvX, GLfloat(i) * uvY); totalIndex++; @@ -95,161 +103,305 @@ void SurfaceObject::setUpSmoothData(const QSurfaceDataArray &dataArray, const QR // Create normals int rowLimit = m_rows - 1; int colLimit = m_columns - 1; - int rowColLimit = rowLimit * m_columns; - int totalLimit = totalSize - 1; if (changeGeometry) m_normals.resize(totalSize); totalIndex = 0; - const bool flipNormal = checkFlipNormal(dataArray); - for (int row = 0; row < rowColLimit; row += m_columns) { - for (int j = 0; j < colLimit; j++) { - m_normals[totalIndex++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(row + m_columns + j), - flipNormal); - } - int p = row + colLimit; - m_normals[totalIndex++] = normal(m_vertices.at(p), - m_vertices.at(p + m_columns), - m_vertices.at(p - 1), - flipNormal); - } - for (int j = rowColLimit; j < totalLimit; j++) { - m_normals[totalIndex++] = normal(m_vertices.at(j), - m_vertices.at(j - m_columns), - m_vertices.at(j + 1), - flipNormal); + + if ((m_dataDimension == BothAscending) || (m_dataDimension == XDescending)) { + for (int row = 0; row < rowLimit; row++) + createSmoothNormalBodyLine(totalIndex, row * m_columns); + createSmoothNormalUpperLine(totalIndex); + } else { // BothDescending || ZDescending + createSmoothNormalUpperLine(totalIndex); + for (int row = 1; row < m_rows; row++) + createSmoothNormalBodyLine(totalIndex, row * m_columns); } - m_normals[totalIndex++] = normal(m_vertices.at(totalLimit), - m_vertices.at(totalLimit - 1), - m_vertices.at(totalLimit - m_columns), - flipNormal); // Create indices table - if (changeGeometry) + if (changeGeometry || indicesDirty) createSmoothIndices(0, 0, colLimit, rowLimit); // Create line element indices if (changeGeometry) createSmoothGridlineIndices(0, 0, colLimit, rowLimit); - createBuffers(m_vertices, uvs, m_normals, 0, changeGeometry); + createBuffers(m_vertices, uvs, m_normals, 0); } -void SurfaceObject::updateSmoothRow(const QSurfaceDataArray &dataArray, int rowIndex) +void SurfaceObject::createSmoothNormalBodyLine(int &totalIndex, int column) +{ + int colLimit = m_columns - 1; + + if (m_dataDimension == BothAscending) { + int end = colLimit + column; + for (int j = column; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + 1), + m_vertices.at(j + m_columns)); + } + m_normals[totalIndex++] = normal(m_vertices.at(end), + m_vertices.at(end + m_columns), + m_vertices.at(end - 1)); + } else if (m_dataDimension == XDescending) { + m_normals[totalIndex++] = normal(m_vertices.at(column), + m_vertices.at(column + m_columns), + m_vertices.at(column + 1)); + int end = column + m_columns; + for (int j = column + 1; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - 1), + m_vertices.at(j + m_columns)); + } + } else if (m_dataDimension == ZDescending) { + int end = colLimit + column; + for (int j = column; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + 1), + m_vertices.at(j - m_columns)); + } + m_normals[totalIndex++] = normal(m_vertices.at(end), + m_vertices.at(end - m_columns), + m_vertices.at(end - 1)); + } else { // BothDescending + m_normals[totalIndex++] = normal(m_vertices.at(column), + m_vertices.at(column - m_columns), + m_vertices.at(column + 1)); + int end = column + m_columns; + for (int j = column + 1; j < end; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - 1), + m_vertices.at(j - m_columns)); + } + } +} + +void SurfaceObject::createSmoothNormalUpperLine(int &totalIndex) +{ + if (m_dataDimension == BothAscending) { + int lineEnd = m_rows * m_columns - 1; + for (int j = (m_rows - 1) * m_columns; j < lineEnd; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - m_columns), + m_vertices.at(j + 1)); + } + m_normals[totalIndex++] = normal(m_vertices.at(lineEnd), + m_vertices.at(lineEnd - 1), + m_vertices.at(lineEnd - m_columns)); + } else if (m_dataDimension == XDescending) { + int lineStart = (m_rows - 1) * m_columns; + int lineEnd = m_rows * m_columns; + m_normals[totalIndex++] = normal(m_vertices.at(lineStart), + m_vertices.at(lineStart + 1), + m_vertices.at(lineStart - m_columns)); + for (int j = lineStart + 1; j < lineEnd; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j - m_columns), + m_vertices.at(j - 1)); + } + } else if (m_dataDimension == ZDescending) { + int colLimit = m_columns - 1; + for (int j = 0; j < colLimit; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + m_columns), + m_vertices.at(j + 1)); + } + m_normals[totalIndex++] = normal(m_vertices.at(colLimit), + m_vertices.at(colLimit - 1), + m_vertices.at(colLimit + m_columns)); + } else { // BothDescending + m_normals[totalIndex++] = normal(m_vertices.at(0), + m_vertices.at(1), + m_vertices.at(m_columns)); + for (int j = 1; j < m_columns; j++) { + m_normals[totalIndex++] = normal(m_vertices.at(j), + m_vertices.at(j + m_columns), + m_vertices.at(j - 1)); + } + } +} + +QVector3D SurfaceObject::createSmoothNormalBodyLineItem(int x, int y) +{ + int p = y * m_columns + x; + if (m_dataDimension == BothAscending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p + 1), + m_vertices.at(p + m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p - 1)); + } + } else if (m_dataDimension == XDescending) { + if (x == 0) { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p + m_columns)); + } + } else if (m_dataDimension == ZDescending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p + 1), + m_vertices.at(p - m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p - 1)); + } + } else { // BothDescending + if (x == 0) { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p - m_columns)); + } + } +} + +QVector3D SurfaceObject::createSmoothNormalUpperLineItem(int x, int y) +{ + int p = y * m_columns + x; + if (m_dataDimension == BothAscending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p - m_columns)); + } + } else if (m_dataDimension == XDescending) { + if (x == 0) { + return normal(m_vertices.at(p), m_vertices.at(p + 1), + m_vertices.at(p - m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - m_columns), + m_vertices.at(p - 1)); + } + } else if (m_dataDimension == ZDescending) { + if (x < m_columns - 1) { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p + 1)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p - 1), + m_vertices.at(p + m_columns)); + } + } else { // BothDescending + if (x == 0) { + return normal(m_vertices.at(0), m_vertices.at(1), + m_vertices.at(m_columns)); + } else { + return normal(m_vertices.at(p), m_vertices.at(p + m_columns), + m_vertices.at(p - 1)); + } + } +} + +void SurfaceObject::smoothUVs(const QSurfaceDataArray &dataArray, + const QSurfaceDataArray &modelArray) +{ + int columns = dataArray.at(0)->size(); + int rows = dataArray.size(); + float xRangeNormalizer = dataArray.at(0)->at(columns - 1).x() - dataArray.at(0)->at(0).x(); + float zRangeNormalizer = dataArray.at(rows - 1)->at(0).z() - dataArray.at(0)->at(0).z(); + float xMin = dataArray.at(0)->at(0).x(); + float zMin = dataArray.at(0)->at(0).z(); + const bool zDescending = m_dataDimension.testFlag(SurfaceObject::ZDescending); + const bool xDescending = m_dataDimension.testFlag(SurfaceObject::XDescending); + + QVector<QVector2D> uvs; + uvs.resize(m_rows * m_columns); + int index = 0; + for (int i = 0; i < m_rows; i++) { + float y = (modelArray.at(i)->at(0).z() - zMin) / zRangeNormalizer; + if (zDescending) + y = 1.0f - y; + const QSurfaceDataRow &p = *modelArray.at(i); + for (int j = 0; j < m_columns; j++) { + float x = (p.at(j).x() - xMin) / xRangeNormalizer; + if (xDescending) + x = 1.0f - x; + uvs[index] = QVector2D(x, y); + index++; + } + } + + glBindBuffer(GL_ARRAY_BUFFER, m_uvTextureBuffer); + glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(QVector2D), + &uvs.at(0), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + m_returnTextureBuffer = true; +} + +void SurfaceObject::updateSmoothRow(const QSurfaceDataArray &dataArray, int rowIndex, bool polar) { // Update vertices int p = rowIndex * m_columns; const QSurfaceDataRow &dataRow = *dataArray.at(rowIndex); - for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = dataRow.at(j); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[p++] = QVector3D(normalizedX, normalizedY, normalizedZ); - } + for (int j = 0; j < m_columns; j++) + getNormalizedVertex(dataRow.at(j), m_vertices[p++], polar, false); // Create normals - int colLimit = m_columns - 1; + bool upwards = (m_dataDimension == BothAscending) || (m_dataDimension == XDescending); int startRow = rowIndex; - if (startRow > 0) + if ((startRow > 0) && upwards) startRow--; + int endRow = rowIndex; + if (!upwards && (rowIndex < m_rows - 1)) + endRow++; + if ((endRow == m_rows - 1) && upwards) + endRow--; int totalIndex = startRow * m_columns; - int rowLimit = (rowIndex + 1) * m_columns; - if (rowIndex == m_rows - 1) - rowLimit = rowIndex * m_columns; // The rowIndex is top most row, special handling - const bool flipNormal = checkFlipNormal(dataArray); - for (int row = totalIndex; row < rowLimit; row += m_columns) { - for (int j = 0; j < colLimit; j++) { - // One right and one up - m_normals[totalIndex++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(row + m_columns + j), - flipNormal); - } - int p = row + colLimit; - // One up and one left - m_normals[totalIndex++] = normal(m_vertices.at(p), - m_vertices.at(p + m_columns), - m_vertices.at(p - 1), - flipNormal); + if ((startRow == 0) && !upwards) { + createSmoothNormalUpperLine(totalIndex); + startRow++; } - if (rowIndex == m_rows - 1) { - // Top most line, nothing above, must have different handling. - // Take from one down and one right. Read till second-to-last - rowLimit = (rowIndex + 1) * m_columns - 1; - for (int j = rowIndex * m_columns; j < rowLimit; j++) { - m_normals[totalIndex++] = normal(m_vertices.at(j), - m_vertices.at(j - m_columns), - m_vertices.at(j + 1), - flipNormal); - } - // Top left corner. Take from one left and one down - m_normals[totalIndex++] = normal(m_vertices.at(rowLimit), - m_vertices.at(rowLimit - 1), - m_vertices.at(rowLimit - m_columns), - flipNormal); - } + for (int row = startRow; row <= endRow; row++) + createSmoothNormalBodyLine(totalIndex, row * m_columns); + + if ((rowIndex == m_rows - 1) && upwards) + createSmoothNormalUpperLine(totalIndex); } -void SurfaceObject::updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column) +void SurfaceObject::updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column, + bool polar) { // Update a vertice - const QSurfaceDataItem &data = dataArray.at(row)->at(column); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[row * m_columns + column] = QVector3D(normalizedX, normalizedY, normalizedZ); + getNormalizedVertex(dataArray.at(row)->at(column), + m_vertices[row * m_columns + column], polar, false); // Create normals + bool upwards = (m_dataDimension == BothAscending) || (m_dataDimension == XDescending); + bool rightwards = (m_dataDimension == BothAscending) || (m_dataDimension == ZDescending); int startRow = row; - if (startRow > 0) - startRow--; // Change the normal for previous row also + if ((startRow > 0) && upwards) + startRow--; + int endRow = row; + if (!upwards && (row < m_rows - 1)) + endRow++; + if ((endRow == m_rows - 1) && upwards) + endRow--; int startCol = column; - if (startCol > 0) + if ((startCol > 0) && rightwards) startCol--; - int rightCol = m_columns - 1; - int topRow = m_rows - 1; + int endCol = column; + if ((endCol < m_columns - 1) && !rightwards) + endCol++; - const bool flipNormal = checkFlipNormal(dataArray); - for (int i = startRow; i <= row; i++) { - for (int j = startCol; j <= column; j++) { + for (int i = startRow; i <= endRow; i++) { + for (int j = startCol; j <= endCol; j++) { int p = i * m_columns + j; - if (i < topRow) { - if (j < rightCol) { - // One right and one up - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + 1), - m_vertices.at(p + m_columns), - flipNormal); - } else { - // Last item, nothing on the right. One up and one left - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + m_columns), - m_vertices.at(p - 1), - flipNormal); - } - } else { - // Top most line, nothing above, must have different handling. - if (j < rightCol) { - // Take from one down and one right. Read till second-to-last - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p - m_columns), - m_vertices.at(p + 1), - flipNormal); - } else { - // Top left corner. Take from one left and one down - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p - 1), - m_vertices.at(p - m_columns), - flipNormal); - } - } - } + if ((i == 0) && !upwards) + m_normals[p] = createSmoothNormalUpperLineItem(j, i); + else if ((i == m_rows - 1) && upwards) + m_normals[p] = createSmoothNormalUpperLineItem(j, i); + else + m_normals[p] = createSmoothNormalBodyLineItem(j, i); + } } } @@ -271,15 +423,38 @@ void SurfaceObject::createSmoothIndices(int x, int y, int endX, int endY) int rowEnd = endY * m_columns; for (int row = y * m_columns; row < rowEnd; row += m_columns) { for (int j = x; j < endX; j++) { - // Left triangle - indices[p++] = row + j + 1; - indices[p++] = row + m_columns + j; - indices[p++] = row + j; - - // Right triangle - indices[p++] = row + m_columns + j + 1; - indices[p++] = row + m_columns + j; - indices[p++] = row + j + 1; + if ((m_dataDimension == BothAscending) || (m_dataDimension == BothDescending)) { + // Left triangle + indices[p++] = row + j + 1; + indices[p++] = row + m_columns + j; + indices[p++] = row + j; + + // Right triangle + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + m_columns + j; + indices[p++] = row + j + 1; + } else if (m_dataDimension == XDescending) { + // Right triangle + indices[p++] = row + m_columns + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j; + + // Left triangle + indices[p++] = row + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j + 1; + } else { + // Left triangle + indices[p++] = row + m_columns + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j; + + // Right triangle + indices[p++] = row + j; + indices[p++] = row + m_columns + j + 1; + indices[p++] = row + j + 1; + + } } } @@ -331,7 +506,7 @@ void SurfaceObject::createSmoothGridlineIndices(int x, int y, int endX, int endY } void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ) + bool changeGeometry, bool polar, bool flipXZ) { m_columns = space.width(); m_rows = space.height(); @@ -339,6 +514,12 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s GLfloat uvX = 1.0f / GLfloat(m_columns - 1); GLfloat uvY = 1.0f / GLfloat(m_rows - 1); + checkDirections(dataArray); + bool indicesDirty = false; + if (m_dataDimension != m_oldDataDimension) + indicesDirty = true; + m_oldDataDimension = m_dataDimension; + m_surfaceType = SurfaceFlat; // Create vertix table @@ -355,17 +536,14 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s int doubleColumns = m_columns * 2 - 2; int rowColLimit = rowLimit * doubleColumns; - AxisRenderCache &xCache = flipXZ ? m_axisCacheZ : m_axisCacheX; - AxisRenderCache &zCache = flipXZ ? m_axisCacheX : m_axisCacheZ; + // Init min and max to ridiculous values + m_minY = 10000000.0; + m_maxY = -10000000.0f; for (int i = 0; i < m_rows; i++) { const QSurfaceDataRow &row = *dataArray.at(i); for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = row.at(j); - float normalizedX = xCache.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = zCache.positionAt(data.z()); - m_vertices[totalIndex] = QVector3D(normalizedX, normalizedY, normalizedZ); + getNormalizedVertex(row.at(j), m_vertices[totalIndex], polar, flipXZ); if (changeGeometry) uvs[totalIndex] = QVector2D(GLfloat(j) * uvX, GLfloat(i) * uvY); @@ -389,42 +567,23 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s // Create normals & indices table GLint *indices = 0; - int p = 0; - if (changeGeometry) { + if (changeGeometry || indicesDirty) { int normalCount = 2 * colLimit * rowLimit; m_indexCount = 3 * normalCount; indices = new GLint[m_indexCount]; m_normals.resize(normalCount); } + int p = 0; totalIndex = 0; - const bool flipNormal = checkFlipNormal(dataArray); for (int row = 0, upperRow = doubleColumns; row < rowColLimit; row += doubleColumns, upperRow += doubleColumns) { for (int j = 0; j < doubleColumns; j += 2) { - // Normal for the left triangle - m_normals[totalIndex++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - - // Normal for the right triangle - m_normals[totalIndex++] = normal(m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - - if (changeGeometry) { - // Left triangle - indices[p++] = row + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j; + createNormals(totalIndex, row, upperRow, j); - // Right triangle - indices[p++] = upperRow + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j + 1; + if (changeGeometry || indicesDirty) { + createCoarseIndices(indices, p, row, upperRow, j); } } } @@ -433,12 +592,54 @@ void SurfaceObject::setUpData(const QSurfaceDataArray &dataArray, const QRect &s if (changeGeometry) createCoarseGridlineIndices(0, 0, colLimit, rowLimit); - createBuffers(m_vertices, uvs, m_normals, indices, changeGeometry); + createBuffers(m_vertices, uvs, m_normals, indices); delete[] indices; } -void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex) +void SurfaceObject::coarseUVs(const QSurfaceDataArray &dataArray, + const QSurfaceDataArray &modelArray) +{ + int columns = dataArray.at(0)->size(); + int rows = dataArray.size(); + float xRangeNormalizer = dataArray.at(0)->at(columns - 1).x() - dataArray.at(0)->at(0).x(); + float zRangeNormalizer = dataArray.at(rows - 1)->at(0).z() - dataArray.at(0)->at(0).z(); + float xMin = dataArray.at(0)->at(0).x(); + float zMin = dataArray.at(0)->at(0).z(); + const bool zDescending = m_dataDimension.testFlag(SurfaceObject::ZDescending); + const bool xDescending = m_dataDimension.testFlag(SurfaceObject::XDescending); + + QVector<QVector2D> uvs; + uvs.resize(m_rows * m_columns * 2); + int index = 0; + int colLimit = m_columns - 1; + for (int i = 0; i < m_rows; i++) { + float y = (modelArray.at(i)->at(0).z() - zMin) / zRangeNormalizer; + if (zDescending) + y = 1.0f - y; + const QSurfaceDataRow &p = *modelArray.at(i); + for (int j = 0; j < m_columns; j++) { + float x = (p.at(j).x() - xMin) / xRangeNormalizer; + if (xDescending) + x = 1.0f - x; + uvs[index] = QVector2D(x, y); + index++; + if (j > 0 && j < colLimit) { + uvs[index] = uvs[index - 1]; + index++; + } + } + } + + glBindBuffer(GL_ARRAY_BUFFER, m_uvTextureBuffer); + glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(QVector2D), + &uvs.at(0), GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + m_returnTextureBuffer = true; +} + +void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex, bool polar) { int colLimit = m_columns - 1; int doubleColumns = m_columns * 2 - 2; @@ -447,12 +648,7 @@ void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowI const QSurfaceDataRow &dataRow = *dataArray.at(rowIndex); for (int j = 0; j < m_columns; j++) { - const QSurfaceDataItem &data = dataRow.at(j); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[p++] = QVector3D(normalizedX, normalizedY, normalizedZ); - + getNormalizedVertex(dataRow.at(j), m_vertices[p++], polar, false); if (j > 0 && j < colLimit) { m_vertices[p] = m_vertices[p - 1]; p++; @@ -466,39 +662,23 @@ void SurfaceObject::updateCoarseRow(const QSurfaceDataArray &dataArray, int rowI int rowLimit = (rowIndex + 1) * doubleColumns; if (rowIndex == m_rows - 1) rowLimit = rowIndex * doubleColumns; //Topmost row, no normals - const bool flipNormal = checkFlipNormal(dataArray); for (int row = p, upperRow = p + doubleColumns; row < rowLimit; row += doubleColumns, upperRow += doubleColumns) { - for (int j = 0; j < doubleColumns; j += 2) { - // Normal for the left triangle - m_normals[p++] = normal(m_vertices.at(row + j), - m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - - // Normal for the right triangle - m_normals[p++] = normal(m_vertices.at(row + j + 1), - m_vertices.at(upperRow + j + 1), - m_vertices.at(upperRow + j), - flipNormal); - } + for (int j = 0; j < doubleColumns; j += 2) + createNormals(p, row, upperRow, j); } } -void SurfaceObject::updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column) +void SurfaceObject::updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column, + bool polar) { int colLimit = m_columns - 1; int doubleColumns = m_columns * 2 - 2; // Update a vertice int p = row * doubleColumns + column * 2 - (column > 0); - const QSurfaceDataItem &data = dataArray.at(row)->at(column); - float normalizedX = m_axisCacheX.positionAt(data.x()); - float normalizedY = m_axisCacheY.positionAt(data.y()); - float normalizedZ = m_axisCacheZ.positionAt(data.z()); - m_vertices[p] = QVector3D(normalizedX, normalizedY, normalizedZ); - p++; + getNormalizedVertex(dataArray.at(row)->at(column), m_vertices[p++], polar, false); if (column > 0 && column < colLimit) m_vertices[p] = m_vertices[p - 1]; @@ -515,27 +695,15 @@ void SurfaceObject::updateCoarseItem(const QSurfaceDataArray &dataArray, int row if (column == m_columns - 1) column--; - const bool flipNormal = checkFlipNormal(dataArray); for (int i = startRow; i <= row; i++) { for (int j = startCol; j <= column; j++) { p = i * doubleColumns + j * 2; - // Normal for the left triangle - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + 1), - m_vertices.at(p + doubleColumns), - flipNormal); - p++; - - // Normal for the right triangle - m_normals[p] = normal(m_vertices.at(p), - m_vertices.at(p + doubleColumns), - m_vertices.at(p + doubleColumns - 1), - flipNormal); + createNormals(p, i * doubleColumns, (i + 1) * doubleColumns, j * 2); } } } -void SurfaceObject::createCoarseIndices(int x, int y, int columns, int rows) +void SurfaceObject::createCoarseSubSection(int x, int y, int columns, int rows) { if (columns > m_columns) columns = m_columns; @@ -557,17 +725,8 @@ void SurfaceObject::createCoarseIndices(int x, int y, int columns, int rows) for (int row = y * doubleColumns, upperRow = (y + 1) * doubleColumns; row < rowColLimit; row += doubleColumns, upperRow += doubleColumns) { - for (int j = 2 * x; j < doubleColumnsLimit; j += 2) { - // Left triangle - indices[p++] = row + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j; - - // Right triangle - indices[p++] = upperRow + j + 1; - indices[p++] = upperRow + j; - indices[p++] = row + j + 1; - } + for (int j = 2 * x; j < doubleColumnsLimit; j += 2) + createCoarseIndices(indices, p, row, upperRow, j); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementbuffer); @@ -631,12 +790,11 @@ void SurfaceObject::createCoarseGridlineIndices(int x, int y, int endX, int endY void SurfaceObject::uploadBuffers() { QVector<QVector2D> uvs; // Empty dummy - createBuffers(m_vertices, uvs, m_normals, 0, false); + createBuffers(m_vertices, uvs, m_normals, 0); } void SurfaceObject::createBuffers(const QVector<QVector3D> &vertices, const QVector<QVector2D> &uvs, - const QVector<QVector3D> &normals, const GLint *indices, - bool changeGeometry) + const QVector<QVector3D> &normals, const GLint *indices) { // Move to buffers glBindBuffer(GL_ARRAY_BUFFER, m_vertexbuffer); @@ -647,30 +805,62 @@ void SurfaceObject::createBuffers(const QVector<QVector3D> &vertices, const QVec glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(QVector3D), &normals.at(0), GL_DYNAMIC_DRAW); - if (changeGeometry) { + if (uvs.size()) { glBindBuffer(GL_ARRAY_BUFFER, m_uvbuffer); glBufferData(GL_ARRAY_BUFFER, uvs.size() * sizeof(QVector2D), &uvs.at(0), GL_STATIC_DRAW); + } - if (indices) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementbuffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount * sizeof(GLint), - indices, GL_STATIC_DRAW); - } - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + if (indices) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementbuffer); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexCount * sizeof(GLint), + indices, GL_STATIC_DRAW); } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); m_meshDataLoaded = true; } -bool SurfaceObject::checkFlipNormal(const QSurfaceDataArray &array) +void SurfaceObject::checkDirections(const QSurfaceDataArray &array) +{ + m_dataDimension = BothAscending; + + if (array.at(0)->at(0).x() > array.at(0)->at(array.at(0)->size() - 1).x()) + m_dataDimension |= XDescending; + if (m_axisCacheX.reversed()) + m_dataDimension ^= XDescending; + + if (array.at(0)->at(0).z() > array.at(array.size() - 1)->at(0).z()) + m_dataDimension |= ZDescending; + if (m_axisCacheZ.reversed()) + m_dataDimension ^= ZDescending; +} + +void SurfaceObject::getNormalizedVertex(const QSurfaceDataItem &data, QVector3D &vertex, + bool polar, bool flipXZ) { - const bool ascendingX = array.at(0)->at(0).x() < array.at(0)->at(array.at(0)->size() - 1).x(); - const bool ascendingZ = array.at(0)->at(0).z() < array.at(array.size() - 1)->at(0).z(); - return ascendingX != ascendingZ; + float normalizedX; + float normalizedZ; + if (polar) { + // Slice don't use polar, so don't care about flip + m_renderer->calculatePolarXZ(data.position(), normalizedX, normalizedZ); + } else { + if (flipXZ) { + normalizedX = m_axisCacheZ.positionAt(data.x()); + normalizedZ = m_axisCacheX.positionAt(data.z()); + } else { + normalizedX = m_axisCacheX.positionAt(data.x()); + normalizedZ = m_axisCacheZ.positionAt(data.z()); + } + } + float normalizedY = m_axisCacheY.positionAt(data.y()); + m_minY = qMin(normalizedY, m_minY); + m_maxY = qMax(normalizedY, m_maxY); + vertex.setX(normalizedX); + vertex.setY(normalizedY); + vertex.setZ(normalizedZ); } GLuint SurfaceObject::gridElementBuf() @@ -680,6 +870,17 @@ GLuint SurfaceObject::gridElementBuf() return m_gridElementbuffer; } +GLuint SurfaceObject::uvBuf() +{ + if (!m_meshDataLoaded) + qFatal("No loaded object"); + + if (m_returnTextureBuffer) + return m_uvTextureBuffer; + else + return m_uvbuffer; +} + GLuint SurfaceObject::gridIndexCount() { return m_gridIndexCount; @@ -707,14 +908,74 @@ void SurfaceObject::clear() m_normals.clear(); } -QVector3D SurfaceObject::normal(const QVector3D &a, const QVector3D &b, const QVector3D &c, - bool flipNormal) +void SurfaceObject::createCoarseIndices(GLint *indices, int &p, int row, int upperRow, int j) +{ + if ((m_dataDimension == BothAscending) || (m_dataDimension == BothDescending)) { + // Left triangle + indices[p++] = row + j + 1; + indices[p++] = upperRow + j; + indices[p++] = row + j; + + // Right triangle + indices[p++] = upperRow + j + 1; + indices[p++] = upperRow + j; + indices[p++] = row + j + 1; + } else if (m_dataDimension == XDescending) { + indices[p++] = upperRow + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j; + + indices[p++] = row + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j + 1; + } else { + // Left triangle + indices[p++] = upperRow + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j; + + // Right triangle + indices[p++] = row + j; + indices[p++] = upperRow + j + 1; + indices[p++] = row + j + 1; + } +} + +void SurfaceObject::createNormals(int &p, int row, int upperRow, int j) +{ + if ((m_dataDimension == BothAscending) || (m_dataDimension == BothDescending)) { + m_normals[p++] = normal(m_vertices.at(row + j), + m_vertices.at(row + j + 1), + m_vertices.at(upperRow + j)); + + m_normals[p++] = normal(m_vertices.at(row + j + 1), + m_vertices.at(upperRow + j + 1), + m_vertices.at(upperRow + j)); + } else if (m_dataDimension == XDescending) { + m_normals[p++] = normal(m_vertices.at(row + j), + m_vertices.at(upperRow + j), + m_vertices.at(upperRow + j + 1)); + + m_normals[p++] = normal(m_vertices.at(row + j + 1), + m_vertices.at(row + j), + m_vertices.at(upperRow + j + 1)); + } else { + m_normals[p++] = normal(m_vertices.at(row + j), + m_vertices.at(upperRow + j), + m_vertices.at(upperRow + j + 1)); + + m_normals[p++] = normal(m_vertices.at(row + j + 1), + m_vertices.at(row + j), + m_vertices.at(upperRow + j + 1)); + } +} + +QVector3D SurfaceObject::normal(const QVector3D &a, const QVector3D &b, const QVector3D &c) { QVector3D v1 = b - a; QVector3D v2 = c - a; QVector3D normal = QVector3D::crossProduct(v1, v2); - if (flipNormal) - normal *= -1; + return normal; } diff --git a/src/datavisualization/utils/surfaceobject_p.h b/src/datavisualization/utils/surfaceobject_p.h index 9c18dcb2..e7b61310 100644 --- a/src/datavisualization/utils/surfaceobject_p.h +++ b/src/datavisualization/utils/surfaceobject_p.h @@ -49,34 +49,55 @@ public: Undefined }; + enum DataDimension { + BothAscending = 0, + XDescending = 1, + ZDescending = 2, + BothDescending = XDescending | ZDescending + }; + Q_DECLARE_FLAGS(DataDimensions, DataDimension) + public: SurfaceObject(Surface3DRenderer *renderer); - ~SurfaceObject(); + virtual ~SurfaceObject(); void setUpData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ = false); + bool changeGeometry, bool polar, bool flipXZ = false); void setUpSmoothData(const QSurfaceDataArray &dataArray, const QRect &space, - bool changeGeometry, bool flipXZ = false); - void updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex); - void updateSmoothRow(const QSurfaceDataArray &dataArray, int startRow); - void updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column); - void updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column); + bool changeGeometry, bool polar, bool flipXZ = false); + void smoothUVs(const QSurfaceDataArray &dataArray, const QSurfaceDataArray &modelArray); + void coarseUVs(const QSurfaceDataArray &dataArray, const QSurfaceDataArray &modelArray); + void updateCoarseRow(const QSurfaceDataArray &dataArray, int rowIndex, bool polar); + void updateSmoothRow(const QSurfaceDataArray &dataArray, int startRow, bool polar); + void updateSmoothItem(const QSurfaceDataArray &dataArray, int row, int column, bool polar); + void updateCoarseItem(const QSurfaceDataArray &dataArray, int row, int column, bool polar); void createSmoothIndices(int x, int y, int endX, int endY); - void createCoarseIndices(int x, int y, int columns, int rows); + void createCoarseSubSection(int x, int y, int columns, int rows); void createSmoothGridlineIndices(int x, int y, int endX, int endY); void createCoarseGridlineIndices(int x, int y, int endX, int endY); void uploadBuffers(); GLuint gridElementBuf(); + GLuint uvBuf(); GLuint gridIndexCount(); QVector3D vertexAt(int column, int row); void clear(); + float minYValue() const { return m_minY; } + float maxYValue() const { return m_maxY; } + inline void activateSurfaceTexture(bool value) { m_returnTextureBuffer = value; } private: - QVector3D normal(const QVector3D &a, const QVector3D &b, const QVector3D &c, bool flipNormal); + void createCoarseIndices(GLint *indices, int &p, int row, int upperRow, int j); + void createNormals(int &p, int row, int upperRow, int j); + void createSmoothNormalBodyLine(int &totalIndex, int column); + void createSmoothNormalUpperLine(int &totalIndex); + QVector3D createSmoothNormalBodyLineItem(int x, int y); + QVector3D createSmoothNormalUpperLineItem(int x, int y); + QVector3D normal(const QVector3D &a, const QVector3D &b, const QVector3D &c); void createBuffers(const QVector<QVector3D> &vertices, const QVector<QVector2D> &uvs, - const QVector<QVector3D> &normals, const GLint *indices, - bool changeGeometry); - bool checkFlipNormal(const QSurfaceDataArray &array); + const QVector<QVector3D> &normals, const GLint *indices); + void checkDirections(const QSurfaceDataArray &array); + inline void getNormalizedVertex(const QSurfaceDataItem &data, QVector3D &vertex, bool polar, + bool flipXZ); private: SurfaceType m_surfaceType; @@ -90,6 +111,13 @@ private: AxisRenderCache &m_axisCacheX; AxisRenderCache &m_axisCacheY; AxisRenderCache &m_axisCacheZ; + Surface3DRenderer *m_renderer; + float m_minY; + float m_maxY; + GLuint m_uvTextureBuffer; + bool m_returnTextureBuffer; + SurfaceObject::DataDimensions m_dataDimension; + SurfaceObject::DataDimensions m_oldDataDimension; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/texturehelper.cpp b/src/datavisualization/utils/texturehelper.cpp index 2a2a89dd..3944fb0c 100644 --- a/src/datavisualization/utils/texturehelper.cpp +++ b/src/datavisualization/utils/texturehelper.cpp @@ -21,12 +21,29 @@ #include <QtGui/QImage> #include <QtGui/QPainter> +#include <QtCore/QTime> QT_BEGIN_NAMESPACE_DATAVISUALIZATION +// Defined in shaderhelper.cpp +extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg); + TextureHelper::TextureHelper() { initializeOpenGLFunctions(); +#if !defined(QT_OPENGL_ES_2) + if (!Utils::isOpenGLES()) { + // Discard warnings about deprecated functions + QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs); + + m_openGlFunctions_2_1 = + QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_1>(); + m_openGlFunctions_2_1->initializeOpenGLFunctions(); + + // Restore original message handler + qInstallMessageHandler(handler); + } +#endif } TextureHelper::~TextureHelper() @@ -41,17 +58,16 @@ GLuint TextureHelper::create2DTexture(const QImage &image, bool useTrilinearFilt QImage texImage = image; -#if defined(QT_OPENGL_ES_2) - GLuint temp; - GLuint imageWidth = Utils::getNearestPowerOfTwo(image.width(), temp); - GLuint imageHeight = Utils::getNearestPowerOfTwo(image.height(), temp); - if (smoothScale) { - texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, - Qt::SmoothTransformation); - } else { - texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio); + if (!Utils::isOpenGLES()) { + GLuint imageWidth = Utils::getNearestPowerOfTwo(image.width()); + GLuint imageHeight = Utils::getNearestPowerOfTwo(image.height()); + if (smoothScale) { + texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + } else { + texImage = image.scaled(imageWidth, imageHeight, Qt::IgnoreAspectRatio); + } } -#endif GLuint textureId; glGenTextures(1, &textureId); @@ -73,6 +89,53 @@ GLuint TextureHelper::create2DTexture(const QImage &image, bool useTrilinearFilt if (clampY) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); + + return textureId; +} + +GLuint TextureHelper::create3DTexture(const QVector<uchar> *data, int width, int height, int depth, + QImage::Format dataFormat) +{ + if (Utils::isOpenGLES() || !width || !height || !depth) + return 0; + + GLuint textureId = 0; +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(dataFormat) + Q_UNUSED(data) +#else + glEnable(GL_TEXTURE_3D); + + glGenTextures(1, &textureId); + glBindTexture(GL_TEXTURE_3D, textureId); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + GLenum status = glGetError(); + // glGetError docs advise to call glGetError in loop to clear all error flags + while (status) + status = glGetError(); + + GLint internalFormat = 4; + GLint format = GL_BGRA; + if (dataFormat == QImage::Format_Indexed8) { + internalFormat = 1; + format = GL_RED; + // Align width to 32bits + width = width + width % 4; + } + m_openGlFunctions_2_1->glTexImage3D(GL_TEXTURE_3D, 0, internalFormat, width, height, depth, 0, + format, GL_UNSIGNED_BYTE, data->constData()); + status = glGetError(); + if (status) + qWarning() << __FUNCTION__ << "3D texture creation failed:" << status; + + glBindTexture(GL_TEXTURE_3D, 0); + glDisable(GL_TEXTURE_3D); +#endif return textureId; } @@ -113,14 +176,27 @@ GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuf glBindTexture(GL_TEXTURE_2D, 0); // Create render buffer - if (!depthBuffer) - glGenRenderbuffers(1, &depthBuffer); + if (depthBuffer) + glDeleteRenderbuffers(1, &depthBuffer); + + glGenRenderbuffers(1, &depthBuffer); glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer); -#if !defined(QT_OPENGL_ES_2) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.width(), size.height()); -#else - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width(), size.height()); -#endif + GLenum status = glGetError(); + // glGetError docs advise to call glGetError in loop to clear all error flags + while (status) + status = glGetError(); + if (Utils::isOpenGLES()) + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size.width(), size.height()); + else + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.width(), size.height()); + + status = glGetError(); + if (status) { + qCritical() << "Selection texture render buffer creation failed:" << status; + glDeleteTextures(1, &textureid); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + return 0; + } glBindRenderbuffer(GL_RENDERBUFFER, 0); // Create frame buffer @@ -134,10 +210,11 @@ GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuf glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer); // Verify that the frame buffer is complete - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { - qCritical() << "Frame buffer creation failed" << status; - return 0; + qCritical() << "Selection texture frame buffer creation failed:" << status; + glDeleteTextures(1, &textureid); + textureid = 0; } // Restore the default framebuffer @@ -146,6 +223,31 @@ GLuint TextureHelper::createSelectionTexture(const QSize &size, GLuint &frameBuf return textureid; } +GLuint TextureHelper::createCursorPositionTexture(const QSize &size, GLuint &frameBuffer) +{ + GLuint textureid; + glGenTextures(1, &textureid); + glBindTexture(GL_TEXTURE_2D, textureid); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0, GL_RGBA, + GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, &frameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + textureid, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + qCritical() << "Cursor position mapper frame buffer creation failed:" << status; + glDeleteTextures(1, &textureid); + textureid = 0; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return textureid; +} + GLuint TextureHelper::createUniformTexture(const QColor &color) { QImage image(QSize(int(uniformTextureWidth), int(uniformTextureHeight)), @@ -170,76 +272,64 @@ GLuint TextureHelper::createGradientTexture(const QLinearGradient &gradient) return create2DTexture(image, false, true, false, true); } -#if !defined(QT_OPENGL_ES_2) GLuint TextureHelper::createDepthTexture(const QSize &size, GLuint textureSize) { - GLuint depthtextureid; - - // Create depth texture for the shadow mapping - glGenTextures(1, &depthtextureid); - glBindTexture(GL_TEXTURE_2D, depthtextureid); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width() * textureSize, - size.height() * textureSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); - glBindTexture(GL_TEXTURE_2D, 0); - + GLuint depthtextureid = 0; +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(size) + Q_UNUSED(textureSize) +#else + if (!Utils::isOpenGLES()) { + // Create depth texture for the shadow mapping + glGenTextures(1, &depthtextureid); + glBindTexture(GL_TEXTURE_2D, depthtextureid); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width() * textureSize, + size.height() * textureSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + } +#endif return depthtextureid; } -#endif -#if !defined(QT_OPENGL_ES_2) GLuint TextureHelper::createDepthTextureFrameBuffer(const QSize &size, GLuint &frameBuffer, GLuint textureSize) { GLuint depthtextureid = createDepthTexture(size, textureSize); +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(frameBuffer) +#else + if (!Utils::isOpenGLES()) { + // Create frame buffer + if (!frameBuffer) + glGenFramebuffers(1, &frameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); + + // Attach texture to depth attachment + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthtextureid, 0); + + m_openGlFunctions_2_1->glDrawBuffer(GL_NONE); + m_openGlFunctions_2_1->glReadBuffer(GL_NONE); + + // Verify that the frame buffer is complete + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + qCritical() << "Depth texture frame buffer creation failed" << status; + glDeleteTextures(1, &depthtextureid); + depthtextureid = 0; + } - // Create frame buffer - if (!frameBuffer) - glGenFramebuffers(1, &frameBuffer); - glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); - - // Attach texture to depth attachment - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthtextureid, 0); - - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - - // Verify that the frame buffer is complete - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) { - qCritical() << "Frame buffer creation failed" << status; - return 0; + // Restore the default framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); } - - // Restore the default framebuffer - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - return depthtextureid; -} #endif - -#if !defined(QT_OPENGL_ES_2) -void TextureHelper::fillDepthTexture(GLuint texture,const QSize &size, GLuint textureSize, - GLfloat value) -{ - int nItems = size.width() * textureSize * size.height() * textureSize; - GLfloat *bits = new GLfloat[nItems]; - for (int i = 0; i < nItems; i++) - bits[i] = value; - - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, size.width() * textureSize, - size.height() * textureSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, bits); - glBindTexture(GL_TEXTURE_2D, 0); - - delete[] bits; + return depthtextureid; } -#endif void TextureHelper::deleteTexture(GLuint *texture) { diff --git a/src/datavisualization/utils/texturehelper_p.h b/src/datavisualization/utils/texturehelper_p.h index aec137a4..6c0aa3de 100644 --- a/src/datavisualization/utils/texturehelper_p.h +++ b/src/datavisualization/utils/texturehelper_p.h @@ -32,6 +32,10 @@ #include "datavisualizationglobal_p.h" #include <QtGui/QRgb> #include <QtGui/QLinearGradient> +#if !defined(QT_OPENGL_ES_2) +// 3D Textures are not supported by ES set +# include <QtGui/QOpenGLFunctions_2_1> +#endif QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -44,17 +48,17 @@ class TextureHelper : protected QOpenGLFunctions // Ownership of created texture is transferred to caller GLuint create2DTexture(const QImage &image, bool useTrilinearFiltering = false, bool convert = true, bool smoothScale = true, bool clampY = false); + GLuint create3DTexture(const QVector<uchar> *data, int width, int height, int depth, + QImage::Format dataFormat); GLuint createCubeMapTexture(const QImage &image, bool useTrilinearFiltering = false); // Returns selection texture and inserts generated framebuffers to framebuffer parameters GLuint createSelectionTexture(const QSize &size, GLuint &frameBuffer, GLuint &depthBuffer); + GLuint createCursorPositionTexture(const QSize &size, GLuint &frameBuffer); GLuint createUniformTexture(const QColor &color); GLuint createGradientTexture(const QLinearGradient &gradient); -#if !defined(QT_OPENGL_ES_2) GLuint createDepthTexture(const QSize &size, GLuint textureSize); // Returns depth texture and inserts generated framebuffer to parameter GLuint createDepthTextureFrameBuffer(const QSize &size, GLuint &frameBuffer, GLuint textureSize); - void fillDepthTexture(GLuint texture, const QSize &size, GLuint textureSize, GLfloat value); -#endif void deleteTexture(GLuint *texture); private: @@ -62,9 +66,13 @@ class TextureHelper : protected QOpenGLFunctions void convertToGLFormatHelper(QImage &dstImage, const QImage &srcImage, GLenum texture_format); QRgb qt_gl_convertToGLFormatHelper(QRgb src_pixel, GLenum texture_format); +#if !defined(QT_OPENGL_ES_2) + QOpenGLFunctions_2_1 *m_openGlFunctions_2_1; // Not owned +#endif friend class Bars3DRenderer; friend class Surface3DRenderer; friend class Scatter3DRenderer; + friend class Abstract3DRenderer; }; QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/utils.cpp b/src/datavisualization/utils/utils.cpp index 2368d86a..cb64eb8f 100644 --- a/src/datavisualization/utils/utils.cpp +++ b/src/datavisualization/utils/utils.cpp @@ -17,6 +17,7 @@ ****************************************************************************/ #include "utils_p.h" +#include "qutils.h" #include <QtGui/QPainter> @@ -25,11 +26,12 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION #define NUM_IN_POWER(y, x) for (;y<x;y<<=1) #define MIN_POWER 2 -GLuint Utils::getNearestPowerOfTwo(GLuint value, GLuint &padding) +static GLint maxTextureSize = 0; // Safe, as all instances have the same texture size + +GLuint Utils::getNearestPowerOfTwo(GLuint value) { GLuint powOfTwoValue = MIN_POWER; NUM_IN_POWER(powOfTwoValue, value); - padding = powOfTwoValue - value; return powOfTwoValue; } @@ -54,35 +56,76 @@ QImage Utils::printTextToImage(const QFont &font, const QString &text, const QCo const QColor &txtColor, bool labelBackground, bool borders, int maxLabelWidth) { + if (maxTextureSize == 0) { + QOpenGLContext::currentContext()->functions()->glGetIntegerv( + GL_MAX_TEXTURE_SIZE, &maxTextureSize); + } GLuint paddingWidth = 20; GLuint paddingHeight = 20; + GLuint prePadding = 20; + GLint targetWidth = maxTextureSize; + // Calculate text dimensions QFont valueFont = font; valueFont.setPointSize(textureFontSize); QFontMetrics valueFM(valueFont); int valueStrWidth = valueFM.width(text); - if (maxLabelWidth && labelBackground) + + // ES2 needs to use maxLabelWidth always (when given) because of the power of 2 -issue. + if (maxLabelWidth && (labelBackground || Utils::isOpenGLES())) valueStrWidth = maxLabelWidth; int valueStrHeight = valueFM.height(); valueStrWidth += paddingWidth / 2; // Fix clipping problem with skewed fonts (italic or italic-style) QSize labelSize; + qreal fontRatio = 1.0; -#if defined(QT_OPENGL_ES_2) - // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly. - // Add some padding before converting to power of two to avoid too tight fit - GLuint prePadding = 5; - // ES2 needs to use this always (when given) because of the power of 2 -issue. - if (maxLabelWidth) - valueStrWidth = maxLabelWidth + paddingWidth / 2; - labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding); - labelSize.setWidth(getNearestPowerOfTwo(labelSize.width(), paddingWidth)); - labelSize.setHeight(getNearestPowerOfTwo(labelSize.height(), paddingHeight)); -#else - if (!labelBackground) - labelSize = QSize(valueStrWidth, valueStrHeight); - else - labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2); -#endif + if (Utils::isOpenGLES()) { + // Test if text with slighly smaller font would fit into one step smaller texture + // ie. if the text is just exceeded the smaller texture boundary, it would + // make a label with large empty space + uint testWidth = getNearestPowerOfTwo(valueStrWidth + prePadding) >> 1; + int diffToFit = (valueStrWidth + prePadding) - testWidth; + int maxSqueeze = int((valueStrWidth + prePadding) * 0.1f); + if (diffToFit < maxSqueeze && maxTextureSize > GLint(testWidth)) + targetWidth = testWidth; + } + + bool sizeOk = false; + int currentFontSize = textureFontSize; + do { + if (Utils::isOpenGLES()) { + // ES2 can't handle textures with dimensions not in power of 2. Resize labels accordingly. + // Add some padding before converting to power of two to avoid too tight fit + labelSize = QSize(valueStrWidth + prePadding, valueStrHeight + prePadding); + labelSize.setWidth(getNearestPowerOfTwo(labelSize.width())); + labelSize.setHeight(getNearestPowerOfTwo(labelSize.height())); + } else { + if (!labelBackground) + labelSize = QSize(valueStrWidth, valueStrHeight); + else + labelSize = QSize(valueStrWidth + paddingWidth * 2, valueStrHeight + paddingHeight * 2); + } + + if (!maxTextureSize || (labelSize.width() <= maxTextureSize + && (labelSize.width() <= targetWidth || !Utils::isOpenGLES()))) { + // Make sure the label is not too wide + sizeOk = true; + } else if (--currentFontSize == 4) { + qCritical() << "Label" << text << "is too long to be generated."; + return QImage(); + } else { + fontRatio = (qreal)currentFontSize / (qreal)textureFontSize; + // Reduce font size and try again + valueFont.setPointSize(currentFontSize); + QFontMetrics currentValueFM(valueFont); + if (maxLabelWidth && (labelBackground || Utils::isOpenGLES())) + valueStrWidth = maxLabelWidth * fontRatio; + else + valueStrWidth = currentValueFM.width(text); + valueStrHeight = currentValueFM.height(); + valueStrWidth += paddingWidth / 2; + } + } while (!sizeOk); // Create image QImage image = QImage(labelSize, QImage::Format_ARGB32); @@ -96,27 +139,30 @@ QImage Utils::printTextToImage(const QFont &font, const QString &text, const QCo painter.setFont(valueFont); if (!labelBackground) { painter.setPen(txtColor); -#if defined(QT_OPENGL_ES_2) - painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, - (labelSize.height() - valueStrHeight) / 2.0f, - valueStrWidth, valueStrHeight, - Qt::AlignCenter | Qt::AlignVCenter, - text); -#else - painter.drawText(0, 0, - valueStrWidth, valueStrHeight, - Qt::AlignCenter | Qt::AlignVCenter, - text); -#endif + if (Utils::isOpenGLES()) { + painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, + (labelSize.height() - valueStrHeight) / 2.0f, + valueStrWidth, valueStrHeight, + Qt::AlignCenter | Qt::AlignVCenter, + text); + } else { + painter.drawText(0, 0, + valueStrWidth, valueStrHeight, + Qt::AlignCenter | Qt::AlignVCenter, + text); + } } else { painter.setBrush(QBrush(bgrColor)); + qreal radius = 10.0 * fontRatio; if (borders) { - painter.setPen(QPen(QBrush(txtColor), 5, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); - painter.drawRoundedRect(5, 5, labelSize.width() - 10, labelSize.height() - 10, - 10.0, 10.0); + painter.setPen(QPen(QBrush(txtColor), 5.0 * fontRatio, + Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); + painter.drawRoundedRect(5, 5, + labelSize.width() - 10, labelSize.height() - 10, + radius, radius); } else { painter.setPen(bgrColor); - painter.drawRoundedRect(0, 0, labelSize.width(), labelSize.height(), 10.0, 10.0); + painter.drawRoundedRect(0, 0, labelSize.width(), labelSize.height(), radius, radius); } painter.setPen(txtColor); painter.drawText((labelSize.width() - valueStrWidth) / 2.0f, @@ -133,62 +179,74 @@ QVector4D Utils::getSelection(QPoint mousepos, int height) // This is the only one that works with OpenGL ES 2.0, so we're forced to use it // Item count will be limited to 256*256*256 GLubyte pixel[4] = {255, 255, 255, 255}; - glReadPixels(mousepos.x(), height - mousepos.y(), 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - (void *)pixel); + QOpenGLContext::currentContext()->functions()->glReadPixels(mousepos.x(), height - mousepos.y(), + 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, + (void *)pixel); QVector4D selectedColor(pixel[0], pixel[1], pixel[2], pixel[3]); return selectedColor; } -QImage Utils::getGradientImage(const QLinearGradient &gradient) +QImage Utils::getGradientImage(QLinearGradient &gradient) { - QImage image(QSize(1, 101), QImage::Format_RGB32); + QImage image(QSize(gradientTextureWidth, gradientTextureHeight), QImage::Format_RGB32); + gradient.setFinalStop(qreal(gradientTextureWidth), qreal(gradientTextureHeight)); + gradient.setStart(0.0, 0.0); + QPainter pmp(&image); pmp.setBrush(QBrush(gradient)); pmp.setPen(Qt::NoPen); - pmp.drawRect(0, 0, 1, 101); + pmp.drawRect(0, 0, int(gradientTextureWidth), int(gradientTextureHeight)); return image; } -Utils::ParamType Utils::mapFormatCharToParamType(const QChar &formatChar) +Utils::ParamType Utils::preParseFormat(const QString &format, QString &preStr, QString &postStr, + int &precision, char &formatSpec) { - ParamType retVal = ParamTypeUnknown; - if (formatChar == QLatin1Char('d') - || formatChar == QLatin1Char('i') - || formatChar == QLatin1Char('c')) { - retVal = ParamTypeInt; - } else if (formatChar == QLatin1Char('u') - || formatChar == QLatin1Char('o') - || formatChar == QLatin1Char('x') - || formatChar == QLatin1Char('X')) { - retVal = ParamTypeUInt; - } else if (formatChar == QLatin1Char('f') - || formatChar == QLatin1Char('F') - || formatChar == QLatin1Char('e') - || formatChar == QLatin1Char('E') - || formatChar == QLatin1Char('g') - || formatChar == QLatin1Char('G')) { - retVal = ParamTypeReal; + static QRegExp formatMatcher(QStringLiteral("^([^%]*)%([\\-\\+#\\s\\d\\.lhjztL]*)([dicuoxfegXFEG])(.*)$")); + static QRegExp precisionMatcher(QStringLiteral("\\.(\\d+)")); + + Utils::ParamType retVal; + + if (formatMatcher.indexIn(format, 0) != -1) { + preStr = formatMatcher.cap(1); + // Six and 'g' are defaults in Qt API + precision = 6; + if (!formatMatcher.cap(2).isEmpty()) { + if (precisionMatcher.indexIn(formatMatcher.cap(2), 0) != -1) + precision = precisionMatcher.cap(1).toInt(); + } + if (formatMatcher.cap(3).isEmpty()) + formatSpec = 'g'; + else + formatSpec = formatMatcher.cap(3).at(0).toLatin1(); + postStr = formatMatcher.cap(4); + retVal = mapFormatCharToParamType(formatSpec); + } else { + retVal = ParamTypeUnknown; + // The out parameters are irrelevant in unknown case } return retVal; } -Utils::ParamType Utils::findFormatParamType(const QString &format) +Utils::ParamType Utils::mapFormatCharToParamType(char formatSpec) { - static QRegExp formatMatcher(QStringLiteral("%[\\-\\+#\\s\\d\\.lhjztL]*([dicuoxfegXFEG])")); - - if (formatMatcher.indexIn(format, 0) != -1) { - QString capStr = formatMatcher.cap(1); - if (capStr.isEmpty()) - return ParamTypeUnknown; - else - return mapFormatCharToParamType(capStr.at(0)); + ParamType retVal = ParamTypeUnknown; + if (formatSpec == 'd' || formatSpec == 'i' || formatSpec == 'c') { + retVal = ParamTypeInt; + } else if (formatSpec == 'u' || formatSpec == 'o' + || formatSpec == 'x'|| formatSpec == 'X') { + retVal = ParamTypeUInt; + } else if (formatSpec == 'f' || formatSpec == 'F' + || formatSpec == 'e' || formatSpec == 'E' + || formatSpec == 'g' || formatSpec == 'G') { + retVal = ParamTypeReal; } - return ParamTypeUnknown; + return retVal; } -QString Utils::formatLabel(const QByteArray &format, ParamType paramType, qreal value) +QString Utils::formatLabelSprintf(const QByteArray &format, Utils::ParamType paramType, qreal value) { switch (paramType) { case ParamTypeInt: @@ -198,7 +256,24 @@ QString Utils::formatLabel(const QByteArray &format, ParamType paramType, qreal case ParamTypeReal: return QString().sprintf(format, value); default: - return QString::fromUtf8(format); // To detect errors + // Return format string to detect errors. Bars selection label logic also depends on this. + return QString::fromUtf8(format); + } +} + +QString Utils::formatLabelLocalized(Utils::ParamType paramType, qreal value, + const QLocale &locale, const QString &preStr, const QString &postStr, + int precision, char formatSpec, const QByteArray &format) +{ + switch (paramType) { + case ParamTypeInt: + case ParamTypeUInt: + return preStr + locale.toString(qint64(value)) + postStr; + case ParamTypeReal: + return preStr + locale.toString(value, formatSpec, precision) + postStr; + default: + // Return format string to detect errors. Bars selection label logic also depends on this. + return QString::fromUtf8(format); } } @@ -238,4 +313,41 @@ QQuaternion Utils::calculateRotation(const QVector3D &xyzRotations) return totalRotation; } +bool Utils::isOpenGLES() +{ +#if defined(QT_OPENGL_ES_2) + return true; +#elif (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) + return false; +#else + static bool resolved = false; + static bool isES = false; + if (!resolved) { + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QWindow *dummySurface = 0; + if (!ctx) { + QSurfaceFormat surfaceFormat = qDefaultSurfaceFormat(); + dummySurface = new QWindow(); + dummySurface->setSurfaceType(QWindow::OpenGLSurface); + dummySurface->setFormat(surfaceFormat); + dummySurface->create(); + ctx = new QOpenGLContext; + ctx->setFormat(surfaceFormat); + ctx->create(); + ctx->makeCurrent(dummySurface); + } + + isES = ctx->isOpenGLES(); + resolved = true; + + if (dummySurface) { + ctx->doneCurrent(); + delete ctx; + delete dummySurface; + } + } + return isES; +#endif +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/utils/utils.pri b/src/datavisualization/utils/utils.pri index 30bfb156..904407b8 100644 --- a/src/datavisualization/utils/utils.pri +++ b/src/datavisualization/utils/utils.pri @@ -22,3 +22,5 @@ SOURCES += $$PWD/meshloader.cpp \ $$PWD/surfaceobject.cpp \ $$PWD/scatterobjectbufferhelper.cpp \ $$PWD/scatterpointbufferhelper.cpp + +INCLUDEPATH += $$PWD diff --git a/src/datavisualization/utils/utils_p.h b/src/datavisualization/utils/utils_p.h index d7187c16..1a46c731 100644 --- a/src/datavisualization/utils/utils_p.h +++ b/src/datavisualization/utils/utils_p.h @@ -45,7 +45,7 @@ public: ParamTypeReal }; - static GLuint getNearestPowerOfTwo(GLuint value, GLuint &padding); + static GLuint getNearestPowerOfTwo(GLuint value); static QVector4D vectorFromColor(const QColor &color); static QColor colorFromVector(const QVector3D &colorVector); static QColor colorFromVector(const QVector4D &colorVector); @@ -57,17 +57,22 @@ public: bool borders = false, int maxLabelWidth = 0); static QVector4D getSelection(QPoint mousepos, int height); - static QImage getGradientImage(const QLinearGradient &gradient); + static QImage getGradientImage(QLinearGradient &gradient); - static ParamType findFormatParamType(const QString &format); - static QString formatLabel(const QByteArray &format, ParamType paramType, qreal value); + static ParamType preParseFormat(const QString &format, QString &preStr, QString &postStr, + int &precision, char &formatSpec); + static QString formatLabelSprintf(const QByteArray &format, ParamType paramType, qreal value); + static QString formatLabelLocalized(ParamType paramType, qreal value, + const QLocale &locale, const QString &preStr, const QString &postStr, + int precision, char formatSpec, const QByteArray &format); static QString defaultLabelFormat(); static float wrapValue(float value, float min, float max); static QQuaternion calculateRotation(const QVector3D &xyzRotations); + static bool isOpenGLES(); private: - static ParamType mapFormatCharToParamType(const QChar &formatChar); + static ParamType mapFormatCharToParamType(char formatSpec); }; QT_END_NAMESPACE_DATAVISUALIZATION |