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ää --- 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 + 7 files changed, 473 insertions(+), 1 deletion(-) 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 (limited to 'tests') 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