From 169a4d638c6c1b6634ffcfd19c4fe3cb94cf27d5 Mon Sep 17 00:00:00 2001 From: Miikka Heikkinen Date: Wed, 13 Aug 2014 15:09:36 +0300 Subject: Implement volume rendering support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New subclass of QCustom3DItem, QCustom3DVolume is provided. The documentation for the example will be done in a separate commit. Change-Id: Idb3fdb0654c6bec7606ca012b75852a5a8412397 Reviewed-by: Tomi Korpipää --- examples/datavisualization/datavisualization.pro | 3 +- .../volumetric/doc/src/volumetric.qdoc | 27 + examples/datavisualization/volumetric/main.cpp | 105 ++++ .../datavisualization/volumetric/volumetric.cpp | 179 ++++++ examples/datavisualization/volumetric/volumetric.h | 59 ++ .../datavisualization/volumetric/volumetric.pro | 15 + src/datavisualization/data/customrenderitem.cpp | 25 +- src/datavisualization/data/customrenderitem_p.h | 35 ++ src/datavisualization/data/data.pri | 7 +- src/datavisualization/data/qcustom3ditem.cpp | 6 +- src/datavisualization/data/qcustom3ditem_p.h | 1 + src/datavisualization/data/qcustom3dvolume.cpp | 669 +++++++++++++++++++++ src/datavisualization/data/qcustom3dvolume.h | 106 ++++ src/datavisualization/data/qcustom3dvolume_p.h | 90 +++ .../engine/abstract3drenderer.cpp | 176 +++++- .../engine/abstract3drenderer_p.h | 11 +- src/datavisualization/engine/bars3drenderer.cpp | 15 +- src/datavisualization/engine/drawer.cpp | 19 +- src/datavisualization/engine/drawer_p.h | 2 +- src/datavisualization/engine/engine.qrc | 3 + src/datavisualization/engine/scatter3drenderer.cpp | 19 +- src/datavisualization/engine/shaders/default.frag | 1 + .../engine/shaders/texture3d.frag | 70 +++ .../engine/shaders/texture3d.vert | 12 + .../engine/shaders/texture3dslice.frag | 119 ++++ src/datavisualization/engine/surface3drenderer.cpp | 23 +- src/datavisualization/utils/shaderhelper.cpp | 37 ++ src/datavisualization/utils/shaderhelper_p.h | 9 + src/datavisualization/utils/texturehelper.cpp | 60 ++ src/datavisualization/utils/texturehelper_p.h | 11 + tests/tests.pro | 3 +- tests/volumetrictest/logo.png | Bin 0 -> 2205 bytes tests/volumetrictest/main.cpp | 105 ++++ tests/volumetrictest/volumetrictest.cpp | 282 +++++++++ tests/volumetrictest/volumetrictest.h | 61 ++ tests/volumetrictest/volumetrictest.pro | 18 + tests/volumetrictest/volumetrictest.qrc | 5 + 37 files changed, 2343 insertions(+), 45 deletions(-) create mode 100644 examples/datavisualization/volumetric/doc/src/volumetric.qdoc create mode 100644 examples/datavisualization/volumetric/main.cpp create mode 100644 examples/datavisualization/volumetric/volumetric.cpp create mode 100644 examples/datavisualization/volumetric/volumetric.h create mode 100644 examples/datavisualization/volumetric/volumetric.pro create mode 100644 src/datavisualization/data/qcustom3dvolume.cpp create mode 100644 src/datavisualization/data/qcustom3dvolume.h create mode 100644 src/datavisualization/data/qcustom3dvolume_p.h create mode 100644 src/datavisualization/engine/shaders/texture3d.frag create mode 100644 src/datavisualization/engine/shaders/texture3d.vert create mode 100644 src/datavisualization/engine/shaders/texture3dslice.frag create mode 100644 tests/volumetrictest/logo.png create mode 100644 tests/volumetrictest/main.cpp create mode 100644 tests/volumetrictest/volumetrictest.cpp create mode 100644 tests/volumetrictest/volumetrictest.h create mode 100644 tests/volumetrictest/volumetrictest.pro create mode 100644 tests/volumetrictest/volumetrictest.qrc diff --git a/examples/datavisualization/datavisualization.pro b/examples/datavisualization/datavisualization.pro index cb861b81..eab15b6d 100644 --- a/examples/datavisualization/datavisualization.pro +++ b/examples/datavisualization/datavisualization.pro @@ -21,7 +21,8 @@ SUBDIRS += qmlbars \ rotations \ draggableaxes \ customitems \ - texturesurface + texturesurface \ + volumetric } qtHaveModule(multimedia):!android:!ios: SUBDIRS += audiolevels diff --git a/examples/datavisualization/volumetric/doc/src/volumetric.qdoc b/examples/datavisualization/volumetric/doc/src/volumetric.qdoc new file mode 100644 index 00000000..48651cb1 --- /dev/null +++ b/examples/datavisualization/volumetric/doc/src/volumetric.qdoc @@ -0,0 +1,27 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +/*! + \example volumetric + \title Volumetric rendering Example + \ingroup qtdatavisualization_examples + \brief Rendering volumetric objects. + + TODO + \section1 Example contents +*/ diff --git a/examples/datavisualization/volumetric/main.cpp b/examples/datavisualization/volumetric/main.cpp new file mode 100644 index 00000000..7e4c9973 --- /dev/null +++ b/examples/datavisualization/volumetric/main.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 "volumetric.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + Q3DScatter *graph = new Q3DScatter(); + QWidget *container = QWidget::createWindowContainer(graph); + + QSize screenSize = graph->screen()->size(); + container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5)); + container->setMaximumSize(screenSize); + container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + container->setFocusPolicy(Qt::StrongFocus); + + QWidget *widget = new QWidget; + QHBoxLayout *hLayout = new QHBoxLayout(widget); + QVBoxLayout *vLayout = new QVBoxLayout(); + hLayout->addWidget(container, 1); + hLayout->addLayout(vLayout); + + widget->setWindowTitle(QStringLiteral("Volumetric Object Example")); + + QCheckBox *sliceXCheckBox = new QCheckBox(widget); + sliceXCheckBox->setText(QStringLiteral("Slice volume on X axis")); + sliceXCheckBox->setChecked(false); + QCheckBox *sliceYCheckBox = new QCheckBox(widget); + sliceYCheckBox->setText(QStringLiteral("Slice volume on Y axis")); + sliceYCheckBox->setChecked(false); + QCheckBox *sliceZCheckBox = new QCheckBox(widget); + sliceZCheckBox->setText(QStringLiteral("Slice volume on Z axis")); + sliceZCheckBox->setChecked(false); + + QSlider *sliceXSlider = new QSlider(Qt::Horizontal, widget); + sliceXSlider->setMinimum(0); + sliceXSlider->setMaximum(1024); + sliceXSlider->setValue(512); + sliceXSlider->setEnabled(true); + QSlider *sliceYSlider = new QSlider(Qt::Horizontal, widget); + sliceYSlider->setMinimum(0); + sliceYSlider->setMaximum(1024); + sliceYSlider->setValue(512); + sliceYSlider->setEnabled(true); + QSlider *sliceZSlider = new QSlider(Qt::Horizontal, widget); + sliceZSlider->setMinimum(0); + sliceZSlider->setMaximum(1024); + sliceZSlider->setValue(512); + sliceZSlider->setEnabled(true); + + QLabel *fpsLabel = new QLabel(QStringLiteral("Fps: "), widget); + + vLayout->addWidget(fpsLabel); + vLayout->addWidget(sliceXCheckBox); + vLayout->addWidget(sliceXSlider); + vLayout->addWidget(sliceYCheckBox); + vLayout->addWidget(sliceYSlider); + vLayout->addWidget(sliceZCheckBox); + vLayout->addWidget(sliceZSlider, 1, Qt::AlignTop); + + VolumetricModifier *modifier = new VolumetricModifier(graph); + modifier->setFpsLabel(fpsLabel); + + QObject::connect(sliceXCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::sliceX); + QObject::connect(sliceYCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::sliceY); + QObject::connect(sliceZCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::sliceZ); + QObject::connect(sliceXSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustSliceX); + QObject::connect(sliceYSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustSliceY); + QObject::connect(sliceZSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustSliceZ); + + widget->show(); + return app.exec(); +} diff --git a/examples/datavisualization/volumetric/volumetric.cpp b/examples/datavisualization/volumetric/volumetric.cpp new file mode 100644 index 00000000..a9233cab --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** 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 "volumetric.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace QtDataVisualization; + +const int textureSize = 256; + +VolumetricModifier::VolumetricModifier(Q3DScatter *scatter) + : m_graph(scatter), + m_volumeItem(0), + m_sliceIndexX(textureSize / 2), + m_sliceIndexY(textureSize / 4), + m_sliceIndexZ(textureSize / 2) +{ + m_graph->activeTheme()->setType(Q3DTheme::ThemeQt); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone); + m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); + m_graph->setOrthoProjection(true); + + createVolume(); + + m_graph->addCustomItem(m_volumeItem); + m_graph->setMeasureFps(true); + + QObject::connect(m_graph->scene()->activeCamera(), &Q3DCamera::zoomLevelChanged, this, + &VolumetricModifier::handleZoomLevelChange); + QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this, + &VolumetricModifier::handleFpsChange); +} + +VolumetricModifier::~VolumetricModifier() +{ + delete m_graph; +} + +void VolumetricModifier::setFpsLabel(QLabel *fpsLabel) +{ + m_fpsLabel = fpsLabel; +} + +void VolumetricModifier::sliceX(int enabled) +{ + m_volumeItem->setSliceIndexX(enabled ? m_sliceIndexX : -1); +} + +void VolumetricModifier::sliceY(int enabled) +{ + m_volumeItem->setSliceIndexY(enabled ? m_sliceIndexY : -1); +} + +void VolumetricModifier::sliceZ(int enabled) +{ + m_volumeItem->setSliceIndexZ(enabled ? m_sliceIndexZ : -1); +} + +void VolumetricModifier::adjustSliceX(int value) +{ + m_sliceIndexX = value / (1024 / textureSize); + if (m_volumeItem->sliceIndexX() != -1) + m_volumeItem->setSliceIndexX(m_sliceIndexX); +} + +void VolumetricModifier::adjustSliceY(int value) +{ + m_sliceIndexY = value / (1024 / textureSize * 2); + if (m_volumeItem->sliceIndexY() != -1) + m_volumeItem->setSliceIndexY(m_sliceIndexY); +} + +void VolumetricModifier::adjustSliceZ(int value) +{ + m_sliceIndexZ = value / (1024 / textureSize); + if (m_volumeItem->sliceIndexZ() != -1) + m_volumeItem->setSliceIndexZ(m_sliceIndexZ); +} + +void VolumetricModifier::handleZoomLevelChange() +{ + // Zooming inside volumetric object causes ugly clipping issues, so restrict zoom level a bit + if (m_graph->scene()->activeCamera()->zoomLevel() > 220) + m_graph->scene()->activeCamera()->setZoomLevel(220); +} + +void VolumetricModifier::handleFpsChange(qreal fps) +{ + const QString fpsFormat = QStringLiteral("Fps: %1"); + int fps10 = int(fps * 10.0); + m_fpsLabel->setText(fpsFormat.arg(QString::number(qreal(fps10) / 10.0))); +} + +void VolumetricModifier::createVolume() +{ + m_volumeItem = new QCustom3DVolume; + m_volumeItem->setScaling(QVector3D(2.0f, 1.0f, 2.0f)); + m_volumeItem->setTextureWidth(textureSize); + m_volumeItem->setTextureHeight(textureSize / 2); + m_volumeItem->setTextureDepth(textureSize); + m_volumeItem->setTextureFormat(QImage::Format_Indexed8); + + // Generate color table. First color is fully transparent used to fill outer + // portions of the volume. The second, red layer, is somewhat transparent. + // Rest of to colors are opaque. + QVector colors; + colors.resize(256); + + colors[0] = qRgba(0, 0, 0, 0); + for (int i = 1; i < 256; i++) { + if (i < 60) { + colors[i] = qRgba((i * 2) + 120, 0, 0, 100); + } else if (i < 120) { + colors[i] = qRgba(0, ((i - 60) * 2) + 120, 0, 255); + } else if (i < 180) { + colors[i] = qRgba(0, 0, ((i - 120) * 2) + 120, 255); + } else { + colors[i] = qRgba(i, i, i, 255); + } + } + m_volumeItem->setColorTable(colors); + + // Generate texture data for an half-ellipsoid. + // This can take a while if the dimensions are large. + // Note that in real world cases, the texture data is usually be supplied + // as a stack of slice images. + + QVector *textureData = new QVector(textureSize * textureSize * textureSize / 2); + + QVector3D midPoint(float(textureSize) / 2.0f, + float(textureSize) / 2.0f, + float(textureSize) / 2.0f); + + int index = 0; + for (int i = 0; i < textureSize; i++) { + for (int j = 0; j < textureSize / 2; j++) { + for (int k = 0; k < textureSize; k++) { + int colorIndex = 0; + // Take a slice out of the ellipsoid + if (i >= textureSize / 2 || j >= textureSize / 4 || k >= textureSize / 2) { + QVector3D distVec = QVector3D(float(k), float(j * 2), float(i)) - midPoint; + float adjLen = qMin(255.0f, (distVec.length() * float(textureSize / 128))); + if (adjLen < 230) + colorIndex = 255 - int(adjLen); + else + colorIndex = 0; + } + + (*textureData)[index] = colorIndex; + index++; + } + } + } + + m_volumeItem->setTextureData(textureData); +} + diff --git a/examples/datavisualization/volumetric/volumetric.h b/examples/datavisualization/volumetric/volumetric.h new file mode 100644 index 00000000..722d4b48 --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** 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 VOLUMETRICMODIFIER_H +#define VOLUMETRICMODIFIER_H + +#include +#include + +class QLabel; + +using namespace QtDataVisualization; + +class VolumetricModifier : public QObject +{ + Q_OBJECT +public: + explicit VolumetricModifier(Q3DScatter *scatter); + ~VolumetricModifier(); + + void setFpsLabel(QLabel *fpsLabel); + +public slots: + void sliceX(int enabled); + void sliceY(int enabled); + void sliceZ(int enabled); + void adjustSliceX(int value); + void adjustSliceY(int value); + void adjustSliceZ(int value); + void handleZoomLevelChange(); + void handleFpsChange(qreal fps); + +private: + void createVolume(); + + Q3DScatter *m_graph; + QCustom3DVolume *m_volumeItem; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + QLabel *m_fpsLabel; +}; + +#endif diff --git a/examples/datavisualization/volumetric/volumetric.pro b/examples/datavisualization/volumetric/volumetric.pro new file mode 100644 index 00000000..c5e31891 --- /dev/null +++ b/examples/datavisualization/volumetric/volumetric.pro @@ -0,0 +1,15 @@ +android|ios { + error( "This example is not supported for android or ios." ) +} + +!include( ../examples.pri ) { + error( "Couldn't find the examples.pri file!" ) +} + +SOURCES += main.cpp volumetric.cpp +HEADERS += volumetric.h + +QT += widgets + +OTHER_FILES += doc/src/* \ + doc/images/* diff --git a/src/datavisualization/data/customrenderitem.cpp b/src/datavisualization/data/customrenderitem.cpp index a1c70057..b96a4957 100644 --- a/src/datavisualization/data/customrenderitem.cpp +++ b/src/datavisualization/data/customrenderitem.cpp @@ -23,9 +23,20 @@ QT_BEGIN_NAMESPACE_DATAVISUALIZATION CustomRenderItem::CustomRenderItem() : AbstractRenderItem(), m_texture(0), + m_absolute(false), 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_textureWidth(0), + m_textureHeight(0), + m_textureDepth(0), + m_isVolume(false) { } @@ -39,4 +50,16 @@ void CustomRenderItem::setMesh(const QString &meshFile) ObjectHelper::resetObjectHelper(m_renderer, m_object, meshFile); } +void CustomRenderItem::setColorTable(const QVector &colors) +{ + m_colorTable.resize(colors.size()); + for (int i = 0; i < m_colorTable.size(); i++) { + 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); + } +} + QT_END_NAMESPACE_DATAVISUALIZATION diff --git a/src/datavisualization/data/customrenderitem_p.h b/src/datavisualization/data/customrenderitem_p.h index 4fb94276..e21b6f39 100644 --- a/src/datavisualization/data/customrenderitem_p.h +++ b/src/datavisualization/data/customrenderitem_p.h @@ -31,6 +31,8 @@ #include "abstractrenderitem_p.h" #include "objecthelper_p.h" +#include +#include QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -69,6 +71,28 @@ public: inline bool isFacingCamera() const { return m_isFacingCamera; } inline void setRenderer(Abstract3DRenderer *renderer) { m_renderer = renderer; } + // Volume specific + inline void setTextureWidth(int width) { m_textureWidth = width; } + inline int textureWidth() const { return m_textureWidth; } + inline void setTextureHeight(int height) { m_textureHeight = height; } + inline int textureHeight() const { return m_textureHeight; } + inline void setTextureDepth(int depth) { m_textureDepth = depth; } + inline int textureDepth() const { return m_textureDepth; } + inline int textureSize() const { return m_textureWidth * m_textureHeight * m_textureDepth; } + inline void setColorTable(const QVector &colors) { m_colorTable = colors; } + void setColorTable(const QVector &colors); + inline const QVector &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; } + inline void setSliceIndexY(int index) { m_sliceIndexY = index; } + inline void setSliceIndexZ(int index) { m_sliceIndexZ = index; } + int sliceIndexX() const { return m_sliceIndexX; } + int sliceIndexY() const { return m_sliceIndexY; } + int sliceIndexZ() const { return m_sliceIndexZ; } + private: Q_DISABLE_COPY(CustomRenderItem) @@ -85,6 +109,17 @@ private: bool m_isFacingCamera; QCustom3DItem *m_item; Abstract3DRenderer *m_renderer; + + // Volume specific + int m_textureWidth; + int m_textureHeight; + int m_textureDepth; + QVector m_colorTable; + bool m_isVolume; + QImage::Format m_textureFormat; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; }; typedef QHash CustomRenderItemArray; diff --git a/src/datavisualization/data/data.pri b/src/datavisualization/data/data.pri index 37a8d084..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,6 +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/qcustom3ditem.cpp b/src/datavisualization/data/qcustom3ditem.cpp index efee3b15..1d419cee 100644 --- a/src/datavisualization/data/qcustom3ditem.cpp +++ b/src/datavisualization/data/qcustom3ditem.cpp @@ -373,7 +373,8 @@ QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q) : m_rotation(QQuaternion(0.0f, 0.0f, 0.0f, 0.0f)), m_visible(true), m_shadowCasting(true), - m_isLabelItem(false) + m_isLabelItem(false), + m_isVolumeItem(false) { } @@ -388,7 +389,8 @@ QCustom3DItemPrivate::QCustom3DItemPrivate(QCustom3DItem *q, const QString &mesh m_rotation(rotation), m_visible(true), m_shadowCasting(true), - m_isLabelItem(false) + m_isLabelItem(false), + m_isVolumeItem(false) { } diff --git a/src/datavisualization/data/qcustom3ditem_p.h b/src/datavisualization/data/qcustom3ditem_p.h index c1ce5996..d766bcf3 100644 --- a/src/datavisualization/data/qcustom3ditem_p.h +++ b/src/datavisualization/data/qcustom3ditem_p.h @@ -82,6 +82,7 @@ public: bool m_shadowCasting; bool m_isLabelItem; + bool m_isVolumeItem; QCustomItemDirtyBitField m_dirtyBits; diff --git a/src/datavisualization/data/qcustom3dvolume.cpp b/src/datavisualization/data/qcustom3dvolume.cpp new file mode 100644 index 00000000..766f1589 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume.cpp @@ -0,0 +1,669 @@ +/**************************************************************************** +** +** 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. + * + * \note Volumetric objects are only supported with orthographic projection. + * + * \sa QAbstract3DGraph::addCustomItem(), QAbstract3DGraph::orthoProjection + */ + +/*! + * \qmltype Custom3DVolume + * \inqmlmodule QtDataVisualization + * \since QtDataVisualization 1.2 + * \ingroup datavisualization_qml + * \instantiates QCustom3DVolume + * \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. + * + * \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 QML ui. + * + * \note Volumetric objects are only supported with orthographic projection. + * + * \sa AbstractGraph3D::orthoProjection + */ + +/*! \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 for that dimension is drawn. + * If all dimensions are negative, no slices are drawn and the volume is drawn normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData + */ + +/*! \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 for that dimension is drawn. + * If all dimensions are negative, no slices are drawn and the volume is drawn normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData + */ + +/*! \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 for that dimension is drawn. + * If all dimensions are negative, no slices are drawn and the volume is drawn normally. + * Defaults to \c{-1}. + * + * \sa QCustom3DVolume::textureData + */ + +/*! + * 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, textureFormat, colorTable + */ +QCustom3DVolume::QCustom3DVolume(const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, + QVector *textureData, QImage::Format textureFormat, + const QVector &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, textureFormat, 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, textureFormat + */ +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, textureFormat + */ +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); + setTextureWidth(height); + setTextureWidth(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 for that dimension is drawn. + * If all dimensions are negative, no slices are drawn and the volume is drawn normally. + * Defaults to \c{-1}. + * + * \sa textureData + */ +void QCustom3DVolume::setSliceIndexX(int value) +{ + if (dptr()->m_sliceIndexX != value) { + dptr()->m_sliceIndexX = value; + dptr()->m_dirtyBitsVolume.sliceIndicesDirty = 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 for that dimension is drawn. + * If all dimensions are negative, no slices are drawn and the volume is drawn normally. + * Defaults to \c{-1}. + * + * \sa textureData + */ +void QCustom3DVolume::setSliceIndexY(int value) +{ + if (dptr()->m_sliceIndexY != value) { + dptr()->m_sliceIndexY = value; + dptr()->m_dirtyBitsVolume.sliceIndicesDirty = 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 for that dimension is drawn. + * If all dimensions are negative, no slices are drawn and the volume is drawn normally. + * Defaults to \c{-1}. + * + * \sa textureData + */ +void QCustom3DVolume::setSliceIndexZ(int value) +{ + if (dptr()->m_sliceIndexZ != value) { + dptr()->m_sliceIndexZ = value; + dptr()->m_dirtyBitsVolume.sliceIndicesDirty = 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, textureFormat, QImage::colorTable() + */ +void QCustom3DVolume::setColorTable(const QVector &colors) +{ + if (dptr()->m_colorTable != colors) { + dptr()->m_colorTable = colors; + dptr()->m_dirtyBitsVolume.colorTableDirty = true; + emit colorTableChanged(); + emit dptr()->needUpdate(); + } +} + +QVector 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. + * + * Defaults to \c{0}. + * + * \sa colorTable, textureFormat, setSubTextureData(), textureDataWidth() + */ +void QCustom3DVolume::setTextureData(QVector *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, textureFormat + */ +QVector *QCustom3DVolume::createTextureData(const QVector &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 *newTextureData = new QVector; + 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(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 *QCustom3DVolume::textureData() const +{ + return dptrc()->m_textureData; +} + +/*! + * This function allows setting a single 2D subtexture of the 3D texture. + * The \a depthIndex parameter specifies the subtexture to set. + * The texture\a data must be in the format specified by textureFormat property and have size of + * (\c{textureDataWidth * textureHeight * texture format color depth in bytes}). + * + * \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. + * + * \sa textureData + */ +void QCustom3DVolume::setSubTextureData(int depthIndex, const uchar *data) +{ + if (data) { + int frameSize = textureDataWidth() * dptr()->m_textureHeight; + int startIndex = depthIndex * frameSize; + + if (depthIndex >= dptr()->m_textureDepth + || (startIndex + frameSize) > dptr()->m_textureData->size()) { + qWarning() << __FUNCTION__ << "Attempted to set invalid subtexture."; + } else { + void *subTexPtr = dptr()->m_textureData->data() + startIndex; + memcpy(subTexPtr, static_cast(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 to a source \a image; + * The \a depthIndex parameter specifies the subtexture to set. + * The 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 (\c{textureWidth * textureHeight}). + * + * \sa textureData + */ +void QCustom3DVolume::setSubTextureData(int depthIndex, const QImage &image) +{ + if (image.width() == dptr()->m_textureWidth + && image.height() == dptr()->m_textureHeight + && (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(depthIndex, convertedImage.bits()); + } else { + qWarning() << __FUNCTION__ << "Invalid image size or format."; + } +} + +/*! \property QCustom3DVolume::textureFormat + * + * The format of the textureData. 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."; + } +} + +QImage::Format QCustom3DVolume::textureFormat() const +{ + return dptrc()->m_textureFormat; +} + + +/*! + * \internal + */ +QCustom3DVolumePrivate *QCustom3DVolume::dptr() +{ + return static_cast(d_ptr.data()); +} + +/*! + * \internal + */ +const QCustom3DVolumePrivate *QCustom3DVolume::dptrc() const +{ + return static_cast(d_ptr.data()); +} + +QCustom3DVolumePrivate::QCustom3DVolumePrivate(QCustom3DVolume *q) : + QCustom3DItemPrivate(q), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_textureWidth(0), + m_textureHeight(0), + m_textureDepth(0), + m_textureFormat(QImage::Format_ARGB32), + m_textureData(0) +{ + 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 *textureData, + QImage::Format textureFormat, + const QVector &colorTable) : + QCustom3DItemPrivate(q, QStringLiteral(":/defaultMeshes/barFull"), position, scaling, rotation), + m_sliceIndexX(-1), + m_sliceIndexY(-1), + m_sliceIndexZ(-1), + m_textureWidth(textureWidth), + m_textureHeight(textureHeight), + m_textureDepth(textureDepth), + m_textureFormat(textureFormat), + m_colorTable(colorTable), + m_textureData(textureData) +{ + 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_colorTable.size() != 256) + m_colorTable.clear(); + + 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.sliceIndicesDirty = false; + m_dirtyBitsVolume.colorTableDirty = false; + m_dirtyBitsVolume.textureDataDirty = false; + m_dirtyBitsVolume.textureFormatDirty = false; +} + +QCustom3DVolume *QCustom3DVolumePrivate::qptr() +{ + return static_cast(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..498922e8 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** 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 +#include +#include +#include + +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 colorTable READ colorTable WRITE setColorTable NOTIFY colorTableChanged) + Q_PROPERTY(QVector *textureData READ textureData WRITE setTextureData NOTIFY textureDataChanged) + Q_PROPERTY(QImage::Format textureFormat READ textureFormat WRITE setTextureFormat NOTIFY textureFormatChanged) + +public: + + explicit QCustom3DVolume(QObject *parent = 0); + explicit QCustom3DVolume(const QVector3D &position, const QVector3D &scaling, + const QQuaternion &rotation, int textureWidth, + int textureHeight, int textureDepth, + QVector *textureData, QImage::Format textureFormat, + const QVector &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 &colors); + QVector colorTable() const; + + void setTextureData(QVector *data); + QVector *createTextureData(const QVector &images); + QVector *textureData() const; + void setSubTextureData(int depthIndex, const uchar *data); + void setSubTextureData(int depthIndex, const QImage &image); + + void setTextureFormat(QImage::Format format); + QImage::Format textureFormat() const; + +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 *data); + void textureFormatChanged(QImage::Format format); + +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..69dd1eb2 --- /dev/null +++ b/src/datavisualization/data/qcustom3dvolume_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 sliceIndicesDirty : 1; + bool colorTableDirty : 1; + bool textureDataDirty : 1; + bool textureFormatDirty : 1; + + QCustomVolumeDirtyBitField() + : textureDimensionsDirty(false), + sliceIndicesDirty(false), + colorTableDirty(false), + textureDataDirty(false), + textureFormatDirty(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 *textureData, + QImage::Format textureFormat, const QVector &colorTable); + virtual ~QCustom3DVolumePrivate(); + + void resetDirtyBits(); + + 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 m_colorTable; + QVector *m_textureData; + + QCustomVolumeDirtyBitField m_dirtyBitsVolume; + +private: + friend class QCustom3DVolume; +}; + +QT_END_NAMESPACE_DATAVISUALIZATION + +#endif diff --git a/src/datavisualization/engine/abstract3drenderer.cpp b/src/datavisualization/engine/abstract3drenderer.cpp index 14cf7109..cbb90af0 100644 --- a/src/datavisualization/engine/abstract3drenderer.cpp +++ b/src/datavisualization/engine/abstract3drenderer.cpp @@ -24,6 +24,7 @@ #include "shaderhelper_p.h" #include "qcustom3ditem_p.h" #include "qcustom3dlabel_p.h" +#include "qcustom3dvolume_p.h" #include @@ -57,6 +58,8 @@ Abstract3DRenderer::Abstract3DRenderer(Abstract3DController *controller) m_selectionLabelItem(0), m_visibleSeriesCount(0), m_customItemShader(0), + m_volumeTextureShader(0), + m_volumeTextureSliceShader(0), m_useOrthoProjection(false), m_xFlipped(false), m_yFlipped(false), @@ -96,6 +99,8 @@ Abstract3DRenderer::~Abstract3DRenderer() delete m_cachedTheme; delete m_selectionLabelItem; delete m_customItemShader; + delete m_volumeTextureShader; + delete m_volumeTextureSliceShader; foreach (SeriesRenderCache *cache, m_renderCacheList) { cache->cleanup(m_textureHelper); @@ -196,6 +201,20 @@ void Abstract3DRenderer::initCustomItemShaders(const QString &vertexShader, m_customItemShader->initialize(); } +void Abstract3DRenderer::initVolumeTextureShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &sliceShader) +{ + if (m_volumeTextureShader) + delete m_volumeTextureShader; + m_volumeTextureShader = new ShaderHelper(this, vertexShader, fragmentShader); + m_volumeTextureShader->initialize(); + if (m_volumeTextureSliceShader) + delete m_volumeTextureSliceShader; + m_volumeTextureSliceShader = new ShaderHelper(this, vertexShader, sliceShader); + m_volumeTextureSliceShader->initialize(); +} + void Abstract3DRenderer::updateTheme(Q3DTheme *theme) { // Synchronize the controller theme with renderer @@ -279,6 +298,9 @@ void Abstract3DRenderer::reInitShaders() initCustomItemShaders(QStringLiteral(":/shaders/vertexTexture"), QStringLiteral(":/shaders/fragmentTexture")); } + initVolumeTextureShaders(QStringLiteral(":/shaders/vertexTexture3D"), + QStringLiteral(":/shaders/fragmentTexture3D"), + QStringLiteral(":/shaders/fragmentTexture3DSlice")); #else initGradientShaders(QStringLiteral(":/shaders/vertex"), QStringLiteral(":/shaders/fragmentColorOnYES2")); @@ -906,6 +928,7 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) QVector3D scaling = item->scaling(); QImage textureImage = item->d_ptr->textureImage(); bool facingCamera = false; + GLuint texture; if (item->d_ptr->m_isLabelItem) { QCustom3DLabel *labelItem = static_cast(item); float pointSize = labelItem->font().pointSizeF(); @@ -925,13 +948,39 @@ CustomRenderItem *Abstract3DRenderer::addCustomItem(QCustom3DItem *item) scaling.setY(scaling.y() * textureImage.height() * scaledFontSize); // Check if facing camera facingCamera = labelItem->isFacingCamera(); +#if !defined(QT_OPENGL_ES_2) + } else if (item->d_ptr->m_isVolumeItem) { + QCustom3DVolume *volumeItem = static_cast(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()); +#endif } newItem->setScaling(scaling); 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); +#if !defined(QT_OPENGL_ES_2) + // In OpenGL ES we simply draw volumes as regular custom item placeholders. + if (!item->d_ptr->m_isVolumeItem) +#endif + { + newItem->setBlendNeeded(textureImage.hasAlphaChannel()); + texture = m_textureHelper->create2DTexture(textureImage, true, true, true); + } newItem->setTexture(texture); item->d_ptr->clearTextureImage(); QVector3D translation = convertPositionToTranslation(item->position(), @@ -996,12 +1045,17 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) m_cachedTheme->isLabelBorderEnabled()); textureImage = item->d_ptr->textureImage(); } + } else +#if !defined(QT_OPENGL_ES_2) + if (!item->d_ptr->m_isVolumeItem) +#endif + { + 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; } @@ -1028,6 +1082,39 @@ void Abstract3DRenderer::updateCustomItem(CustomRenderItem *renderItem) renderItem->setFacingCamera(labelItem->isFacingCamera()); labelItem->dptr()->m_facingCameraDirty = false; } +#if !defined(QT_OPENGL_ES_2) + } else if (item->d_ptr->m_isVolumeItem) { + QCustom3DVolume *volumeItem = static_cast(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.sliceIndicesDirty) { + renderItem->setSliceIndexX(volumeItem->sliceIndexX()); + renderItem->setSliceIndexY(volumeItem->sliceIndexY()); + renderItem->setSliceIndexZ(volumeItem->sliceIndexZ()); + volumeItem->dptr()->m_dirtyBitsVolume.sliceIndicesDirty = false; + } +#endif } } @@ -1042,7 +1129,9 @@ void Abstract3DRenderer::updateCustomItemPositions() #ifdef USE_REFLECTIONS void Abstract3DRenderer::drawCustomItems(RenderingState state, - ShaderHelper *shader, + ShaderHelper *regularShader, + ShaderHelper *volumeShader, + ShaderHelper *volumeSliceShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, @@ -1051,7 +1140,9 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state, GLfloat reflection) #else void Abstract3DRenderer::drawCustomItems(RenderingState state, - ShaderHelper *shader, + ShaderHelper *regularShader, + ShaderHelper *volumeShader, + ShaderHelper *volumeSliceShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, @@ -1059,11 +1150,17 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state, GLfloat shadowQuality) #endif { +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(volumeShader) + Q_UNUSED(volumeSliceShader) +#endif 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(), @@ -1137,6 +1234,22 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state, if (RenderingNormal == state) { // Normal render +#if !defined(QT_OPENGL_ES_2) + ShaderHelper *prevShader = shader; + if (item->isVolume()) { + if (item->sliceIndexX() >= 0 + || item->sliceIndexY() >= 0 + || item->sliceIndexZ() >= 0) { + shader = volumeSliceShader; + } else { + shader = volumeShader; + } + } else { + shader = regularShader; + } + if (shader != prevShader) + shader->bind(); +#endif shader->setUniformValue(shader->model(), modelMatrix); shader->setUniformValue(shader->MVP(), MVPMatrix); shader->setUniformValue(shader->nModel(), itModelMatrix.inverted().transposed()); @@ -1144,14 +1257,17 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state, if (item->isBlendNeeded()) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); +#if !defined(QT_OPENGL_ES_2) + if (!item->isVolume()) +#endif + glDisable(GL_CULL_FACE); } else { glDisable(GL_BLEND); glEnable(GL_CULL_FACE); } #if !defined(QT_OPENGL_ES_2) - if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone) { + if (m_cachedShadowQuality > QAbstract3DGraph::ShadowQualityNone && !item->isVolume()) { // Set shadow shader bindings shader->setUniformValue(shader->shadowQ(), shadowQuality); shader->setUniformValue(shader->depth(), depthProjectionViewMatrix * modelMatrix); @@ -1164,8 +1280,34 @@ void Abstract3DRenderer::drawCustomItems(RenderingState state, #endif { // Set shadowless shader bindings - shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); - m_drawer->drawObject(shader, item->mesh(), item->texture()); +#if !defined(QT_OPENGL_ES_2) + if (item->isVolume()) { + // Volume shaders repurpose light position for camera position relative to item + QVector3D cameraPos = m_cachedScene->activeCamera()->position(); + cameraPos = MVPMatrix.inverted().map(cameraPos); + 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); + if (shader == volumeSliceShader) { + QVector3D slices((float(item->sliceIndexX()) + 0.5f) + / float(item->textureWidth()) * 2.0 - 1.0, + (float(item->sliceIndexY()) + 0.5f) + / float(item->textureHeight()) * 2.0 - 1.0, + (float(item->sliceIndexZ()) + 0.5f) + / float(item->textureDepth()) * 2.0 - 1.0); + shader->setUniformValue(shader->volumeSliceIndices(), slices); + } + m_drawer->drawObject(shader, item->mesh(), 0, 0, item->texture()); + } else +#endif + { + shader->setUniformValue(shader->lightS(), m_cachedTheme->lightStrength()); + m_drawer->drawObject(shader, item->mesh(), item->texture()); + } } } else if (RenderingSelection == state) { // Selection render @@ -1203,6 +1345,9 @@ void Abstract3DRenderer::drawRadialGrid(ShaderHelper *shader, float yFloorLinePo const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthMatrix) { +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(depthMatrix) +#endif static QVector lineRotations; if (!lineRotations.size()) { lineRotations.resize(polarGridRoundness); @@ -1264,6 +1409,9 @@ void Abstract3DRenderer::drawAngularGrid(ShaderHelper *shader, float yFloorLineP const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthMatrix) { +#if defined(QT_OPENGL_ES_2) + Q_UNUSED(depthMatrix) +#endif float halfRatio((m_polarRadius + (labelMargin / 2.0f)) / 2.0f); QVector3D gridLineScaler(gridLineWidth, gridLineWidth, halfRatio); int gridLineCount = m_axisCacheX.gridLineCount(); diff --git a/src/datavisualization/engine/abstract3drenderer_p.h b/src/datavisualization/engine/abstract3drenderer_p.h index 50370954..7874a1ad 100644 --- a/src/datavisualization/engine/abstract3drenderer_p.h +++ b/src/datavisualization/engine/abstract3drenderer_p.h @@ -94,6 +94,9 @@ public: const QString &fragmentShader) = 0; virtual void initCustomItemShaders(const QString &vertexShader, const QString &fragmentShader); + virtual void initVolumeTextureShaders(const QString &vertexShader, + const QString &fragmentShader, + const QString &sliceShader); virtual void updateAxisType(QAbstract3DAxis::AxisOrientation orientation, QAbstract3DAxis::AxisType type); virtual void updateAxisTitle(QAbstract3DAxis::AxisOrientation orientation, @@ -145,13 +148,15 @@ public: QString &selectionLabel(); #ifdef USE_REFLECTIONS - void drawCustomItems(RenderingState state, ShaderHelper *shader, + void drawCustomItems(RenderingState state, ShaderHelper *regularShader, + ShaderHelper *volumeShader, ShaderHelper *volumeSliceShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, GLuint depthTexture, GLfloat shadowQuality, GLfloat reflection = 1.0f); #else - void drawCustomItems(RenderingState state, ShaderHelper *shader, + void drawCustomItems(RenderingState state, ShaderHelper *regularShader, + ShaderHelper *volumeShader, ShaderHelper *volumeSliceShader, const QMatrix4x4 &viewMatrix, const QMatrix4x4 &projectionViewMatrix, const QMatrix4x4 &depthProjectionViewMatrix, @@ -247,6 +252,8 @@ protected: int m_visibleSeriesCount; ShaderHelper *m_customItemShader; + ShaderHelper *m_volumeTextureShader; + ShaderHelper *m_volumeTextureSliceShader; bool m_useOrthoProjection; bool m_xFlipped; diff --git a/src/datavisualization/engine/bars3drenderer.cpp b/src/datavisualization/engine/bars3drenderer.cpp index 38fa3147..085b881e 100644 --- a/src/datavisualization/engine/bars3drenderer.cpp +++ b/src/datavisualization/engine/bars3drenderer.cpp @@ -1093,9 +1093,11 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to depth framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -1179,7 +1181,9 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) } } glCullFace(GL_BACK); - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, + m_volumeTextureShader, m_volumeTextureSliceShader, + viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -1267,7 +1271,8 @@ void Bars3DRenderer::drawScene(GLuint defaultFboHandle) drawGridLines(depthProjectionViewMatrix, projectionViewMatrix, viewMatrix); // Draw custom items - Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); diff --git a/src/datavisualization/engine/drawer.cpp b/src/datavisualization/engine/drawer.cpp index b8726840..4191efc4 100644 --- a/src/datavisualization/engine/drawer.cpp +++ b/src/datavisualization/engine/drawer.cpp @@ -93,8 +93,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 +111,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()); @@ -139,6 +150,12 @@ void Drawer::drawObject(ShaderHelper *shader, AbstractObjectHelper *object, GLui 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); diff --git a/src/datavisualization/engine/drawer_p.h b/src/datavisualization/engine/drawer_p.h index ffcea315..709503dd 100644 --- a/src/datavisualization/engine/drawer_p.h +++ b/src/datavisualization/engine/drawer_p.h @@ -75,7 +75,7 @@ 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); diff --git a/src/datavisualization/engine/engine.qrc b/src/datavisualization/engine/engine.qrc index f788b5df..936ed53f 100644 --- a/src/datavisualization/engine/engine.qrc +++ b/src/datavisualization/engine/engine.qrc @@ -59,5 +59,8 @@ shaders/texture.vert shaders/surfaceTexturedShadowFlat.frag shaders/surfaceTexturedFlat.frag + shaders/texture3d.frag + shaders/texture3d.vert + shaders/texture3dslice.frag diff --git a/src/datavisualization/engine/scatter3drenderer.cpp b/src/datavisualization/engine/scatter3drenderer.cpp index 09225b99..13f6e02c 100644 --- a/src/datavisualization/engine/scatter3drenderer.cpp +++ b/src/datavisualization/engine/scatter3drenderer.cpp @@ -538,9 +538,11 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -641,9 +643,11 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_selectionShader, + m_volumeTextureShader, m_volumeTextureSliceShader, + viewMatrix, projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -1407,7 +1411,8 @@ void Scatter3DRenderer::drawScene(const GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); diff --git a/src/datavisualization/engine/shaders/default.frag b/src/datavisualization/engine/shaders/default.frag index c03b1054..0276ed95 100644 --- a/src/datavisualization/engine/shaders/default.frag +++ b/src/datavisualization/engine/shaders/default.frag @@ -33,5 +33,6 @@ void main() { materialDiffuseColor * lightStrength * pow(cosTheta, 2) / distance + materialSpecularColor * lightStrength * pow(cosAlpha, 5) / distance; gl_FragColor.a = color_mdl.a; + gl_FragColor = clamp(gl_FragColor, 0.0, 1.0); } diff --git a/src/datavisualization/engine/shaders/texture3d.frag b/src/datavisualization/engine/shaders/texture3d.frag new file mode 100644 index 00000000..054b59cb --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3d.frag @@ -0,0 +1,70 @@ +#version 120 + +varying highp vec3 pos; + +uniform highp sampler3D textureSampler; +uniform highp vec3 cameraPositionRelativeToModel; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; + +const float maxDist = sqrt(2.0); +const int sampleCount = 1024; +const float alphaThreshold = 0.001; +void main() { + // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1 + + // Find out where ray intersects the object + highp vec3 rayDir = -(cameraPositionRelativeToModel - pos); + highp vec3 invRayDir = 1.0 / rayDir; + highp vec3 minCorner = vec3(-1.0); + highp vec3 maxCorner = vec3(1.0); + highp vec3 t1 = invRayDir * (minCorner - pos); + highp vec3 t2 = invRayDir * (maxCorner - pos); + highp vec3 tmin = min(t1, t2); + highp vec3 tmax = max(t1, t2); + highp vec2 t = max(tmin.xx, tmin.yz); + t = min(tmax.xx, tmax.yz); + float tFar = min(t.x, t.y); + highp vec3 rayStart = pos; + // Flip Y and Z so QImage bits work directly for texture and first image is in the front + rayStart.yz = -rayStart.yz; + highp vec3 rayStop = pos + rayDir * tFar; + rayStop.yz = -rayStop.yz; + + // Convert intersections to texture coords + rayStart = 0.5 * (rayStart + 1.0); + rayStop = 0.5 * (rayStop + 1.0); + + highp vec3 curPos = rayStart; + highp float fullDist = distance(rayStop, rayStart); + highp float stepSize = maxDist / float(sampleCount); // TODO: Stepsize needs to be improved + highp vec3 step = normalize(rayStop - rayStart) * stepSize; + highp float totalDist = 0.0; + highp float totalAlpha = 0.0; + highp vec4 destColor = vec4(0, 0, 0, 0); + highp vec4 curColor = vec4(0, 0, 0, 0); + highp vec3 curRgb = vec3(0, 0, 0); + highp float curAlpha = 0.0; + + for (int i = 0; i < sampleCount; i++) { + curColor = texture3D(textureSampler, curPos); + if (color8Bit != 0) + curColor = colorIndex[int(curColor.r * 255.0)]; + + curAlpha = curColor.a; + if (curAlpha > alphaThreshold) { + curRgb = curColor.rgb * curAlpha * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += curAlpha; + } + curPos += step; + totalDist += stepSize; + if (totalDist > fullDist || totalAlpha >= 1.0) { + break; + } + } + + destColor.a = totalAlpha; + 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..cad1ce06 --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3d.vert @@ -0,0 +1,12 @@ +uniform highp mat4 MVP; + +attribute highp vec3 vertexPosition_mdl; +attribute highp vec2 vertexUV; +attribute highp vec3 vertexNormal_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/texture3dslice.frag b/src/datavisualization/engine/shaders/texture3dslice.frag new file mode 100644 index 00000000..641b32a5 --- /dev/null +++ b/src/datavisualization/engine/shaders/texture3dslice.frag @@ -0,0 +1,119 @@ +#version 120 + +varying highp vec3 pos; + +uniform highp sampler3D textureSampler; +uniform highp vec3 cameraPositionRelativeToModel; +uniform highp vec3 volumeSliceIndices; +uniform highp vec4 colorIndex[256]; +uniform highp int color8Bit; + +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); + +const float alphaThreshold = 0.001; +void main() { + // Raytrace into volume, need to sample pixels along the eye ray until we hit opacity 1 + + // Find out where ray intersects the slice planes + highp vec3 rayDir = -(cameraPositionRelativeToModel - pos); + highp vec3 rayStart = pos; + // Flip Y and Z so QImage bits work directly for texture and first image is in the front + rayStart.yz = -rayStart.yz; + rayDir.yz = -rayDir.yz; + highp vec3 invRayDir = 1.0 / rayDir; + highp vec3 minCorner = vec3(-1.0); + highp vec3 maxCorner = vec3(1.0); + highp vec3 t1 = invRayDir * (minCorner - rayStart); + highp vec3 t2 = invRayDir * (maxCorner - rayStart); + highp vec3 tmin = min(t1, t2); + highp vec3 tmax = max(t1, t2); + highp vec2 t = max(tmin.xx, tmin.yz); + t = min(tmax.xx, tmax.yz); + float tFar = min(t.x, t.y); + + 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 = tFar + 1.0; + highp float secondD = firstD; + highp float thirdD = firstD; + if (volumeSliceIndices.x >= -1.0) { + highp float dx = dot(xPoint - rayStart, xPlaneNormal) / dot(rayDir, xPlaneNormal); + if (dx >= 0.0 && dx <= tFar) + firstD = min(dx, firstD); + } + if (volumeSliceIndices.y >= -1.0) { + highp float dy = dot(yPoint - rayStart, yPlaneNormal) / dot(rayDir, yPlaneNormal); + if (dy >= 0.0 && dy <= tFar) { + if (dy < firstD) { + secondD = firstD; + firstD = dy; + } else { + secondD = dy; + } + } + } + if (volumeSliceIndices.z >= -1.0) { + highp float dz = dot(zPoint - rayStart, zPlaneNormal) / dot(rayDir, zPlaneNormal); + if (dz >= 0.0) { + if (dz < firstD && dz <= tFar) { + 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 float totalAlpha = 0.0; + highp vec3 curRgb = vec3(0, 0, 0); + + // Convert intersection to texture coords + + if (firstD <= tFar) { + highp vec3 firstTex = rayStart + rayDir * firstD; + firstTex = 0.5 * (firstTex + 1.0); + highp vec4 firstColor = texture3D(textureSampler, firstTex); + if (color8Bit != 0) + firstColor = colorIndex[int(firstColor.r * 255.0)]; + + if (firstColor.a > alphaThreshold) { + destColor.rgb = firstColor.rgb * firstColor.a; + totalAlpha = firstColor.a; + } + if (secondD <= tFar && totalAlpha < 1.0) { + highp vec3 secondTex = rayStart + rayDir * secondD; + secondTex = 0.5 * (secondTex + 1.0); + highp vec4 secondColor = texture3D(textureSampler, secondTex); + if (color8Bit != 0) + secondColor = colorIndex[int(secondColor.r * 255.0)]; + if (secondColor.a > alphaThreshold) { + curRgb = secondColor.rgb * secondColor.a * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += secondColor.a; + } + if (thirdD <= tFar && totalAlpha < 1.0) { + highp vec3 thirdTex = rayStart + rayDir * thirdD; + thirdTex = 0.5 * (thirdTex + 1.0); + highp vec4 thirdColor = texture3D(textureSampler, thirdTex); + if (color8Bit != 0) + thirdColor = colorIndex[int(thirdColor.r * 255.0)]; + if (thirdColor.a > alphaThreshold) { + curRgb = thirdColor.rgb * thirdColor.a * (1.0 - totalAlpha); + destColor.rgb += curRgb; + totalAlpha += thirdColor.a; + } + } + } + } + destColor.a = totalAlpha; + gl_FragColor = clamp(destColor, 0.0, 1.0); +} + diff --git a/src/datavisualization/engine/surface3drenderer.cpp b/src/datavisualization/engine/surface3drenderer.cpp index 1607f66a..17be3278 100644 --- a/src/datavisualization/engine/surface3drenderer.cpp +++ b/src/datavisualization/engine/surface3drenderer.cpp @@ -1196,9 +1196,11 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) glEnable(GL_CULL_FACE); glCullFace(GL_FRONT); - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthModelTexture, 0); @@ -1232,9 +1234,11 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) glDisableVertexAttribArray(m_depthShader->posAtt()); - Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, viewMatrix, - projectionViewMatrix, depthProjectionViewMatrix, - m_depthTexture, m_shadowQualityToShader); + Abstract3DRenderer::drawCustomItems(RenderingDepth, m_depthShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, + projectionViewMatrix, + depthProjectionViewMatrix, m_depthTexture, + m_shadowQualityToShader); // Disable drawing to depth framebuffer (= enable drawing to screen) glBindFramebuffer(GL_FRAMEBUFFER, defaultFboHandle); @@ -1287,7 +1291,9 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) } } m_surfaceGridShader->bind(); - Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingSelection, m_surfaceGridShader, + m_volumeTextureShader, m_volumeTextureSliceShader, + viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); drawLabels(true, activeCamera, viewMatrix, projectionMatrix); @@ -1858,7 +1864,8 @@ void Surface3DRenderer::drawScene(GLuint defaultFboHandle) } } - Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, viewMatrix, + Abstract3DRenderer::drawCustomItems(RenderingNormal, m_customItemShader, m_volumeTextureShader, + m_volumeTextureSliceShader, viewMatrix, projectionViewMatrix, depthProjectionViewMatrix, m_depthTexture, m_shadowQualityToShader); diff --git a/src/datavisualization/utils/shaderhelper.cpp b/src/datavisualization/utils/shaderhelper.cpp index 7fb237c6..3c3d93ac 100644 --- a/src/datavisualization/utils/shaderhelper.cpp +++ b/src/datavisualization/utils/shaderhelper.cpp @@ -93,6 +93,10 @@ void ShaderHelper::initialize() m_gradientMinUniform = m_program->uniformLocation("gradMin"); m_gradientHeightUniform = m_program->uniformLocation("gradHeight"); m_lightColorUniform = m_program->uniformLocation("lightColor"); + m_volumeSliceIndices = m_program->uniformLocation("volumeSliceIndices"); + m_colorIndex = m_program->uniformLocation("colorIndex"); + m_cameraPositionRelativeToModel = m_program->uniformLocation("cameraPositionRelativeToModel"); + m_color8Bit = m_program->uniformLocation("color8Bit"); m_initialized = true; } @@ -150,6 +154,11 @@ void ShaderHelper::setUniformValue(GLuint uniform, GLint value) m_program->setUniformValue(uniform, value); } +void ShaderHelper::setUniformValueArray(GLuint uniform, const QVector4D *values, int count) +{ + m_program->setUniformValueArray(uniform, values, count); +} + GLuint ShaderHelper::MVP() { if (!m_initialized) @@ -255,6 +264,34 @@ GLuint ShaderHelper::lightColor() return m_lightColorUniform; } +GLuint ShaderHelper::volumeSliceIndices() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_volumeSliceIndices; +} + +GLuint ShaderHelper::colorIndex() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_colorIndex; +} + +GLuint ShaderHelper::cameraPositionRelativeToModel() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_cameraPositionRelativeToModel; +} + +GLuint ShaderHelper::color8Bit() +{ + if (!m_initialized) + qFatal("Shader not initialized"); + return m_color8Bit; +} + GLuint ShaderHelper::posAtt() { if (!m_initialized) diff --git a/src/datavisualization/utils/shaderhelper_p.h b/src/datavisualization/utils/shaderhelper_p.h index fdef0dff..67e8a2f3 100644 --- a/src/datavisualization/utils/shaderhelper_p.h +++ b/src/datavisualization/utils/shaderhelper_p.h @@ -57,6 +57,7 @@ class ShaderHelper void setUniformValue(GLuint uniform, const QMatrix4x4 &value); void setUniformValue(GLuint uniform, GLfloat value); void setUniformValue(GLuint uniform, GLint value); + void setUniformValueArray(GLuint uniform, const QVector4D *values, int count); GLuint MVP(); GLuint view(); @@ -73,6 +74,10 @@ class ShaderHelper GLuint gradientMin(); GLuint gradientHeight(); GLuint lightColor(); + GLuint volumeSliceIndices(); + GLuint colorIndex(); + GLuint cameraPositionRelativeToModel(); + GLuint color8Bit(); GLuint posAtt(); GLuint uvAtt(); @@ -107,6 +112,10 @@ class ShaderHelper GLuint m_gradientMinUniform; GLuint m_gradientHeightUniform; GLuint m_lightColorUniform; + GLuint m_volumeSliceIndices; + GLuint m_colorIndex; + GLuint m_cameraPositionRelativeToModel; + GLuint m_color8Bit; GLboolean m_initialized; }; diff --git a/src/datavisualization/utils/texturehelper.cpp b/src/datavisualization/utils/texturehelper.cpp index 616c1981..5e21b5cd 100644 --- a/src/datavisualization/utils/texturehelper.cpp +++ b/src/datavisualization/utils/texturehelper.cpp @@ -21,11 +21,25 @@ #include #include +#include QT_BEGIN_NAMESPACE_DATAVISUALIZATION +// Defined in shaderhelper.cpp +extern void discardDebugMsgs(QtMsgType type, const QMessageLogContext &context, const QString &msg); + TextureHelper::TextureHelper() { +#if !defined(QT_OPENGL_ES_2) + // Discard warnings about deprecated functions + QtMessageHandler handler = qInstallMessageHandler(discardDebugMsgs); + + m_openGlFunctions_2_1 = new QOpenGLFunctions_2_1; + m_openGlFunctions_2_1->initializeOpenGLFunctions(); + + // Restore original message handler + qInstallMessageHandler(handler); +#endif initializeOpenGLFunctions(); } @@ -73,8 +87,54 @@ 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; +} + +#if !defined(QT_OPENGL_ES_2) +GLuint TextureHelper::create3DTexture(const QVector *data, int width, int height, int depth, + QImage::Format dataFormat) +{ + if (!width || !height || !depth) + return 0; + + glEnable(GL_TEXTURE_3D); + + GLuint textureId; + 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); + return textureId; } +#endif GLuint TextureHelper::createCubeMapTexture(const QImage &image, bool useTrilinearFiltering) { diff --git a/src/datavisualization/utils/texturehelper_p.h b/src/datavisualization/utils/texturehelper_p.h index c20f293c..c1dfe8c9 100644 --- a/src/datavisualization/utils/texturehelper_p.h +++ b/src/datavisualization/utils/texturehelper_p.h @@ -32,6 +32,10 @@ #include "datavisualizationglobal_p.h" #include #include +#if !defined(QT_OPENGL_ES_2) +// 3D Textures are not supported by ES set +# include +#endif QT_BEGIN_NAMESPACE_DATAVISUALIZATION @@ -44,6 +48,10 @@ 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); +#if !defined(QT_OPENGL_ES_2) + GLuint create3DTexture(const QVector *data, int width, int height, int depth, + QImage::Format dataFormat); +#endif 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); @@ -61,6 +69,9 @@ 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; +#endif friend class Bars3DRenderer; friend class Surface3DRenderer; friend class Scatter3DRenderer; diff --git a/tests/tests.pro b/tests/tests.pro index 8bbdc3f2..21036b59 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -16,7 +16,8 @@ SUBDIRS += barstest \ directional \ qmlmultiwindow \ itemmodeltest \ - qmlmultitest + qmlmultitest \ + volumetrictest #SUBDIRS += kinectsurface diff --git a/tests/volumetrictest/logo.png b/tests/volumetrictest/logo.png new file mode 100644 index 00000000..1e7ed4cf Binary files /dev/null and b/tests/volumetrictest/logo.png differ diff --git a/tests/volumetrictest/main.cpp b/tests/volumetrictest/main.cpp new file mode 100644 index 00000000..46edf576 --- /dev/null +++ b/tests/volumetrictest/main.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 "volumetrictest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + Q3DScatter *graph = new Q3DScatter(); + QWidget *container = QWidget::createWindowContainer(graph); + + QSize screenSize = graph->screen()->size(); + container->setMinimumSize(QSize(screenSize.width() / 2, screenSize.height() / 1.5)); + container->setMaximumSize(screenSize); + container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + container->setFocusPolicy(Qt::StrongFocus); + + QWidget *widget = new QWidget; + QHBoxLayout *hLayout = new QHBoxLayout(widget); + QVBoxLayout *vLayout = new QVBoxLayout(); + hLayout->addWidget(container, 1); + hLayout->addLayout(vLayout); + + widget->setWindowTitle(QStringLiteral("Volumetric TEST")); + + QCheckBox *sliceXCheckBox = new QCheckBox(widget); + sliceXCheckBox->setText(QStringLiteral("Slice volume on X axis")); + sliceXCheckBox->setChecked(false); + QCheckBox *sliceYCheckBox = new QCheckBox(widget); + sliceYCheckBox->setText(QStringLiteral("Slice volume on Y axis")); + sliceYCheckBox->setChecked(false); + QCheckBox *sliceZCheckBox = new QCheckBox(widget); + sliceZCheckBox->setText(QStringLiteral("Slice volume on Z axis")); + sliceZCheckBox->setChecked(false); + + QSlider *sliceXSlider = new QSlider(Qt::Horizontal, widget); + sliceXSlider->setMinimum(0); + sliceXSlider->setMaximum(1024); + sliceXSlider->setValue(512); + sliceXSlider->setEnabled(true); + QSlider *sliceYSlider = new QSlider(Qt::Horizontal, widget); + sliceYSlider->setMinimum(0); + sliceYSlider->setMaximum(1024); + sliceYSlider->setValue(512); + sliceYSlider->setEnabled(true); + QSlider *sliceZSlider = new QSlider(Qt::Horizontal, widget); + sliceZSlider->setMinimum(0); + sliceZSlider->setMaximum(1024); + sliceZSlider->setValue(512); + sliceZSlider->setEnabled(true); + + QLabel *fpsLabel = new QLabel(QStringLiteral("Fps: "), widget); + + vLayout->addWidget(fpsLabel); + vLayout->addWidget(sliceXCheckBox); + vLayout->addWidget(sliceXSlider); + vLayout->addWidget(sliceYCheckBox); + vLayout->addWidget(sliceYSlider); + vLayout->addWidget(sliceZCheckBox); + vLayout->addWidget(sliceZSlider, 1, Qt::AlignTop); + + VolumetricModifier *modifier = new VolumetricModifier(graph); + modifier->setFpsLabel(fpsLabel); + + QObject::connect(sliceXCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::sliceX); + QObject::connect(sliceYCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::sliceY); + QObject::connect(sliceZCheckBox, &QCheckBox::stateChanged, modifier, + &VolumetricModifier::sliceZ); + QObject::connect(sliceXSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustSliceX); + QObject::connect(sliceYSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustSliceY); + QObject::connect(sliceZSlider, &QSlider::valueChanged, modifier, + &VolumetricModifier::adjustSliceZ); + + widget->show(); + return app.exec(); +} diff --git a/tests/volumetrictest/volumetrictest.cpp b/tests/volumetrictest/volumetrictest.cpp new file mode 100644 index 00000000..04aad052 --- /dev/null +++ b/tests/volumetrictest/volumetrictest.cpp @@ -0,0 +1,282 @@ +/**************************************************************************** +** +** 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 "volumetrictest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace QtDataVisualization; + +const int imageCount = 512; + +VolumetricModifier::VolumetricModifier(Q3DScatter *scatter) + : m_graph(scatter), + m_volumeItem(0), + m_sliceIndexX(0), + m_sliceIndexY(0), + m_sliceIndexZ(0) +{ + m_graph->activeTheme()->setType(Q3DTheme::ThemeQt); + m_graph->setShadowQuality(QAbstract3DGraph::ShadowQualityNone); + m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); + m_graph->setOrthoProjection(true); + //m_graph->scene()->activeCamera()->setTarget(QVector3D(0.5f, 0.5f, 0.5f)); + + createVolume(); + createAnotherVolume(); + + m_graph->addCustomItem(m_volumeItem); + m_graph->addCustomItem(m_volumeItem2); + m_graph->setMeasureFps(true); + + QObject::connect(m_graph->scene()->activeCamera(), &Q3DCamera::zoomLevelChanged, this, + &VolumetricModifier::handleZoomLevelChange); + QObject::connect(m_graph, &QAbstract3DGraph::currentFpsChanged, this, + &VolumetricModifier::handleFpsChange); +} + +VolumetricModifier::~VolumetricModifier() +{ + delete m_graph; +} + +void VolumetricModifier::setFpsLabel(QLabel *fpsLabel) +{ + m_fpsLabel = fpsLabel; +} + +void VolumetricModifier::sliceX(int enabled) +{ + m_volumeItem->setSliceIndexX(enabled ? m_sliceIndexX : -1); + m_volumeItem2->setSliceIndexX(enabled ? m_sliceIndexX : -1); +} + +void VolumetricModifier::sliceY(int enabled) +{ + m_volumeItem->setSliceIndexY(enabled ? m_sliceIndexY : -1); + m_volumeItem2->setSliceIndexY(enabled ? m_sliceIndexY : -1); +} + +void VolumetricModifier::sliceZ(int enabled) +{ + m_volumeItem->setSliceIndexZ(enabled ? m_sliceIndexZ : -1); + m_volumeItem2->setSliceIndexZ(enabled ? m_sliceIndexZ : -1); +} + +void VolumetricModifier::adjustSliceX(int value) +{ + m_sliceIndexX = int(float(value) / (1024.0f / m_volumeItem->textureWidth())); + if (m_sliceIndexX == m_volumeItem->textureWidth()) + m_sliceIndexX--; + qDebug() << "m_sliceIndexX:" << m_sliceIndexX; + if (m_volumeItem->sliceIndexX() != -1) { + m_volumeItem->setSliceIndexX(m_sliceIndexX); + m_volumeItem2->setSliceIndexX(m_sliceIndexX); + } +} + +void VolumetricModifier::adjustSliceY(int value) +{ + m_sliceIndexY = int(float(value) / (1024.0f / m_volumeItem->textureHeight())); + if (m_sliceIndexY == m_volumeItem->textureHeight()) + m_sliceIndexY--; + qDebug() << "m_sliceIndexY:" << m_sliceIndexY; + if (m_volumeItem->sliceIndexY() != -1) { + m_volumeItem->setSliceIndexY(m_sliceIndexY); + m_volumeItem2->setSliceIndexY(m_sliceIndexY); + } +} + +void VolumetricModifier::adjustSliceZ(int value) +{ + m_sliceIndexZ = int(float(value) / (1024.0f / m_volumeItem->textureDepth())); + if (m_sliceIndexZ == m_volumeItem->textureDepth()) + m_sliceIndexZ--; + qDebug() << "m_sliceIndexZ:" << m_sliceIndexZ; + if (m_volumeItem->sliceIndexZ() != -1) { + m_volumeItem->setSliceIndexZ(m_sliceIndexZ); + m_volumeItem2->setSliceIndexZ(m_sliceIndexZ); + } +} + +void VolumetricModifier::handleZoomLevelChange() +{ + // Zooming inside volumetric object causes ugly clipping issues, so restrict zoom level a bit + if (m_graph->scene()->activeCamera()->zoomLevel() > 220) + m_graph->scene()->activeCamera()->setZoomLevel(220); +} + +void VolumetricModifier::handleFpsChange(qreal fps) +{ + const QString fpsFormat = QStringLiteral("Fps: %1"); + int fps10 = int(fps * 10.0); + m_fpsLabel->setText(fpsFormat.arg(QString::number(qreal(fps10) / 10.0))); +} + +void VolumetricModifier::createVolume() +{ + m_volumeItem = new QCustom3DVolume; + m_volumeItem->setTextureFormat(QImage::Format_ARGB32); + m_volumeItem->setRotation(QQuaternion::fromAxisAndAngle(1.0f, 1.0f, 0.0f, 10.0f)); + m_volumeItem->setPosition(QVector3D(-0.5f, 0.0f, 0.0f)); + + QImage logo; + logo.load(QStringLiteral(":/logo.png")); + qDebug() << "image dimensions:" << logo.width() << logo.height() + << logo.byteCount() << (logo.width() * logo.height()) + << logo.bytesPerLine(); + + QVector imageArray(imageCount); + for (int i = 0; i < imageCount; i++) { + QImage *newImage = new QImage(logo); + imageArray[i] = newImage; + } + + m_volumeItem->createTextureData(imageArray); + + m_sliceIndexX = m_volumeItem->textureWidth() / 2; + m_sliceIndexY = m_volumeItem->textureWidth() / 2; + m_sliceIndexZ = m_volumeItem->textureWidth() / 2; + + QVector colorTable = m_volumeItem->colorTable(); + colorTable.append(qRgba(255, 0, 0, 255)); + colorTable.append(qRgba(0, 255, 0, 255)); + colorTable.append(qRgba(0, 0, 255, 255)); + m_volumeItem->setColorTable(colorTable); + int redIndex = colorTable.size() - 3; + int greenIndex = colorTable.size() - 2; + int blueIndex = colorTable.size() - 1; + int width = m_volumeItem->textureDataWidth(); + int height = m_volumeItem->textureHeight(); + int depth = m_volumeItem->textureDepth(); + int frameSize = width * height; + qDebug() << width << height << depth << m_volumeItem->textureData()->size(); + m_volumeItem->setScaling(QVector3D(float(width) / float(depth) * 2.0f, + float(height) / float(depth) * 2.0f, + 2.0f)); + + uchar *data = m_volumeItem->textureData()->data(); + uchar *p = data; + + // Change one picture using subtexture replacement + QImage flipped = logo.mirrored(); + m_volumeItem->setSubTextureData(101, flipped); + + // Red first subtexture +// for (int j = 0; j < height; j++) { +// for (int i = 0; i < width; i++) { +// *p = redIndex; +// p++; +// } +// } + + // Red last subtexture + p = data + frameSize * (imageCount - 1); + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i++) { + *p = redIndex; + p++; + } + } + + // Blue x = 0 + p = data; + for (int k = 0; k < depth; k++) { + for (int j = 0; j < height; j++) { + *p = blueIndex; + p += width; + } + } + + // Blue x = max + p = data + width - 1; + for (int k = 0; k < depth; k++) { + for (int j = 0; j < height; j++) { + *p = blueIndex; + p += width; + } + } + + // Green y = 0 +// p = data; +// for (int k = 0; k < depth; k++) { +// for (int i = 0; i < width; i++) { +// *p = greenIndex; +// p++; +// } +// p += (frameSize - width); +// } + + // Green y = max + p = data + frameSize - width; + for (int k = 0; k < depth; k++) { + for (int i = 0; i < width; i++) { + *p = greenIndex; + p++; + } + p += (frameSize - width); + } +} + +void VolumetricModifier::createAnotherVolume() +{ + m_volumeItem2 = new QCustom3DVolume; + m_volumeItem2->setTextureFormat(QImage::Format_ARGB32); + m_volumeItem2->setPosition(QVector3D(0.5f, 0.0f, 0.0f)); + + QImage logo; + logo.load(QStringLiteral(":/logo.png")); + //logo = logo.convertToFormat(QImage::Format_ARGB8555_Premultiplied); + qDebug() << "second image dimensions:" << logo.width() << logo.height() + << logo.byteCount() << (logo.width() * logo.height()) + << logo.bytesPerLine(); + + logo.save("d:/qt/goobar.png"); + + QVector imageArray(imageCount); + for (int i = 0; i < imageCount; i++) { + QImage *newImage = new QImage(logo); + imageArray[i] = newImage; + } + + m_volumeItem2->createTextureData(imageArray); + + m_sliceIndexX = m_volumeItem2->textureWidth() / 2; + m_sliceIndexY = m_volumeItem2->textureWidth() / 2; + m_sliceIndexZ = m_volumeItem2->textureWidth() / 2; + + int width = m_volumeItem2->textureWidth(); + int height = m_volumeItem2->textureHeight(); + int depth = m_volumeItem2->textureDepth(); + qDebug() << width << height << depth << m_volumeItem2->textureData()->size(); + m_volumeItem2->setScaling(QVector3D(float(width) / float(depth) * 2.0f, + float(height) / float(depth) * 2.0f, + 2.0f)); + + // Change one picture using subtexture replacement + QImage flipped = logo.mirrored(); + m_volumeItem2->setSubTextureData(101, flipped); +} + diff --git a/tests/volumetrictest/volumetrictest.h b/tests/volumetrictest/volumetrictest.h new file mode 100644 index 00000000..677a1231 --- /dev/null +++ b/tests/volumetrictest/volumetrictest.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** 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 VOLUMETRICMODIFIER_H +#define VOLUMETRICMODIFIER_H + +#include +#include + +class QLabel; + +using namespace QtDataVisualization; + +class VolumetricModifier : public QObject +{ + Q_OBJECT +public: + explicit VolumetricModifier(Q3DScatter *scatter); + ~VolumetricModifier(); + + void setFpsLabel(QLabel *fpsLabel); + +public slots: + void sliceX(int enabled); + void sliceY(int enabled); + void sliceZ(int enabled); + void adjustSliceX(int value); + void adjustSliceY(int value); + void adjustSliceZ(int value); + void handleZoomLevelChange(); + void handleFpsChange(qreal fps); + +private: + void createVolume(); + void createAnotherVolume(); + + Q3DScatter *m_graph; + QCustom3DVolume *m_volumeItem; + QCustom3DVolume *m_volumeItem2; + int m_sliceIndexX; + int m_sliceIndexY; + int m_sliceIndexZ; + QLabel *m_fpsLabel; +}; + +#endif diff --git a/tests/volumetrictest/volumetrictest.pro b/tests/volumetrictest/volumetrictest.pro new file mode 100644 index 00000000..137e5bab --- /dev/null +++ b/tests/volumetrictest/volumetrictest.pro @@ -0,0 +1,18 @@ +android|ios { + error( "This example is not supported for android or ios." ) +} + +!include( ../tests.pri ) { + error( "Couldn't find the tests.pri file!" ) +} + +SOURCES += main.cpp volumetrictest.cpp +HEADERS += volumetrictest.h + +QT += widgets + +OTHER_FILES += doc/src/* \ + doc/images/* + +RESOURCES += \ + volumetrictest.qrc diff --git a/tests/volumetrictest/volumetrictest.qrc b/tests/volumetrictest/volumetrictest.qrc new file mode 100644 index 00000000..90517ff1 --- /dev/null +++ b/tests/volumetrictest/volumetrictest.qrc @@ -0,0 +1,5 @@ + + + logo.png + + -- cgit v1.2.3